mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-06 01:54:11 +08:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f469e225b | ||
|
|
5dca69fbec | ||
|
|
ac626e5895 | ||
|
|
cb78758839 | ||
|
|
844a2412b2 | ||
|
|
650d877430 | ||
|
|
f459061ad5 | ||
|
|
a6f9701679 | ||
|
|
26a325efff | ||
|
|
0a96ee16a8 | ||
|
|
43c962b48b | ||
|
|
724545ebd6 | ||
|
|
a9a2004d4a | ||
|
|
5b14c8a832 | ||
|
|
e2c5a514cb | ||
|
|
296761a34e | ||
|
|
1d3436d51b | ||
|
|
60bb11c315 | ||
|
|
72fe6195af | ||
|
|
04fb3b7ee3 | ||
|
|
942fca7ad8 | ||
|
|
39df995e37 | ||
|
|
efaa8b6620 | ||
|
|
35bd0aa8f6 | ||
|
|
0f9adc59f9 | ||
|
|
c43a72ef46 | ||
|
|
7a61119c55 | ||
|
|
d620eac621 | ||
|
|
1dbffbee2d | ||
|
|
c67817f46e | ||
|
|
d654419423 | ||
|
|
1e2240dbe9 | ||
|
|
b3778ef48c | ||
|
|
a16cf5c8d3 | ||
|
|
d82bf5a823 | ||
|
|
132eec900c |
@@ -16,11 +16,9 @@ description: |
|
||||
color: yellow
|
||||
---
|
||||
|
||||
You are a pure execution agent specialized in creating actionable implementation plans. You receive requirements and control flags from the command layer and execute planning tasks without complex decision-making logic.
|
||||
|
||||
## Overview
|
||||
|
||||
**Agent Role**: Transform user requirements and brainstorming artifacts into structured, executable implementation plans with quantified deliverables and measurable acceptance criteria.
|
||||
**Agent Role**: Pure execution agent that transforms user requirements and brainstorming artifacts into structured, executable implementation plans with quantified deliverables and measurable acceptance criteria. Receives requirements and control flags from the command layer and executes planning tasks without complex decision-making logic.
|
||||
|
||||
**Core Capabilities**:
|
||||
- Load and synthesize context from multiple sources (session metadata, context packages, brainstorming artifacts)
|
||||
@@ -33,7 +31,7 @@ You are a pure execution agent specialized in creating actionable implementation
|
||||
|
||||
---
|
||||
|
||||
## 1. Execution Process
|
||||
## 1. Input & Execution
|
||||
|
||||
### 1.1 Input Processing
|
||||
|
||||
@@ -43,7 +41,6 @@ You are a pure execution agent specialized in creating actionable implementation
|
||||
- `context_package_path`: Context package with brainstorming artifacts catalog
|
||||
- **Metadata**: Simple values
|
||||
- `session_id`: Workflow session identifier (WFS-[topic])
|
||||
- `execution_mode`: agent-mode | cli-execute-mode
|
||||
- `mcp_capabilities`: Available MCP tools (exa_code, exa_web, code_index)
|
||||
|
||||
**Legacy Support** (backward compatibility):
|
||||
@@ -51,7 +48,7 @@ You are a pure execution agent specialized in creating actionable implementation
|
||||
- **Control flags**: DEEP_ANALYSIS_REQUIRED, etc.
|
||||
- **Task requirements**: Direct task description
|
||||
|
||||
### 1.2 Two-Phase Execution Flow
|
||||
### 1.2 Execution Flow
|
||||
|
||||
#### Phase 1: Context Loading & Assembly
|
||||
|
||||
@@ -89,6 +86,27 @@ You are a pure execution agent specialized in creating actionable implementation
|
||||
6. Assess task complexity (simple/medium/complex)
|
||||
```
|
||||
|
||||
**MCP Integration** (when `mcp_capabilities` available):
|
||||
|
||||
```javascript
|
||||
// Exa Code Context (mcp_capabilities.exa_code = true)
|
||||
mcp__exa__get_code_context_exa(
|
||||
query="TypeScript OAuth2 JWT authentication patterns",
|
||||
tokensNum="dynamic"
|
||||
)
|
||||
|
||||
// Integration in flow_control.pre_analysis
|
||||
{
|
||||
"step": "local_codebase_exploration",
|
||||
"action": "Explore codebase structure",
|
||||
"commands": [
|
||||
"bash(rg '^(function|class|interface).*[task_keyword]' --type ts -n --max-count 15)",
|
||||
"bash(find . -name '*[task_keyword]*' -type f | grep -v node_modules | head -10)"
|
||||
],
|
||||
"output_to": "codebase_structure"
|
||||
}
|
||||
```
|
||||
|
||||
**Context Package Structure** (fields defined by context-search-agent):
|
||||
|
||||
**Always Present**:
|
||||
@@ -170,30 +188,6 @@ if (contextPackage.brainstorm_artifacts?.role_analyses?.length > 0) {
|
||||
5. Update session state for execution readiness
|
||||
```
|
||||
|
||||
### 1.3 MCP Integration Guidelines
|
||||
|
||||
**Exa Code Context** (`mcp_capabilities.exa_code = true`):
|
||||
```javascript
|
||||
// Get best practices and examples
|
||||
mcp__exa__get_code_context_exa(
|
||||
query="TypeScript OAuth2 JWT authentication patterns",
|
||||
tokensNum="dynamic"
|
||||
)
|
||||
```
|
||||
|
||||
**Integration in flow_control.pre_analysis**:
|
||||
```json
|
||||
{
|
||||
"step": "local_codebase_exploration",
|
||||
"action": "Explore codebase structure",
|
||||
"commands": [
|
||||
"bash(rg '^(function|class|interface).*[task_keyword]' --type ts -n --max-count 15)",
|
||||
"bash(find . -name '*[task_keyword]*' -type f | grep -v node_modules | head -10)"
|
||||
],
|
||||
"output_to": "codebase_structure"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Output Specifications
|
||||
@@ -214,7 +208,11 @@ Generate individual `.task/IMPL-*.json` files with the following structure:
|
||||
```
|
||||
|
||||
**Field Descriptions**:
|
||||
- `id`: Task identifier (format: `IMPL-N`)
|
||||
- `id`: Task identifier
|
||||
- Single module format: `IMPL-N` (e.g., IMPL-001, IMPL-002)
|
||||
- Multi-module format: `IMPL-{prefix}{seq}` (e.g., IMPL-A1, IMPL-B1, IMPL-C1)
|
||||
- Prefix: A, B, C... (assigned by module detection order)
|
||||
- Sequence: 1, 2, 3... (per-module increment)
|
||||
- `title`: Descriptive task name summarizing the work
|
||||
- `status`: Task state - `pending` (not started), `active` (in progress), `completed` (done), `blocked` (waiting on dependencies)
|
||||
- `context_package_path`: Path to smart context package containing project structure, dependencies, and brainstorming artifacts catalog
|
||||
@@ -226,7 +224,8 @@ Generate individual `.task/IMPL-*.json` files with the following structure:
|
||||
"meta": {
|
||||
"type": "feature|bugfix|refactor|test-gen|test-fix|docs",
|
||||
"agent": "@code-developer|@action-planning-agent|@test-fix-agent|@universal-executor",
|
||||
"execution_group": "parallel-abc123|null"
|
||||
"execution_group": "parallel-abc123|null",
|
||||
"module": "frontend|backend|shared|null"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -235,6 +234,7 @@ Generate individual `.task/IMPL-*.json` files with the following structure:
|
||||
- `type`: Task category - `feature` (new functionality), `bugfix` (fix defects), `refactor` (restructure code), `test-gen` (generate tests), `test-fix` (fix failing tests), `docs` (documentation)
|
||||
- `agent`: Assigned agent for execution
|
||||
- `execution_group`: Parallelization group ID (tasks with same ID can run concurrently) or `null` for sequential tasks
|
||||
- `module`: Module identifier for multi-module projects (e.g., `frontend`, `backend`, `shared`) or `null` for single-module
|
||||
|
||||
**Test Task Extensions** (for type="test-gen" or type="test-fix"):
|
||||
|
||||
@@ -244,8 +244,7 @@ Generate individual `.task/IMPL-*.json` files with the following structure:
|
||||
"type": "test-gen|test-fix",
|
||||
"agent": "@code-developer|@test-fix-agent",
|
||||
"test_framework": "jest|vitest|pytest|junit|mocha",
|
||||
"coverage_target": "80%",
|
||||
"use_codex": true|false
|
||||
"coverage_target": "80%"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -253,7 +252,8 @@ Generate individual `.task/IMPL-*.json` files with the following structure:
|
||||
**Test-Specific Fields**:
|
||||
- `test_framework`: Existing test framework from project (required for test tasks)
|
||||
- `coverage_target`: Target code coverage percentage (optional)
|
||||
- `use_codex`: Whether to use Codex for automated fixes in test-fix tasks (optional, default: false)
|
||||
|
||||
**Note**: CLI tool usage for test-fix tasks is now controlled via `flow_control.implementation_approach` steps with `command` fields, not via `meta.use_codex`.
|
||||
|
||||
#### Context Object
|
||||
|
||||
@@ -485,15 +485,31 @@ The `implementation_approach` supports **two execution modes** based on the pres
|
||||
- `bash(codex --full-auto exec '[task]' resume --last --skip-git-repo-check -s danger-full-access)` (multi-step)
|
||||
- `bash(cd [path] && gemini -p '[prompt]' --approval-mode yolo)` (write mode)
|
||||
|
||||
**Mode Selection Strategy**:
|
||||
- **Default to agent execution** for most tasks
|
||||
- **Use CLI mode** when:
|
||||
- User explicitly requests CLI tool (codex/gemini/qwen)
|
||||
- Task requires multi-step autonomous reasoning beyond agent capability
|
||||
- Complex refactoring needs specialized tool analysis
|
||||
- Building on previous CLI execution context (use `resume --last`)
|
||||
**Semantic CLI Tool Selection**:
|
||||
|
||||
**Key Principle**: The `command` field is **optional**. Agent must decide based on task complexity and user preference.
|
||||
Agent determines CLI tool usage per-step based on user semantics and task nature.
|
||||
|
||||
**Source**: Scan `metadata.task_description` from context-package.json for CLI tool preferences.
|
||||
|
||||
**User Semantic Triggers** (patterns to detect in task_description):
|
||||
- "use Codex/codex" → Add `command` field with Codex CLI
|
||||
- "use Gemini/gemini" → Add `command` field with Gemini CLI
|
||||
- "use Qwen/qwen" → Add `command` field with Qwen CLI
|
||||
- "CLI execution" / "automated" → Infer appropriate CLI tool
|
||||
|
||||
**Task-Based Selection** (when no explicit user preference):
|
||||
- **Implementation/coding**: Codex preferred for autonomous development
|
||||
- **Analysis/exploration**: Gemini preferred for large context analysis
|
||||
- **Documentation**: Gemini/Qwen with write mode (`--approval-mode yolo`)
|
||||
- **Testing**: Depends on complexity - simple=agent, complex=Codex
|
||||
|
||||
**Default Behavior**: Agent always executes the workflow. CLI commands are embedded in `implementation_approach` steps:
|
||||
- Agent orchestrates task execution
|
||||
- When step has `command` field, agent executes it via Bash
|
||||
- When step has no `command` field, agent implements directly
|
||||
- This maintains agent control while leveraging CLI tool power
|
||||
|
||||
**Key Principle**: The `command` field is **optional**. Agent decides based on user semantics and task complexity.
|
||||
|
||||
**Examples**:
|
||||
|
||||
@@ -589,10 +605,42 @@ The `implementation_approach` supports **two execution modes** based on the pres
|
||||
- Analysis results (technical approach, architecture decisions)
|
||||
- Brainstorming artifacts (role analyses, guidance specifications)
|
||||
|
||||
**Multi-Module Format** (when modules detected):
|
||||
|
||||
When multiple modules are detected (frontend/backend, etc.), organize IMPL_PLAN.md by module:
|
||||
|
||||
```markdown
|
||||
# Implementation Plan
|
||||
|
||||
## Module A: Frontend (N tasks)
|
||||
### IMPL-A1: [Task Title]
|
||||
[Task details...]
|
||||
|
||||
### IMPL-A2: [Task Title]
|
||||
[Task details...]
|
||||
|
||||
## Module B: Backend (N tasks)
|
||||
### IMPL-B1: [Task Title]
|
||||
[Task details...]
|
||||
|
||||
### IMPL-B2: [Task Title]
|
||||
[Task details...]
|
||||
|
||||
## Cross-Module Dependencies
|
||||
- IMPL-A1 → IMPL-B1 (Frontend depends on Backend API)
|
||||
- IMPL-A2 → IMPL-B2 (UI state depends on Backend service)
|
||||
```
|
||||
|
||||
**Cross-Module Dependency Notation**:
|
||||
- During parallel planning, use `CROSS::{module}::{pattern}` format
|
||||
- Example: `depends_on: ["CROSS::B::api-endpoint"]`
|
||||
- Integration phase resolves to actual task IDs: `CROSS::B::api → IMPL-B1`
|
||||
|
||||
### 2.3 TODO_LIST.md Structure
|
||||
|
||||
Generate at `.workflow/active/{session_id}/TODO_LIST.md`:
|
||||
|
||||
**Single Module Format**:
|
||||
```markdown
|
||||
# Tasks: {Session Topic}
|
||||
|
||||
@@ -606,30 +654,54 @@ Generate at `.workflow/active/{session_id}/TODO_LIST.md`:
|
||||
- `- [x]` = Completed task
|
||||
```
|
||||
|
||||
**Multi-Module Format** (hierarchical by module):
|
||||
```markdown
|
||||
# Tasks: {Session Topic}
|
||||
|
||||
## Module A (Frontend)
|
||||
- [ ] **IMPL-A1**: [Task Title] → [📋](./.task/IMPL-A1.json)
|
||||
- [ ] **IMPL-A2**: [Task Title] → [📋](./.task/IMPL-A2.json)
|
||||
|
||||
## Module B (Backend)
|
||||
- [ ] **IMPL-B1**: [Task Title] → [📋](./.task/IMPL-B1.json)
|
||||
- [ ] **IMPL-B2**: [Task Title] → [📋](./.task/IMPL-B2.json)
|
||||
|
||||
## Cross-Module Dependencies
|
||||
- IMPL-A1 → IMPL-B1 (Frontend depends on Backend API)
|
||||
|
||||
## Status Legend
|
||||
- `- [ ]` = Pending task
|
||||
- `- [x]` = Completed task
|
||||
```
|
||||
|
||||
**Linking Rules**:
|
||||
- Todo items → task JSON: `[📋](./.task/IMPL-XXX.json)`
|
||||
- Completed tasks → summaries: `[✅](./.summaries/IMPL-XXX-summary.md)`
|
||||
- Consistent ID schemes: IMPL-XXX
|
||||
- Consistent ID schemes: `IMPL-N` (single) or `IMPL-{prefix}{seq}` (multi-module)
|
||||
|
||||
### 2.4 Complexity-Based Structure Selection
|
||||
### 2.4 Complexity & Structure Selection
|
||||
|
||||
Use `analysis_results.complexity` or task count to determine structure:
|
||||
|
||||
**Simple Tasks** (≤5 tasks):
|
||||
- Flat structure: IMPL_PLAN.md + TODO_LIST.md + task JSONs
|
||||
- All tasks at same level
|
||||
**Single Module Mode**:
|
||||
- **Simple Tasks** (≤5 tasks): Flat structure
|
||||
- **Medium Tasks** (6-12 tasks): Flat structure
|
||||
- **Complex Tasks** (>12 tasks): Re-scope required (maximum 12 tasks hard limit)
|
||||
|
||||
**Medium Tasks** (6-12 tasks):
|
||||
- Flat structure: IMPL_PLAN.md + TODO_LIST.md + task JSONs
|
||||
- All tasks at same level
|
||||
**Multi-Module Mode** (N+1 parallel planning):
|
||||
- **Per-module limit**: ≤9 tasks per module
|
||||
- **Total limit**: Sum of all module tasks ≤27 (3 modules × 9 tasks)
|
||||
- **Task ID format**: `IMPL-{prefix}{seq}` (e.g., IMPL-A1, IMPL-B1)
|
||||
- **Structure**: Hierarchical by module in IMPL_PLAN.md and TODO_LIST.md
|
||||
|
||||
**Complex Tasks** (>12 tasks):
|
||||
- **Re-scope required**: Maximum 12 tasks hard limit
|
||||
- If analysis_results contains >12 tasks, consolidate or request re-scoping
|
||||
**Multi-Module Detection Triggers**:
|
||||
- Explicit frontend/backend separation (`src/frontend`, `src/backend`)
|
||||
- Monorepo structure (`packages/*`, `apps/*`)
|
||||
- Context-package dependency clustering (2+ distinct module groups)
|
||||
|
||||
---
|
||||
|
||||
## 3. Quality & Standards
|
||||
## 3. Quality Standards
|
||||
|
||||
### 3.1 Quantification Requirements (MANDATORY)
|
||||
|
||||
@@ -655,47 +727,46 @@ Use `analysis_results.complexity` or task count to determine structure:
|
||||
- [ ] Each implementation step has its own acceptance criteria
|
||||
|
||||
**Examples**:
|
||||
- ✅ GOOD: `"Implement 5 commands: [cmd1, cmd2, cmd3, cmd4, cmd5]"`
|
||||
- ❌ BAD: `"Implement new commands"`
|
||||
- ✅ GOOD: `"5 files created: verify by ls .claude/commands/*.md | wc -l = 5"`
|
||||
- ❌ BAD: `"All commands implemented successfully"`
|
||||
- GOOD: `"Implement 5 commands: [cmd1, cmd2, cmd3, cmd4, cmd5]"`
|
||||
- BAD: `"Implement new commands"`
|
||||
- GOOD: `"5 files created: verify by ls .claude/commands/*.md | wc -l = 5"`
|
||||
- BAD: `"All commands implemented successfully"`
|
||||
|
||||
### 3.2 Planning Principles
|
||||
### 3.2 Planning & Organization Standards
|
||||
|
||||
**Planning Principles**:
|
||||
- Each stage produces working, testable code
|
||||
- Clear success criteria for each deliverable
|
||||
- Dependencies clearly identified between stages
|
||||
- Incremental progress over big bangs
|
||||
|
||||
### 3.3 File Organization
|
||||
|
||||
**File Organization**:
|
||||
- Session naming: `WFS-[topic-slug]`
|
||||
- Task IDs: IMPL-XXX (flat structure only)
|
||||
- Directory structure: flat task organization
|
||||
|
||||
### 3.4 Document Standards
|
||||
- Task IDs:
|
||||
- Single module: `IMPL-N` (e.g., IMPL-001, IMPL-002)
|
||||
- Multi-module: `IMPL-{prefix}{seq}` (e.g., IMPL-A1, IMPL-B1)
|
||||
- Directory structure: flat task organization (all tasks in `.task/`)
|
||||
|
||||
**Document Standards**:
|
||||
- Proper linking between documents
|
||||
- Consistent navigation and references
|
||||
|
||||
---
|
||||
|
||||
## 4. Key Reminders
|
||||
### 3.3 Guidelines Checklist
|
||||
|
||||
**ALWAYS:**
|
||||
- **Apply Quantification Requirements**: All requirements, acceptance criteria, and modification points MUST include explicit counts and enumerations
|
||||
- **Load IMPL_PLAN template**: Read(~/.claude/workflows/cli-templates/prompts/workflow/impl-plan-template.txt) before generating IMPL_PLAN.md
|
||||
- **Use provided context package**: Extract all information from structured context
|
||||
- **Respect memory-first rule**: Use provided content (already loaded from memory/file)
|
||||
- **Follow 6-field schema**: All task JSONs must have id, title, status, context_package_path, meta, context, flow_control
|
||||
- **Map artifacts**: Use artifacts_inventory to populate task.context.artifacts array
|
||||
- **Add MCP integration**: Include MCP tool steps in flow_control.pre_analysis when capabilities available
|
||||
- **Validate task count**: Maximum 12 tasks hard limit, request re-scope if exceeded
|
||||
- **Use session paths**: Construct all paths using provided session_id
|
||||
- **Link documents properly**: Use correct linking format (📋 for JSON, ✅ for summaries)
|
||||
- **Run validation checklist**: Verify all quantification requirements before finalizing task JSONs
|
||||
- **Apply 举一反三 principle**: Adapt pre-analysis patterns to task-specific needs dynamically
|
||||
- **Follow template validation**: Complete IMPL_PLAN.md template validation checklist before finalization
|
||||
- Apply Quantification Requirements to all requirements, acceptance criteria, and modification points
|
||||
- Load IMPL_PLAN template: `Read(~/.claude/workflows/cli-templates/prompts/workflow/impl-plan-template.txt)` before generating IMPL_PLAN.md
|
||||
- Use provided context package: Extract all information from structured context
|
||||
- Respect memory-first rule: Use provided content (already loaded from memory/file)
|
||||
- Follow 6-field schema: All task JSONs must have id, title, status, context_package_path, meta, context, flow_control
|
||||
- Map artifacts: Use artifacts_inventory to populate task.context.artifacts array
|
||||
- Add MCP integration: Include MCP tool steps in flow_control.pre_analysis when capabilities available
|
||||
- Validate task count: Maximum 12 tasks hard limit, request re-scope if exceeded
|
||||
- Use session paths: Construct all paths using provided session_id
|
||||
- Link documents properly: Use correct linking format (📋 for JSON, ✅ for summaries)
|
||||
- Run validation checklist: Verify all quantification requirements before finalizing task JSONs
|
||||
- Apply 举一反三 principle: Adapt pre-analysis patterns to task-specific needs dynamically
|
||||
- Follow template validation: Complete IMPL_PLAN.md template validation checklist before finalization
|
||||
|
||||
**NEVER:**
|
||||
- Load files directly (use provided context package instead)
|
||||
|
||||
@@ -66,8 +66,7 @@ You are a specialized execution agent that bridges CLI analysis tools with task
|
||||
"task_config": {
|
||||
"agent": "@test-fix-agent",
|
||||
"type": "test-fix-iteration",
|
||||
"max_iterations": 5,
|
||||
"use_codex": false
|
||||
"max_iterations": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -263,7 +262,6 @@ function extractModificationPoints() {
|
||||
"analysis_report": ".process/iteration-{iteration}-analysis.md",
|
||||
"cli_output": ".process/iteration-{iteration}-cli-output.txt",
|
||||
"max_iterations": "{task_config.max_iterations}",
|
||||
"use_codex": "{task_config.use_codex}",
|
||||
"parent_task": "{parent_task_id}",
|
||||
"created_by": "@cli-planning-agent",
|
||||
"created_at": "{timestamp}"
|
||||
|
||||
@@ -24,8 +24,6 @@ You are a code execution specialist focused on implementing high-quality, produc
|
||||
- **Context-driven** - Use provided context and existing code patterns
|
||||
- **Quality over speed** - Write boring, reliable code that works
|
||||
|
||||
|
||||
|
||||
## Execution Process
|
||||
|
||||
### 1. Context Assessment
|
||||
|
||||
@@ -119,17 +119,6 @@ This agent processes **simplified inline [FLOW_CONTROL]** format from brainstorm
|
||||
- No dependency management
|
||||
- Used for temporary context preparation
|
||||
|
||||
### NOT Handled by This Agent
|
||||
|
||||
**JSON format** (used by code-developer, test-fix-agent):
|
||||
```json
|
||||
"flow_control": {
|
||||
"pre_analysis": [...],
|
||||
"implementation_approach": [...]
|
||||
}
|
||||
```
|
||||
|
||||
This complete JSON format is stored in `.task/IMPL-*.json` files and handled by implementation agents, not conceptual-planning-agent.
|
||||
|
||||
### Role-Specific Analysis Dimensions
|
||||
|
||||
@@ -146,14 +135,14 @@ This complete JSON format is stored in `.task/IMPL-*.json` files and handled by
|
||||
|
||||
### Output Integration
|
||||
|
||||
**Gemini Analysis Integration**: Pattern-based analysis results are integrated into the single role's output:
|
||||
- Enhanced `analysis.md` with codebase insights and architectural patterns
|
||||
**Gemini Analysis Integration**: Pattern-based analysis results are integrated into role output documents:
|
||||
- Enhanced analysis documents with codebase insights and architectural patterns
|
||||
- Role-specific technical recommendations based on existing conventions
|
||||
- Pattern-based best practices from actual code examination
|
||||
- Realistic feasibility assessments based on current implementation
|
||||
|
||||
**Codex Analysis Integration**: Autonomous analysis results provide comprehensive insights:
|
||||
- Enhanced `analysis.md` with autonomous development recommendations
|
||||
- Enhanced analysis documents with autonomous development recommendations
|
||||
- Role-specific strategy based on intelligent system understanding
|
||||
- Autonomous development approaches and implementation guidance
|
||||
- Self-guided optimization and integration recommendations
|
||||
@@ -229,26 +218,23 @@ Generate documents according to loaded role template specifications:
|
||||
|
||||
**Output Location**: `.workflow/WFS-[session]/.brainstorming/[assigned-role]/`
|
||||
|
||||
**Required Files**:
|
||||
- **analysis.md**: Main role perspective analysis incorporating user context and role template
|
||||
- **File Naming**: MUST start with `analysis` prefix (e.g., `analysis.md`, `analysis-1.md`, `analysis-2.md`)
|
||||
**Output Files**:
|
||||
- **analysis.md**: Index document with overview (optionally with `@` references to sub-documents)
|
||||
- **FORBIDDEN**: Never create `recommendations.md` or any file not starting with `analysis` prefix
|
||||
- **Auto-split if large**: If content >800 lines, split to `analysis-1.md`, `analysis-2.md` (max 3 files: analysis.md, analysis-1.md, analysis-2.md)
|
||||
- **Content**: Includes both analysis AND recommendations sections within analysis files
|
||||
- **[role-deliverables]/**: Directory for specialized role outputs as defined in planning role template (optional)
|
||||
- **analysis-{slug}.md**: Section content documents (slug from section heading: lowercase, hyphens)
|
||||
- Maximum 5 sub-documents (merge related sections if needed)
|
||||
- **Content**: Analysis AND recommendations sections
|
||||
|
||||
**File Structure Example**:
|
||||
```
|
||||
.workflow/WFS-[session]/.brainstorming/system-architect/
|
||||
├── analysis.md # Main system architecture analysis with recommendations
|
||||
├── analysis-1.md # (Optional) Continuation if content >800 lines
|
||||
└── deliverables/ # (Optional) Additional role-specific outputs
|
||||
├── technical-architecture.md # System design specifications
|
||||
├── technology-stack.md # Technology selection rationale
|
||||
└── scalability-plan.md # Scaling strategy
|
||||
├── analysis.md # Index with overview + @references
|
||||
├── analysis-architecture-assessment.md # Section content
|
||||
├── analysis-technology-evaluation.md # Section content
|
||||
├── analysis-integration-strategy.md # Section content
|
||||
└── analysis-recommendations.md # Section content (max 5 sub-docs total)
|
||||
|
||||
NOTE: ALL brainstorming output files MUST start with 'analysis' prefix
|
||||
FORBIDDEN: recommendations.md, recommendations-*.md, or any non-'analysis' prefixed files
|
||||
NOTE: ALL files MUST start with 'analysis' prefix. Max 5 sub-documents.
|
||||
```
|
||||
|
||||
## Role-Specific Planning Process
|
||||
@@ -268,14 +254,10 @@ FORBIDDEN: recommendations.md, recommendations-*.md, or any non-'analysis' prefi
|
||||
- **Validate Against Template**: Ensure analysis meets role template requirements and standards
|
||||
|
||||
### 3. Brainstorming Documentation Phase
|
||||
- **Create analysis.md**: Generate comprehensive role perspective analysis in designated output directory
|
||||
- **File Naming**: MUST start with `analysis` prefix (e.g., `analysis.md`, `analysis-1.md`, `analysis-2.md`)
|
||||
- **FORBIDDEN**: Never create `recommendations.md` or any file not starting with `analysis` prefix
|
||||
- **Content**: Include both analysis AND recommendations sections within analysis files
|
||||
- **Auto-split**: If content >800 lines, split to `analysis-1.md`, `analysis-2.md` (max 3 files total)
|
||||
- **Generate Role Deliverables**: Create specialized outputs as defined in planning role template (optional)
|
||||
- **Create analysis.md**: Main document with overview (optionally with `@` references)
|
||||
- **Create sub-documents**: `analysis-{slug}.md` for major sections (max 5)
|
||||
- **Validate Output Structure**: Ensure all files saved to correct `.brainstorming/[role]/` directory
|
||||
- **Naming Validation**: Verify NO files with `recommendations` prefix exist
|
||||
- **Naming Validation**: Verify ALL files start with `analysis` prefix
|
||||
- **Quality Review**: Ensure outputs meet role template standards and user requirements
|
||||
|
||||
## Role-Specific Analysis Framework
|
||||
@@ -324,5 +306,3 @@ When analysis is complete, ensure:
|
||||
- **Relevance**: Directly addresses user's specified requirements
|
||||
- **Actionability**: Provides concrete next steps and recommendations
|
||||
|
||||
### Windows Path Format Guidelines
|
||||
- **Quick Ref**: `C:\Users` → MCP: `C:\\Users` | Bash: `/c/Users` or `C:/Users`
|
||||
|
||||
@@ -449,7 +449,12 @@ Calculate risk level based on:
|
||||
{
|
||||
"path": "system-architect/analysis.md",
|
||||
"type": "primary",
|
||||
"content": "# System Architecture Analysis\n\n## Overview\n..."
|
||||
"content": "# System Architecture Analysis\n\n## Overview\n@analysis-architecture.md\n@analysis-recommendations.md"
|
||||
},
|
||||
{
|
||||
"path": "system-architect/analysis-architecture.md",
|
||||
"type": "supplementary",
|
||||
"content": "# Architecture Assessment\n\n..."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -142,9 +142,9 @@ run_test_layer "L1-unit" "$UNIT_CMD"
|
||||
|
||||
### 3. Failure Diagnosis & Fixing Loop
|
||||
|
||||
**Execution Modes**:
|
||||
**Execution Modes** (determined by `flow_control.implementation_approach`):
|
||||
|
||||
**A. Manual Mode (Default, meta.use_codex=false)**:
|
||||
**A. Agent Mode (Default, no `command` field in steps)**:
|
||||
```
|
||||
WHILE tests are failing AND iterations < max_iterations:
|
||||
1. Use Gemini to diagnose failure (bug-fix template)
|
||||
@@ -155,17 +155,17 @@ WHILE tests are failing AND iterations < max_iterations:
|
||||
END WHILE
|
||||
```
|
||||
|
||||
**B. Codex Mode (meta.use_codex=true)**:
|
||||
**B. CLI Mode (`command` field present in implementation_approach steps)**:
|
||||
```
|
||||
WHILE tests are failing AND iterations < max_iterations:
|
||||
1. Use Gemini to diagnose failure (bug-fix template)
|
||||
2. Use Codex to apply fixes automatically with resume mechanism
|
||||
2. Execute `command` field (e.g., Codex) to apply fixes automatically
|
||||
3. Re-run test suite
|
||||
4. Verify fix doesn't break other tests
|
||||
END WHILE
|
||||
```
|
||||
|
||||
**Codex Resume in Test-Fix Cycle** (when `meta.use_codex=true`):
|
||||
**Codex Resume in Test-Fix Cycle** (when step has `command` with Codex):
|
||||
- First iteration: Start new Codex session with full context
|
||||
- Subsequent iterations: Use `resume --last` to maintain fix history and apply consistent strategies
|
||||
|
||||
|
||||
@@ -64,12 +64,17 @@ Lightweight planner that analyzes project structure, decomposes documentation wo
|
||||
```bash
|
||||
# Get target path, project name, and root
|
||||
bash(pwd && basename "$(pwd)" && git rev-parse --show-toplevel 2>/dev/null || pwd && date +%Y%m%d-%H%M%S)
|
||||
```
|
||||
|
||||
# Create session directories (replace timestamp)
|
||||
bash(mkdir -p .workflow/active/WFS-docs-{timestamp}/.{task,process,summaries})
|
||||
```javascript
|
||||
// Create docs session (type: docs)
|
||||
SlashCommand(command="/workflow:session:start --type docs --new \"{project_name}-docs-{timestamp}\"")
|
||||
// Parse output to get sessionId
|
||||
```
|
||||
|
||||
# Create workflow-session.json (replace values)
|
||||
bash(echo '{"session_id":"WFS-docs-{timestamp}","project":"{project} documentation","status":"planning","timestamp":"2024-01-20T14:30:22+08:00","path":".","target_path":"{target_path}","project_root":"{project_root}","project_name":"{project_name}","mode":"full","tool":"gemini","cli_execute":false}' | jq '.' > .workflow/active/WFS-docs-{timestamp}/workflow-session.json)
|
||||
```bash
|
||||
# Update workflow-session.json with docs-specific fields
|
||||
bash(jq '. + {"target_path":"{target_path}","project_root":"{project_root}","project_name":"{project_name}","mode":"full","tool":"gemini","cli_execute":false}' .workflow/active/{sessionId}/workflow-session.json > tmp.json && mv tmp.json .workflow/active/{sessionId}/workflow-session.json)
|
||||
```
|
||||
|
||||
### Phase 2: Analyze Structure
|
||||
|
||||
@@ -101,7 +101,7 @@ Load only minimal necessary context from each artifact:
|
||||
- Dependencies (depends_on, blocks)
|
||||
- Context (requirements, focus_paths, acceptance, artifacts)
|
||||
- Flow control (pre_analysis, implementation_approach)
|
||||
- Meta (complexity, priority, use_codex)
|
||||
- Meta (complexity, priority)
|
||||
|
||||
### 3. Build Semantic Models
|
||||
|
||||
|
||||
@@ -2,452 +2,359 @@
|
||||
name: artifacts
|
||||
description: Interactive clarification generating confirmed guidance specification through role-based analysis and synthesis
|
||||
argument-hint: "topic or challenge description [--count N]"
|
||||
allowed-tools: TodoWrite(*), Read(*), Write(*), Glob(*)
|
||||
allowed-tools: TodoWrite(*), Read(*), Write(*), Glob(*), AskUserQuestion(*)
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Six-phase workflow: **Automatic project context collection** → Extract topic challenges → Select roles → Generate task-specific questions → Detect conflicts → Generate confirmed guidance (declarative statements only).
|
||||
Seven-phase workflow: **Context collection** → **Topic analysis** → **Role selection** → **Role questions** → **Conflict resolution** → **Final check** → **Generate specification**
|
||||
|
||||
All user interactions use AskUserQuestion tool (max 4 questions per call, multi-round).
|
||||
|
||||
**Input**: `"GOAL: [objective] SCOPE: [boundaries] CONTEXT: [background]" [--count N]`
|
||||
**Output**: `.workflow/active/WFS-{topic}/.brainstorming/guidance-specification.md` (CONFIRMED/SELECTED format)
|
||||
**Core Principle**: Questions dynamically generated from project context + topic keywords/challenges, NOT from generic templates
|
||||
**Output**: `.workflow/active/WFS-{topic}/.brainstorming/guidance-specification.md`
|
||||
**Core Principle**: Questions dynamically generated from project context + topic keywords, NOT generic templates
|
||||
|
||||
**Parameters**:
|
||||
- `topic` (required): Topic or challenge description (structured format recommended)
|
||||
- `--count N` (optional): Number of roles user WANTS to select (system will recommend N+2 options for user to choose from, default: 3)
|
||||
- `--count N` (optional): Number of roles to select (system recommends N+2 options, default: 3)
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Phase Summary
|
||||
|
||||
| Phase | Goal | AskUserQuestion | Storage |
|
||||
|-------|------|-----------------|---------|
|
||||
| 0 | Context collection | - | context-package.json |
|
||||
| 1 | Topic analysis | 2-4 questions | intent_context |
|
||||
| 2 | Role selection | 1 multi-select | selected_roles |
|
||||
| 3 | Role questions | 3-4 per role | role_decisions[role] |
|
||||
| 4 | Conflict resolution | max 4 per round | cross_role_decisions |
|
||||
| 4.5 | Final check | progressive rounds | additional_decisions |
|
||||
| 5 | Generate spec | - | guidance-specification.md |
|
||||
|
||||
### AskUserQuestion Pattern
|
||||
|
||||
```javascript
|
||||
// Single-select (Phase 1, 3, 4)
|
||||
AskUserQuestion({
|
||||
questions: [
|
||||
{
|
||||
question: "{问题文本}",
|
||||
header: "{短标签}", // max 12 chars
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "{选项}", description: "{说明和影响}" },
|
||||
{ label: "{选项}", description: "{说明和影响}" },
|
||||
{ label: "{选项}", description: "{说明和影响}" }
|
||||
]
|
||||
}
|
||||
// ... max 4 questions per call
|
||||
]
|
||||
})
|
||||
|
||||
// Multi-select (Phase 2)
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "请选择 {count} 个角色",
|
||||
header: "角色选择",
|
||||
multiSelect: true,
|
||||
options: [/* max 4 options per call */]
|
||||
}]
|
||||
})
|
||||
```
|
||||
|
||||
### Multi-Round Execution
|
||||
|
||||
```javascript
|
||||
const BATCH_SIZE = 4;
|
||||
for (let i = 0; i < allQuestions.length; i += BATCH_SIZE) {
|
||||
const batch = allQuestions.slice(i, i + BATCH_SIZE);
|
||||
AskUserQuestion({ questions: batch });
|
||||
// Store responses before next round
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task Tracking
|
||||
|
||||
**⚠️ TodoWrite Rule**: EXTEND auto-parallel's task list (NOT replace/overwrite)
|
||||
**TodoWrite Rule**: EXTEND auto-parallel's task list (NOT replace/overwrite)
|
||||
|
||||
**When called from auto-parallel**:
|
||||
- Find the artifacts parent task: "Execute artifacts command for interactive framework generation"
|
||||
- Mark parent task as "in_progress"
|
||||
- APPEND artifacts sub-tasks AFTER the parent task (Phase 0-5)
|
||||
- Mark each sub-task as it completes
|
||||
- When Phase 5 completes, mark parent task as "completed"
|
||||
- **PRESERVE all other auto-parallel tasks** (role agents, synthesis)
|
||||
- Find artifacts parent task → Mark "in_progress"
|
||||
- APPEND sub-tasks (Phase 0-5) → Mark each as completes
|
||||
- When Phase 5 completes → Mark parent "completed"
|
||||
- PRESERVE all other auto-parallel tasks
|
||||
|
||||
**Standalone Mode**:
|
||||
```json
|
||||
[
|
||||
{"content": "Initialize session (.workflow/active/ session check, parse --count parameter)", "status": "pending", "activeForm": "Initializing"},
|
||||
{"content": "Phase 0: Automatic project context collection (call context-gather)", "status": "pending", "activeForm": "Phase 0 context collection"},
|
||||
{"content": "Phase 1: Extract challenges, output 2-4 task-specific questions, wait for user input", "status": "pending", "activeForm": "Phase 1 topic analysis"},
|
||||
{"content": "Phase 2: Recommend count+2 roles, output role selection, wait for user input", "status": "pending", "activeForm": "Phase 2 role selection"},
|
||||
{"content": "Phase 3: Generate 3-4 questions per role, output and wait for answers (max 10 per round)", "status": "pending", "activeForm": "Phase 3 role questions"},
|
||||
{"content": "Phase 4: Detect conflicts, output clarifications, wait for answers (max 10 per round)", "status": "pending", "activeForm": "Phase 4 conflict resolution"},
|
||||
{"content": "Phase 5: Transform Q&A to declarative statements, write guidance-specification.md", "status": "pending", "activeForm": "Phase 5 document generation"}
|
||||
{"content": "Initialize session", "status": "pending", "activeForm": "Initializing"},
|
||||
{"content": "Phase 0: Context collection", "status": "pending", "activeForm": "Phase 0"},
|
||||
{"content": "Phase 1: Topic analysis (2-4 questions)", "status": "pending", "activeForm": "Phase 1"},
|
||||
{"content": "Phase 2: Role selection", "status": "pending", "activeForm": "Phase 2"},
|
||||
{"content": "Phase 3: Role questions (per role)", "status": "pending", "activeForm": "Phase 3"},
|
||||
{"content": "Phase 4: Conflict resolution", "status": "pending", "activeForm": "Phase 4"},
|
||||
{"content": "Phase 4.5: Final clarification", "status": "pending", "activeForm": "Phase 4.5"},
|
||||
{"content": "Phase 5: Generate specification", "status": "pending", "activeForm": "Phase 5"}
|
||||
]
|
||||
```
|
||||
|
||||
## User Interaction Protocol
|
||||
|
||||
### Question Output Format
|
||||
|
||||
All questions output as structured text (detailed format with descriptions):
|
||||
|
||||
```markdown
|
||||
【问题{N} - {短标签}】{问题文本}
|
||||
a) {选项标签}
|
||||
说明:{选项说明和影响}
|
||||
b) {选项标签}
|
||||
说明:{选项说明和影响}
|
||||
c) {选项标签}
|
||||
说明:{选项说明和影响}
|
||||
|
||||
请回答:{N}a 或 {N}b 或 {N}c
|
||||
```
|
||||
|
||||
**Multi-select format** (Phase 2 role selection):
|
||||
```markdown
|
||||
【角色选择】请选择 {count} 个角色参与头脑风暴分析
|
||||
|
||||
a) {role-name} ({中文名})
|
||||
推荐理由:{基于topic的相关性说明}
|
||||
b) {role-name} ({中文名})
|
||||
推荐理由:{基于topic的相关性说明}
|
||||
...
|
||||
|
||||
支持格式:
|
||||
- 分别选择:2a 2c 2d (选择第2题的a、c、d选项)
|
||||
- 合并语法:2acd (选择a、c、d)
|
||||
- 逗号分隔:2a,c,d
|
||||
|
||||
请输入选择:
|
||||
```
|
||||
|
||||
### Input Parsing Rules
|
||||
|
||||
**Supported formats** (intelligent parsing):
|
||||
|
||||
1. **Space-separated**: `1a 2b 3c` → Q1:a, Q2:b, Q3:c
|
||||
2. **Comma-separated**: `1a,2b,3c` → Q1:a, Q2:b, Q3:c
|
||||
3. **Multi-select combined**: `2abc` → Q2: options a,b,c
|
||||
4. **Multi-select spaces**: `2 a b c` → Q2: options a,b,c
|
||||
5. **Multi-select comma**: `2a,b,c` → Q2: options a,b,c
|
||||
6. **Natural language**: `问题1选a` → 1a (fallback parsing)
|
||||
|
||||
**Parsing algorithm**:
|
||||
- Extract question numbers and option letters
|
||||
- Validate question numbers match output
|
||||
- Validate option letters exist for each question
|
||||
- If ambiguous/invalid, output example format and request re-input
|
||||
|
||||
**Error handling** (lenient):
|
||||
- Recognize common variations automatically
|
||||
- If parsing fails, show example and wait for clarification
|
||||
- Support re-input without penalty
|
||||
|
||||
### Batching Strategy
|
||||
|
||||
**Batch limits**:
|
||||
- **Default**: Maximum 10 questions per round
|
||||
- **Phase 2 (role selection)**: Display all recommended roles at once (count+2 roles)
|
||||
- **Auto-split**: If questions > 10, split into multiple rounds with clear round indicators
|
||||
|
||||
**Round indicators**:
|
||||
```markdown
|
||||
===== 第 1 轮问题 (共2轮) =====
|
||||
【问题1 - ...】...
|
||||
【问题2 - ...】...
|
||||
...
|
||||
【问题10 - ...】...
|
||||
|
||||
请回答 (格式: 1a 2b ... 10c):
|
||||
```
|
||||
|
||||
### Interaction Flow
|
||||
|
||||
**Standard flow**:
|
||||
1. Output questions in formatted text
|
||||
2. Output expected input format example
|
||||
3. Wait for user input
|
||||
4. Parse input with intelligent matching
|
||||
5. If parsing succeeds → Store answers and continue
|
||||
6. If parsing fails → Show error, example, and wait for re-input
|
||||
|
||||
**No question/option limits**: Text-based interaction removes previous 4-question and 4-option restrictions
|
||||
---
|
||||
|
||||
## Execution Phases
|
||||
|
||||
### Session Management
|
||||
|
||||
- Check `.workflow/active/` for existing sessions
|
||||
- Multiple sessions → Prompt selection | Single → Use it | None → Create `WFS-[topic-slug]`
|
||||
- Parse `--count N` parameter from user input (default: 3 if not specified)
|
||||
- Store decisions in `workflow-session.json` including count parameter
|
||||
- Multiple → Prompt selection | Single → Use it | None → Create `WFS-[topic-slug]`
|
||||
- Parse `--count N` parameter (default: 3)
|
||||
- Store decisions in `workflow-session.json`
|
||||
|
||||
### Phase 0: Automatic Project Context Collection
|
||||
### Phase 0: Context Collection
|
||||
|
||||
**Goal**: Gather project architecture, documentation, and relevant code context BEFORE user interaction
|
||||
**Goal**: Gather project context BEFORE user interaction
|
||||
|
||||
**Detection Mechanism** (execute first):
|
||||
```javascript
|
||||
// Check if context-package already exists
|
||||
const contextPackagePath = `.workflow/active/WFS-{session-id}/.process/context-package.json`;
|
||||
**Steps**:
|
||||
1. Check if `context-package.json` exists → Skip if valid
|
||||
2. Invoke `context-search-agent` (BRAINSTORM MODE - lightweight)
|
||||
3. Output: `.workflow/active/WFS-{session-id}/.process/context-package.json`
|
||||
|
||||
if (file_exists(contextPackagePath)) {
|
||||
// Validate package
|
||||
const package = Read(contextPackagePath);
|
||||
if (package.metadata.session_id === session_id) {
|
||||
console.log("✅ Valid context-package found, skipping Phase 0");
|
||||
return; // Skip to Phase 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation**: Invoke `context-search-agent` only if package doesn't exist
|
||||
**Graceful Degradation**: If agent fails, continue to Phase 1 without context
|
||||
|
||||
```javascript
|
||||
Task(
|
||||
subagent_type="context-search-agent",
|
||||
description="Gather project context for brainstorm",
|
||||
prompt=`
|
||||
You are executing as context-search-agent (.claude/agents/context-search-agent.md).
|
||||
Execute context-search-agent in BRAINSTORM MODE (Phase 1-2 only).
|
||||
|
||||
## Execution Mode
|
||||
**BRAINSTORM MODE** (Lightweight) - Phase 1-2 only (skip deep analysis)
|
||||
Session: ${session_id}
|
||||
Task: ${task_description}
|
||||
Output: .workflow/${session_id}/.process/context-package.json
|
||||
|
||||
## Session Information
|
||||
- **Session ID**: ${session_id}
|
||||
- **Task Description**: ${task_description}
|
||||
- **Output Path**: .workflow/${session_id}/.process/context-package.json
|
||||
|
||||
## Mission
|
||||
Execute complete context-search-agent workflow for implementation planning:
|
||||
|
||||
### Phase 1: Initialization & Pre-Analysis
|
||||
1. **Detection**: Check for existing context-package (early exit if valid)
|
||||
2. **Foundation**: Initialize code-index, get project structure, load docs
|
||||
3. **Analysis**: Extract keywords, determine scope, classify complexity
|
||||
|
||||
### Phase 2: Multi-Source Context Discovery
|
||||
Execute all 3 discovery tracks:
|
||||
- **Track 1**: Reference documentation (CLAUDE.md, architecture docs)
|
||||
- **Track 2**: Web examples (use Exa MCP for unfamiliar tech/APIs)
|
||||
- **Track 3**: Codebase analysis (5-layer discovery: files, content, patterns, deps, config/tests)
|
||||
|
||||
### Phase 3: Synthesis, Assessment & Packaging
|
||||
1. Apply relevance scoring and build dependency graph
|
||||
2. Synthesize 3-source data (docs > code > web)
|
||||
3. Integrate brainstorm artifacts (if .brainstorming/ exists, read content)
|
||||
4. Perform conflict detection with risk assessment
|
||||
5. Generate and validate context-package.json
|
||||
|
||||
## Output Requirements
|
||||
Complete context-package.json with:
|
||||
- **metadata**: task_description, keywords, complexity, tech_stack, session_id
|
||||
- **project_context**: architecture_patterns, coding_conventions, tech_stack
|
||||
- **assets**: {documentation[], source_code[], config[], tests[]} with relevance scores
|
||||
- **dependencies**: {internal[], external[]} with dependency graph
|
||||
- **brainstorm_artifacts**: {guidance_specification, role_analyses[], synthesis_output} with content
|
||||
- **conflict_detection**: {risk_level, risk_factors, affected_modules[], mitigation_strategy}
|
||||
|
||||
## Quality Validation
|
||||
Before completion verify:
|
||||
- [ ] Valid JSON format with all required fields
|
||||
- [ ] File relevance accuracy >80%
|
||||
- [ ] Dependency graph complete (max 2 transitive levels)
|
||||
- [ ] Conflict risk level calculated correctly
|
||||
- [ ] No sensitive data exposed
|
||||
- [ ] Total files ≤50 (prioritize high-relevance)
|
||||
|
||||
Execute autonomously following agent documentation.
|
||||
Report completion with statistics.
|
||||
Required fields: metadata, project_context, assets, dependencies, conflict_detection
|
||||
`
|
||||
)
|
||||
```
|
||||
|
||||
**Graceful Degradation**:
|
||||
- If agent fails: Log warning, continue to Phase 1 without project context
|
||||
- If package invalid: Re-run context-search-agent
|
||||
### Phase 1: Topic Analysis
|
||||
|
||||
### Phase 1: Topic Analysis & Intent Classification
|
||||
|
||||
**Goal**: Extract keywords/challenges to drive all subsequent question generation, **enriched by Phase 0 project context**
|
||||
**Goal**: Extract keywords/challenges enriched by Phase 0 context
|
||||
|
||||
**Steps**:
|
||||
1. **Load Phase 0 context** (if available):
|
||||
- Read `.workflow/active/WFS-{session-id}/.process/context-package.json`
|
||||
- Extract: tech_stack, existing modules, conflict_risk, relevant files
|
||||
1. Load Phase 0 context (tech_stack, modules, conflict_risk)
|
||||
2. Deep topic analysis (entities, challenges, constraints, metrics)
|
||||
3. Generate 2-4 context-aware probing questions
|
||||
4. AskUserQuestion → Store to `session.intent_context`
|
||||
|
||||
2. **Deep topic analysis** (context-aware):
|
||||
- Extract technical entities from topic + existing codebase
|
||||
- Identify core challenges considering existing architecture
|
||||
- Consider constraints (timeline/budget/compliance)
|
||||
- Define success metrics based on current project state
|
||||
|
||||
3. **Generate 2-4 context-aware probing questions**:
|
||||
- Reference existing tech stack in questions
|
||||
- Consider integration with existing modules
|
||||
- Address identified conflict risks from Phase 0
|
||||
- Target root challenges and trade-off priorities
|
||||
|
||||
4. **User interaction**: Output questions using text format (see User Interaction Protocol), wait for user input
|
||||
|
||||
5. **Parse user answers**: Use intelligent parsing to extract answers from user input (support multiple formats)
|
||||
|
||||
6. **Storage**: Store answers to `session.intent_context` with `{extracted_keywords, identified_challenges, user_answers, project_context_used}`
|
||||
|
||||
**Example Output**:
|
||||
```markdown
|
||||
===== Phase 1: 项目意图分析 =====
|
||||
|
||||
【问题1 - 核心挑战】实时协作平台的主要技术挑战?
|
||||
a) 实时数据同步
|
||||
说明:100+用户同时在线,状态同步复杂度高
|
||||
b) 可扩展性架构
|
||||
说明:用户规模增长时的系统扩展能力
|
||||
c) 冲突解决机制
|
||||
说明:多用户同时编辑的冲突处理策略
|
||||
|
||||
【问题2 - 优先级】MVP阶段最关注的指标?
|
||||
a) 功能完整性
|
||||
说明:实现所有核心功能
|
||||
b) 用户体验
|
||||
说明:流畅的交互体验和响应速度
|
||||
c) 系统稳定性
|
||||
说明:高可用性和数据一致性
|
||||
|
||||
请回答 (格式: 1a 2b):
|
||||
**Example**:
|
||||
```javascript
|
||||
AskUserQuestion({
|
||||
questions: [
|
||||
{
|
||||
question: "实时协作平台的主要技术挑战?",
|
||||
header: "核心挑战",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "实时数据同步", description: "100+用户同时在线,状态同步复杂度高" },
|
||||
{ label: "可扩展性架构", description: "用户规模增长时的系统扩展能力" },
|
||||
{ label: "冲突解决机制", description: "多用户同时编辑的冲突处理策略" }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "MVP阶段最关注的指标?",
|
||||
header: "优先级",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "功能完整性", description: "实现所有核心功能" },
|
||||
{ label: "用户体验", description: "流畅的交互体验和响应速度" },
|
||||
{ label: "系统稳定性", description: "高可用性和数据一致性" }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
**User input examples**:
|
||||
- `1a 2c` → Q1:a, Q2:c
|
||||
- `1a,2c` → Q1:a, Q2:c
|
||||
|
||||
**⚠️ CRITICAL**: Questions MUST reference topic keywords. Generic "Project type?" violates dynamic generation.
|
||||
|
||||
### Phase 2: Role Selection
|
||||
|
||||
**⚠️ CRITICAL**: User MUST interact to select roles. NEVER auto-select without user confirmation.
|
||||
**Goal**: User selects roles from intelligent recommendations
|
||||
|
||||
**Available Roles**:
|
||||
- data-architect (数据架构师)
|
||||
- product-manager (产品经理)
|
||||
- product-owner (产品负责人)
|
||||
- scrum-master (敏捷教练)
|
||||
- subject-matter-expert (领域专家)
|
||||
- system-architect (系统架构师)
|
||||
- test-strategist (测试策略师)
|
||||
- ui-designer (UI 设计师)
|
||||
- ux-expert (UX 专家)
|
||||
**Available Roles**: data-architect, product-manager, product-owner, scrum-master, subject-matter-expert, system-architect, test-strategist, ui-designer, ux-expert
|
||||
|
||||
**Steps**:
|
||||
1. **Intelligent role recommendation** (AI analysis):
|
||||
- Analyze Phase 1 extracted keywords and challenges
|
||||
- Use AI reasoning to determine most relevant roles for the specific topic
|
||||
- Recommend count+2 roles (e.g., if user wants 3 roles, recommend 5 options)
|
||||
- Provide clear rationale for each recommended role based on topic context
|
||||
1. Analyze Phase 1 keywords → Recommend count+2 roles with rationale
|
||||
2. AskUserQuestion (multiSelect=true) → Store to `session.selected_roles`
|
||||
3. If count+2 > 4, split into multiple rounds
|
||||
|
||||
2. **User selection** (text interaction):
|
||||
- Output all recommended roles at once (no batching needed for count+2 roles)
|
||||
- Display roles with labels and relevance rationale
|
||||
- Wait for user input in multi-select format
|
||||
- Parse user input (support multiple formats)
|
||||
- **Storage**: Store selections to `session.selected_roles`
|
||||
|
||||
**Example Output**:
|
||||
```markdown
|
||||
===== Phase 2: 角色选择 =====
|
||||
|
||||
【角色选择】请选择 3 个角色参与头脑风暴分析
|
||||
|
||||
a) system-architect (系统架构师)
|
||||
推荐理由:实时同步架构设计和技术选型的核心角色
|
||||
b) ui-designer (UI设计师)
|
||||
推荐理由:协作界面用户体验和实时状态展示
|
||||
c) product-manager (产品经理)
|
||||
推荐理由:功能优先级和MVP范围决策
|
||||
d) data-architect (数据架构师)
|
||||
推荐理由:数据同步模型和存储方案设计
|
||||
e) ux-expert (UX专家)
|
||||
推荐理由:多用户协作交互流程优化
|
||||
|
||||
支持格式:
|
||||
- 分别选择:2a 2c 2d (选择a、c、d)
|
||||
- 合并语法:2acd (选择a、c、d)
|
||||
- 逗号分隔:2a,c,d (选择a、c、d)
|
||||
|
||||
请输入选择:
|
||||
**Example**:
|
||||
```javascript
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "请选择 3 个角色参与头脑风暴分析",
|
||||
header: "角色选择",
|
||||
multiSelect: true,
|
||||
options: [
|
||||
{ label: "system-architect", description: "实时同步架构设计和技术选型" },
|
||||
{ label: "ui-designer", description: "协作界面用户体验和状态展示" },
|
||||
{ label: "product-manager", description: "功能优先级和MVP范围决策" },
|
||||
{ label: "data-architect", description: "数据同步模型和存储方案设计" }
|
||||
]
|
||||
}]
|
||||
})
|
||||
```
|
||||
|
||||
**User input examples**:
|
||||
- `2acd` → Roles: a, c, d (system-architect, product-manager, data-architect)
|
||||
- `2a 2c 2d` → Same result
|
||||
- `2a,c,d` → Same result
|
||||
**⚠️ CRITICAL**: User MUST interact. NEVER auto-select without confirmation.
|
||||
|
||||
**Role Recommendation Rules**:
|
||||
- NO hardcoded keyword-to-role mappings
|
||||
- Use intelligent analysis of topic, challenges, and requirements
|
||||
- Consider role synergies and coverage gaps
|
||||
- Explain WHY each role is relevant to THIS specific topic
|
||||
- Default recommendation: count+2 roles for user to choose from
|
||||
|
||||
### Phase 3: Role-Specific Questions (Dynamic Generation)
|
||||
### Phase 3: Role-Specific Questions
|
||||
|
||||
**Goal**: Generate deep questions mapping role expertise to Phase 1 challenges
|
||||
|
||||
**Algorithm**:
|
||||
```
|
||||
FOR each selected role:
|
||||
1. Map Phase 1 challenges to role domain:
|
||||
- "real-time sync" + system-architect → State management pattern
|
||||
- "100 users" + system-architect → Communication protocol
|
||||
- "low latency" + system-architect → Conflict resolution
|
||||
1. FOR each selected role:
|
||||
- Map Phase 1 challenges to role domain
|
||||
- Generate 3-4 questions (implementation depth, trade-offs, edge cases)
|
||||
- AskUserQuestion per role → Store to `session.role_decisions[role]`
|
||||
2. Process roles sequentially (one at a time for clarity)
|
||||
3. If role needs > 4 questions, split into multiple rounds
|
||||
|
||||
2. Generate 3-4 questions per role probing implementation depth, trade-offs, edge cases:
|
||||
Q: "How handle real-time state sync for 100+ users?" (explores approach)
|
||||
Q: "How resolve conflicts when 2 users edit simultaneously?" (explores edge case)
|
||||
Options: [Event Sourcing/Centralized/CRDT] (concrete, explain trade-offs for THIS use case)
|
||||
|
||||
3. Output questions in text format per role:
|
||||
- Display all questions for current role (3-4 questions, no 10-question limit)
|
||||
- Questions in Chinese (用中文提问)
|
||||
- Wait for user input
|
||||
- Parse answers using intelligent parsing
|
||||
- Store answers to session.role_decisions[role]
|
||||
**Example** (system-architect):
|
||||
```javascript
|
||||
AskUserQuestion({
|
||||
questions: [
|
||||
{
|
||||
question: "100+ 用户实时状态同步方案?",
|
||||
header: "状态同步",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Event Sourcing", description: "完整事件历史,支持回溯,存储成本高" },
|
||||
{ label: "集中式状态管理", description: "实现简单,单点瓶颈风险" },
|
||||
{ label: "CRDT", description: "去中心化,自动合并,学习曲线陡" }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "两个用户同时编辑冲突如何解决?",
|
||||
header: "冲突解决",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "自动合并", description: "用户无感知,可能产生意外结果" },
|
||||
{ label: "手动解决", description: "用户控制,增加交互复杂度" },
|
||||
{ label: "版本控制", description: "保留历史,需要分支管理" }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
**Batching Strategy**:
|
||||
- Each role outputs all its questions at once (typically 3-4 questions)
|
||||
- No need to split per role (within 10-question batch limit)
|
||||
- Multiple roles processed sequentially (one role at a time for clarity)
|
||||
### Phase 4: Conflict Resolution
|
||||
|
||||
**Output Format**: Follow standard format from "User Interaction Protocol" section (single-choice question format)
|
||||
|
||||
**Example Topic-Specific Questions** (system-architect role for "real-time collaboration platform"):
|
||||
- "100+ 用户实时状态同步方案?" → Options: Event Sourcing / 集中式状态管理 / CRDT
|
||||
- "两个用户同时编辑冲突如何解决?" → Options: 自动合并 / 手动解决 / 版本控制
|
||||
- "低延迟通信协议选择?" → Options: WebSocket / SSE / 轮询
|
||||
- "系统扩展性架构方案?" → Options: 微服务 / 单体+缓存 / Serverless
|
||||
|
||||
**Quality Requirements**: See "Question Generation Guidelines" section for detailed rules
|
||||
|
||||
### Phase 4: Cross-Role Clarification (Conflict Detection)
|
||||
|
||||
**Goal**: Resolve ACTUAL conflicts from Phase 3 answers, not pre-defined relationships
|
||||
**Goal**: Resolve ACTUAL conflicts from Phase 3 answers
|
||||
|
||||
**Algorithm**:
|
||||
```
|
||||
1. Analyze Phase 3 answers for conflicts:
|
||||
- Contradictory choices: product-manager "fast iteration" vs system-architect "complex Event Sourcing"
|
||||
- Missing integration: ui-designer "Optimistic updates" but system-architect didn't address conflict handling
|
||||
- Implicit dependencies: ui-designer "Live cursors" but no auth approach defined
|
||||
|
||||
2. FOR each detected conflict:
|
||||
Generate clarification questions referencing SPECIFIC Phase 3 choices
|
||||
|
||||
3. Output clarification questions in text format:
|
||||
- Batch conflicts into rounds (max 10 questions per round)
|
||||
- Display questions with context from Phase 3 answers
|
||||
- Questions in Chinese (用中文提问)
|
||||
- Wait for user input
|
||||
- Parse answers using intelligent parsing
|
||||
- Store answers to session.cross_role_decisions
|
||||
|
||||
- Contradictory choices (e.g., "fast iteration" vs "complex Event Sourcing")
|
||||
- Missing integration (e.g., "Optimistic updates" but no conflict handling)
|
||||
- Implicit dependencies (e.g., "Live cursors" but no auth defined)
|
||||
2. Generate clarification questions referencing SPECIFIC Phase 3 choices
|
||||
3. AskUserQuestion (max 4 per call, multi-round) → Store to `session.cross_role_decisions`
|
||||
4. If NO conflicts: Skip Phase 4 (inform user: "未检测到跨角色冲突,跳过Phase 4")
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "CRDT 与 UI 回滚期望冲突,如何解决?\n背景:system-architect选择CRDT,ui-designer期望回滚UI",
|
||||
header: "架构冲突",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "采用 CRDT", description: "保持去中心化,调整UI期望" },
|
||||
{ label: "显示合并界面", description: "增加用户交互,展示冲突详情" },
|
||||
{ label: "切换到 OT", description: "支持回滚,增加服务器复杂度" }
|
||||
]
|
||||
}]
|
||||
})
|
||||
```
|
||||
|
||||
**Batching Strategy**:
|
||||
- Maximum 10 clarification questions per round
|
||||
- If conflicts > 10, split into multiple rounds
|
||||
- Prioritize most critical conflicts first
|
||||
### Phase 4.5: Final Clarification
|
||||
|
||||
**Output Format**: Follow standard format from "User Interaction Protocol" section (single-choice question format with background context)
|
||||
|
||||
**Example Conflict Detection** (from Phase 3 answers):
|
||||
- **Architecture Conflict**: "CRDT 与 UI 回滚期望冲突,如何解决?"
|
||||
- Background: system-architect chose CRDT, ui-designer expects rollback UI
|
||||
- Options: 采用 CRDT / 显示合并界面 / 切换到 OT
|
||||
- **Integration Gap**: "实时光标功能缺少身份认证方案"
|
||||
- Background: ui-designer chose live cursors, no auth defined
|
||||
- Options: OAuth 2.0 / JWT Token / Session-based
|
||||
|
||||
**Quality Requirements**: See "Question Generation Guidelines" section for conflict-specific rules
|
||||
|
||||
### Phase 5: Generate Guidance Specification
|
||||
**Purpose**: Ensure no important points missed before generating specification
|
||||
|
||||
**Steps**:
|
||||
1. Load all decisions: `intent_context` + `selected_roles` + `role_decisions` + `cross_role_decisions`
|
||||
2. Transform Q&A pairs to declarative: Questions → Headers, Answers → CONFIRMED/SELECTED statements
|
||||
3. Generate guidance-specification.md (template below) - **PRIMARY OUTPUT FILE**
|
||||
4. Update workflow-session.json with **METADATA ONLY**:
|
||||
- session_id (e.g., "WFS-topic-slug")
|
||||
- selected_roles[] (array of role names, e.g., ["system-architect", "ui-designer", "product-manager"])
|
||||
- topic (original user input string)
|
||||
- timestamp (ISO-8601 format)
|
||||
- phase_completed: "artifacts"
|
||||
- count_parameter (number from --count flag)
|
||||
5. Validate: No interrogative sentences in .md file, all decisions traceable, no content duplication in .json
|
||||
1. Ask initial check:
|
||||
```javascript
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "在生成最终规范之前,是否有前面未澄清的重点需要补充?",
|
||||
header: "补充确认",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "无需补充", description: "前面的讨论已经足够完整" },
|
||||
{ label: "需要补充", description: "还有重要内容需要澄清" }
|
||||
]
|
||||
}]
|
||||
})
|
||||
```
|
||||
2. If "需要补充":
|
||||
- Analyze user's additional points
|
||||
- Generate progressive questions (not role-bound, interconnected)
|
||||
- AskUserQuestion (max 4 per round) → Store to `session.additional_decisions`
|
||||
- Repeat until user confirms completion
|
||||
3. If "无需补充": Proceed to Phase 5
|
||||
|
||||
**⚠️ CRITICAL OUTPUT SEPARATION**:
|
||||
- **guidance-specification.md**: Full guidance content (decisions, rationale, integration points)
|
||||
- **workflow-session.json**: Session metadata ONLY (no guidance content, no decisions, no Q&A pairs)
|
||||
- **NO content duplication**: Guidance stays in .md, metadata stays in .json
|
||||
**Progressive Pattern**: Questions interconnected, each round informs next, continue until resolved.
|
||||
|
||||
## Output Document Template
|
||||
### Phase 5: Generate Specification
|
||||
|
||||
**Steps**:
|
||||
1. Load all decisions: `intent_context` + `selected_roles` + `role_decisions` + `cross_role_decisions` + `additional_decisions`
|
||||
2. Transform Q&A to declarative: Questions → Headers, Answers → CONFIRMED/SELECTED statements
|
||||
3. Generate `guidance-specification.md`
|
||||
4. Update `workflow-session.json` (metadata only)
|
||||
5. Validate: No interrogative sentences, all decisions traceable
|
||||
|
||||
---
|
||||
|
||||
## Question Guidelines
|
||||
|
||||
### Core Principle
|
||||
|
||||
**Target**: 开发者(理解技术但需要从用户需求出发)
|
||||
|
||||
**Question Structure**: `[业务场景/需求前提] + [技术关注点]`
|
||||
**Option Structure**: `标签:[技术方案] + 说明:[业务影响] + [技术权衡]`
|
||||
|
||||
### Quality Rules
|
||||
|
||||
**MUST Include**:
|
||||
- ✅ All questions in Chinese (用中文提问)
|
||||
- ✅ 业务场景作为问题前提
|
||||
- ✅ 技术选项的业务影响说明
|
||||
- ✅ 量化指标和约束条件
|
||||
|
||||
**MUST Avoid**:
|
||||
- ❌ 纯技术选型无业务上下文
|
||||
- ❌ 过度抽象的用户体验问题
|
||||
- ❌ 脱离话题的通用架构问题
|
||||
|
||||
### Phase-Specific Requirements
|
||||
|
||||
| Phase | Focus | Key Requirements |
|
||||
|-------|-------|------------------|
|
||||
| 1 | 意图理解 | Reference topic keywords, 用户场景、业务约束、优先级 |
|
||||
| 2 | 角色推荐 | Intelligent analysis (NOT keyword mapping), explain relevance |
|
||||
| 3 | 角色问题 | Reference Phase 1 keywords, concrete options with trade-offs |
|
||||
| 4 | 冲突解决 | Reference SPECIFIC Phase 3 choices, explain impact on both roles |
|
||||
|
||||
---
|
||||
|
||||
## Output & Governance
|
||||
|
||||
### Output Template
|
||||
|
||||
**File**: `.workflow/active/WFS-{topic}/.brainstorming/guidance-specification.md`
|
||||
|
||||
@@ -478,9 +385,9 @@ FOR each selected role:
|
||||
|
||||
## Next Steps
|
||||
**⚠️ Automatic Continuation** (when called from auto-parallel):
|
||||
- auto-parallel will assign agents to generate role-specific analysis documents
|
||||
- Each selected role gets dedicated conceptual-planning-agent
|
||||
- Agents read this guidance-specification.md for framework context
|
||||
- auto-parallel assigns agents for role-specific analysis
|
||||
- Each selected role gets conceptual-planning-agent
|
||||
- Agents read this guidance-specification.md for context
|
||||
|
||||
## Appendix: Decision Tracking
|
||||
| Decision ID | Category | Question | Selected | Phase | Rationale |
|
||||
@@ -490,95 +397,19 @@ FOR each selected role:
|
||||
| D-003+ | [Role] | [Q] | [A] | 3 | [Why] |
|
||||
```
|
||||
|
||||
## Question Generation Guidelines
|
||||
|
||||
### Core Principle: Developer-Facing Questions with User Context
|
||||
|
||||
**Target Audience**: 开发者(理解技术但需要从用户需求出发)
|
||||
|
||||
**Generation Philosophy**:
|
||||
1. **Phase 1**: 用户场景、业务约束、优先级(建立上下文)
|
||||
2. **Phase 2**: 基于话题分析的智能角色推荐(非关键词映射)
|
||||
3. **Phase 3**: 业务需求 + 技术选型(需求驱动的技术决策)
|
||||
4. **Phase 4**: 技术冲突的业务权衡(帮助开发者理解影响)
|
||||
|
||||
### Universal Quality Rules
|
||||
|
||||
**Question Structure** (all phases):
|
||||
```
|
||||
[业务场景/需求前提] + [技术关注点]
|
||||
```
|
||||
|
||||
**Option Structure** (all phases):
|
||||
```
|
||||
标签:[技术方案简称] + (业务特征)
|
||||
说明:[业务影响] + [技术权衡]
|
||||
```
|
||||
|
||||
**MUST Include** (all phases):
|
||||
- ✅ All questions in Chinese (用中文提问)
|
||||
- ✅ 业务场景作为问题前提
|
||||
- ✅ 技术选项的业务影响说明
|
||||
- ✅ 量化指标和约束条件
|
||||
|
||||
**MUST Avoid** (all phases):
|
||||
- ❌ 纯技术选型无业务上下文
|
||||
- ❌ 过度抽象的用户体验问题
|
||||
- ❌ 脱离话题的通用架构问题
|
||||
|
||||
### Phase-Specific Requirements
|
||||
|
||||
**Phase 1 Requirements**:
|
||||
- Questions MUST reference topic keywords (NOT generic "Project type?")
|
||||
- Focus: 用户使用场景(谁用?怎么用?多频繁?)、业务约束(预算、时间、团队、合规)
|
||||
- Success metrics: 性能指标、用户体验目标
|
||||
- Priority ranking: MVP vs 长期规划
|
||||
|
||||
**Phase 3 Requirements**:
|
||||
- Questions MUST reference Phase 1 keywords (e.g., "real-time", "100 users")
|
||||
- Options MUST be concrete approaches with relevance to topic
|
||||
- Each option includes trade-offs specific to this use case
|
||||
- Include 业务需求驱动的技术问题、量化指标(并发数、延迟、可用性)
|
||||
|
||||
**Phase 4 Requirements**:
|
||||
- Questions MUST reference SPECIFIC Phase 3 choices in background context
|
||||
- Options address the detected conflict directly
|
||||
- Each option explains impact on both conflicting roles
|
||||
- NEVER use static "Cross-Role Matrix" - ALWAYS analyze actual Phase 3 answers
|
||||
- Focus: 技术冲突的业务权衡、帮助开发者理解不同选择的影响
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
Generated guidance-specification.md MUST:
|
||||
- ✅ No interrogative sentences (use CONFIRMED/SELECTED)
|
||||
- ✅ Every decision traceable to user answer
|
||||
- ✅ Cross-role conflicts resolved or documented
|
||||
- ✅ Next steps concrete and specific
|
||||
- ✅ All Phase 1-4 decisions in session metadata
|
||||
|
||||
## Update Mechanism
|
||||
### File Structure
|
||||
|
||||
```
|
||||
IF guidance-specification.md EXISTS:
|
||||
Prompt: "Regenerate completely / Update sections / Cancel"
|
||||
ELSE:
|
||||
Run full Phase 1-5 flow
|
||||
.workflow/active/WFS-[topic]/
|
||||
├── workflow-session.json # Metadata ONLY
|
||||
├── .process/
|
||||
│ └── context-package.json # Phase 0 output
|
||||
└── .brainstorming/
|
||||
└── guidance-specification.md # Full guidance content
|
||||
```
|
||||
|
||||
## Governance Rules
|
||||
### Session Metadata
|
||||
|
||||
**Output Requirements**:
|
||||
- All decisions MUST use CONFIRMED/SELECTED (NO "?" in decision sections)
|
||||
- Every decision MUST trace to user answer
|
||||
- Conflicts MUST be resolved (not marked "TBD")
|
||||
- Next steps MUST be actionable
|
||||
- Topic preserved as authoritative reference in session
|
||||
|
||||
**CRITICAL**: Guidance is single source of truth for downstream phases. Ambiguity violates governance.
|
||||
|
||||
## Storage Validation
|
||||
|
||||
**workflow-session.json** (metadata only):
|
||||
```json
|
||||
{
|
||||
"session_id": "WFS-{topic-slug}",
|
||||
@@ -591,14 +422,31 @@ ELSE:
|
||||
}
|
||||
```
|
||||
|
||||
**⚠️ Rule**: Session JSON stores ONLY metadata (session_id, selected_roles[], topic, timestamps). All guidance content goes to guidance-specification.md.
|
||||
**⚠️ Rule**: Session JSON stores ONLY metadata. All guidance content goes to guidance-specification.md.
|
||||
|
||||
## File Structure
|
||||
### Validation Checklist
|
||||
|
||||
- ✅ No interrogative sentences (use CONFIRMED/SELECTED)
|
||||
- ✅ Every decision traceable to user answer
|
||||
- ✅ Cross-role conflicts resolved or documented
|
||||
- ✅ Next steps concrete and specific
|
||||
- ✅ No content duplication between .json and .md
|
||||
|
||||
### Update Mechanism
|
||||
|
||||
```
|
||||
.workflow/active/WFS-[topic]/
|
||||
├── workflow-session.json # Session metadata ONLY
|
||||
└── .brainstorming/
|
||||
└── guidance-specification.md # Full guidance content
|
||||
IF guidance-specification.md EXISTS:
|
||||
Prompt: "Regenerate completely / Update sections / Cancel"
|
||||
ELSE:
|
||||
Run full Phase 0-5 flow
|
||||
```
|
||||
|
||||
### Governance Rules
|
||||
|
||||
- All decisions MUST use CONFIRMED/SELECTED (NO "?" in decision sections)
|
||||
- Every decision MUST trace to user answer
|
||||
- Conflicts MUST be resolved (not marked "TBD")
|
||||
- Next steps MUST be actionable
|
||||
- Topic preserved as authoritative reference
|
||||
|
||||
**CRITICAL**: Guidance is single source of truth for downstream phases. Ambiguity violates governance.
|
||||
|
||||
@@ -141,26 +141,10 @@ OUTPUT_LOCATION: .workflow/active/WFS-{session}/.brainstorming/{role}/
|
||||
TOPIC: {user-provided-topic}
|
||||
|
||||
## Flow Control Steps
|
||||
1. **load_topic_framework**
|
||||
- Action: Load structured topic discussion framework
|
||||
- Command: Read(.workflow/active/WFS-{session}/.brainstorming/guidance-specification.md)
|
||||
- Output: topic_framework_content
|
||||
|
||||
2. **load_role_template**
|
||||
- Action: Load {role-name} planning template
|
||||
- Command: Read(~/.claude/workflows/cli-templates/planning-roles/{role}.md)
|
||||
- Output: role_template_guidelines
|
||||
|
||||
3. **load_session_metadata**
|
||||
- Action: Load session metadata and original user intent
|
||||
- Command: Read(.workflow/active/WFS-{session}/workflow-session.json)
|
||||
- Output: session_context (contains original user prompt as PRIMARY reference)
|
||||
|
||||
4. **load_style_skill** (ONLY for ui-designer role when style_skill_package exists)
|
||||
- Action: Load style SKILL package for design system reference
|
||||
- Command: Read(.claude/skills/style-{style_skill_package}/SKILL.md) AND Read(.workflow/reference_style/{style_skill_package}/design-tokens.json)
|
||||
- Output: style_skill_content, design_tokens
|
||||
- Usage: Apply design tokens in ui-designer analysis and artifacts
|
||||
1. load_topic_framework → .workflow/active/WFS-{session}/.brainstorming/guidance-specification.md
|
||||
2. load_role_template → ~/.claude/workflows/cli-templates/planning-roles/{role}.md
|
||||
3. load_session_metadata → .workflow/active/WFS-{session}/workflow-session.json
|
||||
4. load_style_skill (ui-designer only, if style_skill_package) → .claude/skills/style-{style_skill_package}/
|
||||
|
||||
## Analysis Requirements
|
||||
**Primary Reference**: Original user prompt from workflow-session.json is authoritative
|
||||
@@ -170,13 +154,9 @@ TOPIC: {user-provided-topic}
|
||||
**Template Integration**: Apply role template guidelines within framework structure
|
||||
|
||||
## Expected Deliverables
|
||||
1. **analysis.md**: Comprehensive {role-name} analysis addressing all framework discussion points
|
||||
- **File Naming**: MUST start with `analysis` prefix (e.g., `analysis.md`, `analysis-1.md`, `analysis-2.md`)
|
||||
- **FORBIDDEN**: Never use `recommendations.md` or any filename not starting with `analysis`
|
||||
- **Auto-split if large**: If content >800 lines, split to `analysis-1.md`, `analysis-2.md` (max 3 files: analysis.md, analysis-1.md, analysis-2.md)
|
||||
- **Content**: Includes both analysis AND recommendations sections within analysis files
|
||||
2. **Framework Reference**: Include @../guidance-specification.md reference in analysis
|
||||
3. **User Intent Alignment**: Validate analysis aligns with original user objectives from session_context
|
||||
1. **analysis.md** (optionally with analysis-{slug}.md sub-documents)
|
||||
2. **Framework Reference**: @../guidance-specification.md
|
||||
3. **User Intent Alignment**: Validate against session_context
|
||||
|
||||
## Completion Criteria
|
||||
- Address each discussion point from guidance-specification.md with {role-name} expertise
|
||||
@@ -199,10 +179,10 @@ TOPIC: {user-provided-topic}
|
||||
- guidance-specification.md path
|
||||
|
||||
**Validation**:
|
||||
- Each role creates `.workflow/active/WFS-{topic}/.brainstorming/{role}/analysis.md` (primary file)
|
||||
- If content is large (>800 lines), may split to `analysis-1.md`, `analysis-2.md` (max 3 files total)
|
||||
- **File naming pattern**: ALL files MUST start with `analysis` prefix (use `analysis*.md` for globbing)
|
||||
- **FORBIDDEN naming**: No `recommendations.md`, `recommendations-*.md`, or any non-`analysis` prefixed files
|
||||
- Each role creates `.workflow/active/WFS-{topic}/.brainstorming/{role}/analysis.md`
|
||||
- Optionally with `analysis-{slug}.md` sub-documents (max 5)
|
||||
- **File pattern**: `analysis*.md` for globbing
|
||||
- **FORBIDDEN**: `recommendations.md` or any non-`analysis` prefixed files
|
||||
- All N role analyses completed
|
||||
|
||||
**TodoWrite Update (Phase 2 agents dispatched - tasks attached in parallel)**:
|
||||
@@ -453,12 +433,9 @@ CONTEXT_VARS:
|
||||
├── workflow-session.json # Session metadata ONLY
|
||||
└── .brainstorming/
|
||||
├── guidance-specification.md # Framework (Phase 1)
|
||||
├── {role-1}/
|
||||
│ └── analysis.md # Role analysis (Phase 2)
|
||||
├── {role-2}/
|
||||
│ └── analysis.md
|
||||
├── {role-N}/
|
||||
│ └── analysis.md
|
||||
├── {role}/
|
||||
│ ├── analysis.md # Main document (with optional @references)
|
||||
│ └── analysis-{slug}.md # Section documents (max 5)
|
||||
└── synthesis-specification.md # Integration (Phase 3)
|
||||
```
|
||||
|
||||
|
||||
@@ -2,325 +2,318 @@
|
||||
name: synthesis
|
||||
description: Clarify and refine role analyses through intelligent Q&A and targeted updates with synthesis agent
|
||||
argument-hint: "[optional: --session session-id]"
|
||||
allowed-tools: Task(conceptual-planning-agent), TodoWrite(*), Read(*), Write(*), Edit(*), Glob(*)
|
||||
allowed-tools: Task(conceptual-planning-agent), TodoWrite(*), Read(*), Write(*), Edit(*), Glob(*), AskUserQuestion(*)
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Three-phase workflow to eliminate ambiguities and enhance conceptual depth in role analyses:
|
||||
Six-phase workflow to eliminate ambiguities and enhance conceptual depth in role analyses:
|
||||
|
||||
**Phase 1-2 (Main Flow)**: Session detection → File discovery → Path preparation
|
||||
**Phase 1-2**: Session detection → File discovery → Path preparation
|
||||
**Phase 3A**: Cross-role analysis agent → Generate recommendations
|
||||
**Phase 4**: User selects enhancements → User answers clarifications (via AskUserQuestion)
|
||||
**Phase 5**: Parallel update agents (one per role)
|
||||
**Phase 6**: Context package update → Metadata update → Completion report
|
||||
|
||||
**Phase 3A (Analysis Agent)**: Cross-role analysis → Generate recommendations
|
||||
|
||||
**Phase 4 (Main Flow)**: User selects enhancements → User answers clarifications → Build update plan
|
||||
|
||||
**Phase 5 (Parallel Update Agents)**: Each agent updates ONE role document → Parallel execution
|
||||
|
||||
**Phase 6 (Main Flow)**: Metadata update → Completion report
|
||||
|
||||
**Key Features**:
|
||||
- Multi-agent architecture (analysis agent + parallel update agents)
|
||||
- Clear separation: Agent analysis vs Main flow interaction
|
||||
- Parallel document updates (one agent per role)
|
||||
- User intent alignment validation
|
||||
All user interactions use AskUserQuestion tool (max 4 questions per call, multi-round).
|
||||
|
||||
**Document Flow**:
|
||||
- Input: `[role]/analysis*.md`, `guidance-specification.md`, session metadata
|
||||
- Output: Updated `[role]/analysis*.md` with Enhancements + Clarifications sections
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Phase Summary
|
||||
|
||||
| Phase | Goal | Executor | Output |
|
||||
|-------|------|----------|--------|
|
||||
| 1 | Session detection | Main flow | session_id, brainstorm_dir |
|
||||
| 2 | File discovery | Main flow | role_analysis_paths |
|
||||
| 3A | Cross-role analysis | Agent | enhancement_recommendations |
|
||||
| 4 | User interaction | Main flow + AskUserQuestion | update_plan |
|
||||
| 5 | Document updates | Parallel agents | Updated analysis*.md |
|
||||
| 6 | Finalization | Main flow | context-package.json, report |
|
||||
|
||||
### AskUserQuestion Pattern
|
||||
|
||||
```javascript
|
||||
// Enhancement selection (multi-select)
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "请选择要应用的改进建议",
|
||||
header: "改进选择",
|
||||
multiSelect: true,
|
||||
options: [
|
||||
{ label: "EP-001: API Contract", description: "添加详细的请求/响应 schema 定义" },
|
||||
{ label: "EP-002: User Intent", description: "明确用户需求优先级和验收标准" }
|
||||
]
|
||||
}]
|
||||
})
|
||||
|
||||
// Clarification questions (single-select, multi-round)
|
||||
AskUserQuestion({
|
||||
questions: [
|
||||
{
|
||||
question: "MVP 阶段的核心目标是什么?",
|
||||
header: "用户意图",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "快速验证", description: "最小功能集,快速上线获取反馈" },
|
||||
{ label: "技术壁垒", description: "完善架构,为长期发展打基础" },
|
||||
{ label: "功能完整", description: "覆盖所有规划功能,延迟上线" }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task Tracking
|
||||
|
||||
```json
|
||||
[
|
||||
{"content": "Detect session and validate analyses", "status": "in_progress", "activeForm": "Detecting session"},
|
||||
{"content": "Detect session and validate analyses", "status": "pending", "activeForm": "Detecting session"},
|
||||
{"content": "Discover role analysis file paths", "status": "pending", "activeForm": "Discovering paths"},
|
||||
{"content": "Execute analysis agent (cross-role analysis)", "status": "pending", "activeForm": "Executing analysis agent"},
|
||||
{"content": "Present enhancements for user selection", "status": "pending", "activeForm": "Presenting enhancements"},
|
||||
{"content": "Generate and present clarification questions", "status": "pending", "activeForm": "Clarifying with user"},
|
||||
{"content": "Build update plan from user input", "status": "pending", "activeForm": "Building update plan"},
|
||||
{"content": "Execute parallel update agents (one per role)", "status": "pending", "activeForm": "Updating documents in parallel"},
|
||||
{"content": "Update session metadata and generate report", "status": "pending", "activeForm": "Finalizing session"}
|
||||
{"content": "Execute analysis agent (cross-role analysis)", "status": "pending", "activeForm": "Executing analysis"},
|
||||
{"content": "Present enhancements via AskUserQuestion", "status": "pending", "activeForm": "Selecting enhancements"},
|
||||
{"content": "Clarification questions via AskUserQuestion", "status": "pending", "activeForm": "Clarifying"},
|
||||
{"content": "Execute parallel update agents", "status": "pending", "activeForm": "Updating documents"},
|
||||
{"content": "Update context package and metadata", "status": "pending", "activeForm": "Finalizing"}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Execution Phases
|
||||
|
||||
### Phase 1: Discovery & Validation
|
||||
|
||||
1. **Detect Session**: Use `--session` parameter or find `.workflow/active/WFS-*` directories
|
||||
1. **Detect Session**: Use `--session` parameter or find `.workflow/active/WFS-*`
|
||||
2. **Validate Files**:
|
||||
- `guidance-specification.md` (optional, warn if missing)
|
||||
- `*/analysis*.md` (required, error if empty)
|
||||
3. **Load User Intent**: Extract from `workflow-session.json` (project/description field)
|
||||
3. **Load User Intent**: Extract from `workflow-session.json`
|
||||
|
||||
### Phase 2: Role Discovery & Path Preparation
|
||||
|
||||
**Main flow prepares file paths for Agent**:
|
||||
|
||||
1. **Discover Analysis Files**:
|
||||
- Glob(.workflow/active/WFS-{session}/.brainstorming/*/analysis*.md)
|
||||
- Supports: analysis.md, analysis-1.md, analysis-2.md, analysis-3.md
|
||||
- Validate: At least one file exists (error if empty)
|
||||
- Glob: `.workflow/active/WFS-{session}/.brainstorming/*/analysis*.md`
|
||||
- Supports: analysis.md + analysis-{slug}.md (max 5)
|
||||
|
||||
2. **Extract Role Information**:
|
||||
- `role_analysis_paths`: Relative paths from brainstorm_dir
|
||||
- `participating_roles`: Role names extracted from directory paths
|
||||
- `role_analysis_paths`: Relative paths
|
||||
- `participating_roles`: Role names from directories
|
||||
|
||||
3. **Pass to Agent** (Phase 3):
|
||||
- `session_id`
|
||||
- `brainstorm_dir`: .workflow/active/WFS-{session}/.brainstorming/
|
||||
- `role_analysis_paths`: ["product-manager/analysis.md", "system-architect/analysis-1.md", ...]
|
||||
- `participating_roles`: ["product-manager", "system-architect", ...]
|
||||
|
||||
**Main Flow Responsibility**: File discovery and path preparation only (NO file content reading)
|
||||
3. **Pass to Agent**: session_id, brainstorm_dir, role_analysis_paths, participating_roles
|
||||
|
||||
### Phase 3A: Analysis & Enhancement Agent
|
||||
|
||||
**First agent call**: Cross-role analysis and generate enhancement recommendations
|
||||
**Agent executes cross-role analysis**:
|
||||
|
||||
```bash
|
||||
Task(conceptual-planning-agent): "
|
||||
```javascript
|
||||
Task(conceptual-planning-agent, `
|
||||
## Agent Mission
|
||||
Analyze role documents, identify conflicts/gaps, and generate enhancement recommendations
|
||||
Analyze role documents, identify conflicts/gaps, generate enhancement recommendations
|
||||
|
||||
## Input from Main Flow
|
||||
- brainstorm_dir: {brainstorm_dir}
|
||||
- role_analysis_paths: {role_analysis_paths}
|
||||
- participating_roles: {participating_roles}
|
||||
## Input
|
||||
- brainstorm_dir: ${brainstorm_dir}
|
||||
- role_analysis_paths: ${role_analysis_paths}
|
||||
- participating_roles: ${participating_roles}
|
||||
|
||||
## Execution Instructions
|
||||
[FLOW_CONTROL]
|
||||
## Flow Control Steps
|
||||
1. load_session_metadata → Read workflow-session.json
|
||||
2. load_role_analyses → Read all analysis files
|
||||
3. cross_role_analysis → Identify consensus, conflicts, gaps, ambiguities
|
||||
4. generate_recommendations → Format as EP-001, EP-002, ...
|
||||
|
||||
### Flow Control Steps
|
||||
**AGENT RESPONSIBILITY**: Execute these analysis steps sequentially with context accumulation:
|
||||
|
||||
1. **load_session_metadata**
|
||||
- Action: Load original user intent as primary reference
|
||||
- Command: Read({brainstorm_dir}/../workflow-session.json)
|
||||
- Output: original_user_intent (from project/description field)
|
||||
|
||||
2. **load_role_analyses**
|
||||
- Action: Load all role analysis documents
|
||||
- Command: For each path in role_analysis_paths: Read({brainstorm_dir}/{path})
|
||||
- Output: role_analyses_content_map = {role_name: content}
|
||||
|
||||
3. **cross_role_analysis**
|
||||
- Action: Identify consensus themes, conflicts, gaps, underspecified areas
|
||||
- Output: consensus_themes, conflicting_views, gaps_list, ambiguities
|
||||
|
||||
4. **generate_recommendations**
|
||||
- Action: Convert cross-role analysis findings into structured enhancement recommendations
|
||||
- Format: EP-001, EP-002, ... (sequential numbering)
|
||||
- Fields: id, title, affected_roles, category, current_state, enhancement, rationale, priority
|
||||
- Taxonomy: Map to 9 categories (User Intent, Requirements, Architecture, UX, Feasibility, Risk, Process, Decisions, Terminology)
|
||||
- Output: enhancement_recommendations (JSON array)
|
||||
|
||||
### Output to Main Flow
|
||||
Return JSON array:
|
||||
## Output Format
|
||||
[
|
||||
{
|
||||
\"id\": \"EP-001\",
|
||||
\"title\": \"API Contract Specification\",
|
||||
\"affected_roles\": [\"system-architect\", \"api-designer\"],
|
||||
\"category\": \"Architecture\",
|
||||
\"current_state\": \"High-level API descriptions\",
|
||||
\"enhancement\": \"Add detailed contract definitions with request/response schemas\",
|
||||
\"rationale\": \"Enables precise implementation and testing\",
|
||||
\"priority\": \"High\"
|
||||
},
|
||||
...
|
||||
"id": "EP-001",
|
||||
"title": "API Contract Specification",
|
||||
"affected_roles": ["system-architect", "api-designer"],
|
||||
"category": "Architecture",
|
||||
"current_state": "High-level API descriptions",
|
||||
"enhancement": "Add detailed contract definitions",
|
||||
"rationale": "Enables precise implementation",
|
||||
"priority": "High"
|
||||
}
|
||||
]
|
||||
|
||||
"
|
||||
`)
|
||||
```
|
||||
|
||||
### Phase 4: Main Flow User Interaction
|
||||
### Phase 4: User Interaction
|
||||
|
||||
**Main flow handles all user interaction via text output**:
|
||||
**All interactions via AskUserQuestion (Chinese questions)**
|
||||
|
||||
**⚠️ CRITICAL**: ALL questions MUST use Chinese (所有问题必须用中文) for better user understanding
|
||||
#### Step 1: Enhancement Selection
|
||||
|
||||
1. **Present Enhancement Options** (multi-select):
|
||||
```markdown
|
||||
===== Enhancement 选择 =====
|
||||
```javascript
|
||||
// If enhancements > 4, split into multiple rounds
|
||||
const enhancements = [...]; // from Phase 3A
|
||||
const BATCH_SIZE = 4;
|
||||
|
||||
请选择要应用的改进建议(可多选):
|
||||
for (let i = 0; i < enhancements.length; i += BATCH_SIZE) {
|
||||
const batch = enhancements.slice(i, i + BATCH_SIZE);
|
||||
|
||||
a) EP-001: API Contract Specification
|
||||
影响角色:system-architect, api-designer
|
||||
说明:添加详细的请求/响应 schema 定义
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: `请选择要应用的改进建议 (第${Math.floor(i/BATCH_SIZE)+1}轮)`,
|
||||
header: "改进选择",
|
||||
multiSelect: true,
|
||||
options: batch.map(ep => ({
|
||||
label: `${ep.id}: ${ep.title}`,
|
||||
description: `影响: ${ep.affected_roles.join(', ')} | ${ep.enhancement}`
|
||||
}))
|
||||
}]
|
||||
})
|
||||
|
||||
b) EP-002: User Intent Validation
|
||||
影响角色:product-manager, ux-expert
|
||||
说明:明确用户需求优先级和验收标准
|
||||
// Store selections before next round
|
||||
}
|
||||
|
||||
c) EP-003: Error Handling Strategy
|
||||
影响角色:system-architect
|
||||
说明:统一异常处理和降级方案
|
||||
|
||||
支持格式:1abc 或 1a 1b 1c 或 1a,b,c
|
||||
请输入选择(可跳过输入 skip):
|
||||
// User can also skip: provide "跳过" option
|
||||
```
|
||||
|
||||
2. **Generate Clarification Questions** (based on analysis agent output):
|
||||
- ✅ **ALL questions in Chinese (所有问题必须用中文)**
|
||||
- Use 9-category taxonomy scan results
|
||||
- Prioritize most critical questions (no hard limit)
|
||||
- Each with 2-4 options + descriptions
|
||||
#### Step 2: Clarification Questions
|
||||
|
||||
3. **Interactive Clarification Loop** (max 10 questions per round):
|
||||
```markdown
|
||||
===== Clarification 问题 (第 1/2 轮) =====
|
||||
```javascript
|
||||
// Generate questions based on 9-category taxonomy scan
|
||||
// Categories: User Intent, Requirements, Architecture, UX, Feasibility, Risk, Process, Decisions, Terminology
|
||||
|
||||
【问题1 - 用户意图】MVP 阶段的核心目标是什么?
|
||||
a) 快速验证市场需求
|
||||
说明:最小功能集,快速上线获取反馈
|
||||
b) 建立技术壁垒
|
||||
说明:完善架构,为长期发展打基础
|
||||
c) 实现功能完整性
|
||||
说明:覆盖所有规划功能,延迟上线
|
||||
const clarifications = [...]; // from analysis
|
||||
const BATCH_SIZE = 4;
|
||||
|
||||
【问题2 - 架构决策】技术栈选择的优先考虑因素?
|
||||
a) 团队熟悉度
|
||||
说明:使用现有技术栈,降低学习成本
|
||||
b) 技术先进性
|
||||
说明:采用新技术,提升竞争力
|
||||
c) 生态成熟度
|
||||
说明:选择成熟方案,保证稳定性
|
||||
for (let i = 0; i < clarifications.length; i += BATCH_SIZE) {
|
||||
const batch = clarifications.slice(i, i + BATCH_SIZE);
|
||||
const currentRound = Math.floor(i / BATCH_SIZE) + 1;
|
||||
const totalRounds = Math.ceil(clarifications.length / BATCH_SIZE);
|
||||
|
||||
...(最多10个问题)
|
||||
AskUserQuestion({
|
||||
questions: batch.map(q => ({
|
||||
question: q.question,
|
||||
header: q.category.substring(0, 12),
|
||||
multiSelect: false,
|
||||
options: q.options.map(opt => ({
|
||||
label: opt.label,
|
||||
description: opt.description
|
||||
}))
|
||||
}))
|
||||
})
|
||||
|
||||
请回答 (格式: 1a 2b 3c...):
|
||||
// Store answers before next round
|
||||
}
|
||||
```
|
||||
|
||||
Wait for user input → Parse all answers in batch → Continue to next round if needed
|
||||
### Question Guidelines
|
||||
|
||||
4. **Build Update Plan**:
|
||||
```
|
||||
**Target**: 开发者(理解技术但需要从用户需求出发)
|
||||
|
||||
**Question Structure**: `[跨角色分析发现] + [需要澄清的决策点]`
|
||||
**Option Structure**: `标签:[具体方案] + 说明:[业务影响] + [技术权衡]`
|
||||
|
||||
**9-Category Taxonomy**:
|
||||
|
||||
| Category | Focus | Example Question Pattern |
|
||||
|----------|-------|--------------------------|
|
||||
| User Intent | 用户目标 | "MVP阶段核心目标?" + 验证/壁垒/完整性 |
|
||||
| Requirements | 需求细化 | "功能优先级如何排序?" + 核心/增强/可选 |
|
||||
| Architecture | 架构决策 | "技术栈选择考量?" + 熟悉度/先进性/成熟度 |
|
||||
| UX | 用户体验 | "交互复杂度取舍?" + 简洁/丰富/渐进 |
|
||||
| Feasibility | 可行性 | "资源约束下的范围?" + 最小/标准/完整 |
|
||||
| Risk | 风险管理 | "风险容忍度?" + 保守/平衡/激进 |
|
||||
| Process | 流程规范 | "迭代节奏?" + 快速/稳定/灵活 |
|
||||
| Decisions | 决策确认 | "冲突解决方案?" + 方案A/方案B/折中 |
|
||||
| Terminology | 术语统一 | "统一使用哪个术语?" + 术语A/术语B |
|
||||
|
||||
**Quality Rules**:
|
||||
|
||||
**MUST Include**:
|
||||
- ✅ All questions in Chinese (用中文提问)
|
||||
- ✅ 基于跨角色分析的具体发现
|
||||
- ✅ 选项包含业务影响说明
|
||||
- ✅ 解决实际的模糊点或冲突
|
||||
|
||||
**MUST Avoid**:
|
||||
- ❌ 与角色分析无关的通用问题
|
||||
- ❌ 重复已在 artifacts 阶段确认的内容
|
||||
- ❌ 过于细节的实现级问题
|
||||
|
||||
#### Step 3: Build Update Plan
|
||||
|
||||
```javascript
|
||||
update_plan = {
|
||||
"role1": {
|
||||
"enhancements": [EP-001, EP-003],
|
||||
"enhancements": ["EP-001", "EP-003"],
|
||||
"clarifications": [
|
||||
{"question": "...", "answer": "...", "category": "..."},
|
||||
...
|
||||
{"question": "...", "answer": "...", "category": "..."}
|
||||
]
|
||||
},
|
||||
"role2": {
|
||||
"enhancements": [EP-002],
|
||||
"enhancements": ["EP-002"],
|
||||
"clarifications": [...]
|
||||
},
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 5: Parallel Document Update Agents
|
||||
|
||||
**Parallel agent calls** (one per role needing updates):
|
||||
**Execute in parallel** (one agent per role):
|
||||
|
||||
```bash
|
||||
# Execute in parallel using single message with multiple Task calls
|
||||
|
||||
Task(conceptual-planning-agent): "
|
||||
```javascript
|
||||
// Single message with multiple Task calls for parallelism
|
||||
Task(conceptual-planning-agent, `
|
||||
## Agent Mission
|
||||
Apply user-confirmed enhancements and clarifications to {role1} analysis document
|
||||
Apply enhancements and clarifications to ${role} analysis
|
||||
|
||||
## Agent Intent
|
||||
- **Goal**: Integrate synthesis results into role-specific analysis
|
||||
- **Scope**: Update ONLY {role1}/analysis.md (isolated, no cross-role dependencies)
|
||||
- **Constraints**: Preserve original insights, add refinements without deletion
|
||||
## Input
|
||||
- role: ${role}
|
||||
- analysis_path: ${brainstorm_dir}/${role}/analysis.md
|
||||
- enhancements: ${role_enhancements}
|
||||
- clarifications: ${role_clarifications}
|
||||
- original_user_intent: ${intent}
|
||||
|
||||
## Input from Main Flow
|
||||
- role: {role1}
|
||||
- analysis_path: {brainstorm_dir}/{role1}/analysis.md
|
||||
- enhancements: [EP-001, EP-003] (user-selected improvements)
|
||||
- clarifications: [{question, answer, category}, ...] (user-confirmed answers)
|
||||
- original_user_intent: {from session metadata}
|
||||
## Flow Control Steps
|
||||
1. load_current_analysis → Read analysis file
|
||||
2. add_clarifications_section → Insert Q&A section
|
||||
3. apply_enhancements → Integrate into relevant sections
|
||||
4. resolve_contradictions → Remove conflicts
|
||||
5. enforce_terminology → Align terminology
|
||||
6. validate_intent → Verify alignment with user intent
|
||||
7. write_updated_file → Save changes
|
||||
|
||||
## Execution Instructions
|
||||
[FLOW_CONTROL]
|
||||
|
||||
### Flow Control Steps
|
||||
**AGENT RESPONSIBILITY**: Execute these update steps sequentially:
|
||||
|
||||
1. **load_current_analysis**
|
||||
- Action: Load existing role analysis document
|
||||
- Command: Read({brainstorm_dir}/{role1}/analysis.md)
|
||||
- Output: current_analysis_content
|
||||
|
||||
2. **add_clarifications_section**
|
||||
- Action: Insert Clarifications section with Q&A
|
||||
- Format: \"## Clarifications\\n### Session {date}\\n- **Q**: {question} (Category: {category})\\n **A**: {answer}\"
|
||||
- Output: analysis_with_clarifications
|
||||
|
||||
3. **apply_enhancements**
|
||||
- Action: Integrate EP-001, EP-003 into relevant sections
|
||||
- Strategy: Locate section by category (Architecture → Architecture section, UX → User Experience section)
|
||||
- Output: analysis_with_enhancements
|
||||
|
||||
4. **resolve_contradictions**
|
||||
- Action: Remove conflicts between original content and clarifications/enhancements
|
||||
- Output: contradiction_free_analysis
|
||||
|
||||
5. **enforce_terminology_consistency**
|
||||
- Action: Align all terminology with user-confirmed choices from clarifications
|
||||
- Output: terminology_consistent_analysis
|
||||
|
||||
6. **validate_user_intent_alignment**
|
||||
- Action: Verify all updates support original_user_intent
|
||||
- Output: validated_analysis
|
||||
|
||||
7. **write_updated_file**
|
||||
- Action: Save final analysis document
|
||||
- Command: Write({brainstorm_dir}/{role1}/analysis.md, validated_analysis)
|
||||
- Output: File update confirmation
|
||||
|
||||
### Output
|
||||
Updated {role1}/analysis.md with Clarifications section + enhanced content
|
||||
")
|
||||
|
||||
Task(conceptual-planning-agent): "
|
||||
## Agent Mission
|
||||
Apply user-confirmed enhancements and clarifications to {role2} analysis document
|
||||
|
||||
## Agent Intent
|
||||
- **Goal**: Integrate synthesis results into role-specific analysis
|
||||
- **Scope**: Update ONLY {role2}/analysis.md (isolated, no cross-role dependencies)
|
||||
- **Constraints**: Preserve original insights, add refinements without deletion
|
||||
|
||||
## Input from Main Flow
|
||||
- role: {role2}
|
||||
- analysis_path: {brainstorm_dir}/{role2}/analysis.md
|
||||
- enhancements: [EP-002] (user-selected improvements)
|
||||
- clarifications: [{question, answer, category}, ...] (user-confirmed answers)
|
||||
- original_user_intent: {from session metadata}
|
||||
|
||||
## Execution Instructions
|
||||
[FLOW_CONTROL]
|
||||
|
||||
### Flow Control Steps
|
||||
**AGENT RESPONSIBILITY**: Execute same 7 update steps as {role1} agent (load → clarifications → enhancements → contradictions → terminology → validation → write)
|
||||
|
||||
### Output
|
||||
Updated {role2}/analysis.md with Clarifications section + enhanced content
|
||||
")
|
||||
|
||||
# ... repeat for each role in update_plan
|
||||
## Output
|
||||
Updated ${role}/analysis.md
|
||||
`)
|
||||
```
|
||||
|
||||
**Agent Characteristics**:
|
||||
- **Intent**: Integrate user-confirmed synthesis results (NOT generate new analysis)
|
||||
- **Isolation**: Each agent updates exactly ONE role (parallel execution safe)
|
||||
- **Context**: Minimal - receives only role-specific enhancements + clarifications
|
||||
- **Dependencies**: Zero cross-agent dependencies (full parallelism)
|
||||
- **Isolation**: Each agent updates exactly ONE role (parallel safe)
|
||||
- **Dependencies**: Zero cross-agent dependencies
|
||||
- **Validation**: All updates must align with original_user_intent
|
||||
|
||||
### Phase 6: Completion & Metadata Update
|
||||
### Phase 6: Finalization
|
||||
|
||||
**Main flow finalizes**:
|
||||
#### Step 1: Update Context Package
|
||||
|
||||
```javascript
|
||||
// Sync updated analyses to context-package.json
|
||||
const context_pkg = Read(".workflow/active/WFS-{session}/.process/context-package.json")
|
||||
|
||||
// Update guidance-specification if exists
|
||||
// Update synthesis-specification if exists
|
||||
// Re-read all role analysis files
|
||||
// Update metadata timestamps
|
||||
|
||||
Write(context_pkg_path, JSON.stringify(context_pkg))
|
||||
```
|
||||
|
||||
#### Step 2: Update Session Metadata
|
||||
|
||||
1. Wait for all parallel agents to complete
|
||||
2. Update workflow-session.json:
|
||||
```json
|
||||
{
|
||||
"phases": {
|
||||
@@ -330,15 +323,13 @@ Updated {role2}/analysis.md with Clarifications section + enhanced content
|
||||
"completed_at": "timestamp",
|
||||
"participating_roles": [...],
|
||||
"clarification_results": {
|
||||
"enhancements_applied": ["EP-001", "EP-002", ...],
|
||||
"enhancements_applied": ["EP-001", "EP-002"],
|
||||
"questions_asked": 3,
|
||||
"categories_clarified": ["Architecture", "UX", ...],
|
||||
"roles_updated": ["role1", "role2", ...],
|
||||
"outstanding_items": []
|
||||
"categories_clarified": ["Architecture", "UX"],
|
||||
"roles_updated": ["role1", "role2"]
|
||||
},
|
||||
"quality_metrics": {
|
||||
"user_intent_alignment": "validated",
|
||||
"requirement_coverage": "comprehensive",
|
||||
"ambiguity_resolution": "complete",
|
||||
"terminology_consistency": "enforced"
|
||||
}
|
||||
@@ -347,7 +338,8 @@ Updated {role2}/analysis.md with Clarifications section + enhanced content
|
||||
}
|
||||
```
|
||||
|
||||
3. Generate completion report (show to user):
|
||||
#### Step 3: Completion Report
|
||||
|
||||
```markdown
|
||||
## ✅ Clarification Complete
|
||||
|
||||
@@ -359,9 +351,11 @@ Updated {role2}/analysis.md with Clarifications section + enhanced content
|
||||
✅ PROCEED: `/workflow:plan --session WFS-{session-id}`
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Output
|
||||
|
||||
**Location**: `.workflow/active/WFS-{session}/.brainstorming/[role]/analysis*.md` (in-place updates)
|
||||
**Location**: `.workflow/active/WFS-{session}/.brainstorming/[role]/analysis*.md`
|
||||
|
||||
**Updated Structure**:
|
||||
```markdown
|
||||
@@ -381,116 +375,24 @@ Updated {role2}/analysis.md with Clarifications section + enhanced content
|
||||
- Ambiguities resolved, placeholders removed
|
||||
- Consistent terminology
|
||||
|
||||
### Phase 6: Update Context Package
|
||||
|
||||
**Purpose**: Sync updated role analyses to context-package.json to avoid stale cache
|
||||
|
||||
**Operations**:
|
||||
```bash
|
||||
context_pkg_path = ".workflow/active/WFS-{session}/.process/context-package.json"
|
||||
|
||||
# 1. Read existing package
|
||||
context_pkg = Read(context_pkg_path)
|
||||
|
||||
# 2. Re-read brainstorm artifacts (now with synthesis enhancements)
|
||||
brainstorm_dir = ".workflow/active/WFS-{session}/.brainstorming"
|
||||
|
||||
# 2.1 Update guidance-specification if exists
|
||||
IF exists({brainstorm_dir}/guidance-specification.md):
|
||||
context_pkg.brainstorm_artifacts.guidance_specification.content = Read({brainstorm_dir}/guidance-specification.md)
|
||||
context_pkg.brainstorm_artifacts.guidance_specification.updated_at = NOW()
|
||||
|
||||
# 2.2 Update synthesis-specification if exists
|
||||
IF exists({brainstorm_dir}/synthesis-specification.md):
|
||||
IF context_pkg.brainstorm_artifacts.synthesis_output:
|
||||
context_pkg.brainstorm_artifacts.synthesis_output.content = Read({brainstorm_dir}/synthesis-specification.md)
|
||||
context_pkg.brainstorm_artifacts.synthesis_output.updated_at = NOW()
|
||||
|
||||
# 2.3 Re-read all role analysis files
|
||||
role_analysis_files = Glob({brainstorm_dir}/*/analysis*.md)
|
||||
context_pkg.brainstorm_artifacts.role_analyses = []
|
||||
|
||||
FOR file IN role_analysis_files:
|
||||
role_name = extract_role_from_path(file) # e.g., "ui-designer"
|
||||
relative_path = file.replace({brainstorm_dir}/, "")
|
||||
|
||||
context_pkg.brainstorm_artifacts.role_analyses.push({
|
||||
"role": role_name,
|
||||
"files": [{
|
||||
"path": relative_path,
|
||||
"type": "primary",
|
||||
"content": Read(file),
|
||||
"updated_at": NOW()
|
||||
}]
|
||||
})
|
||||
|
||||
# 3. Update metadata
|
||||
context_pkg.metadata.updated_at = NOW()
|
||||
context_pkg.metadata.synthesis_timestamp = NOW()
|
||||
|
||||
# 4. Write back
|
||||
Write(context_pkg_path, JSON.stringify(context_pkg, indent=2))
|
||||
|
||||
REPORT: "✅ Updated context-package.json with synthesis results"
|
||||
```
|
||||
|
||||
**TodoWrite Update**:
|
||||
```json
|
||||
{"content": "Update context package with synthesis results", "status": "completed", "activeForm": "Updating context package"}
|
||||
```
|
||||
|
||||
## Session Metadata
|
||||
|
||||
Update `workflow-session.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"phases": {
|
||||
"BRAINSTORM": {
|
||||
"status": "clarification_completed",
|
||||
"clarification_completed": true,
|
||||
"completed_at": "timestamp",
|
||||
"participating_roles": ["product-manager", "system-architect", ...],
|
||||
"clarification_results": {
|
||||
"questions_asked": 3,
|
||||
"categories_clarified": ["Architecture & Design", ...],
|
||||
"roles_updated": ["system-architect", "ui-designer", ...],
|
||||
"outstanding_items": []
|
||||
},
|
||||
"quality_metrics": {
|
||||
"user_intent_alignment": "validated",
|
||||
"requirement_coverage": "comprehensive",
|
||||
"ambiguity_resolution": "complete",
|
||||
"terminology_consistency": "enforced",
|
||||
"decision_transparency": "documented"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
**Content**:
|
||||
- All role analyses loaded/analyzed
|
||||
- Cross-role analysis (consensus, conflicts, gaps)
|
||||
- 9-category ambiguity scan
|
||||
- Questions prioritized
|
||||
- Clarifications documented
|
||||
- ✅ All role analyses loaded/analyzed
|
||||
- ✅ Cross-role analysis (consensus, conflicts, gaps)
|
||||
- ✅ 9-category ambiguity scan
|
||||
- ✅ Questions prioritized
|
||||
|
||||
**Analysis**:
|
||||
- User intent validated
|
||||
- Cross-role synthesis complete
|
||||
- Ambiguities resolved
|
||||
- Correct roles updated
|
||||
- Terminology consistent
|
||||
- Contradictions removed
|
||||
- ✅ User intent validated
|
||||
- ✅ Cross-role synthesis complete
|
||||
- ✅ Ambiguities resolved
|
||||
- ✅ Terminology consistent
|
||||
|
||||
**Documents**:
|
||||
- Clarifications section formatted
|
||||
- Sections reflect answers
|
||||
- No placeholders (TODO/TBD)
|
||||
- Valid Markdown
|
||||
- Cross-references maintained
|
||||
|
||||
|
||||
- ✅ Clarifications section formatted
|
||||
- ✅ Sections reflect answers
|
||||
- ✅ No placeholders (TODO/TBD)
|
||||
- ✅ Valid Markdown
|
||||
|
||||
@@ -98,10 +98,10 @@ Analyze project for workflow initialization and generate .workflow/project.json.
|
||||
Generate complete project.json with:
|
||||
- project_name: ${projectName}
|
||||
- initialized_at: current ISO timestamp
|
||||
- overview: {description, technology_stack, architecture, key_components, entry_points, metrics}
|
||||
- overview: {description, technology_stack, architecture, key_components}
|
||||
- features: ${regenerate ? 'preserve from backup' : '[] (empty)'}
|
||||
- development_index: ${regenerate ? 'preserve from backup' : '{feature: [], enhancement: [], bugfix: [], refactor: [], docs: []}'}
|
||||
- statistics: ${regenerate ? 'preserve from backup' : '{total_features: 0, total_sessions: 0, last_updated}'}
|
||||
- memory_resources: {skills, documentation, module_docs, gaps, last_scanned}
|
||||
- _metadata: {initialized_by: "cli-explore-agent", analysis_timestamp, analysis_mode}
|
||||
|
||||
## Analysis Requirements
|
||||
@@ -118,28 +118,11 @@ Generate complete project.json with:
|
||||
- Patterns: singleton, factory, repository
|
||||
- Key components: 5-10 modules {name, path, description, importance}
|
||||
|
||||
**Metrics**:
|
||||
- total_files: Source files (exclude tests/configs)
|
||||
- lines_of_code: Use find + wc -l
|
||||
- module_count: Use ~/.claude/scripts/get_modules_by_depth.sh
|
||||
- complexity: low | medium | high
|
||||
|
||||
**Entry Points**:
|
||||
- main: index.ts, main.py, main.go
|
||||
- cli_commands: package.json scripts, Makefile targets
|
||||
- api_endpoints: HTTP/REST routes (if applicable)
|
||||
|
||||
**Memory Resources**:
|
||||
- skills: Scan .claude/skills/ → [{name, type, path}]
|
||||
- documentation: Scan .workflow/docs/ → [{name, path, has_readme, has_architecture}]
|
||||
- module_docs: Find **/CLAUDE.md (exclude node_modules, .git)
|
||||
- gaps: Identify missing resources
|
||||
|
||||
## Execution
|
||||
1. Structural scan: get_modules_by_depth.sh, find, wc -l
|
||||
2. Semantic analysis: Gemini for patterns/architecture
|
||||
3. Synthesis: Merge findings
|
||||
4. ${regenerate ? 'Merge with preserved features/statistics from .workflow/project.json.backup' : ''}
|
||||
4. ${regenerate ? 'Merge with preserved features/development_index/statistics from .workflow/project.json.backup' : ''}
|
||||
5. Write JSON: Write('.workflow/project.json', jsonContent)
|
||||
6. Report: Return brief completion summary
|
||||
|
||||
@@ -168,17 +151,6 @@ Frameworks: ${projectJson.overview.technology_stack.frameworks.join(', ')}
|
||||
Style: ${projectJson.overview.architecture.style}
|
||||
Components: ${projectJson.overview.key_components.length} core modules
|
||||
|
||||
### Metrics
|
||||
Files: ${projectJson.overview.metrics.total_files}
|
||||
LOC: ${projectJson.overview.metrics.lines_of_code}
|
||||
Complexity: ${projectJson.overview.metrics.complexity}
|
||||
|
||||
### Memory Resources
|
||||
SKILL Packages: ${projectJson.memory_resources.skills.length}
|
||||
Documentation: ${projectJson.memory_resources.documentation.length}
|
||||
Module Docs: ${projectJson.memory_resources.module_docs.length}
|
||||
Gaps: ${projectJson.memory_resources.gaps.join(', ') || 'none'}
|
||||
|
||||
---
|
||||
Project state: .workflow/project.json
|
||||
${regenerate ? 'Backup: .workflow/project.json.backup' : ''}
|
||||
|
||||
@@ -556,6 +556,58 @@ codex --full-auto exec "[Verify plan acceptance criteria at ${plan.json}]" --ski
|
||||
- `@{plan.json}` → `@${executionContext.session.artifacts.plan}`
|
||||
- `[@{exploration.json}]` → exploration files from artifacts (if exists)
|
||||
|
||||
### Step 6: Update Development Index
|
||||
|
||||
**Trigger**: After all executions complete (regardless of code review)
|
||||
|
||||
**Skip Condition**: Skip if `.workflow/project.json` does not exist
|
||||
|
||||
**Operations**:
|
||||
```javascript
|
||||
const projectJsonPath = '.workflow/project.json'
|
||||
if (!fileExists(projectJsonPath)) return // Silent skip
|
||||
|
||||
const projectJson = JSON.parse(Read(projectJsonPath))
|
||||
|
||||
// Initialize if needed
|
||||
if (!projectJson.development_index) {
|
||||
projectJson.development_index = { feature: [], enhancement: [], bugfix: [], refactor: [], docs: [] }
|
||||
}
|
||||
|
||||
// Detect category from keywords
|
||||
function detectCategory(text) {
|
||||
text = text.toLowerCase()
|
||||
if (/\b(fix|bug|error|issue|crash)\b/.test(text)) return 'bugfix'
|
||||
if (/\b(refactor|cleanup|reorganize)\b/.test(text)) return 'refactor'
|
||||
if (/\b(doc|readme|comment)\b/.test(text)) return 'docs'
|
||||
if (/\b(add|new|create|implement)\b/.test(text)) return 'feature'
|
||||
return 'enhancement'
|
||||
}
|
||||
|
||||
// Detect sub_feature from task file paths
|
||||
function detectSubFeature(tasks) {
|
||||
const dirs = tasks.map(t => t.file?.split('/').slice(-2, -1)[0]).filter(Boolean)
|
||||
const counts = dirs.reduce((a, d) => { a[d] = (a[d] || 0) + 1; return a }, {})
|
||||
return Object.entries(counts).sort((a, b) => b[1] - a[1])[0]?.[0] || 'general'
|
||||
}
|
||||
|
||||
const category = detectCategory(`${planObject.summary} ${planObject.approach}`)
|
||||
const entry = {
|
||||
title: planObject.summary.slice(0, 60),
|
||||
sub_feature: detectSubFeature(planObject.tasks),
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
description: planObject.approach.slice(0, 100),
|
||||
status: previousExecutionResults.every(r => r.status === 'completed') ? 'completed' : 'partial',
|
||||
session_id: executionContext?.session?.id || null
|
||||
}
|
||||
|
||||
projectJson.development_index[category].push(entry)
|
||||
projectJson.statistics.last_updated = new Date().toISOString()
|
||||
Write(projectJsonPath, JSON.stringify(projectJson, null, 2))
|
||||
|
||||
console.log(`✓ Development index: [${category}] ${entry.title}`)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
**Input Modes**: In-memory (lite-plan), prompt (standalone), file (JSON/text)
|
||||
|
||||
@@ -43,11 +43,11 @@ Phase 1: Bug Analysis & Diagnosis
|
||||
|- needsDiagnosis=true -> Launch parallel cli-explore-agents (1-4 based on severity)
|
||||
+- needsDiagnosis=false (hotfix) -> Skip directly to Phase 3 (Fix Planning)
|
||||
|
||||
Phase 2: Clarification (optional)
|
||||
Phase 2: Clarification (optional, multi-round)
|
||||
|- Aggregate clarification_needs from all diagnosis angles
|
||||
|- Deduplicate similar questions
|
||||
+- Decision:
|
||||
|- Has clarifications -> AskUserQuestion (max 4 questions)
|
||||
|- Has clarifications -> AskUserQuestion (max 4 questions per round, multiple rounds allowed)
|
||||
+- No clarifications -> Skip to Phase 3
|
||||
|
||||
Phase 3: Fix Planning (NO CODE EXECUTION - planning only)
|
||||
@@ -71,15 +71,18 @@ Phase 5: Dispatch
|
||||
|
||||
### Phase 1: Intelligent Multi-Angle Diagnosis
|
||||
|
||||
**Session Setup**:
|
||||
**Session Setup** (MANDATORY - follow exactly):
|
||||
```javascript
|
||||
// Helper: Get UTC+8 (China Standard Time) ISO string
|
||||
const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString()
|
||||
|
||||
const bugSlug = bug_description.toLowerCase().replace(/[^a-z0-9]+/g, '-').substring(0, 40)
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
||||
const shortTimestamp = timestamp.substring(0, 19).replace('T', '-')
|
||||
const sessionId = `${bugSlug}-${shortTimestamp}`
|
||||
const dateStr = getUtc8ISOString().substring(0, 10) // Format: 2025-11-29
|
||||
|
||||
const sessionId = `${bugSlug}-${dateStr}` // e.g., "user-avatar-upload-fails-2025-11-29"
|
||||
const sessionFolder = `.workflow/.lite-fix/${sessionId}`
|
||||
|
||||
bash(`mkdir -p ${sessionFolder}`)
|
||||
bash(`mkdir -p ${sessionFolder} && test -d ${sessionFolder} && echo "SUCCESS: ${sessionFolder}" || echo "FAILED: ${sessionFolder}"`)
|
||||
```
|
||||
|
||||
**Diagnosis Decision Logic**:
|
||||
@@ -213,7 +216,7 @@ Execute **${angle}** diagnosis for bug root cause analysis. Analyze codebase fro
|
||||
- fix_hints: Suggested fix approaches from ${angle} viewpoint
|
||||
- dependencies: Dependencies relevant to ${angle} diagnosis
|
||||
- constraints: ${angle}-specific limitations affecting fix
|
||||
- clarification_needs: ${angle}-related ambiguities (with options array)
|
||||
- clarification_needs: ${angle}-related ambiguities (options array + recommended index)
|
||||
- _metadata.diagnosis_angle: "${angle}"
|
||||
- _metadata.diagnosis_index: ${index + 1}
|
||||
|
||||
@@ -225,7 +228,7 @@ Execute **${angle}** diagnosis for bug root cause analysis. Analyze codebase fro
|
||||
- [ ] Fix hints are actionable (specific code changes, not generic advice)
|
||||
- [ ] Reproduction steps are verifiable
|
||||
- [ ] JSON output follows schema exactly
|
||||
- [ ] clarification_needs includes options array
|
||||
- [ ] clarification_needs includes options + recommended
|
||||
|
||||
## Output
|
||||
Write: ${sessionFolder}/diagnosis-${angle}.json
|
||||
@@ -248,7 +251,7 @@ const diagnosisFiles = bash(`find ${sessionFolder} -name "diagnosis-*.json" -typ
|
||||
const diagnosisManifest = {
|
||||
session_id: sessionId,
|
||||
bug_description: bug_description,
|
||||
timestamp: new Date().toISOString(),
|
||||
timestamp: getUtc8ISOString(),
|
||||
severity: severity,
|
||||
diagnosis_count: diagnosisFiles.length,
|
||||
diagnoses: diagnosisFiles.map(file => {
|
||||
@@ -284,10 +287,12 @@ Angles diagnosed: ${diagnosisManifest.diagnoses.map(d => d.angle).join(', ')}
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Clarification (Optional)
|
||||
### Phase 2: Clarification (Optional, Multi-Round)
|
||||
|
||||
**Skip if**: No diagnosis or `clarification_needs` is empty across all diagnoses
|
||||
|
||||
**⚠️ CRITICAL**: AskUserQuestion tool limits max 4 questions per call. **MUST execute multiple rounds** to exhaust all clarification needs - do NOT stop at round 1.
|
||||
|
||||
**Aggregate clarification needs from all diagnosis angles**:
|
||||
```javascript
|
||||
// Load manifest and all diagnosis files
|
||||
@@ -324,18 +329,35 @@ function deduplicateClarifications(clarifications) {
|
||||
|
||||
const uniqueClarifications = deduplicateClarifications(allClarifications)
|
||||
|
||||
// Multi-round clarification: batch questions (max 4 per round)
|
||||
// ⚠️ MUST execute ALL rounds until uniqueClarifications exhausted
|
||||
if (uniqueClarifications.length > 0) {
|
||||
AskUserQuestion({
|
||||
questions: uniqueClarifications.map(need => ({
|
||||
question: `[${need.source_angle}] ${need.question}\n\nContext: ${need.context}`,
|
||||
header: need.source_angle,
|
||||
multiSelect: false,
|
||||
options: need.options.map(opt => ({
|
||||
label: opt,
|
||||
description: `Use ${opt} approach`
|
||||
const BATCH_SIZE = 4
|
||||
const totalRounds = Math.ceil(uniqueClarifications.length / BATCH_SIZE)
|
||||
|
||||
for (let i = 0; i < uniqueClarifications.length; i += BATCH_SIZE) {
|
||||
const batch = uniqueClarifications.slice(i, i + BATCH_SIZE)
|
||||
const currentRound = Math.floor(i / BATCH_SIZE) + 1
|
||||
|
||||
console.log(`### Clarification Round ${currentRound}/${totalRounds}`)
|
||||
|
||||
AskUserQuestion({
|
||||
questions: batch.map(need => ({
|
||||
question: `[${need.source_angle}] ${need.question}\n\nContext: ${need.context}`,
|
||||
header: need.source_angle,
|
||||
multiSelect: false,
|
||||
options: need.options.map((opt, index) => {
|
||||
const isRecommended = need.recommended === index
|
||||
return {
|
||||
label: isRecommended ? `${opt} ★` : opt,
|
||||
description: isRecommended ? `Use ${opt} approach (Recommended)` : `Use ${opt} approach`
|
||||
}
|
||||
})
|
||||
}))
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
// Store batch responses in clarificationContext before next round
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -364,7 +386,7 @@ const fixPlan = {
|
||||
recommended_execution: "Agent",
|
||||
severity: severity,
|
||||
risk_level: "...",
|
||||
_metadata: { timestamp: new Date().toISOString(), source: "direct-planning", planning_mode: "direct" }
|
||||
_metadata: { timestamp: getUtc8ISOString(), source: "direct-planning", planning_mode: "direct" }
|
||||
}
|
||||
|
||||
// Step 3: Write fix-plan to session folder
|
||||
@@ -565,7 +587,7 @@ SlashCommand(command="/workflow:lite-execute --in-memory --mode bugfix")
|
||||
## Session Folder Structure
|
||||
|
||||
```
|
||||
.workflow/.lite-fix/{bug-slug}-{timestamp}/
|
||||
.workflow/.lite-fix/{bug-slug}-{YYYY-MM-DD}/
|
||||
|- diagnosis-{angle1}.json # Diagnosis angle 1
|
||||
|- diagnosis-{angle2}.json # Diagnosis angle 2
|
||||
|- diagnosis-{angle3}.json # Diagnosis angle 3 (if applicable)
|
||||
|
||||
@@ -43,11 +43,11 @@ Phase 1: Task Analysis & Exploration
|
||||
├─ needsExploration=true → Launch parallel cli-explore-agents (1-4 based on complexity)
|
||||
└─ needsExploration=false → Skip to Phase 2/3
|
||||
|
||||
Phase 2: Clarification (optional)
|
||||
Phase 2: Clarification (optional, multi-round)
|
||||
├─ Aggregate clarification_needs from all exploration angles
|
||||
├─ Deduplicate similar questions
|
||||
└─ Decision:
|
||||
├─ Has clarifications → AskUserQuestion (max 4 questions)
|
||||
├─ Has clarifications → AskUserQuestion (max 4 questions per round, multiple rounds allowed)
|
||||
└─ No clarifications → Skip to Phase 3
|
||||
|
||||
Phase 3: Planning (NO CODE EXECUTION - planning only)
|
||||
@@ -71,15 +71,18 @@ Phase 5: Dispatch
|
||||
|
||||
### Phase 1: Intelligent Multi-Angle Exploration
|
||||
|
||||
**Session Setup**:
|
||||
**Session Setup** (MANDATORY - follow exactly):
|
||||
```javascript
|
||||
// Helper: Get UTC+8 (China Standard Time) ISO string
|
||||
const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString()
|
||||
|
||||
const taskSlug = task_description.toLowerCase().replace(/[^a-z0-9]+/g, '-').substring(0, 40)
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
||||
const shortTimestamp = timestamp.substring(0, 19).replace('T', '-')
|
||||
const sessionId = `${taskSlug}-${shortTimestamp}`
|
||||
const dateStr = getUtc8ISOString().substring(0, 10) // Format: 2025-11-29
|
||||
|
||||
const sessionId = `${taskSlug}-${dateStr}` // e.g., "implement-jwt-refresh-2025-11-29"
|
||||
const sessionFolder = `.workflow/.lite-plan/${sessionId}`
|
||||
|
||||
bash(`mkdir -p ${sessionFolder}`)
|
||||
bash(`mkdir -p ${sessionFolder} && test -d ${sessionFolder} && echo "SUCCESS: ${sessionFolder}" || echo "FAILED: ${sessionFolder}"`)
|
||||
```
|
||||
|
||||
**Exploration Decision Logic**:
|
||||
@@ -203,7 +206,7 @@ Execute **${angle}** exploration for task planning context. Analyze codebase fro
|
||||
- dependencies: Dependencies relevant to ${angle}
|
||||
- integration_points: Where to integrate from ${angle} viewpoint (include file:line locations)
|
||||
- constraints: ${angle}-specific limitations/conventions
|
||||
- clarification_needs: ${angle}-related ambiguities (with options array)
|
||||
- clarification_needs: ${angle}-related ambiguities (options array + recommended index)
|
||||
- _metadata.exploration_angle: "${angle}"
|
||||
|
||||
## Success Criteria
|
||||
@@ -214,7 +217,7 @@ Execute **${angle}** exploration for task planning context. Analyze codebase fro
|
||||
- [ ] Integration points include file:line locations
|
||||
- [ ] Constraints are project-specific to ${angle}
|
||||
- [ ] JSON output follows schema exactly
|
||||
- [ ] clarification_needs includes options array
|
||||
- [ ] clarification_needs includes options + recommended
|
||||
|
||||
## Output
|
||||
Write: ${sessionFolder}/exploration-${angle}.json
|
||||
@@ -237,7 +240,7 @@ const explorationFiles = bash(`find ${sessionFolder} -name "exploration-*.json"
|
||||
const explorationManifest = {
|
||||
session_id: sessionId,
|
||||
task_description: task_description,
|
||||
timestamp: new Date().toISOString(),
|
||||
timestamp: getUtc8ISOString(),
|
||||
complexity: complexity,
|
||||
exploration_count: explorationCount,
|
||||
explorations: explorationFiles.map(file => {
|
||||
@@ -273,10 +276,12 @@ Angles explored: ${explorationManifest.explorations.map(e => e.angle).join(', ')
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Clarification (Optional)
|
||||
### Phase 2: Clarification (Optional, Multi-Round)
|
||||
|
||||
**Skip if**: No exploration or `clarification_needs` is empty across all explorations
|
||||
|
||||
**⚠️ CRITICAL**: AskUserQuestion tool limits max 4 questions per call. **MUST execute multiple rounds** to exhaust all clarification needs - do NOT stop at round 1.
|
||||
|
||||
**Aggregate clarification needs from all exploration angles**:
|
||||
```javascript
|
||||
// Load manifest and all exploration files
|
||||
@@ -299,32 +304,40 @@ explorations.forEach(exp => {
|
||||
}
|
||||
})
|
||||
|
||||
// Deduplicate by question similarity
|
||||
function deduplicateClarifications(clarifications) {
|
||||
const unique = []
|
||||
clarifications.forEach(c => {
|
||||
const isDuplicate = unique.some(u =>
|
||||
u.question.toLowerCase() === c.question.toLowerCase()
|
||||
)
|
||||
if (!isDuplicate) unique.push(c)
|
||||
})
|
||||
return unique
|
||||
}
|
||||
// Deduplicate exact same questions only
|
||||
const seen = new Set()
|
||||
const dedupedClarifications = allClarifications.filter(c => {
|
||||
const key = c.question.toLowerCase()
|
||||
if (seen.has(key)) return false
|
||||
seen.add(key)
|
||||
return true
|
||||
})
|
||||
|
||||
const uniqueClarifications = deduplicateClarifications(allClarifications)
|
||||
// Multi-round clarification: batch questions (max 4 per round)
|
||||
if (dedupedClarifications.length > 0) {
|
||||
const BATCH_SIZE = 4
|
||||
const totalRounds = Math.ceil(dedupedClarifications.length / BATCH_SIZE)
|
||||
|
||||
if (uniqueClarifications.length > 0) {
|
||||
AskUserQuestion({
|
||||
questions: uniqueClarifications.map(need => ({
|
||||
question: `[${need.source_angle}] ${need.question}\n\nContext: ${need.context}`,
|
||||
header: need.source_angle,
|
||||
multiSelect: false,
|
||||
options: need.options.map(opt => ({
|
||||
label: opt,
|
||||
description: `Use ${opt} approach`
|
||||
for (let i = 0; i < dedupedClarifications.length; i += BATCH_SIZE) {
|
||||
const batch = dedupedClarifications.slice(i, i + BATCH_SIZE)
|
||||
const currentRound = Math.floor(i / BATCH_SIZE) + 1
|
||||
|
||||
console.log(`### Clarification Round ${currentRound}/${totalRounds}`)
|
||||
|
||||
AskUserQuestion({
|
||||
questions: batch.map(need => ({
|
||||
question: `[${need.source_angle}] ${need.question}\n\nContext: ${need.context}`,
|
||||
header: need.source_angle.substring(0, 12),
|
||||
multiSelect: false,
|
||||
options: need.options.map((opt, index) => ({
|
||||
label: need.recommended === index ? `${opt} ★` : opt,
|
||||
description: need.recommended === index ? `Recommended` : `Use ${opt}`
|
||||
}))
|
||||
}))
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
// Store batch responses in clarificationContext before next round
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -351,7 +364,7 @@ const plan = {
|
||||
estimated_time: "...",
|
||||
recommended_execution: "Agent",
|
||||
complexity: "Low",
|
||||
_metadata: { timestamp: new Date().toISOString(), source: "direct-planning", planning_mode: "direct" }
|
||||
_metadata: { timestamp: getUtc8ISOString(), source: "direct-planning", planning_mode: "direct" }
|
||||
}
|
||||
|
||||
// Step 3: Write plan to session folder
|
||||
@@ -549,7 +562,7 @@ SlashCommand(command="/workflow:lite-execute --in-memory")
|
||||
## Session Folder Structure
|
||||
|
||||
```
|
||||
.workflow/.lite-plan/{task-slug}-{timestamp}/
|
||||
.workflow/.lite-plan/{task-slug}-{YYYY-MM-DD}/
|
||||
├── exploration-{angle1}.json # Exploration angle 1
|
||||
├── exploration-{angle2}.json # Exploration angle 2
|
||||
├── exploration-{angle3}.json # Exploration angle 3 (if applicable)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: plan
|
||||
description: 5-phase planning workflow with action-planning-agent task generation, outputs IMPL_PLAN.md and task JSONs with optional CLI auto-execution
|
||||
argument-hint: "[--cli-execute] \"text description\"|file.md"
|
||||
description: 5-phase planning workflow with action-planning-agent task generation, outputs IMPL_PLAN.md and task JSONs
|
||||
argument-hint: "\"text description\"|file.md"
|
||||
allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
---
|
||||
|
||||
@@ -69,7 +69,7 @@ Phase 3: Conflict Resolution (conditional)
|
||||
└─ conflict_risk < medium → Skip to Phase 4
|
||||
|
||||
Phase 4: Task Generation
|
||||
└─ /workflow:tools:task-generate-agent --session sessionId [--cli-execute]
|
||||
└─ /workflow:tools:task-generate-agent --session sessionId
|
||||
└─ Output: IMPL_PLAN.md, task JSONs, TODO_LIST.md
|
||||
|
||||
Return:
|
||||
@@ -273,15 +273,10 @@ SlashCommand(command="/workflow:tools:conflict-resolution --session [sessionId]
|
||||
**Step 4.1: Dispatch** - Generate implementation plan and task JSONs
|
||||
|
||||
```javascript
|
||||
// Default (agent mode)
|
||||
SlashCommand(command="/workflow:tools:task-generate-agent --session [sessionId]")
|
||||
|
||||
// With CLI execution (if --cli-execute flag present)
|
||||
SlashCommand(command="/workflow:tools:task-generate-agent --session [sessionId] --cli-execute")
|
||||
```
|
||||
|
||||
**Flag**:
|
||||
- `--cli-execute`: Generate tasks with Codex execution commands
|
||||
**CLI Execution Note**: CLI tool usage is now determined semantically by action-planning-agent based on user's task description. If user specifies "use Codex/Gemini/Qwen for X", the agent embeds `command` fields in relevant `implementation_approach` steps.
|
||||
|
||||
**Input**: `sessionId` from Phase 1
|
||||
|
||||
@@ -423,7 +418,7 @@ Phase 3: conflict-resolution [AUTO-TRIGGERED if conflict_risk ≥ medium]
|
||||
↓ Output: Modified brainstorm artifacts (NO report file)
|
||||
↓ Skip if conflict_risk is none/low → proceed directly to Phase 4
|
||||
↓
|
||||
Phase 4: task-generate-agent --session sessionId [--cli-execute]
|
||||
Phase 4: task-generate-agent --session sessionId
|
||||
↓ Input: sessionId + resolved brainstorm artifacts + session memory
|
||||
↓ Output: IMPL_PLAN.md, task JSONs, TODO_LIST.md
|
||||
↓
|
||||
@@ -504,9 +499,7 @@ Return summary to user
|
||||
- **If conflict_risk ≥ medium**: Launch Phase 3 conflict-resolution with sessionId and contextPath
|
||||
- Wait for Phase 3 to finish executing (if executed), verify CONFLICT_RESOLUTION.md created
|
||||
- **If conflict_risk is none/low**: Skip Phase 3, proceed directly to Phase 4
|
||||
- **Build Phase 4 command**:
|
||||
- Base command: `/workflow:tools:task-generate-agent --session [sessionId]`
|
||||
- Add `--cli-execute` if flag present
|
||||
- **Build Phase 4 command**: `/workflow:tools:task-generate-agent --session [sessionId]`
|
||||
- Pass session ID to Phase 4 command
|
||||
- Verify all Phase 4 outputs
|
||||
- Update TodoWrite after each phase (dynamically adjust for Phase 3 presence)
|
||||
|
||||
@@ -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
|
||||
2. **Multi-stage Coordination**: Supports complex parallel + serial execution with dependency management
|
||||
3. **Conservative Safety**: Mandatory test verification with automatic rollback on failure
|
||||
4. **Real-time Visibility**: Dashboard shows planning progress, stage timeline, and active agents
|
||||
5. **Resume Support**: Checkpoint-based recovery for interrupted sessions
|
||||
4. **Resume Support**: Checkpoint-based recovery for interrupted sessions
|
||||
|
||||
### Orchestrator Boundary (CRITICAL)
|
||||
- **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
|
||||
└─ 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)
|
||||
├─ Analyze findings for patterns and dependencies
|
||||
├─ Group by file + dimension + root cause similarity
|
||||
├─ Determine execution strategy (parallel/serial/hybrid)
|
||||
├─ Generate fix timeline with stages
|
||||
└─ Output: fix-plan.json (dashboard auto-polls for status)
|
||||
└─ Output: fix-plan.json
|
||||
|
||||
Phase 3: Execution Orchestration (Stage-based)
|
||||
For each timeline stage:
|
||||
@@ -198,12 +197,10 @@ if (result.passRate < 100%) {
|
||||
- Session creation: Generate fix-session-id (`fix-{timestamp}`)
|
||||
- Directory structure: Create `{review-dir}/fixes/{fix-session-id}/` with subdirectories
|
||||
- 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
|
||||
|
||||
**Phase 2: Planning Coordination**
|
||||
- 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)
|
||||
- Load plan into memory for execution phase
|
||||
- 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)
|
||||
- Handle agent failures gracefully (mark group as failed, continue)
|
||||
- Advance to next stage only when current stage complete
|
||||
- Dashboard polls and aggregates fix-progress-{N}.json files for display
|
||||
|
||||
**Phase 4: Completion & Aggregation**
|
||||
- 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
|
||||
- Remove active-fix-session.json
|
||||
- TodoWrite completion: Mark all phases done
|
||||
- Output summary to user with dashboard link
|
||||
- Output summary to user
|
||||
|
||||
**Phase 5: Session Completion (Optional)**
|
||||
- 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."
|
||||
- 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
|
||||
|
||||
```
|
||||
.workflow/active/WFS-{session-id}/.review/
|
||||
├── fix-export-{timestamp}.json # Exported findings (input)
|
||||
└── 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-progress-1.json # Group 1 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**:
|
||||
- **Orchestrator**: `fix-dashboard.html` (generated once from template during Phase 1)
|
||||
- **Planning Agent**: `fix-plan.json` (with metadata), all `fix-progress-*.json` (initial state)
|
||||
- **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
|
||||
@@ -347,7 +300,7 @@ For each group (G1, G2, G3, ...), generate fix-progress-{N}.json following templ
|
||||
- Flow control: Empty implementation_approach 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
|
||||
|
||||
@@ -419,7 +372,7 @@ Task({
|
||||
description: `Fix ${group.findings.length} issues: ${group.group_name}`,
|
||||
prompt: `
|
||||
## 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
|
||||
- Group ID: ${group.group_id}
|
||||
@@ -549,7 +502,6 @@ When all findings processed:
|
||||
|
||||
### Progress File Updates
|
||||
- **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
|
||||
- **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
|
||||
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
|
||||
4. **Monitor Dashboard**: Real-time stage timeline and agent status provide execution visibility
|
||||
5. **Resume Support**: Fix sessions can resume from checkpoints after interruption
|
||||
6. **Manual Review**: Always review failed fixes manually - may require architectural changes
|
||||
7. **Incremental Fixing**: Start with small batches (5-10 findings) before large-scale fixes
|
||||
4. **Resume Support**: Fix sessions can resume from checkpoints after interruption
|
||||
5. **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
|
||||
|
||||
## 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
|
||||
3. **Comprehensive Coverage**: Same 7 specialized dimensions as session review
|
||||
4. **Intelligent Prioritization**: Automatic identification of critical issues and cross-cutting concerns
|
||||
5. **Real-time Visibility**: JSON-based progress tracking with interactive HTML dashboard
|
||||
6. **Unified Archive**: Review results archived with session for historical reference
|
||||
5. **Unified Archive**: Review results archived with session for historical reference
|
||||
|
||||
### Orchestrator Boundary (CRITICAL)
|
||||
- **ONLY command** for independent multi-dimensional module review
|
||||
- Manages: dimension coordination, aggregation, iteration control, progress tracking
|
||||
- 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
|
||||
|
||||
@@ -66,7 +64,7 @@ Independent multi-dimensional code review orchestrator with **hybrid parallel-it
|
||||
|
||||
```
|
||||
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)
|
||||
├─ Launch 7 review agents simultaneously
|
||||
@@ -90,7 +88,7 @@ Phase 4: Iterative Deep-Dive (optional)
|
||||
└─ Loop until no critical findings OR max iterations
|
||||
|
||||
Phase 5: Completion
|
||||
└─ Finalize review-progress.json → Output dashboard path
|
||||
└─ Finalize review-progress.json
|
||||
```
|
||||
|
||||
### Agent Roles
|
||||
@@ -188,8 +186,8 @@ const CATEGORIES = {
|
||||
|
||||
**Step 1: Session Creation**
|
||||
```javascript
|
||||
// Create workflow session for this review
|
||||
SlashCommand(command="/workflow:session:start \"Code review for [target_pattern]\"")
|
||||
// Create workflow session for this review (type: review)
|
||||
SlashCommand(command="/workflow:session:start --type review \"Code review for [target_pattern]\"")
|
||||
|
||||
// Parse output
|
||||
const sessionId = output.match(/SESSION_ID: (WFS-[^\s]+)/)[1];
|
||||
@@ -219,37 +217,9 @@ done
|
||||
|
||||
**Step 4: Initialize Review 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**
|
||||
|
||||
**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**
|
||||
**Step 5: TodoWrite Initialization**
|
||||
- Set up progress tracking with hierarchical structure
|
||||
- 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
|
||||
- Update review-state.json with completion_time and phase=complete
|
||||
- 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
|
||||
│ ├── iteration-1-finding-{uuid}.json
|
||||
│ └── iteration-2-finding-{uuid}.json
|
||||
├── reports/ # Human-readable reports
|
||||
│ ├── security-analysis.md
|
||||
│ ├── security-cli-output.txt
|
||||
│ ├── deep-dive-1-{uuid}.md
|
||||
│ └── ...
|
||||
└── dashboard.html # Interactive dashboard (primary output)
|
||||
└── reports/ # Human-readable reports
|
||||
├── security-analysis.md
|
||||
├── security-cli-output.txt
|
||||
├── deep-dive-1-{uuid}.md
|
||||
└── ...
|
||||
```
|
||||
|
||||
**Session Context**:
|
||||
@@ -772,23 +740,25 @@ TodoWrite({
|
||||
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
|
||||
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
|
||||
|
||||
### View Review Progress
|
||||
Use `ccw view` to open the review dashboard in browser:
|
||||
|
||||
```bash
|
||||
ccw view
|
||||
```
|
||||
|
||||
### 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
|
||||
# Step 1: Complete review (this command)
|
||||
/workflow:review-module-cycle src/auth/**
|
||||
|
||||
# Step 2: Open dashboard, select findings, and export
|
||||
# Dashboard generates: fix-export-{timestamp}.json
|
||||
|
||||
# Step 3: Run automated fixes
|
||||
/workflow:review-fix .workflow/active/WFS-{session-id}/.review/fix-export-{timestamp}.json
|
||||
# Step 2: Run automated fixes using dimension findings
|
||||
/workflow:review-fix .workflow/active/WFS-{session-id}/.review/
|
||||
```
|
||||
|
||||
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
|
||||
2. **Intelligent Prioritization**: Automatic identification of critical issues and cross-cutting concerns
|
||||
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)
|
||||
- **ONLY command** for comprehensive multi-dimensional review
|
||||
- Manages: dimension coordination, aggregation, iteration control, progress tracking
|
||||
- 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
|
||||
|
||||
@@ -59,7 +57,7 @@ Session-based multi-dimensional code review orchestrator with **hybrid parallel-
|
||||
|
||||
```
|
||||
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)
|
||||
├─ Launch 7 review agents simultaneously
|
||||
@@ -83,7 +81,7 @@ Phase 4: Iterative Deep-Dive (optional)
|
||||
└─ Loop until no critical findings OR max iterations
|
||||
|
||||
Phase 5: Completion
|
||||
└─ Finalize review-progress.json → Output dashboard path
|
||||
└─ Finalize review-progress.json
|
||||
```
|
||||
|
||||
### Agent Roles
|
||||
@@ -199,36 +197,9 @@ git log --since="${sessionCreatedAt}" --name-only --pretty=format: | sort -u
|
||||
|
||||
**Step 5: Initialize Review 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**
|
||||
|
||||
**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**
|
||||
**Step 6: TodoWrite Initialization**
|
||||
- Set up progress tracking with hierarchical structure
|
||||
- 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
|
||||
- Update review-state.json with completion_time and phase=complete
|
||||
- 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
|
||||
│ ├── iteration-1-finding-{uuid}.json
|
||||
│ └── iteration-2-finding-{uuid}.json
|
||||
├── reports/ # Human-readable reports
|
||||
│ ├── security-analysis.md
|
||||
│ ├── security-cli-output.txt
|
||||
│ ├── deep-dive-1-{uuid}.md
|
||||
│ └── ...
|
||||
└── dashboard.html # Interactive dashboard (primary output)
|
||||
└── reports/ # Human-readable reports
|
||||
├── security-analysis.md
|
||||
├── security-cli-output.txt
|
||||
├── deep-dive-1-{uuid}.md
|
||||
└── ...
|
||||
```
|
||||
|
||||
**Session Context**:
|
||||
@@ -782,23 +751,25 @@ TodoWrite({
|
||||
2. **Parallel Execution**: ~60 minutes for full initial review (7 dimensions)
|
||||
3. **Trust Aggregation Logic**: Auto-selection based on proven heuristics
|
||||
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
|
||||
|
||||
### View Review Progress
|
||||
Use `ccw view` to open the review dashboard in browser:
|
||||
|
||||
```bash
|
||||
ccw view
|
||||
```
|
||||
|
||||
### 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
|
||||
# Step 1: Complete review (this command)
|
||||
/workflow:review-session-cycle
|
||||
|
||||
# Step 2: Open dashboard, select findings, and export
|
||||
# Dashboard generates: fix-export-{timestamp}.json
|
||||
|
||||
# Step 3: Run automated fixes
|
||||
/workflow:review-fix .workflow/active/WFS-{session-id}/.review/fix-export-{timestamp}.json
|
||||
# Step 2: Run automated fixes using dimension findings
|
||||
/workflow:review-fix .workflow/active/WFS-{session-id}/.review/
|
||||
```
|
||||
|
||||
See `/workflow:review-fix` for automated fixing with smart grouping, parallel execution, and test verification.
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
---
|
||||
name: start
|
||||
description: Discover existing sessions or start new workflow session with intelligent session management and conflict detection
|
||||
argument-hint: [--auto|--new] [optional: task description for new session]
|
||||
argument-hint: [--type <workflow|review|tdd|test|docs>] [--auto|--new] [optional: task description for new session]
|
||||
examples:
|
||||
- /workflow:session:start
|
||||
- /workflow:session:start --auto "implement OAuth2 authentication"
|
||||
- /workflow:session:start --new "fix login bug"
|
||||
- /workflow:session:start --type review "Code review for auth module"
|
||||
- /workflow:session:start --type tdd --auto "implement user authentication"
|
||||
- /workflow:session:start --type test --new "test payment flow"
|
||||
---
|
||||
|
||||
# Start Workflow Session (/workflow:session:start)
|
||||
@@ -17,6 +19,23 @@ Manages workflow sessions with three operation modes: discovery (manual), auto (
|
||||
1. **Project-level initialization** (first-time only): Creates `.workflow/project.json` for feature registry
|
||||
2. **Session-level initialization** (always): Creates session directory structure
|
||||
|
||||
## Session Types
|
||||
|
||||
The `--type` parameter classifies sessions for CCW dashboard organization:
|
||||
|
||||
| Type | Description | Default For |
|
||||
|------|-------------|-------------|
|
||||
| `workflow` | Standard implementation (default) | `/workflow:plan` |
|
||||
| `review` | Code review sessions | `/workflow:review-module-cycle` |
|
||||
| `tdd` | TDD-based development | `/workflow:tdd-plan` |
|
||||
| `test` | Test generation/fix sessions | `/workflow:test-fix-gen` |
|
||||
| `docs` | Documentation sessions | `/memory:docs` |
|
||||
|
||||
**Validation**: If `--type` is provided with invalid value, return error:
|
||||
```
|
||||
ERROR: Invalid session type. Valid types: workflow, review, tdd, test, docs
|
||||
```
|
||||
|
||||
## Step 0: Initialize Project State (First-time Only)
|
||||
|
||||
**Executed before all modes** - Ensures project-level state file exists by calling `/workflow:init`.
|
||||
@@ -86,8 +105,8 @@ bash(mkdir -p .workflow/active/WFS-implement-oauth2-auth/.process)
|
||||
bash(mkdir -p .workflow/active/WFS-implement-oauth2-auth/.task)
|
||||
bash(mkdir -p .workflow/active/WFS-implement-oauth2-auth/.summaries)
|
||||
|
||||
# Create metadata
|
||||
bash(echo '{"session_id":"WFS-implement-oauth2-auth","project":"implement OAuth2 auth","status":"planning"}' > .workflow/active/WFS-implement-oauth2-auth/workflow-session.json)
|
||||
# Create metadata (include type field, default to "workflow" if not specified)
|
||||
bash(echo '{"session_id":"WFS-implement-oauth2-auth","project":"implement OAuth2 auth","status":"planning","type":"workflow","created_at":"2024-12-04T08:00:00Z"}' > .workflow/active/WFS-implement-oauth2-auth/workflow-session.json)
|
||||
```
|
||||
|
||||
**Output**: `SESSION_ID: WFS-implement-oauth2-auth`
|
||||
@@ -143,7 +162,8 @@ bash(mkdir -p .workflow/active/WFS-fix-login-bug/.summaries)
|
||||
|
||||
### Step 3: Create Metadata
|
||||
```bash
|
||||
bash(echo '{"session_id":"WFS-fix-login-bug","project":"fix login bug","status":"planning"}' > .workflow/active/WFS-fix-login-bug/workflow-session.json)
|
||||
# Include type field from --type parameter (default: "workflow")
|
||||
bash(echo '{"session_id":"WFS-fix-login-bug","project":"fix login bug","status":"planning","type":"workflow","created_at":"2024-12-04T08:00:00Z"}' > .workflow/active/WFS-fix-login-bug/workflow-session.json)
|
||||
```
|
||||
|
||||
**Output**: `SESSION_ID: WFS-fix-login-bug`
|
||||
|
||||
@@ -1,357 +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')}
|
||||
|
||||
**Metrics**:
|
||||
- Files: ${overview.metrics.total_files}
|
||||
- Lines of Code: ${overview.metrics.lines_of_code}
|
||||
- Complexity: ${overview.metrics.complexity}
|
||||
|
||||
---
|
||||
` : ''}
|
||||
|
||||
### 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
|
||||
```
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: tdd-plan
|
||||
description: TDD workflow planning with Red-Green-Refactor task chain generation, test-first development structure, and cycle tracking
|
||||
argument-hint: "[--cli-execute] \"feature description\"|file.md"
|
||||
argument-hint: "\"feature description\"|file.md"
|
||||
allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
---
|
||||
|
||||
@@ -11,9 +11,7 @@ allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
|
||||
**This command is a pure orchestrator**: Dispatches 6 slash commands in sequence, parse outputs, pass context, and ensure complete TDD workflow creation with Red-Green-Refactor task generation.
|
||||
|
||||
**Execution Modes**:
|
||||
- **Agent Mode** (default): Use `/workflow:tools:task-generate-tdd` (autonomous agent-driven)
|
||||
- **CLI Mode** (`--cli-execute`): Use `/workflow:tools:task-generate-tdd --cli-execute` (Gemini/Qwen)
|
||||
**CLI Tool Selection**: CLI tool usage is determined semantically from user's task description. Include "use Codex/Gemini/Qwen" in your request for CLI execution.
|
||||
|
||||
**Task Attachment Model**:
|
||||
- SlashCommand dispatch **expands workflow** by attaching sub-tasks to current TodoWrite
|
||||
@@ -46,7 +44,7 @@ allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
**Step 1.1: Dispatch** - Session discovery and initialization
|
||||
|
||||
```javascript
|
||||
SlashCommand(command="/workflow:session:start --auto \"TDD: [structured-description]\"")
|
||||
SlashCommand(command="/workflow:session:start --type tdd --auto \"TDD: [structured-description]\"")
|
||||
```
|
||||
|
||||
**TDD Structured Format**:
|
||||
@@ -235,13 +233,11 @@ SlashCommand(command="/workflow:tools:conflict-resolution --session [sessionId]
|
||||
**Step 5.1: Dispatch** - TDD task generation via action-planning-agent
|
||||
|
||||
```javascript
|
||||
// Agent Mode (default)
|
||||
SlashCommand(command="/workflow:tools:task-generate-tdd --session [sessionId]")
|
||||
|
||||
// CLI Mode (--cli-execute flag)
|
||||
SlashCommand(command="/workflow:tools:task-generate-tdd --session [sessionId] --cli-execute")
|
||||
```
|
||||
|
||||
**Note**: CLI tool usage is determined semantically from user's task description.
|
||||
|
||||
**Parse**: Extract feature count, task count (not chain count - tasks now contain internal TDD cycles)
|
||||
|
||||
**Validate**:
|
||||
@@ -454,8 +450,7 @@ Convert user input to TDD-structured format:
|
||||
- `/workflow:tools:test-context-gather` - Phase 3: Analyze existing test patterns and coverage
|
||||
- `/workflow:tools:conflict-resolution` - Phase 4: Detect and resolve conflicts (auto-triggered if conflict_risk ≥ medium)
|
||||
- `/compact` - Phase 4: Memory optimization (if context approaching limits)
|
||||
- `/workflow:tools:task-generate-tdd` - Phase 5: Generate TDD tasks with agent-driven approach (default, autonomous)
|
||||
- `/workflow:tools:task-generate-tdd --cli-execute` - Phase 5: Generate TDD tasks with CLI tools (Gemini/Qwen, when `--cli-execute` flag used)
|
||||
- `/workflow:tools:task-generate-tdd` - Phase 5: Generate TDD tasks (CLI tool usage determined semantically)
|
||||
|
||||
**Follow-up Commands**:
|
||||
- `/workflow:action-plan-verify` - Recommended: Verify TDD plan quality and structure before execution
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: test-fix-gen
|
||||
description: Create test-fix workflow session from session ID, description, or file path with test strategy generation and task planning
|
||||
argument-hint: "[--use-codex] [--cli-execute] (source-session-id | \"feature description\" | /path/to/file.md)"
|
||||
argument-hint: "(source-session-id | \"feature description\" | /path/to/file.md)"
|
||||
allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
---
|
||||
|
||||
@@ -43,7 +43,7 @@ fi
|
||||
- **Session Isolation**: Creates independent `WFS-test-[slug]` session
|
||||
- **Context-First**: Gathers implementation context via appropriate method
|
||||
- **Format Reuse**: Creates standard `IMPL-*.json` tasks with `meta.type: "test-fix"`
|
||||
- **Manual First**: Default to manual fixes, use `--use-codex` for automation
|
||||
- **Semantic CLI Selection**: CLI tool usage determined from user's task description
|
||||
- **Automatic Detection**: Input pattern determines execution mode
|
||||
|
||||
### Coordinator Role
|
||||
@@ -79,16 +79,14 @@ This command is a **pure planning coordinator**:
|
||||
|
||||
```bash
|
||||
# Basic syntax
|
||||
/workflow:test-fix-gen [FLAGS] <INPUT>
|
||||
|
||||
# Flags (optional)
|
||||
--use-codex # Enable Codex automated fixes in IMPL-002
|
||||
--cli-execute # Enable CLI execution in IMPL-001
|
||||
/workflow:test-fix-gen <INPUT>
|
||||
|
||||
# Input
|
||||
<INPUT> # Session ID, description, or file path
|
||||
```
|
||||
|
||||
**Note**: CLI tool usage is determined semantically from the task description. To request CLI execution, include it in your description (e.g., "use Codex for automated fixes").
|
||||
|
||||
### Usage Examples
|
||||
|
||||
#### Session Mode
|
||||
@@ -96,11 +94,8 @@ This command is a **pure planning coordinator**:
|
||||
# Test validation for completed implementation
|
||||
/workflow:test-fix-gen WFS-user-auth-v2
|
||||
|
||||
# With automated fixes
|
||||
/workflow:test-fix-gen --use-codex WFS-api-endpoints
|
||||
|
||||
# With CLI execution
|
||||
/workflow:test-fix-gen --cli-execute --use-codex WFS-payment-flow
|
||||
# With semantic CLI request
|
||||
/workflow:test-fix-gen WFS-api-endpoints # Add "use Codex" in description for automated fixes
|
||||
```
|
||||
|
||||
#### Prompt Mode - Text Description
|
||||
@@ -108,17 +103,14 @@ This command is a **pure planning coordinator**:
|
||||
# Generate tests from feature description
|
||||
/workflow:test-fix-gen "Test the user authentication API endpoints in src/auth/api.ts"
|
||||
|
||||
# With automated fixes
|
||||
/workflow:test-fix-gen --use-codex "Test user registration and login flows"
|
||||
# With CLI execution (semantic)
|
||||
/workflow:test-fix-gen "Test user registration and login flows, use Codex for automated fixes"
|
||||
```
|
||||
|
||||
#### Prompt Mode - File Reference
|
||||
```bash
|
||||
# Generate tests from requirements file
|
||||
/workflow:test-fix-gen ./docs/api-requirements.md
|
||||
|
||||
# With flags
|
||||
/workflow:test-fix-gen --use-codex --cli-execute ./specs/feature.md
|
||||
```
|
||||
|
||||
### Mode Comparison
|
||||
@@ -143,7 +135,7 @@ This command is a **pure planning coordinator**:
|
||||
5. **Complete All Phases**: Do not return until Phase 5 completes
|
||||
6. **Track Progress**: Update TodoWrite dynamically with task attachment/collapse pattern
|
||||
7. **Automatic Detection**: Mode auto-detected from input pattern
|
||||
8. **Parse Flags**: Extract `--use-codex` and `--cli-execute` flags for Phase 4
|
||||
8. **Semantic CLI Detection**: CLI tool usage determined from user's task description for Phase 4
|
||||
9. **Task Attachment Model**: SlashCommand dispatch **attaches** sub-tasks to current workflow. Orchestrator **executes** these attached tasks itself, then **collapses** them after completion
|
||||
10. **⚠️ CRITICAL: DO NOT STOP**: Continuous multi-phase workflow. After executing all attached tasks, immediately collapse them and execute next phase
|
||||
|
||||
@@ -151,23 +143,35 @@ This command is a **pure planning coordinator**:
|
||||
|
||||
#### Phase 1: Create Test Session
|
||||
|
||||
**Step 1.1: Dispatch** - Create test workflow session
|
||||
**Step 1.0: Load Source Session Intent (Session Mode Only)** - Preserve user's original task description for semantic CLI selection
|
||||
|
||||
```javascript
|
||||
// Session Mode
|
||||
SlashCommand(command="/workflow:session:start --new \"Test validation for [sourceSessionId]\"")
|
||||
// Session Mode: Read source session metadata to get original task description
|
||||
Read(".workflow/active/[sourceSessionId]/workflow-session.json")
|
||||
// OR if context-package exists:
|
||||
Read(".workflow/active/[sourceSessionId]/.process/context-package.json")
|
||||
|
||||
// Prompt Mode
|
||||
SlashCommand(command="/workflow:session:start --new \"Test generation for: [description]\"")
|
||||
// Extract: metadata.task_description or project/description field
|
||||
// This preserves user's CLI tool preferences (e.g., "use Codex for fixes")
|
||||
```
|
||||
|
||||
**Step 1.1: Dispatch** - Create test workflow session with preserved intent
|
||||
|
||||
```javascript
|
||||
// Session Mode - Include original task description to enable semantic CLI selection
|
||||
SlashCommand(command="/workflow:session:start --type test --new \"Test validation for [sourceSessionId]: [originalTaskDescription]\"")
|
||||
|
||||
// Prompt Mode - User's description already contains their intent
|
||||
SlashCommand(command="/workflow:session:start --type test --new \"Test generation for: [description]\"")
|
||||
```
|
||||
|
||||
**Input**: User argument (session ID, description, or file path)
|
||||
|
||||
**Expected Behavior**:
|
||||
- Creates new session: `WFS-test-[slug]`
|
||||
- Writes `workflow-session.json` metadata:
|
||||
- **Session Mode**: Includes `workflow_type: "test_session"`, `source_session_id: "[sourceId]"`
|
||||
- **Prompt Mode**: Includes `workflow_type: "test_session"` only
|
||||
- Writes `workflow-session.json` metadata with `type: "test"`
|
||||
- **Session Mode**: Additionally includes `source_session_id: "[sourceId]"`, description with original user intent
|
||||
- **Prompt Mode**: Uses user's description (already contains intent)
|
||||
- Returns new session ID
|
||||
|
||||
**Parse Output**:
|
||||
@@ -283,13 +287,13 @@ For each targeted file/function, Gemini MUST generate:
|
||||
**Step 4.1: Dispatch** - Generate test task JSONs
|
||||
|
||||
```javascript
|
||||
SlashCommand(command="/workflow:tools:test-task-generate [--use-codex] [--cli-execute] --session [testSessionId]")
|
||||
SlashCommand(command="/workflow:tools:test-task-generate --session [testSessionId]")
|
||||
```
|
||||
|
||||
**Input**:
|
||||
- `testSessionId` from Phase 1
|
||||
- `--use-codex` flag (if present) - Controls IMPL-002 fix mode
|
||||
- `--cli-execute` flag (if present) - Controls IMPL-001 generation mode
|
||||
|
||||
**Note**: CLI tool usage is determined semantically from user's task description.
|
||||
|
||||
**Expected Behavior**:
|
||||
- Parse TEST_ANALYSIS_RESULTS.md from Phase 3 (multi-layered test plan)
|
||||
@@ -422,7 +426,7 @@ CRITICAL - Next Steps:
|
||||
- **Phase 2**: Mode-specific context gathering (session summaries vs codebase analysis)
|
||||
- **Phase 3**: Multi-layered test requirements analysis (L0: Static, L1: Unit, L2: Integration, L3: E2E)
|
||||
- **Phase 4**: Multi-task generation with quality gate (IMPL-001, IMPL-001.5-review, IMPL-002)
|
||||
- **Fix Mode Configuration**: `--use-codex` flag controls IMPL-002 fix mode (manual vs automated)
|
||||
- **Fix Mode Configuration**: CLI tool usage determined semantically from user's task description
|
||||
|
||||
|
||||
---
|
||||
@@ -521,16 +525,15 @@ If quality gate fails:
|
||||
- Task ID: `IMPL-002`
|
||||
- `meta.type: "test-fix"`
|
||||
- `meta.agent: "@test-fix-agent"`
|
||||
- `meta.use_codex: true|false` (based on `--use-codex` flag)
|
||||
- `context.depends_on: ["IMPL-001"]`
|
||||
- `context.requirements`: Execute and fix tests
|
||||
|
||||
**Test-Fix Cycle Specification**:
|
||||
**Note**: This specification describes what test-cycle-execute orchestrator will do. The agent only executes single tasks.
|
||||
- **Cycle Pattern** (orchestrator-managed): test → gemini_diagnose → manual_fix (or codex) → retest
|
||||
- **Cycle Pattern** (orchestrator-managed): test → gemini_diagnose → fix (agent or CLI) → retest
|
||||
- **Tools Configuration** (orchestrator-controlled):
|
||||
- Gemini for analysis with bug-fix template → surgical fix suggestions
|
||||
- Manual fix application (default) OR Codex if `--use-codex` flag (resume mechanism)
|
||||
- Agent fix application (default) OR CLI if `command` field present in implementation_approach
|
||||
- **Exit Conditions** (orchestrator-enforced):
|
||||
- Success: All tests pass
|
||||
- Failure: Max iterations reached (5)
|
||||
@@ -576,11 +579,11 @@ WFS-test-[session]/
|
||||
**File**: `workflow-session.json`
|
||||
|
||||
**Session Mode** includes:
|
||||
- `workflow_type: "test_session"`
|
||||
- `type: "test"` (set by session:start --type test)
|
||||
- `source_session_id: "[sourceSessionId]"` (enables automatic cross-session context)
|
||||
|
||||
**Prompt Mode** includes:
|
||||
- `workflow_type: "test_session"`
|
||||
- `type: "test"` (set by session:start --type test)
|
||||
- No `source_session_id` field
|
||||
|
||||
### Execution Flow Diagram
|
||||
@@ -674,8 +677,7 @@ Key Points:
|
||||
4. **Mode Selection**:
|
||||
- Use **Session Mode** for completed workflow validation
|
||||
- Use **Prompt Mode** for ad-hoc test generation
|
||||
- Use `--use-codex` for autonomous fix application
|
||||
- Use `--cli-execute` for enhanced generation capabilities
|
||||
- Include "use Codex" in description for autonomous fix application
|
||||
|
||||
## Related Commands
|
||||
|
||||
@@ -688,9 +690,7 @@ Key Points:
|
||||
- `/workflow:tools:test-context-gather` - Phase 2 (Session Mode): Gather source session context
|
||||
- `/workflow:tools:context-gather` - Phase 2 (Prompt Mode): Analyze codebase directly
|
||||
- `/workflow:tools:test-concept-enhanced` - Phase 3: Generate test requirements using Gemini
|
||||
- `/workflow:tools:test-task-generate` - Phase 4: Generate test task JSONs using action-planning-agent (autonomous, default)
|
||||
- `/workflow:tools:test-task-generate --use-codex` - Phase 4: With automated Codex fixes for IMPL-002 (when `--use-codex` flag used)
|
||||
- `/workflow:tools:test-task-generate --cli-execute` - Phase 4: With CLI execution mode for IMPL-001 test generation (when `--cli-execute` flag used)
|
||||
- `/workflow:tools:test-task-generate` - Phase 4: Generate test task JSONs (CLI tool usage determined semantically)
|
||||
|
||||
**Follow-up Commands**:
|
||||
- `/workflow:status` - Review generated test tasks
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: test-gen
|
||||
description: Create independent test-fix workflow session from completed implementation session, analyzes code to generate test tasks
|
||||
argument-hint: "[--use-codex] [--cli-execute] source-session-id"
|
||||
argument-hint: "source-session-id"
|
||||
allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
---
|
||||
|
||||
@@ -16,7 +16,7 @@ allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
- **Context-First**: Prioritizes gathering code changes and summaries from source session
|
||||
- **Format Reuse**: Creates standard `IMPL-*.json` task, using `meta.type: "test-fix"` for agent assignment
|
||||
- **Parameter Simplification**: Tools auto-detect test session type via metadata, no manual cross-session parameters needed
|
||||
- **Manual First**: Default to manual fixes, use `--use-codex` flag for automated Codex fix application
|
||||
- **Semantic CLI Selection**: CLI tool usage is determined by user's task description (e.g., "use Codex for fixes")
|
||||
|
||||
**Task Attachment Model**:
|
||||
- SlashCommand dispatch **expands workflow** by attaching sub-tasks to current TodoWrite
|
||||
@@ -48,7 +48,7 @@ allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
5. **Complete All Phases**: Do not return to user until Phase 5 completes (summary returned)
|
||||
6. **Track Progress**: Update TodoWrite dynamically with task attachment/collapse pattern
|
||||
7. **Automatic Detection**: context-gather auto-detects test session and gathers source session context
|
||||
8. **Parse --use-codex Flag**: Extract flag from arguments and pass to Phase 4 (test-task-generate)
|
||||
8. **Semantic CLI Selection**: CLI tool usage determined from user's task description, passed to Phase 4
|
||||
9. **Command Boundary**: This command ends at Phase 5 summary. Test execution is NOT part of this command.
|
||||
10. **Task Attachment Model**: SlashCommand dispatch **attaches** sub-tasks to current workflow. Orchestrator **executes** these attached tasks itself, then **collapses** them after completion
|
||||
11. **⚠️ CRITICAL: DO NOT STOP**: Continuous multi-phase workflow. After executing all attached tasks, immediately collapse them and execute next phase
|
||||
@@ -57,19 +57,35 @@ allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
|
||||
### Phase 1: Create Test Session
|
||||
|
||||
**Step 1.1: Dispatch** - Create new test workflow session
|
||||
**Step 1.0: Load Source Session Intent** - Preserve user's original task description for semantic CLI selection
|
||||
|
||||
```javascript
|
||||
SlashCommand(command="/workflow:session:start --new \"Test validation for [sourceSessionId]\"")
|
||||
// Read source session metadata to get original task description
|
||||
Read(".workflow/active/[sourceSessionId]/workflow-session.json")
|
||||
// OR if context-package exists:
|
||||
Read(".workflow/active/[sourceSessionId]/.process/context-package.json")
|
||||
|
||||
// Extract: metadata.task_description or project/description field
|
||||
// This preserves user's CLI tool preferences (e.g., "use Codex for fixes")
|
||||
```
|
||||
|
||||
**Input**: `sourceSessionId` from user argument (e.g., `WFS-user-auth`)
|
||||
**Step 1.1: Dispatch** - Create new test workflow session with preserved intent
|
||||
|
||||
```javascript
|
||||
// Include original task description to enable semantic CLI selection
|
||||
SlashCommand(command="/workflow:session:start --new \"Test validation for [sourceSessionId]: [originalTaskDescription]\"")
|
||||
```
|
||||
|
||||
**Input**:
|
||||
- `sourceSessionId` from user argument (e.g., `WFS-user-auth`)
|
||||
- `originalTaskDescription` from source session metadata (preserves CLI tool preferences)
|
||||
|
||||
**Expected Behavior**:
|
||||
- Creates new session with pattern `WFS-test-[source-slug]` (e.g., `WFS-test-user-auth`)
|
||||
- Writes metadata to `workflow-session.json`:
|
||||
- `workflow_type: "test_session"`
|
||||
- `source_session_id: "[sourceSessionId]"`
|
||||
- Description includes original user intent for semantic CLI selection
|
||||
- Returns new session ID for subsequent phases
|
||||
|
||||
**Parse Output**:
|
||||
@@ -224,13 +240,13 @@ SlashCommand(command="/workflow:tools:test-concept-enhanced --session [testSessi
|
||||
**Step 4.1: Dispatch** - Generate test task JSON files and planning documents
|
||||
|
||||
```javascript
|
||||
SlashCommand(command="/workflow:tools:test-task-generate [--use-codex] [--cli-execute] --session [testSessionId]")
|
||||
SlashCommand(command="/workflow:tools:test-task-generate --session [testSessionId]")
|
||||
```
|
||||
|
||||
**Input**:
|
||||
- `testSessionId` from Phase 1
|
||||
- `--use-codex` flag (if present in original command) - Controls IMPL-002 fix mode
|
||||
- `--cli-execute` flag (if present in original command) - Controls IMPL-001 generation mode
|
||||
|
||||
**Note**: CLI tool usage for fixes is determined semantically from user's task description (e.g., "use Codex for automated fixes").
|
||||
|
||||
**Expected Behavior**:
|
||||
- Parse TEST_ANALYSIS_RESULTS.md from Phase 3
|
||||
@@ -260,16 +276,15 @@ SlashCommand(command="/workflow:tools:test-task-generate [--use-codex] [--cli-ex
|
||||
- Task ID: `IMPL-002`
|
||||
- `meta.type: "test-fix"`
|
||||
- `meta.agent: "@test-fix-agent"`
|
||||
- `meta.use_codex: true|false` (based on --use-codex flag)
|
||||
- `context.depends_on: ["IMPL-001"]`
|
||||
- `context.requirements`: Execute and fix tests
|
||||
- `flow_control.implementation_approach.test_fix_cycle`: Complete cycle specification
|
||||
- **Cycle pattern**: test → gemini_diagnose → manual_fix (or codex if --use-codex) → retest
|
||||
- **Tools configuration**: Gemini for analysis with bug-fix template, manual or Codex for fixes
|
||||
- **Cycle pattern**: test → gemini_diagnose → fix (agent or CLI based on `command` field) → retest
|
||||
- **Tools configuration**: Gemini for analysis with bug-fix template, agent or CLI for fixes
|
||||
- **Exit conditions**: Success (all pass) or failure (max iterations)
|
||||
- `flow_control.implementation_approach.modification_points`: 3-phase execution flow
|
||||
- Phase 1: Initial test execution
|
||||
- Phase 2: Iterative Gemini diagnosis + manual/Codex fixes (based on flag)
|
||||
- Phase 2: Iterative Gemini diagnosis + fixes (agent or CLI based on step's `command` field)
|
||||
- Phase 3: Final validation and certification
|
||||
|
||||
<!-- TodoWrite: When test-task-generate dispatched, INSERT 3 test-task-generate tasks -->
|
||||
@@ -327,7 +342,7 @@ Artifacts Created:
|
||||
|
||||
Test Framework: [detected framework]
|
||||
Test Files to Generate: [count]
|
||||
Fix Mode: [Manual|Codex Automated] (based on --use-codex flag)
|
||||
Fix Mode: [Agent|CLI] (based on `command` field in implementation_approach steps)
|
||||
|
||||
Review Generated Artifacts:
|
||||
- Test plan: .workflow/[testSessionId]/IMPL_PLAN.md
|
||||
@@ -373,7 +388,7 @@ Ready for execution. Use appropriate workflow commands to proceed.
|
||||
- **Phase 2**: Cross-session context gathering from source implementation session
|
||||
- **Phase 3**: Test requirements analysis with Gemini for generation strategy
|
||||
- **Phase 4**: Dual-task generation (IMPL-001 for test generation, IMPL-002 for test execution)
|
||||
- **Fix Mode Configuration**: `--use-codex` flag controls IMPL-002 fix mode (manual vs automated)
|
||||
- **Fix Mode Configuration**: CLI tool usage determined semantically from user's task description
|
||||
|
||||
|
||||
|
||||
@@ -444,7 +459,7 @@ Generates two task definition files:
|
||||
- Agent: @test-fix-agent
|
||||
- Dependency: IMPL-001 must complete first
|
||||
- Max iterations: 5
|
||||
- Fix mode: Manual or Codex (based on --use-codex flag)
|
||||
- Fix mode: Agent or CLI (based on `command` field in implementation_approach)
|
||||
|
||||
See `/workflow:tools:test-task-generate` for complete task JSON schemas.
|
||||
|
||||
@@ -481,11 +496,10 @@ Created in `.workflow/active/WFS-test-[session]/`:
|
||||
**IMPL-002.json Structure**:
|
||||
- `meta.type: "test-fix"`
|
||||
- `meta.agent: "@test-fix-agent"`
|
||||
- `meta.use_codex`: true/false (based on --use-codex flag)
|
||||
- `context.depends_on: ["IMPL-001"]`
|
||||
- `flow_control.implementation_approach.test_fix_cycle`: Complete cycle specification
|
||||
- Gemini diagnosis template
|
||||
- Fix application mode (manual/codex)
|
||||
- Fix application mode (agent or CLI based on `command` field)
|
||||
- Max iterations: 5
|
||||
- `flow_control.implementation_approach.modification_points`: 3-phase flow
|
||||
|
||||
@@ -503,13 +517,11 @@ See `/workflow:tools:test-task-generate` for complete JSON schemas.
|
||||
**Prerequisite Commands**:
|
||||
- `/workflow:plan` or `/workflow:execute` - Complete implementation session that needs test validation
|
||||
|
||||
**Dispatched by This Command** (5 phases):
|
||||
**Dispatched by This Command** (4 phases):
|
||||
- `/workflow:session:start` - Phase 1: Create independent test workflow session
|
||||
- `/workflow:tools:test-context-gather` - Phase 2: Analyze test coverage and gather source session context
|
||||
- `/workflow:tools:test-concept-enhanced` - Phase 3: Generate test requirements and strategy using Gemini
|
||||
- `/workflow:tools:test-task-generate` - Phase 4: Generate test task JSONs using action-planning-agent (autonomous, default)
|
||||
- `/workflow:tools:test-task-generate --use-codex` - Phase 4: With automated Codex fixes for IMPL-002 (when `--use-codex` flag used)
|
||||
- `/workflow:tools:test-task-generate --cli-execute` - Phase 4: With CLI execution mode for IMPL-001 test generation (when `--cli-execute` flag used)
|
||||
- `/workflow:tools:test-task-generate` - Phase 4: Generate test task JSONs (CLI tool usage determined semantically)
|
||||
|
||||
**Follow-up Commands**:
|
||||
- `/workflow:status` - Review generated test tasks
|
||||
|
||||
@@ -171,7 +171,7 @@ Execute **${angle}** exploration for task planning context. Analyze codebase fro
|
||||
- dependencies: Dependencies relevant to ${angle}
|
||||
- integration_points: Where to integrate from ${angle} viewpoint (include file:line locations)
|
||||
- constraints: ${angle}-specific limitations/conventions
|
||||
- clarification_needs: ${angle}-related ambiguities (with options array)
|
||||
- clarification_needs: ${angle}-related ambiguities (options array + recommended index)
|
||||
- _metadata.exploration_angle: "${angle}"
|
||||
|
||||
## Success Criteria
|
||||
@@ -182,7 +182,7 @@ Execute **${angle}** exploration for task planning context. Analyze codebase fro
|
||||
- [ ] Integration points include file:line locations
|
||||
- [ ] Constraints are project-specific to ${angle}
|
||||
- [ ] JSON output follows schema exactly
|
||||
- [ ] clarification_needs includes options array
|
||||
- [ ] clarification_needs includes options + recommended
|
||||
|
||||
## Output
|
||||
Write: ${sessionFolder}/exploration-${angle}.json
|
||||
@@ -251,7 +251,7 @@ Execute all discovery tracks:
|
||||
### Phase 3: Synthesis, Assessment & Packaging
|
||||
1. Apply relevance scoring and build dependency graph
|
||||
2. **Synthesize 4-source data**: Merge findings from all sources (archive > docs > code > web). **Prioritize the context from `project.json`** for architecture and tech stack unless code analysis reveals it's outdated.
|
||||
3. **Populate `project_context`**: Directly use the `overview` from `project.json` to fill the `project_context` section of the output `context-package.json`. Include technology_stack, architecture, key_components, and entry_points.
|
||||
3. **Populate `project_context`**: Directly use the `overview` from `project.json` to fill the `project_context` section of the output `context-package.json`. Include description, technology_stack, architecture, and key_components.
|
||||
4. Integrate brainstorm artifacts (if .brainstorming/ exists, read content)
|
||||
5. Perform conflict detection with risk assessment
|
||||
6. **Inject historical conflicts** from archive analysis into conflict_detection
|
||||
@@ -260,7 +260,7 @@ Execute all discovery tracks:
|
||||
## Output Requirements
|
||||
Complete context-package.json with:
|
||||
- **metadata**: task_description, keywords, complexity, tech_stack, session_id
|
||||
- **project_context**: architecture_patterns, coding_conventions, tech_stack (sourced from `project.json` overview)
|
||||
- **project_context**: description, technology_stack, architecture, key_components (sourced from `project.json` overview)
|
||||
- **assets**: {documentation[], source_code[], config[], tests[]} with relevance scores
|
||||
- **dependencies**: {internal[], external[]} with dependency graph
|
||||
- **brainstorm_artifacts**: {guidance_specification, role_analyses[], synthesis_output} with content
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
---
|
||||
name: task-generate-agent
|
||||
description: Generate implementation plan documents (IMPL_PLAN.md, task JSONs, TODO_LIST.md) using action-planning-agent - produces planning artifacts, does NOT execute code implementation
|
||||
argument-hint: "--session WFS-session-id [--cli-execute]"
|
||||
argument-hint: "--session WFS-session-id"
|
||||
examples:
|
||||
- /workflow:tools:task-generate-agent --session WFS-auth
|
||||
- /workflow:tools:task-generate-agent --session WFS-auth --cli-execute
|
||||
---
|
||||
|
||||
# Generate Implementation Plan Command
|
||||
@@ -15,8 +14,8 @@ Generate implementation planning documents (IMPL_PLAN.md, task JSONs, TODO_LIST.
|
||||
## Core Philosophy
|
||||
- **Planning Only**: Generate planning documents (IMPL_PLAN.md, task JSONs, TODO_LIST.md) - does NOT implement code
|
||||
- **Agent-Driven Document Generation**: Delegate plan generation to action-planning-agent
|
||||
- **N+1 Parallel Planning**: Auto-detect multi-module projects, enable parallel planning (2+1 or 3+1 mode)
|
||||
- **Progressive Loading**: Load context incrementally (Core → Selective → On-Demand) due to analysis.md file size
|
||||
- **Two-Phase Flow**: Discovery (context gathering) → Output (planning document generation)
|
||||
- **Memory-First**: Reuse loaded documents from conversation memory
|
||||
- **Smart Selection**: Load synthesis_output OR guidance + relevant role analyses, NOT all role analyses
|
||||
- **MCP-Enhanced**: Use MCP tools for advanced code analysis and research
|
||||
@@ -26,25 +25,41 @@ Generate implementation planning documents (IMPL_PLAN.md, task JSONs, TODO_LIST.
|
||||
|
||||
```
|
||||
Input Parsing:
|
||||
├─ Parse flags: --session, --cli-execute
|
||||
├─ Parse flags: --session
|
||||
└─ Validation: session_id REQUIRED
|
||||
|
||||
Phase 1: Context Preparation (Command)
|
||||
Phase 1: Context Preparation & Module Detection (Command)
|
||||
├─ Assemble session paths (metadata, context package, output dirs)
|
||||
└─ Provide metadata (session_id, execution_mode, mcp_capabilities)
|
||||
├─ Provide metadata (session_id, execution_mode, mcp_capabilities)
|
||||
├─ Auto-detect modules from context-package + directory structure
|
||||
└─ Decision:
|
||||
├─ modules.length == 1 → Single Agent Mode (Phase 2A)
|
||||
└─ modules.length >= 2 → Parallel Mode (Phase 2B + Phase 3)
|
||||
|
||||
Phase 2: Planning Document Generation (Agent)
|
||||
Phase 2A: Single Agent Planning (Original Flow)
|
||||
├─ Load context package (progressive loading strategy)
|
||||
├─ Generate Task JSON Files (.task/IMPL-*.json)
|
||||
├─ Create IMPL_PLAN.md
|
||||
└─ Generate TODO_LIST.md
|
||||
|
||||
Phase 2B: N Parallel Planning (Multi-Module)
|
||||
├─ Launch N action-planning-agents simultaneously (one per module)
|
||||
├─ Each agent generates module-scoped tasks (IMPL-{prefix}{seq}.json)
|
||||
├─ Task ID format: IMPL-A1, IMPL-A2... / IMPL-B1, IMPL-B2...
|
||||
└─ Each module limited to ≤9 tasks
|
||||
|
||||
Phase 3: Integration (+1 Coordinator, Multi-Module Only)
|
||||
├─ Collect all module task JSONs
|
||||
├─ Resolve cross-module dependencies (CROSS::{module}::{pattern} → actual ID)
|
||||
├─ Generate unified IMPL_PLAN.md (grouped by module)
|
||||
└─ Generate TODO_LIST.md (hierarchical: module → tasks)
|
||||
```
|
||||
|
||||
## Document Generation Lifecycle
|
||||
|
||||
### Phase 1: Context Preparation (Command Responsibility)
|
||||
### Phase 1: Context Preparation & Module Detection (Command Responsibility)
|
||||
|
||||
**Command prepares session paths and metadata for planning document generation.**
|
||||
**Command prepares session paths, metadata, and detects module structure.**
|
||||
|
||||
**Session Path Structure**:
|
||||
```
|
||||
@@ -53,8 +68,12 @@ Phase 2: Planning Document Generation (Agent)
|
||||
├── .process/
|
||||
│ └── context-package.json # Context package with artifact catalog
|
||||
├── .task/ # Output: Task JSON files
|
||||
├── IMPL_PLAN.md # Output: Implementation plan
|
||||
└── TODO_LIST.md # Output: TODO list
|
||||
│ ├── IMPL-A1.json # Multi-module: prefixed by module
|
||||
│ ├── IMPL-A2.json
|
||||
│ ├── IMPL-B1.json
|
||||
│ └── ...
|
||||
├── IMPL_PLAN.md # Output: Implementation plan (grouped by module)
|
||||
└── TODO_LIST.md # Output: TODO list (hierarchical)
|
||||
```
|
||||
|
||||
**Command Preparation**:
|
||||
@@ -65,10 +84,42 @@ Phase 2: Planning Document Generation (Agent)
|
||||
|
||||
2. **Provide Metadata** (simple values):
|
||||
- `session_id`
|
||||
- `execution_mode` (agent-mode | cli-execute-mode)
|
||||
- `mcp_capabilities` (available MCP tools)
|
||||
|
||||
### Phase 2: Planning Document Generation (Agent Responsibility)
|
||||
3. **Auto Module Detection** (determines single vs parallel mode):
|
||||
```javascript
|
||||
function autoDetectModules(contextPackage, projectRoot) {
|
||||
// Priority 1: Explicit frontend/backend separation
|
||||
if (exists('src/frontend') && exists('src/backend')) {
|
||||
return [
|
||||
{ name: 'frontend', prefix: 'A', paths: ['src/frontend'] },
|
||||
{ name: 'backend', prefix: 'B', paths: ['src/backend'] }
|
||||
];
|
||||
}
|
||||
|
||||
// Priority 2: Monorepo structure
|
||||
if (exists('packages/*') || exists('apps/*')) {
|
||||
return detectMonorepoModules(); // Returns 2-3 main packages
|
||||
}
|
||||
|
||||
// Priority 3: Context-package dependency clustering
|
||||
const modules = clusterByDependencies(contextPackage.dependencies?.internal);
|
||||
if (modules.length >= 2) return modules.slice(0, 3);
|
||||
|
||||
// Default: Single module (original flow)
|
||||
return [{ name: 'main', prefix: '', paths: ['.'] }];
|
||||
}
|
||||
```
|
||||
|
||||
**Decision Logic**:
|
||||
- `modules.length == 1` → Phase 2A (Single Agent, original flow)
|
||||
- `modules.length >= 2` → Phase 2B + Phase 3 (N+1 Parallel)
|
||||
|
||||
**Note**: CLI tool usage is now determined semantically by action-planning-agent based on user's task description, not by flags.
|
||||
|
||||
### Phase 2A: Single Agent Planning (Original Flow)
|
||||
|
||||
**Condition**: `modules.length == 1` (no multi-module detected)
|
||||
|
||||
**Purpose**: Generate IMPL_PLAN.md, task JSONs, and TODO_LIST.md - planning documents only, NOT code implementation.
|
||||
|
||||
@@ -97,9 +148,13 @@ Output:
|
||||
|
||||
## CONTEXT METADATA
|
||||
Session ID: {session-id}
|
||||
Planning Mode: {agent-mode | cli-execute-mode}
|
||||
MCP Capabilities: {exa_code, exa_web, code_index}
|
||||
|
||||
## CLI TOOL SELECTION
|
||||
Determine CLI tool usage per-step based on user's task description:
|
||||
- If user specifies "use Codex/Gemini/Qwen for X" → Add command field to relevant steps
|
||||
- Default: Agent execution (no command field) unless user explicitly requests CLI
|
||||
|
||||
## EXPLORATION CONTEXT (from context-package.exploration_results)
|
||||
- Load exploration_results from context-package.json
|
||||
- Use aggregated_insights.critical_files for focus_paths generation
|
||||
@@ -128,7 +183,7 @@ MCP Capabilities: {exa_code, exa_web, code_index}
|
||||
|
||||
## QUALITY STANDARDS
|
||||
Hard Constraints:
|
||||
- Task count <= 12 (hard limit - request re-scope if exceeded)
|
||||
- Task count <= 18 (hard limit - request re-scope if exceeded)
|
||||
- All requirements quantified (explicit counts and enumerated lists)
|
||||
- Acceptance criteria measurable (include verification commands)
|
||||
- Artifact references mapped from context package
|
||||
@@ -144,4 +199,93 @@ Hard Constraints:
|
||||
)
|
||||
```
|
||||
|
||||
、
|
||||
### Phase 2B: N Parallel Planning (Multi-Module)
|
||||
|
||||
**Condition**: `modules.length >= 2` (multi-module detected)
|
||||
|
||||
**Purpose**: Launch N action-planning-agents simultaneously, one per module, for parallel task generation.
|
||||
|
||||
**Parallel Agent Invocation**:
|
||||
```javascript
|
||||
// Launch N agents in parallel (one per module)
|
||||
const planningTasks = modules.map(module =>
|
||||
Task(
|
||||
subagent_type="action-planning-agent",
|
||||
description=`Plan ${module.name} module`,
|
||||
prompt=`
|
||||
## SCOPE
|
||||
- Module: ${module.name} (${module.type})
|
||||
- Focus Paths: ${module.paths.join(', ')}
|
||||
- Task ID Prefix: IMPL-${module.prefix}
|
||||
- Task Limit: ≤9 tasks
|
||||
- Other Modules: ${otherModules.join(', ')}
|
||||
- Cross-module deps format: "CROSS::{module}::{pattern}"
|
||||
|
||||
## SESSION PATHS
|
||||
Input:
|
||||
- Context Package: .workflow/active/{session-id}/.process/context-package.json
|
||||
Output:
|
||||
- Task Dir: .workflow/active/{session-id}/.task/
|
||||
|
||||
## INSTRUCTIONS
|
||||
- Generate tasks ONLY for ${module.name} module
|
||||
- Use task ID format: IMPL-${module.prefix}1, IMPL-${module.prefix}2, ...
|
||||
- For cross-module dependencies, use: depends_on: ["CROSS::B::api-endpoint"]
|
||||
- Maximum 9 tasks per module
|
||||
`
|
||||
)
|
||||
);
|
||||
|
||||
// Execute all in parallel
|
||||
await Promise.all(planningTasks);
|
||||
```
|
||||
|
||||
**Output Structure** (direct to .task/):
|
||||
```
|
||||
.task/
|
||||
├── IMPL-A1.json # Module A (e.g., frontend)
|
||||
├── IMPL-A2.json
|
||||
├── IMPL-B1.json # Module B (e.g., backend)
|
||||
├── IMPL-B2.json
|
||||
└── IMPL-C1.json # Module C (e.g., shared)
|
||||
```
|
||||
|
||||
**Task ID Naming**:
|
||||
- Format: `IMPL-{prefix}{seq}.json`
|
||||
- Prefix: A, B, C... (assigned by detection order)
|
||||
- Sequence: 1, 2, 3... (per-module increment)
|
||||
|
||||
### Phase 3: Integration (+1 Coordinator, Multi-Module Only)
|
||||
|
||||
**Condition**: Only executed when `modules.length >= 2`
|
||||
|
||||
**Purpose**: Collect all module tasks, resolve cross-module dependencies, generate unified documents.
|
||||
|
||||
**Integration Logic**:
|
||||
```javascript
|
||||
// 1. Collect all module task JSONs
|
||||
const allTasks = glob('.task/IMPL-*.json').map(loadJson);
|
||||
|
||||
// 2. Resolve cross-module dependencies
|
||||
for (const task of allTasks) {
|
||||
if (task.depends_on) {
|
||||
task.depends_on = task.depends_on.map(dep => {
|
||||
if (dep.startsWith('CROSS::')) {
|
||||
// CROSS::B::api-endpoint → find matching IMPL-B* task
|
||||
const [, targetModule, pattern] = dep.match(/CROSS::(\w+)::(.+)/);
|
||||
return findTaskByModuleAndPattern(allTasks, targetModule, pattern);
|
||||
}
|
||||
return dep;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Generate unified IMPL_PLAN.md (grouped by module)
|
||||
generateIMPL_PLAN(allTasks, modules);
|
||||
|
||||
// 4. Generate TODO_LIST.md (hierarchical structure)
|
||||
generateTODO_LIST(allTasks, modules);
|
||||
```
|
||||
|
||||
**Note**: IMPL_PLAN.md and TODO_LIST.md structure definitions are in `action-planning-agent.md`.
|
||||
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
---
|
||||
name: task-generate-tdd
|
||||
description: Autonomous TDD task generation using action-planning-agent with Red-Green-Refactor cycles, test-first structure, and cycle validation
|
||||
argument-hint: "--session WFS-session-id [--cli-execute]"
|
||||
argument-hint: "--session WFS-session-id"
|
||||
examples:
|
||||
- /workflow:tools:task-generate-tdd --session WFS-auth
|
||||
- /workflow:tools:task-generate-tdd --session WFS-auth --cli-execute
|
||||
---
|
||||
|
||||
# Autonomous TDD Task Generation Command
|
||||
|
||||
## Overview
|
||||
Autonomous TDD task JSON and IMPL_PLAN.md generation using action-planning-agent with two-phase execution: discovery and document generation. Supports both agent-driven execution (default) and CLI tool execution modes. Generates complete Red-Green-Refactor cycles contained within each task.
|
||||
Autonomous TDD task JSON and IMPL_PLAN.md generation using action-planning-agent with two-phase execution: discovery and document generation. Generates complete Red-Green-Refactor cycles contained within each task.
|
||||
|
||||
## Core Philosophy
|
||||
- **Agent-Driven**: Delegate execution to action-planning-agent for autonomous operation
|
||||
- **Two-Phase Flow**: Discovery (context gathering) → Output (document generation)
|
||||
- **Memory-First**: Reuse loaded documents from conversation memory
|
||||
- **MCP-Enhanced**: Use MCP tools for advanced code analysis and research
|
||||
- **Pre-Selected Templates**: Command selects correct TDD template based on `--cli-execute` flag **before** invoking agent
|
||||
- **Agent Simplicity**: Agent receives pre-selected template and focuses only on content generation
|
||||
- **Semantic CLI Selection**: CLI tool usage determined from user's task description, not flags
|
||||
- **Agent Simplicity**: Agent generates content with semantic CLI detection
|
||||
- **Path Clarity**: All `focus_paths` prefer absolute paths (e.g., `D:\\project\\src\\module`), or clear relative paths from project root (e.g., `./src/module`)
|
||||
- **TDD-First**: Every feature starts with a failing test (Red phase)
|
||||
- **Feature-Complete Tasks**: Each task contains complete Red-Green-Refactor cycle
|
||||
@@ -43,10 +42,10 @@ Autonomous TDD task JSON and IMPL_PLAN.md generation using action-planning-agent
|
||||
- Different tech stacks or domains within feature
|
||||
|
||||
### Task Limits
|
||||
- **Maximum 10 tasks** (hard limit for TDD workflows)
|
||||
- **Maximum 18 tasks** (hard limit for TDD workflows)
|
||||
- **Feature-based**: Complete functional units with internal TDD cycles
|
||||
- **Hierarchy**: Flat (≤5 simple features) | Two-level (6-10 for complex features with sub-features)
|
||||
- **Re-scope**: If >10 tasks needed, break project into multiple TDD workflow sessions
|
||||
- **Re-scope**: If >18 tasks needed, break project into multiple TDD workflow sessions
|
||||
|
||||
### TDD Cycle Mapping
|
||||
- **Old approach**: 1 feature = 3 tasks (TEST-N.M, IMPL-N.M, REFACTOR-N.M)
|
||||
@@ -57,7 +56,7 @@ Autonomous TDD task JSON and IMPL_PLAN.md generation using action-planning-agent
|
||||
|
||||
```
|
||||
Input Parsing:
|
||||
├─ Parse flags: --session, --cli-execute
|
||||
├─ Parse flags: --session
|
||||
└─ Validation: session_id REQUIRED
|
||||
|
||||
Phase 1: Discovery & Context Loading (Memory-First)
|
||||
@@ -69,7 +68,7 @@ Phase 1: Discovery & Context Loading (Memory-First)
|
||||
└─ Optional: MCP external research
|
||||
|
||||
Phase 2: Agent Execution (Document Generation)
|
||||
├─ Pre-agent template selection (agent-mode OR cli-execute-mode)
|
||||
├─ Pre-agent template selection (semantic CLI detection)
|
||||
├─ Invoke action-planning-agent
|
||||
├─ Generate TDD Task JSON Files (.task/IMPL-*.json)
|
||||
│ └─ Each task: complete Red-Green-Refactor cycle internally
|
||||
@@ -86,11 +85,8 @@ Phase 2: Agent Execution (Document Generation)
|
||||
```javascript
|
||||
{
|
||||
"session_id": "WFS-[session-id]",
|
||||
"execution_mode": "agent-mode" | "cli-execute-mode", // Determined by flag
|
||||
"task_json_template_path": "~/.claude/workflows/cli-templates/prompts/workflow/task-json-agent-mode.txt"
|
||||
| "~/.claude/workflows/cli-templates/prompts/workflow/task-json-cli-mode.txt",
|
||||
// Path selected by command based on --cli-execute flag, agent reads it
|
||||
"workflow_type": "tdd",
|
||||
// Note: CLI tool usage is determined semantically by action-planning-agent based on user's task description
|
||||
"session_metadata": {
|
||||
// If in memory: use cached content
|
||||
// Else: Load from .workflow/active//{session-id}/workflow-session.json
|
||||
@@ -199,8 +195,7 @@ Task(
|
||||
|
||||
**Session ID**: WFS-{session-id}
|
||||
**Workflow Type**: TDD
|
||||
**Execution Mode**: {agent-mode | cli-execute-mode}
|
||||
**Task JSON Template Path**: {template_path}
|
||||
**Note**: CLI tool usage is determined semantically from user's task description
|
||||
|
||||
## Phase 1: Discovery Results (Provided Context)
|
||||
|
||||
@@ -254,7 +249,7 @@ Refer to: @.claude/agents/action-planning-agent.md for:
|
||||
- Each task executes Red-Green-Refactor phases sequentially
|
||||
- Task count = Feature count (typically 5 features = 5 tasks)
|
||||
- Subtasks only when complexity >2500 lines or >6 files per cycle
|
||||
- **Maximum 10 tasks** (hard limit for TDD workflows)
|
||||
- **Maximum 18 tasks** (hard limit for TDD workflows)
|
||||
|
||||
#### TDD Cycle Mapping
|
||||
- **Simple features**: IMPL-N with internal Red-Green-Refactor phases
|
||||
@@ -265,16 +260,15 @@ Refer to: @.claude/agents/action-planning-agent.md for:
|
||||
|
||||
##### 1. TDD Task JSON Files (.task/IMPL-*.json)
|
||||
- **Location**: `.workflow/active//{session-id}/.task/`
|
||||
- **Template**: Read from `{template_path}` (pre-selected by command based on `--cli-execute` flag)
|
||||
- **Schema**: 5-field structure with TDD-specific metadata
|
||||
- `meta.tdd_workflow`: true (REQUIRED)
|
||||
- `meta.max_iterations`: 3 (Green phase test-fix cycle limit)
|
||||
- `meta.use_codex`: false (manual fixes by default)
|
||||
- `context.tdd_cycles`: Array with quantified test cases and coverage
|
||||
- `flow_control.implementation_approach`: Exactly 3 steps with `tdd_phase` field
|
||||
1. Red Phase (`tdd_phase: "red"`): Write failing tests
|
||||
2. Green Phase (`tdd_phase: "green"`): Implement to pass tests
|
||||
3. Refactor Phase (`tdd_phase: "refactor"`): Improve code quality
|
||||
- CLI tool usage determined semantically (add `command` field when user requests CLI execution)
|
||||
- **Details**: See action-planning-agent.md § TDD Task JSON Generation
|
||||
|
||||
##### 2. IMPL_PLAN.md (TDD Variant)
|
||||
@@ -324,7 +318,7 @@ Refer to: @.claude/agents/action-planning-agent.md for:
|
||||
|
||||
**Quality Gates** (Full checklist in action-planning-agent.md):
|
||||
- ✓ Quantification requirements enforced (explicit counts, measurable acceptance, exact targets)
|
||||
- ✓ Task count ≤10 (hard limit)
|
||||
- ✓ Task count ≤18 (hard limit)
|
||||
- ✓ Each task has meta.tdd_workflow: true
|
||||
- ✓ Each task has exactly 3 implementation steps with tdd_phase field
|
||||
- ✓ Green phase includes test-fix cycle logic
|
||||
@@ -475,16 +469,14 @@ This section provides quick reference for TDD task JSON structure. For complete
|
||||
|
||||
**Basic Usage**:
|
||||
```bash
|
||||
# Agent mode (default, autonomous execution)
|
||||
# Standard execution
|
||||
/workflow:tools:task-generate-tdd --session WFS-auth
|
||||
|
||||
# CLI tool mode (use Gemini/Qwen for generation)
|
||||
/workflow:tools:task-generate-tdd --session WFS-auth --cli-execute
|
||||
# With semantic CLI request (include in task description)
|
||||
# e.g., "Generate TDD tasks for auth module, use Codex for implementation"
|
||||
```
|
||||
|
||||
**Execution Modes**:
|
||||
- **Agent mode** (default): Uses `action-planning-agent` with agent-mode task template
|
||||
- **CLI mode** (`--cli-execute`): Uses Gemini/Qwen with cli-mode task template
|
||||
**CLI Tool Selection**: Determined semantically from user's task description. Include "use Codex/Gemini/Qwen" in your request for CLI execution.
|
||||
|
||||
**Output**:
|
||||
- TDD task JSON files in `.task/` directory (IMPL-N.json format)
|
||||
@@ -513,7 +505,7 @@ IMPL (Green phase) tasks include automatic test-fix cycle:
|
||||
3. **Success Path**: Tests pass → Complete task
|
||||
4. **Failure Path**: Tests fail → Enter iterative fix cycle:
|
||||
- **Gemini Diagnosis**: Analyze failures with bug-fix template
|
||||
- **Fix Application**: Manual (default) or Codex (if meta.use_codex=true)
|
||||
- **Fix Application**: Agent (default) or CLI (if `command` field present)
|
||||
- **Retest**: Verify fix resolves failures
|
||||
- **Repeat**: Up to max_iterations (default: 3)
|
||||
5. **Safety Net**: Auto-revert all changes if max iterations reached
|
||||
@@ -522,5 +514,5 @@ IMPL (Green phase) tasks include automatic test-fix cycle:
|
||||
|
||||
## Configuration Options
|
||||
- **meta.max_iterations**: Number of fix attempts (default: 3 for TDD, 5 for test-gen)
|
||||
- **meta.use_codex**: Enable Codex automated fixes (default: false, manual)
|
||||
- **CLI tool usage**: Determined semantically from user's task description via `command` field in implementation_approach
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
---
|
||||
name: test-task-generate
|
||||
description: Generate test planning documents (IMPL_PLAN.md, test task JSONs, TODO_LIST.md) using action-planning-agent - produces test planning artifacts, does NOT execute tests
|
||||
argument-hint: "[--use-codex] [--cli-execute] --session WFS-test-session-id"
|
||||
argument-hint: "--session WFS-test-session-id"
|
||||
examples:
|
||||
- /workflow:tools:test-task-generate --session WFS-test-auth
|
||||
- /workflow:tools:test-task-generate --use-codex --session WFS-test-auth
|
||||
- /workflow:tools:test-task-generate --cli-execute --session WFS-test-auth
|
||||
---
|
||||
|
||||
# Generate Test Planning Documents Command
|
||||
@@ -26,17 +24,17 @@ Generate test planning documents (IMPL_PLAN.md, test task JSONs, TODO_LIST.md) u
|
||||
|
||||
### Test Generation (IMPL-001)
|
||||
- **Agent Mode** (default): @code-developer generates tests within agent context
|
||||
- **CLI Execute Mode** (`--cli-execute`): Use Codex CLI for autonomous test generation
|
||||
- **CLI Mode**: Use CLI tools when `command` field present in implementation_approach (determined semantically)
|
||||
|
||||
### Test Execution & Fix (IMPL-002+)
|
||||
- **Manual Mode** (default): Gemini diagnosis → user applies fixes
|
||||
- **Codex Mode** (`--use-codex`): Gemini diagnosis → Codex applies fixes with resume mechanism
|
||||
- **Agent Mode** (default): Gemini diagnosis → agent applies fixes
|
||||
- **CLI Mode**: Gemini diagnosis → CLI applies fixes (when `command` field present in implementation_approach)
|
||||
|
||||
## Execution Process
|
||||
|
||||
```
|
||||
Input Parsing:
|
||||
├─ Parse flags: --session, --use-codex, --cli-execute
|
||||
├─ Parse flags: --session
|
||||
└─ Validation: session_id REQUIRED
|
||||
|
||||
Phase 1: Context Preparation (Command)
|
||||
@@ -44,7 +42,7 @@ Phase 1: Context Preparation (Command)
|
||||
│ ├─ session_metadata_path
|
||||
│ ├─ test_analysis_results_path (REQUIRED)
|
||||
│ └─ test_context_package_path
|
||||
└─ Provide metadata (session_id, execution_mode, use_codex, source_session_id)
|
||||
└─ Provide metadata (session_id, source_session_id)
|
||||
|
||||
Phase 2: Test Document Generation (Agent)
|
||||
├─ Load TEST_ANALYSIS_RESULTS.md as primary requirements source
|
||||
@@ -83,11 +81,11 @@ Phase 2: Test Document Generation (Agent)
|
||||
|
||||
2. **Provide Metadata** (simple values):
|
||||
- `session_id`
|
||||
- `execution_mode` (agent-mode | cli-execute-mode)
|
||||
- `use_codex` flag (true | false)
|
||||
- `source_session_id` (if exists)
|
||||
- `mcp_capabilities` (available MCP tools)
|
||||
|
||||
**Note**: CLI tool usage is now determined semantically from user's task description, not by flags.
|
||||
|
||||
### Phase 2: Test Document Generation (Agent Responsibility)
|
||||
|
||||
**Purpose**: Generate test-specific IMPL_PLAN.md, task JSONs, and TODO_LIST.md - planning documents only, NOT test execution.
|
||||
@@ -134,11 +132,14 @@ Output:
|
||||
## CONTEXT METADATA
|
||||
Session ID: {test-session-id}
|
||||
Workflow Type: test_session
|
||||
Planning Mode: {agent-mode | cli-execute-mode}
|
||||
Use Codex: {true | false}
|
||||
Source Session: {source-session-id} (if exists)
|
||||
MCP Capabilities: {exa_code, exa_web, code_index}
|
||||
|
||||
## CLI TOOL SELECTION
|
||||
Determine CLI tool usage per-step based on user's task description:
|
||||
- If user specifies "use Codex/Gemini/Qwen for X" → Add command field to relevant steps
|
||||
- Default: Agent execution (no command field) unless user explicitly requests CLI
|
||||
|
||||
## TEST-SPECIFIC REQUIREMENTS SUMMARY
|
||||
(Detailed specifications in your agent definition)
|
||||
|
||||
@@ -149,25 +150,26 @@ MCP Capabilities: {exa_code, exa_web, code_index}
|
||||
Task Configuration:
|
||||
IMPL-001 (Test Generation):
|
||||
- meta.type: "test-gen"
|
||||
- meta.agent: "@code-developer" (agent-mode) OR CLI execution (cli-execute-mode)
|
||||
- meta.agent: "@code-developer"
|
||||
- meta.test_framework: Specify existing framework (e.g., "jest", "vitest", "pytest")
|
||||
- flow_control: Test generation strategy from TEST_ANALYSIS_RESULTS.md
|
||||
- CLI execution: Add `command` field when user requests (determined semantically)
|
||||
|
||||
IMPL-002+ (Test Execution & Fix):
|
||||
- meta.type: "test-fix"
|
||||
- meta.agent: "@test-fix-agent"
|
||||
- meta.use_codex: true/false (based on flag)
|
||||
- flow_control: Test-fix cycle with iteration limits and diagnosis configuration
|
||||
- CLI execution: Add `command` field when user requests (determined semantically)
|
||||
|
||||
### Test-Fix Cycle Specification (IMPL-002+)
|
||||
Required flow_control fields:
|
||||
- max_iterations: 5
|
||||
- diagnosis_tool: "gemini"
|
||||
- diagnosis_template: "~/.claude/workflows/cli-templates/prompts/analysis/01-diagnose-bug-root-cause.txt"
|
||||
- fix_mode: "manual" OR "codex" (based on use_codex flag)
|
||||
- cycle_pattern: "test → gemini_diagnose → fix → retest"
|
||||
- exit_conditions: ["all_tests_pass", "max_iterations_reached"]
|
||||
- auto_revert_on_failure: true
|
||||
- CLI fix: Add `command` field when user specifies CLI tool usage
|
||||
|
||||
### Automation Framework Configuration
|
||||
Select automation tools based on test requirements from TEST_ANALYSIS_RESULTS.md:
|
||||
@@ -191,8 +193,9 @@ PRIMARY requirements source - extract and map to task JSONs:
|
||||
## EXPECTED DELIVERABLES
|
||||
1. Test Task JSON Files (.task/IMPL-*.json)
|
||||
- 6-field schema with quantified requirements from TEST_ANALYSIS_RESULTS.md
|
||||
- Test-specific metadata: type, agent, use_codex, test_framework, coverage_target
|
||||
- Test-specific metadata: type, agent, test_framework, coverage_target
|
||||
- flow_control includes: reusable_test_tools, test_commands (from project config)
|
||||
- CLI execution via `command` field when user requests (determined semantically)
|
||||
- Artifact references from test-context-package.json
|
||||
- Absolute paths in context.files_to_test
|
||||
|
||||
@@ -209,13 +212,13 @@ PRIMARY requirements source - extract and map to task JSONs:
|
||||
|
||||
## QUALITY STANDARDS
|
||||
Hard Constraints:
|
||||
- Task count: minimum 2, maximum 12
|
||||
- Task count: minimum 2, maximum 18
|
||||
- All requirements quantified from TEST_ANALYSIS_RESULTS.md
|
||||
- Test framework matches existing project framework
|
||||
- flow_control includes reusable_test_tools and test_commands from project
|
||||
- use_codex flag correctly set in IMPL-002+ tasks
|
||||
- Absolute paths for all focus_paths
|
||||
- Acceptance criteria include verification commands
|
||||
- CLI `command` field added only when user explicitly requests CLI tool usage
|
||||
|
||||
## SUCCESS CRITERIA
|
||||
- All test planning documents generated successfully
|
||||
@@ -233,21 +236,18 @@ Hard Constraints:
|
||||
|
||||
### Usage Examples
|
||||
```bash
|
||||
# Agent mode (default)
|
||||
# Standard execution
|
||||
/workflow:tools:test-task-generate --session WFS-test-auth
|
||||
|
||||
# With automated Codex fixes
|
||||
/workflow:tools:test-task-generate --use-codex --session WFS-test-auth
|
||||
|
||||
# CLI execution mode for test generation
|
||||
/workflow:tools:test-task-generate --cli-execute --session WFS-test-auth
|
||||
# With semantic CLI request (include in task description)
|
||||
# e.g., "Generate tests, use Codex for implementation and fixes"
|
||||
```
|
||||
|
||||
### Flag Behavior
|
||||
- **No flags**: `meta.use_codex=false` (manual fixes), agent-mode test generation
|
||||
- **--use-codex**: `meta.use_codex=true` (Codex automated fixes in IMPL-002+)
|
||||
- **--cli-execute**: CLI tool execution mode for IMPL-001 test generation
|
||||
- **Both flags**: CLI generation + automated Codex fixes
|
||||
### CLI Tool Selection
|
||||
CLI tool usage is determined semantically from user's task description:
|
||||
- Include "use Codex" for automated fixes
|
||||
- Include "use Gemini" for analysis
|
||||
- Default: Agent execution (no `command` field)
|
||||
|
||||
### Output
|
||||
- Test task JSON files in `.task/` directory (minimum 2)
|
||||
|
||||
@@ -409,8 +409,8 @@
|
||||
{
|
||||
"name": "plan",
|
||||
"command": "/workflow:plan",
|
||||
"description": "5-phase planning workflow with action-planning-agent task generation, outputs IMPL_PLAN.md and task JSONs with optional CLI auto-execution",
|
||||
"arguments": "[--cli-execute] \\\"text description\\\"|file.md",
|
||||
"description": "5-phase planning workflow with action-planning-agent task generation, outputs IMPL_PLAN.md and task JSONs",
|
||||
"arguments": "\\\"text description\\\"|file.md",
|
||||
"category": "workflow",
|
||||
"subcategory": null,
|
||||
"usage_scenario": "planning",
|
||||
@@ -531,7 +531,7 @@
|
||||
"name": "tdd-plan",
|
||||
"command": "/workflow:tdd-plan",
|
||||
"description": "TDD workflow planning with Red-Green-Refactor task chain generation, test-first development structure, and cycle tracking",
|
||||
"arguments": "[--cli-execute] \\\"feature description\\\"|file.md",
|
||||
"arguments": "\\\"feature description\\\"|file.md",
|
||||
"category": "workflow",
|
||||
"subcategory": null,
|
||||
"usage_scenario": "planning",
|
||||
@@ -564,7 +564,7 @@
|
||||
"name": "test-fix-gen",
|
||||
"command": "/workflow:test-fix-gen",
|
||||
"description": "Create test-fix workflow session from session ID, description, or file path with test strategy generation and task planning",
|
||||
"arguments": "[--use-codex] [--cli-execute] (source-session-id | \\\"feature description\\\" | /path/to/file.md)",
|
||||
"arguments": "(source-session-id | \\\"feature description\\\" | /path/to/file.md)",
|
||||
"category": "workflow",
|
||||
"subcategory": null,
|
||||
"usage_scenario": "testing",
|
||||
@@ -575,7 +575,7 @@
|
||||
"name": "test-gen",
|
||||
"command": "/workflow:test-gen",
|
||||
"description": "Create independent test-fix workflow session from completed implementation session, analyzes code to generate test tasks",
|
||||
"arguments": "[--use-codex] [--cli-execute] source-session-id",
|
||||
"arguments": "source-session-id",
|
||||
"category": "workflow",
|
||||
"subcategory": null,
|
||||
"usage_scenario": "testing",
|
||||
@@ -608,7 +608,7 @@
|
||||
"name": "task-generate-agent",
|
||||
"command": "/workflow:tools:task-generate-agent",
|
||||
"description": "Generate implementation plan documents (IMPL_PLAN.md, task JSONs, TODO_LIST.md) using action-planning-agent - produces planning artifacts, does NOT execute code implementation",
|
||||
"arguments": "--session WFS-session-id [--cli-execute]",
|
||||
"arguments": "--session WFS-session-id",
|
||||
"category": "workflow",
|
||||
"subcategory": "tools",
|
||||
"usage_scenario": "implementation",
|
||||
@@ -619,7 +619,7 @@
|
||||
"name": "task-generate-tdd",
|
||||
"command": "/workflow:tools:task-generate-tdd",
|
||||
"description": "Autonomous TDD task generation using action-planning-agent with Red-Green-Refactor cycles, test-first structure, and cycle validation",
|
||||
"arguments": "--session WFS-session-id [--cli-execute]",
|
||||
"arguments": "--session WFS-session-id",
|
||||
"category": "workflow",
|
||||
"subcategory": "tools",
|
||||
"usage_scenario": "implementation",
|
||||
@@ -663,7 +663,7 @@
|
||||
"name": "test-task-generate",
|
||||
"command": "/workflow:tools:test-task-generate",
|
||||
"description": "Generate test planning documents (IMPL_PLAN.md, test task JSONs, TODO_LIST.md) using action-planning-agent - produces test planning artifacts, does NOT execute tests",
|
||||
"arguments": "[--use-codex] [--cli-execute] --session WFS-test-session-id",
|
||||
"arguments": "--session WFS-test-session-id",
|
||||
"category": "workflow",
|
||||
"subcategory": "tools",
|
||||
"usage_scenario": "implementation",
|
||||
|
||||
@@ -295,8 +295,8 @@
|
||||
{
|
||||
"name": "plan",
|
||||
"command": "/workflow:plan",
|
||||
"description": "5-phase planning workflow with action-planning-agent task generation, outputs IMPL_PLAN.md and task JSONs with optional CLI auto-execution",
|
||||
"arguments": "[--cli-execute] \\\"text description\\\"|file.md",
|
||||
"description": "5-phase planning workflow with action-planning-agent task generation, outputs IMPL_PLAN.md and task JSONs",
|
||||
"arguments": "\\\"text description\\\"|file.md",
|
||||
"category": "workflow",
|
||||
"subcategory": null,
|
||||
"usage_scenario": "planning",
|
||||
@@ -373,7 +373,7 @@
|
||||
"name": "tdd-plan",
|
||||
"command": "/workflow:tdd-plan",
|
||||
"description": "TDD workflow planning with Red-Green-Refactor task chain generation, test-first development structure, and cycle tracking",
|
||||
"arguments": "[--cli-execute] \\\"feature description\\\"|file.md",
|
||||
"arguments": "\\\"feature description\\\"|file.md",
|
||||
"category": "workflow",
|
||||
"subcategory": null,
|
||||
"usage_scenario": "planning",
|
||||
@@ -406,7 +406,7 @@
|
||||
"name": "test-fix-gen",
|
||||
"command": "/workflow:test-fix-gen",
|
||||
"description": "Create test-fix workflow session from session ID, description, or file path with test strategy generation and task planning",
|
||||
"arguments": "[--use-codex] [--cli-execute] (source-session-id | \\\"feature description\\\" | /path/to/file.md)",
|
||||
"arguments": "(source-session-id | \\\"feature description\\\" | /path/to/file.md)",
|
||||
"category": "workflow",
|
||||
"subcategory": null,
|
||||
"usage_scenario": "testing",
|
||||
@@ -417,7 +417,7 @@
|
||||
"name": "test-gen",
|
||||
"command": "/workflow:test-gen",
|
||||
"description": "Create independent test-fix workflow session from completed implementation session, analyzes code to generate test tasks",
|
||||
"arguments": "[--use-codex] [--cli-execute] source-session-id",
|
||||
"arguments": "source-session-id",
|
||||
"category": "workflow",
|
||||
"subcategory": null,
|
||||
"usage_scenario": "testing",
|
||||
@@ -632,7 +632,7 @@
|
||||
"name": "task-generate-agent",
|
||||
"command": "/workflow:tools:task-generate-agent",
|
||||
"description": "Generate implementation plan documents (IMPL_PLAN.md, task JSONs, TODO_LIST.md) using action-planning-agent - produces planning artifacts, does NOT execute code implementation",
|
||||
"arguments": "--session WFS-session-id [--cli-execute]",
|
||||
"arguments": "--session WFS-session-id",
|
||||
"category": "workflow",
|
||||
"subcategory": "tools",
|
||||
"usage_scenario": "implementation",
|
||||
@@ -643,7 +643,7 @@
|
||||
"name": "task-generate-tdd",
|
||||
"command": "/workflow:tools:task-generate-tdd",
|
||||
"description": "Autonomous TDD task generation using action-planning-agent with Red-Green-Refactor cycles, test-first structure, and cycle validation",
|
||||
"arguments": "--session WFS-session-id [--cli-execute]",
|
||||
"arguments": "--session WFS-session-id",
|
||||
"category": "workflow",
|
||||
"subcategory": "tools",
|
||||
"usage_scenario": "implementation",
|
||||
@@ -687,7 +687,7 @@
|
||||
"name": "test-task-generate",
|
||||
"command": "/workflow:tools:test-task-generate",
|
||||
"description": "Generate test planning documents (IMPL_PLAN.md, test task JSONs, TODO_LIST.md) using action-planning-agent - produces test planning artifacts, does NOT execute tests",
|
||||
"arguments": "[--use-codex] [--cli-execute] --session WFS-test-session-id",
|
||||
"arguments": "--session WFS-test-session-id",
|
||||
"category": "workflow",
|
||||
"subcategory": "tools",
|
||||
"usage_scenario": "implementation",
|
||||
|
||||
@@ -469,8 +469,8 @@
|
||||
{
|
||||
"name": "plan",
|
||||
"command": "/workflow:plan",
|
||||
"description": "5-phase planning workflow with action-planning-agent task generation, outputs IMPL_PLAN.md and task JSONs with optional CLI auto-execution",
|
||||
"arguments": "[--cli-execute] \\\"text description\\\"|file.md",
|
||||
"description": "5-phase planning workflow with action-planning-agent task generation, outputs IMPL_PLAN.md and task JSONs",
|
||||
"arguments": "\\\"text description\\\"|file.md",
|
||||
"category": "workflow",
|
||||
"subcategory": null,
|
||||
"usage_scenario": "planning",
|
||||
@@ -492,7 +492,7 @@
|
||||
"name": "tdd-plan",
|
||||
"command": "/workflow:tdd-plan",
|
||||
"description": "TDD workflow planning with Red-Green-Refactor task chain generation, test-first development structure, and cycle tracking",
|
||||
"arguments": "[--cli-execute] \\\"feature description\\\"|file.md",
|
||||
"arguments": "\\\"feature description\\\"|file.md",
|
||||
"category": "workflow",
|
||||
"subcategory": null,
|
||||
"usage_scenario": "planning",
|
||||
@@ -604,7 +604,7 @@
|
||||
"name": "task-generate-agent",
|
||||
"command": "/workflow:tools:task-generate-agent",
|
||||
"description": "Generate implementation plan documents (IMPL_PLAN.md, task JSONs, TODO_LIST.md) using action-planning-agent - produces planning artifacts, does NOT execute code implementation",
|
||||
"arguments": "--session WFS-session-id [--cli-execute]",
|
||||
"arguments": "--session WFS-session-id",
|
||||
"category": "workflow",
|
||||
"subcategory": "tools",
|
||||
"usage_scenario": "implementation",
|
||||
@@ -615,7 +615,7 @@
|
||||
"name": "task-generate-tdd",
|
||||
"command": "/workflow:tools:task-generate-tdd",
|
||||
"description": "Autonomous TDD task generation using action-planning-agent with Red-Green-Refactor cycles, test-first structure, and cycle validation",
|
||||
"arguments": "--session WFS-session-id [--cli-execute]",
|
||||
"arguments": "--session WFS-session-id",
|
||||
"category": "workflow",
|
||||
"subcategory": "tools",
|
||||
"usage_scenario": "implementation",
|
||||
@@ -626,7 +626,7 @@
|
||||
"name": "test-task-generate",
|
||||
"command": "/workflow:tools:test-task-generate",
|
||||
"description": "Generate test planning documents (IMPL_PLAN.md, test task JSONs, TODO_LIST.md) using action-planning-agent - produces test planning artifacts, does NOT execute tests",
|
||||
"arguments": "[--use-codex] [--cli-execute] --session WFS-test-session-id",
|
||||
"arguments": "--session WFS-test-session-id",
|
||||
"category": "workflow",
|
||||
"subcategory": "tools",
|
||||
"usage_scenario": "implementation",
|
||||
@@ -742,7 +742,7 @@
|
||||
"name": "test-fix-gen",
|
||||
"command": "/workflow:test-fix-gen",
|
||||
"description": "Create test-fix workflow session from session ID, description, or file path with test strategy generation and task planning",
|
||||
"arguments": "[--use-codex] [--cli-execute] (source-session-id | \\\"feature description\\\" | /path/to/file.md)",
|
||||
"arguments": "(source-session-id | \\\"feature description\\\" | /path/to/file.md)",
|
||||
"category": "workflow",
|
||||
"subcategory": null,
|
||||
"usage_scenario": "testing",
|
||||
@@ -753,7 +753,7 @@
|
||||
"name": "test-gen",
|
||||
"command": "/workflow:test-gen",
|
||||
"description": "Create independent test-fix workflow session from completed implementation session, analyzes code to generate test tasks",
|
||||
"arguments": "[--use-codex] [--cli-execute] source-session-id",
|
||||
"arguments": "source-session-id",
|
||||
"category": "workflow",
|
||||
"subcategory": null,
|
||||
"usage_scenario": "testing",
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
{
|
||||
"name": "plan",
|
||||
"command": "/workflow:plan",
|
||||
"description": "5-phase planning workflow with action-planning-agent task generation, outputs IMPL_PLAN.md and task JSONs with optional CLI auto-execution",
|
||||
"arguments": "[--cli-execute] \\\"text description\\\"|file.md",
|
||||
"description": "5-phase planning workflow with action-planning-agent task generation, outputs IMPL_PLAN.md and task JSONs",
|
||||
"arguments": "\\\"text description\\\"|file.md",
|
||||
"category": "workflow",
|
||||
"subcategory": null,
|
||||
"usage_scenario": "planning",
|
||||
|
||||
@@ -43,7 +43,6 @@ You are a pure execution agent specialized in creating actionable implementation
|
||||
- `context_package_path`: Context package with brainstorming artifacts catalog
|
||||
- **Metadata**: Simple values
|
||||
- `session_id`: Workflow session identifier (WFS-[topic])
|
||||
- `execution_mode`: agent-mode | cli-execute-mode
|
||||
- `mcp_capabilities`: Available MCP tools (exa_code, exa_web, code_index)
|
||||
|
||||
**Legacy Support** (backward compatibility):
|
||||
@@ -244,8 +243,7 @@ Generate individual `.task/IMPL-*.json` files with the following structure:
|
||||
"type": "test-gen|test-fix",
|
||||
"agent": "@code-developer|@test-fix-agent",
|
||||
"test_framework": "jest|vitest|pytest|junit|mocha",
|
||||
"coverage_target": "80%",
|
||||
"use_codex": true|false
|
||||
"coverage_target": "80%"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -253,7 +251,8 @@ Generate individual `.task/IMPL-*.json` files with the following structure:
|
||||
**Test-Specific Fields**:
|
||||
- `test_framework`: Existing test framework from project (required for test tasks)
|
||||
- `coverage_target`: Target code coverage percentage (optional)
|
||||
- `use_codex`: Whether to use Codex for automated fixes in test-fix tasks (optional, default: false)
|
||||
|
||||
**Note**: CLI tool usage for test-fix tasks is now controlled via `flow_control.implementation_approach` steps with `command` fields, not via `meta.use_codex`.
|
||||
|
||||
#### Context Object
|
||||
|
||||
@@ -485,15 +484,31 @@ The `implementation_approach` supports **two execution modes** based on the pres
|
||||
- `bash(codex --full-auto exec '[task]' resume --last --skip-git-repo-check -s danger-full-access)` (multi-step)
|
||||
- `bash(cd [path] && gemini -p '[prompt]' --approval-mode yolo)` (write mode)
|
||||
|
||||
**Mode Selection Strategy**:
|
||||
- **Default to agent execution** for most tasks
|
||||
- **Use CLI mode** when:
|
||||
- User explicitly requests CLI tool (codex/gemini/qwen)
|
||||
- Task requires multi-step autonomous reasoning beyond agent capability
|
||||
- Complex refactoring needs specialized tool analysis
|
||||
- Building on previous CLI execution context (use `resume --last`)
|
||||
**Semantic CLI Tool Selection**:
|
||||
|
||||
**Key Principle**: The `command` field is **optional**. Agent must decide based on task complexity and user preference.
|
||||
Agent determines CLI tool usage per-step based on user semantics and task nature.
|
||||
|
||||
**Source**: Scan `metadata.task_description` from context-package.json for CLI tool preferences.
|
||||
|
||||
**User Semantic Triggers** (patterns to detect in task_description):
|
||||
- "use Codex/codex" → Add `command` field with Codex CLI
|
||||
- "use Gemini/gemini" → Add `command` field with Gemini CLI
|
||||
- "use Qwen/qwen" → Add `command` field with Qwen CLI
|
||||
- "CLI execution" / "automated" → Infer appropriate CLI tool
|
||||
|
||||
**Task-Based Selection** (when no explicit user preference):
|
||||
- **Implementation/coding**: Codex preferred for autonomous development
|
||||
- **Analysis/exploration**: Gemini preferred for large context analysis
|
||||
- **Documentation**: Gemini/Qwen with write mode (`--approval-mode yolo`)
|
||||
- **Testing**: Depends on complexity - simple=agent, complex=Codex
|
||||
|
||||
**Default Behavior**: Agent always executes the workflow. CLI commands are embedded in `implementation_approach` steps:
|
||||
- Agent orchestrates task execution
|
||||
- When step has `command` field, agent executes it via Bash
|
||||
- When step has no `command` field, agent implements directly
|
||||
- This maintains agent control while leveraging CLI tool power
|
||||
|
||||
**Key Principle**: The `command` field is **optional**. Agent decides based on user semantics and task complexity.
|
||||
|
||||
**Examples**:
|
||||
|
||||
|
||||
@@ -66,8 +66,7 @@ You are a specialized execution agent that bridges CLI analysis tools with task
|
||||
"task_config": {
|
||||
"agent": "@test-fix-agent",
|
||||
"type": "test-fix-iteration",
|
||||
"max_iterations": 5,
|
||||
"use_codex": false
|
||||
"max_iterations": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -263,7 +262,6 @@ function extractModificationPoints() {
|
||||
"analysis_report": ".process/iteration-{iteration}-analysis.md",
|
||||
"cli_output": ".process/iteration-{iteration}-cli-output.txt",
|
||||
"max_iterations": "{task_config.max_iterations}",
|
||||
"use_codex": "{task_config.use_codex}",
|
||||
"parent_task": "{parent_task_id}",
|
||||
"created_by": "@cli-planning-agent",
|
||||
"created_at": "{timestamp}"
|
||||
|
||||
@@ -24,8 +24,6 @@ You are a code execution specialist focused on implementing high-quality, produc
|
||||
- **Context-driven** - Use provided context and existing code patterns
|
||||
- **Quality over speed** - Write boring, reliable code that works
|
||||
|
||||
|
||||
|
||||
## Execution Process
|
||||
|
||||
### 1. Context Assessment
|
||||
|
||||
@@ -100,10 +100,88 @@ if (!memory.has("README.md")) Read(README.md)
|
||||
|
||||
### Phase 2: Multi-Source Context Discovery
|
||||
|
||||
Execute all 3 tracks in parallel for comprehensive coverage.
|
||||
Execute all tracks in parallel for comprehensive coverage.
|
||||
|
||||
**Note**: Historical archive analysis (querying `.workflow/archives/manifest.json`) is optional and should be performed if the manifest exists. Inject findings into `conflict_detection.historical_conflicts[]`.
|
||||
|
||||
#### Track 0: Exploration Synthesis (Optional)
|
||||
|
||||
**Trigger**: When `explorations-manifest.json` exists in session `.process/` folder
|
||||
|
||||
**Purpose**: Transform raw exploration data into prioritized, deduplicated insights. This is NOT simple aggregation - it synthesizes `critical_files` (priority-ranked), deduplicates patterns/integration_points, and generates `conflict_indicators`.
|
||||
|
||||
```javascript
|
||||
// Check for exploration results from context-gather parallel explore phase
|
||||
const manifestPath = `.workflow/active/${session_id}/.process/explorations-manifest.json`;
|
||||
if (file_exists(manifestPath)) {
|
||||
const manifest = JSON.parse(Read(manifestPath));
|
||||
|
||||
// Load full exploration data from each file
|
||||
const explorationData = manifest.explorations.map(exp => ({
|
||||
...exp,
|
||||
data: JSON.parse(Read(exp.path))
|
||||
}));
|
||||
|
||||
// Build explorations array with summaries
|
||||
const explorations = explorationData.map(exp => ({
|
||||
angle: exp.angle,
|
||||
file: exp.file,
|
||||
path: exp.path,
|
||||
index: exp.data._metadata?.exploration_index || exp.index,
|
||||
summary: {
|
||||
relevant_files_count: exp.data.relevant_files?.length || 0,
|
||||
key_patterns: exp.data.patterns,
|
||||
integration_points: exp.data.integration_points
|
||||
}
|
||||
}));
|
||||
|
||||
// SYNTHESIS (not aggregation): Transform raw data into prioritized insights
|
||||
const aggregated_insights = {
|
||||
// CRITICAL: Synthesize priority-ranked critical_files from multiple relevant_files lists
|
||||
// - Deduplicate by path
|
||||
// - Rank by: mention count across angles + individual relevance scores
|
||||
// - Top 10-15 files only (focused, actionable)
|
||||
critical_files: synthesizeCriticalFiles(explorationData.flatMap(e => e.data.relevant_files || [])),
|
||||
|
||||
// SYNTHESIS: Generate conflict indicators from pattern mismatches, constraint violations
|
||||
conflict_indicators: synthesizeConflictIndicators(explorationData),
|
||||
|
||||
// Deduplicate clarification questions (merge similar questions)
|
||||
clarification_needs: deduplicateQuestions(explorationData.flatMap(e => e.data.clarification_needs || [])),
|
||||
|
||||
// Preserve source attribution for traceability
|
||||
constraints: explorationData.map(e => ({ constraint: e.data.constraints, source_angle: e.angle })).filter(c => c.constraint),
|
||||
|
||||
// Deduplicate patterns across angles (merge identical patterns)
|
||||
all_patterns: deduplicatePatterns(explorationData.map(e => ({ patterns: e.data.patterns, source_angle: e.angle }))),
|
||||
|
||||
// Deduplicate integration points (merge by file:line location)
|
||||
all_integration_points: deduplicateIntegrationPoints(explorationData.map(e => ({ points: e.data.integration_points, source_angle: e.angle })))
|
||||
};
|
||||
|
||||
// Store for Phase 3 packaging
|
||||
exploration_results = { manifest_path: manifestPath, exploration_count: manifest.exploration_count,
|
||||
complexity: manifest.complexity, angles: manifest.angles_explored,
|
||||
explorations, aggregated_insights };
|
||||
}
|
||||
|
||||
// Synthesis helper functions (conceptual)
|
||||
function synthesizeCriticalFiles(allRelevantFiles) {
|
||||
// 1. Group by path
|
||||
// 2. Count mentions across angles
|
||||
// 3. Average relevance scores
|
||||
// 4. Rank by: (mention_count * 0.6) + (avg_relevance * 0.4)
|
||||
// 5. Return top 10-15 with mentioned_by_angles attribution
|
||||
}
|
||||
|
||||
function synthesizeConflictIndicators(explorationData) {
|
||||
// 1. Detect pattern mismatches across angles
|
||||
// 2. Identify constraint violations
|
||||
// 3. Flag files mentioned with conflicting integration approaches
|
||||
// 4. Assign severity: critical/high/medium/low
|
||||
}
|
||||
```
|
||||
|
||||
#### Track 1: Reference Documentation
|
||||
|
||||
Extract from Phase 0 loaded docs:
|
||||
@@ -393,10 +471,39 @@ Calculate risk level based on:
|
||||
},
|
||||
"affected_modules": ["auth", "user-model", "middleware"],
|
||||
"mitigation_strategy": "Incremental refactoring with backward compatibility"
|
||||
},
|
||||
"exploration_results": {
|
||||
"manifest_path": ".workflow/active/{session}/.process/explorations-manifest.json",
|
||||
"exploration_count": 3,
|
||||
"complexity": "Medium",
|
||||
"angles": ["architecture", "dependencies", "testing"],
|
||||
"explorations": [
|
||||
{
|
||||
"angle": "architecture",
|
||||
"file": "exploration-architecture.json",
|
||||
"path": ".workflow/active/{session}/.process/exploration-architecture.json",
|
||||
"index": 1,
|
||||
"summary": {
|
||||
"relevant_files_count": 5,
|
||||
"key_patterns": "Service layer with DI",
|
||||
"integration_points": "Container.registerService:45-60"
|
||||
}
|
||||
}
|
||||
],
|
||||
"aggregated_insights": {
|
||||
"critical_files": [{"path": "src/auth/AuthService.ts", "relevance": 0.95, "mentioned_by_angles": ["architecture"]}],
|
||||
"conflict_indicators": [{"type": "pattern_mismatch", "description": "...", "source_angle": "architecture", "severity": "medium"}],
|
||||
"clarification_needs": [{"question": "...", "context": "...", "options": [], "source_angle": "architecture"}],
|
||||
"constraints": [{"constraint": "Must follow existing DI pattern", "source_angle": "architecture"}],
|
||||
"all_patterns": [{"patterns": "Service layer with DI", "source_angle": "architecture"}],
|
||||
"all_integration_points": [{"points": "Container.registerService:45-60", "source_angle": "architecture"}]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: `exploration_results` is populated when exploration files exist (from context-gather parallel explore phase). If no explorations, this field is omitted or empty.
|
||||
|
||||
|
||||
|
||||
## Quality Validation
|
||||
|
||||
@@ -142,9 +142,9 @@ run_test_layer "L1-unit" "$UNIT_CMD"
|
||||
|
||||
### 3. Failure Diagnosis & Fixing Loop
|
||||
|
||||
**Execution Modes**:
|
||||
**Execution Modes** (determined by `flow_control.implementation_approach`):
|
||||
|
||||
**A. Manual Mode (Default, meta.use_codex=false)**:
|
||||
**A. Agent Mode (Default, no `command` field in steps)**:
|
||||
```
|
||||
WHILE tests are failing AND iterations < max_iterations:
|
||||
1. Use Gemini to diagnose failure (bug-fix template)
|
||||
@@ -155,17 +155,17 @@ WHILE tests are failing AND iterations < max_iterations:
|
||||
END WHILE
|
||||
```
|
||||
|
||||
**B. Codex Mode (meta.use_codex=true)**:
|
||||
**B. CLI Mode (`command` field present in implementation_approach steps)**:
|
||||
```
|
||||
WHILE tests are failing AND iterations < max_iterations:
|
||||
1. Use Gemini to diagnose failure (bug-fix template)
|
||||
2. Use Codex to apply fixes automatically with resume mechanism
|
||||
2. Execute `command` field (e.g., Codex) to apply fixes automatically
|
||||
3. Re-run test suite
|
||||
4. Verify fix doesn't break other tests
|
||||
END WHILE
|
||||
```
|
||||
|
||||
**Codex Resume in Test-Fix Cycle** (when `meta.use_codex=true`):
|
||||
**Codex Resume in Test-Fix Cycle** (when step has `command` with Codex):
|
||||
- First iteration: Start new Codex session with full context
|
||||
- Subsequent iterations: Use `resume --last` to maintain fix history and apply consistent strategies
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ Load only minimal necessary context from each artifact:
|
||||
- Dependencies (depends_on, blocks)
|
||||
- Context (requirements, focus_paths, acceptance, artifacts)
|
||||
- Flow control (pre_analysis, implementation_approach)
|
||||
- Meta (complexity, priority, use_codex)
|
||||
- Meta (complexity, priority)
|
||||
|
||||
### 3. Build Semantic Models
|
||||
|
||||
|
||||
@@ -98,10 +98,10 @@ Analyze project for workflow initialization and generate .workflow/project.json.
|
||||
Generate complete project.json with:
|
||||
- project_name: ${projectName}
|
||||
- initialized_at: current ISO timestamp
|
||||
- overview: {description, technology_stack, architecture, key_components, entry_points, metrics}
|
||||
- overview: {description, technology_stack, architecture, key_components}
|
||||
- features: ${regenerate ? 'preserve from backup' : '[] (empty)'}
|
||||
- development_index: ${regenerate ? 'preserve from backup' : '{feature: [], enhancement: [], bugfix: [], refactor: [], docs: []}'}
|
||||
- statistics: ${regenerate ? 'preserve from backup' : '{total_features: 0, total_sessions: 0, last_updated}'}
|
||||
- memory_resources: {skills, documentation, module_docs, gaps, last_scanned}
|
||||
- _metadata: {initialized_by: "cli-explore-agent", analysis_timestamp, analysis_mode}
|
||||
|
||||
## Analysis Requirements
|
||||
@@ -118,23 +118,6 @@ Generate complete project.json with:
|
||||
- Patterns: singleton, factory, repository
|
||||
- Key components: 5-10 modules {name, path, description, importance}
|
||||
|
||||
**Metrics**:
|
||||
- total_files: Source files (exclude tests/configs)
|
||||
- lines_of_code: Use find + wc -l
|
||||
- module_count: Use ~/.claude/scripts/get_modules_by_depth.sh
|
||||
- complexity: low | medium | high
|
||||
|
||||
**Entry Points**:
|
||||
- main: index.ts, main.py, main.go
|
||||
- cli_commands: package.json scripts, Makefile targets
|
||||
- api_endpoints: HTTP/REST routes (if applicable)
|
||||
|
||||
**Memory Resources**:
|
||||
- skills: Scan .claude/skills/ → [{name, type, path}]
|
||||
- documentation: Scan .workflow/docs/ → [{name, path, has_readme, has_architecture}]
|
||||
- module_docs: Find **/CLAUDE.md (exclude node_modules, .git)
|
||||
- gaps: Identify missing resources
|
||||
|
||||
## Execution
|
||||
1. Structural scan: get_modules_by_depth.sh, find, wc -l
|
||||
2. Semantic analysis: Gemini for patterns/architecture
|
||||
@@ -168,17 +151,6 @@ Frameworks: ${projectJson.overview.technology_stack.frameworks.join(', ')}
|
||||
Style: ${projectJson.overview.architecture.style}
|
||||
Components: ${projectJson.overview.key_components.length} core modules
|
||||
|
||||
### Metrics
|
||||
Files: ${projectJson.overview.metrics.total_files}
|
||||
LOC: ${projectJson.overview.metrics.lines_of_code}
|
||||
Complexity: ${projectJson.overview.metrics.complexity}
|
||||
|
||||
### Memory Resources
|
||||
SKILL Packages: ${projectJson.memory_resources.skills.length}
|
||||
Documentation: ${projectJson.memory_resources.documentation.length}
|
||||
Module Docs: ${projectJson.memory_resources.module_docs.length}
|
||||
Gaps: ${projectJson.memory_resources.gaps.join(', ') || 'none'}
|
||||
|
||||
---
|
||||
Project state: .workflow/project.json
|
||||
${regenerate ? 'Backup: .workflow/project.json.backup' : ''}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -73,8 +73,11 @@ Phase 5: Dispatch
|
||||
|
||||
**Session Setup**:
|
||||
```javascript
|
||||
// Helper: Get UTC+8 (China Standard Time) ISO string
|
||||
const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString()
|
||||
|
||||
const taskSlug = task_description.toLowerCase().replace(/[^a-z0-9]+/g, '-').substring(0, 40)
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
||||
const timestamp = getUtc8ISOString().replace(/[:.]/g, '-')
|
||||
const shortTimestamp = timestamp.substring(0, 19).replace('T', '-')
|
||||
const sessionId = `${taskSlug}-${shortTimestamp}`
|
||||
const sessionFolder = `.workflow/.lite-plan/${sessionId}`
|
||||
@@ -237,7 +240,7 @@ const explorationFiles = bash(`find ${sessionFolder} -name "exploration-*.json"
|
||||
const explorationManifest = {
|
||||
session_id: sessionId,
|
||||
task_description: task_description,
|
||||
timestamp: new Date().toISOString(),
|
||||
timestamp: getUtc8ISOString(),
|
||||
complexity: complexity,
|
||||
exploration_count: explorationCount,
|
||||
explorations: explorationFiles.map(file => {
|
||||
@@ -351,7 +354,7 @@ const plan = {
|
||||
estimated_time: "...",
|
||||
recommended_execution: "Agent",
|
||||
complexity: "Low",
|
||||
_metadata: { timestamp: new Date().toISOString(), source: "direct-planning", planning_mode: "direct" }
|
||||
_metadata: { timestamp: getUtc8ISOString(), source: "direct-planning", planning_mode: "direct" }
|
||||
}
|
||||
|
||||
// Step 3: Write plan to session folder
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: plan
|
||||
description: 5-phase planning workflow with action-planning-agent task generation, outputs IMPL_PLAN.md and task JSONs with optional CLI auto-execution
|
||||
argument-hint: "[--cli-execute] \"text description\"|file.md"
|
||||
description: 5-phase planning workflow with action-planning-agent task generation, outputs IMPL_PLAN.md and task JSONs
|
||||
argument-hint: "\"text description\"|file.md"
|
||||
allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
---
|
||||
|
||||
@@ -69,7 +69,7 @@ Phase 3: Conflict Resolution (conditional)
|
||||
└─ conflict_risk < medium → Skip to Phase 4
|
||||
|
||||
Phase 4: Task Generation
|
||||
└─ /workflow:tools:task-generate-agent --session sessionId [--cli-execute]
|
||||
└─ /workflow:tools:task-generate-agent --session sessionId
|
||||
└─ Output: IMPL_PLAN.md, task JSONs, TODO_LIST.md
|
||||
|
||||
Return:
|
||||
@@ -273,15 +273,10 @@ SlashCommand(command="/workflow:tools:conflict-resolution --session [sessionId]
|
||||
**Step 4.1: Dispatch** - Generate implementation plan and task JSONs
|
||||
|
||||
```javascript
|
||||
// Default (agent mode)
|
||||
SlashCommand(command="/workflow:tools:task-generate-agent --session [sessionId]")
|
||||
|
||||
// With CLI execution (if --cli-execute flag present)
|
||||
SlashCommand(command="/workflow:tools:task-generate-agent --session [sessionId] --cli-execute")
|
||||
```
|
||||
|
||||
**Flag**:
|
||||
- `--cli-execute`: Generate tasks with Codex execution commands
|
||||
**CLI Execution Note**: CLI tool usage is now determined semantically by action-planning-agent based on user's task description. If user specifies "use Codex/Gemini/Qwen for X", the agent embeds `command` fields in relevant `implementation_approach` steps.
|
||||
|
||||
**Input**: `sessionId` from Phase 1
|
||||
|
||||
@@ -423,7 +418,7 @@ Phase 3: conflict-resolution [AUTO-TRIGGERED if conflict_risk ≥ medium]
|
||||
↓ Output: Modified brainstorm artifacts (NO report file)
|
||||
↓ Skip if conflict_risk is none/low → proceed directly to Phase 4
|
||||
↓
|
||||
Phase 4: task-generate-agent --session sessionId [--cli-execute]
|
||||
Phase 4: task-generate-agent --session sessionId
|
||||
↓ Input: sessionId + resolved brainstorm artifacts + session memory
|
||||
↓ Output: IMPL_PLAN.md, task JSONs, TODO_LIST.md
|
||||
↓
|
||||
@@ -504,9 +499,7 @@ Return summary to user
|
||||
- **If conflict_risk ≥ medium**: Launch Phase 3 conflict-resolution with sessionId and contextPath
|
||||
- Wait for Phase 3 to finish executing (if executed), verify CONFLICT_RESOLUTION.md created
|
||||
- **If conflict_risk is none/low**: Skip Phase 3, proceed directly to Phase 4
|
||||
- **Build Phase 4 command**:
|
||||
- Base command: `/workflow:tools:task-generate-agent --session [sessionId]`
|
||||
- Add `--cli-execute` if flag present
|
||||
- **Build Phase 4 command**: `/workflow:tools:task-generate-agent --session [sessionId]`
|
||||
- Pass session ID to Phase 4 command
|
||||
- Verify all Phase 4 outputs
|
||||
- Update TodoWrite after each phase (dynamically adjust for Phase 3 presence)
|
||||
|
||||
@@ -113,11 +113,6 @@ 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')}
|
||||
|
||||
**Metrics**:
|
||||
- Files: ${overview.metrics.total_files}
|
||||
- Lines of Code: ${overview.metrics.lines_of_code}
|
||||
- Complexity: ${overview.metrics.complexity}
|
||||
|
||||
---
|
||||
` : ''}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: tdd-plan
|
||||
description: TDD workflow planning with Red-Green-Refactor task chain generation, test-first development structure, and cycle tracking
|
||||
argument-hint: "[--cli-execute] \"feature description\"|file.md"
|
||||
argument-hint: "\"feature description\"|file.md"
|
||||
allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
---
|
||||
|
||||
@@ -11,9 +11,7 @@ allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
|
||||
**This command is a pure orchestrator**: Dispatches 6 slash commands in sequence, parse outputs, pass context, and ensure complete TDD workflow creation with Red-Green-Refactor task generation.
|
||||
|
||||
**Execution Modes**:
|
||||
- **Agent Mode** (default): Use `/workflow:tools:task-generate-tdd` (autonomous agent-driven)
|
||||
- **CLI Mode** (`--cli-execute`): Use `/workflow:tools:task-generate-tdd --cli-execute` (Gemini/Qwen)
|
||||
**CLI Tool Selection**: CLI tool usage is determined semantically from user's task description. Include "use Codex/Gemini/Qwen" in your request for CLI execution.
|
||||
|
||||
**Task Attachment Model**:
|
||||
- SlashCommand dispatch **expands workflow** by attaching sub-tasks to current TodoWrite
|
||||
@@ -235,13 +233,11 @@ SlashCommand(command="/workflow:tools:conflict-resolution --session [sessionId]
|
||||
**Step 5.1: Dispatch** - TDD task generation via action-planning-agent
|
||||
|
||||
```javascript
|
||||
// Agent Mode (default)
|
||||
SlashCommand(command="/workflow:tools:task-generate-tdd --session [sessionId]")
|
||||
|
||||
// CLI Mode (--cli-execute flag)
|
||||
SlashCommand(command="/workflow:tools:task-generate-tdd --session [sessionId] --cli-execute")
|
||||
```
|
||||
|
||||
**Note**: CLI tool usage is determined semantically from user's task description.
|
||||
|
||||
**Parse**: Extract feature count, task count (not chain count - tasks now contain internal TDD cycles)
|
||||
|
||||
**Validate**:
|
||||
@@ -454,8 +450,7 @@ Convert user input to TDD-structured format:
|
||||
- `/workflow:tools:test-context-gather` - Phase 3: Analyze existing test patterns and coverage
|
||||
- `/workflow:tools:conflict-resolution` - Phase 4: Detect and resolve conflicts (auto-triggered if conflict_risk ≥ medium)
|
||||
- `/compact` - Phase 4: Memory optimization (if context approaching limits)
|
||||
- `/workflow:tools:task-generate-tdd` - Phase 5: Generate TDD tasks with agent-driven approach (default, autonomous)
|
||||
- `/workflow:tools:task-generate-tdd --cli-execute` - Phase 5: Generate TDD tasks with CLI tools (Gemini/Qwen, when `--cli-execute` flag used)
|
||||
- `/workflow:tools:task-generate-tdd` - Phase 5: Generate TDD tasks (CLI tool usage determined semantically)
|
||||
|
||||
**Follow-up Commands**:
|
||||
- `/workflow:action-plan-verify` - Recommended: Verify TDD plan quality and structure before execution
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: test-fix-gen
|
||||
description: Create test-fix workflow session from session ID, description, or file path with test strategy generation and task planning
|
||||
argument-hint: "[--use-codex] [--cli-execute] (source-session-id | \"feature description\" | /path/to/file.md)"
|
||||
argument-hint: "(source-session-id | \"feature description\" | /path/to/file.md)"
|
||||
allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
---
|
||||
|
||||
@@ -43,7 +43,7 @@ fi
|
||||
- **Session Isolation**: Creates independent `WFS-test-[slug]` session
|
||||
- **Context-First**: Gathers implementation context via appropriate method
|
||||
- **Format Reuse**: Creates standard `IMPL-*.json` tasks with `meta.type: "test-fix"`
|
||||
- **Manual First**: Default to manual fixes, use `--use-codex` for automation
|
||||
- **Semantic CLI Selection**: CLI tool usage determined from user's task description
|
||||
- **Automatic Detection**: Input pattern determines execution mode
|
||||
|
||||
### Coordinator Role
|
||||
@@ -79,16 +79,14 @@ This command is a **pure planning coordinator**:
|
||||
|
||||
```bash
|
||||
# Basic syntax
|
||||
/workflow:test-fix-gen [FLAGS] <INPUT>
|
||||
|
||||
# Flags (optional)
|
||||
--use-codex # Enable Codex automated fixes in IMPL-002
|
||||
--cli-execute # Enable CLI execution in IMPL-001
|
||||
/workflow:test-fix-gen <INPUT>
|
||||
|
||||
# Input
|
||||
<INPUT> # Session ID, description, or file path
|
||||
```
|
||||
|
||||
**Note**: CLI tool usage is determined semantically from the task description. To request CLI execution, include it in your description (e.g., "use Codex for automated fixes").
|
||||
|
||||
### Usage Examples
|
||||
|
||||
#### Session Mode
|
||||
@@ -96,11 +94,8 @@ This command is a **pure planning coordinator**:
|
||||
# Test validation for completed implementation
|
||||
/workflow:test-fix-gen WFS-user-auth-v2
|
||||
|
||||
# With automated fixes
|
||||
/workflow:test-fix-gen --use-codex WFS-api-endpoints
|
||||
|
||||
# With CLI execution
|
||||
/workflow:test-fix-gen --cli-execute --use-codex WFS-payment-flow
|
||||
# With semantic CLI request
|
||||
/workflow:test-fix-gen WFS-api-endpoints # Add "use Codex" in description for automated fixes
|
||||
```
|
||||
|
||||
#### Prompt Mode - Text Description
|
||||
@@ -108,17 +103,14 @@ This command is a **pure planning coordinator**:
|
||||
# Generate tests from feature description
|
||||
/workflow:test-fix-gen "Test the user authentication API endpoints in src/auth/api.ts"
|
||||
|
||||
# With automated fixes
|
||||
/workflow:test-fix-gen --use-codex "Test user registration and login flows"
|
||||
# With CLI execution (semantic)
|
||||
/workflow:test-fix-gen "Test user registration and login flows, use Codex for automated fixes"
|
||||
```
|
||||
|
||||
#### Prompt Mode - File Reference
|
||||
```bash
|
||||
# Generate tests from requirements file
|
||||
/workflow:test-fix-gen ./docs/api-requirements.md
|
||||
|
||||
# With flags
|
||||
/workflow:test-fix-gen --use-codex --cli-execute ./specs/feature.md
|
||||
```
|
||||
|
||||
### Mode Comparison
|
||||
@@ -143,7 +135,7 @@ This command is a **pure planning coordinator**:
|
||||
5. **Complete All Phases**: Do not return until Phase 5 completes
|
||||
6. **Track Progress**: Update TodoWrite dynamically with task attachment/collapse pattern
|
||||
7. **Automatic Detection**: Mode auto-detected from input pattern
|
||||
8. **Parse Flags**: Extract `--use-codex` and `--cli-execute` flags for Phase 4
|
||||
8. **Semantic CLI Detection**: CLI tool usage determined from user's task description for Phase 4
|
||||
9. **Task Attachment Model**: SlashCommand dispatch **attaches** sub-tasks to current workflow. Orchestrator **executes** these attached tasks itself, then **collapses** them after completion
|
||||
10. **⚠️ CRITICAL: DO NOT STOP**: Continuous multi-phase workflow. After executing all attached tasks, immediately collapse them and execute next phase
|
||||
|
||||
@@ -151,13 +143,25 @@ This command is a **pure planning coordinator**:
|
||||
|
||||
#### Phase 1: Create Test Session
|
||||
|
||||
**Step 1.1: Dispatch** - Create test workflow session
|
||||
**Step 1.0: Load Source Session Intent (Session Mode Only)** - Preserve user's original task description for semantic CLI selection
|
||||
|
||||
```javascript
|
||||
// Session Mode
|
||||
SlashCommand(command="/workflow:session:start --new \"Test validation for [sourceSessionId]\"")
|
||||
// Session Mode: Read source session metadata to get original task description
|
||||
Read(".workflow/active/[sourceSessionId]/workflow-session.json")
|
||||
// OR if context-package exists:
|
||||
Read(".workflow/active/[sourceSessionId]/.process/context-package.json")
|
||||
|
||||
// Prompt Mode
|
||||
// Extract: metadata.task_description or project/description field
|
||||
// This preserves user's CLI tool preferences (e.g., "use Codex for fixes")
|
||||
```
|
||||
|
||||
**Step 1.1: Dispatch** - Create test workflow session with preserved intent
|
||||
|
||||
```javascript
|
||||
// Session Mode - Include original task description to enable semantic CLI selection
|
||||
SlashCommand(command="/workflow:session:start --new \"Test validation for [sourceSessionId]: [originalTaskDescription]\"")
|
||||
|
||||
// Prompt Mode - User's description already contains their intent
|
||||
SlashCommand(command="/workflow:session:start --new \"Test generation for: [description]\"")
|
||||
```
|
||||
|
||||
@@ -166,8 +170,8 @@ SlashCommand(command="/workflow:session:start --new \"Test generation for: [desc
|
||||
**Expected Behavior**:
|
||||
- Creates new session: `WFS-test-[slug]`
|
||||
- Writes `workflow-session.json` metadata:
|
||||
- **Session Mode**: Includes `workflow_type: "test_session"`, `source_session_id: "[sourceId]"`
|
||||
- **Prompt Mode**: Includes `workflow_type: "test_session"` only
|
||||
- **Session Mode**: Includes `workflow_type: "test_session"`, `source_session_id: "[sourceId]"`, description with original user intent
|
||||
- **Prompt Mode**: Includes `workflow_type: "test_session"` only (user's description already contains intent)
|
||||
- Returns new session ID
|
||||
|
||||
**Parse Output**:
|
||||
@@ -283,13 +287,13 @@ For each targeted file/function, Gemini MUST generate:
|
||||
**Step 4.1: Dispatch** - Generate test task JSONs
|
||||
|
||||
```javascript
|
||||
SlashCommand(command="/workflow:tools:test-task-generate [--use-codex] [--cli-execute] --session [testSessionId]")
|
||||
SlashCommand(command="/workflow:tools:test-task-generate --session [testSessionId]")
|
||||
```
|
||||
|
||||
**Input**:
|
||||
- `testSessionId` from Phase 1
|
||||
- `--use-codex` flag (if present) - Controls IMPL-002 fix mode
|
||||
- `--cli-execute` flag (if present) - Controls IMPL-001 generation mode
|
||||
|
||||
**Note**: CLI tool usage is determined semantically from user's task description.
|
||||
|
||||
**Expected Behavior**:
|
||||
- Parse TEST_ANALYSIS_RESULTS.md from Phase 3 (multi-layered test plan)
|
||||
@@ -422,7 +426,7 @@ CRITICAL - Next Steps:
|
||||
- **Phase 2**: Mode-specific context gathering (session summaries vs codebase analysis)
|
||||
- **Phase 3**: Multi-layered test requirements analysis (L0: Static, L1: Unit, L2: Integration, L3: E2E)
|
||||
- **Phase 4**: Multi-task generation with quality gate (IMPL-001, IMPL-001.5-review, IMPL-002)
|
||||
- **Fix Mode Configuration**: `--use-codex` flag controls IMPL-002 fix mode (manual vs automated)
|
||||
- **Fix Mode Configuration**: CLI tool usage determined semantically from user's task description
|
||||
|
||||
|
||||
---
|
||||
@@ -521,16 +525,15 @@ If quality gate fails:
|
||||
- Task ID: `IMPL-002`
|
||||
- `meta.type: "test-fix"`
|
||||
- `meta.agent: "@test-fix-agent"`
|
||||
- `meta.use_codex: true|false` (based on `--use-codex` flag)
|
||||
- `context.depends_on: ["IMPL-001"]`
|
||||
- `context.requirements`: Execute and fix tests
|
||||
|
||||
**Test-Fix Cycle Specification**:
|
||||
**Note**: This specification describes what test-cycle-execute orchestrator will do. The agent only executes single tasks.
|
||||
- **Cycle Pattern** (orchestrator-managed): test → gemini_diagnose → manual_fix (or codex) → retest
|
||||
- **Cycle Pattern** (orchestrator-managed): test → gemini_diagnose → fix (agent or CLI) → retest
|
||||
- **Tools Configuration** (orchestrator-controlled):
|
||||
- Gemini for analysis with bug-fix template → surgical fix suggestions
|
||||
- Manual fix application (default) OR Codex if `--use-codex` flag (resume mechanism)
|
||||
- Agent fix application (default) OR CLI if `command` field present in implementation_approach
|
||||
- **Exit Conditions** (orchestrator-enforced):
|
||||
- Success: All tests pass
|
||||
- Failure: Max iterations reached (5)
|
||||
@@ -674,8 +677,7 @@ Key Points:
|
||||
4. **Mode Selection**:
|
||||
- Use **Session Mode** for completed workflow validation
|
||||
- Use **Prompt Mode** for ad-hoc test generation
|
||||
- Use `--use-codex` for autonomous fix application
|
||||
- Use `--cli-execute` for enhanced generation capabilities
|
||||
- Include "use Codex" in description for autonomous fix application
|
||||
|
||||
## Related Commands
|
||||
|
||||
@@ -688,9 +690,7 @@ Key Points:
|
||||
- `/workflow:tools:test-context-gather` - Phase 2 (Session Mode): Gather source session context
|
||||
- `/workflow:tools:context-gather` - Phase 2 (Prompt Mode): Analyze codebase directly
|
||||
- `/workflow:tools:test-concept-enhanced` - Phase 3: Generate test requirements using Gemini
|
||||
- `/workflow:tools:test-task-generate` - Phase 4: Generate test task JSONs using action-planning-agent (autonomous, default)
|
||||
- `/workflow:tools:test-task-generate --use-codex` - Phase 4: With automated Codex fixes for IMPL-002 (when `--use-codex` flag used)
|
||||
- `/workflow:tools:test-task-generate --cli-execute` - Phase 4: With CLI execution mode for IMPL-001 test generation (when `--cli-execute` flag used)
|
||||
- `/workflow:tools:test-task-generate` - Phase 4: Generate test task JSONs (CLI tool usage determined semantically)
|
||||
|
||||
**Follow-up Commands**:
|
||||
- `/workflow:status` - Review generated test tasks
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: test-gen
|
||||
description: Create independent test-fix workflow session from completed implementation session, analyzes code to generate test tasks
|
||||
argument-hint: "[--use-codex] [--cli-execute] source-session-id"
|
||||
argument-hint: "source-session-id"
|
||||
allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
---
|
||||
|
||||
@@ -16,7 +16,7 @@ allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
- **Context-First**: Prioritizes gathering code changes and summaries from source session
|
||||
- **Format Reuse**: Creates standard `IMPL-*.json` task, using `meta.type: "test-fix"` for agent assignment
|
||||
- **Parameter Simplification**: Tools auto-detect test session type via metadata, no manual cross-session parameters needed
|
||||
- **Manual First**: Default to manual fixes, use `--use-codex` flag for automated Codex fix application
|
||||
- **Semantic CLI Selection**: CLI tool usage is determined by user's task description (e.g., "use Codex for fixes")
|
||||
|
||||
**Task Attachment Model**:
|
||||
- SlashCommand dispatch **expands workflow** by attaching sub-tasks to current TodoWrite
|
||||
@@ -48,7 +48,7 @@ allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
5. **Complete All Phases**: Do not return to user until Phase 5 completes (summary returned)
|
||||
6. **Track Progress**: Update TodoWrite dynamically with task attachment/collapse pattern
|
||||
7. **Automatic Detection**: context-gather auto-detects test session and gathers source session context
|
||||
8. **Parse --use-codex Flag**: Extract flag from arguments and pass to Phase 4 (test-task-generate)
|
||||
8. **Semantic CLI Selection**: CLI tool usage determined from user's task description, passed to Phase 4
|
||||
9. **Command Boundary**: This command ends at Phase 5 summary. Test execution is NOT part of this command.
|
||||
10. **Task Attachment Model**: SlashCommand dispatch **attaches** sub-tasks to current workflow. Orchestrator **executes** these attached tasks itself, then **collapses** them after completion
|
||||
11. **⚠️ CRITICAL: DO NOT STOP**: Continuous multi-phase workflow. After executing all attached tasks, immediately collapse them and execute next phase
|
||||
@@ -57,19 +57,35 @@ allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
|
||||
### Phase 1: Create Test Session
|
||||
|
||||
**Step 1.1: Dispatch** - Create new test workflow session
|
||||
**Step 1.0: Load Source Session Intent** - Preserve user's original task description for semantic CLI selection
|
||||
|
||||
```javascript
|
||||
SlashCommand(command="/workflow:session:start --new \"Test validation for [sourceSessionId]\"")
|
||||
// Read source session metadata to get original task description
|
||||
Read(".workflow/active/[sourceSessionId]/workflow-session.json")
|
||||
// OR if context-package exists:
|
||||
Read(".workflow/active/[sourceSessionId]/.process/context-package.json")
|
||||
|
||||
// Extract: metadata.task_description or project/description field
|
||||
// This preserves user's CLI tool preferences (e.g., "use Codex for fixes")
|
||||
```
|
||||
|
||||
**Input**: `sourceSessionId` from user argument (e.g., `WFS-user-auth`)
|
||||
**Step 1.1: Dispatch** - Create new test workflow session with preserved intent
|
||||
|
||||
```javascript
|
||||
// Include original task description to enable semantic CLI selection
|
||||
SlashCommand(command="/workflow:session:start --new \"Test validation for [sourceSessionId]: [originalTaskDescription]\"")
|
||||
```
|
||||
|
||||
**Input**:
|
||||
- `sourceSessionId` from user argument (e.g., `WFS-user-auth`)
|
||||
- `originalTaskDescription` from source session metadata (preserves CLI tool preferences)
|
||||
|
||||
**Expected Behavior**:
|
||||
- Creates new session with pattern `WFS-test-[source-slug]` (e.g., `WFS-test-user-auth`)
|
||||
- Writes metadata to `workflow-session.json`:
|
||||
- `workflow_type: "test_session"`
|
||||
- `source_session_id: "[sourceSessionId]"`
|
||||
- Description includes original user intent for semantic CLI selection
|
||||
- Returns new session ID for subsequent phases
|
||||
|
||||
**Parse Output**:
|
||||
@@ -224,13 +240,13 @@ SlashCommand(command="/workflow:tools:test-concept-enhanced --session [testSessi
|
||||
**Step 4.1: Dispatch** - Generate test task JSON files and planning documents
|
||||
|
||||
```javascript
|
||||
SlashCommand(command="/workflow:tools:test-task-generate [--use-codex] [--cli-execute] --session [testSessionId]")
|
||||
SlashCommand(command="/workflow:tools:test-task-generate --session [testSessionId]")
|
||||
```
|
||||
|
||||
**Input**:
|
||||
- `testSessionId` from Phase 1
|
||||
- `--use-codex` flag (if present in original command) - Controls IMPL-002 fix mode
|
||||
- `--cli-execute` flag (if present in original command) - Controls IMPL-001 generation mode
|
||||
|
||||
**Note**: CLI tool usage for fixes is determined semantically from user's task description (e.g., "use Codex for automated fixes").
|
||||
|
||||
**Expected Behavior**:
|
||||
- Parse TEST_ANALYSIS_RESULTS.md from Phase 3
|
||||
@@ -260,16 +276,15 @@ SlashCommand(command="/workflow:tools:test-task-generate [--use-codex] [--cli-ex
|
||||
- Task ID: `IMPL-002`
|
||||
- `meta.type: "test-fix"`
|
||||
- `meta.agent: "@test-fix-agent"`
|
||||
- `meta.use_codex: true|false` (based on --use-codex flag)
|
||||
- `context.depends_on: ["IMPL-001"]`
|
||||
- `context.requirements`: Execute and fix tests
|
||||
- `flow_control.implementation_approach.test_fix_cycle`: Complete cycle specification
|
||||
- **Cycle pattern**: test → gemini_diagnose → manual_fix (or codex if --use-codex) → retest
|
||||
- **Tools configuration**: Gemini for analysis with bug-fix template, manual or Codex for fixes
|
||||
- **Cycle pattern**: test → gemini_diagnose → fix (agent or CLI based on `command` field) → retest
|
||||
- **Tools configuration**: Gemini for analysis with bug-fix template, agent or CLI for fixes
|
||||
- **Exit conditions**: Success (all pass) or failure (max iterations)
|
||||
- `flow_control.implementation_approach.modification_points`: 3-phase execution flow
|
||||
- Phase 1: Initial test execution
|
||||
- Phase 2: Iterative Gemini diagnosis + manual/Codex fixes (based on flag)
|
||||
- Phase 2: Iterative Gemini diagnosis + fixes (agent or CLI based on step's `command` field)
|
||||
- Phase 3: Final validation and certification
|
||||
|
||||
<!-- TodoWrite: When test-task-generate dispatched, INSERT 3 test-task-generate tasks -->
|
||||
@@ -327,7 +342,7 @@ Artifacts Created:
|
||||
|
||||
Test Framework: [detected framework]
|
||||
Test Files to Generate: [count]
|
||||
Fix Mode: [Manual|Codex Automated] (based on --use-codex flag)
|
||||
Fix Mode: [Agent|CLI] (based on `command` field in implementation_approach steps)
|
||||
|
||||
Review Generated Artifacts:
|
||||
- Test plan: .workflow/[testSessionId]/IMPL_PLAN.md
|
||||
@@ -373,7 +388,7 @@ Ready for execution. Use appropriate workflow commands to proceed.
|
||||
- **Phase 2**: Cross-session context gathering from source implementation session
|
||||
- **Phase 3**: Test requirements analysis with Gemini for generation strategy
|
||||
- **Phase 4**: Dual-task generation (IMPL-001 for test generation, IMPL-002 for test execution)
|
||||
- **Fix Mode Configuration**: `--use-codex` flag controls IMPL-002 fix mode (manual vs automated)
|
||||
- **Fix Mode Configuration**: CLI tool usage determined semantically from user's task description
|
||||
|
||||
|
||||
|
||||
@@ -444,7 +459,7 @@ Generates two task definition files:
|
||||
- Agent: @test-fix-agent
|
||||
- Dependency: IMPL-001 must complete first
|
||||
- Max iterations: 5
|
||||
- Fix mode: Manual or Codex (based on --use-codex flag)
|
||||
- Fix mode: Agent or CLI (based on `command` field in implementation_approach)
|
||||
|
||||
See `/workflow:tools:test-task-generate` for complete task JSON schemas.
|
||||
|
||||
@@ -481,11 +496,10 @@ Created in `.workflow/active/WFS-test-[session]/`:
|
||||
**IMPL-002.json Structure**:
|
||||
- `meta.type: "test-fix"`
|
||||
- `meta.agent: "@test-fix-agent"`
|
||||
- `meta.use_codex`: true/false (based on --use-codex flag)
|
||||
- `context.depends_on: ["IMPL-001"]`
|
||||
- `flow_control.implementation_approach.test_fix_cycle`: Complete cycle specification
|
||||
- Gemini diagnosis template
|
||||
- Fix application mode (manual/codex)
|
||||
- Fix application mode (agent or CLI based on `command` field)
|
||||
- Max iterations: 5
|
||||
- `flow_control.implementation_approach.modification_points`: 3-phase flow
|
||||
|
||||
@@ -503,13 +517,11 @@ See `/workflow:tools:test-task-generate` for complete JSON schemas.
|
||||
**Prerequisite Commands**:
|
||||
- `/workflow:plan` or `/workflow:execute` - Complete implementation session that needs test validation
|
||||
|
||||
**Dispatched by This Command** (5 phases):
|
||||
**Dispatched by This Command** (4 phases):
|
||||
- `/workflow:session:start` - Phase 1: Create independent test workflow session
|
||||
- `/workflow:tools:test-context-gather` - Phase 2: Analyze test coverage and gather source session context
|
||||
- `/workflow:tools:test-concept-enhanced` - Phase 3: Generate test requirements and strategy using Gemini
|
||||
- `/workflow:tools:test-task-generate` - Phase 4: Generate test task JSONs using action-planning-agent (autonomous, default)
|
||||
- `/workflow:tools:test-task-generate --use-codex` - Phase 4: With automated Codex fixes for IMPL-002 (when `--use-codex` flag used)
|
||||
- `/workflow:tools:test-task-generate --cli-execute` - Phase 4: With CLI execution mode for IMPL-001 test generation (when `--cli-execute` flag used)
|
||||
- `/workflow:tools:test-task-generate` - Phase 4: Generate test task JSONs (CLI tool usage determined semantically)
|
||||
|
||||
**Follow-up Commands**:
|
||||
- `/workflow:status` - Review generated test tasks
|
||||
|
||||
@@ -114,35 +114,44 @@ Task(subagent_type="cli-execution-agent", prompt=`
|
||||
- Risk: {conflict_risk}
|
||||
- Files: {existing_files_list}
|
||||
|
||||
## Exploration Context (from context-package.exploration_results)
|
||||
- Exploration Count: ${contextPackage.exploration_results?.exploration_count || 0}
|
||||
- Angles Analyzed: ${JSON.stringify(contextPackage.exploration_results?.angles || [])}
|
||||
- Pre-identified Conflict Indicators: ${JSON.stringify(contextPackage.exploration_results?.aggregated_insights?.conflict_indicators || [])}
|
||||
- Critical Files: ${JSON.stringify(contextPackage.exploration_results?.aggregated_insights?.critical_files?.map(f => f.path) || [])}
|
||||
- All Patterns: ${JSON.stringify(contextPackage.exploration_results?.aggregated_insights?.all_patterns || [])}
|
||||
- All Integration Points: ${JSON.stringify(contextPackage.exploration_results?.aggregated_insights?.all_integration_points || [])}
|
||||
|
||||
## Analysis Steps
|
||||
|
||||
### 1. Load Context
|
||||
- Read existing files from conflict_detection.existing_files
|
||||
- Load plan from .workflow/active/{session_id}/.process/context-package.json
|
||||
- **NEW**: Load exploration_results and use aggregated_insights for enhanced analysis
|
||||
- Extract role analyses and requirements
|
||||
|
||||
### 2. Execute CLI Analysis (Enhanced with Scenario Uniqueness Detection)
|
||||
### 2. Execute CLI Analysis (Enhanced with Exploration + Scenario Uniqueness)
|
||||
|
||||
Primary (Gemini):
|
||||
cd {project_root} && gemini -p "
|
||||
PURPOSE: Detect conflicts between plan and codebase, including module scenario overlaps
|
||||
PURPOSE: Detect conflicts between plan and codebase, using exploration insights
|
||||
TASK:
|
||||
• Compare architectures
|
||||
• **Review pre-identified conflict_indicators from exploration results**
|
||||
• Compare architectures (use exploration key_patterns)
|
||||
• Identify breaking API changes
|
||||
• Detect data model incompatibilities
|
||||
• Assess dependency conflicts
|
||||
• **NEW: Analyze module scenario uniqueness**
|
||||
- Extract new module functionality from plan
|
||||
- Search all existing modules with similar functionality
|
||||
- Compare scenario coverage and identify overlaps
|
||||
• **Analyze module scenario uniqueness**
|
||||
- Use exploration integration_points for precise locations
|
||||
- Cross-validate with exploration critical_files
|
||||
- Generate clarification questions for boundary definition
|
||||
MODE: analysis
|
||||
CONTEXT: @**/*.ts @**/*.js @**/*.tsx @**/*.jsx @.workflow/active/{session_id}/**/*
|
||||
EXPECTED: Conflict list with severity ratings, including ModuleOverlap conflicts with:
|
||||
- Existing module list with scenarios
|
||||
- Overlap analysis matrix
|
||||
EXPECTED: Conflict list with severity ratings, including:
|
||||
- Validation of exploration conflict_indicators
|
||||
- ModuleOverlap conflicts with overlap_analysis
|
||||
- Targeted clarification questions
|
||||
RULES: $(cat ~/.claude/workflows/cli-templates/prompts/analysis/02-analyze-code-patterns.txt) | Focus on breaking changes, migration needs, and functional overlaps | analysis=READ-ONLY
|
||||
RULES: $(cat ~/.claude/workflows/cli-templates/prompts/analysis/02-analyze-code-patterns.txt) | Focus on breaking changes, migration needs, and functional overlaps | Prioritize exploration-identified conflicts | analysis=READ-ONLY
|
||||
"
|
||||
|
||||
Fallback: Qwen (same prompt) → Claude (manual analysis)
|
||||
|
||||
@@ -36,24 +36,23 @@ Step 1: Context-Package Detection
|
||||
├─ Valid package exists → Return existing (skip execution)
|
||||
└─ No valid package → Continue to Step 2
|
||||
|
||||
Step 2: Invoke Context-Search Agent
|
||||
├─ Phase 1: Initialization & Pre-Analysis
|
||||
│ ├─ Load project.json as primary context
|
||||
│ ├─ Initialize code-index
|
||||
│ └─ Classify complexity
|
||||
├─ Phase 2: Multi-Source Discovery
|
||||
│ ├─ Track 1: Historical archive analysis
|
||||
│ ├─ Track 2: Reference documentation
|
||||
│ ├─ Track 3: Web examples (Exa MCP)
|
||||
│ └─ Track 4: Codebase analysis (5-layer)
|
||||
└─ Phase 3: Synthesis & Packaging
|
||||
├─ Apply relevance scoring
|
||||
├─ Integrate brainstorm artifacts
|
||||
├─ Perform conflict detection
|
||||
└─ Generate context-package.json
|
||||
Step 2: Complexity Assessment & Parallel Explore (NEW)
|
||||
├─ Analyze task_description → classify Low/Medium/High
|
||||
├─ Select exploration angles (1-4 based on complexity)
|
||||
├─ Launch N cli-explore-agents in parallel
|
||||
│ └─ Each outputs: exploration-{angle}.json
|
||||
└─ Generate explorations-manifest.json
|
||||
|
||||
Step 3: Output Verification
|
||||
└─ Verify context-package.json created
|
||||
Step 3: Invoke Context-Search Agent (with exploration input)
|
||||
├─ Phase 1: Initialization & Pre-Analysis
|
||||
├─ Phase 2: Multi-Source Discovery
|
||||
│ ├─ Track 0: Exploration Synthesis (prioritize & deduplicate)
|
||||
│ ├─ Track 1-4: Existing tracks
|
||||
└─ Phase 3: Synthesis & Packaging
|
||||
└─ Generate context-package.json with exploration_results
|
||||
|
||||
Step 4: Output Verification
|
||||
└─ Verify context-package.json contains exploration_results
|
||||
```
|
||||
|
||||
## Execution Flow
|
||||
@@ -80,10 +79,139 @@ if (file_exists(contextPackagePath)) {
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Invoke Context-Search Agent
|
||||
### Step 2: Complexity Assessment & Parallel Explore
|
||||
|
||||
**Only execute if Step 1 finds no valid package**
|
||||
|
||||
```javascript
|
||||
// 2.1 Complexity Assessment
|
||||
function analyzeTaskComplexity(taskDescription) {
|
||||
const text = taskDescription.toLowerCase();
|
||||
if (/architect|refactor|restructure|modular|cross-module/.test(text)) return 'High';
|
||||
if (/multiple|several|integrate|migrate|extend/.test(text)) return 'Medium';
|
||||
return 'Low';
|
||||
}
|
||||
|
||||
const ANGLE_PRESETS = {
|
||||
architecture: ['architecture', 'dependencies', 'modularity', 'integration-points'],
|
||||
security: ['security', 'auth-patterns', 'dataflow', 'validation'],
|
||||
performance: ['performance', 'bottlenecks', 'caching', 'data-access'],
|
||||
bugfix: ['error-handling', 'dataflow', 'state-management', 'edge-cases'],
|
||||
feature: ['patterns', 'integration-points', 'testing', 'dependencies'],
|
||||
refactor: ['architecture', 'patterns', 'dependencies', 'testing']
|
||||
};
|
||||
|
||||
function selectAngles(taskDescription, complexity) {
|
||||
const text = taskDescription.toLowerCase();
|
||||
let preset = 'feature';
|
||||
if (/refactor|architect|restructure/.test(text)) preset = 'architecture';
|
||||
else if (/security|auth|permission/.test(text)) preset = 'security';
|
||||
else if (/performance|slow|optimi/.test(text)) preset = 'performance';
|
||||
else if (/fix|bug|error|issue/.test(text)) preset = 'bugfix';
|
||||
|
||||
const count = complexity === 'High' ? 4 : (complexity === 'Medium' ? 3 : 1);
|
||||
return ANGLE_PRESETS[preset].slice(0, count);
|
||||
}
|
||||
|
||||
const complexity = analyzeTaskComplexity(task_description);
|
||||
const selectedAngles = selectAngles(task_description, complexity);
|
||||
const sessionFolder = `.workflow/active/${session_id}/.process`;
|
||||
|
||||
// 2.2 Launch Parallel Explore Agents
|
||||
const explorationTasks = selectedAngles.map((angle, index) =>
|
||||
Task(
|
||||
subagent_type="cli-explore-agent",
|
||||
description=`Explore: ${angle}`,
|
||||
prompt=`
|
||||
## Task Objective
|
||||
Execute **${angle}** exploration for task planning context. Analyze codebase from this specific angle to discover relevant structure, patterns, and constraints.
|
||||
|
||||
## Assigned Context
|
||||
- **Exploration Angle**: ${angle}
|
||||
- **Task Description**: ${task_description}
|
||||
- **Session ID**: ${session_id}
|
||||
- **Exploration Index**: ${index + 1} of ${selectedAngles.length}
|
||||
- **Output File**: ${sessionFolder}/exploration-${angle}.json
|
||||
|
||||
## MANDATORY FIRST STEPS (Execute by Agent)
|
||||
**You (cli-explore-agent) MUST execute these steps in order:**
|
||||
1. Run: ~/.claude/scripts/get_modules_by_depth.sh (project structure)
|
||||
2. Run: rg -l "{keyword_from_task}" --type ts (locate relevant files)
|
||||
3. Execute: cat ~/.claude/workflows/cli-templates/schemas/explore-json-schema.json (get output schema reference)
|
||||
|
||||
## Exploration Strategy (${angle} focus)
|
||||
|
||||
**Step 1: Structural Scan** (Bash)
|
||||
- get_modules_by_depth.sh → identify modules related to ${angle}
|
||||
- find/rg → locate files relevant to ${angle} aspect
|
||||
- Analyze imports/dependencies from ${angle} perspective
|
||||
|
||||
**Step 2: Semantic Analysis** (Gemini CLI)
|
||||
- How does existing code handle ${angle} concerns?
|
||||
- What patterns are used for ${angle}?
|
||||
- Where would new code integrate from ${angle} viewpoint?
|
||||
|
||||
**Step 3: Write Output**
|
||||
- Consolidate ${angle} findings into JSON
|
||||
- Identify ${angle}-specific clarification needs
|
||||
|
||||
## Expected Output
|
||||
|
||||
**File**: ${sessionFolder}/exploration-${angle}.json
|
||||
|
||||
**Schema Reference**: Schema obtained in MANDATORY FIRST STEPS step 3, follow schema exactly
|
||||
|
||||
**Required Fields** (all ${angle} focused):
|
||||
- project_structure: Modules/architecture relevant to ${angle}
|
||||
- relevant_files: Files affected from ${angle} perspective
|
||||
**IMPORTANT**: Use object format with relevance scores for synthesis:
|
||||
\`[{path: "src/file.ts", relevance: 0.85, rationale: "Core ${angle} logic"}]\`
|
||||
Scores: 0.7+ high priority, 0.5-0.7 medium, <0.5 low
|
||||
- patterns: ${angle}-related patterns to follow
|
||||
- dependencies: Dependencies relevant to ${angle}
|
||||
- integration_points: Where to integrate from ${angle} viewpoint (include file:line locations)
|
||||
- constraints: ${angle}-specific limitations/conventions
|
||||
- clarification_needs: ${angle}-related ambiguities (with options array)
|
||||
- _metadata.exploration_angle: "${angle}"
|
||||
|
||||
## Success Criteria
|
||||
- [ ] Schema obtained via cat explore-json-schema.json
|
||||
- [ ] get_modules_by_depth.sh executed
|
||||
- [ ] At least 3 relevant files identified with ${angle} rationale
|
||||
- [ ] Patterns are actionable (code examples, not generic advice)
|
||||
- [ ] Integration points include file:line locations
|
||||
- [ ] Constraints are project-specific to ${angle}
|
||||
- [ ] JSON output follows schema exactly
|
||||
- [ ] clarification_needs includes options array
|
||||
|
||||
## Output
|
||||
Write: ${sessionFolder}/exploration-${angle}.json
|
||||
Return: 2-3 sentence summary of ${angle} findings
|
||||
`
|
||||
)
|
||||
);
|
||||
|
||||
// 2.3 Generate Manifest after all complete
|
||||
const explorationFiles = bash(`find ${sessionFolder} -name "exploration-*.json" -type f`).split('\n').filter(f => f.trim());
|
||||
const explorationManifest = {
|
||||
session_id,
|
||||
task_description,
|
||||
timestamp: new Date().toISOString(),
|
||||
complexity,
|
||||
exploration_count: selectedAngles.length,
|
||||
angles_explored: selectedAngles,
|
||||
explorations: explorationFiles.map(file => {
|
||||
const data = JSON.parse(Read(file));
|
||||
return { angle: data._metadata.exploration_angle, file: file.split('/').pop(), path: file, index: data._metadata.exploration_index };
|
||||
})
|
||||
};
|
||||
Write(`${sessionFolder}/explorations-manifest.json`, JSON.stringify(explorationManifest, null, 2));
|
||||
```
|
||||
|
||||
### Step 3: Invoke Context-Search Agent
|
||||
|
||||
**Only execute after Step 2 completes**
|
||||
|
||||
```javascript
|
||||
Task(
|
||||
subagent_type="context-search-agent",
|
||||
@@ -97,6 +225,12 @@ Task(
|
||||
- **Task Description**: ${task_description}
|
||||
- **Output Path**: .workflow/${session_id}/.process/context-package.json
|
||||
|
||||
## Exploration Input (from Step 2)
|
||||
- **Manifest**: ${sessionFolder}/explorations-manifest.json
|
||||
- **Exploration Count**: ${explorationManifest.exploration_count}
|
||||
- **Angles**: ${explorationManifest.angles_explored.join(', ')}
|
||||
- **Complexity**: ${complexity}
|
||||
|
||||
## Mission
|
||||
Execute complete context-search-agent workflow for implementation planning:
|
||||
|
||||
@@ -107,7 +241,8 @@ Execute complete context-search-agent workflow for implementation planning:
|
||||
4. **Analysis**: Extract keywords, determine scope, classify complexity based on task description and project state
|
||||
|
||||
### Phase 2: Multi-Source Context Discovery
|
||||
Execute all 4 discovery tracks:
|
||||
Execute all discovery tracks:
|
||||
- **Track 0**: Exploration Synthesis (load ${sessionFolder}/explorations-manifest.json, prioritize critical_files, deduplicate patterns/integration_points)
|
||||
- **Track 1**: Historical archive analysis (query manifest.json for lessons learned)
|
||||
- **Track 2**: Reference documentation (CLAUDE.md, architecture docs)
|
||||
- **Track 3**: Web examples (use Exa MCP for unfamiliar tech/APIs)
|
||||
@@ -130,6 +265,7 @@ Complete context-package.json with:
|
||||
- **dependencies**: {internal[], external[]} with dependency graph
|
||||
- **brainstorm_artifacts**: {guidance_specification, role_analyses[], synthesis_output} with content
|
||||
- **conflict_detection**: {risk_level, risk_factors, affected_modules[], mitigation_strategy, historical_conflicts[]}
|
||||
- **exploration_results**: {manifest_path, exploration_count, angles, explorations[], aggregated_insights} (from Track 0)
|
||||
|
||||
## Quality Validation
|
||||
Before completion verify:
|
||||
@@ -146,7 +282,7 @@ Report completion with statistics.
|
||||
)
|
||||
```
|
||||
|
||||
### Step 3: Output Verification
|
||||
### Step 4: Output Verification
|
||||
|
||||
After agent completes, verify output:
|
||||
|
||||
@@ -156,6 +292,12 @@ const outputPath = `.workflow/${session_id}/.process/context-package.json`;
|
||||
if (!file_exists(outputPath)) {
|
||||
throw new Error("❌ Agent failed to generate context-package.json");
|
||||
}
|
||||
|
||||
// Verify exploration_results included
|
||||
const pkg = JSON.parse(Read(outputPath));
|
||||
if (pkg.exploration_results?.exploration_count > 0) {
|
||||
console.log(`✅ Exploration results aggregated: ${pkg.exploration_results.exploration_count} angles`);
|
||||
}
|
||||
```
|
||||
|
||||
## Parameter Reference
|
||||
@@ -176,6 +318,7 @@ Refer to `context-search-agent.md` Phase 3.7 for complete `context-package.json`
|
||||
- **dependencies**: Internal and external dependency graphs
|
||||
- **brainstorm_artifacts**: Brainstorm documents with full content (if exists)
|
||||
- **conflict_detection**: Risk assessment with mitigation strategies and historical conflicts
|
||||
- **exploration_results**: Aggregated exploration insights (from parallel explore phase)
|
||||
|
||||
## Historical Archive Analysis
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
---
|
||||
name: task-generate-agent
|
||||
description: Generate implementation plan documents (IMPL_PLAN.md, task JSONs, TODO_LIST.md) using action-planning-agent - produces planning artifacts, does NOT execute code implementation
|
||||
argument-hint: "--session WFS-session-id [--cli-execute]"
|
||||
argument-hint: "--session WFS-session-id"
|
||||
examples:
|
||||
- /workflow:tools:task-generate-agent --session WFS-auth
|
||||
- /workflow:tools:task-generate-agent --session WFS-auth --cli-execute
|
||||
---
|
||||
|
||||
# Generate Implementation Plan Command
|
||||
@@ -26,7 +25,7 @@ Generate implementation planning documents (IMPL_PLAN.md, task JSONs, TODO_LIST.
|
||||
|
||||
```
|
||||
Input Parsing:
|
||||
├─ Parse flags: --session, --cli-execute
|
||||
├─ Parse flags: --session
|
||||
└─ Validation: session_id REQUIRED
|
||||
|
||||
Phase 1: Context Preparation (Command)
|
||||
@@ -65,9 +64,10 @@ Phase 2: Planning Document Generation (Agent)
|
||||
|
||||
2. **Provide Metadata** (simple values):
|
||||
- `session_id`
|
||||
- `execution_mode` (agent-mode | cli-execute-mode)
|
||||
- `mcp_capabilities` (available MCP tools)
|
||||
|
||||
**Note**: CLI tool usage is now determined semantically by action-planning-agent based on user's task description, not by flags.
|
||||
|
||||
### Phase 2: Planning Document Generation (Agent Responsibility)
|
||||
|
||||
**Purpose**: Generate IMPL_PLAN.md, task JSONs, and TODO_LIST.md - planning documents only, NOT code implementation.
|
||||
@@ -97,15 +97,28 @@ Output:
|
||||
|
||||
## CONTEXT METADATA
|
||||
Session ID: {session-id}
|
||||
Planning Mode: {agent-mode | cli-execute-mode}
|
||||
MCP Capabilities: {exa_code, exa_web, code_index}
|
||||
|
||||
## CLI TOOL SELECTION
|
||||
Determine CLI tool usage per-step based on user's task description:
|
||||
- If user specifies "use Codex/Gemini/Qwen for X" → Add command field to relevant steps
|
||||
- Default: Agent execution (no command field) unless user explicitly requests CLI
|
||||
|
||||
## EXPLORATION CONTEXT (from context-package.exploration_results)
|
||||
- Load exploration_results from context-package.json
|
||||
- Use aggregated_insights.critical_files for focus_paths generation
|
||||
- Apply aggregated_insights.constraints to acceptance criteria
|
||||
- Reference aggregated_insights.all_patterns for implementation approach
|
||||
- Use aggregated_insights.all_integration_points for precise modification locations
|
||||
- Use conflict_indicators for risk-aware task sequencing
|
||||
|
||||
## EXPECTED DELIVERABLES
|
||||
1. Task JSON Files (.task/IMPL-*.json)
|
||||
- 6-field schema (id, title, status, context_package_path, meta, context, flow_control)
|
||||
- Quantified requirements with explicit counts
|
||||
- Artifacts integration from context package
|
||||
- Flow control with pre_analysis steps
|
||||
- **focus_paths enhanced with exploration critical_files**
|
||||
- Flow control with pre_analysis steps (include exploration integration_points analysis)
|
||||
|
||||
2. Implementation Plan (IMPL_PLAN.md)
|
||||
- Context analysis and artifact references
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
---
|
||||
name: task-generate-tdd
|
||||
description: Autonomous TDD task generation using action-planning-agent with Red-Green-Refactor cycles, test-first structure, and cycle validation
|
||||
argument-hint: "--session WFS-session-id [--cli-execute]"
|
||||
argument-hint: "--session WFS-session-id"
|
||||
examples:
|
||||
- /workflow:tools:task-generate-tdd --session WFS-auth
|
||||
- /workflow:tools:task-generate-tdd --session WFS-auth --cli-execute
|
||||
---
|
||||
|
||||
# Autonomous TDD Task Generation Command
|
||||
|
||||
## Overview
|
||||
Autonomous TDD task JSON and IMPL_PLAN.md generation using action-planning-agent with two-phase execution: discovery and document generation. Supports both agent-driven execution (default) and CLI tool execution modes. Generates complete Red-Green-Refactor cycles contained within each task.
|
||||
Autonomous TDD task JSON and IMPL_PLAN.md generation using action-planning-agent with two-phase execution: discovery and document generation. Generates complete Red-Green-Refactor cycles contained within each task.
|
||||
|
||||
## Core Philosophy
|
||||
- **Agent-Driven**: Delegate execution to action-planning-agent for autonomous operation
|
||||
- **Two-Phase Flow**: Discovery (context gathering) → Output (document generation)
|
||||
- **Memory-First**: Reuse loaded documents from conversation memory
|
||||
- **MCP-Enhanced**: Use MCP tools for advanced code analysis and research
|
||||
- **Pre-Selected Templates**: Command selects correct TDD template based on `--cli-execute` flag **before** invoking agent
|
||||
- **Agent Simplicity**: Agent receives pre-selected template and focuses only on content generation
|
||||
- **Semantic CLI Selection**: CLI tool usage determined from user's task description, not flags
|
||||
- **Agent Simplicity**: Agent generates content with semantic CLI detection
|
||||
- **Path Clarity**: All `focus_paths` prefer absolute paths (e.g., `D:\\project\\src\\module`), or clear relative paths from project root (e.g., `./src/module`)
|
||||
- **TDD-First**: Every feature starts with a failing test (Red phase)
|
||||
- **Feature-Complete Tasks**: Each task contains complete Red-Green-Refactor cycle
|
||||
@@ -57,7 +56,7 @@ Autonomous TDD task JSON and IMPL_PLAN.md generation using action-planning-agent
|
||||
|
||||
```
|
||||
Input Parsing:
|
||||
├─ Parse flags: --session, --cli-execute
|
||||
├─ Parse flags: --session
|
||||
└─ Validation: session_id REQUIRED
|
||||
|
||||
Phase 1: Discovery & Context Loading (Memory-First)
|
||||
@@ -69,7 +68,7 @@ Phase 1: Discovery & Context Loading (Memory-First)
|
||||
└─ Optional: MCP external research
|
||||
|
||||
Phase 2: Agent Execution (Document Generation)
|
||||
├─ Pre-agent template selection (agent-mode OR cli-execute-mode)
|
||||
├─ Pre-agent template selection (semantic CLI detection)
|
||||
├─ Invoke action-planning-agent
|
||||
├─ Generate TDD Task JSON Files (.task/IMPL-*.json)
|
||||
│ └─ Each task: complete Red-Green-Refactor cycle internally
|
||||
@@ -86,11 +85,8 @@ Phase 2: Agent Execution (Document Generation)
|
||||
```javascript
|
||||
{
|
||||
"session_id": "WFS-[session-id]",
|
||||
"execution_mode": "agent-mode" | "cli-execute-mode", // Determined by flag
|
||||
"task_json_template_path": "~/.claude/workflows/cli-templates/prompts/workflow/task-json-agent-mode.txt"
|
||||
| "~/.claude/workflows/cli-templates/prompts/workflow/task-json-cli-mode.txt",
|
||||
// Path selected by command based on --cli-execute flag, agent reads it
|
||||
"workflow_type": "tdd",
|
||||
// Note: CLI tool usage is determined semantically by action-planning-agent based on user's task description
|
||||
"session_metadata": {
|
||||
// If in memory: use cached content
|
||||
// Else: Load from .workflow/active//{session-id}/workflow-session.json
|
||||
@@ -199,8 +195,7 @@ Task(
|
||||
|
||||
**Session ID**: WFS-{session-id}
|
||||
**Workflow Type**: TDD
|
||||
**Execution Mode**: {agent-mode | cli-execute-mode}
|
||||
**Task JSON Template Path**: {template_path}
|
||||
**Note**: CLI tool usage is determined semantically from user's task description
|
||||
|
||||
## Phase 1: Discovery Results (Provided Context)
|
||||
|
||||
@@ -265,16 +260,15 @@ Refer to: @.claude/agents/action-planning-agent.md for:
|
||||
|
||||
##### 1. TDD Task JSON Files (.task/IMPL-*.json)
|
||||
- **Location**: `.workflow/active//{session-id}/.task/`
|
||||
- **Template**: Read from `{template_path}` (pre-selected by command based on `--cli-execute` flag)
|
||||
- **Schema**: 5-field structure with TDD-specific metadata
|
||||
- `meta.tdd_workflow`: true (REQUIRED)
|
||||
- `meta.max_iterations`: 3 (Green phase test-fix cycle limit)
|
||||
- `meta.use_codex`: false (manual fixes by default)
|
||||
- `context.tdd_cycles`: Array with quantified test cases and coverage
|
||||
- `flow_control.implementation_approach`: Exactly 3 steps with `tdd_phase` field
|
||||
1. Red Phase (`tdd_phase: "red"`): Write failing tests
|
||||
2. Green Phase (`tdd_phase: "green"`): Implement to pass tests
|
||||
3. Refactor Phase (`tdd_phase: "refactor"`): Improve code quality
|
||||
- CLI tool usage determined semantically (add `command` field when user requests CLI execution)
|
||||
- **Details**: See action-planning-agent.md § TDD Task JSON Generation
|
||||
|
||||
##### 2. IMPL_PLAN.md (TDD Variant)
|
||||
@@ -475,16 +469,14 @@ This section provides quick reference for TDD task JSON structure. For complete
|
||||
|
||||
**Basic Usage**:
|
||||
```bash
|
||||
# Agent mode (default, autonomous execution)
|
||||
# Standard execution
|
||||
/workflow:tools:task-generate-tdd --session WFS-auth
|
||||
|
||||
# CLI tool mode (use Gemini/Qwen for generation)
|
||||
/workflow:tools:task-generate-tdd --session WFS-auth --cli-execute
|
||||
# With semantic CLI request (include in task description)
|
||||
# e.g., "Generate TDD tasks for auth module, use Codex for implementation"
|
||||
```
|
||||
|
||||
**Execution Modes**:
|
||||
- **Agent mode** (default): Uses `action-planning-agent` with agent-mode task template
|
||||
- **CLI mode** (`--cli-execute`): Uses Gemini/Qwen with cli-mode task template
|
||||
**CLI Tool Selection**: Determined semantically from user's task description. Include "use Codex/Gemini/Qwen" in your request for CLI execution.
|
||||
|
||||
**Output**:
|
||||
- TDD task JSON files in `.task/` directory (IMPL-N.json format)
|
||||
@@ -513,7 +505,7 @@ IMPL (Green phase) tasks include automatic test-fix cycle:
|
||||
3. **Success Path**: Tests pass → Complete task
|
||||
4. **Failure Path**: Tests fail → Enter iterative fix cycle:
|
||||
- **Gemini Diagnosis**: Analyze failures with bug-fix template
|
||||
- **Fix Application**: Manual (default) or Codex (if meta.use_codex=true)
|
||||
- **Fix Application**: Agent (default) or CLI (if `command` field present)
|
||||
- **Retest**: Verify fix resolves failures
|
||||
- **Repeat**: Up to max_iterations (default: 3)
|
||||
5. **Safety Net**: Auto-revert all changes if max iterations reached
|
||||
@@ -522,5 +514,5 @@ IMPL (Green phase) tasks include automatic test-fix cycle:
|
||||
|
||||
## Configuration Options
|
||||
- **meta.max_iterations**: Number of fix attempts (default: 3 for TDD, 5 for test-gen)
|
||||
- **meta.use_codex**: Enable Codex automated fixes (default: false, manual)
|
||||
- **CLI tool usage**: Determined semantically from user's task description via `command` field in implementation_approach
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
---
|
||||
name: test-task-generate
|
||||
description: Generate test planning documents (IMPL_PLAN.md, test task JSONs, TODO_LIST.md) using action-planning-agent - produces test planning artifacts, does NOT execute tests
|
||||
argument-hint: "[--use-codex] [--cli-execute] --session WFS-test-session-id"
|
||||
argument-hint: "--session WFS-test-session-id"
|
||||
examples:
|
||||
- /workflow:tools:test-task-generate --session WFS-test-auth
|
||||
- /workflow:tools:test-task-generate --use-codex --session WFS-test-auth
|
||||
- /workflow:tools:test-task-generate --cli-execute --session WFS-test-auth
|
||||
---
|
||||
|
||||
# Generate Test Planning Documents Command
|
||||
@@ -26,17 +24,17 @@ Generate test planning documents (IMPL_PLAN.md, test task JSONs, TODO_LIST.md) u
|
||||
|
||||
### Test Generation (IMPL-001)
|
||||
- **Agent Mode** (default): @code-developer generates tests within agent context
|
||||
- **CLI Execute Mode** (`--cli-execute`): Use Codex CLI for autonomous test generation
|
||||
- **CLI Mode**: Use CLI tools when `command` field present in implementation_approach (determined semantically)
|
||||
|
||||
### Test Execution & Fix (IMPL-002+)
|
||||
- **Manual Mode** (default): Gemini diagnosis → user applies fixes
|
||||
- **Codex Mode** (`--use-codex`): Gemini diagnosis → Codex applies fixes with resume mechanism
|
||||
- **Agent Mode** (default): Gemini diagnosis → agent applies fixes
|
||||
- **CLI Mode**: Gemini diagnosis → CLI applies fixes (when `command` field present in implementation_approach)
|
||||
|
||||
## Execution Process
|
||||
|
||||
```
|
||||
Input Parsing:
|
||||
├─ Parse flags: --session, --use-codex, --cli-execute
|
||||
├─ Parse flags: --session
|
||||
└─ Validation: session_id REQUIRED
|
||||
|
||||
Phase 1: Context Preparation (Command)
|
||||
@@ -44,7 +42,7 @@ Phase 1: Context Preparation (Command)
|
||||
│ ├─ session_metadata_path
|
||||
│ ├─ test_analysis_results_path (REQUIRED)
|
||||
│ └─ test_context_package_path
|
||||
└─ Provide metadata (session_id, execution_mode, use_codex, source_session_id)
|
||||
└─ Provide metadata (session_id, source_session_id)
|
||||
|
||||
Phase 2: Test Document Generation (Agent)
|
||||
├─ Load TEST_ANALYSIS_RESULTS.md as primary requirements source
|
||||
@@ -83,11 +81,11 @@ Phase 2: Test Document Generation (Agent)
|
||||
|
||||
2. **Provide Metadata** (simple values):
|
||||
- `session_id`
|
||||
- `execution_mode` (agent-mode | cli-execute-mode)
|
||||
- `use_codex` flag (true | false)
|
||||
- `source_session_id` (if exists)
|
||||
- `mcp_capabilities` (available MCP tools)
|
||||
|
||||
**Note**: CLI tool usage is now determined semantically from user's task description, not by flags.
|
||||
|
||||
### Phase 2: Test Document Generation (Agent Responsibility)
|
||||
|
||||
**Purpose**: Generate test-specific IMPL_PLAN.md, task JSONs, and TODO_LIST.md - planning documents only, NOT test execution.
|
||||
@@ -134,11 +132,14 @@ Output:
|
||||
## CONTEXT METADATA
|
||||
Session ID: {test-session-id}
|
||||
Workflow Type: test_session
|
||||
Planning Mode: {agent-mode | cli-execute-mode}
|
||||
Use Codex: {true | false}
|
||||
Source Session: {source-session-id} (if exists)
|
||||
MCP Capabilities: {exa_code, exa_web, code_index}
|
||||
|
||||
## CLI TOOL SELECTION
|
||||
Determine CLI tool usage per-step based on user's task description:
|
||||
- If user specifies "use Codex/Gemini/Qwen for X" → Add command field to relevant steps
|
||||
- Default: Agent execution (no command field) unless user explicitly requests CLI
|
||||
|
||||
## TEST-SPECIFIC REQUIREMENTS SUMMARY
|
||||
(Detailed specifications in your agent definition)
|
||||
|
||||
@@ -149,25 +150,26 @@ MCP Capabilities: {exa_code, exa_web, code_index}
|
||||
Task Configuration:
|
||||
IMPL-001 (Test Generation):
|
||||
- meta.type: "test-gen"
|
||||
- meta.agent: "@code-developer" (agent-mode) OR CLI execution (cli-execute-mode)
|
||||
- meta.agent: "@code-developer"
|
||||
- meta.test_framework: Specify existing framework (e.g., "jest", "vitest", "pytest")
|
||||
- flow_control: Test generation strategy from TEST_ANALYSIS_RESULTS.md
|
||||
- CLI execution: Add `command` field when user requests (determined semantically)
|
||||
|
||||
IMPL-002+ (Test Execution & Fix):
|
||||
- meta.type: "test-fix"
|
||||
- meta.agent: "@test-fix-agent"
|
||||
- meta.use_codex: true/false (based on flag)
|
||||
- flow_control: Test-fix cycle with iteration limits and diagnosis configuration
|
||||
- CLI execution: Add `command` field when user requests (determined semantically)
|
||||
|
||||
### Test-Fix Cycle Specification (IMPL-002+)
|
||||
Required flow_control fields:
|
||||
- max_iterations: 5
|
||||
- diagnosis_tool: "gemini"
|
||||
- diagnosis_template: "~/.claude/workflows/cli-templates/prompts/analysis/01-diagnose-bug-root-cause.txt"
|
||||
- fix_mode: "manual" OR "codex" (based on use_codex flag)
|
||||
- cycle_pattern: "test → gemini_diagnose → fix → retest"
|
||||
- exit_conditions: ["all_tests_pass", "max_iterations_reached"]
|
||||
- auto_revert_on_failure: true
|
||||
- CLI fix: Add `command` field when user specifies CLI tool usage
|
||||
|
||||
### Automation Framework Configuration
|
||||
Select automation tools based on test requirements from TEST_ANALYSIS_RESULTS.md:
|
||||
@@ -191,8 +193,9 @@ PRIMARY requirements source - extract and map to task JSONs:
|
||||
## EXPECTED DELIVERABLES
|
||||
1. Test Task JSON Files (.task/IMPL-*.json)
|
||||
- 6-field schema with quantified requirements from TEST_ANALYSIS_RESULTS.md
|
||||
- Test-specific metadata: type, agent, use_codex, test_framework, coverage_target
|
||||
- Test-specific metadata: type, agent, test_framework, coverage_target
|
||||
- flow_control includes: reusable_test_tools, test_commands (from project config)
|
||||
- CLI execution via `command` field when user requests (determined semantically)
|
||||
- Artifact references from test-context-package.json
|
||||
- Absolute paths in context.files_to_test
|
||||
|
||||
@@ -213,9 +216,9 @@ Hard Constraints:
|
||||
- All requirements quantified from TEST_ANALYSIS_RESULTS.md
|
||||
- Test framework matches existing project framework
|
||||
- flow_control includes reusable_test_tools and test_commands from project
|
||||
- use_codex flag correctly set in IMPL-002+ tasks
|
||||
- Absolute paths for all focus_paths
|
||||
- Acceptance criteria include verification commands
|
||||
- CLI `command` field added only when user explicitly requests CLI tool usage
|
||||
|
||||
## SUCCESS CRITERIA
|
||||
- All test planning documents generated successfully
|
||||
@@ -233,21 +236,18 @@ Hard Constraints:
|
||||
|
||||
### Usage Examples
|
||||
```bash
|
||||
# Agent mode (default)
|
||||
# Standard execution
|
||||
/workflow:tools:test-task-generate --session WFS-test-auth
|
||||
|
||||
# With automated Codex fixes
|
||||
/workflow:tools:test-task-generate --use-codex --session WFS-test-auth
|
||||
|
||||
# CLI execution mode for test generation
|
||||
/workflow:tools:test-task-generate --cli-execute --session WFS-test-auth
|
||||
# With semantic CLI request (include in task description)
|
||||
# e.g., "Generate tests, use Codex for implementation and fixes"
|
||||
```
|
||||
|
||||
### Flag Behavior
|
||||
- **No flags**: `meta.use_codex=false` (manual fixes), agent-mode test generation
|
||||
- **--use-codex**: `meta.use_codex=true` (Codex automated fixes in IMPL-002+)
|
||||
- **--cli-execute**: CLI tool execution mode for IMPL-001 test generation
|
||||
- **Both flags**: CLI generation + automated Codex fixes
|
||||
### CLI Tool Selection
|
||||
CLI tool usage is determined semantically from user's task description:
|
||||
- Include "use Codex" for automated fixes
|
||||
- Include "use Gemini" for analysis
|
||||
- Default: Agent execution (no `command` field)
|
||||
|
||||
### Output
|
||||
- Test task JSON files in `.task/` directory (minimum 2)
|
||||
|
||||
@@ -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>
|
||||
@@ -70,6 +70,11 @@
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Available options for user to choose from (2-4 options)"
|
||||
},
|
||||
"recommended": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "Zero-based index of recommended option in the options array. Based on codebase patterns and best practices analysis."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"overview",
|
||||
"features",
|
||||
"statistics",
|
||||
"memory_resources",
|
||||
"_metadata"
|
||||
],
|
||||
"properties": {
|
||||
@@ -28,9 +27,7 @@
|
||||
"description",
|
||||
"technology_stack",
|
||||
"architecture",
|
||||
"key_components",
|
||||
"entry_points",
|
||||
"metrics"
|
||||
"key_components"
|
||||
],
|
||||
"properties": {
|
||||
"description": {
|
||||
@@ -125,49 +122,6 @@
|
||||
}
|
||||
},
|
||||
"description": "5-10 core modules/components"
|
||||
},
|
||||
"entry_points": {
|
||||
"type": "object",
|
||||
"required": ["main", "cli_commands", "api_endpoints"],
|
||||
"properties": {
|
||||
"main": {
|
||||
"type": "string",
|
||||
"description": "Primary application entry point (index.ts, main.py, etc.)"
|
||||
},
|
||||
"cli_commands": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Available CLI commands (npm start, make build, etc.)"
|
||||
},
|
||||
"api_endpoints": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "HTTP/REST/GraphQL endpoints (if applicable)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metrics": {
|
||||
"type": "object",
|
||||
"required": ["total_files", "lines_of_code", "module_count", "complexity"],
|
||||
"properties": {
|
||||
"total_files": {
|
||||
"type": "integer",
|
||||
"description": "Total source code files"
|
||||
},
|
||||
"lines_of_code": {
|
||||
"type": "integer",
|
||||
"description": "Estimated total lines of code"
|
||||
},
|
||||
"module_count": {
|
||||
"type": "integer",
|
||||
"description": "Number of top-level modules/packages"
|
||||
},
|
||||
"complexity": {
|
||||
"type": "string",
|
||||
"enum": ["low", "medium", "high"],
|
||||
"description": "Overall project complexity rating"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -199,6 +153,17 @@
|
||||
},
|
||||
"description": "Completed workflow features (populated by /workflow:session:complete)"
|
||||
},
|
||||
"development_index": {
|
||||
"type": "object",
|
||||
"description": "Categorized development history (lite-plan/lite-execute)",
|
||||
"properties": {
|
||||
"feature": { "type": "array", "items": { "$ref": "#/$defs/devIndexEntry" } },
|
||||
"enhancement": { "type": "array", "items": { "$ref": "#/$defs/devIndexEntry" } },
|
||||
"bugfix": { "type": "array", "items": { "$ref": "#/$defs/devIndexEntry" } },
|
||||
"refactor": { "type": "array", "items": { "$ref": "#/$defs/devIndexEntry" } },
|
||||
"docs": { "type": "array", "items": { "$ref": "#/$defs/devIndexEntry" } }
|
||||
}
|
||||
},
|
||||
"statistics": {
|
||||
"type": "object",
|
||||
"required": ["total_features", "total_sessions", "last_updated"],
|
||||
@@ -218,85 +183,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"memory_resources": {
|
||||
"type": "object",
|
||||
"required": ["skills", "documentation", "module_docs", "gaps", "last_scanned"],
|
||||
"properties": {
|
||||
"skills": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["name", "type", "path"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "SKILL package name"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["workflow_progress", "project_docs", "tech_stacks", "codemap", "style"],
|
||||
"description": "SKILL package type"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Relative path to SKILL package directory"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "SKILL packages generated by /memory:* commands"
|
||||
},
|
||||
"documentation": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["name", "path", "has_readme", "has_architecture"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Documentation project name"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Path to documentation directory"
|
||||
},
|
||||
"has_readme": {
|
||||
"type": "boolean",
|
||||
"description": "README.md exists"
|
||||
},
|
||||
"has_architecture": {
|
||||
"type": "boolean",
|
||||
"description": "ARCHITECTURE.md exists"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Documentation generated by /memory:docs"
|
||||
},
|
||||
"module_docs": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Paths to CLAUDE.md files generated by /memory:update-*"
|
||||
},
|
||||
"gaps": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"project_skill",
|
||||
"documentation",
|
||||
"tech_stack",
|
||||
"workflow_history",
|
||||
"module_docs_low_coverage"
|
||||
]
|
||||
},
|
||||
"description": "Missing memory resources"
|
||||
},
|
||||
"last_scanned": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "ISO 8601 timestamp of last memory scan"
|
||||
}
|
||||
}
|
||||
},
|
||||
"_metadata": {
|
||||
"type": "object",
|
||||
"required": ["initialized_by", "analysis_timestamp", "analysis_mode"],
|
||||
@@ -317,5 +203,19 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"devIndexEntry": {
|
||||
"type": "object",
|
||||
"required": ["title", "sub_feature", "date", "description", "status"],
|
||||
"properties": {
|
||||
"title": { "type": "string", "maxLength": 60 },
|
||||
"sub_feature": { "type": "string", "description": "Module/component area" },
|
||||
"date": { "type": "string", "format": "date" },
|
||||
"description": { "type": "string", "maxLength": 100 },
|
||||
"status": { "type": "string", "enum": ["completed", "partial"] },
|
||||
"session_id": { "type": "string", "description": "lite-plan session ID" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,7 +582,7 @@ prompts/
|
||||
- **Complex** (implementation, migration): 20-60min (1200000-3600000ms)
|
||||
- **Heavy** (large codebase, multi-file): 60-120min (3600000-7200000ms)
|
||||
|
||||
**Codex Multiplier**: 1.5x of allocated time
|
||||
**Codex Multiplier**: 3x of allocated time (minimum 15min / 900000ms)
|
||||
|
||||
**Application**: All bash() wrapped commands including Gemini, Qwen and Codex executions
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# MCP Tool Strategy: Exa Usage
|
||||
|
||||
## ⚡ Exa Triggering Mechanisms
|
||||
|
||||
**Auto-Trigger**:
|
||||
- User mentions "exa-code" or code-related queries → `mcp__exa__get_code_context_exa`
|
||||
- Need current web information → `mcp__exa__web_search_exa`
|
||||
|
||||
**Manual Trigger**:
|
||||
- Complex API research → Exa Code Context
|
||||
- Real-time information needs → Exa Web Search
|
||||
71
.claude/workflows/tool-strategy.md
Normal file
71
.claude/workflows/tool-strategy.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Tool Strategy
|
||||
|
||||
## ⚡ Exa Triggering Mechanisms
|
||||
|
||||
**Auto-Trigger**:
|
||||
- User mentions "exa-code" or code-related queries → `mcp__exa__get_code_context_exa`
|
||||
- Need current web information → `mcp__exa__web_search_exa`
|
||||
|
||||
**Manual Trigger**:
|
||||
- Complex API research → Exa Code Context
|
||||
- Real-time information needs → Exa Web Search
|
||||
|
||||
## ⚡ Bash Text Processing (sed/awk)
|
||||
|
||||
**When to Use**: Edit tool fails 2+ times on same file
|
||||
|
||||
### sed Quick Reference
|
||||
|
||||
```bash
|
||||
# Replace first occurrence per line
|
||||
sed 's/old/new/' file.txt
|
||||
|
||||
# Replace all occurrences (global)
|
||||
sed 's/old/new/g' file.txt
|
||||
|
||||
# In-place edit (modify file directly)
|
||||
sed -i 's/old/new/g' file.txt
|
||||
|
||||
# Delete lines matching pattern
|
||||
sed '/pattern/d' file.txt
|
||||
|
||||
# Insert line before match
|
||||
sed '/pattern/i\new line' file.txt
|
||||
|
||||
# Insert line after match
|
||||
sed '/pattern/a\new line' file.txt
|
||||
|
||||
# Replace on specific line number
|
||||
sed '5s/old/new/' file.txt
|
||||
|
||||
# Multi-line replacement (escape newlines)
|
||||
sed ':a;N;$!ba;s/old\npattern/new\ntext/g' file.txt
|
||||
```
|
||||
|
||||
### awk Quick Reference
|
||||
|
||||
```bash
|
||||
# Print specific column
|
||||
awk '{print $1}' file.txt
|
||||
|
||||
# Print lines matching pattern
|
||||
awk '/pattern/' file.txt
|
||||
|
||||
# Replace field value
|
||||
awk '{$2="new"; print}' file.txt
|
||||
|
||||
# Conditional replacement
|
||||
awk '/pattern/{gsub(/old/,"new")}1' file.txt
|
||||
|
||||
# Insert line after match
|
||||
awk '/pattern/{print; print "new line"; next}1' file.txt
|
||||
|
||||
# Multi-field operations
|
||||
awk -F',' '{print $1, $3}' file.csv
|
||||
```
|
||||
|
||||
### Fallback Strategy
|
||||
|
||||
1. **Edit fails 2+ times** → Try sed for simple replacements
|
||||
2. **sed fails** → Try awk for complex patterns
|
||||
3. **awk fails** → Use Write to recreate file
|
||||
@@ -782,15 +782,11 @@ All workflows use the same file structure definition regardless of complexity. *
|
||||
|
||||
**Examples**:
|
||||
|
||||
*Analysis Commands (read-only):*
|
||||
- `/cli:analyze "security"` (no session) → `.scratchpad/analyze-security-20250105-143022.md`
|
||||
- `/cli:chat "build process"` (unrelated to active session) → `.scratchpad/chat-build-process-20250105-143045.md`
|
||||
- `/cli:mode:plan "feature idea"` (exploratory) → `.scratchpad/plan-feature-idea-20250105-143110.md`
|
||||
- `/cli:mode:code-analysis "trace auth flow"` (no session) → `.scratchpad/code-analysis-auth-flow-20250105-143130.md`
|
||||
*Workflow Commands (lightweight):*
|
||||
- `/workflow:lite-plan "feature idea"` (exploratory) → `.scratchpad/lite-plan-feature-idea-20250105-143110.md`
|
||||
- `/workflow:lite-fix "bug description"` (bug fixing) → `.scratchpad/lite-fix-bug-20250105-143130.md`
|
||||
|
||||
*Implementation Commands (⚠️ modifies code):*
|
||||
- `/cli:execute "implement JWT auth"` (no session) → `.scratchpad/execute-jwt-auth-20250105-143200.md`
|
||||
- `/cli:codex-execute "refactor API layer"` (no session) → `.scratchpad/codex-execute-api-refactor-20250105-143230.md`
|
||||
> **Note**: Direct CLI commands (`/cli:analyze`, `/cli:execute`, etc.) have been replaced by semantic invocation and workflow commands.
|
||||
|
||||
**Maintenance**:
|
||||
- Periodically review and clean up old scratchpad files
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
**Role**: Autonomous development, implementation, and testing specialist
|
||||
|
||||
**File**: `d:\Claude_dms3\.codex\AGENTS.md`
|
||||
|
||||
## Prompt Structure
|
||||
|
||||
All prompts follow this 6-field format:
|
||||
|
||||
51
.npmignore
Normal file
51
.npmignore
Normal file
@@ -0,0 +1,51 @@
|
||||
# Git
|
||||
.git/
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# History and temp files
|
||||
.history/
|
||||
*.log
|
||||
*.tmp
|
||||
|
||||
# Development files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.md
|
||||
!README.md
|
||||
!CLAUDE.md
|
||||
|
||||
# Workflow runtime data
|
||||
.workflow/
|
||||
|
||||
# Test files
|
||||
test/
|
||||
tests/
|
||||
*.test.js
|
||||
*.spec.js
|
||||
|
||||
# Build artifacts
|
||||
node_modules/
|
||||
*.tgz
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Documentation (keep only essential)
|
||||
CHANGELOG.md
|
||||
CONTRIBUTING.md
|
||||
COMMAND_REFERENCE.md
|
||||
COMMAND_SPEC.md
|
||||
FAQ.md
|
||||
GETTING_STARTED.md
|
||||
GETTING_STARTED_CN.md
|
||||
|
||||
# PowerShell installer (not needed for npm)
|
||||
Install-Claude.ps1
|
||||
install-remote.ps1
|
||||
|
||||
# ccw internal files
|
||||
ccw/package.json
|
||||
ccw/node_modules/
|
||||
ccw/*.md
|
||||
21
CLAUDE.md
21
CLAUDE.md
@@ -5,7 +5,7 @@
|
||||
This document defines project-specific coding standards and development principles.
|
||||
### CLI Tool Context Protocols
|
||||
For all CLI tool usage, command syntax, and integration guidelines:
|
||||
- **MCP Tool Strategy**: @~/.claude/workflows/mcp-tool-strategy.md
|
||||
- **Tool Strategy**: @~/.claude/workflows/tool-strategy.md
|
||||
- **Intelligent Context Strategy**: @~/.claude/workflows/intelligent-tools-strategy.md
|
||||
- **Context Search Commands**: @~/.claude/workflows/context-search-strategy.md
|
||||
|
||||
@@ -28,8 +28,8 @@ For all CLI tool usage, command syntax, and integration guidelines:
|
||||
- **Learning from existing code** - Study and plan before implementing
|
||||
- **Clear intent over clever code** - Be boring and obvious
|
||||
- **Follow existing code style** - Match import patterns, naming conventions, and formatting of existing codebase
|
||||
- **No unsolicited reports** - Task summaries can be performed internally, but NEVER generate additional reports, documentation files, or summary files without explicit user permission
|
||||
- **Minimal documentation output** - Avoid unnecessary documentation. If required, save to .workflow/.scratchpad/
|
||||
- **Minimize changes** - Only modify what's directly required; avoid refactoring, adding features, or "improving" code beyond the request
|
||||
- **No unsolicited documentation** - NEVER generate reports, documentation files, or summaries without explicit user request. If required, save to .workflow/.scratchpad/
|
||||
|
||||
### Simplicity Means
|
||||
|
||||
@@ -54,11 +54,15 @@ For all CLI tool usage, command syntax, and integration guidelines:
|
||||
- Use project's formatter/linter settings
|
||||
- Don't introduce new tools without strong justification
|
||||
|
||||
## Important Reminders
|
||||
|
||||
### Fix, Don't Hide
|
||||
|
||||
- **Solve problems, don't silence symptoms** - Skipped tests, `@ts-ignore`, empty catch, `as any`, excessive timeouts = hiding bugs, not fixing them
|
||||
|
||||
**NEVER**:
|
||||
- Make assumptions - verify with existing code
|
||||
- Generate reports, summaries, or documentation files without explicit user request
|
||||
- Use suppression mechanisms (`skip`, `ignore`, `disable`) without fixing root cause
|
||||
|
||||
**ALWAYS**:
|
||||
- Plan complex tasks thoroughly before implementation
|
||||
@@ -69,15 +73,14 @@ For all CLI tool usage, command syntax, and integration guidelines:
|
||||
- Update plan documentation and progress tracking as you go
|
||||
- Learn from existing implementations
|
||||
- Stop after 3 failed attempts and reassess
|
||||
- **Edit fallback**: When Edit tool fails 2+ times on same file, try Bash sed/awk first, then Write to recreate if still failing
|
||||
|
||||
## Platform-Specific Guidelines
|
||||
|
||||
### Windows Path Format Guidelines
|
||||
- always use complete absolute Windows paths with drive letters and backslashes for ALL file operations
|
||||
- **MCP Tools**: Use double backslash `D:\\path\\file.txt` (MCP doesn't support POSIX `/d/path`)
|
||||
- **Bash Commands**: Use forward slash `D:/path/file.txt` or POSIX `/d/path/file.txt`
|
||||
- **Relative Paths**: No conversion needed `./src`, `../config`
|
||||
- **Quick Ref**: `C:\Users` → MCP: `C:\\Users` | Bash: `/c/Users` or `C:/Users`
|
||||
- **MCP Tools**: Double backslash `D:\\path\\file.txt`
|
||||
- **Bash Commands**: Forward slash `D:/path/file.txt` or `/d/path/file.txt`
|
||||
- **Relative Paths**: Universal (works in both)
|
||||
|
||||
#### **Content Uniqueness Rules**
|
||||
|
||||
|
||||
@@ -4,21 +4,13 @@ This document provides a comprehensive reference for all commands available in t
|
||||
|
||||
> **Version 5.9.6 Update**: Enhanced review cycle with dashboards, optimized lite-plan with parallel execution, and added lite-fix workflow for intelligent bug diagnosis.
|
||||
|
||||
## Unified CLI Commands (`/cli:*`)
|
||||
|
||||
These commands provide direct access to AI tools for quick analysis and interaction without initiating a full workflow.
|
||||
## CLI Commands (`/cli:*`)
|
||||
|
||||
| Command | Description |
|
||||
|---|---|
|
||||
| `/cli:analyze` | Quick codebase analysis using CLI tools (codex/gemini/qwen). |
|
||||
| `/cli:chat` | Simple CLI interaction command for direct codebase analysis. |
|
||||
| `/cli:cli-init`| Initialize CLI tool configurations (Gemini and Qwen) based on workspace analysis. |
|
||||
| `/cli:codex-execute` | Automated task decomposition and execution with Codex using resume mechanism. |
|
||||
| `/cli:discuss-plan` | Orchestrates an iterative, multi-model discussion for planning and analysis without implementation. |
|
||||
| `/cli:execute` | Auto-execution of implementation tasks with YOLO permissions and intelligent context inference. |
|
||||
| `/cli:mode:bug-diagnosis` | Bug analysis and fix suggestions using CLI tools. |
|
||||
| `/cli:mode:code-analysis` | Deep code analysis and debugging using CLI tools with specialized template. |
|
||||
| `/cli:mode:plan` | Project planning and architecture analysis using CLI tools. |
|
||||
|
||||
> **Note**: For analysis, planning, and bug fixing, use workflow commands (`/workflow:lite-plan`, `/workflow:lite-fix`) or semantic invocation through natural language.
|
||||
|
||||
## Workflow Commands (`/workflow:*`)
|
||||
|
||||
|
||||
@@ -191,25 +191,7 @@ Commands for creating, listing, and managing workflow sessions.
|
||||
|
||||
## 5. CLI Commands
|
||||
|
||||
Direct access to AI tools for analysis and code interaction without a full workflow structure.
|
||||
|
||||
### **/cli:analyze**
|
||||
- **Syntax**: `/cli:analyze [--agent] [--tool codex|gemini|qwen] [--enhance] <analysis target>`
|
||||
- **Responsibilities**: Performs read-only codebase analysis. Can operate in standard mode (direct tool call) or agent mode (`@cli-execution-agent`) for automated context discovery.
|
||||
- **Agent Calls**: `@cli-execution-agent` (if `--agent` is used).
|
||||
- **Example**:
|
||||
```bash
|
||||
/cli:analyze "authentication patterns"
|
||||
```
|
||||
|
||||
### **/cli:chat**
|
||||
- **Syntax**: `/cli:chat [--agent] [--tool codex|gemini|qwen] [--enhance] <inquiry>`
|
||||
- **Responsibilities**: Provides a direct Q&A interface with AI tools for codebase questions. Read-only.
|
||||
- **Agent Calls**: `@cli-execution-agent` (if `--agent` is used).
|
||||
- **Example**:
|
||||
```bash
|
||||
/cli:chat "how does the caching layer work?"
|
||||
```
|
||||
CLI tool configuration commands.
|
||||
|
||||
### **/cli:cli-init**
|
||||
- **Syntax**: `/cli:cli-init [--tool gemini|qwen|all] [--output path] [--preview]`
|
||||
@@ -220,59 +202,7 @@ Direct access to AI tools for analysis and code interaction without a full workf
|
||||
/cli:cli-init
|
||||
```
|
||||
|
||||
### **/cli:codex-execute**
|
||||
- **Syntax**: `/cli:codex-execute [--verify-git] <description|task-id>`
|
||||
- **Responsibilities**: Orchestrates automated task decomposition and sequential execution using Codex. It uses the `resume --last` mechanism for context continuity between subtasks.
|
||||
- **Agent Calls**: None directly, but orchestrates `codex` CLI tool.
|
||||
- **Example**:
|
||||
```bash
|
||||
/cli:codex-execute "implement user authentication system"
|
||||
```
|
||||
|
||||
### **/cli:discuss-plan**
|
||||
- **Syntax**: `/cli:discuss-plan [--topic '...'] [--task-id '...'] [--rounds N] <input>`
|
||||
- **Responsibilities**: Orchestrates an iterative, multi-model (Gemini, Codex, Claude) discussion to perform deep analysis and planning without modifying code.
|
||||
- **Agent Calls**: None directly, but orchestrates `gemini` and `codex` CLI tools.
|
||||
- **Example**:
|
||||
```bash
|
||||
/cli:discuss-plan --topic "Design a new caching layer"
|
||||
```
|
||||
|
||||
### **/cli:execute**
|
||||
- **Syntax**: `/cli:execute [--agent] [--tool codex|gemini|qwen] [--enhance] <description|task-id>`
|
||||
- **Responsibilities**: Executes implementation tasks with auto-approval (`YOLO` mode). **MODIFIES CODE**.
|
||||
- **Agent Calls**: `@cli-execution-agent` (if `--agent` is used).
|
||||
- **Example**:
|
||||
```bash
|
||||
/cli:execute "implement JWT authentication with middleware"
|
||||
```
|
||||
|
||||
### **/cli:mode:bug-diagnosis**
|
||||
- **Syntax**: `/cli:mode:bug-diagnosis [--tool ...] [--enhance] [--cd path] <bug description>`
|
||||
- **Responsibilities**: Performs systematic bug analysis using the `bug-fix.md` template. Read-only.
|
||||
- **Agent Calls**: `@cli-execution-agent` (default).
|
||||
- **Example**:
|
||||
```bash
|
||||
/cli:mode:bug-diagnosis "null pointer error in login flow"
|
||||
```
|
||||
|
||||
### **/cli:mode:code-analysis**
|
||||
- **Syntax**: `/cli:mode:code-analysis [--agent] [--tool ...] [--enhance] [--cd path] <analysis target>`
|
||||
- **Responsibilities**: Performs deep code analysis and execution path tracing using the `code-analysis.md` template. Read-only.
|
||||
- **Agent Calls**: `@cli-execution-agent` (if `--agent` is used).
|
||||
- **Example**:
|
||||
```bash
|
||||
/cli:mode:code-analysis "trace authentication execution flow"
|
||||
```
|
||||
|
||||
### **/cli:mode:plan**
|
||||
- **Syntax**: `/cli:mode:plan [--agent] [--tool ...] [--enhance] [--cd path] <topic>`
|
||||
- **Responsibilities**: Performs comprehensive planning and architecture analysis using the `plan.md` template. Read-only.
|
||||
- **Agent Calls**: `@cli-execution-agent` (if `--agent` is used).
|
||||
- **Example**:
|
||||
```bash
|
||||
/cli:mode:plan "design user dashboard architecture"
|
||||
```
|
||||
> **Note**: For analysis, planning, and bug fixing, use workflow commands (`/workflow:lite-plan`, `/workflow:lite-fix`) or semantic invocation through natural language. Claude will automatically use appropriate CLI tools (Gemini/Qwen/Codex) with templates as needed.
|
||||
|
||||
---
|
||||
|
||||
@@ -380,13 +310,14 @@ Commands for managing individual tasks within a workflow session.
|
||||
```
|
||||
|
||||
### **/enhance-prompt**
|
||||
- **Syntax**: `/enhance-prompt <user input>`
|
||||
- **Responsibilities**: A system-level skill that enhances a user's prompt by adding context from session memory and codebase analysis. It is typically triggered automatically by other commands that include the `--enhance` flag.
|
||||
- **Skill Invocation**: This is a core skill, invoked when `--enhance` is used.
|
||||
- **Syntax**: `/enhance-prompt <user input>` or use `-e` flag in conversation
|
||||
- **Responsibilities**: A system-level skill that enhances a user's prompt by adding context from session memory and codebase analysis. It is typically triggered by the `-e` flag in natural conversation.
|
||||
- **Skill Invocation**: This is a core skill, invoked when `-e` is used in conversation.
|
||||
- **Agent Calls**: None.
|
||||
- **Example (as part of another command)**:
|
||||
```bash
|
||||
/cli:execute --enhance "fix the login button"
|
||||
- **Example (in natural conversation)**:
|
||||
```
|
||||
User: "fix the login button -e"
|
||||
→ Prompt-enhancer expands and enhances the request
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
24
FAQ.md
24
FAQ.md
@@ -248,16 +248,16 @@ CCW-help
|
||||
### What's the difference between `/cli:*` and `/workflow:*` commands?
|
||||
|
||||
**`/cli:*` commands**:
|
||||
- Direct access to external AI tools
|
||||
- No workflow session required
|
||||
- Quick one-off tasks
|
||||
- Examples: `/cli:analyze`, `/cli:chat`
|
||||
- CLI tool configuration
|
||||
- Example: `/cli:cli-init` (initialize Gemini/Qwen configurations)
|
||||
|
||||
**`/workflow:*` commands**:
|
||||
- Multi-phase orchestration
|
||||
- Session-based
|
||||
- Complex development workflows
|
||||
- Examples: `/workflow:plan`, `/workflow:execute`
|
||||
- Examples: `/workflow:plan`, `/workflow:lite-plan`, `/workflow:lite-fix`
|
||||
|
||||
> **Note**: Most CLI commands have been replaced by **semantic invocation**. Simply describe your needs in natural language, and Claude will automatically use the appropriate tools.
|
||||
|
||||
### How do I use command flags?
|
||||
|
||||
@@ -270,9 +270,6 @@ Most commands support flags for customization:
|
||||
# With CLI execution flag
|
||||
/workflow:plan --cli-execute "Feature description"
|
||||
|
||||
# With tool selection
|
||||
/cli:analyze --tool gemini "Code analysis"
|
||||
|
||||
# With multiple flags
|
||||
/workflow:ui-design:explore-auto --prompt "Login page" --style-variants 3 --layout-variants 2
|
||||
```
|
||||
@@ -281,17 +278,12 @@ Most commands support flags for customization:
|
||||
|
||||
**Yes!** Claude understands semantic invocation:
|
||||
|
||||
Instead of:
|
||||
```bash
|
||||
/cli:analyze --tool gemini "Authentication module"
|
||||
```
|
||||
|
||||
You can say:
|
||||
Instead of using specific commands, you can say:
|
||||
```
|
||||
"Use Gemini to analyze the authentication module architecture"
|
||||
```
|
||||
|
||||
Claude will automatically execute the appropriate command.
|
||||
Claude will automatically select and execute the appropriate CLI tools (Gemini/Qwen/Codex) with optimized templates.
|
||||
|
||||
### What does the `-e` or `--enhance` flag do?
|
||||
|
||||
@@ -303,8 +295,6 @@ User: "Analyze authentication module -e"
|
||||
|
||||
Claude will expand and enhance your request for better results.
|
||||
|
||||
**Note**: `--enhance` in CLI commands (e.g., `/cli:analyze --enhance`) is a different feature built into CLI tools.
|
||||
|
||||
---
|
||||
|
||||
## 📦 Sessions & Tasks
|
||||
|
||||
@@ -178,62 +178,36 @@ After planning, validate your implementation plan for consistency and completene
|
||||
Quick bug analysis and fix workflow:
|
||||
|
||||
```bash
|
||||
# Analyze the bug
|
||||
/cli:mode:bug-diagnosis "Incorrect success message with wrong password"
|
||||
# Lightweight bug fix workflow with intelligent diagnosis
|
||||
/workflow:lite-fix "Incorrect success message with wrong password"
|
||||
|
||||
# Claude will analyze and then directly implement the fix based on the analysis
|
||||
# Claude will analyze severity, diagnose root cause, and implement the fix
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Workflow-Free Usage: Standalone Tools
|
||||
## 🔧 Lightweight Commands
|
||||
|
||||
Beyond the full workflow mode, CCW provides standalone CLI tools and commands suitable for quick analysis, ad-hoc queries, and routine maintenance tasks.
|
||||
Beyond the full workflow mode, CCW provides lightweight commands suitable for quick analysis and routine tasks.
|
||||
|
||||
### Direct CLI Tool Invocation
|
||||
### Workflow Commands for Quick Tasks
|
||||
|
||||
CCW supports direct invocation of external AI tools (Gemini, Qwen, Codex) through a unified CLI interface without creating workflow sessions.
|
||||
|
||||
#### Code Analysis
|
||||
|
||||
Quickly analyze project code structure and architectural patterns:
|
||||
Use workflow commands for integrated planning and bug fixing:
|
||||
|
||||
```bash
|
||||
# Code analysis with Gemini
|
||||
/cli:analyze --tool gemini "Analyze authentication module architecture"
|
||||
# Lightweight planning workflow
|
||||
/workflow:lite-plan "Design a scalable microservices architecture"
|
||||
|
||||
# Code quality analysis with Qwen
|
||||
/cli:analyze --tool qwen "Review database model design for best practices"
|
||||
# Bug fix workflow with intelligent diagnosis
|
||||
/workflow:lite-fix "Analyze potential causes of memory leak"
|
||||
|
||||
# Initialize CLI tool configurations
|
||||
/cli:cli-init
|
||||
```
|
||||
|
||||
#### Interactive Chat
|
||||
### Semantic Tool Invocation (Replaces Direct CLI Commands)
|
||||
|
||||
Direct interactive dialogue with AI tools:
|
||||
|
||||
```bash
|
||||
# Chat with Gemini
|
||||
/cli:chat --tool gemini "Explain React Hook use cases"
|
||||
|
||||
# Discuss implementation with Codex
|
||||
/cli:chat --tool codex "How to optimize this query performance"
|
||||
```
|
||||
|
||||
#### Specialized Analysis Modes
|
||||
|
||||
Use specific analysis modes for in-depth exploration:
|
||||
|
||||
```bash
|
||||
# Architecture planning mode
|
||||
/cli:mode:plan --tool gemini "Design a scalable microservices architecture"
|
||||
|
||||
# Deep code analysis
|
||||
/cli:mode:code-analysis --tool qwen "Analyze utility functions in src/utils/"
|
||||
|
||||
# Bug analysis mode
|
||||
/cli:mode:bug-index --tool gemini "Analyze potential causes of memory leak"
|
||||
```
|
||||
|
||||
### Semantic Tool Invocation
|
||||
> **Important**: Direct CLI commands (`/cli:analyze`, `/cli:chat`, `/cli:execute`, etc.) have been replaced by **semantic invocation**. Simply describe your needs in natural language, and Claude will automatically select and execute the appropriate CLI tools (Gemini/Qwen/Codex) with optimized templates.
|
||||
|
||||
Users can tell Claude to use specific tools through natural language, and Claude will understand the intent and automatically execute the appropriate commands.
|
||||
|
||||
@@ -381,13 +355,7 @@ User: "Analyze authentication module -e"
|
||||
→ AI uses prompt-enhancer skill to expand the request
|
||||
```
|
||||
|
||||
**CLI Command Enhancement** (built-in CLI feature):
|
||||
```bash
|
||||
# The --enhance flag here is a CLI parameter, not a skill trigger
|
||||
/cli:analyze --enhance "check for security issues"
|
||||
```
|
||||
|
||||
**Important Note**: The `-e` flag works in natural conversation, but `--enhance` in CLI commands is a separate enhancement mechanism, not the skill system.
|
||||
**Important Note**: The `-e` flag works in natural conversation to trigger the prompt-enhancer skill.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -188,62 +188,36 @@
|
||||
快速 Bug 分析和修复工作流:
|
||||
|
||||
```bash
|
||||
# 分析 Bug
|
||||
/cli:mode:bug-diagnosis "密码错误时仍显示成功消息"
|
||||
# 轻量级 Bug 修复工作流,带智能诊断
|
||||
/workflow:lite-fix "密码错误时仍显示成功消息"
|
||||
|
||||
# Claude 会分析后直接根据分析结果实现修复
|
||||
# Claude 会分析严重程度,诊断根因,并实现修复
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 无工作流协作:独立工具使用
|
||||
## 🔧 轻量级命令
|
||||
|
||||
除了完整的工作流模式,CCW 还提供独立的 CLI 工具和命令,适合快速分析、临时查询和日常维护任务。
|
||||
除了完整的工作流模式,CCW 还提供轻量级命令,适合快速分析和日常任务。
|
||||
|
||||
### CLI 工具直接调用
|
||||
### 快速任务工作流命令
|
||||
|
||||
CCW 支持通过统一的 CLI 接口直接调用外部 AI 工具(Gemini、Qwen、Codex),无需创建工作流会话。
|
||||
|
||||
#### 代码分析
|
||||
|
||||
快速分析项目代码结构和架构模式:
|
||||
使用工作流命令进行集成规划和 Bug 修复:
|
||||
|
||||
```bash
|
||||
# 使用 Gemini 进行代码分析
|
||||
/cli:analyze --tool gemini "分析认证模块的架构设计"
|
||||
# 轻量级规划工作流
|
||||
/workflow:lite-plan "设计一个可扩展的微服务架构"
|
||||
|
||||
# 使用 Qwen 分析代码质量
|
||||
/cli:analyze --tool qwen "检查数据库模型的设计是否合理"
|
||||
# 带智能诊断的 Bug 修复工作流
|
||||
/workflow:lite-fix "分析内存泄漏问题的可能原因"
|
||||
|
||||
# 初始化 CLI 工具配置
|
||||
/cli:cli-init
|
||||
```
|
||||
|
||||
#### 交互式对话
|
||||
### 语义调用(替代直接 CLI 命令)
|
||||
|
||||
与 AI 工具进行直接交互式对话:
|
||||
|
||||
```bash
|
||||
# 与 Gemini 交互
|
||||
/cli:chat --tool gemini "解释一下 React Hook 的使用场景"
|
||||
|
||||
# 与 Codex 交互讨论实现方案
|
||||
/cli:chat --tool codex "如何优化这个查询性能"
|
||||
```
|
||||
|
||||
#### 专业模式分析
|
||||
|
||||
使用特定的分析模式进行深度探索:
|
||||
|
||||
```bash
|
||||
# 架构分析模式
|
||||
/cli:mode:plan --tool gemini "设计一个可扩展的微服务架构"
|
||||
|
||||
# 深度代码分析
|
||||
/cli:mode:code-analysis --tool qwen "分析 src/utils/ 目录下的工具函数"
|
||||
|
||||
# Bug 分析模式
|
||||
/cli:mode:bug-diagnosis --tool gemini "分析内存泄漏问题的可能原因"
|
||||
```
|
||||
|
||||
### 工具语义调用
|
||||
> **重要**: 直接 CLI 命令(`/cli:analyze`、`/cli:chat`、`/cli:execute` 等)已被**语义调用**替代。只需使用自然语言描述您的需求,Claude 会自动选择并执行合适的 CLI 工具(Gemini/Qwen/Codex)和优化的模板。
|
||||
|
||||
用户可以通过自然语言告诉 Claude 使用特定工具完成任务,Claude 会理解意图并自动执行相应的命令。
|
||||
|
||||
@@ -391,13 +365,7 @@ CCW 使用分层的 CLAUDE.md 文档系统维护项目上下文。定期更新
|
||||
→ AI 使用 prompt-enhancer 技能扩展请求
|
||||
```
|
||||
|
||||
**CLI 命令增强** (CLI 内置功能):
|
||||
```bash
|
||||
# 这里的 --enhance 标识符是 CLI 参数,不是技能触发器
|
||||
/cli:analyze --enhance "检查安全问题"
|
||||
```
|
||||
|
||||
**重要说明**:`-e` 标识符仅在自然对话中有效,而 CLI 命令中的 `--enhance` 是独立的增强机制,与技能系统无关。
|
||||
**重要说明**:`-e` 标识符用于在自然对话中触发 prompt-enhancer 技能。
|
||||
|
||||
---
|
||||
|
||||
|
||||
86
README.md
86
README.md
@@ -2,7 +2,8 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://github.com/catlog22/Claude-Code-Workflow/releases)
|
||||
[](https://github.com/catlog22/Claude-Code-Workflow/releases)
|
||||
[](https://www.npmjs.com/package/claude-code-workflow)
|
||||
[](LICENSE)
|
||||
[]()
|
||||
|
||||
@@ -12,16 +13,15 @@
|
||||
|
||||
---
|
||||
|
||||
**Claude Code Workflow (CCW)** transforms AI development from simple prompt chaining into a powerful, context-first orchestration system. It solves execution uncertainty and error accumulation through structured planning, deterministic execution, and intelligent multi-model orchestration.
|
||||
**Claude Code Workflow (CCW)** is a JSON-driven multi-agent development framework with intelligent CLI orchestration (Gemini/Qwen/Codex), context-first architecture, and automated workflow execution. It transforms AI development from simple prompt chaining into a powerful orchestration system.
|
||||
|
||||
> **🎉 Version 5.9.6: Review Cycle Enhancement & Dashboard Automation**
|
||||
> **🎉 Version 6.0.0: npm Package & Simplified Installation**
|
||||
>
|
||||
> **Core Improvements**:
|
||||
> - ✨ **Enhanced Review Dashboard**: The `review-cycle` dashboard now supports real-time progress tracking and advanced filtering for better visibility into code reviews.
|
||||
> - 🎯 **New Fix-Tracking Dashboard**: Introduced a new, independent `fix-dashboard.html` to monitor the progress of bug fixes with rich data integration.
|
||||
> - 🚀 **`lite-fix` Workflow**: Added a new `lite-fix` command for intelligent, streamlined bug diagnosis and resolution.
|
||||
> - 🛠️ **`lite-plan` Optimization**: Significantly optimized the `lite-plan` workflow with cost-aware parallel execution, better complexity analysis, and robust context protection.
|
||||
> - 🧠 **Intelligent Test Cycles**: Improved the `test-cycle-execute` command with smart iteration strategies and a universal `@test-fix-agent` for more effective testing.
|
||||
> - 📦 **npm Package**: Now available as `claude-code-workflow` on npm for simplified global installation
|
||||
> - 🖥️ **CCW CLI Tool**: New `ccw` command with dashboard viewer, installation management, and workflow visualization
|
||||
> - 🎯 **Simplified Install Flow**: Unified installation via npm with local-only operation (no GitHub API dependency)
|
||||
> - ✨ **Enhanced Dashboard**: MCP manager, review session improvements, and UI enhancements
|
||||
>
|
||||
> See [CHANGELOG.md](CHANGELOG.md) for complete details.
|
||||
|
||||
@@ -44,9 +44,26 @@ CCW is built on a set of core principles that distinguish it from traditional AI
|
||||
|
||||
## ⚙️ Installation
|
||||
|
||||
For detailed installation instructions, refer to the [**INSTALL.md**](INSTALL.md) guide.
|
||||
### **📦 npm Install (Recommended)**
|
||||
|
||||
### **🚀 One-Click Quick Install**
|
||||
Install globally via npm:
|
||||
```bash
|
||||
npm install -g claude-code-workflow
|
||||
```
|
||||
|
||||
Then install workflow files to your system:
|
||||
```bash
|
||||
# Interactive installation
|
||||
ccw install
|
||||
|
||||
# Global installation (to ~/.claude)
|
||||
ccw install -m Global
|
||||
|
||||
# Project-specific installation
|
||||
ccw install -m Path -p /path/to/project
|
||||
```
|
||||
|
||||
### **🚀 Alternative: One-Click Script Install**
|
||||
|
||||
**Windows (PowerShell):**
|
||||
```powershell
|
||||
@@ -67,6 +84,55 @@ If slash commands (e.g., `/workflow:*`) are recognized, the installation was suc
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ CCW CLI Tool
|
||||
|
||||
The `ccw` command provides a powerful CLI for managing your Claude Code Workflow installation:
|
||||
|
||||
### **Commands**
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `ccw install` | Install workflow files to Global (~/.claude) or specific Path |
|
||||
| `ccw upgrade` | Upgrade existing installations to current package version |
|
||||
| `ccw uninstall` | Remove workflow files from an installation |
|
||||
| `ccw view` | Open the workflow dashboard in browser |
|
||||
| `ccw serve` | Start dashboard server without opening browser |
|
||||
| `ccw list` | List all managed installations |
|
||||
|
||||
### **Usage Examples**
|
||||
|
||||
```bash
|
||||
# Install globally
|
||||
ccw install -m Global
|
||||
|
||||
# Install to specific project
|
||||
ccw install -m Path -p ./my-project
|
||||
|
||||
# Open dashboard
|
||||
ccw view
|
||||
|
||||
# Start dashboard server on custom port
|
||||
ccw serve -p 8080
|
||||
|
||||
# Upgrade all installations
|
||||
ccw upgrade -a
|
||||
|
||||
# List installations
|
||||
ccw list
|
||||
```
|
||||
|
||||
### **Dashboard Features**
|
||||
|
||||
The CCW Dashboard (`ccw view`) provides:
|
||||
- 📊 **Session Overview**: View all workflow sessions with status and progress
|
||||
- 📋 **Task Management**: Track task execution and completion
|
||||
- 🔍 **Review Sessions**: Manage code review cycles
|
||||
- ⚙️ **MCP Manager**: Configure and monitor MCP servers
|
||||
- 🪝 **Hook Manager**: Manage Claude Code hooks
|
||||
- 📁 **Project Explorer**: Navigate project structure and artifacts
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Command Reference
|
||||
|
||||
CCW provides a rich set of commands for managing workflows, tasks, and interactions with AI tools. For a complete list and detailed descriptions of all available commands, please refer to the [**COMMAND_REFERENCE.md**](COMMAND_REFERENCE.md) file.
|
||||
|
||||
@@ -16,7 +16,7 @@ flowchart TD
|
||||
BugFix --> BugSeverity{了解问题根因?}
|
||||
BugSeverity -->|清楚| LiteFix[/ /workflow:lite-fix<br>标准Bug修复 /]
|
||||
BugSeverity -->|生产事故| HotFix[/ /workflow:lite-fix --hotfix<br>热修复模式 /]
|
||||
BugSeverity -->|不清楚| BugDiag[/ /cli:mode:bug-diagnosis<br>先诊断根因 /]
|
||||
BugSeverity -->|不清楚| BugDiag[/ /workflow:lite-fix<br>自动诊断根因 /]
|
||||
|
||||
BugDiag --> LiteFix
|
||||
LiteFix --> BugComplete[Bug修复完成]
|
||||
@@ -142,7 +142,7 @@ flowchart TD
|
||||
|------|------|------|
|
||||
| 🐛 **标准Bug修复** | `/workflow:lite-fix "bug描述"` | 自适应严重性评估,完整诊断→影响评估→修复→验证 |
|
||||
| 🔥 **生产热修复** | `/workflow:lite-fix --hotfix "bug描述"` | 最小化诊断,快速修复,自动生成跟进任务 |
|
||||
| ❓ **根因不清楚** | `/cli:mode:bug-diagnosis` → `/workflow:lite-fix` | 先深度诊断,再执行修复 |
|
||||
| ❓ **根因不清楚** | `/workflow:lite-fix` | 自动进行深度诊断并执行修复 |
|
||||
| ✅ **功能开发** | 继续后续流程 | 不是Bug修复,按正常开发流程 |
|
||||
|
||||
**Lite-Fix 工作流特性**:
|
||||
@@ -171,16 +171,16 @@ flowchart TD
|
||||
→ 最小化诊断 → 假设 Critical → 手术式修复 → 烟雾测试
|
||||
→ 自动生成: 全面修复任务(3天内)+ 事后分析(1周内)
|
||||
|
||||
# 根因不清楚(先诊断)
|
||||
/cli:mode:bug-diagnosis --tool gemini "购物车随机丢失商品"
|
||||
→ 深度诊断报告 → /workflow:lite-fix "修复购物车状态同步问题"
|
||||
# 根因不清楚(lite-fix 自动诊断)
|
||||
/workflow:lite-fix "购物车随机丢失商品"
|
||||
→ 自动深度诊断 → 识别根因 → 实现修复
|
||||
```
|
||||
|
||||
**何时使用 lite-fix**:
|
||||
- ✅ 任何有明确症状的Bug(自动适应严重性)
|
||||
- ✅ 本地化修复(1-5个文件)
|
||||
- ✅ 生产事故(使用 `--hotfix` 模式)
|
||||
- ❌ 根因完全不明 → 先用 `/cli:mode:bug-diagnosis`
|
||||
- ✅ 根因不清楚(自动进行深度诊断)
|
||||
- ❌ 需要架构变更 → 用 `/workflow:plan --mode bugfix`
|
||||
|
||||
---
|
||||
@@ -416,33 +416,24 @@ Phase 1: Gemini 分析 ──┐
|
||||
→ Claude Code 自动生成:codex -C src/auth --full-auto exec "实现注册"
|
||||
```
|
||||
|
||||
**方式二:直接命令调用**
|
||||
|
||||
```bash
|
||||
# 通过 Slash 命令精准调用
|
||||
/cli:chat --tool gemini "解释这个算法"
|
||||
/cli:analyze --tool qwen "分析性能瓶颈"
|
||||
/cli:execute --tool codex "优化查询性能"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 🔗 CLI 结果作为上下文(Memory)
|
||||
#### 🔗 语义调用与结果上下文(Memory)
|
||||
|
||||
CLI 工具的分析结果可以被保存并作为后续操作的上下文(memory),实现智能化的工作流程:
|
||||
通过自然语言描述,Claude 会自动选择并执行适当的 CLI 工具(Gemini/Qwen/Codex),分析结果作为后续操作的上下文。
|
||||
|
||||
**1. 结果持久化**
|
||||
**1. 语义调用示例**
|
||||
|
||||
```bash
|
||||
# CLI 执行结果自动保存到会话目录
|
||||
/cli:chat --tool gemini "分析认证模块架构"
|
||||
→ 保存到:.workflow/active/WFS-xxx/.chat/chat-[timestamp].md
|
||||
# 用自然语言描述需求,Claude 自动选择工具
|
||||
"使用 gemini 分析认证模块架构"
|
||||
→ Claude 自动执行 Gemini CLI 并保存结果
|
||||
|
||||
/cli:analyze --tool qwen "评估性能瓶颈"
|
||||
→ 保存到:.workflow/active/WFS-xxx/.chat/analyze-[timestamp].md
|
||||
"让 qwen 评估性能瓶颈"
|
||||
→ Claude 自动执行 Qwen CLI 并保存结果
|
||||
|
||||
/cli:execute --tool codex "实现功能"
|
||||
→ 保存到:.workflow/active/WFS-xxx/.chat/execute-[timestamp].md
|
||||
"用 codex 实现这个功能"
|
||||
→ Claude 自动执行 Codex CLI
|
||||
```
|
||||
|
||||
**2. 结果作为规划依据**
|
||||
@@ -476,8 +467,8 @@ CLI 工具的分析结果可以被保存并作为后续操作的上下文(memo
|
||||
|
||||
```bash
|
||||
# 引用历史会话的分析结果
|
||||
/cli:execute --tool codex "参考 WFS-2024-001 中的架构分析,实现新的支付模块"
|
||||
→ 系统自动加载指定会话的上下文
|
||||
"参考 WFS-2024-001 中的架构分析,用 codex 实现新的支付模块"
|
||||
→ Claude 自动加载指定会话的上下文
|
||||
→ 基于历史分析进行实现
|
||||
```
|
||||
|
||||
|
||||
@@ -348,33 +348,24 @@ Phase 1: Gemini analysis ──┐
|
||||
→ Claude Code auto-generates: codex -C src/auth --full-auto exec "Implement registration"
|
||||
```
|
||||
|
||||
**Method 2: Direct Command Invocation**
|
||||
|
||||
```bash
|
||||
# Precise invocation via Slash commands
|
||||
/cli:chat --tool gemini "Explain this algorithm"
|
||||
/cli:analyze --tool qwen "Analyze performance bottlenecks"
|
||||
/cli:execute --tool codex "Optimize query performance"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 🔗 CLI Results as Context (Memory)
|
||||
#### 🔗 Semantic Invocation & Results Context (Memory)
|
||||
|
||||
CLI tool analysis results can be saved and used as context (memory) for subsequent operations, enabling intelligent workflows:
|
||||
Through natural language, Claude automatically selects and executes appropriate CLI tools (Gemini/Qwen/Codex), with results serving as context for subsequent operations.
|
||||
|
||||
**1. Result Persistence**
|
||||
**1. Semantic Invocation Examples**
|
||||
|
||||
```bash
|
||||
# CLI execution results automatically saved to session directory
|
||||
/cli:chat --tool gemini "Analyze authentication module architecture"
|
||||
→ Saved to: .workflow/active/WFS-xxx/.chat/chat-[timestamp].md
|
||||
# Describe needs in natural language, Claude auto-selects tools
|
||||
"Use gemini to analyze authentication module architecture"
|
||||
→ Claude auto-executes Gemini CLI and saves results
|
||||
|
||||
/cli:analyze --tool qwen "Evaluate performance bottlenecks"
|
||||
→ Saved to: .workflow/active/WFS-xxx/.chat/analyze-[timestamp].md
|
||||
"Have qwen evaluate performance bottlenecks"
|
||||
→ Claude auto-executes Qwen CLI and saves results
|
||||
|
||||
/cli:execute --tool codex "Implement feature"
|
||||
→ Saved to: .workflow/active/WFS-xxx/.chat/execute-[timestamp].md
|
||||
"Use codex to implement this feature"
|
||||
→ Claude auto-executes Codex CLI
|
||||
```
|
||||
|
||||
**2. Results as Planning Basis**
|
||||
@@ -408,8 +399,8 @@ Have codex synthesize above Gemini and Qwen analyses to implement optimal soluti
|
||||
|
||||
```bash
|
||||
# Reference historical session analysis results
|
||||
/cli:execute --tool codex "Refer to architecture analysis in WFS-2024-001, implement new payment module"
|
||||
→ System automatically loads specified session context
|
||||
"Refer to architecture analysis in WFS-2024-001, use codex to implement new payment module"
|
||||
→ Claude automatically loads specified session context
|
||||
→ Implement based on historical analysis
|
||||
```
|
||||
|
||||
@@ -630,16 +621,13 @@ Use gemini to review code quality
|
||||
### Scenario D: Bug Fixing
|
||||
|
||||
```bash
|
||||
# 1. Diagnosis
|
||||
/cli:mode:bug-diagnosis --tool gemini "User login fails with token expired error"
|
||||
# 1. Intelligent bug fix workflow (includes diagnosis)
|
||||
/workflow:lite-fix "User login fails with token expired error"
|
||||
|
||||
# 2. Quick fix
|
||||
/workflow:lite-plan "Fix JWT token expiration validation logic"
|
||||
|
||||
# 3. Test fix
|
||||
# 2. Test fix (if needed)
|
||||
/workflow:test-cycle-execute
|
||||
|
||||
# 4. Complete
|
||||
# 3. Complete
|
||||
```
|
||||
|
||||
---
|
||||
@@ -654,7 +642,7 @@ Use gemini to review code quality
|
||||
| ❓ Know what, don't know how | `/workflow:brainstorm:auto-parallel "Design technical solution"` |
|
||||
| ✅ Know what and how | `/workflow:plan "Specific implementation description"` |
|
||||
| ⚡ Simple, clear small task | `/workflow:lite-plan "Task description"` |
|
||||
| 🐛 Bug fixing | `/cli:mode:bug-diagnosis` + `/workflow:lite-plan` |
|
||||
| 🐛 Bug fixing | `/workflow:lite-fix "bug description"` |
|
||||
|
||||
### Choose by Project Phase
|
||||
|
||||
|
||||
121
ccw/README.md
Normal file
121
ccw/README.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# CCW - Claude Code Workflow CLI
|
||||
|
||||
A command-line tool for viewing workflow sessions and code review results from the Claude Code Workflow system.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Install globally
|
||||
npm install -g ccw
|
||||
|
||||
# Or install from local source
|
||||
cd path/to/ccw
|
||||
npm install
|
||||
npm link
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### View Dashboard
|
||||
|
||||
```bash
|
||||
# Open workflow dashboard in browser
|
||||
ccw view
|
||||
|
||||
# Specify project path
|
||||
ccw view -p /path/to/project
|
||||
|
||||
# Generate dashboard without opening browser
|
||||
ccw view --no-browser
|
||||
|
||||
# Custom output path
|
||||
ccw view -o report.html
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### Workflow Dashboard
|
||||
- **Active Sessions**: View all active workflow sessions with task progress
|
||||
- **Archived Sessions**: Browse completed/archived sessions
|
||||
- **Task Tracking**: See individual task status (pending/in_progress/completed)
|
||||
- **Progress Bars**: Visual progress indicators for each session
|
||||
|
||||
### Review Integration
|
||||
- **Code Review Findings**: View results from `review-module-cycle`
|
||||
- **Severity Distribution**: Critical/High/Medium/Low finding counts
|
||||
- **Dimension Analysis**: Findings by review dimension (Security, Architecture, Quality, etc.)
|
||||
- **Tabbed Interface**: Switch between Workflow and Reviews tabs
|
||||
|
||||
## Dashboard Data Sources
|
||||
|
||||
The CLI reads data from the `.workflow/` directory structure:
|
||||
|
||||
```
|
||||
.workflow/
|
||||
├── active/
|
||||
│ └── WFS-{session-id}/
|
||||
│ ├── workflow-session.json # Session metadata
|
||||
│ ├── .task/
|
||||
│ │ └── IMPL-*.json # Task definitions
|
||||
│ └── .review/
|
||||
│ ├── review-progress.json # Review progress
|
||||
│ └── dimensions/
|
||||
│ └── *.json # Dimension findings
|
||||
└── archives/
|
||||
└── WFS-{session-id}/ # Archived sessions
|
||||
```
|
||||
|
||||
## Bundled Templates
|
||||
|
||||
The CLI includes bundled dashboard templates:
|
||||
- `workflow-dashboard.html` - Workflow session and task visualization
|
||||
- `review-cycle-dashboard.html` - Code review findings display
|
||||
|
||||
No external template installation required - templates are included in the npm package.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js >= 16.0.0
|
||||
- npm or yarn
|
||||
|
||||
## Integration with Claude Code Workflow
|
||||
|
||||
This CLI is a standalone tool that works with the Claude Code Workflow system:
|
||||
|
||||
1. **Install CCW CLI** (via npm)
|
||||
- `npm install -g ccw`
|
||||
- Provides `ccw view` command for dashboard viewing
|
||||
- Templates are bundled - no additional installation required
|
||||
|
||||
2. **Optional: Install Claude Code Workflow** (via `Install-Claude.ps1`)
|
||||
- Provides workflow commands, agents, and automation
|
||||
- CCW will automatically detect and display workflow sessions
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `-p, --path <path>` | Path to project directory (default: current directory) |
|
||||
| `--no-browser` | Generate dashboard without opening browser |
|
||||
| `-o, --output <file>` | Custom output path for HTML file |
|
||||
| `-V, --version` | Display version number |
|
||||
| `-h, --help` | Display help information |
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Clone and install dependencies
|
||||
git clone <repo-url>
|
||||
cd ccw
|
||||
npm install
|
||||
|
||||
# Link for local testing
|
||||
npm link
|
||||
|
||||
# Test the CLI
|
||||
ccw view -p /path/to/test/project
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
10
ccw/bin/ccw.js
Normal file
10
ccw/bin/ccw.js
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* CCW CLI - Claude Code Workflow Dashboard
|
||||
* Entry point for global CLI installation
|
||||
*/
|
||||
|
||||
import { run } from '../src/cli.js';
|
||||
|
||||
run(process.argv);
|
||||
1914
ccw/package-lock.json
generated
Normal file
1914
ccw/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
47
ccw/package.json
Normal file
47
ccw/package.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "ccw",
|
||||
"version": "1.0.0",
|
||||
"description": "Claude Code Workflow CLI - Dashboard viewer for workflow sessions and reviews",
|
||||
"type": "module",
|
||||
"main": "src/index.js",
|
||||
"bin": {
|
||||
"ccw": "./bin/ccw.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node --test",
|
||||
"lint": "eslint src/"
|
||||
},
|
||||
"keywords": [
|
||||
"claude",
|
||||
"workflow",
|
||||
"cli",
|
||||
"dashboard",
|
||||
"code-review"
|
||||
],
|
||||
"author": "Claude Code Workflow",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^11.0.0",
|
||||
"open": "^9.1.0",
|
||||
"chalk": "^5.3.0",
|
||||
"glob": "^10.3.0",
|
||||
"inquirer": "^9.2.0",
|
||||
"ora": "^7.0.0",
|
||||
"figlet": "^1.7.0",
|
||||
"boxen": "^7.1.0",
|
||||
"gradient-string": "^2.0.2"
|
||||
},
|
||||
"files": [
|
||||
"bin/",
|
||||
"src/",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/claude-code-workflow/ccw"
|
||||
}
|
||||
}
|
||||
100
ccw/src/cli.js
Normal file
100
ccw/src/cli.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Command } from 'commander';
|
||||
import { viewCommand } from './commands/view.js';
|
||||
import { serveCommand } from './commands/serve.js';
|
||||
import { installCommand } from './commands/install.js';
|
||||
import { uninstallCommand } from './commands/uninstall.js';
|
||||
import { upgradeCommand } from './commands/upgrade.js';
|
||||
import { listCommand } from './commands/list.js';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
/**
|
||||
* Load package.json with error handling
|
||||
* @returns {Object} - Package info with version
|
||||
*/
|
||||
function loadPackageInfo() {
|
||||
const pkgPath = join(__dirname, '../package.json');
|
||||
|
||||
try {
|
||||
if (!existsSync(pkgPath)) {
|
||||
console.error('Fatal Error: package.json not found.');
|
||||
console.error(`Expected location: ${pkgPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const content = readFileSync(pkgPath, 'utf8');
|
||||
return JSON.parse(content);
|
||||
} catch (error) {
|
||||
if (error instanceof SyntaxError) {
|
||||
console.error('Fatal Error: package.json contains invalid JSON.');
|
||||
console.error(`Parse error: ${error.message}`);
|
||||
} else {
|
||||
console.error('Fatal Error: Could not read package.json.');
|
||||
console.error(`Error: ${error.message}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const pkg = loadPackageInfo();
|
||||
|
||||
export function run(argv) {
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('ccw')
|
||||
.description('Claude Code Workflow CLI - Dashboard and workflow tools')
|
||||
.version(pkg.version);
|
||||
|
||||
// View command (server mode with live path switching)
|
||||
program
|
||||
.command('view')
|
||||
.description('Open workflow dashboard server with live path switching')
|
||||
.option('-p, --path <path>', 'Path to project directory', '.')
|
||||
.option('--port <port>', 'Server port', '3456')
|
||||
.option('--no-browser', 'Start server without opening browser')
|
||||
.action(viewCommand);
|
||||
|
||||
// Serve command (alias for view)
|
||||
program
|
||||
.command('serve')
|
||||
.description('Alias for view command')
|
||||
.option('-p, --path <path>', 'Initial project directory')
|
||||
.option('--port <port>', 'Server port', '3456')
|
||||
.option('--no-browser', 'Start server without opening browser')
|
||||
.action(serveCommand);
|
||||
|
||||
// Install command
|
||||
program
|
||||
.command('install')
|
||||
.description('Install Claude Code Workflow to your system')
|
||||
.option('-m, --mode <mode>', 'Installation mode: Global or Path')
|
||||
.option('-p, --path <path>', 'Installation path (for Path mode)')
|
||||
.option('-f, --force', 'Force installation without prompts')
|
||||
.action(installCommand);
|
||||
|
||||
// Uninstall command
|
||||
program
|
||||
.command('uninstall')
|
||||
.description('Uninstall Claude Code Workflow')
|
||||
.action(uninstallCommand);
|
||||
|
||||
// Upgrade command
|
||||
program
|
||||
.command('upgrade')
|
||||
.description('Upgrade Claude Code Workflow installations')
|
||||
.option('-a, --all', 'Upgrade all installations without prompting')
|
||||
.action(upgradeCommand);
|
||||
|
||||
// List command
|
||||
program
|
||||
.command('list')
|
||||
.description('List all installed Claude Code Workflow instances')
|
||||
.action(listCommand);
|
||||
|
||||
program.parse(argv);
|
||||
}
|
||||
324
ccw/src/commands/install.js
Normal file
324
ccw/src/commands/install.js
Normal file
@@ -0,0 +1,324 @@
|
||||
import { existsSync, mkdirSync, readdirSync, statSync, copyFileSync, readFileSync, writeFileSync } from 'fs';
|
||||
import { join, dirname, basename } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { fileURLToPath } from 'url';
|
||||
import inquirer from 'inquirer';
|
||||
import chalk from 'chalk';
|
||||
import { showHeader, createSpinner, info, warning, error, summaryBox, divider } from '../utils/ui.js';
|
||||
import { createManifest, addFileEntry, addDirectoryEntry, saveManifest, findManifest, getAllManifests } from '../core/manifest.js';
|
||||
import { validatePath } from '../utils/path-resolver.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Source directories to install
|
||||
const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen'];
|
||||
|
||||
// Get package root directory (ccw/src/commands -> ccw)
|
||||
function getPackageRoot() {
|
||||
return join(__dirname, '..', '..');
|
||||
}
|
||||
|
||||
// Get source installation directory (parent of ccw)
|
||||
function getSourceDir() {
|
||||
return join(getPackageRoot(), '..');
|
||||
}
|
||||
|
||||
/**
|
||||
* Install command handler
|
||||
* @param {Object} options - Command options
|
||||
*/
|
||||
export async function installCommand(options) {
|
||||
const version = getVersion();
|
||||
|
||||
// Show beautiful header
|
||||
showHeader(version);
|
||||
|
||||
// Check for existing installations
|
||||
const existingManifests = getAllManifests();
|
||||
if (existingManifests.length > 0 && !options.force) {
|
||||
info('Existing installations detected:');
|
||||
console.log('');
|
||||
existingManifests.forEach((m, i) => {
|
||||
console.log(chalk.gray(` ${i + 1}. ${m.installation_mode} - ${m.installation_path}`));
|
||||
console.log(chalk.gray(` Installed: ${new Date(m.installation_date).toLocaleDateString()}`));
|
||||
});
|
||||
console.log('');
|
||||
|
||||
const { proceed } = await inquirer.prompt([{
|
||||
type: 'confirm',
|
||||
name: 'proceed',
|
||||
message: 'Continue with new installation?',
|
||||
default: true
|
||||
}]);
|
||||
|
||||
if (!proceed) {
|
||||
info('Installation cancelled');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Local installation from package source
|
||||
const sourceDir = getSourceDir();
|
||||
|
||||
// Interactive mode selection
|
||||
const mode = options.mode || await selectMode();
|
||||
|
||||
let installPath;
|
||||
if (mode === 'Global') {
|
||||
installPath = homedir();
|
||||
info(`Global installation to: ${installPath}`);
|
||||
} else {
|
||||
const inputPath = options.path || await selectPath();
|
||||
|
||||
// Validate the installation path
|
||||
const pathValidation = validatePath(inputPath, { mustExist: true });
|
||||
if (!pathValidation.valid) {
|
||||
error(`Invalid installation path: ${pathValidation.error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
installPath = pathValidation.path;
|
||||
info(`Path installation to: ${installPath}`);
|
||||
}
|
||||
|
||||
// Validate source directories exist
|
||||
const availableDirs = SOURCE_DIRS.filter(dir => existsSync(join(sourceDir, dir)));
|
||||
|
||||
if (availableDirs.length === 0) {
|
||||
error('No source directories found to install.');
|
||||
error(`Expected directories in: ${sourceDir}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
info(`Found ${availableDirs.length} directories to install: ${availableDirs.join(', ')}`);
|
||||
divider();
|
||||
|
||||
// Check for existing installation at target path
|
||||
const existingManifest = findManifest(installPath, mode);
|
||||
if (existingManifest) {
|
||||
warning('Existing installation found at this location');
|
||||
const { backup } = await inquirer.prompt([{
|
||||
type: 'confirm',
|
||||
name: 'backup',
|
||||
message: 'Create backup before reinstalling?',
|
||||
default: true
|
||||
}]);
|
||||
|
||||
if (backup) {
|
||||
await createBackup(installPath, existingManifest);
|
||||
}
|
||||
}
|
||||
|
||||
// Create manifest
|
||||
const manifest = createManifest(mode, installPath);
|
||||
|
||||
// Perform installation
|
||||
console.log('');
|
||||
const spinner = createSpinner('Installing files...').start();
|
||||
|
||||
let totalFiles = 0;
|
||||
let totalDirs = 0;
|
||||
|
||||
try {
|
||||
for (const dir of availableDirs) {
|
||||
const srcPath = join(sourceDir, dir);
|
||||
const destPath = join(installPath, dir);
|
||||
|
||||
spinner.text = `Installing ${dir}...`;
|
||||
|
||||
const { files, directories } = await copyDirectory(srcPath, destPath, manifest);
|
||||
totalFiles += files;
|
||||
totalDirs += directories;
|
||||
}
|
||||
|
||||
// Copy CLAUDE.md to .claude directory
|
||||
const claudeMdSrc = join(sourceDir, 'CLAUDE.md');
|
||||
const claudeMdDest = join(installPath, '.claude', 'CLAUDE.md');
|
||||
if (existsSync(claudeMdSrc) && existsSync(dirname(claudeMdDest))) {
|
||||
spinner.text = 'Installing CLAUDE.md...';
|
||||
copyFileSync(claudeMdSrc, claudeMdDest);
|
||||
addFileEntry(manifest, claudeMdDest);
|
||||
totalFiles++;
|
||||
}
|
||||
|
||||
// Create version.json
|
||||
const versionPath = join(installPath, '.claude', 'version.json');
|
||||
if (existsSync(dirname(versionPath))) {
|
||||
const versionData = {
|
||||
version: version,
|
||||
installedAt: new Date().toISOString(),
|
||||
mode: mode,
|
||||
installer: 'ccw'
|
||||
};
|
||||
writeFileSync(versionPath, JSON.stringify(versionData, null, 2), 'utf8');
|
||||
addFileEntry(manifest, versionPath);
|
||||
totalFiles++;
|
||||
}
|
||||
|
||||
spinner.succeed('Installation complete!');
|
||||
|
||||
} catch (err) {
|
||||
spinner.fail('Installation failed');
|
||||
error(err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Save manifest
|
||||
const manifestPath = saveManifest(manifest);
|
||||
|
||||
// Show summary
|
||||
console.log('');
|
||||
const summaryLines = [
|
||||
chalk.green.bold('✓ Installation Successful'),
|
||||
'',
|
||||
chalk.white(`Mode: ${chalk.cyan(mode)}`),
|
||||
chalk.white(`Path: ${chalk.cyan(installPath)}`),
|
||||
chalk.white(`Version: ${chalk.cyan(version)}`),
|
||||
'',
|
||||
chalk.gray(`Files installed: ${totalFiles}`),
|
||||
chalk.gray(`Directories created: ${totalDirs}`),
|
||||
'',
|
||||
chalk.gray(`Manifest: ${basename(manifestPath)}`)
|
||||
];
|
||||
|
||||
summaryBox({
|
||||
title: ' Installation Summary ',
|
||||
lines: summaryLines,
|
||||
borderColor: 'green'
|
||||
});
|
||||
|
||||
// Show next steps
|
||||
console.log('');
|
||||
info('Next steps:');
|
||||
console.log(chalk.gray(' 1. Restart Claude Code or your IDE'));
|
||||
console.log(chalk.gray(' 2. Run: ccw view - to open the workflow dashboard'));
|
||||
console.log(chalk.gray(' 3. Run: ccw uninstall - to remove this installation'));
|
||||
console.log('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive mode selection
|
||||
* @returns {Promise<string>} - Selected mode
|
||||
*/
|
||||
async function selectMode() {
|
||||
const { mode } = await inquirer.prompt([{
|
||||
type: 'list',
|
||||
name: 'mode',
|
||||
message: 'Select installation mode:',
|
||||
choices: [
|
||||
{
|
||||
name: `${chalk.cyan('Global')} - Install to home directory (recommended)`,
|
||||
value: 'Global'
|
||||
},
|
||||
{
|
||||
name: `${chalk.yellow('Path')} - Install to specific project path`,
|
||||
value: 'Path'
|
||||
}
|
||||
]
|
||||
}]);
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interactive path selection
|
||||
* @returns {Promise<string>} - Selected path
|
||||
*/
|
||||
async function selectPath() {
|
||||
const { path } = await inquirer.prompt([{
|
||||
type: 'input',
|
||||
name: 'path',
|
||||
message: 'Enter installation path:',
|
||||
default: process.cwd(),
|
||||
validate: (input) => {
|
||||
if (!input) return 'Path is required';
|
||||
if (!existsSync(input)) {
|
||||
return `Path does not exist: ${input}`;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}]);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create backup of existing installation
|
||||
* @param {string} installPath - Installation path
|
||||
* @param {Object} manifest - Existing manifest
|
||||
*/
|
||||
async function createBackup(installPath, manifest) {
|
||||
const spinner = createSpinner('Creating backup...').start();
|
||||
|
||||
try {
|
||||
const timestamp = new Date().toISOString().replace(/[-:]/g, '').replace('T', '-').split('.')[0];
|
||||
const backupDir = join(installPath, `.claude-backup-${timestamp}`);
|
||||
|
||||
mkdirSync(backupDir, { recursive: true });
|
||||
|
||||
// Copy existing .claude directory
|
||||
const claudeDir = join(installPath, '.claude');
|
||||
if (existsSync(claudeDir)) {
|
||||
await copyDirectory(claudeDir, join(backupDir, '.claude'));
|
||||
}
|
||||
|
||||
spinner.succeed(`Backup created: ${backupDir}`);
|
||||
} catch (err) {
|
||||
spinner.warn(`Backup failed: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy directory recursively
|
||||
* @param {string} src - Source directory
|
||||
* @param {string} dest - Destination directory
|
||||
* @param {Object} manifest - Manifest to track files (optional)
|
||||
* @returns {Object} - Count of files and directories
|
||||
*/
|
||||
async function copyDirectory(src, dest, manifest = null) {
|
||||
let files = 0;
|
||||
let directories = 0;
|
||||
|
||||
// Create destination directory
|
||||
if (!existsSync(dest)) {
|
||||
mkdirSync(dest, { recursive: true });
|
||||
directories++;
|
||||
if (manifest) addDirectoryEntry(manifest, dest);
|
||||
}
|
||||
|
||||
const entries = readdirSync(src);
|
||||
|
||||
for (const entry of entries) {
|
||||
const srcPath = join(src, entry);
|
||||
const destPath = join(dest, entry);
|
||||
const stat = statSync(srcPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
const result = await copyDirectory(srcPath, destPath, manifest);
|
||||
files += result.files;
|
||||
directories += result.directories;
|
||||
} else {
|
||||
copyFileSync(srcPath, destPath);
|
||||
files++;
|
||||
if (manifest) addFileEntry(manifest, destPath);
|
||||
}
|
||||
}
|
||||
|
||||
return { files, directories };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get package version
|
||||
* @returns {string} - Version string
|
||||
*/
|
||||
function getVersion() {
|
||||
try {
|
||||
const pkgPath = join(getPackageRoot(), 'package.json');
|
||||
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
||||
return pkg.version || '1.0.0';
|
||||
} catch {
|
||||
return '1.0.0';
|
||||
}
|
||||
}
|
||||
37
ccw/src/commands/list.js
Normal file
37
ccw/src/commands/list.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import chalk from 'chalk';
|
||||
import { showBanner, divider, info } from '../utils/ui.js';
|
||||
import { getAllManifests } from '../core/manifest.js';
|
||||
|
||||
/**
|
||||
* List command handler - shows all installations
|
||||
*/
|
||||
export async function listCommand() {
|
||||
showBanner();
|
||||
console.log(chalk.cyan.bold(' Installed Claude Code Workflow Instances\n'));
|
||||
|
||||
const manifests = getAllManifests();
|
||||
|
||||
if (manifests.length === 0) {
|
||||
info('No installations found.');
|
||||
console.log('');
|
||||
console.log(chalk.gray(' Run: ccw install - to install Claude Code Workflow'));
|
||||
console.log('');
|
||||
return;
|
||||
}
|
||||
|
||||
manifests.forEach((m, i) => {
|
||||
const modeColor = m.installation_mode === 'Global' ? chalk.cyan : chalk.yellow;
|
||||
|
||||
console.log(chalk.white.bold(` ${i + 1}. `) + modeColor.bold(m.installation_mode));
|
||||
console.log(chalk.gray(` Path: ${m.installation_path}`));
|
||||
console.log(chalk.gray(` Date: ${new Date(m.installation_date).toLocaleDateString()}`));
|
||||
console.log(chalk.gray(` Version: ${m.application_version}`));
|
||||
console.log(chalk.gray(` Files: ${m.files_count}`));
|
||||
console.log(chalk.gray(` Dirs: ${m.directories_count}`));
|
||||
console.log('');
|
||||
});
|
||||
|
||||
divider();
|
||||
console.log(chalk.gray(' Run: ccw uninstall - to remove an installation'));
|
||||
console.log('');
|
||||
}
|
||||
67
ccw/src/commands/serve.js
Normal file
67
ccw/src/commands/serve.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import { startServer } from '../core/server.js';
|
||||
import { launchBrowser } from '../utils/browser-launcher.js';
|
||||
import { resolvePath, validatePath } from '../utils/path-resolver.js';
|
||||
import chalk from 'chalk';
|
||||
|
||||
/**
|
||||
* Serve command handler - starts dashboard server with live path switching
|
||||
* @param {Object} options - Command options
|
||||
*/
|
||||
export async function serveCommand(options) {
|
||||
const port = options.port || 3456;
|
||||
|
||||
// Validate project path
|
||||
let initialPath = process.cwd();
|
||||
if (options.path) {
|
||||
const pathValidation = validatePath(options.path, { mustExist: true });
|
||||
if (!pathValidation.valid) {
|
||||
console.error(chalk.red(`\n Error: ${pathValidation.error}\n`));
|
||||
process.exit(1);
|
||||
}
|
||||
initialPath = pathValidation.path;
|
||||
}
|
||||
|
||||
console.log(chalk.blue.bold('\n CCW Dashboard Server\n'));
|
||||
console.log(chalk.gray(` Initial project: ${initialPath}`));
|
||||
console.log(chalk.gray(` Port: ${port}\n`));
|
||||
|
||||
try {
|
||||
// Start server
|
||||
console.log(chalk.cyan(' Starting server...'));
|
||||
const server = await startServer({ port, initialPath });
|
||||
|
||||
const url = `http://localhost:${port}`;
|
||||
console.log(chalk.green(` Server running at ${url}`));
|
||||
|
||||
// Open browser
|
||||
if (options.browser !== false) {
|
||||
console.log(chalk.cyan(' Opening in browser...'));
|
||||
try {
|
||||
await launchBrowser(url);
|
||||
console.log(chalk.green.bold('\n Dashboard opened in browser!'));
|
||||
} catch (err) {
|
||||
console.log(chalk.yellow(`\n Could not open browser: ${err.message}`));
|
||||
console.log(chalk.gray(` Open manually: ${url}`));
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.gray('\n Press Ctrl+C to stop the server\n'));
|
||||
|
||||
// Handle graceful shutdown
|
||||
process.on('SIGINT', () => {
|
||||
console.log(chalk.yellow('\n Shutting down server...'));
|
||||
server.close(() => {
|
||||
console.log(chalk.green(' Server stopped.\n'));
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`\n Error: ${error.message}\n`));
|
||||
if (error.code === 'EADDRINUSE') {
|
||||
console.error(chalk.yellow(` Port ${port} is already in use.`));
|
||||
console.error(chalk.gray(` Try a different port: ccw serve --port ${port + 1}\n`));
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
238
ccw/src/commands/uninstall.js
Normal file
238
ccw/src/commands/uninstall.js
Normal file
@@ -0,0 +1,238 @@
|
||||
import { existsSync, unlinkSync, rmdirSync, readdirSync, statSync } from 'fs';
|
||||
import { join, dirname, basename } from 'path';
|
||||
import inquirer from 'inquirer';
|
||||
import chalk from 'chalk';
|
||||
import { showBanner, createSpinner, success, info, warning, error, summaryBox, divider } from '../utils/ui.js';
|
||||
import { getAllManifests, deleteManifest } from '../core/manifest.js';
|
||||
|
||||
/**
|
||||
* Uninstall command handler
|
||||
* @param {Object} options - Command options
|
||||
*/
|
||||
export async function uninstallCommand(options) {
|
||||
showBanner();
|
||||
console.log(chalk.cyan.bold(' Uninstall Claude Code Workflow\n'));
|
||||
|
||||
// Get all manifests
|
||||
const manifests = getAllManifests();
|
||||
|
||||
if (manifests.length === 0) {
|
||||
warning('No installations found.');
|
||||
info('Nothing to uninstall.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Display installations
|
||||
console.log(chalk.white.bold(' Found installations:\n'));
|
||||
|
||||
manifests.forEach((m, i) => {
|
||||
const modeColor = m.installation_mode === 'Global' ? chalk.cyan : chalk.yellow;
|
||||
console.log(chalk.white(` ${i + 1}. `) + modeColor.bold(m.installation_mode));
|
||||
console.log(chalk.gray(` Path: ${m.installation_path}`));
|
||||
console.log(chalk.gray(` Date: ${new Date(m.installation_date).toLocaleDateString()}`));
|
||||
console.log(chalk.gray(` Version: ${m.application_version}`));
|
||||
console.log(chalk.gray(` Files: ${m.files_count} | Dirs: ${m.directories_count}`));
|
||||
console.log('');
|
||||
});
|
||||
|
||||
divider();
|
||||
|
||||
// Select installation to uninstall
|
||||
let selectedManifest;
|
||||
|
||||
if (manifests.length === 1) {
|
||||
const { confirm } = await inquirer.prompt([{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: `Uninstall ${manifests[0].installation_mode} installation at ${manifests[0].installation_path}?`,
|
||||
default: false
|
||||
}]);
|
||||
|
||||
if (!confirm) {
|
||||
info('Uninstall cancelled');
|
||||
return;
|
||||
}
|
||||
|
||||
selectedManifest = manifests[0];
|
||||
} else {
|
||||
const choices = manifests.map((m, i) => ({
|
||||
name: `${m.installation_mode} - ${m.installation_path}`,
|
||||
value: i
|
||||
}));
|
||||
|
||||
choices.push({ name: chalk.gray('Cancel'), value: -1 });
|
||||
|
||||
const { selection } = await inquirer.prompt([{
|
||||
type: 'list',
|
||||
name: 'selection',
|
||||
message: 'Select installation to uninstall:',
|
||||
choices
|
||||
}]);
|
||||
|
||||
if (selection === -1) {
|
||||
info('Uninstall cancelled');
|
||||
return;
|
||||
}
|
||||
|
||||
selectedManifest = manifests[selection];
|
||||
|
||||
// Confirm selection
|
||||
const { confirm } = await inquirer.prompt([{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: `Are you sure you want to uninstall ${selectedManifest.installation_mode} installation?`,
|
||||
default: false
|
||||
}]);
|
||||
|
||||
if (!confirm) {
|
||||
info('Uninstall cancelled');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// Perform uninstallation
|
||||
const spinner = createSpinner('Removing files...').start();
|
||||
|
||||
let removedFiles = 0;
|
||||
let removedDirs = 0;
|
||||
let failedFiles = [];
|
||||
|
||||
try {
|
||||
// Remove files first (in reverse order to handle nested files)
|
||||
const files = [...(selectedManifest.files || [])].reverse();
|
||||
|
||||
for (const fileEntry of files) {
|
||||
const filePath = fileEntry.path;
|
||||
spinner.text = `Removing: ${basename(filePath)}`;
|
||||
|
||||
try {
|
||||
if (existsSync(filePath)) {
|
||||
unlinkSync(filePath);
|
||||
removedFiles++;
|
||||
}
|
||||
} catch (err) {
|
||||
failedFiles.push({ path: filePath, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
// Remove directories (in reverse order to remove nested dirs first)
|
||||
const directories = [...(selectedManifest.directories || [])].reverse();
|
||||
|
||||
// Sort by path length (deepest first)
|
||||
directories.sort((a, b) => b.path.length - a.path.length);
|
||||
|
||||
for (const dirEntry of directories) {
|
||||
const dirPath = dirEntry.path;
|
||||
spinner.text = `Removing directory: ${basename(dirPath)}`;
|
||||
|
||||
try {
|
||||
if (existsSync(dirPath)) {
|
||||
// Only remove if empty
|
||||
const contents = readdirSync(dirPath);
|
||||
if (contents.length === 0) {
|
||||
rmdirSync(dirPath);
|
||||
removedDirs++;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignore directory removal errors (might not be empty)
|
||||
}
|
||||
}
|
||||
|
||||
// Try to clean up parent directories if empty
|
||||
const installPath = selectedManifest.installation_path;
|
||||
for (const dir of ['.claude', '.codex', '.gemini', '.qwen']) {
|
||||
const dirPath = join(installPath, dir);
|
||||
try {
|
||||
if (existsSync(dirPath)) {
|
||||
await removeEmptyDirs(dirPath);
|
||||
}
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
spinner.succeed('Uninstall complete!');
|
||||
|
||||
} catch (err) {
|
||||
spinner.fail('Uninstall failed');
|
||||
error(err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete manifest
|
||||
deleteManifest(selectedManifest.manifest_file);
|
||||
|
||||
// Show summary
|
||||
console.log('');
|
||||
|
||||
if (failedFiles.length > 0) {
|
||||
summaryBox({
|
||||
title: ' Uninstall Summary ',
|
||||
lines: [
|
||||
chalk.yellow.bold('⚠ Partially Completed'),
|
||||
'',
|
||||
chalk.white(`Files removed: ${chalk.green(removedFiles)}`),
|
||||
chalk.white(`Directories removed: ${chalk.green(removedDirs)}`),
|
||||
chalk.white(`Failed: ${chalk.red(failedFiles.length)}`),
|
||||
'',
|
||||
chalk.gray('Some files could not be removed.'),
|
||||
chalk.gray('They may be in use or require elevated permissions.'),
|
||||
],
|
||||
borderColor: 'yellow'
|
||||
});
|
||||
|
||||
if (process.env.DEBUG) {
|
||||
console.log('');
|
||||
console.log(chalk.gray('Failed files:'));
|
||||
failedFiles.forEach(f => {
|
||||
console.log(chalk.red(` ${f.path}: ${f.error}`));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
summaryBox({
|
||||
title: ' Uninstall Summary ',
|
||||
lines: [
|
||||
chalk.green.bold('✓ Successfully Uninstalled'),
|
||||
'',
|
||||
chalk.white(`Files removed: ${chalk.green(removedFiles)}`),
|
||||
chalk.white(`Directories removed: ${chalk.green(removedDirs)}`),
|
||||
'',
|
||||
chalk.gray('Manifest removed'),
|
||||
],
|
||||
borderColor: 'green'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively remove empty directories
|
||||
* @param {string} dirPath - Directory path
|
||||
*/
|
||||
async function removeEmptyDirs(dirPath) {
|
||||
if (!existsSync(dirPath)) return;
|
||||
|
||||
const stat = statSync(dirPath);
|
||||
if (!stat.isDirectory()) return;
|
||||
|
||||
let files = readdirSync(dirPath);
|
||||
|
||||
// Recursively check subdirectories
|
||||
for (const file of files) {
|
||||
const filePath = join(dirPath, file);
|
||||
if (statSync(filePath).isDirectory()) {
|
||||
await removeEmptyDirs(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
// Re-check after processing subdirectories
|
||||
files = readdirSync(dirPath);
|
||||
if (files.length === 0) {
|
||||
rmdirSync(dirPath);
|
||||
}
|
||||
}
|
||||
|
||||
307
ccw/src/commands/upgrade.js
Normal file
307
ccw/src/commands/upgrade.js
Normal file
@@ -0,0 +1,307 @@
|
||||
import { existsSync, readdirSync, statSync, copyFileSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
||||
import { join, dirname, basename } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import inquirer from 'inquirer';
|
||||
import chalk from 'chalk';
|
||||
import { showBanner, createSpinner, info, warning, error, summaryBox, divider } from '../utils/ui.js';
|
||||
import { getAllManifests, createManifest, addFileEntry, addDirectoryEntry, saveManifest, deleteManifest } from '../core/manifest.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Source directories to install
|
||||
const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen'];
|
||||
|
||||
// Get package root directory (ccw/src/commands -> ccw)
|
||||
function getPackageRoot() {
|
||||
return join(__dirname, '..', '..');
|
||||
}
|
||||
|
||||
// Get source installation directory (parent of ccw)
|
||||
function getSourceDir() {
|
||||
return join(getPackageRoot(), '..');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get package version
|
||||
* @returns {string} - Version string
|
||||
*/
|
||||
function getVersion() {
|
||||
try {
|
||||
const pkgPath = join(getPackageRoot(), 'package.json');
|
||||
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
||||
return pkg.version || '1.0.0';
|
||||
} catch {
|
||||
return '1.0.0';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade command handler
|
||||
* @param {Object} options - Command options
|
||||
*/
|
||||
export async function upgradeCommand(options) {
|
||||
showBanner();
|
||||
console.log(chalk.cyan.bold(' Upgrade Claude Code Workflow\n'));
|
||||
|
||||
const currentVersion = getVersion();
|
||||
|
||||
// Get all manifests
|
||||
const manifests = getAllManifests();
|
||||
|
||||
if (manifests.length === 0) {
|
||||
warning('No installations found.');
|
||||
info('Run "ccw install" to install first.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Display current installations
|
||||
console.log(chalk.white.bold(' Current installations:\n'));
|
||||
|
||||
const upgradeTargets = [];
|
||||
|
||||
for (let i = 0; i < manifests.length; i++) {
|
||||
const m = manifests[i];
|
||||
const modeColor = m.installation_mode === 'Global' ? chalk.cyan : chalk.yellow;
|
||||
|
||||
// Read installed version
|
||||
const versionFile = join(m.installation_path, '.claude', 'version.json');
|
||||
let installedVersion = 'unknown';
|
||||
|
||||
if (existsSync(versionFile)) {
|
||||
try {
|
||||
const versionData = JSON.parse(readFileSync(versionFile, 'utf8'));
|
||||
installedVersion = versionData.version || 'unknown';
|
||||
} catch {
|
||||
// Ignore parse errors
|
||||
}
|
||||
}
|
||||
|
||||
// Check if upgrade needed
|
||||
const needsUpgrade = installedVersion !== currentVersion;
|
||||
|
||||
console.log(chalk.white(` ${i + 1}. `) + modeColor.bold(m.installation_mode));
|
||||
console.log(chalk.gray(` Path: ${m.installation_path}`));
|
||||
console.log(chalk.gray(` Installed: ${installedVersion}`));
|
||||
|
||||
if (needsUpgrade) {
|
||||
console.log(chalk.green(` Package: ${currentVersion} `) + chalk.green('← Update available'));
|
||||
upgradeTargets.push({ manifest: m, installedVersion, index: i });
|
||||
} else {
|
||||
console.log(chalk.gray(` Up to date ✓`));
|
||||
}
|
||||
console.log('');
|
||||
}
|
||||
|
||||
divider();
|
||||
|
||||
if (upgradeTargets.length === 0) {
|
||||
info('All installations are up to date.');
|
||||
console.log('');
|
||||
info('To upgrade ccw itself, run:');
|
||||
console.log(chalk.cyan(' npm update -g ccw'));
|
||||
console.log('');
|
||||
return;
|
||||
}
|
||||
|
||||
// Select which installations to upgrade
|
||||
let selectedManifests = [];
|
||||
|
||||
if (options.all) {
|
||||
selectedManifests = upgradeTargets.map(t => t.manifest);
|
||||
} else if (upgradeTargets.length === 1) {
|
||||
const target = upgradeTargets[0];
|
||||
const { confirm } = await inquirer.prompt([{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: `Upgrade ${target.manifest.installation_mode} installation (${target.installedVersion} → ${currentVersion})?`,
|
||||
default: true
|
||||
}]);
|
||||
|
||||
if (!confirm) {
|
||||
info('Upgrade cancelled');
|
||||
return;
|
||||
}
|
||||
|
||||
selectedManifests = [target.manifest];
|
||||
} else {
|
||||
const choices = upgradeTargets.map((t, i) => ({
|
||||
name: `${t.manifest.installation_mode} - ${t.manifest.installation_path} (${t.installedVersion} → ${currentVersion})`,
|
||||
value: i,
|
||||
checked: true
|
||||
}));
|
||||
|
||||
const { selections } = await inquirer.prompt([{
|
||||
type: 'checkbox',
|
||||
name: 'selections',
|
||||
message: 'Select installations to upgrade:',
|
||||
choices
|
||||
}]);
|
||||
|
||||
if (selections.length === 0) {
|
||||
info('No installations selected');
|
||||
return;
|
||||
}
|
||||
|
||||
selectedManifests = selections.map(i => upgradeTargets[i].manifest);
|
||||
}
|
||||
|
||||
// Perform upgrades
|
||||
console.log('');
|
||||
const results = [];
|
||||
const sourceDir = getSourceDir();
|
||||
|
||||
for (const manifest of selectedManifests) {
|
||||
const upgradeSpinner = createSpinner(`Upgrading ${manifest.installation_mode} at ${manifest.installation_path}...`).start();
|
||||
|
||||
try {
|
||||
const result = await performUpgrade(manifest, sourceDir, currentVersion);
|
||||
upgradeSpinner.succeed(`Upgraded ${manifest.installation_mode}: ${result.files} files`);
|
||||
results.push({ manifest, success: true, ...result });
|
||||
} catch (err) {
|
||||
upgradeSpinner.fail(`Failed to upgrade ${manifest.installation_mode}`);
|
||||
error(err.message);
|
||||
results.push({ manifest, success: false, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
// Show summary
|
||||
console.log('');
|
||||
|
||||
const successCount = results.filter(r => r.success).length;
|
||||
const failCount = results.filter(r => !r.success).length;
|
||||
|
||||
const summaryLines = [
|
||||
successCount === results.length
|
||||
? chalk.green.bold('✓ Upgrade Successful')
|
||||
: chalk.yellow.bold('⚠ Upgrade Completed with Issues'),
|
||||
'',
|
||||
chalk.white(`Version: ${chalk.cyan(currentVersion)}`),
|
||||
''
|
||||
];
|
||||
|
||||
if (successCount > 0) {
|
||||
summaryLines.push(chalk.green(`Upgraded: ${successCount} installation(s)`));
|
||||
}
|
||||
if (failCount > 0) {
|
||||
summaryLines.push(chalk.red(`Failed: ${failCount} installation(s)`));
|
||||
}
|
||||
|
||||
summaryBox({
|
||||
title: ' Upgrade Summary ',
|
||||
lines: summaryLines,
|
||||
borderColor: failCount > 0 ? 'yellow' : 'green'
|
||||
});
|
||||
|
||||
// Show next steps
|
||||
console.log('');
|
||||
info('Next steps:');
|
||||
console.log(chalk.gray(' 1. Restart Claude Code or your IDE'));
|
||||
console.log(chalk.gray(' 2. Run: ccw view - to open the workflow dashboard'));
|
||||
console.log('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform upgrade for a single installation
|
||||
* @param {Object} manifest - Installation manifest
|
||||
* @param {string} sourceDir - Source directory
|
||||
* @param {string} version - Version string
|
||||
* @returns {Promise<Object>} - Upgrade result
|
||||
*/
|
||||
async function performUpgrade(manifest, sourceDir, version) {
|
||||
const installPath = manifest.installation_path;
|
||||
|
||||
// Get available source directories
|
||||
const availableDirs = SOURCE_DIRS.filter(dir => existsSync(join(sourceDir, dir)));
|
||||
|
||||
if (availableDirs.length === 0) {
|
||||
throw new Error('No source directories found');
|
||||
}
|
||||
|
||||
// Create new manifest
|
||||
const newManifest = createManifest(manifest.installation_mode, installPath);
|
||||
|
||||
let totalFiles = 0;
|
||||
let totalDirs = 0;
|
||||
|
||||
// Copy each directory
|
||||
for (const dir of availableDirs) {
|
||||
const srcPath = join(sourceDir, dir);
|
||||
const destPath = join(installPath, dir);
|
||||
|
||||
const { files, directories } = await copyDirectory(srcPath, destPath, newManifest);
|
||||
totalFiles += files;
|
||||
totalDirs += directories;
|
||||
}
|
||||
|
||||
// Copy CLAUDE.md to .claude directory
|
||||
const claudeMdSrc = join(sourceDir, 'CLAUDE.md');
|
||||
const claudeMdDest = join(installPath, '.claude', 'CLAUDE.md');
|
||||
if (existsSync(claudeMdSrc) && existsSync(dirname(claudeMdDest))) {
|
||||
copyFileSync(claudeMdSrc, claudeMdDest);
|
||||
addFileEntry(newManifest, claudeMdDest);
|
||||
totalFiles++;
|
||||
}
|
||||
|
||||
// Update version.json
|
||||
const versionPath = join(installPath, '.claude', 'version.json');
|
||||
if (existsSync(dirname(versionPath))) {
|
||||
const versionData = {
|
||||
version: version,
|
||||
installedAt: new Date().toISOString(),
|
||||
upgradedAt: new Date().toISOString(),
|
||||
mode: manifest.installation_mode,
|
||||
installer: 'ccw'
|
||||
};
|
||||
writeFileSync(versionPath, JSON.stringify(versionData, null, 2), 'utf8');
|
||||
addFileEntry(newManifest, versionPath);
|
||||
totalFiles++;
|
||||
}
|
||||
|
||||
// Delete old manifest and save new one
|
||||
if (manifest.manifest_file) {
|
||||
deleteManifest(manifest.manifest_file);
|
||||
}
|
||||
saveManifest(newManifest);
|
||||
|
||||
return { files: totalFiles, directories: totalDirs };
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy directory recursively
|
||||
* @param {string} src - Source directory
|
||||
* @param {string} dest - Destination directory
|
||||
* @param {Object} manifest - Manifest to track files
|
||||
* @returns {Object} - Count of files and directories
|
||||
*/
|
||||
async function copyDirectory(src, dest, manifest) {
|
||||
let files = 0;
|
||||
let directories = 0;
|
||||
|
||||
// Create destination directory
|
||||
if (!existsSync(dest)) {
|
||||
mkdirSync(dest, { recursive: true });
|
||||
directories++;
|
||||
addDirectoryEntry(manifest, dest);
|
||||
}
|
||||
|
||||
const entries = readdirSync(src);
|
||||
|
||||
for (const entry of entries) {
|
||||
const srcPath = join(src, entry);
|
||||
const destPath = join(dest, entry);
|
||||
const stat = statSync(srcPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
const result = await copyDirectory(srcPath, destPath, manifest);
|
||||
files += result.files;
|
||||
directories += result.directories;
|
||||
} else {
|
||||
copyFileSync(srcPath, destPath);
|
||||
files++;
|
||||
addFileEntry(manifest, destPath);
|
||||
}
|
||||
}
|
||||
|
||||
return { files, directories };
|
||||
}
|
||||
14
ccw/src/commands/view.js
Normal file
14
ccw/src/commands/view.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { serveCommand } from './serve.js';
|
||||
|
||||
/**
|
||||
* View command handler - starts dashboard server (unified with serve mode)
|
||||
* @param {Object} options - Command options
|
||||
*/
|
||||
export async function viewCommand(options) {
|
||||
// Forward to serve command with same options
|
||||
await serveCommand({
|
||||
path: options.path,
|
||||
port: options.port || 3456,
|
||||
browser: options.browser
|
||||
});
|
||||
}
|
||||
29
ccw/src/core/dashboard-generator-patch.js
Normal file
29
ccw/src/core/dashboard-generator-patch.js
Normal file
@@ -0,0 +1,29 @@
|
||||
// Add after line 13 (after REVIEW_TEMPLATE constant)
|
||||
|
||||
// Modular dashboard JS files (in dependency order)
|
||||
const MODULE_FILES = [
|
||||
// Base (no dependencies)
|
||||
'dashboard-js/state.js',
|
||||
'dashboard-js/utils.js',
|
||||
'dashboard-js/api.js',
|
||||
// Components (independent)
|
||||
'dashboard-js/components/theme.js',
|
||||
'dashboard-js/components/sidebar.js',
|
||||
'dashboard-js/components/modals.js',
|
||||
'dashboard-js/components/flowchart.js',
|
||||
// Components (dependent)
|
||||
'dashboard-js/components/task-drawer-renderers.js',
|
||||
'dashboard-js/components/task-drawer-core.js',
|
||||
'dashboard-js/components/tabs-context.js',
|
||||
'dashboard-js/components/tabs-other.js',
|
||||
// Views
|
||||
'dashboard-js/views/home.js',
|
||||
'dashboard-js/views/project-overview.js',
|
||||
'dashboard-js/views/review-session.js',
|
||||
'dashboard-js/views/fix-session.js',
|
||||
'dashboard-js/views/lite-tasks.js',
|
||||
'dashboard-js/views/session-detail.js',
|
||||
// Navigation & Main
|
||||
'dashboard-js/components/navigation.js',
|
||||
'dashboard-js/main.js'
|
||||
];
|
||||
667
ccw/src/core/dashboard-generator.js
Normal file
667
ccw/src/core/dashboard-generator.js
Normal file
@@ -0,0 +1,667 @@
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Bundled template paths
|
||||
const UNIFIED_TEMPLATE = join(__dirname, '../templates/dashboard.html');
|
||||
const JS_FILE = join(__dirname, '../templates/dashboard.js');
|
||||
const CSS_FILE = join(__dirname, '../templates/dashboard.css');
|
||||
const WORKFLOW_TEMPLATE = join(__dirname, '../templates/workflow-dashboard.html');
|
||||
const REVIEW_TEMPLATE = join(__dirname, '../templates/review-cycle-dashboard.html');
|
||||
|
||||
const MODULE_FILES = [
|
||||
'utils.js',
|
||||
'state.js',
|
||||
'api.js',
|
||||
'components/theme.js',
|
||||
'components/modals.js',
|
||||
'components/navigation.js',
|
||||
'components/sidebar.js',
|
||||
'components/tabs-context.js',
|
||||
'components/tabs-other.js',
|
||||
'components/task-drawer-core.js',
|
||||
'components/task-drawer-renderers.js',
|
||||
'components/flowchart.js',
|
||||
'views/home.js',
|
||||
'views/project-overview.js',
|
||||
'views/session-detail.js',
|
||||
'views/review-session.js',
|
||||
'views/lite-tasks.js',
|
||||
'views/fix-session.js',
|
||||
'main.js'
|
||||
];
|
||||
|
||||
/**
|
||||
* Generate dashboard HTML from aggregated data
|
||||
* Uses bundled templates from ccw package
|
||||
* @param {Object} data - Aggregated dashboard data
|
||||
* @returns {Promise<string>} - Generated HTML
|
||||
*/
|
||||
export async function generateDashboard(data) {
|
||||
// Use new unified template (with sidebar layout)
|
||||
if (existsSync(UNIFIED_TEMPLATE)) {
|
||||
return generateFromUnifiedTemplate(data);
|
||||
}
|
||||
|
||||
// Fallback to legacy workflow template
|
||||
if (existsSync(WORKFLOW_TEMPLATE)) {
|
||||
return generateFromBundledTemplate(data, WORKFLOW_TEMPLATE);
|
||||
}
|
||||
|
||||
// Fallback to inline dashboard if templates missing
|
||||
return generateInlineDashboard(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate dashboard using unified template (new sidebar layout)
|
||||
* @param {Object} data - Dashboard data
|
||||
* @returns {string} - Generated HTML
|
||||
*/
|
||||
function generateFromUnifiedTemplate(data) {
|
||||
let html = readFileSync(UNIFIED_TEMPLATE, 'utf8');
|
||||
|
||||
// Read CSS file
|
||||
let cssContent = existsSync(CSS_FILE) ? readFileSync(CSS_FILE, 'utf8') : '';
|
||||
|
||||
// Read JS content
|
||||
let jsContent = '';
|
||||
const moduleBase = join(__dirname, '../templates/dashboard-js');
|
||||
|
||||
if (existsSync(moduleBase)) {
|
||||
jsContent = MODULE_FILES.map(file => {
|
||||
const filePath = join(moduleBase, file);
|
||||
return existsSync(filePath) ? readFileSync(filePath, 'utf8') : '';
|
||||
}).join('\n\n');
|
||||
} else if (existsSync(JS_FILE)) {
|
||||
jsContent = readFileSync(JS_FILE, 'utf8');
|
||||
}
|
||||
|
||||
// Prepare complete workflow data
|
||||
const workflowData = {
|
||||
generatedAt: data.generatedAt || new Date().toISOString(),
|
||||
activeSessions: data.activeSessions || [],
|
||||
archivedSessions: data.archivedSessions || [],
|
||||
liteTasks: data.liteTasks || { litePlan: [], liteFix: [] },
|
||||
reviewData: data.reviewData || { dimensions: {} },
|
||||
statistics: data.statistics || {
|
||||
totalSessions: 0,
|
||||
activeSessions: 0,
|
||||
totalTasks: 0,
|
||||
completedTasks: 0,
|
||||
litePlanCount: 0,
|
||||
liteFixCount: 0
|
||||
}
|
||||
};
|
||||
|
||||
// Get project path and recent paths
|
||||
const projectPath = data.projectPath || process.cwd();
|
||||
const recentPaths = data.recentPaths || [projectPath];
|
||||
|
||||
// Replace JS placeholders with actual data
|
||||
jsContent = jsContent.replace('{{WORKFLOW_DATA}}', JSON.stringify(workflowData, null, 2));
|
||||
jsContent = jsContent.replace(/\{\{PROJECT_PATH\}\}/g, projectPath.replace(/\\/g, '/'));
|
||||
jsContent = jsContent.replace('{{RECENT_PATHS}}', JSON.stringify(recentPaths));
|
||||
|
||||
// Inject JS and CSS into HTML template
|
||||
html = html.replace('{{JS_CONTENT}}', jsContent);
|
||||
html = html.replace('{{CSS_CONTENT}}', cssContent);
|
||||
|
||||
// Also replace any remaining placeholders in HTML
|
||||
html = html.replace(/\{\{PROJECT_PATH\}\}/g, projectPath.replace(/\\/g, '/'));
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate dashboard using bundled template
|
||||
* @param {Object} data - Dashboard data
|
||||
* @param {string} templatePath - Path to workflow-dashboard.html
|
||||
* @returns {string} - Generated HTML
|
||||
*/
|
||||
function generateFromBundledTemplate(data, templatePath) {
|
||||
let html = readFileSync(templatePath, 'utf8');
|
||||
|
||||
// Prepare workflow data for injection
|
||||
const workflowData = {
|
||||
activeSessions: data.activeSessions,
|
||||
archivedSessions: data.archivedSessions
|
||||
};
|
||||
|
||||
// Inject workflow data
|
||||
html = html.replace('{{WORKFLOW_DATA}}', JSON.stringify(workflowData, null, 2));
|
||||
|
||||
// If we have review data, add a review tab
|
||||
if (data.reviewData && data.reviewData.totalFindings > 0) {
|
||||
html = injectReviewTab(html, data.reviewData);
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject review tab into existing dashboard
|
||||
* @param {string} html - Base dashboard HTML
|
||||
* @param {Object} reviewData - Review data to display
|
||||
* @returns {string} - Modified HTML with review tab
|
||||
*/
|
||||
function injectReviewTab(html, reviewData) {
|
||||
// Add review tab button in header controls
|
||||
const tabButtonHtml = `
|
||||
<button class="btn" data-tab="reviews" id="reviewTabBtn">Reviews (${reviewData.totalFindings})</button>
|
||||
`;
|
||||
|
||||
// Insert after filter-group
|
||||
html = html.replace(
|
||||
'</div>\n </div>\n </header>',
|
||||
`</div>
|
||||
<div class="filter-group" style="margin-left: auto;">
|
||||
${tabButtonHtml}
|
||||
</div>
|
||||
</div>
|
||||
</header>`
|
||||
);
|
||||
|
||||
// Add review section before closing container
|
||||
const reviewSectionHtml = generateReviewSection(reviewData);
|
||||
|
||||
html = html.replace(
|
||||
'</div>\n\n <button class="theme-toggle"',
|
||||
`</div>
|
||||
|
||||
${reviewSectionHtml}
|
||||
</div>
|
||||
|
||||
<button class="theme-toggle"`
|
||||
);
|
||||
|
||||
// Add review tab JavaScript
|
||||
const reviewScript = generateReviewScript(reviewData);
|
||||
html = html.replace('</script>', `\n${reviewScript}\n</script>`);
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate review section HTML
|
||||
* @param {Object} reviewData - Review data
|
||||
* @returns {string} - HTML for review section
|
||||
*/
|
||||
function generateReviewSection(reviewData) {
|
||||
const severityBars = Object.entries(reviewData.severityDistribution)
|
||||
.map(([severity, count]) => {
|
||||
const colors = {
|
||||
critical: '#c53030',
|
||||
high: '#f56565',
|
||||
medium: '#ed8936',
|
||||
low: '#48bb78'
|
||||
};
|
||||
const percent = reviewData.totalFindings > 0
|
||||
? Math.round((count / reviewData.totalFindings) * 100)
|
||||
: 0;
|
||||
return `
|
||||
<div class="severity-bar-item">
|
||||
<span class="severity-label">${severity}</span>
|
||||
<div class="severity-bar">
|
||||
<div class="severity-fill" style="width: ${percent}%; background-color: ${colors[severity]}"></div>
|
||||
</div>
|
||||
<span class="severity-count">${count}</span>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
const dimensionCards = Object.entries(reviewData.dimensionSummary)
|
||||
.map(([name, info]) => `
|
||||
<div class="dimension-card">
|
||||
<div class="dimension-name">${name}</div>
|
||||
<div class="dimension-count">${info.count} findings</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
return `
|
||||
<div class="section" id="reviewSectionContainer" style="display: none;">
|
||||
<div class="section-header">
|
||||
<h2 class="section-title">Code Review Findings</h2>
|
||||
</div>
|
||||
|
||||
<div class="review-stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #c53030;">${reviewData.severityDistribution.critical}</div>
|
||||
<div class="stat-label">Critical</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #f56565;">${reviewData.severityDistribution.high}</div>
|
||||
<div class="stat-label">High</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #ed8936;">${reviewData.severityDistribution.medium}</div>
|
||||
<div class="stat-label">Medium</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #48bb78;">${reviewData.severityDistribution.low}</div>
|
||||
<div class="stat-label">Low</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="severity-distribution">
|
||||
<h3 style="margin-bottom: 15px; color: var(--text-secondary);">Severity Distribution</h3>
|
||||
${severityBars}
|
||||
</div>
|
||||
|
||||
<div class="dimensions-grid" style="margin-top: 30px;">
|
||||
<h3 style="margin-bottom: 15px; color: var(--text-secondary);">By Dimension</h3>
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px;">
|
||||
${dimensionCards}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.review-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.severity-distribution {
|
||||
background: var(--bg-card);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.severity-bar-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.severity-label {
|
||||
width: 80px;
|
||||
text-transform: capitalize;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.severity-bar {
|
||||
flex: 1;
|
||||
height: 20px;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.severity-fill {
|
||||
height: 100%;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
.severity-count {
|
||||
width: 40px;
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
}
|
||||
.dimension-card {
|
||||
background: var(--bg-card);
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.dimension-name {
|
||||
font-weight: 600;
|
||||
text-transform: capitalize;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.dimension-count {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.review-stats {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate JavaScript for review tab functionality
|
||||
* @param {Object} reviewData - Review data
|
||||
* @returns {string} - JavaScript code
|
||||
*/
|
||||
function generateReviewScript(reviewData) {
|
||||
return `
|
||||
// Review tab functionality
|
||||
const reviewTabBtn = document.getElementById('reviewTabBtn');
|
||||
const reviewSection = document.getElementById('reviewSectionContainer');
|
||||
const activeSectionContainer = document.getElementById('activeSectionContainer');
|
||||
const archivedSectionContainer = document.getElementById('archivedSectionContainer');
|
||||
|
||||
if (reviewTabBtn) {
|
||||
reviewTabBtn.addEventListener('click', () => {
|
||||
const isActive = reviewTabBtn.classList.contains('active');
|
||||
|
||||
// Toggle review section
|
||||
if (isActive) {
|
||||
// Hide reviews, show workflow
|
||||
reviewTabBtn.classList.remove('active');
|
||||
reviewSection.style.display = 'none';
|
||||
activeSectionContainer.style.display = 'block';
|
||||
archivedSectionContainer.style.display = 'block';
|
||||
} else {
|
||||
// Show reviews, hide workflow
|
||||
reviewTabBtn.classList.add('active');
|
||||
reviewSection.style.display = 'block';
|
||||
activeSectionContainer.style.display = 'none';
|
||||
archivedSectionContainer.style.display = 'none';
|
||||
|
||||
// Reset filter buttons
|
||||
document.querySelectorAll('[data-filter]').forEach(b => b.classList.remove('active'));
|
||||
document.querySelector('[data-filter="all"]').classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate inline dashboard HTML (fallback if bundled templates missing)
|
||||
* @param {Object} data - Dashboard data
|
||||
* @returns {string}
|
||||
*/
|
||||
function generateInlineDashboard(data) {
|
||||
const stats = data.statistics;
|
||||
const hasReviews = data.reviewData && data.reviewData.totalFindings > 0;
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CCW Dashboard</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);
|
||||
}
|
||||
[data-theme="dark"] {
|
||||
--bg-primary: #1a202c;
|
||||
--bg-secondary: #2d3748;
|
||||
--bg-card: #2d3748;
|
||||
--text-primary: #f7fafc;
|
||||
--text-secondary: #a0aec0;
|
||||
--border-color: #4a5568;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
.container { max-width: 1400px; margin: 0 auto; padding: 20px; }
|
||||
header {
|
||||
background: var(--bg-secondary);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow);
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
h1 { font-size: 2rem; color: var(--accent-color); margin-bottom: 10px; }
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.stat-card {
|
||||
background: var(--bg-card);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.stat-value { font-size: 2rem; font-weight: bold; color: var(--accent-color); }
|
||||
.stat-label { color: var(--text-secondary); font-size: 0.9rem; }
|
||||
.section { margin-bottom: 40px; }
|
||||
.section-title { font-size: 1.5rem; margin-bottom: 20px; }
|
||||
.sessions-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
.session-card {
|
||||
background: var(--bg-card);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.session-title { font-size: 1.2rem; font-weight: 600; margin-bottom: 10px; }
|
||||
.session-meta { color: var(--text-secondary); font-size: 0.9rem; }
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 4px;
|
||||
margin: 15px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--accent-color), var(--success-color));
|
||||
}
|
||||
.task-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
margin-bottom: 8px;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 6px;
|
||||
border-left: 3px solid var(--border-color);
|
||||
}
|
||||
.task-item.completed { border-left-color: var(--success-color); opacity: 0.8; }
|
||||
.task-item.in_progress { border-left-color: var(--warning-color); }
|
||||
.task-title { flex: 1; font-size: 0.9rem; }
|
||||
.task-id { font-size: 0.75rem; color: var(--text-secondary); font-family: monospace; }
|
||||
.empty-state { text-align: center; padding: 60px 20px; color: var(--text-secondary); }
|
||||
.tabs { display: flex; gap: 10px; margin-top: 15px; }
|
||||
.tab-btn {
|
||||
padding: 10px 20px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
.tab-btn.active { background: var(--accent-color); color: white; border-color: var(--accent-color); }
|
||||
.theme-toggle {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1.5rem;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>CCW Dashboard</h1>
|
||||
<p style="color: var(--text-secondary);">Workflow Sessions and Reviews</p>
|
||||
<div class="tabs">
|
||||
<button class="tab-btn active" data-tab="workflow">Workflow</button>
|
||||
${hasReviews ? '<button class="tab-btn" data-tab="reviews">Reviews</button>' : ''}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="workflowTab">
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">${stats.totalSessions}</div>
|
||||
<div class="stat-label">Total Sessions</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">${stats.activeSessions}</div>
|
||||
<div class="stat-label">Active Sessions</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">${stats.totalTasks}</div>
|
||||
<div class="stat-label">Total Tasks</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">${stats.completedTasks}</div>
|
||||
<div class="stat-label">Completed Tasks</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">Active Sessions</h2>
|
||||
<div class="sessions-grid" id="activeSessions">
|
||||
${data.activeSessions.length === 0
|
||||
? '<div class="empty-state">No active sessions</div>'
|
||||
: data.activeSessions.map(s => renderSessionCard(s, true)).join('')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">Archived Sessions</h2>
|
||||
<div class="sessions-grid" id="archivedSessions">
|
||||
${data.archivedSessions.length === 0
|
||||
? '<div class="empty-state">No archived sessions</div>'
|
||||
: data.archivedSessions.map(s => renderSessionCard(s, false)).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${hasReviews ? renderReviewTab(data.reviewData) : ''}
|
||||
</div>
|
||||
|
||||
<button class="theme-toggle" onclick="toggleTheme()">🌙</button>
|
||||
|
||||
<script>
|
||||
function toggleTheme() {
|
||||
const html = document.documentElement;
|
||||
const current = html.getAttribute('data-theme');
|
||||
const next = current === 'dark' ? 'light' : 'dark';
|
||||
html.setAttribute('data-theme', next);
|
||||
localStorage.setItem('theme', next);
|
||||
document.querySelector('.theme-toggle').textContent = next === 'dark' ? '☀️' : '🌙';
|
||||
}
|
||||
|
||||
// Initialize theme
|
||||
const savedTheme = localStorage.getItem('theme') || 'light';
|
||||
document.documentElement.setAttribute('data-theme', savedTheme);
|
||||
document.querySelector('.theme-toggle').textContent = savedTheme === 'dark' ? '☀️' : '🌙';
|
||||
|
||||
// Tab switching
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
|
||||
const tab = btn.dataset.tab;
|
||||
document.getElementById('workflowTab').style.display = tab === 'workflow' ? 'block' : 'none';
|
||||
const reviewTab = document.getElementById('reviewsTab');
|
||||
if (reviewTab) reviewTab.style.display = tab === 'reviews' ? 'block' : 'none';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a session card
|
||||
* @param {Object} session - Session data
|
||||
* @param {boolean} isActive - Whether session is active
|
||||
* @returns {string} - HTML string
|
||||
*/
|
||||
function renderSessionCard(session, isActive) {
|
||||
const completedTasks = isActive
|
||||
? session.tasks.filter(t => t.status === 'completed').length
|
||||
: session.taskCount;
|
||||
const totalTasks = isActive ? session.tasks.length : session.taskCount;
|
||||
const progress = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
|
||||
|
||||
const tasksHtml = isActive && session.tasks.length > 0
|
||||
? session.tasks.map(t => `
|
||||
<div class="task-item ${t.status}">
|
||||
<div class="task-title">${t.title}</div>
|
||||
<span class="task-id">${t.task_id}</span>
|
||||
</div>
|
||||
`).join('')
|
||||
: '';
|
||||
|
||||
return `
|
||||
<div class="session-card">
|
||||
<div class="session-title">${session.session_id}</div>
|
||||
<div class="session-meta">
|
||||
${session.project ? `<div>${session.project}</div>` : ''}
|
||||
<div>${session.created_at} | ${completedTasks}/${totalTasks} tasks</div>
|
||||
</div>
|
||||
${totalTasks > 0 ? `
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: ${progress}%"></div>
|
||||
</div>
|
||||
` : ''}
|
||||
${tasksHtml}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render review tab HTML
|
||||
* @param {Object} reviewData - Review data
|
||||
* @returns {string} - HTML string
|
||||
*/
|
||||
function renderReviewTab(reviewData) {
|
||||
const { severityDistribution, dimensionSummary } = reviewData;
|
||||
|
||||
return `
|
||||
<div id="reviewsTab" style="display: none;">
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #c53030;">${severityDistribution.critical}</div>
|
||||
<div class="stat-label">Critical</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #f56565;">${severityDistribution.high}</div>
|
||||
<div class="stat-label">High</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #ed8936;">${severityDistribution.medium}</div>
|
||||
<div class="stat-label">Medium</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" style="color: #48bb78;">${severityDistribution.low}</div>
|
||||
<div class="stat-label">Low</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">Findings by Dimension</h2>
|
||||
<div class="sessions-grid">
|
||||
${Object.entries(dimensionSummary).map(([name, info]) => `
|
||||
<div class="session-card">
|
||||
<div class="session-title" style="text-transform: capitalize;">${name}</div>
|
||||
<div class="session-meta">${info.count} findings</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
409
ccw/src/core/data-aggregator.js
Normal file
409
ccw/src/core/data-aggregator.js
Normal file
@@ -0,0 +1,409 @@
|
||||
import { glob } from 'glob';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { join, basename } from 'path';
|
||||
import { scanLiteTasks } from './lite-scanner.js';
|
||||
|
||||
/**
|
||||
* Aggregate all data for dashboard rendering
|
||||
* @param {Object} sessions - Scanned sessions from session-scanner
|
||||
* @param {string} workflowDir - Path to .workflow directory
|
||||
* @returns {Promise<Object>} - Aggregated dashboard data
|
||||
*/
|
||||
export async function aggregateData(sessions, workflowDir) {
|
||||
const data = {
|
||||
generatedAt: new Date().toISOString(),
|
||||
activeSessions: [],
|
||||
archivedSessions: [],
|
||||
liteTasks: {
|
||||
litePlan: [],
|
||||
liteFix: []
|
||||
},
|
||||
reviewData: null,
|
||||
projectOverview: null,
|
||||
statistics: {
|
||||
totalSessions: 0,
|
||||
activeSessions: 0,
|
||||
totalTasks: 0,
|
||||
completedTasks: 0,
|
||||
reviewFindings: 0,
|
||||
litePlanCount: 0,
|
||||
liteFixCount: 0
|
||||
}
|
||||
};
|
||||
|
||||
// Process active sessions
|
||||
for (const session of sessions.active) {
|
||||
const sessionData = await processSession(session, true);
|
||||
data.activeSessions.push(sessionData);
|
||||
data.statistics.totalTasks += sessionData.tasks.length;
|
||||
data.statistics.completedTasks += sessionData.tasks.filter(t => t.status === 'completed').length;
|
||||
}
|
||||
|
||||
// Process archived sessions
|
||||
for (const session of sessions.archived) {
|
||||
const sessionData = await processSession(session, false);
|
||||
data.archivedSessions.push(sessionData);
|
||||
data.statistics.totalTasks += sessionData.taskCount || 0;
|
||||
data.statistics.completedTasks += sessionData.taskCount || 0;
|
||||
}
|
||||
|
||||
// Aggregate review data if present
|
||||
if (sessions.hasReviewData) {
|
||||
data.reviewData = await aggregateReviewData(sessions.active);
|
||||
data.statistics.reviewFindings = data.reviewData.totalFindings;
|
||||
}
|
||||
|
||||
data.statistics.totalSessions = sessions.active.length + sessions.archived.length;
|
||||
data.statistics.activeSessions = sessions.active.length;
|
||||
|
||||
// Scan and include lite tasks
|
||||
try {
|
||||
const liteTasks = await scanLiteTasks(workflowDir);
|
||||
data.liteTasks = liteTasks;
|
||||
data.statistics.litePlanCount = liteTasks.litePlan.length;
|
||||
data.statistics.liteFixCount = liteTasks.liteFix.length;
|
||||
} catch (err) {
|
||||
console.error('Error scanning lite tasks:', err.message);
|
||||
}
|
||||
|
||||
// Load project overview from project.json
|
||||
try {
|
||||
data.projectOverview = loadProjectOverview(workflowDir);
|
||||
} catch (err) {
|
||||
console.error('Error loading project overview:', err.message);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single session, loading tasks and review info
|
||||
* @param {Object} session - Session object from scanner
|
||||
* @param {boolean} isActive - Whether session is active
|
||||
* @returns {Promise<Object>} - Processed session data
|
||||
*/
|
||||
async function processSession(session, isActive) {
|
||||
const result = {
|
||||
session_id: session.session_id,
|
||||
project: session.project || session.session_id,
|
||||
status: session.status || (isActive ? 'active' : 'archived'),
|
||||
type: session.type || 'workflow', // Session type (workflow, review, test, docs)
|
||||
workflow_type: session.workflow_type || null, // Original workflow_type for reference
|
||||
created_at: session.created_at || null, // Raw ISO string - let frontend format
|
||||
archived_at: session.archived_at || null, // Raw ISO string - let frontend format
|
||||
path: session.path,
|
||||
tasks: [],
|
||||
taskCount: 0,
|
||||
hasReview: false,
|
||||
reviewSummary: null,
|
||||
reviewDimensions: []
|
||||
};
|
||||
|
||||
// Load tasks for active sessions (full details)
|
||||
if (isActive) {
|
||||
const taskDir = join(session.path, '.task');
|
||||
if (existsSync(taskDir)) {
|
||||
const taskFiles = await safeGlob('IMPL-*.json', taskDir);
|
||||
for (const taskFile of taskFiles) {
|
||||
try {
|
||||
const taskData = JSON.parse(readFileSync(join(taskDir, taskFile), 'utf8'));
|
||||
result.tasks.push({
|
||||
task_id: taskData.id || basename(taskFile, '.json'),
|
||||
title: taskData.title || 'Untitled Task',
|
||||
status: taskData.status || 'pending',
|
||||
type: taskData.meta?.type || 'task',
|
||||
meta: taskData.meta || {},
|
||||
context: taskData.context || {},
|
||||
flow_control: taskData.flow_control || {}
|
||||
});
|
||||
} catch {
|
||||
// Skip invalid task files
|
||||
}
|
||||
}
|
||||
// Sort tasks by ID
|
||||
result.tasks.sort((a, b) => sortTaskIds(a.task_id, b.task_id));
|
||||
}
|
||||
result.taskCount = result.tasks.length;
|
||||
|
||||
// Check for review data
|
||||
const reviewDir = join(session.path, '.review');
|
||||
if (existsSync(reviewDir)) {
|
||||
result.hasReview = true;
|
||||
result.reviewSummary = loadReviewSummary(reviewDir);
|
||||
// Load dimension data for review sessions
|
||||
if (session.type === 'review') {
|
||||
result.reviewDimensions = await loadDimensionData(reviewDir);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For archived, also load tasks (same as active)
|
||||
const taskDir = join(session.path, '.task');
|
||||
if (existsSync(taskDir)) {
|
||||
const taskFiles = await safeGlob('IMPL-*.json', taskDir);
|
||||
for (const taskFile of taskFiles) {
|
||||
try {
|
||||
const taskData = JSON.parse(readFileSync(join(taskDir, taskFile), 'utf8'));
|
||||
result.tasks.push({
|
||||
task_id: taskData.id || basename(taskFile, '.json'),
|
||||
title: taskData.title || 'Untitled Task',
|
||||
status: taskData.status || 'completed', // Archived tasks are usually completed
|
||||
type: taskData.meta?.type || 'task'
|
||||
});
|
||||
} catch {
|
||||
// Skip invalid task files
|
||||
}
|
||||
}
|
||||
// Sort tasks by ID
|
||||
result.tasks.sort((a, b) => sortTaskIds(a.task_id, b.task_id));
|
||||
result.taskCount = result.tasks.length;
|
||||
}
|
||||
|
||||
// Check for review data in archived sessions too
|
||||
const reviewDir = join(session.path, '.review');
|
||||
if (existsSync(reviewDir)) {
|
||||
result.hasReview = true;
|
||||
result.reviewSummary = loadReviewSummary(reviewDir);
|
||||
// Load dimension data for review sessions
|
||||
if (session.type === 'review') {
|
||||
result.reviewDimensions = await loadDimensionData(reviewDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregate review data from all active sessions with reviews
|
||||
* @param {Array} activeSessions - Active session objects
|
||||
* @returns {Promise<Object>} - Aggregated review data
|
||||
*/
|
||||
async function aggregateReviewData(activeSessions) {
|
||||
const reviewData = {
|
||||
totalFindings: 0,
|
||||
severityDistribution: { critical: 0, high: 0, medium: 0, low: 0 },
|
||||
dimensionSummary: {},
|
||||
sessions: []
|
||||
};
|
||||
|
||||
for (const session of activeSessions) {
|
||||
const reviewDir = join(session.path, '.review');
|
||||
if (!existsSync(reviewDir)) continue;
|
||||
|
||||
const reviewProgress = loadReviewProgress(reviewDir);
|
||||
const dimensionData = await loadDimensionData(reviewDir);
|
||||
|
||||
if (reviewProgress || dimensionData.length > 0) {
|
||||
const sessionReview = {
|
||||
session_id: session.session_id,
|
||||
progress: reviewProgress,
|
||||
dimensions: dimensionData,
|
||||
findings: []
|
||||
};
|
||||
|
||||
// Collect and count findings
|
||||
for (const dim of dimensionData) {
|
||||
if (dim.findings && Array.isArray(dim.findings)) {
|
||||
for (const finding of dim.findings) {
|
||||
const severity = (finding.severity || 'low').toLowerCase();
|
||||
if (reviewData.severityDistribution.hasOwnProperty(severity)) {
|
||||
reviewData.severityDistribution[severity]++;
|
||||
}
|
||||
reviewData.totalFindings++;
|
||||
sessionReview.findings.push({
|
||||
...finding,
|
||||
dimension: dim.name
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Track dimension summary
|
||||
if (!reviewData.dimensionSummary[dim.name]) {
|
||||
reviewData.dimensionSummary[dim.name] = { count: 0, sessions: [] };
|
||||
}
|
||||
reviewData.dimensionSummary[dim.name].count += dim.findings?.length || 0;
|
||||
reviewData.dimensionSummary[dim.name].sessions.push(session.session_id);
|
||||
}
|
||||
|
||||
reviewData.sessions.push(sessionReview);
|
||||
}
|
||||
}
|
||||
|
||||
return reviewData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load review progress from review-progress.json
|
||||
* @param {string} reviewDir - Path to .review directory
|
||||
* @returns {Object|null}
|
||||
*/
|
||||
function loadReviewProgress(reviewDir) {
|
||||
const progressFile = join(reviewDir, 'review-progress.json');
|
||||
if (!existsSync(progressFile)) return null;
|
||||
try {
|
||||
return JSON.parse(readFileSync(progressFile, 'utf8'));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load review summary from review-state.json
|
||||
* @param {string} reviewDir - Path to .review directory
|
||||
* @returns {Object|null}
|
||||
*/
|
||||
function loadReviewSummary(reviewDir) {
|
||||
const stateFile = join(reviewDir, 'review-state.json');
|
||||
if (!existsSync(stateFile)) return null;
|
||||
try {
|
||||
const state = JSON.parse(readFileSync(stateFile, 'utf8'));
|
||||
return {
|
||||
phase: state.phase || 'unknown',
|
||||
severityDistribution: state.severity_distribution || {},
|
||||
criticalFiles: (state.critical_files || []).slice(0, 3),
|
||||
status: state.status || 'in_progress'
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load dimension data from .review/dimensions/
|
||||
* @param {string} reviewDir - Path to .review directory
|
||||
* @returns {Promise<Array>}
|
||||
*/
|
||||
async function loadDimensionData(reviewDir) {
|
||||
const dimensionsDir = join(reviewDir, 'dimensions');
|
||||
if (!existsSync(dimensionsDir)) return [];
|
||||
|
||||
const dimensions = [];
|
||||
const dimFiles = await safeGlob('*.json', dimensionsDir);
|
||||
|
||||
for (const file of dimFiles) {
|
||||
try {
|
||||
const data = JSON.parse(readFileSync(join(dimensionsDir, file), 'utf8'));
|
||||
// Handle array structure: [ { findings: [...], summary: {...} } ]
|
||||
let findings = [];
|
||||
let summary = null;
|
||||
let status = 'completed';
|
||||
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
const dimData = data[0];
|
||||
findings = dimData.findings || [];
|
||||
summary = dimData.summary || null;
|
||||
status = dimData.status || 'completed';
|
||||
} else if (data.findings) {
|
||||
findings = data.findings;
|
||||
summary = data.summary || null;
|
||||
status = data.status || 'completed';
|
||||
}
|
||||
|
||||
dimensions.push({
|
||||
name: basename(file, '.json'),
|
||||
findings: findings,
|
||||
summary: summary,
|
||||
status: status
|
||||
});
|
||||
} catch {
|
||||
// Skip invalid dimension files
|
||||
}
|
||||
}
|
||||
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safe glob wrapper that returns empty array on error
|
||||
* @param {string} pattern - Glob pattern
|
||||
* @param {string} cwd - Current working directory
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
async function safeGlob(pattern, cwd) {
|
||||
try {
|
||||
return await glob(pattern, { cwd, absolute: false });
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// formatDate removed - dates are now passed as raw ISO strings
|
||||
// Frontend (dashboard.js) handles all date formatting
|
||||
|
||||
/**
|
||||
* Sort task IDs numerically (IMPL-1, IMPL-2, IMPL-1.1, etc.)
|
||||
* @param {string} a - First task ID
|
||||
* @param {string} b - Second task ID
|
||||
* @returns {number}
|
||||
*/
|
||||
function sortTaskIds(a, b) {
|
||||
const parseId = (id) => {
|
||||
const match = id.match(/IMPL-(\d+)(?:\.(\d+))?/);
|
||||
if (!match) return [0, 0];
|
||||
return [parseInt(match[1]), parseInt(match[2] || 0)];
|
||||
};
|
||||
const [a1, a2] = parseId(a);
|
||||
const [b1, b2] = parseId(b);
|
||||
return a1 - b1 || a2 - b2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load project overview from project.json
|
||||
* @param {string} workflowDir - Path to .workflow directory
|
||||
* @returns {Object|null} - Project overview data or null if not found
|
||||
*/
|
||||
function loadProjectOverview(workflowDir) {
|
||||
const projectFile = join(workflowDir, 'project.json');
|
||||
|
||||
if (!existsSync(projectFile)) {
|
||||
console.log(`Project file not found at: ${projectFile}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const fileContent = readFileSync(projectFile, 'utf8');
|
||||
const projectData = JSON.parse(fileContent);
|
||||
|
||||
console.log(`Successfully loaded project overview: ${projectData.project_name || 'Unknown'}`);
|
||||
|
||||
return {
|
||||
projectName: projectData.project_name || 'Unknown',
|
||||
description: projectData.overview?.description || '',
|
||||
initializedAt: projectData.initialized_at || null,
|
||||
technologyStack: projectData.overview?.technology_stack || {
|
||||
languages: [],
|
||||
frameworks: [],
|
||||
build_tools: [],
|
||||
test_frameworks: []
|
||||
},
|
||||
architecture: projectData.overview?.architecture || {
|
||||
style: 'Unknown',
|
||||
layers: [],
|
||||
patterns: []
|
||||
},
|
||||
keyComponents: projectData.overview?.key_components || [],
|
||||
features: projectData.features || [],
|
||||
developmentIndex: projectData.development_index || {
|
||||
feature: [],
|
||||
enhancement: [],
|
||||
bugfix: [],
|
||||
refactor: [],
|
||||
docs: []
|
||||
},
|
||||
statistics: projectData.statistics || {
|
||||
total_features: 0,
|
||||
total_sessions: 0,
|
||||
last_updated: null
|
||||
},
|
||||
metadata: projectData._metadata || {
|
||||
initialized_by: 'unknown',
|
||||
analysis_timestamp: null,
|
||||
analysis_mode: 'unknown'
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
console.error(`Failed to parse project.json at ${projectFile}:`, err.message);
|
||||
console.error('Error stack:', err.stack);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
290
ccw/src/core/lite-scanner.js
Normal file
290
ccw/src/core/lite-scanner.js
Normal file
@@ -0,0 +1,290 @@
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
/**
|
||||
* Scan lite-plan and lite-fix directories for task sessions
|
||||
* @param {string} workflowDir - Path to .workflow directory
|
||||
* @returns {Promise<Object>} - Lite tasks data
|
||||
*/
|
||||
export async function scanLiteTasks(workflowDir) {
|
||||
const litePlanDir = join(workflowDir, '.lite-plan');
|
||||
const liteFixDir = join(workflowDir, '.lite-fix');
|
||||
|
||||
return {
|
||||
litePlan: scanLiteDir(litePlanDir, 'lite-plan'),
|
||||
liteFix: scanLiteDir(liteFixDir, 'lite-fix')
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan a lite task directory
|
||||
* @param {string} dir - Directory path
|
||||
* @param {string} type - Task type ('lite-plan' or 'lite-fix')
|
||||
* @returns {Array} - Array of lite task sessions
|
||||
*/
|
||||
function scanLiteDir(dir, type) {
|
||||
if (!existsSync(dir)) return [];
|
||||
|
||||
try {
|
||||
const sessions = readdirSync(dir, { withFileTypes: true })
|
||||
.filter(d => d.isDirectory())
|
||||
.map(d => {
|
||||
const sessionPath = join(dir, d.name);
|
||||
const session = {
|
||||
id: d.name,
|
||||
type,
|
||||
path: sessionPath,
|
||||
createdAt: getCreatedTime(sessionPath),
|
||||
plan: loadPlanJson(sessionPath),
|
||||
tasks: loadTaskJsons(sessionPath)
|
||||
};
|
||||
|
||||
// Calculate progress
|
||||
session.progress = calculateProgress(session.tasks);
|
||||
|
||||
return session;
|
||||
})
|
||||
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
||||
|
||||
return sessions;
|
||||
} catch (err) {
|
||||
console.error(`Error scanning ${dir}:`, err.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load plan.json from session directory
|
||||
* @param {string} sessionPath - Session directory path
|
||||
* @returns {Object|null} - Plan data or null
|
||||
*/
|
||||
function loadPlanJson(sessionPath) {
|
||||
const planPath = join(sessionPath, 'plan.json');
|
||||
if (!existsSync(planPath)) return null;
|
||||
|
||||
try {
|
||||
const content = readFileSync(planPath, 'utf8');
|
||||
return JSON.parse(content);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all task JSON files from session directory
|
||||
* Supports multiple task formats:
|
||||
* 1. .task/IMPL-*.json files
|
||||
* 2. tasks array in plan.json
|
||||
* 3. task-*.json files in session root
|
||||
* @param {string} sessionPath - Session directory path
|
||||
* @returns {Array} - Array of task objects
|
||||
*/
|
||||
function loadTaskJsons(sessionPath) {
|
||||
let tasks = [];
|
||||
|
||||
// Method 1: Check .task/IMPL-*.json files
|
||||
const taskDir = join(sessionPath, '.task');
|
||||
if (existsSync(taskDir)) {
|
||||
try {
|
||||
const implTasks = readdirSync(taskDir)
|
||||
.filter(f => f.endsWith('.json') && (
|
||||
f.startsWith('IMPL-') ||
|
||||
f.startsWith('TASK-') ||
|
||||
f.startsWith('task-') ||
|
||||
/^T\d+\.json$/i.test(f)
|
||||
))
|
||||
.map(f => {
|
||||
const taskPath = join(taskDir, f);
|
||||
try {
|
||||
const content = readFileSync(taskPath, 'utf8');
|
||||
return normalizeTask(JSON.parse(content));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
tasks = tasks.concat(implTasks);
|
||||
} catch {
|
||||
// Continue to other methods
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Check plan.json for embedded tasks array
|
||||
if (tasks.length === 0) {
|
||||
const planPath = join(sessionPath, 'plan.json');
|
||||
if (existsSync(planPath)) {
|
||||
try {
|
||||
const plan = JSON.parse(readFileSync(planPath, 'utf8'));
|
||||
if (Array.isArray(plan.tasks)) {
|
||||
tasks = plan.tasks.map(t => normalizeTask(t));
|
||||
}
|
||||
} catch {
|
||||
// Continue to other methods
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method 3: Check for task-*.json files in session root
|
||||
if (tasks.length === 0) {
|
||||
try {
|
||||
const rootTasks = readdirSync(sessionPath)
|
||||
.filter(f => f.endsWith('.json') && (
|
||||
f.startsWith('task-') ||
|
||||
f.startsWith('TASK-') ||
|
||||
/^T\d+\.json$/i.test(f)
|
||||
))
|
||||
.map(f => {
|
||||
const taskPath = join(sessionPath, f);
|
||||
try {
|
||||
const content = readFileSync(taskPath, 'utf8');
|
||||
return normalizeTask(JSON.parse(content));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
tasks = tasks.concat(rootTasks);
|
||||
} catch {
|
||||
// No tasks found
|
||||
}
|
||||
}
|
||||
|
||||
// Sort tasks by ID
|
||||
return tasks.sort((a, b) => {
|
||||
const aNum = parseInt(a.id?.replace(/\D/g, '') || '0');
|
||||
const bNum = parseInt(b.id?.replace(/\D/g, '') || '0');
|
||||
return aNum - bNum;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize task object to consistent structure
|
||||
* @param {Object} task - Raw task object
|
||||
* @returns {Object} - Normalized task
|
||||
*/
|
||||
function normalizeTask(task) {
|
||||
if (!task) return null;
|
||||
|
||||
// Determine status - support various status formats
|
||||
let status = task.status || 'pending';
|
||||
if (typeof status === 'object') {
|
||||
status = status.state || status.value || 'pending';
|
||||
}
|
||||
|
||||
return {
|
||||
id: task.id || task.task_id || 'unknown',
|
||||
title: task.title || task.name || task.summary || 'Untitled Task',
|
||||
status: status.toLowerCase(),
|
||||
// Preserve original fields for flexible rendering
|
||||
meta: task.meta || {
|
||||
type: task.type || task.action || 'task',
|
||||
agent: task.agent || null,
|
||||
scope: task.scope || null,
|
||||
module: task.module || null
|
||||
},
|
||||
context: task.context || {
|
||||
requirements: task.requirements || task.description ? [task.description] : [],
|
||||
focus_paths: task.focus_paths || task.modification_points?.map(m => m.file) || [],
|
||||
acceptance: task.acceptance || [],
|
||||
depends_on: task.depends_on || []
|
||||
},
|
||||
flow_control: task.flow_control || {
|
||||
implementation_approach: task.implementation?.map((step, i) => ({
|
||||
step: `Step ${i + 1}`,
|
||||
action: step
|
||||
})) || []
|
||||
},
|
||||
// Keep all original fields for raw JSON view
|
||||
_raw: task
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get directory creation time
|
||||
* @param {string} dirPath - Directory path
|
||||
* @returns {string} - ISO date string
|
||||
*/
|
||||
function getCreatedTime(dirPath) {
|
||||
try {
|
||||
const stat = statSync(dirPath);
|
||||
return stat.birthtime.toISOString();
|
||||
} catch {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate progress from tasks
|
||||
* @param {Array} tasks - Array of task objects
|
||||
* @returns {Object} - Progress info
|
||||
*/
|
||||
function calculateProgress(tasks) {
|
||||
if (!tasks || tasks.length === 0) {
|
||||
return { total: 0, completed: 0, percentage: 0 };
|
||||
}
|
||||
|
||||
const total = tasks.length;
|
||||
const completed = tasks.filter(t => t.status === 'completed').length;
|
||||
const percentage = Math.round((completed / total) * 100);
|
||||
|
||||
return { total, completed, percentage };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed lite task info
|
||||
* @param {string} workflowDir - Workflow directory
|
||||
* @param {string} type - 'lite-plan' or 'lite-fix'
|
||||
* @param {string} sessionId - Session ID
|
||||
* @returns {Object|null} - Detailed task info
|
||||
*/
|
||||
export function getLiteTaskDetail(workflowDir, type, sessionId) {
|
||||
const dir = type === 'lite-plan'
|
||||
? join(workflowDir, '.lite-plan', sessionId)
|
||||
: join(workflowDir, '.lite-fix', sessionId);
|
||||
|
||||
if (!existsSync(dir)) return null;
|
||||
|
||||
return {
|
||||
id: sessionId,
|
||||
type,
|
||||
path: dir,
|
||||
plan: loadPlanJson(dir),
|
||||
tasks: loadTaskJsons(dir),
|
||||
explorations: loadExplorations(dir),
|
||||
clarifications: loadClarifications(dir)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Load exploration results
|
||||
* @param {string} sessionPath - Session directory path
|
||||
* @returns {Array} - Exploration results
|
||||
*/
|
||||
function loadExplorations(sessionPath) {
|
||||
const explorePath = join(sessionPath, 'explorations.json');
|
||||
if (!existsSync(explorePath)) return [];
|
||||
|
||||
try {
|
||||
const content = readFileSync(explorePath, 'utf8');
|
||||
return JSON.parse(content);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load clarification data
|
||||
* @param {string} sessionPath - Session directory path
|
||||
* @returns {Object|null} - Clarification data
|
||||
*/
|
||||
function loadClarifications(sessionPath) {
|
||||
const clarifyPath = join(sessionPath, 'clarifications.json');
|
||||
if (!existsSync(clarifyPath)) return null;
|
||||
|
||||
try {
|
||||
const content = readFileSync(clarifyPath, 'utf8');
|
||||
return JSON.parse(content);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
201
ccw/src/core/manifest.js
Normal file
201
ccw/src/core/manifest.js
Normal file
@@ -0,0 +1,201 @@
|
||||
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, unlinkSync, statSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { homedir } from 'os';
|
||||
|
||||
// Manifest directory location
|
||||
const MANIFEST_DIR = join(homedir(), '.claude-manifests');
|
||||
|
||||
/**
|
||||
* Ensure manifest directory exists
|
||||
*/
|
||||
function ensureManifestDir() {
|
||||
if (!existsSync(MANIFEST_DIR)) {
|
||||
mkdirSync(MANIFEST_DIR, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new installation manifest
|
||||
* @param {string} mode - Installation mode (Global/Path)
|
||||
* @param {string} installPath - Installation path
|
||||
* @returns {Object} - New manifest object
|
||||
*/
|
||||
export function createManifest(mode, installPath) {
|
||||
ensureManifestDir();
|
||||
|
||||
const timestamp = new Date().toISOString().replace(/[-:]/g, '').replace('T', '-').split('.')[0];
|
||||
const modePrefix = mode === 'Global' ? 'manifest-global' : 'manifest-path';
|
||||
const manifestId = `${modePrefix}-${timestamp}`;
|
||||
|
||||
return {
|
||||
manifest_id: manifestId,
|
||||
version: '1.0',
|
||||
installation_mode: mode,
|
||||
installation_path: installPath,
|
||||
installation_date: new Date().toISOString(),
|
||||
installer_version: '1.0.0',
|
||||
files: [],
|
||||
directories: []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add file entry to manifest
|
||||
* @param {Object} manifest - Manifest object
|
||||
* @param {string} filePath - File path
|
||||
*/
|
||||
export function addFileEntry(manifest, filePath) {
|
||||
manifest.files.push({
|
||||
path: filePath,
|
||||
type: 'File',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add directory entry to manifest
|
||||
* @param {Object} manifest - Manifest object
|
||||
* @param {string} dirPath - Directory path
|
||||
*/
|
||||
export function addDirectoryEntry(manifest, dirPath) {
|
||||
manifest.directories.push({
|
||||
path: dirPath,
|
||||
type: 'Directory',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save manifest to disk
|
||||
* @param {Object} manifest - Manifest object
|
||||
* @returns {string} - Path to saved manifest
|
||||
*/
|
||||
export function saveManifest(manifest) {
|
||||
ensureManifestDir();
|
||||
|
||||
// Remove old manifests for same path and mode
|
||||
removeOldManifests(manifest.installation_path, manifest.installation_mode);
|
||||
|
||||
const manifestPath = join(MANIFEST_DIR, `${manifest.manifest_id}.json`);
|
||||
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
||||
|
||||
return manifestPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove old manifests for the same installation path and mode
|
||||
* @param {string} installPath - Installation path
|
||||
* @param {string} mode - Installation mode
|
||||
*/
|
||||
function removeOldManifests(installPath, mode) {
|
||||
if (!existsSync(MANIFEST_DIR)) return;
|
||||
|
||||
const normalizedPath = installPath.toLowerCase().replace(/[\\/]+$/, '');
|
||||
|
||||
try {
|
||||
const files = readdirSync(MANIFEST_DIR).filter(f => f.endsWith('.json'));
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
const filePath = join(MANIFEST_DIR, file);
|
||||
const content = JSON.parse(readFileSync(filePath, 'utf8'));
|
||||
|
||||
const manifestPath = (content.installation_path || '').toLowerCase().replace(/[\\/]+$/, '');
|
||||
const manifestMode = content.installation_mode || 'Global';
|
||||
|
||||
if (manifestPath === normalizedPath && manifestMode === mode) {
|
||||
unlinkSync(filePath);
|
||||
}
|
||||
} catch {
|
||||
// Skip invalid manifest files
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all installation manifests
|
||||
* @returns {Array} - Array of manifest objects
|
||||
*/
|
||||
export function getAllManifests() {
|
||||
if (!existsSync(MANIFEST_DIR)) return [];
|
||||
|
||||
const manifests = [];
|
||||
|
||||
try {
|
||||
const files = readdirSync(MANIFEST_DIR).filter(f => f.endsWith('.json'));
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
const filePath = join(MANIFEST_DIR, file);
|
||||
const content = JSON.parse(readFileSync(filePath, 'utf8'));
|
||||
|
||||
// Try to read version.json for application version
|
||||
let appVersion = 'unknown';
|
||||
try {
|
||||
const versionPath = join(content.installation_path, '.claude', 'version.json');
|
||||
if (existsSync(versionPath)) {
|
||||
const versionInfo = JSON.parse(readFileSync(versionPath, 'utf8'));
|
||||
appVersion = versionInfo.version || 'unknown';
|
||||
}
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
manifests.push({
|
||||
...content,
|
||||
manifest_file: filePath,
|
||||
application_version: appVersion,
|
||||
files_count: content.files?.length || 0,
|
||||
directories_count: content.directories?.length || 0
|
||||
});
|
||||
} catch {
|
||||
// Skip invalid manifest files
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by installation date (newest first)
|
||||
manifests.sort((a, b) => new Date(b.installation_date) - new Date(a.installation_date));
|
||||
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
|
||||
return manifests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find manifest for a specific path and mode
|
||||
* @param {string} installPath - Installation path
|
||||
* @param {string} mode - Installation mode
|
||||
* @returns {Object|null} - Manifest or null
|
||||
*/
|
||||
export function findManifest(installPath, mode) {
|
||||
const manifests = getAllManifests();
|
||||
const normalizedPath = installPath.toLowerCase().replace(/[\\/]+$/, '');
|
||||
|
||||
return manifests.find(m => {
|
||||
const manifestPath = (m.installation_path || '').toLowerCase().replace(/[\\/]+$/, '');
|
||||
return manifestPath === normalizedPath && m.installation_mode === mode;
|
||||
}) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a manifest file
|
||||
* @param {string} manifestFile - Path to manifest file
|
||||
*/
|
||||
export function deleteManifest(manifestFile) {
|
||||
if (existsSync(manifestFile)) {
|
||||
unlinkSync(manifestFile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get manifest directory path
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getManifestDir() {
|
||||
return MANIFEST_DIR;
|
||||
}
|
||||
1348
ccw/src/core/server.js
Normal file
1348
ccw/src/core/server.js
Normal file
File diff suppressed because it is too large
Load Diff
385
ccw/src/core/server.js.bak
Normal file
385
ccw/src/core/server.js.bak
Normal file
@@ -0,0 +1,385 @@
|
||||
import http from 'http';
|
||||
import { URL } from 'url';
|
||||
import { readFileSync, existsSync, readdirSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { scanSessions } from './session-scanner.js';
|
||||
import { aggregateData } from './data-aggregator.js';
|
||||
import { resolvePath, getRecentPaths, trackRecentPath, normalizePathForDisplay, getWorkflowDir } from '../utils/path-resolver.js';
|
||||
|
||||
const TEMPLATE_PATH = join(import.meta.dirname, '../templates/dashboard.html');
|
||||
const CSS_FILE = join(import.meta.dirname, '../templates/dashboard.css');
|
||||
const JS_FILE = join(import.meta.dirname, '../templates/dashboard.js');
|
||||
|
||||
/**
|
||||
* Create and start the dashboard server
|
||||
* @param {Object} options - Server options
|
||||
* @param {number} options.port - Port to listen on (default: 3456)
|
||||
* @param {string} options.initialPath - Initial project path
|
||||
* @returns {Promise<http.Server>}
|
||||
*/
|
||||
export async function startServer(options = {}) {
|
||||
const port = options.port || 3456;
|
||||
const initialPath = options.initialPath || process.cwd();
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url, `http://localhost:${port}`);
|
||||
const pathname = url.pathname;
|
||||
|
||||
// CORS headers for API requests
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// API: Get workflow data for a path
|
||||
if (pathname === '/api/data') {
|
||||
const projectPath = url.searchParams.get('path') || initialPath;
|
||||
const data = await getWorkflowData(projectPath);
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(data));
|
||||
return;
|
||||
}
|
||||
|
||||
// API: Get recent paths
|
||||
if (pathname === '/api/recent-paths') {
|
||||
const paths = getRecentPaths();
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ paths }));
|
||||
return;
|
||||
}
|
||||
|
||||
// API: Get session detail data (context, summaries, impl-plan, review)
|
||||
if (pathname === '/api/session-detail') {
|
||||
const sessionPath = url.searchParams.get('path');
|
||||
const dataType = url.searchParams.get('type') || 'all';
|
||||
|
||||
if (!sessionPath) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'Session path is required' }));
|
||||
return;
|
||||
}
|
||||
|
||||
const detail = await getSessionDetailData(sessionPath, dataType);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(detail));
|
||||
return;
|
||||
}
|
||||
|
||||
// Serve dashboard HTML
|
||||
if (pathname === '/' || pathname === '/index.html') {
|
||||
const html = generateServerDashboard(initialPath);
|
||||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
res.end(html);
|
||||
return;
|
||||
}
|
||||
|
||||
// 404
|
||||
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
||||
res.end('Not Found');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Server error:', error);
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: error.message }));
|
||||
}
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
server.listen(port, () => {
|
||||
console.log(`Dashboard server running at http://localhost:${port}`);
|
||||
resolve(server);
|
||||
});
|
||||
server.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get workflow data for a project path
|
||||
* @param {string} projectPath
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function getWorkflowData(projectPath) {
|
||||
const resolvedPath = resolvePath(projectPath);
|
||||
const workflowDir = join(resolvedPath, '.workflow');
|
||||
|
||||
// Track this path
|
||||
trackRecentPath(resolvedPath);
|
||||
|
||||
// Check if .workflow exists
|
||||
if (!existsSync(workflowDir)) {
|
||||
return {
|
||||
generatedAt: new Date().toISOString(),
|
||||
activeSessions: [],
|
||||
archivedSessions: [],
|
||||
liteTasks: { litePlan: [], liteFix: [] },
|
||||
reviewData: { dimensions: {} },
|
||||
projectOverview: null,
|
||||
statistics: {
|
||||
totalSessions: 0,
|
||||
activeSessions: 0,
|
||||
totalTasks: 0,
|
||||
completedTasks: 0,
|
||||
reviewFindings: 0,
|
||||
litePlanCount: 0,
|
||||
liteFixCount: 0
|
||||
},
|
||||
projectPath: normalizePathForDisplay(resolvedPath),
|
||||
recentPaths: getRecentPaths()
|
||||
};
|
||||
}
|
||||
|
||||
// Scan and aggregate data
|
||||
const sessions = await scanSessions(workflowDir);
|
||||
const data = await aggregateData(sessions, workflowDir);
|
||||
|
||||
data.projectPath = normalizePathForDisplay(resolvedPath);
|
||||
data.recentPaths = getRecentPaths();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session detail data (context, summaries, impl-plan, review)
|
||||
* @param {string} sessionPath - Path to session directory
|
||||
* @param {string} dataType - Type of data to load: context, summary, impl-plan, review, or all
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function getSessionDetailData(sessionPath, dataType) {
|
||||
const result = {};
|
||||
|
||||
// Normalize path
|
||||
const normalizedPath = sessionPath.replace(/\\/g, '/');
|
||||
|
||||
try {
|
||||
// Load context-package.json (in .process/ subfolder)
|
||||
if (dataType === 'context' || dataType === 'all') {
|
||||
// Try .process/context-package.json first (common location)
|
||||
let contextFile = join(normalizedPath, '.process', 'context-package.json');
|
||||
if (!existsSync(contextFile)) {
|
||||
// Fallback to session root
|
||||
contextFile = join(normalizedPath, 'context-package.json');
|
||||
}
|
||||
if (existsSync(contextFile)) {
|
||||
try {
|
||||
result.context = JSON.parse(readFileSync(contextFile, 'utf8'));
|
||||
} catch (e) {
|
||||
result.context = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load task JSONs from .task/ folder
|
||||
if (dataType === 'tasks' || dataType === 'all') {
|
||||
const taskDir = join(normalizedPath, '.task');
|
||||
result.tasks = [];
|
||||
if (existsSync(taskDir)) {
|
||||
const files = readdirSync(taskDir).filter(f => f.endsWith('.json') && f.startsWith('IMPL-'));
|
||||
for (const file of files) {
|
||||
try {
|
||||
const content = JSON.parse(readFileSync(join(taskDir, file), 'utf8'));
|
||||
result.tasks.push({
|
||||
filename: file,
|
||||
task_id: file.replace('.json', ''),
|
||||
...content
|
||||
});
|
||||
} catch (e) {
|
||||
// Skip unreadable files
|
||||
}
|
||||
}
|
||||
// Sort by task ID
|
||||
result.tasks.sort((a, b) => a.task_id.localeCompare(b.task_id));
|
||||
}
|
||||
}
|
||||
|
||||
// Load summaries from .summaries/
|
||||
if (dataType === 'summary' || dataType === 'all') {
|
||||
const summariesDir = join(normalizedPath, '.summaries');
|
||||
result.summaries = [];
|
||||
if (existsSync(summariesDir)) {
|
||||
const files = readdirSync(summariesDir).filter(f => f.endsWith('.md'));
|
||||
for (const file of files) {
|
||||
try {
|
||||
const content = readFileSync(join(summariesDir, file), 'utf8');
|
||||
result.summaries.push({ name: file.replace('.md', ''), content });
|
||||
} catch (e) {
|
||||
// Skip unreadable files
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load plan.json (for lite tasks)
|
||||
if (dataType === 'plan' || dataType === 'all') {
|
||||
const planFile = join(normalizedPath, 'plan.json');
|
||||
if (existsSync(planFile)) {
|
||||
try {
|
||||
result.plan = JSON.parse(readFileSync(planFile, 'utf8'));
|
||||
} catch (e) {
|
||||
result.plan = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load IMPL_PLAN.md
|
||||
if (dataType === 'impl-plan' || dataType === 'all') {
|
||||
const implPlanFile = join(normalizedPath, 'IMPL_PLAN.md');
|
||||
if (existsSync(implPlanFile)) {
|
||||
try {
|
||||
result.implPlan = readFileSync(implPlanFile, 'utf8');
|
||||
} catch (e) {
|
||||
result.implPlan = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load review data from .review/
|
||||
if (dataType === 'review' || dataType === 'all') {
|
||||
const reviewDir = join(normalizedPath, '.review');
|
||||
result.review = {
|
||||
state: null,
|
||||
dimensions: [],
|
||||
severityDistribution: null,
|
||||
totalFindings: 0
|
||||
};
|
||||
|
||||
if (existsSync(reviewDir)) {
|
||||
// Load review-state.json
|
||||
const stateFile = join(reviewDir, 'review-state.json');
|
||||
if (existsSync(stateFile)) {
|
||||
try {
|
||||
const state = JSON.parse(readFileSync(stateFile, 'utf8'));
|
||||
result.review.state = state;
|
||||
result.review.severityDistribution = state.severity_distribution || {};
|
||||
result.review.totalFindings = state.total_findings || 0;
|
||||
result.review.phase = state.phase || 'unknown';
|
||||
result.review.dimensionSummaries = state.dimension_summaries || {};
|
||||
result.review.crossCuttingConcerns = state.cross_cutting_concerns || [];
|
||||
result.review.criticalFiles = state.critical_files || [];
|
||||
} catch (e) {
|
||||
// Skip unreadable state
|
||||
}
|
||||
}
|
||||
|
||||
// Load dimension findings
|
||||
const dimensionsDir = join(reviewDir, 'dimensions');
|
||||
if (existsSync(dimensionsDir)) {
|
||||
const files = readdirSync(dimensionsDir).filter(f => f.endsWith('.json'));
|
||||
for (const file of files) {
|
||||
try {
|
||||
const dimName = file.replace('.json', '');
|
||||
const data = JSON.parse(readFileSync(join(dimensionsDir, file), 'utf8'));
|
||||
|
||||
// Handle array structure: [ { findings: [...] } ]
|
||||
let findings = [];
|
||||
let summary = null;
|
||||
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
const dimData = data[0];
|
||||
findings = dimData.findings || [];
|
||||
summary = dimData.summary || null;
|
||||
} else if (data.findings) {
|
||||
findings = data.findings;
|
||||
summary = data.summary || null;
|
||||
}
|
||||
|
||||
result.review.dimensions.push({
|
||||
name: dimName,
|
||||
findings: findings,
|
||||
summary: summary,
|
||||
count: findings.length
|
||||
});
|
||||
} catch (e) {
|
||||
// Skip unreadable files
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading session detail:', error);
|
||||
result.error = error.message;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate dashboard HTML for server mode
|
||||
* @param {string} initialPath
|
||||
* @returns {string}
|
||||
*/
|
||||
function generateServerDashboard(initialPath) {
|
||||
let html = readFileSync(TEMPLATE_PATH, 'utf8');
|
||||
|
||||
// Read CSS and JS files
|
||||
const cssContent = existsSync(CSS_FILE) ? readFileSync(CSS_FILE, 'utf8') : '';
|
||||
let jsContent = existsSync(JS_FILE) ? readFileSync(JS_FILE, 'utf8') : '';
|
||||
|
||||
// Inject CSS content
|
||||
html = html.replace('{{CSS_CONTENT}}', cssContent);
|
||||
|
||||
// Prepare JS content with empty initial data (will be loaded dynamically)
|
||||
const emptyData = {
|
||||
generatedAt: new Date().toISOString(),
|
||||
activeSessions: [],
|
||||
archivedSessions: [],
|
||||
liteTasks: { litePlan: [], liteFix: [] },
|
||||
reviewData: { dimensions: {} },
|
||||
projectOverview: null,
|
||||
statistics: { totalSessions: 0, activeSessions: 0, totalTasks: 0, completedTasks: 0, reviewFindings: 0, litePlanCount: 0, liteFixCount: 0 }
|
||||
};
|
||||
|
||||
// Replace JS placeholders
|
||||
jsContent = jsContent.replace('{{WORKFLOW_DATA}}', JSON.stringify(emptyData, null, 2));
|
||||
jsContent = jsContent.replace(/\{\{PROJECT_PATH\}\}/g, normalizePathForDisplay(initialPath).replace(/\\/g, '/'));
|
||||
jsContent = jsContent.replace('{{RECENT_PATHS}}', JSON.stringify(getRecentPaths()));
|
||||
|
||||
// Add server mode flag and dynamic loading functions at the start of JS
|
||||
const serverModeScript = `
|
||||
// Server mode - load data dynamically
|
||||
window.SERVER_MODE = true;
|
||||
window.INITIAL_PATH = '${normalizePathForDisplay(initialPath).replace(/\\/g, '/')}';
|
||||
|
||||
async function loadDashboardData(path) {
|
||||
try {
|
||||
const res = await fetch('/api/data?path=' + encodeURIComponent(path));
|
||||
if (!res.ok) throw new Error('Failed to load data');
|
||||
return await res.json();
|
||||
} catch (err) {
|
||||
console.error('Error loading data:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRecentPaths() {
|
||||
try {
|
||||
const res = await fetch('/api/recent-paths');
|
||||
if (!res.ok) return [];
|
||||
const data = await res.json();
|
||||
return data.paths || [];
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
// Prepend server mode script to JS content
|
||||
jsContent = serverModeScript + jsContent;
|
||||
|
||||
// Inject JS content
|
||||
html = html.replace('{{JS_CONTENT}}', jsContent);
|
||||
|
||||
// Replace any remaining placeholders in HTML
|
||||
html = html.replace(/\{\{PROJECT_PATH\}\}/g, normalizePathForDisplay(initialPath).replace(/\\/g, '/'));
|
||||
|
||||
return html;
|
||||
}
|
||||
385
ccw/src/core/server_original.bak
Normal file
385
ccw/src/core/server_original.bak
Normal file
@@ -0,0 +1,385 @@
|
||||
import http from 'http';
|
||||
import { URL } from 'url';
|
||||
import { readFileSync, existsSync, readdirSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { scanSessions } from './session-scanner.js';
|
||||
import { aggregateData } from './data-aggregator.js';
|
||||
import { resolvePath, getRecentPaths, trackRecentPath, normalizePathForDisplay, getWorkflowDir } from '../utils/path-resolver.js';
|
||||
|
||||
const TEMPLATE_PATH = join(import.meta.dirname, '../templates/dashboard.html');
|
||||
const CSS_FILE = join(import.meta.dirname, '../templates/dashboard.css');
|
||||
const JS_FILE = join(import.meta.dirname, '../templates/dashboard.js');
|
||||
|
||||
/**
|
||||
* Create and start the dashboard server
|
||||
* @param {Object} options - Server options
|
||||
* @param {number} options.port - Port to listen on (default: 3456)
|
||||
* @param {string} options.initialPath - Initial project path
|
||||
* @returns {Promise<http.Server>}
|
||||
*/
|
||||
export async function startServer(options = {}) {
|
||||
const port = options.port || 3456;
|
||||
const initialPath = options.initialPath || process.cwd();
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url, `http://localhost:${port}`);
|
||||
const pathname = url.pathname;
|
||||
|
||||
// CORS headers for API requests
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// API: Get workflow data for a path
|
||||
if (pathname === '/api/data') {
|
||||
const projectPath = url.searchParams.get('path') || initialPath;
|
||||
const data = await getWorkflowData(projectPath);
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(data));
|
||||
return;
|
||||
}
|
||||
|
||||
// API: Get recent paths
|
||||
if (pathname === '/api/recent-paths') {
|
||||
const paths = getRecentPaths();
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ paths }));
|
||||
return;
|
||||
}
|
||||
|
||||
// API: Get session detail data (context, summaries, impl-plan, review)
|
||||
if (pathname === '/api/session-detail') {
|
||||
const sessionPath = url.searchParams.get('path');
|
||||
const dataType = url.searchParams.get('type') || 'all';
|
||||
|
||||
if (!sessionPath) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'Session path is required' }));
|
||||
return;
|
||||
}
|
||||
|
||||
const detail = await getSessionDetailData(sessionPath, dataType);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(detail));
|
||||
return;
|
||||
}
|
||||
|
||||
// Serve dashboard HTML
|
||||
if (pathname === '/' || pathname === '/index.html') {
|
||||
const html = generateServerDashboard(initialPath);
|
||||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
res.end(html);
|
||||
return;
|
||||
}
|
||||
|
||||
// 404
|
||||
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
||||
res.end('Not Found');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Server error:', error);
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: error.message }));
|
||||
}
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
server.listen(port, () => {
|
||||
console.log(`Dashboard server running at http://localhost:${port}`);
|
||||
resolve(server);
|
||||
});
|
||||
server.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get workflow data for a project path
|
||||
* @param {string} projectPath
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function getWorkflowData(projectPath) {
|
||||
const resolvedPath = resolvePath(projectPath);
|
||||
const workflowDir = join(resolvedPath, '.workflow');
|
||||
|
||||
// Track this path
|
||||
trackRecentPath(resolvedPath);
|
||||
|
||||
// Check if .workflow exists
|
||||
if (!existsSync(workflowDir)) {
|
||||
return {
|
||||
generatedAt: new Date().toISOString(),
|
||||
activeSessions: [],
|
||||
archivedSessions: [],
|
||||
liteTasks: { litePlan: [], liteFix: [] },
|
||||
reviewData: { dimensions: {} },
|
||||
projectOverview: null,
|
||||
statistics: {
|
||||
totalSessions: 0,
|
||||
activeSessions: 0,
|
||||
totalTasks: 0,
|
||||
completedTasks: 0,
|
||||
reviewFindings: 0,
|
||||
litePlanCount: 0,
|
||||
liteFixCount: 0
|
||||
},
|
||||
projectPath: normalizePathForDisplay(resolvedPath),
|
||||
recentPaths: getRecentPaths()
|
||||
};
|
||||
}
|
||||
|
||||
// Scan and aggregate data
|
||||
const sessions = await scanSessions(workflowDir);
|
||||
const data = await aggregateData(sessions, workflowDir);
|
||||
|
||||
data.projectPath = normalizePathForDisplay(resolvedPath);
|
||||
data.recentPaths = getRecentPaths();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session detail data (context, summaries, impl-plan, review)
|
||||
* @param {string} sessionPath - Path to session directory
|
||||
* @param {string} dataType - Type of data to load: context, summary, impl-plan, review, or all
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function getSessionDetailData(sessionPath, dataType) {
|
||||
const result = {};
|
||||
|
||||
// Normalize path
|
||||
const normalizedPath = sessionPath.replace(/\\/g, '/');
|
||||
|
||||
try {
|
||||
// Load context-package.json (in .process/ subfolder)
|
||||
if (dataType === 'context' || dataType === 'all') {
|
||||
// Try .process/context-package.json first (common location)
|
||||
let contextFile = join(normalizedPath, '.process', 'context-package.json');
|
||||
if (!existsSync(contextFile)) {
|
||||
// Fallback to session root
|
||||
contextFile = join(normalizedPath, 'context-package.json');
|
||||
}
|
||||
if (existsSync(contextFile)) {
|
||||
try {
|
||||
result.context = JSON.parse(readFileSync(contextFile, 'utf8'));
|
||||
} catch (e) {
|
||||
result.context = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load task JSONs from .task/ folder
|
||||
if (dataType === 'tasks' || dataType === 'all') {
|
||||
const taskDir = join(normalizedPath, '.task');
|
||||
result.tasks = [];
|
||||
if (existsSync(taskDir)) {
|
||||
const files = readdirSync(taskDir).filter(f => f.endsWith('.json') && f.startsWith('IMPL-'));
|
||||
for (const file of files) {
|
||||
try {
|
||||
const content = JSON.parse(readFileSync(join(taskDir, file), 'utf8'));
|
||||
result.tasks.push({
|
||||
filename: file,
|
||||
task_id: file.replace('.json', ''),
|
||||
...content
|
||||
});
|
||||
} catch (e) {
|
||||
// Skip unreadable files
|
||||
}
|
||||
}
|
||||
// Sort by task ID
|
||||
result.tasks.sort((a, b) => a.task_id.localeCompare(b.task_id));
|
||||
}
|
||||
}
|
||||
|
||||
// Load summaries from .summaries/
|
||||
if (dataType === 'summary' || dataType === 'all') {
|
||||
const summariesDir = join(normalizedPath, '.summaries');
|
||||
result.summaries = [];
|
||||
if (existsSync(summariesDir)) {
|
||||
const files = readdirSync(summariesDir).filter(f => f.endsWith('.md'));
|
||||
for (const file of files) {
|
||||
try {
|
||||
const content = readFileSync(join(summariesDir, file), 'utf8');
|
||||
result.summaries.push({ name: file.replace('.md', ''), content });
|
||||
} catch (e) {
|
||||
// Skip unreadable files
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load plan.json (for lite tasks)
|
||||
if (dataType === 'plan' || dataType === 'all') {
|
||||
const planFile = join(normalizedPath, 'plan.json');
|
||||
if (existsSync(planFile)) {
|
||||
try {
|
||||
result.plan = JSON.parse(readFileSync(planFile, 'utf8'));
|
||||
} catch (e) {
|
||||
result.plan = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load IMPL_PLAN.md
|
||||
if (dataType === 'impl-plan' || dataType === 'all') {
|
||||
const implPlanFile = join(normalizedPath, 'IMPL_PLAN.md');
|
||||
if (existsSync(implPlanFile)) {
|
||||
try {
|
||||
result.implPlan = readFileSync(implPlanFile, 'utf8');
|
||||
} catch (e) {
|
||||
result.implPlan = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load review data from .review/
|
||||
if (dataType === 'review' || dataType === 'all') {
|
||||
const reviewDir = join(normalizedPath, '.review');
|
||||
result.review = {
|
||||
state: null,
|
||||
dimensions: [],
|
||||
severityDistribution: null,
|
||||
totalFindings: 0
|
||||
};
|
||||
|
||||
if (existsSync(reviewDir)) {
|
||||
// Load review-state.json
|
||||
const stateFile = join(reviewDir, 'review-state.json');
|
||||
if (existsSync(stateFile)) {
|
||||
try {
|
||||
const state = JSON.parse(readFileSync(stateFile, 'utf8'));
|
||||
result.review.state = state;
|
||||
result.review.severityDistribution = state.severity_distribution || {};
|
||||
result.review.totalFindings = state.total_findings || 0;
|
||||
result.review.phase = state.phase || 'unknown';
|
||||
result.review.dimensionSummaries = state.dimension_summaries || {};
|
||||
result.review.crossCuttingConcerns = state.cross_cutting_concerns || [];
|
||||
result.review.criticalFiles = state.critical_files || [];
|
||||
} catch (e) {
|
||||
// Skip unreadable state
|
||||
}
|
||||
}
|
||||
|
||||
// Load dimension findings
|
||||
const dimensionsDir = join(reviewDir, 'dimensions');
|
||||
if (existsSync(dimensionsDir)) {
|
||||
const files = readdirSync(dimensionsDir).filter(f => f.endsWith('.json'));
|
||||
for (const file of files) {
|
||||
try {
|
||||
const dimName = file.replace('.json', '');
|
||||
const data = JSON.parse(readFileSync(join(dimensionsDir, file), 'utf8'));
|
||||
|
||||
// Handle array structure: [ { findings: [...] } ]
|
||||
let findings = [];
|
||||
let summary = null;
|
||||
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
const dimData = data[0];
|
||||
findings = dimData.findings || [];
|
||||
summary = dimData.summary || null;
|
||||
} else if (data.findings) {
|
||||
findings = data.findings;
|
||||
summary = data.summary || null;
|
||||
}
|
||||
|
||||
result.review.dimensions.push({
|
||||
name: dimName,
|
||||
findings: findings,
|
||||
summary: summary,
|
||||
count: findings.length
|
||||
});
|
||||
} catch (e) {
|
||||
// Skip unreadable files
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading session detail:', error);
|
||||
result.error = error.message;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate dashboard HTML for server mode
|
||||
* @param {string} initialPath
|
||||
* @returns {string}
|
||||
*/
|
||||
function generateServerDashboard(initialPath) {
|
||||
let html = readFileSync(TEMPLATE_PATH, 'utf8');
|
||||
|
||||
// Read CSS and JS files
|
||||
const cssContent = existsSync(CSS_FILE) ? readFileSync(CSS_FILE, 'utf8') : '';
|
||||
let jsContent = existsSync(JS_FILE) ? readFileSync(JS_FILE, 'utf8') : '';
|
||||
|
||||
// Inject CSS content
|
||||
html = html.replace('{{CSS_CONTENT}}', cssContent);
|
||||
|
||||
// Prepare JS content with empty initial data (will be loaded dynamically)
|
||||
const emptyData = {
|
||||
generatedAt: new Date().toISOString(),
|
||||
activeSessions: [],
|
||||
archivedSessions: [],
|
||||
liteTasks: { litePlan: [], liteFix: [] },
|
||||
reviewData: { dimensions: {} },
|
||||
projectOverview: null,
|
||||
statistics: { totalSessions: 0, activeSessions: 0, totalTasks: 0, completedTasks: 0, reviewFindings: 0, litePlanCount: 0, liteFixCount: 0 }
|
||||
};
|
||||
|
||||
// Replace JS placeholders
|
||||
jsContent = jsContent.replace('{{WORKFLOW_DATA}}', JSON.stringify(emptyData, null, 2));
|
||||
jsContent = jsContent.replace(/\{\{PROJECT_PATH\}\}/g, normalizePathForDisplay(initialPath).replace(/\\/g, '/'));
|
||||
jsContent = jsContent.replace('{{RECENT_PATHS}}', JSON.stringify(getRecentPaths()));
|
||||
|
||||
// Add server mode flag and dynamic loading functions at the start of JS
|
||||
const serverModeScript = `
|
||||
// Server mode - load data dynamically
|
||||
window.SERVER_MODE = true;
|
||||
window.INITIAL_PATH = '${normalizePathForDisplay(initialPath).replace(/\\/g, '/')}';
|
||||
|
||||
async function loadDashboardData(path) {
|
||||
try {
|
||||
const res = await fetch('/api/data?path=' + encodeURIComponent(path));
|
||||
if (!res.ok) throw new Error('Failed to load data');
|
||||
return await res.json();
|
||||
} catch (err) {
|
||||
console.error('Error loading data:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRecentPaths() {
|
||||
try {
|
||||
const res = await fetch('/api/recent-paths');
|
||||
if (!res.ok) return [];
|
||||
const data = await res.json();
|
||||
return data.paths || [];
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
// Prepend server mode script to JS content
|
||||
jsContent = serverModeScript + jsContent;
|
||||
|
||||
// Inject JS content
|
||||
html = html.replace('{{JS_CONTENT}}', jsContent);
|
||||
|
||||
// Replace any remaining placeholders in HTML
|
||||
html = html.replace(/\{\{PROJECT_PATH\}\}/g, normalizePathForDisplay(initialPath).replace(/\\/g, '/'));
|
||||
|
||||
return html;
|
||||
}
|
||||
235
ccw/src/core/session-scanner.js
Normal file
235
ccw/src/core/session-scanner.js
Normal file
@@ -0,0 +1,235 @@
|
||||
import { glob } from 'glob';
|
||||
import { readFileSync, existsSync, statSync, readdirSync } from 'fs';
|
||||
import { join, basename } from 'path';
|
||||
|
||||
/**
|
||||
* Scan .workflow directory for active and archived sessions
|
||||
* @param {string} workflowDir - Path to .workflow directory
|
||||
* @returns {Promise<{active: Array, archived: Array, hasReviewData: boolean}>}
|
||||
*/
|
||||
export async function scanSessions(workflowDir) {
|
||||
const result = {
|
||||
active: [],
|
||||
archived: [],
|
||||
hasReviewData: false
|
||||
};
|
||||
|
||||
if (!existsSync(workflowDir)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Scan active sessions
|
||||
const activeDir = join(workflowDir, 'active');
|
||||
if (existsSync(activeDir)) {
|
||||
const activeSessions = await findWfsSessions(activeDir);
|
||||
for (const sessionName of activeSessions) {
|
||||
const sessionPath = join(activeDir, sessionName);
|
||||
const sessionData = readSessionData(sessionPath);
|
||||
if (sessionData) {
|
||||
result.active.push({
|
||||
...sessionData,
|
||||
path: sessionPath,
|
||||
isActive: true
|
||||
});
|
||||
// Check for review data
|
||||
if (existsSync(join(sessionPath, '.review'))) {
|
||||
result.hasReviewData = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scan archived sessions
|
||||
const archivesDir = join(workflowDir, 'archives');
|
||||
if (existsSync(archivesDir)) {
|
||||
const archivedSessions = await findWfsSessions(archivesDir);
|
||||
for (const sessionName of archivedSessions) {
|
||||
const sessionPath = join(archivesDir, sessionName);
|
||||
const sessionData = readSessionData(sessionPath);
|
||||
if (sessionData) {
|
||||
result.archived.push({
|
||||
...sessionData,
|
||||
path: sessionPath,
|
||||
isActive: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by creation date (newest first)
|
||||
result.active.sort((a, b) => new Date(b.created_at || 0) - new Date(a.created_at || 0));
|
||||
result.archived.sort((a, b) => new Date(b.archived_at || b.created_at || 0) - new Date(a.archived_at || a.created_at || 0));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find WFS-* directories in a given path
|
||||
* @param {string} dir - Directory to search
|
||||
* @returns {Promise<string[]>} - Array of session directory names
|
||||
*/
|
||||
async function findWfsSessions(dir) {
|
||||
try {
|
||||
// Use glob for cross-platform pattern matching
|
||||
const sessions = await glob('WFS-*', {
|
||||
cwd: dir,
|
||||
onlyDirectories: true,
|
||||
absolute: false
|
||||
});
|
||||
return sessions;
|
||||
} catch {
|
||||
// Fallback: manual directory listing
|
||||
try {
|
||||
const entries = readdirSync(dir, { withFileTypes: true });
|
||||
return entries
|
||||
.filter(e => e.isDirectory() && e.name.startsWith('WFS-'))
|
||||
.map(e => e.name);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse timestamp from session name
|
||||
* Supports formats: WFS-xxx-20251128172537 or WFS-xxx-20251120-170640
|
||||
* @param {string} sessionName - Session directory name
|
||||
* @returns {string|null} - ISO date string or null
|
||||
*/
|
||||
function parseTimestampFromName(sessionName) {
|
||||
// Format: 14-digit timestamp (YYYYMMDDHHmmss)
|
||||
const match14 = sessionName.match(/(\d{14})$/);
|
||||
if (match14) {
|
||||
const ts = match14[1];
|
||||
return `${ts.slice(0,4)}-${ts.slice(4,6)}-${ts.slice(6,8)}T${ts.slice(8,10)}:${ts.slice(10,12)}:${ts.slice(12,14)}Z`;
|
||||
}
|
||||
|
||||
// Format: 8-digit date + 6-digit time separated by hyphen (YYYYMMDD-HHmmss)
|
||||
const match8_6 = sessionName.match(/(\d{8})-(\d{6})$/);
|
||||
if (match8_6) {
|
||||
const d = match8_6[1];
|
||||
const t = match8_6[2];
|
||||
return `${d.slice(0,4)}-${d.slice(4,6)}-${d.slice(6,8)}T${t.slice(0,2)}:${t.slice(2,4)}:${t.slice(4,6)}Z`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer session type from session name pattern
|
||||
* @param {string} sessionName - Session directory name
|
||||
* @returns {string} - Inferred type
|
||||
*/
|
||||
function inferTypeFromName(sessionName) {
|
||||
const name = sessionName.toLowerCase();
|
||||
|
||||
if (name.includes('-review-') || name.includes('-code-review-')) {
|
||||
return 'review';
|
||||
}
|
||||
if (name.includes('-test-')) {
|
||||
return 'test';
|
||||
}
|
||||
if (name.includes('-docs-')) {
|
||||
return 'docs';
|
||||
}
|
||||
if (name.includes('-tdd-')) {
|
||||
return 'tdd';
|
||||
}
|
||||
|
||||
return 'workflow';
|
||||
}
|
||||
|
||||
/**
|
||||
* Read session data from workflow-session.json or create minimal from directory
|
||||
* @param {string} sessionPath - Path to session directory
|
||||
* @returns {Object|null} - Session data object or null if invalid
|
||||
*/
|
||||
function readSessionData(sessionPath) {
|
||||
const sessionFile = join(sessionPath, 'workflow-session.json');
|
||||
const sessionName = basename(sessionPath);
|
||||
|
||||
if (existsSync(sessionFile)) {
|
||||
try {
|
||||
const data = JSON.parse(readFileSync(sessionFile, 'utf8'));
|
||||
|
||||
// Multi-level type detection: JSON type > workflow_type > infer from name
|
||||
let type = data.type || data.workflow_type || inferTypeFromName(sessionName);
|
||||
|
||||
// Normalize workflow_type values
|
||||
if (type === 'test_session') type = 'test';
|
||||
if (type === 'implementation') type = 'workflow';
|
||||
|
||||
return {
|
||||
session_id: data.session_id || sessionName,
|
||||
project: data.project || data.description || '',
|
||||
status: data.status || 'active',
|
||||
created_at: data.created_at || data.initialized_at || data.timestamp || null,
|
||||
archived_at: data.archived_at || null,
|
||||
type: type,
|
||||
workflow_type: data.workflow_type || null // Keep original for reference
|
||||
};
|
||||
} catch {
|
||||
// Fall through to minimal session
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: create minimal session from directory info
|
||||
// Try to extract timestamp from session name first
|
||||
const timestampFromName = parseTimestampFromName(sessionName);
|
||||
const inferredType = inferTypeFromName(sessionName);
|
||||
|
||||
try {
|
||||
const stats = statSync(sessionPath);
|
||||
return {
|
||||
session_id: sessionName,
|
||||
project: '',
|
||||
status: 'unknown',
|
||||
created_at: timestampFromName || stats.birthtime.toISOString(),
|
||||
archived_at: null,
|
||||
type: inferredType,
|
||||
workflow_type: null
|
||||
};
|
||||
} catch {
|
||||
// Even if stat fails, return with name-extracted data
|
||||
if (timestampFromName) {
|
||||
return {
|
||||
session_id: sessionName,
|
||||
project: '',
|
||||
status: 'unknown',
|
||||
created_at: timestampFromName,
|
||||
archived_at: null,
|
||||
type: inferredType,
|
||||
workflow_type: null
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if session has review data
|
||||
* @param {string} sessionPath - Path to session directory
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function hasReviewData(sessionPath) {
|
||||
const reviewDir = join(sessionPath, '.review');
|
||||
return existsSync(reviewDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of task files in session
|
||||
* @param {string} sessionPath - Path to session directory
|
||||
* @returns {Promise<string[]>}
|
||||
*/
|
||||
export async function getTaskFiles(sessionPath) {
|
||||
const taskDir = join(sessionPath, '.task');
|
||||
if (!existsSync(taskDir)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
return await glob('IMPL-*.json', { cwd: taskDir, absolute: false });
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
9
ccw/src/index.js
Normal file
9
ccw/src/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* CCW - Claude Code Workflow CLI
|
||||
* Main exports for programmatic usage
|
||||
*/
|
||||
|
||||
export { run } from './cli.js';
|
||||
export { scanSessions } from './core/session-scanner.js';
|
||||
export { aggregateData } from './core/data-aggregator.js';
|
||||
export { generateDashboard } from './core/dashboard-generator.js';
|
||||
200
ccw/src/templates/dashboard-js/api.js
Normal file
200
ccw/src/templates/dashboard-js/api.js
Normal file
@@ -0,0 +1,200 @@
|
||||
// ========================================
|
||||
// API and Data Loading
|
||||
// ========================================
|
||||
// Server communication and data loading functions
|
||||
// Note: Some functions are only available in server mode
|
||||
|
||||
// ========== Data Loading ==========
|
||||
|
||||
/**
|
||||
* Load dashboard data from API (server mode only)
|
||||
* @param {string} path - Project path to load data for
|
||||
* @returns {Promise<Object|null>} Dashboard data object or null if failed
|
||||
*/
|
||||
async function loadDashboardData(path) {
|
||||
if (!window.SERVER_MODE) {
|
||||
console.warn('loadDashboardData called in static mode');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/data?path=${encodeURIComponent(path)}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
return await response.json();
|
||||
} catch (err) {
|
||||
console.error('Failed to load dashboard data:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Path Management ==========
|
||||
|
||||
/**
|
||||
* Switch to a new project path (server mode only)
|
||||
* Loads dashboard data and updates UI
|
||||
* @param {string} path - Project path to switch to
|
||||
*/
|
||||
async function switchToPath(path) {
|
||||
// Show loading state
|
||||
const container = document.getElementById('mainContent');
|
||||
container.innerHTML = '<div class="loading">Loading...</div>';
|
||||
|
||||
try {
|
||||
const data = await loadDashboardData(path);
|
||||
if (data) {
|
||||
// Update global data
|
||||
workflowData = data;
|
||||
projectPath = data.projectPath;
|
||||
recentPaths = data.recentPaths || [];
|
||||
|
||||
// Update UI
|
||||
document.getElementById('currentPath').textContent = projectPath;
|
||||
renderDashboard();
|
||||
refreshRecentPaths();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to switch path:', err);
|
||||
container.innerHTML = '<div class="error">Failed to load project data</div>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a path from recent paths list
|
||||
* @param {string} path - Path to select
|
||||
*/
|
||||
async function selectPath(path) {
|
||||
localStorage.setItem('selectedPath', path);
|
||||
|
||||
// Server mode: load data dynamically
|
||||
if (window.SERVER_MODE) {
|
||||
await switchToPath(path);
|
||||
return;
|
||||
}
|
||||
|
||||
// Static mode: show command to run
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'path-modal-overlay';
|
||||
modal.innerHTML = `
|
||||
<div class="path-modal">
|
||||
<div class="path-modal-header">
|
||||
<span class="path-modal-icon">${icons.terminal}</span>
|
||||
<h3>Run Command</h3>
|
||||
</div>
|
||||
<div class="path-modal-body">
|
||||
<p>To view the dashboard for this project, run:</p>
|
||||
<div class="path-modal-command">
|
||||
<code>ccw view -p "${path}"</code>
|
||||
<button class="copy-btn" id="copyCommandBtn">${icons.copy} <span>Copy</span></button>
|
||||
</div>
|
||||
<p class="path-modal-note" style="margin-top: 12px;">
|
||||
Or use <code>ccw serve</code> for live path switching.
|
||||
</p>
|
||||
</div>
|
||||
<div class="path-modal-footer">
|
||||
<button class="path-modal-close" onclick="this.closest('.path-modal-overlay').remove()">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Add copy handler
|
||||
document.getElementById('copyCommandBtn').addEventListener('click', function() {
|
||||
navigator.clipboard.writeText('ccw view -p "' + path + '"').then(() => {
|
||||
this.innerHTML = icons.check + ' <span>Copied!</span>';
|
||||
setTimeout(() => { this.innerHTML = icons.copy + ' <span>Copy</span>'; }, 2000);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh recent paths dropdown UI
|
||||
*/
|
||||
function refreshRecentPaths() {
|
||||
const recentContainer = document.getElementById('recentPaths');
|
||||
recentContainer.innerHTML = '';
|
||||
|
||||
recentPaths.forEach(path => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'path-item' + (path === projectPath ? ' active' : '');
|
||||
item.dataset.path = path;
|
||||
|
||||
// Path text
|
||||
const pathText = document.createElement('span');
|
||||
pathText.className = 'path-text';
|
||||
pathText.textContent = path;
|
||||
pathText.addEventListener('click', () => selectPath(path));
|
||||
item.appendChild(pathText);
|
||||
|
||||
// Delete button (only for non-current paths)
|
||||
if (path !== projectPath) {
|
||||
const deleteBtn = document.createElement('button');
|
||||
deleteBtn.className = 'path-delete-btn';
|
||||
deleteBtn.innerHTML = '×';
|
||||
deleteBtn.title = 'Remove from recent';
|
||||
deleteBtn.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
await removeRecentPathFromList(path);
|
||||
});
|
||||
item.appendChild(deleteBtn);
|
||||
}
|
||||
|
||||
recentContainer.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a path from recent paths list
|
||||
*/
|
||||
async function removeRecentPathFromList(path) {
|
||||
try {
|
||||
const response = await fetch('/api/remove-recent-path', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ path })
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
recentPaths = data.paths;
|
||||
refreshRecentPaths();
|
||||
showRefreshToast('Path removed', 'success');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to remove path:', err);
|
||||
showRefreshToast('Failed to remove path', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ========== File System Access ==========
|
||||
|
||||
/**
|
||||
* Browse for folder using File System Access API or fallback to input dialog
|
||||
*/
|
||||
async function browseForFolder() {
|
||||
// Try modern File System Access API first
|
||||
if ('showDirectoryPicker' in window) {
|
||||
try {
|
||||
const dirHandle = await window.showDirectoryPicker({
|
||||
mode: 'read',
|
||||
startIn: 'documents'
|
||||
});
|
||||
// Get the directory name (we can't get full path for security reasons)
|
||||
const dirName = dirHandle.name;
|
||||
showPathSelectedModal(dirName, dirHandle);
|
||||
return;
|
||||
} catch (err) {
|
||||
if (err.name === 'AbortError') {
|
||||
// User cancelled
|
||||
return;
|
||||
}
|
||||
console.warn('Directory picker failed:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: show input dialog
|
||||
showPathInputModal();
|
||||
}
|
||||
112
ccw/src/templates/dashboard-js/components/_conflict_tab.js
Normal file
112
ccw/src/templates/dashboard-js/components/_conflict_tab.js
Normal file
@@ -0,0 +1,112 @@
|
||||
// ==========================================
|
||||
// Conflict Resolution Tab
|
||||
// ==========================================
|
||||
|
||||
async function loadAndRenderConflictTab(session, contentArea) {
|
||||
contentArea.innerHTML = '<div class="tab-loading">Loading conflict resolution...</div>';
|
||||
|
||||
try {
|
||||
if (window.SERVER_MODE && session.path) {
|
||||
const response = await fetch(`/api/session-detail?path=${encodeURIComponent(session.path)}&type=conflict`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
contentArea.innerHTML = renderConflictCards(data.conflictResolution);
|
||||
return;
|
||||
}
|
||||
}
|
||||
contentArea.innerHTML = `
|
||||
<div class="tab-empty-state">
|
||||
<div class="empty-icon">⚖️</div>
|
||||
<div class="empty-title">No Conflict Resolution</div>
|
||||
<div class="empty-text">No conflict-resolution-decisions.json found for this session.</div>
|
||||
</div>
|
||||
`;
|
||||
} catch (err) {
|
||||
contentArea.innerHTML = `<div class="tab-error">Failed to load conflict resolution: ${err.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderConflictCards(conflictResolution) {
|
||||
if (!conflictResolution) {
|
||||
return `
|
||||
<div class="tab-empty-state">
|
||||
<div class="empty-icon">⚖️</div>
|
||||
<div class="empty-title">No Conflict Resolution</div>
|
||||
<div class="empty-text">No conflict decisions found for this session.</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
let cards = [];
|
||||
|
||||
// Header info
|
||||
cards.push(`
|
||||
<div class="conflict-tab-header">
|
||||
<h3>⚖️ Conflict Resolution Decisions</h3>
|
||||
<div class="conflict-meta-info">
|
||||
<span>Session: <strong>${escapeHtml(conflictResolution.session_id || 'N/A')}</strong></span>
|
||||
${conflictResolution.resolved_at ? `<span>Resolved: <strong>${formatDate(conflictResolution.resolved_at)}</strong></span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
// User Decisions as cards
|
||||
if (conflictResolution.user_decisions && Object.keys(conflictResolution.user_decisions).length > 0) {
|
||||
const decisions = Object.entries(conflictResolution.user_decisions);
|
||||
|
||||
cards.push(`<div class="conflict-section-title">🎯 User Decisions (${decisions.length})</div>`);
|
||||
cards.push('<div class="conflict-cards-grid">');
|
||||
|
||||
for (const [key, decision] of decisions) {
|
||||
const label = key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
||||
cards.push(`
|
||||
<div class="conflict-card decision-card">
|
||||
<div class="conflict-card-header">
|
||||
<span class="conflict-card-label">${escapeHtml(label)}</span>
|
||||
</div>
|
||||
<div class="conflict-card-choice">
|
||||
<span class="choice-label">Choice:</span>
|
||||
<span class="choice-value">${escapeHtml(decision.choice || 'N/A')}</span>
|
||||
</div>
|
||||
${decision.description ? `
|
||||
<div class="conflict-card-desc">${escapeHtml(decision.description)}</div>
|
||||
` : ''}
|
||||
${decision.implications && decision.implications.length > 0 ? `
|
||||
<div class="conflict-card-implications">
|
||||
<span class="impl-label">Implications:</span>
|
||||
<ul>
|
||||
${decision.implications.map(impl => `<li>${escapeHtml(impl)}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
cards.push('</div>');
|
||||
}
|
||||
|
||||
// Resolved Conflicts as cards
|
||||
if (conflictResolution.resolved_conflicts && conflictResolution.resolved_conflicts.length > 0) {
|
||||
cards.push(`<div class="conflict-section-title">✅ Resolved Conflicts (${conflictResolution.resolved_conflicts.length})</div>`);
|
||||
cards.push('<div class="conflict-cards-grid">');
|
||||
|
||||
for (const conflict of conflictResolution.resolved_conflicts) {
|
||||
cards.push(`
|
||||
<div class="conflict-card resolved-card">
|
||||
<div class="conflict-card-header">
|
||||
<span class="conflict-card-id">${escapeHtml(conflict.id || 'N/A')}</span>
|
||||
<span class="conflict-category-tag">${escapeHtml(conflict.category || 'General')}</span>
|
||||
</div>
|
||||
<div class="conflict-card-brief">${escapeHtml(conflict.brief || '')}</div>
|
||||
<div class="conflict-card-strategy">
|
||||
<span class="strategy-label">Strategy:</span>
|
||||
<span class="strategy-tag">${escapeHtml(conflict.strategy || 'N/A')}</span>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
cards.push('</div>');
|
||||
}
|
||||
|
||||
return `<div class="conflict-tab-content">${cards.join('')}</div>`;
|
||||
}
|
||||
54
ccw/src/templates/dashboard-js/components/_exp_helpers.js
Normal file
54
ccw/src/templates/dashboard-js/components/_exp_helpers.js
Normal file
@@ -0,0 +1,54 @@
|
||||
// Exploration helpers loaded
|
||||
|
||||
// Helper: Render exploration field with smart type detection
|
||||
function renderExpField(label, value) {
|
||||
if (value === null || value === undefined) return '';
|
||||
let rendered;
|
||||
if (typeof value === 'string') {
|
||||
rendered = `<p>${escapeHtml(value)}</p>`;
|
||||
} else if (Array.isArray(value)) {
|
||||
rendered = renderExpArray(value);
|
||||
} else if (typeof value === 'object') {
|
||||
rendered = renderExpObject(value);
|
||||
} else {
|
||||
rendered = `<p>${escapeHtml(String(value))}</p>`;
|
||||
}
|
||||
return `<div class="exp-field"><label>${escapeHtml(label)}</label>${rendered}</div>`;
|
||||
}
|
||||
|
||||
// Helper: Render array values
|
||||
function renderExpArray(arr) {
|
||||
if (!arr.length) return '<p>-</p>';
|
||||
if (typeof arr[0] === 'object' && arr[0] !== null) {
|
||||
return `<div class="exp-array-objects">${arr.map(item => {
|
||||
if (item.question) {
|
||||
return `<div class="clarification-item">
|
||||
<div class="clarification-question">${escapeHtml(item.question)}</div>
|
||||
${item.impact ? `<div class="clarification-impact">Impact: ${escapeHtml(item.impact)}</div>` : ''}
|
||||
${item.priority ? `<span class="priority-badge priority-${item.priority}">${item.priority}</span>` : ''}
|
||||
</div>`;
|
||||
}
|
||||
return `<div class="exp-object-item">${renderExpObject(item)}</div>`;
|
||||
}).join('')}</div>`;
|
||||
}
|
||||
return `<ul class="exp-list">${arr.map(item => `<li>${escapeHtml(String(item))}</li>`).join('')}</ul>`;
|
||||
}
|
||||
|
||||
// Helper: Render object values recursively
|
||||
function renderExpObject(obj) {
|
||||
if (!obj || typeof obj !== 'object') return '';
|
||||
const entries = Object.entries(obj).filter(([k]) => !k.startsWith('_'));
|
||||
if (!entries.length) return '<p>-</p>';
|
||||
return `<div class="exp-object">${entries.map(([key, val]) => {
|
||||
const label = key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
||||
if (val === null || val === undefined) return '';
|
||||
if (typeof val === 'string') {
|
||||
return `<div class="exp-obj-field"><span class="exp-obj-key">${escapeHtml(label)}:</span> <span class="exp-obj-val">${escapeHtml(val)}</span></div>`;
|
||||
} else if (Array.isArray(val)) {
|
||||
return `<div class="exp-obj-field"><span class="exp-obj-key">${escapeHtml(label)}:</span>${renderExpArray(val)}</div>`;
|
||||
} else if (typeof val === 'object') {
|
||||
return `<div class="exp-obj-nested"><span class="exp-obj-key">${escapeHtml(label)}</span>${renderExpObject(val)}</div>`;
|
||||
}
|
||||
return `<div class="exp-obj-field"><span class="exp-obj-key">${escapeHtml(label)}:</span> <span class="exp-obj-val">${escapeHtml(String(val))}</span></div>`;
|
||||
}).join('')}</div>`;
|
||||
}
|
||||
640
ccw/src/templates/dashboard-js/components/_review_tab.js
Normal file
640
ccw/src/templates/dashboard-js/components/_review_tab.js
Normal file
@@ -0,0 +1,640 @@
|
||||
// ==========================================
|
||||
// Enhanced Review Tab with Multi-Select & Preview
|
||||
// ==========================================
|
||||
|
||||
// Review tab state
|
||||
let reviewTabState = {
|
||||
allFindings: [],
|
||||
filteredFindings: [],
|
||||
selectedFindings: new Set(),
|
||||
currentFilters: {
|
||||
dimension: 'all',
|
||||
severities: new Set(),
|
||||
search: ''
|
||||
},
|
||||
sortConfig: {
|
||||
field: 'severity',
|
||||
order: 'desc'
|
||||
},
|
||||
previewFinding: null,
|
||||
sessionPath: null,
|
||||
sessionId: null
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// Main Review Tab Render
|
||||
// ==========================================
|
||||
|
||||
function renderReviewContent(review) {
|
||||
if (!review || !review.dimensions) {
|
||||
return `
|
||||
<div class="tab-empty-state">
|
||||
<div class="empty-icon">🔍</div>
|
||||
<div class="empty-title">No Review Data</div>
|
||||
<div class="empty-text">No review findings in .review/</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Convert dimensions object to flat findings array
|
||||
const findings = [];
|
||||
let findingIndex = 0;
|
||||
|
||||
Object.entries(review.dimensions).forEach(([dim, rawFindings]) => {
|
||||
let dimFindings = [];
|
||||
if (Array.isArray(rawFindings)) {
|
||||
dimFindings = rawFindings;
|
||||
} else if (rawFindings && typeof rawFindings === 'object') {
|
||||
if (Array.isArray(rawFindings.findings)) {
|
||||
dimFindings = rawFindings.findings;
|
||||
}
|
||||
}
|
||||
|
||||
dimFindings.forEach(f => {
|
||||
findings.push({
|
||||
id: f.id || `finding-${findingIndex++}`,
|
||||
title: f.title || 'Finding',
|
||||
description: f.description || '',
|
||||
severity: (f.severity || 'medium').toLowerCase(),
|
||||
dimension: dim,
|
||||
category: f.category || '',
|
||||
file: f.file || '',
|
||||
line: f.line || '',
|
||||
code_context: f.code_context || f.snippet || '',
|
||||
recommendations: f.recommendations || (f.recommendation ? [f.recommendation] : []),
|
||||
root_cause: f.root_cause || '',
|
||||
impact: f.impact || '',
|
||||
references: f.references || [],
|
||||
metadata: f.metadata || {}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (findings.length === 0) {
|
||||
return `
|
||||
<div class="tab-empty-state">
|
||||
<div class="empty-icon">🔍</div>
|
||||
<div class="empty-title">No Findings</div>
|
||||
<div class="empty-text">No review findings found.</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Store findings in state
|
||||
reviewTabState.allFindings = findings;
|
||||
reviewTabState.filteredFindings = [...findings];
|
||||
reviewTabState.selectedFindings.clear();
|
||||
reviewTabState.previewFinding = null;
|
||||
|
||||
// Get dimensions for tabs
|
||||
const dimensions = [...new Set(findings.map(f => f.dimension))];
|
||||
|
||||
// Count by severity
|
||||
const severityCounts = {
|
||||
critical: findings.filter(f => f.severity === 'critical').length,
|
||||
high: findings.filter(f => f.severity === 'high').length,
|
||||
medium: findings.filter(f => f.severity === 'medium').length,
|
||||
low: findings.filter(f => f.severity === 'low').length
|
||||
};
|
||||
|
||||
return `
|
||||
<div class="review-enhanced-container">
|
||||
<!-- Header with Stats & Controls -->
|
||||
<div class="review-header-bar">
|
||||
<div class="review-severity-stats">
|
||||
<span class="severity-stat critical" onclick="filterReviewBySeverity('critical')" title="Filter Critical">
|
||||
🔴 ${severityCounts.critical}
|
||||
</span>
|
||||
<span class="severity-stat high" onclick="filterReviewBySeverity('high')" title="Filter High">
|
||||
🟠 ${severityCounts.high}
|
||||
</span>
|
||||
<span class="severity-stat medium" onclick="filterReviewBySeverity('medium')" title="Filter Medium">
|
||||
🟡 ${severityCounts.medium}
|
||||
</span>
|
||||
<span class="severity-stat low" onclick="filterReviewBySeverity('low')" title="Filter Low">
|
||||
🟢 ${severityCounts.low}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="review-search-box">
|
||||
<input type="text"
|
||||
id="reviewSearchInput"
|
||||
placeholder="Search findings..."
|
||||
oninput="onReviewSearch(this.value)">
|
||||
</div>
|
||||
|
||||
<div class="review-selection-controls">
|
||||
<span class="selection-counter" id="reviewSelectionCounter">0 selected</span>
|
||||
<button class="btn-mini" onclick="selectAllReviewFindings()">Select All</button>
|
||||
<button class="btn-mini" onclick="selectVisibleReviewFindings()">Select Visible</button>
|
||||
<button class="btn-mini" onclick="clearReviewSelection()">Clear</button>
|
||||
</div>
|
||||
|
||||
<button class="btn-export-fix" id="exportFixBtn" onclick="exportReviewFixJson()" disabled>
|
||||
🔧 Export Fix JSON
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Filter Bar -->
|
||||
<div class="review-filter-bar">
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">Severity:</span>
|
||||
<div class="filter-chips">
|
||||
<label class="filter-chip" id="filter-critical">
|
||||
<input type="checkbox" onchange="toggleReviewSeverityFilter('critical')">
|
||||
<span>Critical</span>
|
||||
</label>
|
||||
<label class="filter-chip" id="filter-high">
|
||||
<input type="checkbox" onchange="toggleReviewSeverityFilter('high')">
|
||||
<span>High</span>
|
||||
</label>
|
||||
<label class="filter-chip" id="filter-medium">
|
||||
<input type="checkbox" onchange="toggleReviewSeverityFilter('medium')">
|
||||
<span>Medium</span>
|
||||
</label>
|
||||
<label class="filter-chip" id="filter-low">
|
||||
<input type="checkbox" onchange="toggleReviewSeverityFilter('low')">
|
||||
<span>Low</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<span class="filter-label">Sort:</span>
|
||||
<select id="reviewSortSelect" class="sort-select" onchange="sortReviewFindings()">
|
||||
<option value="severity">By Severity</option>
|
||||
<option value="dimension">By Dimension</option>
|
||||
<option value="file">By File</option>
|
||||
</select>
|
||||
<button class="btn-sort-order" id="reviewSortOrderBtn" onclick="toggleReviewSortOrder()">
|
||||
<span id="reviewSortOrderIcon">↓</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button class="btn-mini" onclick="resetReviewFilters()">Reset Filters</button>
|
||||
</div>
|
||||
|
||||
<!-- Dimension Tabs -->
|
||||
<div class="review-dimension-tabs">
|
||||
<button class="dim-tab active" data-dimension="all" onclick="filterReviewByDimension('all')">
|
||||
All (${findings.length})
|
||||
</button>
|
||||
${dimensions.map(dim => `
|
||||
<button class="dim-tab" data-dimension="${dim}" onclick="filterReviewByDimension('${dim}')">
|
||||
${escapeHtml(dim)} (${findings.filter(f => f.dimension === dim).length})
|
||||
</button>
|
||||
`).join('')}
|
||||
</div>
|
||||
|
||||
<!-- Split Panel: List + Preview -->
|
||||
<div class="review-split-panel">
|
||||
<!-- Left: Findings List -->
|
||||
<div class="review-findings-panel">
|
||||
<div class="findings-list-header">
|
||||
<span id="reviewFindingsCount">${findings.length} findings</span>
|
||||
</div>
|
||||
<div class="review-findings-list" id="reviewFindingsList">
|
||||
${renderReviewFindingsList(findings)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right: Preview Panel -->
|
||||
<div class="review-preview-panel" id="reviewPreviewPanel">
|
||||
<div class="preview-empty-state">
|
||||
<div class="preview-icon">👆</div>
|
||||
<div class="preview-text">Click on a finding to preview details</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Findings List Rendering
|
||||
// ==========================================
|
||||
|
||||
function renderReviewFindingsList(findings) {
|
||||
if (findings.length === 0) {
|
||||
return `
|
||||
<div class="findings-empty">
|
||||
<span class="empty-icon">✨</span>
|
||||
<span>No findings match your filters</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return findings.map(finding => `
|
||||
<div class="review-finding-item ${finding.severity} ${reviewTabState.selectedFindings.has(finding.id) ? 'selected' : ''}"
|
||||
data-finding-id="${finding.id}"
|
||||
onclick="previewReviewFinding('${finding.id}')">
|
||||
<input type="checkbox"
|
||||
class="finding-checkbox"
|
||||
${reviewTabState.selectedFindings.has(finding.id) ? 'checked' : ''}
|
||||
onclick="toggleReviewFindingSelection('${finding.id}', event)">
|
||||
<div class="finding-content">
|
||||
<div class="finding-top-row">
|
||||
<span class="severity-badge ${finding.severity}">${finding.severity}</span>
|
||||
<span class="dimension-badge">${escapeHtml(finding.dimension)}</span>
|
||||
</div>
|
||||
<div class="finding-title">${escapeHtml(finding.title)}</div>
|
||||
${finding.file ? `<div class="finding-file">📄 ${escapeHtml(finding.file)}${finding.line ? ':' + finding.line : ''}</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Preview Panel Rendering
|
||||
// ==========================================
|
||||
|
||||
function previewReviewFinding(findingId) {
|
||||
const finding = reviewTabState.allFindings.find(f => f.id === findingId);
|
||||
if (!finding) return;
|
||||
|
||||
reviewTabState.previewFinding = finding;
|
||||
|
||||
// Update active state in list
|
||||
document.querySelectorAll('.review-finding-item').forEach(item => {
|
||||
item.classList.toggle('previewing', item.dataset.findingId === findingId);
|
||||
});
|
||||
|
||||
const previewPanel = document.getElementById('reviewPreviewPanel');
|
||||
if (!previewPanel) return;
|
||||
|
||||
previewPanel.innerHTML = `
|
||||
<div class="preview-content">
|
||||
<div class="preview-header">
|
||||
<div class="preview-badges">
|
||||
<span class="severity-badge ${finding.severity}">${finding.severity}</span>
|
||||
<span class="dimension-badge">${escapeHtml(finding.dimension)}</span>
|
||||
${finding.category ? `<span class="category-badge">${escapeHtml(finding.category)}</span>` : ''}
|
||||
</div>
|
||||
<button class="btn-select-finding ${reviewTabState.selectedFindings.has(finding.id) ? 'selected' : ''}"
|
||||
onclick="toggleReviewFindingSelection('${finding.id}', event)">
|
||||
${reviewTabState.selectedFindings.has(finding.id) ? '✓ Selected' : '+ Select for Fix'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h3 class="preview-title">${escapeHtml(finding.title)}</h3>
|
||||
|
||||
${finding.file ? `
|
||||
<div class="preview-section">
|
||||
<div class="preview-section-title">📄 Location</div>
|
||||
<div class="preview-location">
|
||||
<code>${escapeHtml(finding.file)}${finding.line ? ':' + finding.line : ''}</code>
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="preview-section">
|
||||
<div class="preview-section-title">📝 Description</div>
|
||||
<div class="preview-description">${escapeHtml(finding.description)}</div>
|
||||
</div>
|
||||
|
||||
${finding.code_context ? `
|
||||
<div class="preview-section">
|
||||
<div class="preview-section-title">💻 Code Context</div>
|
||||
<pre class="preview-code">${escapeHtml(finding.code_context)}</pre>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${finding.recommendations && finding.recommendations.length > 0 ? `
|
||||
<div class="preview-section">
|
||||
<div class="preview-section-title">✅ Recommendations</div>
|
||||
<ul class="preview-recommendations">
|
||||
${finding.recommendations.map(r => `<li>${escapeHtml(r)}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${finding.root_cause ? `
|
||||
<div class="preview-section">
|
||||
<div class="preview-section-title">🔍 Root Cause</div>
|
||||
<div class="preview-root-cause">${escapeHtml(finding.root_cause)}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${finding.impact ? `
|
||||
<div class="preview-section">
|
||||
<div class="preview-section-title">⚠️ Impact</div>
|
||||
<div class="preview-impact">${escapeHtml(finding.impact)}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${finding.references && finding.references.length > 0 ? `
|
||||
<div class="preview-section">
|
||||
<div class="preview-section-title">🔗 References</div>
|
||||
<ul class="preview-references">
|
||||
${finding.references.map(ref => {
|
||||
const isUrl = ref.startsWith('http');
|
||||
return `<li>${isUrl ? `<a href="${ref}" target="_blank">${ref}</a>` : ref}</li>`;
|
||||
}).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${finding.metadata && Object.keys(finding.metadata).length > 0 ? `
|
||||
<div class="preview-section">
|
||||
<div class="preview-section-title">ℹ️ Metadata</div>
|
||||
<div class="preview-metadata">
|
||||
${Object.entries(finding.metadata).map(([key, value]) => `
|
||||
<div class="metadata-item">
|
||||
<span class="meta-key">${escapeHtml(key)}:</span>
|
||||
<span class="meta-value">${escapeHtml(String(value))}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Selection Management
|
||||
// ==========================================
|
||||
|
||||
function toggleReviewFindingSelection(findingId, event) {
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
if (reviewTabState.selectedFindings.has(findingId)) {
|
||||
reviewTabState.selectedFindings.delete(findingId);
|
||||
} else {
|
||||
reviewTabState.selectedFindings.add(findingId);
|
||||
}
|
||||
|
||||
updateReviewSelectionUI();
|
||||
|
||||
// Update preview panel button if this finding is being previewed
|
||||
if (reviewTabState.previewFinding && reviewTabState.previewFinding.id === findingId) {
|
||||
previewReviewFinding(findingId);
|
||||
}
|
||||
}
|
||||
|
||||
function selectAllReviewFindings() {
|
||||
reviewTabState.allFindings.forEach(f => reviewTabState.selectedFindings.add(f.id));
|
||||
updateReviewSelectionUI();
|
||||
}
|
||||
|
||||
function selectVisibleReviewFindings() {
|
||||
reviewTabState.filteredFindings.forEach(f => reviewTabState.selectedFindings.add(f.id));
|
||||
updateReviewSelectionUI();
|
||||
}
|
||||
|
||||
function selectReviewBySeverity(severity) {
|
||||
reviewTabState.allFindings
|
||||
.filter(f => f.severity === severity)
|
||||
.forEach(f => reviewTabState.selectedFindings.add(f.id));
|
||||
updateReviewSelectionUI();
|
||||
}
|
||||
|
||||
function clearReviewSelection() {
|
||||
reviewTabState.selectedFindings.clear();
|
||||
updateReviewSelectionUI();
|
||||
}
|
||||
|
||||
function updateReviewSelectionUI() {
|
||||
// Update counter
|
||||
const counter = document.getElementById('reviewSelectionCounter');
|
||||
if (counter) {
|
||||
counter.textContent = `${reviewTabState.selectedFindings.size} selected`;
|
||||
}
|
||||
|
||||
// Update export button
|
||||
const exportBtn = document.getElementById('exportFixBtn');
|
||||
if (exportBtn) {
|
||||
exportBtn.disabled = reviewTabState.selectedFindings.size === 0;
|
||||
}
|
||||
|
||||
// Update checkbox states in list
|
||||
document.querySelectorAll('.review-finding-item').forEach(item => {
|
||||
const findingId = item.dataset.findingId;
|
||||
const isSelected = reviewTabState.selectedFindings.has(findingId);
|
||||
item.classList.toggle('selected', isSelected);
|
||||
const checkbox = item.querySelector('.finding-checkbox');
|
||||
if (checkbox) {
|
||||
checkbox.checked = isSelected;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Filtering & Sorting
|
||||
// ==========================================
|
||||
|
||||
function filterReviewByDimension(dimension) {
|
||||
reviewTabState.currentFilters.dimension = dimension;
|
||||
|
||||
// Update tab active state
|
||||
document.querySelectorAll('.dim-tab').forEach(tab => {
|
||||
tab.classList.toggle('active', tab.dataset.dimension === dimension);
|
||||
});
|
||||
|
||||
applyReviewFilters();
|
||||
}
|
||||
|
||||
function filterReviewBySeverity(severity) {
|
||||
// Toggle the severity filter
|
||||
if (reviewTabState.currentFilters.severities.has(severity)) {
|
||||
reviewTabState.currentFilters.severities.delete(severity);
|
||||
} else {
|
||||
reviewTabState.currentFilters.severities.add(severity);
|
||||
}
|
||||
|
||||
// Update filter chip UI
|
||||
const filterChip = document.getElementById(`filter-${severity}`);
|
||||
if (filterChip) {
|
||||
filterChip.classList.toggle('active', reviewTabState.currentFilters.severities.has(severity));
|
||||
const checkbox = filterChip.querySelector('input[type="checkbox"]');
|
||||
if (checkbox) {
|
||||
checkbox.checked = reviewTabState.currentFilters.severities.has(severity);
|
||||
}
|
||||
}
|
||||
|
||||
applyReviewFilters();
|
||||
}
|
||||
|
||||
function toggleReviewSeverityFilter(severity) {
|
||||
filterReviewBySeverity(severity);
|
||||
}
|
||||
|
||||
function onReviewSearch(searchText) {
|
||||
reviewTabState.currentFilters.search = searchText.toLowerCase();
|
||||
applyReviewFilters();
|
||||
}
|
||||
|
||||
function applyReviewFilters() {
|
||||
reviewTabState.filteredFindings = reviewTabState.allFindings.filter(finding => {
|
||||
// Dimension filter
|
||||
if (reviewTabState.currentFilters.dimension !== 'all') {
|
||||
if (finding.dimension !== reviewTabState.currentFilters.dimension) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Severity filter (multi-select)
|
||||
if (reviewTabState.currentFilters.severities.size > 0) {
|
||||
if (!reviewTabState.currentFilters.severities.has(finding.severity)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Search filter
|
||||
if (reviewTabState.currentFilters.search) {
|
||||
const searchText = `${finding.title} ${finding.description} ${finding.file} ${finding.category}`.toLowerCase();
|
||||
if (!searchText.includes(reviewTabState.currentFilters.search)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
sortReviewFindings();
|
||||
}
|
||||
|
||||
function sortReviewFindings() {
|
||||
const sortBy = document.getElementById('reviewSortSelect')?.value || 'severity';
|
||||
reviewTabState.sortConfig.field = sortBy;
|
||||
|
||||
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
||||
|
||||
reviewTabState.filteredFindings.sort((a, b) => {
|
||||
let comparison = 0;
|
||||
|
||||
if (sortBy === 'severity') {
|
||||
comparison = severityOrder[a.severity] - severityOrder[b.severity];
|
||||
} else if (sortBy === 'dimension') {
|
||||
comparison = a.dimension.localeCompare(b.dimension);
|
||||
} else if (sortBy === 'file') {
|
||||
comparison = (a.file || '').localeCompare(b.file || '');
|
||||
}
|
||||
|
||||
return reviewTabState.sortConfig.order === 'asc' ? comparison : -comparison;
|
||||
});
|
||||
|
||||
renderFilteredReviewFindings();
|
||||
}
|
||||
|
||||
function toggleReviewSortOrder() {
|
||||
reviewTabState.sortConfig.order = reviewTabState.sortConfig.order === 'asc' ? 'desc' : 'asc';
|
||||
|
||||
const icon = document.getElementById('reviewSortOrderIcon');
|
||||
if (icon) {
|
||||
icon.textContent = reviewTabState.sortConfig.order === 'asc' ? '↑' : '↓';
|
||||
}
|
||||
|
||||
sortReviewFindings();
|
||||
}
|
||||
|
||||
function resetReviewFilters() {
|
||||
// Reset state
|
||||
reviewTabState.currentFilters.dimension = 'all';
|
||||
reviewTabState.currentFilters.severities.clear();
|
||||
reviewTabState.currentFilters.search = '';
|
||||
reviewTabState.sortConfig.field = 'severity';
|
||||
reviewTabState.sortConfig.order = 'desc';
|
||||
|
||||
// Reset UI
|
||||
document.querySelectorAll('.dim-tab').forEach(tab => {
|
||||
tab.classList.toggle('active', tab.dataset.dimension === 'all');
|
||||
});
|
||||
|
||||
document.querySelectorAll('.filter-chip').forEach(chip => {
|
||||
chip.classList.remove('active');
|
||||
const checkbox = chip.querySelector('input[type="checkbox"]');
|
||||
if (checkbox) checkbox.checked = false;
|
||||
});
|
||||
|
||||
const searchInput = document.getElementById('reviewSearchInput');
|
||||
if (searchInput) searchInput.value = '';
|
||||
|
||||
const sortSelect = document.getElementById('reviewSortSelect');
|
||||
if (sortSelect) sortSelect.value = 'severity';
|
||||
|
||||
const sortIcon = document.getElementById('reviewSortOrderIcon');
|
||||
if (sortIcon) sortIcon.textContent = '↓';
|
||||
|
||||
// Re-apply filters
|
||||
reviewTabState.filteredFindings = [...reviewTabState.allFindings];
|
||||
sortReviewFindings();
|
||||
}
|
||||
|
||||
function renderFilteredReviewFindings() {
|
||||
const listContainer = document.getElementById('reviewFindingsList');
|
||||
const countEl = document.getElementById('reviewFindingsCount');
|
||||
|
||||
if (listContainer) {
|
||||
listContainer.innerHTML = renderReviewFindingsList(reviewTabState.filteredFindings);
|
||||
}
|
||||
|
||||
if (countEl) {
|
||||
countEl.textContent = `${reviewTabState.filteredFindings.length} findings`;
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Export Fix JSON
|
||||
// ==========================================
|
||||
|
||||
function exportReviewFixJson() {
|
||||
if (reviewTabState.selectedFindings.size === 0) {
|
||||
showToast('Please select at least one finding to export', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedFindingsData = reviewTabState.allFindings.filter(f =>
|
||||
reviewTabState.selectedFindings.has(f.id)
|
||||
);
|
||||
|
||||
const session = sessionDataStore[currentSessionDetailKey];
|
||||
const sessionId = session?.session_id || 'unknown';
|
||||
const exportId = `fix-${Date.now()}`;
|
||||
|
||||
const exportData = {
|
||||
export_id: exportId,
|
||||
export_timestamp: new Date().toISOString(),
|
||||
review_id: `review-${sessionId}`,
|
||||
session_id: sessionId,
|
||||
findings_count: selectedFindingsData.length,
|
||||
findings: selectedFindingsData.map(f => ({
|
||||
id: f.id,
|
||||
title: f.title,
|
||||
description: f.description,
|
||||
severity: f.severity,
|
||||
dimension: f.dimension,
|
||||
category: f.category || 'uncategorized',
|
||||
file: f.file,
|
||||
line: f.line,
|
||||
code_context: f.code_context || null,
|
||||
recommendations: f.recommendations || [],
|
||||
root_cause: f.root_cause || null
|
||||
}))
|
||||
};
|
||||
|
||||
// Convert to JSON and download
|
||||
const jsonStr = JSON.stringify(exportData, null, 2);
|
||||
const blob = new Blob([jsonStr], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
const filename = `fix-export-${exportId}.json`;
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
// Show success notification
|
||||
const severityCounts = {
|
||||
critical: selectedFindingsData.filter(f => f.severity === 'critical').length,
|
||||
high: selectedFindingsData.filter(f => f.severity === 'high').length,
|
||||
medium: selectedFindingsData.filter(f => f.severity === 'medium').length,
|
||||
low: selectedFindingsData.filter(f => f.severity === 'low').length
|
||||
};
|
||||
|
||||
showToast(`Exported ${selectedFindingsData.length} findings for fixing (Critical: ${severityCounts.critical}, High: ${severityCounts.high}, Medium: ${severityCounts.medium}, Low: ${severityCounts.low})`, 'success');
|
||||
}
|
||||
398
ccw/src/templates/dashboard-js/components/carousel.js
Normal file
398
ccw/src/templates/dashboard-js/components/carousel.js
Normal file
@@ -0,0 +1,398 @@
|
||||
// ==========================================
|
||||
// CAROUSEL COMPONENT
|
||||
// ==========================================
|
||||
// Active session carousel with detailed task info and smooth transitions
|
||||
|
||||
let carouselIndex = 0;
|
||||
let carouselSessions = [];
|
||||
let carouselInterval = null;
|
||||
let carouselPaused = false;
|
||||
const CAROUSEL_INTERVAL_MS = 5000; // 5 seconds
|
||||
|
||||
function initCarousel() {
|
||||
const prevBtn = document.getElementById('carouselPrev');
|
||||
const nextBtn = document.getElementById('carouselNext');
|
||||
const pauseBtn = document.getElementById('carouselPause');
|
||||
|
||||
if (prevBtn) {
|
||||
prevBtn.addEventListener('click', () => {
|
||||
carouselPrev();
|
||||
resetCarouselInterval();
|
||||
});
|
||||
}
|
||||
|
||||
if (nextBtn) {
|
||||
nextBtn.addEventListener('click', () => {
|
||||
carouselNext();
|
||||
resetCarouselInterval();
|
||||
});
|
||||
}
|
||||
|
||||
if (pauseBtn) {
|
||||
pauseBtn.addEventListener('click', toggleCarouselPause);
|
||||
}
|
||||
}
|
||||
|
||||
function updateCarousel() {
|
||||
// Get active sessions from workflowData
|
||||
const previousSessions = carouselSessions;
|
||||
const previousIndex = carouselIndex;
|
||||
const previousSessionId = previousSessions[previousIndex]?.session_id;
|
||||
|
||||
carouselSessions = workflowData.activeSessions || [];
|
||||
|
||||
// Try to preserve current position
|
||||
if (previousSessionId && carouselSessions.length > 0) {
|
||||
// Find if the same session still exists
|
||||
const newIndex = carouselSessions.findIndex(s => s.session_id === previousSessionId);
|
||||
if (newIndex !== -1) {
|
||||
carouselIndex = newIndex;
|
||||
} else if (previousIndex < carouselSessions.length) {
|
||||
// Keep same index if valid
|
||||
carouselIndex = previousIndex;
|
||||
} else {
|
||||
// Reset to last valid index
|
||||
carouselIndex = Math.max(0, carouselSessions.length - 1);
|
||||
}
|
||||
} else {
|
||||
carouselIndex = 0;
|
||||
}
|
||||
|
||||
renderCarouselDots();
|
||||
renderCarouselSlide('none');
|
||||
startCarouselInterval();
|
||||
}
|
||||
|
||||
function renderCarouselDots() {
|
||||
const dotsContainer = document.getElementById('carouselDots');
|
||||
if (!dotsContainer) return;
|
||||
|
||||
if (carouselSessions.length === 0) {
|
||||
dotsContainer.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
dotsContainer.innerHTML = carouselSessions.map((_, index) => `
|
||||
<button class="carousel-dot w-2 h-2 rounded-full transition-all duration-200 ${index === carouselIndex ? 'bg-primary w-4' : 'bg-muted-foreground/40 hover:bg-muted-foreground/60'}"
|
||||
onclick="carouselGoToIndex(${index})" title="Session ${index + 1}"></button>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function updateActiveDot() {
|
||||
const dots = document.querySelectorAll('.carousel-dot');
|
||||
dots.forEach((dot, index) => {
|
||||
if (index === carouselIndex) {
|
||||
dot.classList.remove('bg-muted-foreground/40', 'hover:bg-muted-foreground/60', 'w-2');
|
||||
dot.classList.add('bg-primary', 'w-4');
|
||||
} else {
|
||||
dot.classList.remove('bg-primary', 'w-4');
|
||||
dot.classList.add('bg-muted-foreground/40', 'hover:bg-muted-foreground/60', 'w-2');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function carouselGoToIndex(index) {
|
||||
if (index < 0 || index >= carouselSessions.length) return;
|
||||
const direction = index > carouselIndex ? 'left' : (index < carouselIndex ? 'right' : 'none');
|
||||
carouselIndex = index;
|
||||
renderCarouselSlide(direction);
|
||||
updateActiveDot();
|
||||
resetCarouselInterval();
|
||||
}
|
||||
|
||||
function renderCarouselSlide(direction = 'none') {
|
||||
const container = document.getElementById('carouselContent');
|
||||
|
||||
if (!container) return;
|
||||
|
||||
if (carouselSessions.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="carousel-empty flex items-center justify-center h-full text-muted-foreground">
|
||||
<div class="text-center">
|
||||
<div class="text-3xl mb-2">🎯</div>
|
||||
<p class="text-sm">No active sessions</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const session = carouselSessions[carouselIndex];
|
||||
const sessionType = session.type || 'workflow';
|
||||
|
||||
// Use simplified view for review sessions
|
||||
if (sessionType === 'review') {
|
||||
renderReviewCarouselSlide(container, session, direction);
|
||||
return;
|
||||
}
|
||||
|
||||
const tasks = session.tasks || [];
|
||||
const completed = tasks.filter(t => t.status === 'completed').length;
|
||||
const inProgress = tasks.filter(t => t.status === 'in_progress').length;
|
||||
const pending = tasks.filter(t => t.status === 'pending').length;
|
||||
const taskCount = session.taskCount || tasks.length;
|
||||
const progress = taskCount > 0 ? Math.round((completed / taskCount) * 100) : 0;
|
||||
|
||||
// Get session type badge
|
||||
const typeBadgeClass = getSessionTypeBadgeClass(sessionType);
|
||||
|
||||
const sessionKey = `session-${session.session_id}`.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||
|
||||
// Animation class based on direction
|
||||
const animClass = direction === 'left' ? 'carousel-slide-left' :
|
||||
direction === 'right' ? 'carousel-slide-right' : 'carousel-fade-in';
|
||||
|
||||
// Get recent task activity
|
||||
const recentTasks = getRecentTaskActivity(tasks);
|
||||
|
||||
// Format timestamps
|
||||
const createdTime = session.created_at ? formatRelativeTime(session.created_at) : '';
|
||||
const updatedTime = session.updated_at ? formatRelativeTime(session.updated_at) : '';
|
||||
|
||||
// Get more tasks for display (up to 4)
|
||||
const displayTasks = getRecentTaskActivity(tasks, 4);
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="carousel-slide ${animClass} h-full">
|
||||
<div class="session-card h-full p-3 cursor-pointer hover:bg-hover/30 transition-colors"
|
||||
onclick="showSessionDetailPage('${sessionKey}')">
|
||||
|
||||
<!-- Two Column Layout -->
|
||||
<div class="flex gap-4 h-full">
|
||||
|
||||
<!-- Left Column: Session Info -->
|
||||
<div class="flex-1 flex flex-col min-w-0">
|
||||
<!-- Session Header -->
|
||||
<div class="flex items-center gap-2 mb-2 flex-wrap">
|
||||
<span class="px-2 py-0.5 text-xs font-medium rounded ${typeBadgeClass}">${sessionType}</span>
|
||||
${inProgress > 0 ? `<span class="inline-flex items-center gap-1 text-xs text-warning"><span class="w-2 h-2 rounded-full bg-warning animate-pulse"></span>${inProgress} running</span>` : ''}
|
||||
</div>
|
||||
<h4 class="font-semibold text-foreground text-sm line-clamp-1 mb-2" title="${escapeHtml(session.session_id)}">${escapeHtml(session.session_id)}</h4>
|
||||
|
||||
<!-- Progress -->
|
||||
<div class="mb-2">
|
||||
<div class="flex items-center justify-between text-xs mb-1">
|
||||
<span class="text-muted-foreground">Progress</span>
|
||||
<span class="text-foreground font-medium">${completed}/${taskCount}</span>
|
||||
</div>
|
||||
<div class="h-1.5 bg-muted rounded-full overflow-hidden">
|
||||
<div class="h-full rounded-full transition-all duration-500 ${progress === 100 ? 'bg-success' : 'bg-primary'}" style="width: ${progress}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Task Status Summary -->
|
||||
<div class="flex items-center gap-3 text-xs mb-2">
|
||||
<span class="flex items-center gap-1"><span class="w-1.5 h-1.5 rounded-full bg-success"></span>${completed}</span>
|
||||
<span class="flex items-center gap-1"><span class="w-1.5 h-1.5 rounded-full bg-warning ${inProgress > 0 ? 'animate-pulse' : ''}"></span>${inProgress}</span>
|
||||
<span class="flex items-center gap-1"><span class="w-1.5 h-1.5 rounded-full bg-muted-foreground"></span>${pending}</span>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="mt-auto flex items-center gap-3 text-xs text-muted-foreground">
|
||||
<span>📅 ${createdTime}</span>
|
||||
${updatedTime && updatedTime !== createdTime ? `<span>🔄 ${updatedTime}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Task List -->
|
||||
<div class="w-[45%] flex flex-col border-l border-border pl-3">
|
||||
<div class="text-xs font-medium text-muted-foreground mb-1.5">Recent Tasks</div>
|
||||
<div class="task-list flex-1 space-y-1 overflow-hidden">
|
||||
${displayTasks.length > 0 ? displayTasks.map(task => `
|
||||
<div class="flex items-center gap-1.5 text-xs">
|
||||
<span class="shrink-0">${getTaskStatusEmoji(task.status)}</span>
|
||||
<span class="truncate flex-1 ${task.status === 'in_progress' ? 'text-foreground font-medium' : 'text-muted-foreground'}">${escapeHtml(task.title || task.id || 'Task')}</span>
|
||||
</div>
|
||||
`).join('') : `
|
||||
<div class="text-xs text-muted-foreground">No tasks yet</div>
|
||||
`}
|
||||
</div>
|
||||
<!-- Progress percentage -->
|
||||
<div class="mt-auto pt-1 text-right">
|
||||
<span class="text-xl font-bold ${progress === 100 ? 'text-success' : 'text-primary'}">${progress}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Store session data for navigation
|
||||
if (!sessionDataStore[sessionKey]) {
|
||||
sessionDataStore[sessionKey] = session;
|
||||
}
|
||||
}
|
||||
|
||||
// Simplified carousel slide for review sessions
|
||||
function renderReviewCarouselSlide(container, session, direction) {
|
||||
const typeBadgeClass = getSessionTypeBadgeClass('review');
|
||||
const sessionKey = `session-${session.session_id}`.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||
const animClass = direction === 'left' ? 'carousel-slide-left' :
|
||||
direction === 'right' ? 'carousel-slide-right' : 'carousel-fade-in';
|
||||
const createdTime = session.created_at ? formatRelativeTime(session.created_at) : '';
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="carousel-slide ${animClass} h-full">
|
||||
<div class="session-card h-full p-3 cursor-pointer hover:bg-hover/30 transition-colors"
|
||||
onclick="showSessionDetailPage('${sessionKey}')">
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="px-2 py-0.5 text-xs font-medium rounded ${typeBadgeClass}">review</span>
|
||||
</div>
|
||||
<h4 class="font-semibold text-foreground text-sm line-clamp-2 mb-3" title="${escapeHtml(session.session_id)}">${escapeHtml(session.session_id)}</h4>
|
||||
|
||||
<!-- Simple info -->
|
||||
<div class="flex-1 flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<div class="text-3xl mb-1">🔍</div>
|
||||
<div class="text-xs text-muted-foreground">Click to view findings</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="mt-auto text-xs text-muted-foreground">
|
||||
📅 ${createdTime}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Store session data for navigation
|
||||
if (!sessionDataStore[sessionKey]) {
|
||||
sessionDataStore[sessionKey] = session;
|
||||
}
|
||||
}
|
||||
|
||||
function getSessionTypeBadgeClass(type) {
|
||||
const classes = {
|
||||
'tdd': 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400',
|
||||
'review': 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400',
|
||||
'test': 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400',
|
||||
'docs': 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400',
|
||||
'workflow': 'bg-primary-light text-primary'
|
||||
};
|
||||
return classes[type] || classes['workflow'];
|
||||
}
|
||||
|
||||
function getRecentTaskActivity(tasks, limit = 4) {
|
||||
if (!tasks || tasks.length === 0) return [];
|
||||
|
||||
// Get in_progress tasks first, then most recently updated
|
||||
const sorted = [...tasks].sort((a, b) => {
|
||||
// in_progress first
|
||||
if (a.status === 'in_progress' && b.status !== 'in_progress') return -1;
|
||||
if (b.status === 'in_progress' && a.status !== 'in_progress') return 1;
|
||||
// Then by updated_at
|
||||
const timeA = a.updated_at || a.created_at || '';
|
||||
const timeB = b.updated_at || b.created_at || '';
|
||||
return timeB.localeCompare(timeA);
|
||||
});
|
||||
|
||||
// Return top N tasks
|
||||
return sorted.slice(0, limit);
|
||||
}
|
||||
|
||||
function getTaskStatusEmoji(status) {
|
||||
const emojis = {
|
||||
'completed': '✅',
|
||||
'in_progress': '🔄',
|
||||
'pending': '⏸️',
|
||||
'blocked': '🚫'
|
||||
};
|
||||
return emojis[status] || '📋';
|
||||
}
|
||||
|
||||
function getTaskStatusIcon(status) {
|
||||
return status === 'in_progress' ? 'animate-spin-slow' : '';
|
||||
}
|
||||
|
||||
function formatRelativeTime(dateString) {
|
||||
if (!dateString) return '';
|
||||
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diffMs = now - date;
|
||||
const diffSecs = Math.floor(diffMs / 1000);
|
||||
const diffMins = Math.floor(diffSecs / 60);
|
||||
const diffHours = Math.floor(diffMins / 60);
|
||||
const diffDays = Math.floor(diffHours / 24);
|
||||
|
||||
if (diffSecs < 60) return 'just now';
|
||||
if (diffMins < 60) return `${diffMins}m ago`;
|
||||
if (diffHours < 24) return `${diffHours}h ago`;
|
||||
if (diffDays < 7) return `${diffDays}d ago`;
|
||||
|
||||
// Format as date for older
|
||||
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
||||
} catch (e) {
|
||||
return dateString;
|
||||
}
|
||||
}
|
||||
|
||||
function carouselNext() {
|
||||
if (carouselSessions.length === 0) return;
|
||||
carouselIndex = (carouselIndex + 1) % carouselSessions.length;
|
||||
renderCarouselSlide('left');
|
||||
updateActiveDot();
|
||||
}
|
||||
|
||||
function carouselPrev() {
|
||||
if (carouselSessions.length === 0) return;
|
||||
carouselIndex = (carouselIndex - 1 + carouselSessions.length) % carouselSessions.length;
|
||||
renderCarouselSlide('right');
|
||||
updateActiveDot();
|
||||
}
|
||||
|
||||
function startCarouselInterval() {
|
||||
stopCarouselInterval();
|
||||
if (!carouselPaused && carouselSessions.length > 1) {
|
||||
carouselInterval = setInterval(carouselNext, CAROUSEL_INTERVAL_MS);
|
||||
}
|
||||
}
|
||||
|
||||
function stopCarouselInterval() {
|
||||
if (carouselInterval) {
|
||||
clearInterval(carouselInterval);
|
||||
carouselInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
function resetCarouselInterval() {
|
||||
if (!carouselPaused) {
|
||||
startCarouselInterval();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleCarouselPause() {
|
||||
carouselPaused = !carouselPaused;
|
||||
const icon = document.getElementById('carouselPauseIcon');
|
||||
|
||||
if (carouselPaused) {
|
||||
stopCarouselInterval();
|
||||
// Change to play icon
|
||||
if (icon) {
|
||||
icon.innerHTML = '<polygon points="5 3 19 12 5 21 5 3"/>';
|
||||
}
|
||||
} else {
|
||||
startCarouselInterval();
|
||||
// Change to pause icon
|
||||
if (icon) {
|
||||
icon.innerHTML = '<rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Jump to specific session in carousel
|
||||
function carouselGoTo(sessionId) {
|
||||
const index = carouselSessions.findIndex(s => s.session_id === sessionId);
|
||||
if (index !== -1) {
|
||||
carouselIndex = index;
|
||||
renderCarouselSlide('none');
|
||||
updateActiveDot();
|
||||
resetCarouselInterval();
|
||||
}
|
||||
}
|
||||
493
ccw/src/templates/dashboard-js/components/flowchart.js
Normal file
493
ccw/src/templates/dashboard-js/components/flowchart.js
Normal file
@@ -0,0 +1,493 @@
|
||||
// ==========================================
|
||||
// FLOWCHART RENDERING (D3.js)
|
||||
// ==========================================
|
||||
|
||||
function renderFlowchartForTask(sessionId, task) {
|
||||
// Will render on section expand
|
||||
}
|
||||
|
||||
function renderFlowchart(containerId, steps) {
|
||||
if (!steps || steps.length === 0) return;
|
||||
if (typeof d3 === 'undefined') {
|
||||
document.getElementById(containerId).innerHTML = '<div class="flowchart-fallback">D3.js not loaded</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const container = document.getElementById(containerId);
|
||||
const width = container.clientWidth || 500;
|
||||
const nodeHeight = 50;
|
||||
const nodeWidth = Math.min(width - 40, 300);
|
||||
const padding = 15;
|
||||
const height = steps.length * (nodeHeight + padding) + padding * 2;
|
||||
|
||||
// Clear existing content
|
||||
container.innerHTML = '';
|
||||
|
||||
const svg = d3.select('#' + containerId)
|
||||
.append('svg')
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.attr('class', 'flowchart-svg');
|
||||
|
||||
// Arrow marker
|
||||
svg.append('defs').append('marker')
|
||||
.attr('id', 'arrow-' + containerId)
|
||||
.attr('viewBox', '0 -5 10 10')
|
||||
.attr('refX', 8)
|
||||
.attr('refY', 0)
|
||||
.attr('markerWidth', 6)
|
||||
.attr('markerHeight', 6)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M0,-5L10,0L0,5')
|
||||
.attr('fill', 'hsl(var(--border))');
|
||||
|
||||
// Draw arrows
|
||||
for (let i = 0; i < steps.length - 1; i++) {
|
||||
const y1 = padding + i * (nodeHeight + padding) + nodeHeight;
|
||||
const y2 = padding + (i + 1) * (nodeHeight + padding);
|
||||
|
||||
svg.append('line')
|
||||
.attr('x1', width / 2)
|
||||
.attr('y1', y1)
|
||||
.attr('x2', width / 2)
|
||||
.attr('y2', y2)
|
||||
.attr('stroke', 'hsl(var(--border))')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('marker-end', 'url(#arrow-' + containerId + ')');
|
||||
}
|
||||
|
||||
// Draw nodes
|
||||
const nodes = svg.selectAll('.node')
|
||||
.data(steps)
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('class', 'flowchart-node')
|
||||
.attr('transform', (d, i) => `translate(${(width - nodeWidth) / 2}, ${padding + i * (nodeHeight + padding)})`);
|
||||
|
||||
// Node rectangles
|
||||
nodes.append('rect')
|
||||
.attr('width', nodeWidth)
|
||||
.attr('height', nodeHeight)
|
||||
.attr('rx', 6)
|
||||
.attr('fill', (d, i) => i === 0 ? 'hsl(var(--primary))' : 'hsl(var(--card))')
|
||||
.attr('stroke', 'hsl(var(--border))')
|
||||
.attr('stroke-width', 1);
|
||||
|
||||
// Step number circle
|
||||
nodes.append('circle')
|
||||
.attr('cx', 20)
|
||||
.attr('cy', nodeHeight / 2)
|
||||
.attr('r', 12)
|
||||
.attr('fill', (d, i) => i === 0 ? 'rgba(255,255,255,0.2)' : 'hsl(var(--muted))');
|
||||
|
||||
nodes.append('text')
|
||||
.attr('x', 20)
|
||||
.attr('y', nodeHeight / 2)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('dominant-baseline', 'central')
|
||||
.attr('font-size', '11px')
|
||||
.attr('fill', (d, i) => i === 0 ? 'white' : 'hsl(var(--muted-foreground))')
|
||||
.text((d, i) => i + 1);
|
||||
|
||||
// Node text (step name)
|
||||
nodes.append('text')
|
||||
.attr('x', 45)
|
||||
.attr('y', nodeHeight / 2)
|
||||
.attr('dominant-baseline', 'central')
|
||||
.attr('fill', (d, i) => i === 0 ? 'white' : 'hsl(var(--foreground))')
|
||||
.attr('font-size', '12px')
|
||||
.text(d => {
|
||||
const text = d.step || d.action || 'Step';
|
||||
return text.length > 35 ? text.substring(0, 32) + '...' : text;
|
||||
});
|
||||
}
|
||||
|
||||
function renderFullFlowchart(flowControl) {
|
||||
if (!flowControl) return;
|
||||
|
||||
const container = document.getElementById('flowchartContainer');
|
||||
if (!container) return;
|
||||
|
||||
const preAnalysis = Array.isArray(flowControl.pre_analysis) ? flowControl.pre_analysis : [];
|
||||
const implSteps = Array.isArray(flowControl.implementation_approach) ? flowControl.implementation_approach : [];
|
||||
|
||||
if (preAnalysis.length === 0 && implSteps.length === 0) {
|
||||
container.innerHTML = '<div class="empty-section">No flowchart data available</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const width = container.clientWidth || 500;
|
||||
const nodeHeight = 90;
|
||||
const nodeWidth = Math.min(width - 40, 420);
|
||||
const nodeGap = 45;
|
||||
const sectionGap = 30;
|
||||
|
||||
// Calculate total nodes and height
|
||||
const totalPreNodes = preAnalysis.length;
|
||||
const totalImplNodes = implSteps.length;
|
||||
const hasBothSections = totalPreNodes > 0 && totalImplNodes > 0;
|
||||
const height = (totalPreNodes + totalImplNodes) * (nodeHeight + nodeGap) +
|
||||
(hasBothSections ? sectionGap + 60 : 0) + 60;
|
||||
|
||||
// Clear existing
|
||||
d3.select('#flowchartContainer').selectAll('*').remove();
|
||||
|
||||
const svg = d3.select('#flowchartContainer')
|
||||
.append('svg')
|
||||
.attr('width', '100%')
|
||||
.attr('height', height)
|
||||
.attr('viewBox', `0 0 ${width} ${height}`);
|
||||
|
||||
// Add arrow markers
|
||||
const defs = svg.append('defs');
|
||||
|
||||
defs.append('marker')
|
||||
.attr('id', 'arrowhead-pre')
|
||||
.attr('viewBox', '0 -5 10 10')
|
||||
.attr('refX', 8)
|
||||
.attr('refY', 0)
|
||||
.attr('markerWidth', 6)
|
||||
.attr('markerHeight', 6)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M0,-5L10,0L0,5')
|
||||
.attr('fill', '#f59e0b');
|
||||
|
||||
defs.append('marker')
|
||||
.attr('id', 'arrowhead-impl')
|
||||
.attr('viewBox', '0 -5 10 10')
|
||||
.attr('refX', 8)
|
||||
.attr('refY', 0)
|
||||
.attr('markerWidth', 6)
|
||||
.attr('markerHeight', 6)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M0,-5L10,0L0,5')
|
||||
.attr('fill', 'hsl(var(--primary))');
|
||||
|
||||
let currentY = 20;
|
||||
|
||||
// Render Pre-Analysis section
|
||||
if (totalPreNodes > 0) {
|
||||
// Section label
|
||||
svg.append('text')
|
||||
.attr('x', 20)
|
||||
.attr('y', currentY)
|
||||
.attr('fill', '#f59e0b')
|
||||
.attr('font-weight', 'bold')
|
||||
.attr('font-size', '13px')
|
||||
.text('📋 Pre-Analysis Steps');
|
||||
|
||||
currentY += 25;
|
||||
|
||||
preAnalysis.forEach((step, idx) => {
|
||||
const x = (width - nodeWidth) / 2;
|
||||
|
||||
// Connection line to next node
|
||||
if (idx < preAnalysis.length - 1) {
|
||||
svg.append('line')
|
||||
.attr('x1', width / 2)
|
||||
.attr('y1', currentY + nodeHeight)
|
||||
.attr('x2', width / 2)
|
||||
.attr('y2', currentY + nodeHeight + nodeGap - 10)
|
||||
.attr('stroke', '#f59e0b')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('marker-end', 'url(#arrowhead-pre)');
|
||||
}
|
||||
|
||||
// Node group
|
||||
const nodeG = svg.append('g')
|
||||
.attr('class', 'flowchart-node')
|
||||
.attr('transform', `translate(${x}, ${currentY})`);
|
||||
|
||||
// Node rectangle (pre-analysis style - amber/orange)
|
||||
nodeG.append('rect')
|
||||
.attr('width', nodeWidth)
|
||||
.attr('height', nodeHeight)
|
||||
.attr('rx', 10)
|
||||
.attr('fill', 'hsl(var(--card))')
|
||||
.attr('stroke', '#f59e0b')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('stroke-dasharray', '5,3');
|
||||
|
||||
// Step badge
|
||||
nodeG.append('circle')
|
||||
.attr('cx', 25)
|
||||
.attr('cy', 25)
|
||||
.attr('r', 15)
|
||||
.attr('fill', '#f59e0b');
|
||||
|
||||
nodeG.append('text')
|
||||
.attr('x', 25)
|
||||
.attr('y', 30)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('fill', 'white')
|
||||
.attr('font-weight', 'bold')
|
||||
.attr('font-size', '11px')
|
||||
.text('P' + (idx + 1));
|
||||
|
||||
// Step name
|
||||
const stepName = step.step || step.action || 'Pre-step ' + (idx + 1);
|
||||
nodeG.append('text')
|
||||
.attr('x', 50)
|
||||
.attr('y', 28)
|
||||
.attr('fill', 'hsl(var(--foreground))')
|
||||
.attr('font-weight', '600')
|
||||
.attr('font-size', '13px')
|
||||
.text(truncateText(stepName, 40));
|
||||
|
||||
// Action description
|
||||
if (step.action && step.action !== stepName) {
|
||||
nodeG.append('text')
|
||||
.attr('x', 15)
|
||||
.attr('y', 52)
|
||||
.attr('fill', 'hsl(var(--muted-foreground))')
|
||||
.attr('font-size', '11px')
|
||||
.text(truncateText(step.action, 50));
|
||||
}
|
||||
|
||||
// Output indicator
|
||||
if (step.output_to) {
|
||||
nodeG.append('text')
|
||||
.attr('x', 15)
|
||||
.attr('y', 75)
|
||||
.attr('fill', '#f59e0b')
|
||||
.attr('font-size', '10px')
|
||||
.text('→ ' + truncateText(step.output_to, 45));
|
||||
}
|
||||
|
||||
currentY += nodeHeight + nodeGap;
|
||||
});
|
||||
}
|
||||
|
||||
// Section divider if both sections exist
|
||||
if (hasBothSections) {
|
||||
currentY += 10;
|
||||
svg.append('line')
|
||||
.attr('x1', 40)
|
||||
.attr('y1', currentY)
|
||||
.attr('x2', width - 40)
|
||||
.attr('y2', currentY)
|
||||
.attr('stroke', 'hsl(var(--border))')
|
||||
.attr('stroke-width', 1)
|
||||
.attr('stroke-dasharray', '4,4');
|
||||
|
||||
// Connecting arrow from pre-analysis to implementation
|
||||
svg.append('line')
|
||||
.attr('x1', width / 2)
|
||||
.attr('y1', currentY - nodeGap + 5)
|
||||
.attr('x2', width / 2)
|
||||
.attr('y2', currentY + sectionGap - 5)
|
||||
.attr('stroke', 'hsl(var(--primary))')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('marker-end', 'url(#arrowhead-impl)');
|
||||
|
||||
currentY += sectionGap;
|
||||
}
|
||||
|
||||
// Render Implementation section
|
||||
if (totalImplNodes > 0) {
|
||||
// Section label
|
||||
svg.append('text')
|
||||
.attr('x', 20)
|
||||
.attr('y', currentY)
|
||||
.attr('fill', 'hsl(var(--primary))')
|
||||
.attr('font-weight', 'bold')
|
||||
.attr('font-size', '13px')
|
||||
.text('🔧 Implementation Steps');
|
||||
|
||||
currentY += 25;
|
||||
|
||||
implSteps.forEach((step, idx) => {
|
||||
const x = (width - nodeWidth) / 2;
|
||||
|
||||
// Connection line to next node
|
||||
if (idx < implSteps.length - 1) {
|
||||
svg.append('line')
|
||||
.attr('x1', width / 2)
|
||||
.attr('y1', currentY + nodeHeight)
|
||||
.attr('x2', width / 2)
|
||||
.attr('y2', currentY + nodeHeight + nodeGap - 10)
|
||||
.attr('stroke', 'hsl(var(--primary))')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('marker-end', 'url(#arrowhead-impl)');
|
||||
}
|
||||
|
||||
// Node group
|
||||
const nodeG = svg.append('g')
|
||||
.attr('class', 'flowchart-node')
|
||||
.attr('transform', `translate(${x}, ${currentY})`);
|
||||
|
||||
// Node rectangle (implementation style - blue)
|
||||
nodeG.append('rect')
|
||||
.attr('width', nodeWidth)
|
||||
.attr('height', nodeHeight)
|
||||
.attr('rx', 10)
|
||||
.attr('fill', 'hsl(var(--card))')
|
||||
.attr('stroke', 'hsl(var(--primary))')
|
||||
.attr('stroke-width', 2);
|
||||
|
||||
// Step badge
|
||||
nodeG.append('circle')
|
||||
.attr('cx', 25)
|
||||
.attr('cy', 25)
|
||||
.attr('r', 15)
|
||||
.attr('fill', 'hsl(var(--primary))');
|
||||
|
||||
nodeG.append('text')
|
||||
.attr('x', 25)
|
||||
.attr('y', 30)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('fill', 'white')
|
||||
.attr('font-weight', 'bold')
|
||||
.attr('font-size', '12px')
|
||||
.text(step.step || idx + 1);
|
||||
|
||||
// Step title
|
||||
nodeG.append('text')
|
||||
.attr('x', 50)
|
||||
.attr('y', 28)
|
||||
.attr('fill', 'hsl(var(--foreground))')
|
||||
.attr('font-weight', '600')
|
||||
.attr('font-size', '13px')
|
||||
.text(truncateText(step.title || 'Step ' + (idx + 1), 40));
|
||||
|
||||
// Description
|
||||
if (step.description) {
|
||||
nodeG.append('text')
|
||||
.attr('x', 15)
|
||||
.attr('y', 52)
|
||||
.attr('fill', 'hsl(var(--muted-foreground))')
|
||||
.attr('font-size', '11px')
|
||||
.text(truncateText(step.description, 50));
|
||||
}
|
||||
|
||||
// Output/depends indicator
|
||||
if (step.depends_on?.length) {
|
||||
nodeG.append('text')
|
||||
.attr('x', 15)
|
||||
.attr('y', 75)
|
||||
.attr('fill', 'var(--warning-color)')
|
||||
.attr('font-size', '10px')
|
||||
.text('← Depends: ' + step.depends_on.join(', '));
|
||||
}
|
||||
|
||||
currentY += nodeHeight + nodeGap;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// D3.js Vertical Flowchart for Implementation Approach (legacy)
|
||||
function renderImplementationFlowchart(steps) {
|
||||
if (!Array.isArray(steps) || steps.length === 0) return;
|
||||
|
||||
const container = document.getElementById('flowchartContainer');
|
||||
if (!container) return;
|
||||
|
||||
const width = container.clientWidth || 500;
|
||||
const nodeHeight = 100;
|
||||
const nodeWidth = Math.min(width - 40, 400);
|
||||
const nodeGap = 50;
|
||||
const height = steps.length * (nodeHeight + nodeGap) + 40;
|
||||
|
||||
// Clear existing
|
||||
d3.select('#flowchartContainer').selectAll('*').remove();
|
||||
|
||||
const svg = d3.select('#flowchartContainer')
|
||||
.append('svg')
|
||||
.attr('width', '100%')
|
||||
.attr('height', height)
|
||||
.attr('viewBox', `0 0 ${width} ${height}`);
|
||||
|
||||
// Add arrow marker
|
||||
svg.append('defs').append('marker')
|
||||
.attr('id', 'arrowhead')
|
||||
.attr('viewBox', '0 -5 10 10')
|
||||
.attr('refX', 8)
|
||||
.attr('refY', 0)
|
||||
.attr('markerWidth', 6)
|
||||
.attr('markerHeight', 6)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M0,-5L10,0L0,5')
|
||||
.attr('fill', 'hsl(var(--primary))');
|
||||
|
||||
// Draw nodes and connections
|
||||
steps.forEach((step, idx) => {
|
||||
const y = idx * (nodeHeight + nodeGap) + 20;
|
||||
const x = (width - nodeWidth) / 2;
|
||||
|
||||
// Connection line to next node
|
||||
if (idx < steps.length - 1) {
|
||||
svg.append('line')
|
||||
.attr('x1', width / 2)
|
||||
.attr('y1', y + nodeHeight)
|
||||
.attr('x2', width / 2)
|
||||
.attr('y2', y + nodeHeight + nodeGap - 10)
|
||||
.attr('stroke', 'hsl(var(--primary))')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('marker-end', 'url(#arrowhead)');
|
||||
}
|
||||
|
||||
// Node group
|
||||
const nodeG = svg.append('g')
|
||||
.attr('class', 'flowchart-node')
|
||||
.attr('transform', `translate(${x}, ${y})`);
|
||||
|
||||
// Node rectangle with gradient
|
||||
nodeG.append('rect')
|
||||
.attr('width', nodeWidth)
|
||||
.attr('height', nodeHeight)
|
||||
.attr('rx', 10)
|
||||
.attr('fill', 'hsl(var(--card))')
|
||||
.attr('stroke', 'hsl(var(--primary))')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('filter', 'drop-shadow(0 2px 4px rgba(0,0,0,0.1))');
|
||||
|
||||
// Step number badge
|
||||
nodeG.append('circle')
|
||||
.attr('cx', 25)
|
||||
.attr('cy', 25)
|
||||
.attr('r', 15)
|
||||
.attr('fill', 'hsl(var(--primary))');
|
||||
|
||||
nodeG.append('text')
|
||||
.attr('x', 25)
|
||||
.attr('y', 30)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('fill', 'white')
|
||||
.attr('font-weight', 'bold')
|
||||
.attr('font-size', '12px')
|
||||
.text(step.step || idx + 1);
|
||||
|
||||
// Step title
|
||||
nodeG.append('text')
|
||||
.attr('x', 50)
|
||||
.attr('y', 30)
|
||||
.attr('fill', 'hsl(var(--foreground))')
|
||||
.attr('font-weight', '600')
|
||||
.attr('font-size', '14px')
|
||||
.text(truncateText(step.title || 'Step ' + (idx + 1), 35));
|
||||
|
||||
// Step description (if available)
|
||||
if (step.description) {
|
||||
nodeG.append('text')
|
||||
.attr('x', 15)
|
||||
.attr('y', 55)
|
||||
.attr('fill', 'hsl(var(--muted-foreground))')
|
||||
.attr('font-size', '12px')
|
||||
.text(truncateText(step.description, 45));
|
||||
}
|
||||
|
||||
// Output indicator
|
||||
if (step.output) {
|
||||
nodeG.append('text')
|
||||
.attr('x', 15)
|
||||
.attr('y', 80)
|
||||
.attr('fill', 'var(--success-color)')
|
||||
.attr('font-size', '11px')
|
||||
.text('→ ' + truncateText(step.output, 40));
|
||||
}
|
||||
});
|
||||
}
|
||||
273
ccw/src/templates/dashboard-js/components/hook-manager.js
Normal file
273
ccw/src/templates/dashboard-js/components/hook-manager.js
Normal file
@@ -0,0 +1,273 @@
|
||||
// Hook Manager Component
|
||||
// Manages Claude Code hooks configuration from settings.json
|
||||
|
||||
// ========== Hook State ==========
|
||||
let hookConfig = {
|
||||
global: { hooks: {} },
|
||||
project: { hooks: {} }
|
||||
};
|
||||
|
||||
// ========== Hook Templates ==========
|
||||
const HOOK_TEMPLATES = {
|
||||
'ccw-notify': {
|
||||
event: 'PostToolUse',
|
||||
matcher: 'Write',
|
||||
command: 'curl',
|
||||
args: ['-s', '-X', 'POST', '-H', 'Content-Type: application/json', '-d', '{"type":"summary_written","filePath":"$CLAUDE_FILE_PATHS"}', 'http://localhost:3456/api/hook']
|
||||
},
|
||||
'log-tool': {
|
||||
event: 'PostToolUse',
|
||||
matcher: '',
|
||||
command: 'bash',
|
||||
args: ['-c', 'echo "[$(date)] Tool: $CLAUDE_TOOL_NAME, Files: $CLAUDE_FILE_PATHS" >> ~/.claude/tool-usage.log']
|
||||
},
|
||||
'lint-check': {
|
||||
event: 'PostToolUse',
|
||||
matcher: 'Write',
|
||||
command: 'bash',
|
||||
args: ['-c', 'for f in $CLAUDE_FILE_PATHS; do if [[ "$f" =~ \\.(js|ts|jsx|tsx)$ ]]; then npx eslint "$f" --fix 2>/dev/null || true; fi; done']
|
||||
},
|
||||
'git-add': {
|
||||
event: 'PostToolUse',
|
||||
matcher: 'Write',
|
||||
command: 'bash',
|
||||
args: ['-c', 'for f in $CLAUDE_FILE_PATHS; do git add "$f" 2>/dev/null || true; done']
|
||||
}
|
||||
};
|
||||
|
||||
// ========== Initialization ==========
|
||||
function initHookManager() {
|
||||
// Initialize Hook navigation
|
||||
document.querySelectorAll('.nav-item[data-view="hook-manager"]').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
setActiveNavItem(item);
|
||||
currentView = 'hook-manager';
|
||||
currentFilter = null;
|
||||
currentLiteType = null;
|
||||
currentSessionDetailKey = null;
|
||||
updateContentTitle();
|
||||
renderHookManager();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ========== Data Loading ==========
|
||||
async function loadHookConfig() {
|
||||
try {
|
||||
const response = await fetch(`/api/hooks?path=${encodeURIComponent(projectPath)}`);
|
||||
if (!response.ok) throw new Error('Failed to load hook config');
|
||||
const data = await response.json();
|
||||
hookConfig = data;
|
||||
updateHookBadge();
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error('Failed to load hook config:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveHook(scope, event, hookData) {
|
||||
try {
|
||||
const response = await fetch('/api/hooks', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
projectPath: projectPath,
|
||||
scope: scope,
|
||||
event: event,
|
||||
hookData: hookData
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to save hook');
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
await loadHookConfig();
|
||||
renderHookManager();
|
||||
showRefreshToast(`Hook saved successfully`, 'success');
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error('Failed to save hook:', err);
|
||||
showRefreshToast(`Failed to save hook: ${err.message}`, 'error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function removeHook(scope, event, hookIndex) {
|
||||
try {
|
||||
const response = await fetch('/api/hooks', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
projectPath: projectPath,
|
||||
scope: scope,
|
||||
event: event,
|
||||
hookIndex: hookIndex
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to remove hook');
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
await loadHookConfig();
|
||||
renderHookManager();
|
||||
showRefreshToast(`Hook removed successfully`, 'success');
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error('Failed to remove hook:', err);
|
||||
showRefreshToast(`Failed to remove hook: ${err.message}`, 'error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Badge Update ==========
|
||||
function updateHookBadge() {
|
||||
const badge = document.getElementById('badgeHooks');
|
||||
if (badge) {
|
||||
let totalHooks = 0;
|
||||
|
||||
// Count global hooks
|
||||
if (hookConfig.global?.hooks) {
|
||||
for (const event of Object.keys(hookConfig.global.hooks)) {
|
||||
const hooks = hookConfig.global.hooks[event];
|
||||
totalHooks += Array.isArray(hooks) ? hooks.length : 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Count project hooks
|
||||
if (hookConfig.project?.hooks) {
|
||||
for (const event of Object.keys(hookConfig.project.hooks)) {
|
||||
const hooks = hookConfig.project.hooks[event];
|
||||
totalHooks += Array.isArray(hooks) ? hooks.length : 1;
|
||||
}
|
||||
}
|
||||
|
||||
badge.textContent = totalHooks;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Hook Modal Functions ==========
|
||||
let editingHookData = null;
|
||||
|
||||
function openHookCreateModal(editData = null) {
|
||||
const modal = document.getElementById('hookCreateModal');
|
||||
const title = document.getElementById('hookModalTitle');
|
||||
|
||||
if (modal) {
|
||||
modal.classList.remove('hidden');
|
||||
editingHookData = editData;
|
||||
|
||||
// Set title based on mode
|
||||
title.textContent = editData ? 'Edit Hook' : 'Create Hook';
|
||||
|
||||
// Clear or populate form
|
||||
if (editData) {
|
||||
document.getElementById('hookEvent').value = editData.event || '';
|
||||
document.getElementById('hookMatcher').value = editData.matcher || '';
|
||||
document.getElementById('hookCommand').value = editData.command || '';
|
||||
document.getElementById('hookArgs').value = (editData.args || []).join('\n');
|
||||
|
||||
// Set scope radio
|
||||
const scopeRadio = document.querySelector(`input[name="hookScope"][value="${editData.scope || 'project'}"]`);
|
||||
if (scopeRadio) scopeRadio.checked = true;
|
||||
} else {
|
||||
document.getElementById('hookEvent').value = '';
|
||||
document.getElementById('hookMatcher').value = '';
|
||||
document.getElementById('hookCommand').value = '';
|
||||
document.getElementById('hookArgs').value = '';
|
||||
document.querySelector('input[name="hookScope"][value="project"]').checked = true;
|
||||
}
|
||||
|
||||
// Focus on event select
|
||||
document.getElementById('hookEvent').focus();
|
||||
}
|
||||
}
|
||||
|
||||
function closeHookCreateModal() {
|
||||
const modal = document.getElementById('hookCreateModal');
|
||||
if (modal) {
|
||||
modal.classList.add('hidden');
|
||||
editingHookData = null;
|
||||
}
|
||||
}
|
||||
|
||||
function applyHookTemplate(templateName) {
|
||||
const template = HOOK_TEMPLATES[templateName];
|
||||
if (!template) return;
|
||||
|
||||
document.getElementById('hookEvent').value = template.event;
|
||||
document.getElementById('hookMatcher').value = template.matcher;
|
||||
document.getElementById('hookCommand').value = template.command;
|
||||
document.getElementById('hookArgs').value = template.args.join('\n');
|
||||
}
|
||||
|
||||
async function submitHookCreate() {
|
||||
const event = document.getElementById('hookEvent').value;
|
||||
const matcher = document.getElementById('hookMatcher').value.trim();
|
||||
const command = document.getElementById('hookCommand').value.trim();
|
||||
const argsText = document.getElementById('hookArgs').value.trim();
|
||||
const scope = document.querySelector('input[name="hookScope"]:checked').value;
|
||||
|
||||
// Validate required fields
|
||||
if (!event) {
|
||||
showRefreshToast('Hook event is required', 'error');
|
||||
document.getElementById('hookEvent').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!command) {
|
||||
showRefreshToast('Command is required', 'error');
|
||||
document.getElementById('hookCommand').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse args (one per line)
|
||||
const args = argsText ? argsText.split('\n').map(a => a.trim()).filter(a => a) : [];
|
||||
|
||||
// Build hook data
|
||||
const hookData = {
|
||||
command: command
|
||||
};
|
||||
|
||||
if (args.length > 0) {
|
||||
hookData.args = args;
|
||||
}
|
||||
|
||||
if (matcher) {
|
||||
hookData.matcher = matcher;
|
||||
}
|
||||
|
||||
// If editing, include original index for replacement
|
||||
if (editingHookData && editingHookData.index !== undefined) {
|
||||
hookData.replaceIndex = editingHookData.index;
|
||||
}
|
||||
|
||||
// Submit to API
|
||||
await saveHook(scope, event, hookData);
|
||||
closeHookCreateModal();
|
||||
}
|
||||
|
||||
// ========== Helpers ==========
|
||||
function getHookEventDescription(event) {
|
||||
const descriptions = {
|
||||
'PreToolUse': 'Runs before a tool is executed',
|
||||
'PostToolUse': 'Runs after a tool completes',
|
||||
'Notification': 'Runs when a notification is triggered',
|
||||
'Stop': 'Runs when the agent stops'
|
||||
};
|
||||
return descriptions[event] || event;
|
||||
}
|
||||
|
||||
function getHookEventIcon(event) {
|
||||
const icons = {
|
||||
'PreToolUse': '⏳',
|
||||
'PostToolUse': '✅',
|
||||
'Notification': '🔔',
|
||||
'Stop': '🛑'
|
||||
};
|
||||
return icons[event] || '🪝';
|
||||
}
|
||||
506
ccw/src/templates/dashboard-js/components/mcp-manager.js
Normal file
506
ccw/src/templates/dashboard-js/components/mcp-manager.js
Normal file
@@ -0,0 +1,506 @@
|
||||
// MCP Manager Component
|
||||
// Manages MCP server configuration from .claude.json
|
||||
|
||||
// ========== MCP State ==========
|
||||
let mcpConfig = null;
|
||||
let mcpAllProjects = {};
|
||||
let mcpCurrentProjectServers = {};
|
||||
let mcpCreateMode = 'form'; // 'form' or 'json'
|
||||
|
||||
// ========== Initialization ==========
|
||||
function initMcpManager() {
|
||||
// Initialize MCP navigation
|
||||
document.querySelectorAll('.nav-item[data-view="mcp-manager"]').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
setActiveNavItem(item);
|
||||
currentView = 'mcp-manager';
|
||||
currentFilter = null;
|
||||
currentLiteType = null;
|
||||
currentSessionDetailKey = null;
|
||||
updateContentTitle();
|
||||
renderMcpManager();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ========== Data Loading ==========
|
||||
async function loadMcpConfig() {
|
||||
try {
|
||||
const response = await fetch('/api/mcp-config');
|
||||
if (!response.ok) throw new Error('Failed to load MCP config');
|
||||
const data = await response.json();
|
||||
mcpConfig = data;
|
||||
mcpAllProjects = data.projects || {};
|
||||
|
||||
// Get current project servers
|
||||
const currentPath = projectPath.replace(/\//g, '\\');
|
||||
mcpCurrentProjectServers = mcpAllProjects[currentPath]?.mcpServers || {};
|
||||
|
||||
// Update badge count
|
||||
updateMcpBadge();
|
||||
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error('Failed to load MCP config:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleMcpServer(serverName, enable) {
|
||||
try {
|
||||
const response = await fetch('/api/mcp-toggle', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
projectPath: projectPath,
|
||||
serverName: serverName,
|
||||
enable: enable
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to toggle MCP server');
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
// Reload config and re-render
|
||||
await loadMcpConfig();
|
||||
renderMcpManager();
|
||||
showRefreshToast(`MCP server "${serverName}" ${enable ? 'enabled' : 'disabled'}`, 'success');
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error('Failed to toggle MCP server:', err);
|
||||
showRefreshToast(`Failed to toggle MCP server: ${err.message}`, 'error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function copyMcpServerToProject(serverName, serverConfig) {
|
||||
try {
|
||||
const response = await fetch('/api/mcp-copy-server', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
projectPath: projectPath,
|
||||
serverName: serverName,
|
||||
serverConfig: serverConfig
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to copy MCP server');
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
await loadMcpConfig();
|
||||
renderMcpManager();
|
||||
showRefreshToast(`MCP server "${serverName}" added to project`, 'success');
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error('Failed to copy MCP server:', err);
|
||||
showRefreshToast(`Failed to add MCP server: ${err.message}`, 'error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function removeMcpServerFromProject(serverName) {
|
||||
try {
|
||||
const response = await fetch('/api/mcp-remove-server', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
projectPath: projectPath,
|
||||
serverName: serverName
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to remove MCP server');
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
await loadMcpConfig();
|
||||
renderMcpManager();
|
||||
showRefreshToast(`MCP server "${serverName}" removed from project`, 'success');
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error('Failed to remove MCP server:', err);
|
||||
showRefreshToast(`Failed to remove MCP server: ${err.message}`, 'error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Badge Update ==========
|
||||
function updateMcpBadge() {
|
||||
const badge = document.getElementById('badgeMcpServers');
|
||||
if (badge) {
|
||||
const currentPath = projectPath.replace(/\//g, '\\');
|
||||
const projectData = mcpAllProjects[currentPath];
|
||||
const servers = projectData?.mcpServers || {};
|
||||
const disabledServers = projectData?.disabledMcpServers || [];
|
||||
|
||||
const totalServers = Object.keys(servers).length;
|
||||
const enabledServers = totalServers - disabledServers.length;
|
||||
|
||||
badge.textContent = `${enabledServers}/${totalServers}`;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Helpers ==========
|
||||
function getAllAvailableMcpServers() {
|
||||
const allServers = {};
|
||||
|
||||
// Collect servers from all projects
|
||||
for (const [path, config] of Object.entries(mcpAllProjects)) {
|
||||
const servers = config.mcpServers || {};
|
||||
for (const [name, serverConfig] of Object.entries(servers)) {
|
||||
if (!allServers[name]) {
|
||||
allServers[name] = {
|
||||
config: serverConfig,
|
||||
usedIn: []
|
||||
};
|
||||
}
|
||||
allServers[name].usedIn.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
return allServers;
|
||||
}
|
||||
|
||||
function isServerEnabledInCurrentProject(serverName) {
|
||||
const currentPath = projectPath.replace(/\//g, '\\');
|
||||
const projectData = mcpAllProjects[currentPath];
|
||||
if (!projectData) return false;
|
||||
|
||||
const disabledServers = projectData.disabledMcpServers || [];
|
||||
return !disabledServers.includes(serverName);
|
||||
}
|
||||
|
||||
function isServerInCurrentProject(serverName) {
|
||||
const currentPath = projectPath.replace(/\//g, '\\');
|
||||
const projectData = mcpAllProjects[currentPath];
|
||||
if (!projectData) return false;
|
||||
|
||||
const servers = projectData.mcpServers || {};
|
||||
return serverName in servers;
|
||||
}
|
||||
|
||||
// ========== MCP Create Modal ==========
|
||||
function openMcpCreateModal() {
|
||||
const modal = document.getElementById('mcpCreateModal');
|
||||
if (modal) {
|
||||
modal.classList.remove('hidden');
|
||||
// Reset to form mode
|
||||
mcpCreateMode = 'form';
|
||||
switchMcpCreateTab('form');
|
||||
// Clear form
|
||||
document.getElementById('mcpServerName').value = '';
|
||||
document.getElementById('mcpServerCommand').value = '';
|
||||
document.getElementById('mcpServerArgs').value = '';
|
||||
document.getElementById('mcpServerEnv').value = '';
|
||||
// Clear JSON input
|
||||
document.getElementById('mcpServerJson').value = '';
|
||||
document.getElementById('mcpJsonPreview').classList.add('hidden');
|
||||
// Focus on name input
|
||||
document.getElementById('mcpServerName').focus();
|
||||
// Setup JSON input listener
|
||||
setupMcpJsonListener();
|
||||
}
|
||||
}
|
||||
|
||||
function closeMcpCreateModal() {
|
||||
const modal = document.getElementById('mcpCreateModal');
|
||||
if (modal) {
|
||||
modal.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function switchMcpCreateTab(tab) {
|
||||
mcpCreateMode = tab;
|
||||
const formMode = document.getElementById('mcpFormMode');
|
||||
const jsonMode = document.getElementById('mcpJsonMode');
|
||||
const tabForm = document.getElementById('mcpTabForm');
|
||||
const tabJson = document.getElementById('mcpTabJson');
|
||||
|
||||
if (tab === 'form') {
|
||||
formMode.classList.remove('hidden');
|
||||
jsonMode.classList.add('hidden');
|
||||
tabForm.classList.add('active');
|
||||
tabJson.classList.remove('active');
|
||||
} else {
|
||||
formMode.classList.add('hidden');
|
||||
jsonMode.classList.remove('hidden');
|
||||
tabForm.classList.remove('active');
|
||||
tabJson.classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
function setupMcpJsonListener() {
|
||||
const jsonInput = document.getElementById('mcpServerJson');
|
||||
if (jsonInput && !jsonInput.hasAttribute('data-listener-attached')) {
|
||||
jsonInput.setAttribute('data-listener-attached', 'true');
|
||||
jsonInput.addEventListener('input', () => {
|
||||
updateMcpJsonPreview();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function parseMcpJsonConfig(jsonText) {
|
||||
if (!jsonText.trim()) {
|
||||
return { servers: {}, error: null };
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(jsonText);
|
||||
let servers = {};
|
||||
|
||||
// Support multiple formats:
|
||||
// 1. {"servers": {...}} format (claude desktop style)
|
||||
// 2. {"mcpServers": {...}} format (claude.json style)
|
||||
// 3. {"serverName": {command, args}} format (direct server config)
|
||||
// 4. {command, args} format (single server without name)
|
||||
|
||||
if (parsed.servers && typeof parsed.servers === 'object') {
|
||||
servers = parsed.servers;
|
||||
} else if (parsed.mcpServers && typeof parsed.mcpServers === 'object') {
|
||||
servers = parsed.mcpServers;
|
||||
} else if (parsed.command && typeof parsed.command === 'string') {
|
||||
// Single server without name - will prompt for name
|
||||
servers = { '__unnamed__': parsed };
|
||||
} else {
|
||||
// Check if all values are server configs (have 'command' property)
|
||||
const isDirectServerConfig = Object.values(parsed).every(
|
||||
v => v && typeof v === 'object' && v.command
|
||||
);
|
||||
if (isDirectServerConfig && Object.keys(parsed).length > 0) {
|
||||
servers = parsed;
|
||||
} else {
|
||||
return { servers: {}, error: 'Invalid MCP server JSON format' };
|
||||
}
|
||||
}
|
||||
|
||||
// Validate each server config
|
||||
for (const [name, config] of Object.entries(servers)) {
|
||||
if (!config.command || typeof config.command !== 'string') {
|
||||
return { servers: {}, error: `Server "${name}" missing required "command" field` };
|
||||
}
|
||||
if (config.args && !Array.isArray(config.args)) {
|
||||
return { servers: {}, error: `Server "${name}" has invalid "args" (must be array)` };
|
||||
}
|
||||
if (config.env && typeof config.env !== 'object') {
|
||||
return { servers: {}, error: `Server "${name}" has invalid "env" (must be object)` };
|
||||
}
|
||||
}
|
||||
|
||||
return { servers, error: null };
|
||||
} catch (e) {
|
||||
return { servers: {}, error: 'Invalid JSON: ' + e.message };
|
||||
}
|
||||
}
|
||||
|
||||
function updateMcpJsonPreview() {
|
||||
const jsonInput = document.getElementById('mcpServerJson');
|
||||
const previewContainer = document.getElementById('mcpJsonPreview');
|
||||
const previewContent = document.getElementById('mcpJsonPreviewContent');
|
||||
|
||||
const jsonText = jsonInput.value;
|
||||
const { servers, error } = parseMcpJsonConfig(jsonText);
|
||||
|
||||
if (!jsonText.trim()) {
|
||||
previewContainer.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
previewContainer.classList.remove('hidden');
|
||||
|
||||
if (error) {
|
||||
previewContent.innerHTML = `<div class="text-destructive">${escapeHtml(error)}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const serverCount = Object.keys(servers).length;
|
||||
if (serverCount === 0) {
|
||||
previewContent.innerHTML = `<div class="text-muted-foreground">No servers found</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const previewHtml = Object.entries(servers).map(([name, config]) => {
|
||||
const displayName = name === '__unnamed__' ? '(will prompt for name)' : name;
|
||||
const argsPreview = config.args ? config.args.slice(0, 2).join(' ') + (config.args.length > 2 ? '...' : '') : '';
|
||||
return `
|
||||
<div class="flex items-center gap-2 p-2 bg-background rounded">
|
||||
<span class="text-success">+</span>
|
||||
<span class="font-medium">${escapeHtml(displayName)}</span>
|
||||
<span class="text-muted-foreground text-xs">${escapeHtml(config.command)} ${escapeHtml(argsPreview)}</span>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
previewContent.innerHTML = previewHtml;
|
||||
}
|
||||
|
||||
async function submitMcpCreate() {
|
||||
if (mcpCreateMode === 'json') {
|
||||
await submitMcpCreateFromJson();
|
||||
} else {
|
||||
await submitMcpCreateFromForm();
|
||||
}
|
||||
}
|
||||
|
||||
async function submitMcpCreateFromForm() {
|
||||
const name = document.getElementById('mcpServerName').value.trim();
|
||||
const command = document.getElementById('mcpServerCommand').value.trim();
|
||||
const argsText = document.getElementById('mcpServerArgs').value.trim();
|
||||
const envText = document.getElementById('mcpServerEnv').value.trim();
|
||||
|
||||
// Validate required fields
|
||||
if (!name) {
|
||||
showRefreshToast('Server name is required', 'error');
|
||||
document.getElementById('mcpServerName').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!command) {
|
||||
showRefreshToast('Command is required', 'error');
|
||||
document.getElementById('mcpServerCommand').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse args (one per line)
|
||||
const args = argsText ? argsText.split('\n').map(a => a.trim()).filter(a => a) : [];
|
||||
|
||||
// Parse env vars (KEY=VALUE per line)
|
||||
const env = {};
|
||||
if (envText) {
|
||||
envText.split('\n').forEach(line => {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed && trimmed.includes('=')) {
|
||||
const eqIndex = trimmed.indexOf('=');
|
||||
const key = trimmed.substring(0, eqIndex).trim();
|
||||
const value = trimmed.substring(eqIndex + 1).trim();
|
||||
if (key) {
|
||||
env[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Build server config
|
||||
const serverConfig = {
|
||||
command: command,
|
||||
args: args
|
||||
};
|
||||
|
||||
// Only add env if there are values
|
||||
if (Object.keys(env).length > 0) {
|
||||
serverConfig.env = env;
|
||||
}
|
||||
|
||||
await createMcpServerWithConfig(name, serverConfig);
|
||||
}
|
||||
|
||||
async function submitMcpCreateFromJson() {
|
||||
const jsonText = document.getElementById('mcpServerJson').value.trim();
|
||||
|
||||
if (!jsonText) {
|
||||
showRefreshToast('Please enter JSON configuration', 'error');
|
||||
document.getElementById('mcpServerJson').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const { servers, error } = parseMcpJsonConfig(jsonText);
|
||||
|
||||
if (error) {
|
||||
showRefreshToast(error, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (Object.keys(servers).length === 0) {
|
||||
showRefreshToast('No valid servers found in JSON', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle unnamed server case
|
||||
if (servers['__unnamed__']) {
|
||||
const serverName = prompt('Enter a name for this MCP server:');
|
||||
if (!serverName || !serverName.trim()) {
|
||||
showRefreshToast('Server name is required', 'error');
|
||||
return;
|
||||
}
|
||||
servers[serverName.trim()] = servers['__unnamed__'];
|
||||
delete servers['__unnamed__'];
|
||||
}
|
||||
|
||||
// Add all servers
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
const serverNames = Object.keys(servers);
|
||||
|
||||
for (const [name, config] of Object.entries(servers)) {
|
||||
try {
|
||||
const response = await fetch('/api/mcp-copy-server', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
projectPath: projectPath,
|
||||
serverName: name,
|
||||
serverConfig: config
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to create MCP server');
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
successCount++;
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Failed to create MCP server "${name}":`, err);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
closeMcpCreateModal();
|
||||
await loadMcpConfig();
|
||||
renderMcpManager();
|
||||
|
||||
if (failCount === 0) {
|
||||
showRefreshToast(`${successCount} MCP server${successCount > 1 ? 's' : ''} created successfully`, 'success');
|
||||
} else if (successCount > 0) {
|
||||
showRefreshToast(`${successCount} created, ${failCount} failed`, 'warning');
|
||||
} else {
|
||||
showRefreshToast('Failed to create MCP servers', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function createMcpServerWithConfig(name, serverConfig) {
|
||||
// Submit to API
|
||||
try {
|
||||
const response = await fetch('/api/mcp-copy-server', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
projectPath: projectPath,
|
||||
serverName: name,
|
||||
serverConfig: serverConfig
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to create MCP server');
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
closeMcpCreateModal();
|
||||
await loadMcpConfig();
|
||||
renderMcpManager();
|
||||
showRefreshToast(`MCP server "${name}" created successfully`, 'success');
|
||||
} else {
|
||||
showRefreshToast(result.error || 'Failed to create MCP server', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to create MCP server:', err);
|
||||
showRefreshToast(`Failed to create MCP server: ${err.message}`, 'error');
|
||||
}
|
||||
}
|
||||
260
ccw/src/templates/dashboard-js/components/modals.js
Normal file
260
ccw/src/templates/dashboard-js/components/modals.js
Normal file
@@ -0,0 +1,260 @@
|
||||
// ==========================================
|
||||
// MODAL DIALOGS
|
||||
// ==========================================
|
||||
|
||||
// SVG Icons
|
||||
const icons = {
|
||||
folder: '<svg viewBox="0 0 24 24"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>',
|
||||
check: '<svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"></polyline></svg>',
|
||||
copy: '<svg viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>',
|
||||
terminal: '<svg viewBox="0 0 24 24"><polyline points="4 17 10 11 4 5"></polyline><line x1="12" y1="19" x2="20" y2="19"></line></svg>'
|
||||
};
|
||||
|
||||
function showPathSelectedModal(dirName, dirHandle) {
|
||||
// Try to guess full path based on current project path
|
||||
const currentPath = projectPath || '';
|
||||
const basePath = currentPath.substring(0, currentPath.lastIndexOf('/')) || 'D:/projects';
|
||||
const suggestedPath = basePath + '/' + dirName;
|
||||
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'path-modal-overlay';
|
||||
modal.innerHTML = `
|
||||
<div class="path-modal">
|
||||
<div class="path-modal-header">
|
||||
<span class="path-modal-icon">${icons.folder}</span>
|
||||
<h3>Folder Selected</h3>
|
||||
</div>
|
||||
<div class="path-modal-body">
|
||||
<div class="selected-folder">
|
||||
<strong>${dirName}</strong>
|
||||
</div>
|
||||
<p class="path-modal-note">
|
||||
Confirm or edit the full path:
|
||||
</p>
|
||||
<div class="path-input-group" style="margin-top: 12px;">
|
||||
<label>Full path:</label>
|
||||
<input type="text" id="fullPathInput" value="${suggestedPath}" />
|
||||
<button class="path-go-btn" id="pathGoBtn">Open</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="path-modal-footer">
|
||||
<button class="path-modal-close" id="pathCancelBtn">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Add event listeners (use arrow functions to ensure proper scope)
|
||||
document.getElementById('pathGoBtn').addEventListener('click', () => {
|
||||
console.log('Open button clicked');
|
||||
goToPath();
|
||||
});
|
||||
document.getElementById('pathCancelBtn').addEventListener('click', () => closePathModal());
|
||||
|
||||
// Focus input, select all text, and add enter key listener
|
||||
setTimeout(() => {
|
||||
const input = document.getElementById('fullPathInput');
|
||||
input?.focus();
|
||||
input?.select();
|
||||
input?.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') goToPath();
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function showPathInputModal() {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'path-modal-overlay';
|
||||
modal.innerHTML = `
|
||||
<div class="path-modal">
|
||||
<div class="path-modal-header">
|
||||
<span class="path-modal-icon">${icons.folder}</span>
|
||||
<h3>Open Project</h3>
|
||||
</div>
|
||||
<div class="path-modal-body">
|
||||
<div class="path-input-group" style="margin-top: 0;">
|
||||
<label>Project path:</label>
|
||||
<input type="text" id="fullPathInput" placeholder="D:/projects/my-project" />
|
||||
<button class="path-go-btn" id="pathGoBtn">Open</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="path-modal-footer">
|
||||
<button class="path-modal-close" id="pathCancelBtn">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Add event listeners (use arrow functions to ensure proper scope)
|
||||
document.getElementById('pathGoBtn').addEventListener('click', () => {
|
||||
console.log('Open button clicked');
|
||||
goToPath();
|
||||
});
|
||||
document.getElementById('pathCancelBtn').addEventListener('click', () => closePathModal());
|
||||
|
||||
// Focus input and add enter key listener
|
||||
setTimeout(() => {
|
||||
const input = document.getElementById('fullPathInput');
|
||||
input?.focus();
|
||||
input?.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') goToPath();
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function goToPath() {
|
||||
const input = document.getElementById('fullPathInput');
|
||||
const path = input?.value?.trim();
|
||||
if (path) {
|
||||
closePathModal();
|
||||
selectPath(path);
|
||||
} else {
|
||||
// Show error - input is empty
|
||||
input.style.borderColor = 'var(--danger-color)';
|
||||
input.placeholder = 'Please enter a path';
|
||||
input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function closePathModal() {
|
||||
const modal = document.querySelector('.path-modal-overlay');
|
||||
if (modal) {
|
||||
modal.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function copyCommand(btn, dirName) {
|
||||
const input = document.getElementById('fullPathInput');
|
||||
const path = input?.value?.trim() || `[full-path-to-${dirName}]`;
|
||||
const command = `ccw view -p "${path}"`;
|
||||
navigator.clipboard.writeText(command).then(() => {
|
||||
btn.innerHTML = icons.check + ' <span>Copied!</span>';
|
||||
setTimeout(() => { btn.innerHTML = icons.copy + ' <span>Copy</span>'; }, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
function showJsonModal(jsonId, taskId) {
|
||||
// Get JSON from memory store instead of DOM
|
||||
const rawTask = taskJsonStore[jsonId];
|
||||
if (!rawTask) return;
|
||||
|
||||
const jsonContent = JSON.stringify(rawTask, null, 2);
|
||||
|
||||
// Create modal
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'json-modal-overlay';
|
||||
overlay.innerHTML = `
|
||||
<div class="json-modal">
|
||||
<div class="json-modal-header">
|
||||
<div class="json-modal-title">
|
||||
<span class="task-id-badge">${escapeHtml(taskId)}</span>
|
||||
<span>Task JSON</span>
|
||||
</div>
|
||||
<button class="json-modal-close" onclick="closeJsonModal(this)">×</button>
|
||||
</div>
|
||||
<div class="json-modal-body">
|
||||
<pre class="json-modal-content">${escapeHtml(jsonContent)}</pre>
|
||||
</div>
|
||||
<div class="json-modal-footer">
|
||||
<button class="btn-copy-json" onclick="copyJsonToClipboard(this)">Copy JSON</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
// Trigger animation
|
||||
requestAnimationFrame(() => overlay.classList.add('active'));
|
||||
|
||||
// Close on overlay click
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) closeJsonModal(overlay.querySelector('.json-modal-close'));
|
||||
});
|
||||
|
||||
// Close on Escape key
|
||||
const escHandler = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeJsonModal(overlay.querySelector('.json-modal-close'));
|
||||
document.removeEventListener('keydown', escHandler);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', escHandler);
|
||||
}
|
||||
|
||||
function closeJsonModal(btn) {
|
||||
const overlay = btn.closest('.json-modal-overlay');
|
||||
overlay.classList.remove('active');
|
||||
setTimeout(() => overlay.remove(), 200);
|
||||
}
|
||||
|
||||
function copyJsonToClipboard(btn) {
|
||||
const content = btn.closest('.json-modal').querySelector('.json-modal-content').textContent;
|
||||
navigator.clipboard.writeText(content).then(() => {
|
||||
const original = btn.textContent;
|
||||
btn.textContent = 'Copied!';
|
||||
setTimeout(() => btn.textContent = original, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
function openMarkdownModal(title, content, type = 'markdown') {
|
||||
const modal = document.getElementById('markdownModal');
|
||||
const titleEl = document.getElementById('markdownModalTitle');
|
||||
const rawEl = document.getElementById('markdownRaw');
|
||||
const previewEl = document.getElementById('markdownPreview');
|
||||
|
||||
// Normalize line endings
|
||||
const normalizedContent = normalizeLineEndings(content);
|
||||
|
||||
titleEl.textContent = title;
|
||||
rawEl.textContent = normalizedContent;
|
||||
|
||||
// Render preview based on type
|
||||
if (typeof marked !== 'undefined' && type === 'markdown') {
|
||||
previewEl.innerHTML = marked.parse(normalizedContent);
|
||||
} else if (type === 'json') {
|
||||
// For JSON, try to parse and re-stringify with formatting
|
||||
try {
|
||||
const parsed = typeof normalizedContent === 'string' ? JSON.parse(normalizedContent) : normalizedContent;
|
||||
const formatted = JSON.stringify(parsed, null, 2);
|
||||
previewEl.innerHTML = '<pre class="whitespace-pre-wrap language-json">' + escapeHtml(formatted) + '</pre>';
|
||||
} catch (e) {
|
||||
// If not valid JSON, show as-is
|
||||
previewEl.innerHTML = '<pre class="whitespace-pre-wrap">' + escapeHtml(normalizedContent) + '</pre>';
|
||||
}
|
||||
} else {
|
||||
// Fallback: simple text with line breaks
|
||||
previewEl.innerHTML = '<pre class="whitespace-pre-wrap">' + escapeHtml(normalizedContent) + '</pre>';
|
||||
}
|
||||
|
||||
// Show modal and default to preview tab
|
||||
modal.classList.remove('hidden');
|
||||
switchMarkdownTab('preview');
|
||||
}
|
||||
|
||||
function closeMarkdownModal() {
|
||||
const modal = document.getElementById('markdownModal');
|
||||
modal.classList.add('hidden');
|
||||
}
|
||||
|
||||
function switchMarkdownTab(tab) {
|
||||
const rawEl = document.getElementById('markdownRaw');
|
||||
const previewEl = document.getElementById('markdownPreview');
|
||||
const rawTabBtn = document.getElementById('mdTabRaw');
|
||||
const previewTabBtn = document.getElementById('mdTabPreview');
|
||||
|
||||
if (tab === 'raw') {
|
||||
rawEl.classList.remove('hidden');
|
||||
previewEl.classList.add('hidden');
|
||||
rawTabBtn.classList.add('active', 'bg-background', 'text-foreground');
|
||||
rawTabBtn.classList.remove('text-muted-foreground');
|
||||
previewTabBtn.classList.remove('active', 'bg-background', 'text-foreground');
|
||||
previewTabBtn.classList.add('text-muted-foreground');
|
||||
} else {
|
||||
rawEl.classList.add('hidden');
|
||||
previewEl.classList.remove('hidden');
|
||||
previewTabBtn.classList.add('active', 'bg-background', 'text-foreground');
|
||||
previewTabBtn.classList.remove('text-muted-foreground');
|
||||
rawTabBtn.classList.remove('active', 'bg-background', 'text-foreground');
|
||||
rawTabBtn.classList.add('text-muted-foreground');
|
||||
}
|
||||
}
|
||||
239
ccw/src/templates/dashboard-js/components/navigation.js
Normal file
239
ccw/src/templates/dashboard-js/components/navigation.js
Normal file
@@ -0,0 +1,239 @@
|
||||
// Navigation and Routing
|
||||
// Manages navigation events, active state, content title updates, search, and path selector
|
||||
|
||||
// Path Selector
|
||||
function initPathSelector() {
|
||||
const btn = document.getElementById('pathButton');
|
||||
const menu = document.getElementById('pathMenu');
|
||||
const recentContainer = document.getElementById('recentPaths');
|
||||
|
||||
// Render recent paths
|
||||
if (recentPaths && recentPaths.length > 0) {
|
||||
recentPaths.forEach(path => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'path-item' + (path === projectPath ? ' active' : '');
|
||||
item.dataset.path = path;
|
||||
|
||||
// Path text
|
||||
const pathText = document.createElement('span');
|
||||
pathText.className = 'path-text';
|
||||
pathText.textContent = path;
|
||||
pathText.addEventListener('click', () => selectPath(path));
|
||||
item.appendChild(pathText);
|
||||
|
||||
// Delete button (only for non-current paths)
|
||||
if (path !== projectPath) {
|
||||
const deleteBtn = document.createElement('button');
|
||||
deleteBtn.className = 'path-delete-btn';
|
||||
deleteBtn.innerHTML = '×';
|
||||
deleteBtn.title = 'Remove from recent';
|
||||
deleteBtn.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
await removeRecentPathFromList(path);
|
||||
});
|
||||
item.appendChild(deleteBtn);
|
||||
}
|
||||
|
||||
recentContainer.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
menu.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
document.addEventListener('click', () => {
|
||||
menu.classList.add('hidden');
|
||||
});
|
||||
|
||||
document.getElementById('browsePath').addEventListener('click', async () => {
|
||||
await browseForFolder();
|
||||
});
|
||||
}
|
||||
|
||||
// Navigation
|
||||
function initNavigation() {
|
||||
document.querySelectorAll('.nav-item[data-filter]').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
setActiveNavItem(item);
|
||||
currentFilter = item.dataset.filter;
|
||||
currentLiteType = null;
|
||||
currentView = 'sessions';
|
||||
currentSessionDetailKey = null;
|
||||
updateContentTitle();
|
||||
showStatsAndSearch();
|
||||
renderSessions();
|
||||
});
|
||||
});
|
||||
|
||||
// Lite Tasks Navigation
|
||||
document.querySelectorAll('.nav-item[data-lite]').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
setActiveNavItem(item);
|
||||
currentLiteType = item.dataset.lite;
|
||||
currentFilter = null;
|
||||
currentView = 'liteTasks';
|
||||
currentSessionDetailKey = null;
|
||||
updateContentTitle();
|
||||
showStatsAndSearch();
|
||||
renderLiteTasks();
|
||||
});
|
||||
});
|
||||
|
||||
// View Navigation (Project Overview, MCP Manager, etc.)
|
||||
document.querySelectorAll('.nav-item[data-view]').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
setActiveNavItem(item);
|
||||
currentView = item.dataset.view;
|
||||
currentFilter = null;
|
||||
currentLiteType = null;
|
||||
currentSessionDetailKey = null;
|
||||
updateContentTitle();
|
||||
|
||||
// Route to appropriate view
|
||||
if (currentView === 'mcp-manager') {
|
||||
renderMcpManager();
|
||||
} else if (currentView === 'project-overview') {
|
||||
renderProjectOverview();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setActiveNavItem(item) {
|
||||
document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
|
||||
item.classList.add('active');
|
||||
}
|
||||
|
||||
function updateContentTitle() {
|
||||
const titleEl = document.getElementById('contentTitle');
|
||||
if (currentView === 'project-overview') {
|
||||
titleEl.textContent = 'Project Overview';
|
||||
} else if (currentView === 'mcp-manager') {
|
||||
titleEl.textContent = 'MCP Server Management';
|
||||
} else if (currentView === 'liteTasks') {
|
||||
const names = { 'lite-plan': 'Lite Plan Sessions', 'lite-fix': 'Lite Fix Sessions' };
|
||||
titleEl.textContent = names[currentLiteType] || 'Lite Tasks';
|
||||
} else if (currentView === 'sessionDetail') {
|
||||
titleEl.textContent = 'Session Detail';
|
||||
} else if (currentView === 'liteTaskDetail') {
|
||||
titleEl.textContent = 'Lite Task Detail';
|
||||
} else {
|
||||
const names = { 'all': 'All Sessions', 'active': 'Active Sessions', 'archived': 'Archived Sessions' };
|
||||
titleEl.textContent = names[currentFilter] || 'Sessions';
|
||||
}
|
||||
}
|
||||
|
||||
// Search
|
||||
function initSearch() {
|
||||
const input = document.getElementById('searchInput');
|
||||
input.addEventListener('input', (e) => {
|
||||
const query = e.target.value.toLowerCase();
|
||||
document.querySelectorAll('.session-card').forEach(card => {
|
||||
const text = card.textContent.toLowerCase();
|
||||
card.style.display = text.includes(query) ? '' : 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Refresh Workspace
|
||||
function initRefreshButton() {
|
||||
const btn = document.getElementById('refreshWorkspace');
|
||||
if (btn) {
|
||||
btn.addEventListener('click', refreshWorkspace);
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshWorkspace() {
|
||||
const btn = document.getElementById('refreshWorkspace');
|
||||
|
||||
// Add spinning animation
|
||||
btn.classList.add('refreshing');
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
if (window.SERVER_MODE) {
|
||||
// Reload data from server
|
||||
const data = await loadDashboardData(projectPath);
|
||||
if (data) {
|
||||
// Update stores
|
||||
sessionDataStore = {};
|
||||
liteTaskDataStore = {};
|
||||
|
||||
// Populate stores
|
||||
[...(data.activeSessions || []), ...(data.archivedSessions || [])].forEach(s => {
|
||||
sessionDataStore[s.session_id] = s;
|
||||
});
|
||||
|
||||
[...(data.liteTasks?.litePlan || []), ...(data.liteTasks?.liteFix || [])].forEach(s => {
|
||||
liteTaskDataStore[s.session_id] = s;
|
||||
});
|
||||
|
||||
// Update global data
|
||||
window.workflowData = data;
|
||||
|
||||
// Update sidebar counts
|
||||
updateSidebarCounts(data);
|
||||
|
||||
// Re-render current view
|
||||
if (currentView === 'sessions') {
|
||||
renderSessions();
|
||||
} else if (currentView === 'liteTasks') {
|
||||
renderLiteTasks();
|
||||
} else if (currentView === 'sessionDetail' && currentSessionDetailKey) {
|
||||
showSessionDetailPage(currentSessionDetailKey);
|
||||
} else if (currentView === 'liteTaskDetail' && currentSessionDetailKey) {
|
||||
showLiteTaskDetailPage(currentSessionDetailKey);
|
||||
} else if (currentView === 'project-overview') {
|
||||
renderProjectOverview();
|
||||
}
|
||||
|
||||
showRefreshToast('Workspace refreshed', 'success');
|
||||
}
|
||||
} else {
|
||||
// Non-server mode: just reload page
|
||||
window.location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Refresh failed:', error);
|
||||
showRefreshToast('Refresh failed: ' + error.message, 'error');
|
||||
} finally {
|
||||
btn.classList.remove('refreshing');
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function updateSidebarCounts(data) {
|
||||
// Update session counts
|
||||
const activeCount = document.querySelector('.nav-item[data-filter="active"] .nav-count');
|
||||
const archivedCount = document.querySelector('.nav-item[data-filter="archived"] .nav-count');
|
||||
const allCount = document.querySelector('.nav-item[data-filter="all"] .nav-count');
|
||||
|
||||
if (activeCount) activeCount.textContent = data.activeSessions?.length || 0;
|
||||
if (archivedCount) archivedCount.textContent = data.archivedSessions?.length || 0;
|
||||
if (allCount) allCount.textContent = (data.activeSessions?.length || 0) + (data.archivedSessions?.length || 0);
|
||||
|
||||
// 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');
|
||||
|
||||
if (litePlanCount) litePlanCount.textContent = data.liteTasks?.litePlan?.length || 0;
|
||||
if (liteFixCount) liteFixCount.textContent = data.liteTasks?.liteFix?.length || 0;
|
||||
}
|
||||
|
||||
function showRefreshToast(message, type) {
|
||||
// Remove existing toast
|
||||
const existing = document.querySelector('.status-toast');
|
||||
if (existing) existing.remove();
|
||||
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `status-toast ${type}`;
|
||||
toast.textContent = message;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.classList.add('fade-out');
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, 2000);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user