mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-08 02:14:08 +08:00
Add end-to-end tests for Graph Explorer, History, Orchestrator, and Project features
- Implemented E2E tests for code relationship visualization in Graph Explorer. - Added tests for archived session management in History, including search, filter, restore, and delete functionalities. - Created tests for workflow orchestration in Orchestrator, covering node creation, connection, deletion, and workflow management. - Developed tests for project statistics and timeline visualization in Project, including error handling and internationalization checks.
This commit is contained in:
639
.claude/skills/workflow-execute/SKILL.md
Normal file
639
.claude/skills/workflow-execute/SKILL.md
Normal file
@@ -0,0 +1,639 @@
|
||||
---
|
||||
name: workflow-execute
|
||||
description: Coordinate agent execution for workflow tasks with automatic session discovery, parallel task processing, and status tracking. Triggers on "workflow execute".
|
||||
allowed-tools: Task, AskUserQuestion, TodoWrite, Read, Write, Edit, Bash, Glob, Grep, Skill, mcp__ace-tool__search_context
|
||||
---
|
||||
|
||||
# Workflow Execute
|
||||
|
||||
Orchestrates autonomous workflow execution through systematic task discovery, agent coordination, and progress tracking. **Executes entire workflow without user interruption** (except initial session selection if multiple active sessions exist), providing complete context to agents and ensuring proper flow control execution with comprehensive TodoWrite tracking.
|
||||
|
||||
**Resume Mode**: When called with `--resume-session` flag, skips discovery phase and directly enters TodoWrite generation and agent execution for the specified session.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Workflow Execute Orchestrator (SKILL.md) │
|
||||
│ → Parse args → Session discovery → Strategy → Execute tasks │
|
||||
└──────────┬───────────────────────────────────────────────────┘
|
||||
│
|
||||
┌──────┴──────┐
|
||||
│ Normal Mode │──→ Phase 1 → Phase 2 → Phase 3 → Phase 4 → Phase 5
|
||||
│ Resume Mode │──→ Phase 3 → Phase 4 → Phase 5
|
||||
└─────────────┘
|
||||
│
|
||||
┌──────┴──────────────────────────────────────────────┐
|
||||
│ Phase 1: Discovery (session selection) │
|
||||
│ Phase 2: Validation (planning doc checks) │
|
||||
│ Phase 3: TodoWrite Gen (progress tracking init) │
|
||||
│ Phase 4: Strategy+Execute (lazy load + agent loop) │
|
||||
│ Phase 5: Completion (status sync + user choice)│
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Key Design Principles
|
||||
|
||||
1. **Autonomous Execution**: Complete entire workflow without user interruption
|
||||
2. **Lazy Loading**: Task JSONs read on-demand during execution, not upfront
|
||||
3. **ONE AGENT = ONE TASK JSON**: Each agent instance executes exactly one task JSON file
|
||||
4. **IMPL_PLAN-Driven Strategy**: Execution model derived from planning document
|
||||
5. **Continuous Progress Tracking**: TodoWrite updates throughout entire workflow
|
||||
|
||||
## Auto Mode
|
||||
|
||||
When `--yes` or `-y`:
|
||||
- **Session Selection**: Automatically selects the first (most recent) active session
|
||||
- **Completion Choice**: Automatically completes session (runs `Skill(skill="workflow:session:complete", args="--yes")`)
|
||||
|
||||
When `--with-commit`:
|
||||
- **Auto-Commit**: After each agent task completes, commit changes based on summary document
|
||||
- **Commit Principle**: Minimal commits - only commit files modified by the completed task
|
||||
- **Commit Message**: Generated from task summary with format: "feat/fix/refactor: {task-title} - {summary}"
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
Skill(skill="workflow-execute", args="<optional flags>")
|
||||
Skill(skill="workflow-execute", args="[FLAGS]")
|
||||
|
||||
# Flags
|
||||
-y, --yes Skip all confirmations (auto mode)
|
||||
--resume-session="<session-id>" Skip discovery, resume specified session
|
||||
--with-commit Auto-commit after each task completion
|
||||
|
||||
# Examples
|
||||
Skill(skill="workflow-execute") # Interactive mode
|
||||
Skill(skill="workflow-execute", args="--yes") # Auto mode
|
||||
Skill(skill="workflow-execute", args="--resume-session=\"WFS-auth\"") # Resume specific session
|
||||
Skill(skill="workflow-execute", args="-y --resume-session=\"WFS-auth\"") # Auto + resume
|
||||
Skill(skill="workflow-execute", args="--with-commit") # With auto-commit
|
||||
Skill(skill="workflow-execute", args="-y --with-commit") # Auto + commit
|
||||
Skill(skill="workflow-execute", args="-y --with-commit --resume-session=\"WFS-auth\"") # All flags
|
||||
```
|
||||
|
||||
## Execution Flow
|
||||
|
||||
```
|
||||
Normal Mode:
|
||||
Phase 1: Discovery
|
||||
├─ Count active sessions
|
||||
└─ Decision:
|
||||
├─ count=0 → ERROR: No active sessions
|
||||
├─ count=1 → Auto-select session → Phase 2
|
||||
└─ count>1 → AskUserQuestion (max 4 options) → Phase 2
|
||||
|
||||
Phase 2: Planning Document Validation
|
||||
├─ Check IMPL_PLAN.md exists
|
||||
├─ Check TODO_LIST.md exists
|
||||
└─ Validate .task/ contains IMPL-*.json files
|
||||
|
||||
Phase 3: TodoWrite Generation
|
||||
├─ Update session status to "active" (Step 0)
|
||||
├─ Parse TODO_LIST.md for task statuses
|
||||
├─ Generate TodoWrite for entire workflow
|
||||
└─ Prepare session context paths
|
||||
|
||||
Phase 4: Execution Strategy & Task Execution
|
||||
├─ Step 4A: Parse execution strategy from IMPL_PLAN.md
|
||||
└─ Step 4B: Execute tasks with lazy loading
|
||||
└─ Loop:
|
||||
├─ Get next in_progress task from TodoWrite
|
||||
├─ Lazy load task JSON
|
||||
├─ Launch agent with task context
|
||||
├─ Mark task completed (update IMPL-*.json status)
|
||||
│ # Quick fix: Update task status for ccw dashboard
|
||||
│ # TS=$(date -Iseconds) && jq --arg ts "$TS" '.status="completed" | .status_history=(.status_history // [])+[{"from":"in_progress","to":"completed","changed_at":$ts}]' IMPL-X.json > tmp.json && mv tmp.json IMPL-X.json
|
||||
├─ [with-commit] Commit changes based on summary (minimal principle)
|
||||
│ # Read summary from .summaries/IMPL-X-summary.md
|
||||
│ # Extract changed files from summary's "Files Modified" section
|
||||
│ # Generate commit message: "feat/fix/refactor: {task-title} - {summary}"
|
||||
│ # git add <changed-files> && git commit -m "<commit-message>"
|
||||
└─ Advance to next task
|
||||
|
||||
Phase 5: Completion
|
||||
├─ Update task statuses in JSON files
|
||||
├─ Generate summaries
|
||||
└─ AskUserQuestion: Choose next step
|
||||
├─ "Enter Review" → Skill(skill="workflow:review")
|
||||
└─ "Complete Session" → Skill(skill="workflow:session:complete")
|
||||
|
||||
Resume Mode (--resume-session):
|
||||
├─ Skip Phase 1 & Phase 2
|
||||
└─ Entry Point: Phase 3 (TodoWrite Generation)
|
||||
├─ Update session status to "active" (if not already)
|
||||
└─ Continue: Phase 4 → Phase 5
|
||||
```
|
||||
|
||||
## Core Rules
|
||||
|
||||
**Complete entire workflow autonomously without user interruption, using TodoWrite for comprehensive progress tracking.**
|
||||
**Execute all discovered pending tasks until workflow completion or blocking dependency.**
|
||||
**User-choice completion: When all tasks finished, ask user to choose review or complete.**
|
||||
**ONE AGENT = ONE TASK JSON: Each agent instance executes exactly one task JSON file - never batch multiple tasks into single agent execution.**
|
||||
|
||||
## Core Responsibilities
|
||||
|
||||
- **Session Discovery**: Identify and select active workflow sessions
|
||||
- **Execution Strategy Parsing**: Extract execution model from IMPL_PLAN.md
|
||||
- **TodoWrite Progress Tracking**: Maintain real-time execution status throughout entire workflow
|
||||
- **Agent Orchestration**: Coordinate specialized agents with complete context
|
||||
- **Status Synchronization**: Update task JSON files and workflow state
|
||||
- **Autonomous Completion**: Continue execution until all tasks complete or reach blocking state
|
||||
- **Session User-Choice Completion**: Ask user to choose review or complete when all tasks finished
|
||||
|
||||
## Execution Philosophy
|
||||
|
||||
- **Progress tracking**: Continuous TodoWrite updates throughout entire workflow execution
|
||||
- **Autonomous completion**: Execute all tasks without user interruption until workflow complete
|
||||
|
||||
## Performance Optimization Strategy
|
||||
|
||||
**Lazy Loading**: Task JSONs read **on-demand** during execution, not upfront. TODO_LIST.md + IMPL_PLAN.md provide metadata for planning.
|
||||
|
||||
**Loading Strategy**:
|
||||
- **TODO_LIST.md**: Read in Phase 3 (task metadata, status, dependencies for TodoWrite generation)
|
||||
- **IMPL_PLAN.md**: Check existence in Phase 2 (normal mode), parse execution strategy in Phase 4A
|
||||
- **Task JSONs**: Lazy loading - read only when task is about to execute (Phase 4B)
|
||||
|
||||
## Execution Lifecycle
|
||||
|
||||
### Phase 1: Discovery
|
||||
**Applies to**: Normal mode only (skipped in resume mode)
|
||||
|
||||
**Purpose**: Find and select active workflow session with user confirmation when multiple sessions exist
|
||||
|
||||
**Process**:
|
||||
|
||||
#### Step 1.1: Count Active Sessions
|
||||
```bash
|
||||
bash(find .workflow/active/ -name "WFS-*" -type d 2>/dev/null | wc -l)
|
||||
```
|
||||
|
||||
#### Step 1.2: Handle Session Selection
|
||||
|
||||
**Case A: No Sessions** (count = 0)
|
||||
```
|
||||
ERROR: No active workflow sessions found
|
||||
Run Skill(skill="workflow:plan", args="\"task description\"") to create a session
|
||||
```
|
||||
|
||||
**Case B: Single Session** (count = 1)
|
||||
```bash
|
||||
bash(find .workflow/active/ -name "WFS-*" -type d 2>/dev/null | head -1 | xargs basename)
|
||||
```
|
||||
Auto-select and continue to Phase 2.
|
||||
|
||||
**Case C: Multiple Sessions** (count > 1)
|
||||
|
||||
List sessions with metadata and prompt user selection:
|
||||
```bash
|
||||
bash(for dir in .workflow/active/WFS-*/; do [ -d "$dir" ] || continue; session=$(basename "$dir"); project=$(jq -r '.project // "Unknown"' "${dir}workflow-session.json" 2>/dev/null || echo "Unknown"); total=$(grep -c '^\- \[' "${dir}TODO_LIST.md" 2>/dev/null || echo 0); completed=$(grep -c '^\- \[x\]' "${dir}TODO_LIST.md" 2>/dev/null || echo 0); if [ "$total" -gt 0 ]; then progress=$((completed * 100 / total)); else progress=0; fi; echo "$session | $project | $completed/$total tasks ($progress%)"; done)
|
||||
```
|
||||
|
||||
**Parse --yes flag**:
|
||||
```javascript
|
||||
const autoYes = $ARGUMENTS.includes('--yes') || $ARGUMENTS.includes('-y')
|
||||
```
|
||||
|
||||
**Conditional Selection**:
|
||||
```javascript
|
||||
if (autoYes) {
|
||||
// Auto mode: Select first session (most recent)
|
||||
const firstSession = sessions[0]
|
||||
console.log(`[--yes] Auto-selecting session: ${firstSession.id}`)
|
||||
selectedSessionId = firstSession.id
|
||||
// Continue to Phase 2
|
||||
} else {
|
||||
// Interactive mode: Use AskUserQuestion to present formatted options (max 4 options shown)
|
||||
// If more than 4 sessions, show most recent 4 with "Other" option for manual input
|
||||
const sessions = getActiveSessions() // sorted by last modified
|
||||
const displaySessions = sessions.slice(0, 4)
|
||||
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "Multiple active sessions detected. Select one:",
|
||||
header: "Session",
|
||||
multiSelect: false,
|
||||
options: displaySessions.map(s => ({
|
||||
label: s.id,
|
||||
description: `${s.project} | ${s.progress}`
|
||||
}))
|
||||
// Note: User can select "Other" to manually enter session ID
|
||||
}]
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Input Validation**:
|
||||
- If user selects from options: Use selected session ID
|
||||
- If user selects "Other" and provides input: Validate session exists
|
||||
- If validation fails: Show error and re-prompt or suggest available sessions
|
||||
|
||||
Parse user input (supports: number "1", full ID "WFS-auth-system", or partial "auth"), validate selection, and continue to Phase 2.
|
||||
|
||||
#### Step 1.3: Load Session Metadata
|
||||
```bash
|
||||
bash(cat .workflow/active/${sessionId}/workflow-session.json)
|
||||
```
|
||||
|
||||
**Output**: Store session metadata in memory
|
||||
**DO NOT read task JSONs yet** - defer until execution phase (lazy loading)
|
||||
|
||||
**Resume Mode**: This entire phase is skipped when `--resume-session="session-id"` flag is provided.
|
||||
|
||||
### Phase 2: Planning Document Validation
|
||||
**Applies to**: Normal mode only (skipped in resume mode)
|
||||
|
||||
**Purpose**: Validate planning artifacts exist before execution
|
||||
|
||||
**Process**:
|
||||
1. **Check IMPL_PLAN.md**: Verify file exists (defer detailed parsing to Phase 4A)
|
||||
2. **Check TODO_LIST.md**: Verify file exists (defer reading to Phase 3)
|
||||
3. **Validate Task Directory**: Ensure `.task/` contains at least one IMPL-*.json file
|
||||
|
||||
**Key Optimization**: Only existence checks here. Actual file reading happens in later phases.
|
||||
|
||||
**Resume Mode**: This phase is skipped when `--resume-session` flag is provided. Resume mode entry point is Phase 3.
|
||||
|
||||
### Phase 3: TodoWrite Generation
|
||||
**Applies to**: Both normal and resume modes (resume mode entry point)
|
||||
|
||||
**Step 0: Update Session Status to Active**
|
||||
Before generating TodoWrite, update session status from "planning" to "active":
|
||||
```bash
|
||||
# Update session status (idempotent - safe to run if already active)
|
||||
jq '.status = "active" | .execution_started_at = (.execution_started_at // now | todate)' \
|
||||
.workflow/active/${sessionId}/workflow-session.json > tmp.json && \
|
||||
mv tmp.json .workflow/active/${sessionId}/workflow-session.json
|
||||
```
|
||||
This ensures the dashboard shows the session as "ACTIVE" during execution.
|
||||
|
||||
**Process**:
|
||||
1. **Create TodoWrite List**: Generate task list from TODO_LIST.md (not from task JSONs)
|
||||
- Parse TODO_LIST.md to extract all tasks with current statuses
|
||||
- Identify first pending task with met dependencies
|
||||
- Generate comprehensive TodoWrite covering entire workflow
|
||||
2. **Prepare Session Context**: Inject workflow paths for agent use (using provided session-id)
|
||||
3. **Validate Prerequisites**: Ensure IMPL_PLAN.md and TODO_LIST.md exist and are valid
|
||||
|
||||
**Resume Mode Behavior**:
|
||||
- Load existing TODO_LIST.md directly from `.workflow/active/{session-id}/`
|
||||
- Extract current progress from TODO_LIST.md
|
||||
- Generate TodoWrite from TODO_LIST.md state
|
||||
- Proceed immediately to agent execution (Phase 4)
|
||||
|
||||
### Phase 4: Execution Strategy Selection & Task Execution
|
||||
**Applies to**: Both normal and resume modes
|
||||
|
||||
**Step 4A: Parse Execution Strategy from IMPL_PLAN.md**
|
||||
|
||||
Read IMPL_PLAN.md Section 4 to extract:
|
||||
- **Execution Model**: Sequential | Parallel | Phased | TDD Cycles
|
||||
- **Parallelization Opportunities**: Which tasks can run in parallel
|
||||
- **Serialization Requirements**: Which tasks must run sequentially
|
||||
- **Critical Path**: Priority execution order
|
||||
|
||||
If IMPL_PLAN.md lacks execution strategy, use intelligent fallback (analyze task structure).
|
||||
|
||||
**Step 4B: Execute Tasks with Lazy Loading**
|
||||
|
||||
**Key Optimization**: Read task JSON **only when needed** for execution
|
||||
|
||||
**Execution Loop Pattern**:
|
||||
```
|
||||
while (TODO_LIST.md has pending tasks) {
|
||||
next_task_id = getTodoWriteInProgressTask()
|
||||
task_json = Read(.workflow/active/{session}/.task/{next_task_id}.json) // Lazy load
|
||||
executeTaskWithAgent(task_json)
|
||||
updateTodoListMarkCompleted(next_task_id)
|
||||
advanceTodoWriteToNextTask()
|
||||
}
|
||||
```
|
||||
|
||||
**Execution Process per Task**:
|
||||
1. **Identify Next Task**: From TodoWrite, get the next `in_progress` task ID
|
||||
2. **Load Task JSON on Demand**: Read `.task/{task-id}.json` for current task ONLY
|
||||
3. **Validate Task Structure**: Ensure all 5 required fields exist (id, title, status, meta, context, flow_control)
|
||||
4. **Launch Agent**: Invoke specialized agent with complete context including flow control steps
|
||||
5. **Monitor Progress**: Track agent execution and handle errors without user interruption
|
||||
6. **Collect Results**: Gather implementation results and outputs
|
||||
7. **[with-commit] Auto-Commit**: If `--with-commit` flag enabled, commit changes based on summary
|
||||
- Read summary from `.summaries/{task-id}-summary.md`
|
||||
- Extract changed files from summary's "Files Modified" section
|
||||
- Determine commit type from `meta.type` (feature→feat, bugfix→fix, refactor→refactor)
|
||||
- Generate commit message: "{type}: {task-title} - {summary-first-line}"
|
||||
- Commit only modified files (minimal principle): `git add <files> && git commit -m "<message>"`
|
||||
8. **Continue Workflow**: Identify next pending task from TODO_LIST.md and repeat
|
||||
|
||||
**Note**: TODO_LIST.md updates are handled by agents (e.g., code-developer.md), not by the orchestrator.
|
||||
|
||||
### Phase 5: Completion
|
||||
**Applies to**: Both normal and resume modes
|
||||
|
||||
**Process**:
|
||||
1. **Update Task Status**: Mark completed tasks in JSON files
|
||||
2. **Generate Summary**: Create task summary in `.summaries/`
|
||||
3. **Update TodoWrite**: Mark current task complete, advance to next
|
||||
4. **Synchronize State**: Update session state and workflow status
|
||||
5. **Check Workflow Complete**: Verify all tasks are completed
|
||||
6. **User Choice**: When all tasks finished, ask user to choose next step:
|
||||
|
||||
```javascript
|
||||
// Parse --yes flag
|
||||
const autoYes = $ARGUMENTS.includes('--yes') || $ARGUMENTS.includes('-y')
|
||||
|
||||
if (autoYes) {
|
||||
// Auto mode: Complete session automatically
|
||||
console.log(`[--yes] Auto-selecting: Complete Session`)
|
||||
Skill(skill="workflow:session:complete", args="--yes")
|
||||
} else {
|
||||
// Interactive mode: Ask user
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "All tasks completed. What would you like to do next?",
|
||||
header: "Next Step",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{
|
||||
label: "Enter Review",
|
||||
description: "Run specialized review (security/architecture/quality/action-items)"
|
||||
},
|
||||
{
|
||||
label: "Complete Session",
|
||||
description: "Archive session and update manifest"
|
||||
}
|
||||
]
|
||||
}]
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Based on user selection**:
|
||||
- **"Enter Review"**: Execute `Skill(skill="workflow:review")`
|
||||
- **"Complete Session"**: Execute `Skill(skill="workflow:session:complete")`
|
||||
|
||||
### Post-Completion Expansion
|
||||
|
||||
完成后询问用户是否扩展为issue(test/enhance/refactor/doc),选中项调用 `Skill(skill="issue:new", args="\"{summary} - {dimension}\"")`
|
||||
|
||||
## Execution Strategy (IMPL_PLAN-Driven)
|
||||
|
||||
### Strategy Priority
|
||||
|
||||
**IMPL_PLAN-Driven Execution (Recommended)**:
|
||||
1. **Read IMPL_PLAN.md execution strategy** (Section 4: Implementation Strategy)
|
||||
2. **Follow explicit guidance**:
|
||||
- Execution Model (Sequential/Parallel/Phased/TDD)
|
||||
- Parallelization Opportunities (which tasks can run in parallel)
|
||||
- Serialization Requirements (which tasks must run sequentially)
|
||||
- Critical Path (priority execution order)
|
||||
3. **Use TODO_LIST.md for status tracking** only
|
||||
4. **IMPL_PLAN decides "HOW"**, execute implements it
|
||||
|
||||
**Intelligent Fallback (When IMPL_PLAN lacks execution details)**:
|
||||
1. **Analyze task structure**:
|
||||
- Check `meta.execution_group` in task JSONs
|
||||
- Analyze `depends_on` relationships
|
||||
- Understand task complexity and risk
|
||||
2. **Apply smart defaults**:
|
||||
- No dependencies + same execution_group → Parallel
|
||||
- Has dependencies → Sequential (wait for deps)
|
||||
- Critical/high-risk tasks → Sequential
|
||||
3. **Conservative approach**: When uncertain, prefer sequential execution
|
||||
|
||||
### Execution Models
|
||||
|
||||
#### 1. Sequential Execution
|
||||
**When**: IMPL_PLAN specifies "Sequential" OR no clear parallelization guidance
|
||||
**Pattern**: Execute tasks one by one in TODO_LIST order
|
||||
**TodoWrite**: ONE task marked as `in_progress` at a time
|
||||
|
||||
#### 2. Parallel Execution
|
||||
**When**: IMPL_PLAN specifies "Parallel" with clear parallelization opportunities
|
||||
**Pattern**: Execute independent task groups concurrently by launching multiple agent instances
|
||||
**TodoWrite**: MULTIPLE tasks (in same batch) marked as `in_progress` simultaneously
|
||||
**Agent Instantiation**: Launch one agent instance per task (respects ONE AGENT = ONE TASK JSON rule)
|
||||
|
||||
#### 3. Phased Execution
|
||||
**When**: IMPL_PLAN specifies "Phased" with phase breakdown
|
||||
**Pattern**: Execute tasks in phases, respect phase boundaries
|
||||
**TodoWrite**: Within each phase, follow Sequential or Parallel rules
|
||||
|
||||
#### 4. Intelligent Fallback
|
||||
**When**: IMPL_PLAN lacks execution strategy details
|
||||
**Pattern**: Analyze task structure and apply smart defaults
|
||||
**TodoWrite**: Follow Sequential or Parallel rules based on analysis
|
||||
|
||||
### Task Status Logic
|
||||
```
|
||||
pending + dependencies_met → executable
|
||||
completed → skip
|
||||
blocked → skip until dependencies clear
|
||||
```
|
||||
|
||||
## TodoWrite Coordination
|
||||
|
||||
### TodoWrite Rules (Unified)
|
||||
|
||||
**Rule 1: Initial Creation**
|
||||
- **Normal Mode**: Generate TodoWrite from discovered pending tasks for entire workflow
|
||||
- **Resume Mode**: Generate from existing session state and current progress
|
||||
|
||||
**Rule 2: In-Progress Task Count (Execution-Model-Dependent)**
|
||||
- **Sequential execution**: Mark ONLY ONE task as `in_progress` at a time
|
||||
- **Parallel batch execution**: Mark ALL tasks in current batch as `in_progress` simultaneously
|
||||
- **Execution group indicator**: Show `[execution_group: group-id]` for parallel tasks
|
||||
|
||||
**Rule 3: Status Updates**
|
||||
- **Immediate Updates**: Update status after each task/batch completion without user interruption
|
||||
- **Status Synchronization**: Sync with JSON task files after updates
|
||||
- **Continuous Tracking**: Maintain TodoWrite throughout entire workflow execution until completion
|
||||
|
||||
**Rule 4: Workflow Completion Check**
|
||||
- When all tasks marked `completed`, prompt user to choose review or complete session
|
||||
|
||||
### TodoWrite Tool Usage
|
||||
|
||||
**Example 1: Sequential Execution**
|
||||
```javascript
|
||||
TodoWrite({
|
||||
todos: [
|
||||
{
|
||||
content: "Execute IMPL-1.1: Design auth schema [code-developer] [FLOW_CONTROL]",
|
||||
status: "in_progress", // ONE task in progress
|
||||
activeForm: "Executing IMPL-1.1: Design auth schema"
|
||||
},
|
||||
{
|
||||
content: "Execute IMPL-1.2: Implement auth logic [code-developer] [FLOW_CONTROL]",
|
||||
status: "pending",
|
||||
activeForm: "Executing IMPL-1.2: Implement auth logic"
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
**Example 2: Parallel Batch Execution**
|
||||
```javascript
|
||||
TodoWrite({
|
||||
todos: [
|
||||
{
|
||||
content: "Execute IMPL-1.1: Build Auth API [code-developer] [execution_group: parallel-auth-api]",
|
||||
status: "in_progress", // Batch task 1
|
||||
activeForm: "Executing IMPL-1.1: Build Auth API"
|
||||
},
|
||||
{
|
||||
content: "Execute IMPL-1.2: Build User UI [code-developer] [execution_group: parallel-ui-comp]",
|
||||
status: "in_progress", // Batch task 2 (running concurrently)
|
||||
activeForm: "Executing IMPL-1.2: Build User UI"
|
||||
},
|
||||
{
|
||||
content: "Execute IMPL-1.3: Setup Database [code-developer] [execution_group: parallel-db-schema]",
|
||||
status: "in_progress", // Batch task 3 (running concurrently)
|
||||
activeForm: "Executing IMPL-1.3: Setup Database"
|
||||
},
|
||||
{
|
||||
content: "Execute IMPL-2.1: Integration Tests [test-fix-agent] [depends_on: IMPL-1.1, IMPL-1.2, IMPL-1.3]",
|
||||
status: "pending", // Next batch (waits for current batch completion)
|
||||
activeForm: "Executing IMPL-2.1: Integration Tests"
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
## Agent Execution Pattern
|
||||
|
||||
### Flow Control Execution
|
||||
**[FLOW_CONTROL]** marker indicates task JSON contains `flow_control.pre_analysis` steps for context preparation.
|
||||
|
||||
**Note**: Orchestrator does NOT execute flow control steps - Agent interprets and executes them autonomously.
|
||||
|
||||
### Agent Prompt Template
|
||||
**Path-Based Invocation**: Pass paths and trigger markers, let agent parse task JSON autonomously.
|
||||
|
||||
```bash
|
||||
Task(subagent_type="{meta.agent}",
|
||||
run_in_background=false,
|
||||
prompt="Implement task {task.id}: {task.title}
|
||||
|
||||
[FLOW_CONTROL]
|
||||
|
||||
**Input**:
|
||||
- Task JSON: {session.task_json_path}
|
||||
- Context Package: {session.context_package_path}
|
||||
|
||||
**Output Location**:
|
||||
- Workflow: {session.workflow_dir}
|
||||
- TODO List: {session.todo_list_path}
|
||||
- Summaries: {session.summaries_dir}
|
||||
|
||||
**Execution**: Read task JSON → Execute pre_analysis → Check execution_config.method → (CLI: handoff to CLI tool | Agent: direct implementation) → Update TODO_LIST.md → Generate summary",
|
||||
description="Implement: {task.id}")
|
||||
```
|
||||
|
||||
**Key Markers**:
|
||||
- `Implement` keyword: Triggers tech stack detection and guidelines loading
|
||||
- `[FLOW_CONTROL]`: Triggers flow_control.pre_analysis execution
|
||||
|
||||
**Why Path-Based**: Agent (code-developer.md) autonomously:
|
||||
- Reads and parses task JSON (requirements, acceptance, flow_control, execution_config)
|
||||
- Executes pre_analysis steps (Phase 1: context gathering)
|
||||
- Checks execution_config.method (Phase 2: determine mode)
|
||||
- CLI mode: Builds handoff prompt and executes via ccw cli with resume strategy
|
||||
- Agent mode: Directly implements using modification_points and logic_flow
|
||||
- Generates structured summary with integration points
|
||||
|
||||
Embedding task content in prompt creates duplication and conflicts with agent's parsing logic.
|
||||
|
||||
### Agent Assignment Rules
|
||||
```
|
||||
meta.agent specified → Use specified agent
|
||||
meta.agent missing → Infer from meta.type:
|
||||
- "feature" → @code-developer
|
||||
- "test-gen" → @code-developer
|
||||
- "test-fix" → @test-fix-agent
|
||||
- "review" → @universal-executor
|
||||
- "docs" → @doc-generator
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
```
|
||||
Phase 1 (Discovery) → selectedSessionId, sessionMetadata
|
||||
↓
|
||||
Phase 2 (Validation) → validated paths (IMPL_PLAN.md, TODO_LIST.md, .task/)
|
||||
↓
|
||||
Phase 3 (TodoWrite Gen) → todoWriteList, sessionContextPaths
|
||||
↓
|
||||
Phase 4 (Execute) → per-task: taskJson (lazy), agentResult, summaryDoc
|
||||
↓
|
||||
Phase 5 (Completion) → updatedStatuses, userChoice (review|complete)
|
||||
```
|
||||
|
||||
## Workflow File Structure Reference
|
||||
```
|
||||
.workflow/active/WFS-[topic-slug]/
|
||||
├── workflow-session.json # Session state and metadata
|
||||
├── IMPL_PLAN.md # Planning document and requirements
|
||||
├── TODO_LIST.md # Progress tracking (updated by agents)
|
||||
├── .task/ # Task definitions (JSON only)
|
||||
│ ├── IMPL-1.json # Main task definitions
|
||||
│ └── IMPL-1.1.json # Subtask definitions
|
||||
├── .summaries/ # Task completion summaries
|
||||
│ ├── IMPL-1-summary.md # Task completion details
|
||||
│ └── IMPL-1.1-summary.md # Subtask completion details
|
||||
└── .process/ # Planning artifacts
|
||||
├── context-package.json # Smart context package
|
||||
└── ANALYSIS_RESULTS.md # Planning analysis results
|
||||
```
|
||||
|
||||
## Auto-Commit Mode (--with-commit)
|
||||
|
||||
**Behavior**: After each agent task completes, automatically commit changes based on summary document.
|
||||
|
||||
**Minimal Principle**: Only commit files modified by the completed task.
|
||||
|
||||
**Commit Message Format**: `{type}: {task-title} - {summary}`
|
||||
|
||||
**Type Mapping** (from `meta.type`):
|
||||
- `feature` → `feat` | `bugfix` → `fix` | `refactor` → `refactor`
|
||||
- `test-gen` → `test` | `docs` → `docs` | `review` → `chore`
|
||||
|
||||
**Implementation**:
|
||||
```bash
|
||||
# 1. Read summary from .summaries/{task-id}-summary.md
|
||||
# 2. Extract files from "Files Modified" section
|
||||
# 3. Commit: git add <files> && git commit -m "{type}: {title} - {summary}"
|
||||
```
|
||||
|
||||
**Error Handling**: Skip commit on no changes/missing summary, log errors, continue workflow.
|
||||
|
||||
## Error Handling & Recovery
|
||||
|
||||
### Common Errors & Recovery
|
||||
|
||||
| Error Type | Cause | Recovery Strategy | Max Attempts |
|
||||
|-----------|-------|------------------|--------------|
|
||||
| **Discovery Errors** |
|
||||
| No active session | No sessions in `.workflow/active/` | Create or resume session: `Skill(skill="workflow:plan", args="\"project\"")` | N/A |
|
||||
| Multiple sessions | Multiple sessions in `.workflow/active/` | Prompt user selection | N/A |
|
||||
| Corrupted session | Invalid JSON files | Recreate session structure or validate files | N/A |
|
||||
| **Execution Errors** |
|
||||
| Agent failure | Agent crash/timeout | Retry with simplified context | 2 |
|
||||
| Flow control error | Command failure | Skip optional, fail critical | 1 per step |
|
||||
| Context loading error | Missing dependencies | Reload from JSON, use defaults | 3 |
|
||||
| JSON file corruption | File system issues | Restore from backup/recreate | 1 |
|
||||
|
||||
### Error Prevention
|
||||
- **Pre-flight Checks**: Validate session integrity before execution
|
||||
- **Backup Strategy**: Create task snapshots before major operations
|
||||
- **Atomic Updates**: Update JSON files atomically to prevent corruption
|
||||
- **Dependency Validation**: Check all depends_on references exist
|
||||
- **Context Verification**: Ensure all required context is available
|
||||
|
||||
## Flag Parsing
|
||||
|
||||
```javascript
|
||||
const autoYes = $ARGUMENTS.includes('--yes') || $ARGUMENTS.includes('-y')
|
||||
const withCommit = $ARGUMENTS.includes('--with-commit')
|
||||
```
|
||||
301
.codex/skills/ccw-loop-b/README.md
Normal file
301
.codex/skills/ccw-loop-b/README.md
Normal file
@@ -0,0 +1,301 @@
|
||||
# CCW Loop-B: Hybrid Orchestrator Pattern
|
||||
|
||||
Iterative development workflow using coordinator + specialized workers architecture.
|
||||
|
||||
## Overview
|
||||
|
||||
CCW Loop-B implements a flexible orchestration pattern:
|
||||
- **Coordinator**: Main agent managing state, user interaction, worker scheduling
|
||||
- **Workers**: Specialized agents (init, develop, debug, validate, complete)
|
||||
- **Modes**: Interactive / Auto / Parallel execution
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Coordinator (Main Agent)
|
||||
|
|
||||
+-- Spawns Workers
|
||||
| - ccw-loop-b-init.md
|
||||
| - ccw-loop-b-develop.md
|
||||
| - ccw-loop-b-debug.md
|
||||
| - ccw-loop-b-validate.md
|
||||
| - ccw-loop-b-complete.md
|
||||
|
|
||||
+-- Batch Wait (parallel mode)
|
||||
+-- Sequential Wait (auto/interactive)
|
||||
+-- State Management
|
||||
+-- User Interaction
|
||||
```
|
||||
|
||||
## Subagent API
|
||||
|
||||
Core APIs for worker orchestration:
|
||||
|
||||
| API | 作用 |
|
||||
|-----|------|
|
||||
| `spawn_agent({ message })` | 创建 worker,返回 `agent_id` |
|
||||
| `wait({ ids, timeout_ms })` | 等待结果(唯一取结果入口) |
|
||||
| `send_input({ id, message })` | 继续交互 |
|
||||
| `close_agent({ id })` | 关闭回收 |
|
||||
|
||||
**可用模式**: 单 agent 深度交互 / 多 agent 并行 / 混合模式
|
||||
|
||||
## Execution Modes
|
||||
|
||||
### Interactive Mode (default)
|
||||
|
||||
Coordinator displays menu, user selects action, spawns corresponding worker.
|
||||
|
||||
```bash
|
||||
/ccw-loop-b TASK="Implement feature X"
|
||||
```
|
||||
|
||||
**Flow**:
|
||||
1. Init: Parse task, create breakdown
|
||||
2. Menu: Show options to user
|
||||
3. User selects action (develop/debug/validate)
|
||||
4. Spawn worker for selected action
|
||||
5. Wait for result
|
||||
6. Display result, back to menu
|
||||
7. Repeat until complete
|
||||
|
||||
### Auto Mode
|
||||
|
||||
Automated sequential execution following predefined workflow.
|
||||
|
||||
```bash
|
||||
/ccw-loop-b --mode=auto TASK="Fix bug Y"
|
||||
```
|
||||
|
||||
**Flow**:
|
||||
1. Init → 2. Develop → 3. Validate → 4. Complete
|
||||
|
||||
If issues found: loop back to Debug → Develop → Validate
|
||||
|
||||
### Parallel Mode
|
||||
|
||||
Spawn multiple workers simultaneously, batch wait for results.
|
||||
|
||||
```bash
|
||||
/ccw-loop-b --mode=parallel TASK="Analyze module Z"
|
||||
```
|
||||
|
||||
**Flow**:
|
||||
1. Init: Create analysis plan
|
||||
2. Spawn workers in parallel: [develop, debug, validate]
|
||||
3. Batch wait: `wait({ ids: [w1, w2, w3] })`
|
||||
4. Merge results
|
||||
5. Coordinator decides next action
|
||||
6. Complete
|
||||
|
||||
## Session Structure
|
||||
|
||||
```
|
||||
.workflow/.loop/
|
||||
+-- {loopId}.json # Master state
|
||||
+-- {loopId}.workers/ # Worker outputs
|
||||
| +-- init.output.json
|
||||
| +-- develop.output.json
|
||||
| +-- debug.output.json
|
||||
| +-- validate.output.json
|
||||
| +-- complete.output.json
|
||||
+-- {loopId}.progress/ # Human-readable logs
|
||||
+-- develop.md
|
||||
+-- debug.md
|
||||
+-- validate.md
|
||||
+-- summary.md
|
||||
```
|
||||
|
||||
## Worker Responsibilities
|
||||
|
||||
| Worker | Role | Specialization |
|
||||
|--------|------|----------------|
|
||||
| **init** | Session initialization | Task parsing, breakdown, planning |
|
||||
| **develop** | Code implementation | File operations, pattern matching, incremental development |
|
||||
| **debug** | Problem diagnosis | Root cause analysis, hypothesis testing, fix recommendations |
|
||||
| **validate** | Testing & verification | Test execution, coverage analysis, quality gates |
|
||||
| **complete** | Session finalization | Summary generation, commit preparation, cleanup |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Example 1: Simple Feature Implementation
|
||||
|
||||
```bash
|
||||
/ccw-loop-b TASK="Add user logout function"
|
||||
```
|
||||
|
||||
**Auto flow**:
|
||||
- Init: Parse requirements
|
||||
- Develop: Implement logout in `src/auth.ts`
|
||||
- Validate: Run tests
|
||||
- Complete: Generate commit message
|
||||
|
||||
### Example 2: Bug Investigation
|
||||
|
||||
```bash
|
||||
/ccw-loop-b TASK="Fix memory leak in WebSocket handler"
|
||||
```
|
||||
|
||||
**Interactive flow**:
|
||||
1. Init: Parse issue
|
||||
2. User selects "debug" → Spawn debug worker
|
||||
3. Debug: Root cause analysis → recommends fix
|
||||
4. User selects "develop" → Apply fix
|
||||
5. User selects "validate" → Verify fix works
|
||||
6. User selects "complete" → Generate summary
|
||||
|
||||
### Example 3: Comprehensive Analysis
|
||||
|
||||
```bash
|
||||
/ccw-loop-b --mode=parallel TASK="Analyze payment module for improvements"
|
||||
```
|
||||
|
||||
**Parallel flow**:
|
||||
- Spawn [develop, debug, validate] workers simultaneously
|
||||
- Develop: Analyze code quality and patterns
|
||||
- Debug: Identify potential issues
|
||||
- Validate: Check test coverage
|
||||
- Wait for all three to complete
|
||||
- Merge findings into comprehensive report
|
||||
|
||||
### Example 4: Resume Existing Loop
|
||||
|
||||
```bash
|
||||
/ccw-loop-b --loop-id=loop-b-20260122-abc123
|
||||
```
|
||||
|
||||
Continues from previous state, respects status (running/paused).
|
||||
|
||||
## Key Features
|
||||
|
||||
### 1. Worker Specialization
|
||||
|
||||
Each worker focuses on one domain:
|
||||
- **No overlap**: Clear boundaries between workers
|
||||
- **Reusable**: Same worker for different tasks
|
||||
- **Composable**: Combine workers for complex workflows
|
||||
|
||||
### 2. Flexible Coordination
|
||||
|
||||
Coordinator adapts to mode:
|
||||
- **Interactive**: Menu-driven, user controls flow
|
||||
- **Auto**: Predetermined sequence
|
||||
- **Parallel**: Concurrent execution with batch wait
|
||||
|
||||
### 3. State Management
|
||||
|
||||
Unified state at `.workflow/.loop/{loopId}.json`:
|
||||
- **API compatible**: Works with CCW API
|
||||
- **Extension fields**: Skill-specific data in `skill_state`
|
||||
- **Worker outputs**: Structured JSON for each action
|
||||
|
||||
### 4. Progress Tracking
|
||||
|
||||
Human-readable logs:
|
||||
- **Per-worker progress**: `{action}.md` files
|
||||
- **Summary**: Consolidated achievements
|
||||
- **Commit-ready**: Formatted commit messages
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Start with Init**: Always initialize before execution
|
||||
2. **Use appropriate mode**:
|
||||
- Interactive: Complex tasks needing user decisions
|
||||
- Auto: Well-defined workflows
|
||||
- Parallel: Independent analysis tasks
|
||||
3. **Clean up workers**: `close_agent()` after each worker completes
|
||||
4. **Batch wait wisely**: Use in parallel mode for efficiency
|
||||
5. **Track progress**: Document in progress files
|
||||
6. **Validate often**: After each develop phase
|
||||
|
||||
## Implementation Patterns
|
||||
|
||||
### Pattern 1: Single Worker Deep Interaction
|
||||
|
||||
```javascript
|
||||
const workerId = spawn_agent({ message: workerPrompt })
|
||||
const result1 = wait({ ids: [workerId] })
|
||||
|
||||
// Continue with same worker
|
||||
send_input({ id: workerId, message: "Continue with next task" })
|
||||
const result2 = wait({ ids: [workerId] })
|
||||
|
||||
close_agent({ id: workerId })
|
||||
```
|
||||
|
||||
### Pattern 2: Multi-Worker Parallel
|
||||
|
||||
```javascript
|
||||
const workers = {
|
||||
develop: spawn_agent({ message: developPrompt }),
|
||||
debug: spawn_agent({ message: debugPrompt }),
|
||||
validate: spawn_agent({ message: validatePrompt })
|
||||
}
|
||||
|
||||
// Batch wait
|
||||
const results = wait({ ids: Object.values(workers), timeout_ms: 900000 })
|
||||
|
||||
// Process all results
|
||||
Object.values(workers).forEach(id => close_agent({ id }))
|
||||
```
|
||||
|
||||
### Pattern 3: Sequential Worker Chain
|
||||
|
||||
```javascript
|
||||
const actions = ['init', 'develop', 'validate', 'complete']
|
||||
|
||||
for (const action of actions) {
|
||||
const workerId = spawn_agent({ message: buildPrompt(action) })
|
||||
const result = wait({ ids: [workerId] })
|
||||
|
||||
updateState(action, result)
|
||||
close_agent({ id: workerId })
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Recovery |
|
||||
|-------|----------|
|
||||
| Worker timeout | `send_input` request convergence |
|
||||
| Worker fails | Log error, coordinator decides retry strategy |
|
||||
| Partial results | Use completed workers, mark incomplete |
|
||||
| State corruption | Rebuild from progress files |
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
.codex/skills/ccw-loop-b/
|
||||
+-- SKILL.md # Entry point
|
||||
+-- README.md # This file
|
||||
+-- phases/
|
||||
| +-- state-schema.md # State structure definition
|
||||
+-- specs/
|
||||
+-- action-catalog.md # Action reference
|
||||
|
||||
.codex/agents/
|
||||
+-- ccw-loop-b-init.md # Worker: Init
|
||||
+-- ccw-loop-b-develop.md # Worker: Develop
|
||||
+-- ccw-loop-b-debug.md # Worker: Debug
|
||||
+-- ccw-loop-b-validate.md # Worker: Validate
|
||||
+-- ccw-loop-b-complete.md # Worker: Complete
|
||||
```
|
||||
|
||||
## Comparison: ccw-loop vs ccw-loop-b
|
||||
|
||||
| Aspect | ccw-loop | ccw-loop-b |
|
||||
|--------|----------|------------|
|
||||
| Pattern | Single agent, multi-phase | Coordinator + workers |
|
||||
| Worker model | Single agent handles all | Specialized workers per action |
|
||||
| Parallelization | Sequential only | Supports parallel mode |
|
||||
| Flexibility | Fixed sequence | Mode-based (interactive/auto/parallel) |
|
||||
| Best for | Simple linear workflows | Complex tasks needing specialization |
|
||||
|
||||
## Contributing
|
||||
|
||||
To add new workers:
|
||||
1. Create worker role file in `.codex/agents/`
|
||||
2. Define clear responsibilities
|
||||
3. Update `action-catalog.md`
|
||||
4. Add worker to coordinator spawn logic
|
||||
5. Test integration with existing workers
|
||||
323
.codex/skills/ccw-loop-b/SKILL.md
Normal file
323
.codex/skills/ccw-loop-b/SKILL.md
Normal file
@@ -0,0 +1,323 @@
|
||||
---
|
||||
name: CCW Loop-B
|
||||
description: Hybrid orchestrator pattern for iterative development. Coordinator + specialized workers with batch wait support. Triggers on "ccw-loop-b".
|
||||
argument-hint: TASK="<task description>" [--loop-id=<id>] [--mode=<interactive|auto|parallel>]
|
||||
---
|
||||
|
||||
# CCW Loop-B - Hybrid Orchestrator Pattern
|
||||
|
||||
协调器 + 专用 worker 的迭代开发工作流。支持单 agent 深度交互、多 agent 并行、混合模式灵活切换。
|
||||
|
||||
## Arguments
|
||||
|
||||
| Arg | Required | Description |
|
||||
|-----|----------|-------------|
|
||||
| TASK | No | Task description (for new loop) |
|
||||
| --loop-id | No | Existing loop ID to continue |
|
||||
| --mode | No | `interactive` (default) / `auto` / `parallel` |
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
+------------------------------------------------------------+
|
||||
| Main Coordinator |
|
||||
| 职责: 状态管理 + worker 调度 + 结果汇聚 + 用户交互 |
|
||||
+------------------------------------------------------------+
|
||||
|
|
||||
+--------------------+--------------------+
|
||||
| | |
|
||||
v v v
|
||||
+----------------+ +----------------+ +----------------+
|
||||
| Worker-Develop | | Worker-Debug | | Worker-Validate|
|
||||
| 专注: 代码实现 | | 专注: 问题诊断 | | 专注: 测试验证 |
|
||||
+----------------+ +----------------+ +----------------+
|
||||
```
|
||||
|
||||
## Execution Modes
|
||||
|
||||
### Mode: Interactive (default)
|
||||
|
||||
协调器展示菜单,用户选择 action,spawn 对应 worker 执行。
|
||||
|
||||
```
|
||||
Coordinator -> Show menu -> User selects -> spawn worker -> wait -> Display result -> Loop
|
||||
```
|
||||
|
||||
### Mode: Auto
|
||||
|
||||
自动按预设顺序执行,worker 完成后自动切换到下一阶段。
|
||||
|
||||
```
|
||||
Init -> Develop -> [if issues] Debug -> Validate -> [if fail] Loop back -> Complete
|
||||
```
|
||||
|
||||
### Mode: Parallel
|
||||
|
||||
并行 spawn 多个 worker 分析不同维度,batch wait 汇聚结果。
|
||||
|
||||
```
|
||||
Coordinator -> spawn [develop, debug, validate] in parallel -> wait({ ids: all }) -> Merge -> Decide
|
||||
```
|
||||
|
||||
## Session Structure
|
||||
|
||||
```
|
||||
.workflow/.loop/
|
||||
+-- {loopId}.json # Master state
|
||||
+-- {loopId}.workers/ # Worker outputs
|
||||
| +-- develop.output.json
|
||||
| +-- debug.output.json
|
||||
| +-- validate.output.json
|
||||
+-- {loopId}.progress/ # Human-readable progress
|
||||
+-- develop.md
|
||||
+-- debug.md
|
||||
+-- validate.md
|
||||
+-- summary.md
|
||||
```
|
||||
|
||||
## Subagent API
|
||||
|
||||
| API | 作用 |
|
||||
|-----|------|
|
||||
| `spawn_agent({ message })` | 创建 agent,返回 `agent_id` |
|
||||
| `wait({ ids, timeout_ms })` | 等待结果(唯一取结果入口) |
|
||||
| `send_input({ id, message })` | 继续交互 |
|
||||
| `close_agent({ id })` | 关闭回收 |
|
||||
|
||||
## Implementation
|
||||
|
||||
### Coordinator Logic
|
||||
|
||||
```javascript
|
||||
// ==================== HYBRID ORCHESTRATOR ====================
|
||||
|
||||
// 1. Initialize
|
||||
const loopId = args['--loop-id'] || generateLoopId()
|
||||
const mode = args['--mode'] || 'interactive'
|
||||
let state = readOrCreateState(loopId, taskDescription)
|
||||
|
||||
// 2. Mode selection
|
||||
switch (mode) {
|
||||
case 'interactive':
|
||||
await runInteractiveMode(loopId, state)
|
||||
break
|
||||
|
||||
case 'auto':
|
||||
await runAutoMode(loopId, state)
|
||||
break
|
||||
|
||||
case 'parallel':
|
||||
await runParallelMode(loopId, state)
|
||||
break
|
||||
}
|
||||
```
|
||||
|
||||
### Interactive Mode (单 agent 交互或按需 spawn worker)
|
||||
|
||||
```javascript
|
||||
async function runInteractiveMode(loopId, state) {
|
||||
while (state.status === 'running') {
|
||||
// Show menu, get user choice
|
||||
const action = await showMenuAndGetChoice(state)
|
||||
|
||||
if (action === 'exit') break
|
||||
|
||||
// Spawn specialized worker for the action
|
||||
const workerId = spawn_agent({
|
||||
message: buildWorkerPrompt(action, loopId, state)
|
||||
})
|
||||
|
||||
// Wait for worker completion
|
||||
const result = wait({ ids: [workerId], timeout_ms: 600000 })
|
||||
const output = result.status[workerId].completed
|
||||
|
||||
// Update state and display result
|
||||
state = updateState(loopId, action, output)
|
||||
displayResult(output)
|
||||
|
||||
// Cleanup worker
|
||||
close_agent({ id: workerId })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Auto Mode (顺序执行 worker 链)
|
||||
|
||||
```javascript
|
||||
async function runAutoMode(loopId, state) {
|
||||
const actionSequence = ['init', 'develop', 'debug', 'validate', 'complete']
|
||||
let currentIndex = state.skill_state?.action_index || 0
|
||||
|
||||
while (currentIndex < actionSequence.length && state.status === 'running') {
|
||||
const action = actionSequence[currentIndex]
|
||||
|
||||
// Spawn worker
|
||||
const workerId = spawn_agent({
|
||||
message: buildWorkerPrompt(action, loopId, state)
|
||||
})
|
||||
|
||||
const result = wait({ ids: [workerId], timeout_ms: 600000 })
|
||||
const output = result.status[workerId].completed
|
||||
|
||||
// Parse worker result to determine next step
|
||||
const workerResult = parseWorkerResult(output)
|
||||
|
||||
// Update state
|
||||
state = updateState(loopId, action, output)
|
||||
|
||||
close_agent({ id: workerId })
|
||||
|
||||
// Determine next action
|
||||
if (workerResult.needs_loop_back) {
|
||||
// Loop back to develop or debug
|
||||
currentIndex = actionSequence.indexOf(workerResult.loop_back_to)
|
||||
} else if (workerResult.status === 'failed') {
|
||||
// Stop on failure
|
||||
break
|
||||
} else {
|
||||
currentIndex++
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Parallel Mode (批量 spawn + wait)
|
||||
|
||||
```javascript
|
||||
async function runParallelMode(loopId, state) {
|
||||
// Spawn multiple workers in parallel
|
||||
const workers = {
|
||||
develop: spawn_agent({ message: buildWorkerPrompt('develop', loopId, state) }),
|
||||
debug: spawn_agent({ message: buildWorkerPrompt('debug', loopId, state) }),
|
||||
validate: spawn_agent({ message: buildWorkerPrompt('validate', loopId, state) })
|
||||
}
|
||||
|
||||
// Batch wait for all workers
|
||||
const results = wait({
|
||||
ids: Object.values(workers),
|
||||
timeout_ms: 900000 // 15 minutes for all
|
||||
})
|
||||
|
||||
// Collect outputs
|
||||
const outputs = {}
|
||||
for (const [role, workerId] of Object.entries(workers)) {
|
||||
outputs[role] = results.status[workerId].completed
|
||||
close_agent({ id: workerId })
|
||||
}
|
||||
|
||||
// Merge and analyze results
|
||||
const mergedAnalysis = mergeWorkerOutputs(outputs)
|
||||
|
||||
// Update state with merged results
|
||||
updateState(loopId, 'parallel-analysis', mergedAnalysis)
|
||||
|
||||
// Coordinator decides next action based on merged results
|
||||
const decision = decideNextAction(mergedAnalysis)
|
||||
return decision
|
||||
}
|
||||
```
|
||||
|
||||
### Worker Prompt Builder
|
||||
|
||||
```javascript
|
||||
function buildWorkerPrompt(action, loopId, state) {
|
||||
const workerRoles = {
|
||||
develop: '~/.codex/agents/ccw-loop-b-develop.md',
|
||||
debug: '~/.codex/agents/ccw-loop-b-debug.md',
|
||||
validate: '~/.codex/agents/ccw-loop-b-validate.md',
|
||||
init: '~/.codex/agents/ccw-loop-b-init.md',
|
||||
complete: '~/.codex/agents/ccw-loop-b-complete.md'
|
||||
}
|
||||
|
||||
return `
|
||||
## TASK ASSIGNMENT
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
1. **Read role definition**: ${workerRoles[action]} (MUST read first)
|
||||
2. Read: .workflow/project-tech.json
|
||||
3. Read: .workflow/project-guidelines.json
|
||||
|
||||
---
|
||||
|
||||
## LOOP CONTEXT
|
||||
|
||||
- **Loop ID**: ${loopId}
|
||||
- **Action**: ${action}
|
||||
- **State File**: .workflow/.loop/${loopId}.json
|
||||
- **Output File**: .workflow/.loop/${loopId}.workers/${action}.output.json
|
||||
- **Progress File**: .workflow/.loop/${loopId}.progress/${action}.md
|
||||
|
||||
## CURRENT STATE
|
||||
|
||||
${JSON.stringify(state, null, 2)}
|
||||
|
||||
## TASK DESCRIPTION
|
||||
|
||||
${state.description}
|
||||
|
||||
## EXPECTED OUTPUT
|
||||
|
||||
\`\`\`
|
||||
WORKER_RESULT:
|
||||
- action: ${action}
|
||||
- status: success | failed | needs_input
|
||||
- summary: <brief summary>
|
||||
- files_changed: [list]
|
||||
- next_suggestion: <suggested next action>
|
||||
- loop_back_to: <action name if needs loop back>
|
||||
|
||||
DETAILED_OUTPUT:
|
||||
<structured output specific to action type>
|
||||
\`\`\`
|
||||
|
||||
Execute the ${action} action now.
|
||||
`
|
||||
}
|
||||
```
|
||||
|
||||
## Worker Roles
|
||||
|
||||
| Worker | Role File | 专注领域 |
|
||||
|--------|-----------|----------|
|
||||
| init | ccw-loop-b-init.md | 会话初始化、任务解析 |
|
||||
| develop | ccw-loop-b-develop.md | 代码实现、重构 |
|
||||
| debug | ccw-loop-b-debug.md | 问题诊断、假设验证 |
|
||||
| validate | ccw-loop-b-validate.md | 测试执行、覆盖率 |
|
||||
| complete | ccw-loop-b-complete.md | 总结收尾 |
|
||||
|
||||
## State Schema
|
||||
|
||||
See [phases/state-schema.md](phases/state-schema.md)
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Interactive mode (default)
|
||||
/ccw-loop-b TASK="Implement user authentication"
|
||||
|
||||
# Auto mode
|
||||
/ccw-loop-b --mode=auto TASK="Fix login bug"
|
||||
|
||||
# Parallel analysis mode
|
||||
/ccw-loop-b --mode=parallel TASK="Analyze and improve payment module"
|
||||
|
||||
# Resume existing loop
|
||||
/ccw-loop-b --loop-id=loop-b-20260122-abc123
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Situation | Action |
|
||||
|-----------|--------|
|
||||
| Worker timeout | send_input 请求收敛 |
|
||||
| Worker failed | Log error, 协调器决策是否重试 |
|
||||
| Batch wait partial timeout | 使用已完成结果继续 |
|
||||
| State corrupted | 从 progress 文件重建 |
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **协调器保持轻量**: 只做调度和状态管理,具体工作交给 worker
|
||||
2. **Worker 职责单一**: 每个 worker 专注一个领域
|
||||
3. **结果标准化**: Worker 输出遵循统一 WORKER_RESULT 格式
|
||||
4. **灵活模式切换**: 根据任务复杂度选择合适模式
|
||||
5. **及时清理**: Worker 完成后 close_agent 释放资源
|
||||
257
.codex/skills/ccw-loop-b/phases/orchestrator.md
Normal file
257
.codex/skills/ccw-loop-b/phases/orchestrator.md
Normal file
@@ -0,0 +1,257 @@
|
||||
# Orchestrator (Hybrid Pattern)
|
||||
|
||||
协调器负责状态管理、worker 调度、结果汇聚。
|
||||
|
||||
## Role
|
||||
|
||||
```
|
||||
Read state -> Select mode -> Spawn workers -> Wait results -> Merge -> Update state -> Loop/Exit
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
### Read State
|
||||
|
||||
```javascript
|
||||
function readState(loopId) {
|
||||
const stateFile = `.workflow/.loop/${loopId}.json`
|
||||
return fs.existsSync(stateFile)
|
||||
? JSON.parse(Read(stateFile))
|
||||
: null
|
||||
}
|
||||
```
|
||||
|
||||
### Create State
|
||||
|
||||
```javascript
|
||||
function createState(loopId, taskDescription, mode) {
|
||||
const now = new Date().toISOString()
|
||||
|
||||
return {
|
||||
loop_id: loopId,
|
||||
title: taskDescription.substring(0, 100),
|
||||
description: taskDescription,
|
||||
mode: mode,
|
||||
status: 'running',
|
||||
current_iteration: 0,
|
||||
max_iterations: 10,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
skill_state: {
|
||||
phase: 'init',
|
||||
action_index: 0,
|
||||
workers_completed: [],
|
||||
parallel_results: null
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Mode Handlers
|
||||
|
||||
### Interactive Mode
|
||||
|
||||
```javascript
|
||||
async function runInteractiveMode(loopId, state) {
|
||||
while (state.status === 'running') {
|
||||
// 1. Show menu
|
||||
const action = await showMenu(state)
|
||||
if (action === 'exit') break
|
||||
|
||||
// 2. Spawn worker
|
||||
const worker = spawn_agent({
|
||||
message: buildWorkerPrompt(action, loopId, state)
|
||||
})
|
||||
|
||||
// 3. Wait for result
|
||||
const result = wait({ ids: [worker], timeout_ms: 600000 })
|
||||
|
||||
// 4. Handle timeout
|
||||
if (result.timed_out) {
|
||||
send_input({ id: worker, message: 'Please converge and output WORKER_RESULT' })
|
||||
const retryResult = wait({ ids: [worker], timeout_ms: 300000 })
|
||||
if (retryResult.timed_out) {
|
||||
console.log('Worker timeout, skipping')
|
||||
close_agent({ id: worker })
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Process output
|
||||
const output = result.status[worker].completed
|
||||
state = processWorkerOutput(loopId, action, output, state)
|
||||
|
||||
// 6. Cleanup
|
||||
close_agent({ id: worker })
|
||||
|
||||
// 7. Display result
|
||||
displayResult(output)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Auto Mode
|
||||
|
||||
```javascript
|
||||
async function runAutoMode(loopId, state) {
|
||||
const sequence = ['init', 'develop', 'debug', 'validate', 'complete']
|
||||
let idx = state.skill_state?.action_index || 0
|
||||
|
||||
while (idx < sequence.length && state.status === 'running') {
|
||||
const action = sequence[idx]
|
||||
|
||||
// Spawn and wait
|
||||
const worker = spawn_agent({ message: buildWorkerPrompt(action, loopId, state) })
|
||||
const result = wait({ ids: [worker], timeout_ms: 600000 })
|
||||
const output = result.status[worker].completed
|
||||
close_agent({ id: worker })
|
||||
|
||||
// Parse result
|
||||
const workerResult = parseWorkerResult(output)
|
||||
state = processWorkerOutput(loopId, action, output, state)
|
||||
|
||||
// Determine next
|
||||
if (workerResult.loop_back_to) {
|
||||
idx = sequence.indexOf(workerResult.loop_back_to)
|
||||
} else if (workerResult.status === 'failed') {
|
||||
break
|
||||
} else {
|
||||
idx++
|
||||
}
|
||||
|
||||
// Update action index
|
||||
state.skill_state.action_index = idx
|
||||
saveState(loopId, state)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Parallel Mode
|
||||
|
||||
```javascript
|
||||
async function runParallelMode(loopId, state) {
|
||||
// Spawn all workers
|
||||
const workers = {
|
||||
develop: spawn_agent({ message: buildWorkerPrompt('develop', loopId, state) }),
|
||||
debug: spawn_agent({ message: buildWorkerPrompt('debug', loopId, state) }),
|
||||
validate: spawn_agent({ message: buildWorkerPrompt('validate', loopId, state) })
|
||||
}
|
||||
|
||||
// Batch wait
|
||||
const results = wait({
|
||||
ids: Object.values(workers),
|
||||
timeout_ms: 900000
|
||||
})
|
||||
|
||||
// Collect outputs
|
||||
const outputs = {}
|
||||
for (const [role, id] of Object.entries(workers)) {
|
||||
if (results.status[id].completed) {
|
||||
outputs[role] = results.status[id].completed
|
||||
}
|
||||
close_agent({ id })
|
||||
}
|
||||
|
||||
// Merge analysis
|
||||
state.skill_state.parallel_results = outputs
|
||||
saveState(loopId, state)
|
||||
|
||||
// Coordinator analyzes merged results
|
||||
return analyzeAndDecide(outputs)
|
||||
}
|
||||
```
|
||||
|
||||
## Worker Prompt Template
|
||||
|
||||
```javascript
|
||||
function buildWorkerPrompt(action, loopId, state) {
|
||||
const roleFiles = {
|
||||
init: '~/.codex/agents/ccw-loop-b-init.md',
|
||||
develop: '~/.codex/agents/ccw-loop-b-develop.md',
|
||||
debug: '~/.codex/agents/ccw-loop-b-debug.md',
|
||||
validate: '~/.codex/agents/ccw-loop-b-validate.md',
|
||||
complete: '~/.codex/agents/ccw-loop-b-complete.md'
|
||||
}
|
||||
|
||||
return `
|
||||
## TASK ASSIGNMENT
|
||||
|
||||
### MANDATORY FIRST STEPS
|
||||
1. **Read role definition**: ${roleFiles[action]}
|
||||
2. Read: .workflow/project-tech.json
|
||||
3. Read: .workflow/project-guidelines.json
|
||||
|
||||
---
|
||||
|
||||
## CONTEXT
|
||||
- Loop ID: ${loopId}
|
||||
- Action: ${action}
|
||||
- State: ${JSON.stringify(state, null, 2)}
|
||||
|
||||
## TASK
|
||||
${state.description}
|
||||
|
||||
## OUTPUT FORMAT
|
||||
\`\`\`
|
||||
WORKER_RESULT:
|
||||
- action: ${action}
|
||||
- status: success | failed | needs_input
|
||||
- summary: <brief>
|
||||
- files_changed: []
|
||||
- next_suggestion: <action>
|
||||
- loop_back_to: <action or null>
|
||||
|
||||
DETAILED_OUTPUT:
|
||||
<action-specific output>
|
||||
\`\`\`
|
||||
`
|
||||
}
|
||||
```
|
||||
|
||||
## Result Processing
|
||||
|
||||
```javascript
|
||||
function parseWorkerResult(output) {
|
||||
const result = {
|
||||
action: 'unknown',
|
||||
status: 'unknown',
|
||||
summary: '',
|
||||
files_changed: [],
|
||||
next_suggestion: null,
|
||||
loop_back_to: null
|
||||
}
|
||||
|
||||
const match = output.match(/WORKER_RESULT:\s*([\s\S]*?)(?:DETAILED_OUTPUT:|$)/)
|
||||
if (match) {
|
||||
const lines = match[1].split('\n')
|
||||
for (const line of lines) {
|
||||
const m = line.match(/^-\s*(\w+):\s*(.+)$/)
|
||||
if (m) {
|
||||
const [, key, value] = m
|
||||
if (key === 'files_changed') {
|
||||
try { result.files_changed = JSON.parse(value) } catch {}
|
||||
} else {
|
||||
result[key] = value.trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
## Termination Conditions
|
||||
|
||||
1. User exits (interactive)
|
||||
2. Sequence complete (auto)
|
||||
3. Worker failed with no recovery
|
||||
4. Max iterations reached
|
||||
5. API paused/stopped
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Worker 生命周期**: spawn → wait → close,不保留 worker
|
||||
2. **结果持久化**: Worker 输出写入 `.workflow/.loop/{loopId}.workers/`
|
||||
3. **状态同步**: 每次 worker 完成后更新 state
|
||||
4. **超时处理**: send_input 请求收敛,再超时则跳过
|
||||
181
.codex/skills/ccw-loop-b/phases/state-schema.md
Normal file
181
.codex/skills/ccw-loop-b/phases/state-schema.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# State Schema (CCW Loop-B)
|
||||
|
||||
## Master State Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"loop_id": "loop-b-20260122-abc123",
|
||||
"title": "Implement user authentication",
|
||||
"description": "Full task description here",
|
||||
"mode": "interactive | auto | parallel",
|
||||
"status": "running | paused | completed | failed",
|
||||
"current_iteration": 3,
|
||||
"max_iterations": 10,
|
||||
"created_at": "2026-01-22T10:00:00.000Z",
|
||||
"updated_at": "2026-01-22T10:30:00.000Z",
|
||||
|
||||
"skill_state": {
|
||||
"phase": "develop | debug | validate | complete",
|
||||
"action_index": 2,
|
||||
"workers_completed": ["init", "develop"],
|
||||
"parallel_results": null,
|
||||
"pending_tasks": [],
|
||||
"completed_tasks": [],
|
||||
"findings": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Field Descriptions
|
||||
|
||||
### Core Fields (API Compatible)
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `loop_id` | string | Unique identifier |
|
||||
| `title` | string | Short title (max 100 chars) |
|
||||
| `description` | string | Full task description |
|
||||
| `mode` | enum | Execution mode |
|
||||
| `status` | enum | Current status |
|
||||
| `current_iteration` | number | Iteration counter |
|
||||
| `max_iterations` | number | Safety limit |
|
||||
| `created_at` | ISO string | Creation timestamp |
|
||||
| `updated_at` | ISO string | Last update timestamp |
|
||||
|
||||
### Skill State Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `phase` | enum | Current execution phase |
|
||||
| `action_index` | number | Position in action sequence (auto mode) |
|
||||
| `workers_completed` | array | List of completed worker actions |
|
||||
| `parallel_results` | object | Merged results from parallel mode |
|
||||
| `pending_tasks` | array | Tasks waiting to be executed |
|
||||
| `completed_tasks` | array | Tasks already done |
|
||||
| `findings` | array | Discoveries during execution |
|
||||
|
||||
## Worker Output Structure
|
||||
|
||||
Each worker writes to `.workflow/.loop/{loopId}.workers/{action}.output.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "develop",
|
||||
"status": "success",
|
||||
"summary": "Implemented 3 functions",
|
||||
"files_changed": ["src/auth.ts", "src/utils.ts"],
|
||||
"next_suggestion": "validate",
|
||||
"loop_back_to": null,
|
||||
"timestamp": "2026-01-22T10:15:00.000Z",
|
||||
"detailed_output": {
|
||||
"tasks_completed": [
|
||||
{ "id": "T1", "description": "Create auth module" }
|
||||
],
|
||||
"metrics": {
|
||||
"lines_added": 150,
|
||||
"lines_removed": 20
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Progress File Structure
|
||||
|
||||
Human-readable progress in `.workflow/.loop/{loopId}.progress/{action}.md`:
|
||||
|
||||
```markdown
|
||||
# Develop Progress
|
||||
|
||||
## Session: loop-b-20260122-abc123
|
||||
|
||||
### Iteration 1 (2026-01-22 10:15)
|
||||
|
||||
**Task**: Implement auth module
|
||||
|
||||
**Changes**:
|
||||
- Created `src/auth.ts` with login/logout functions
|
||||
- Added JWT token handling in `src/utils.ts`
|
||||
|
||||
**Status**: Success
|
||||
|
||||
---
|
||||
|
||||
### Iteration 2 (2026-01-22 10:30)
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
## Status Transitions
|
||||
|
||||
```
|
||||
+--------+
|
||||
| init |
|
||||
+--------+
|
||||
|
|
||||
v
|
||||
+------> +---------+
|
||||
| | develop |
|
||||
| +---------+
|
||||
| |
|
||||
| +--------+--------+
|
||||
| | |
|
||||
| v v
|
||||
| +-------+ +---------+
|
||||
| | debug |<------| validate|
|
||||
| +-------+ +---------+
|
||||
| | |
|
||||
| +--------+--------+
|
||||
| |
|
||||
| v
|
||||
| [needs fix?]
|
||||
| yes | | no
|
||||
| v v
|
||||
+------------+ +----------+
|
||||
| complete |
|
||||
+----------+
|
||||
```
|
||||
|
||||
## Parallel Results Schema
|
||||
|
||||
When `mode === 'parallel'`:
|
||||
|
||||
```json
|
||||
{
|
||||
"parallel_results": {
|
||||
"develop": {
|
||||
"status": "success",
|
||||
"summary": "...",
|
||||
"suggestions": []
|
||||
},
|
||||
"debug": {
|
||||
"status": "success",
|
||||
"issues_found": [],
|
||||
"suggestions": []
|
||||
},
|
||||
"validate": {
|
||||
"status": "success",
|
||||
"test_results": {},
|
||||
"coverage": {}
|
||||
},
|
||||
"merged_at": "2026-01-22T10:45:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
.workflow/.loop/
|
||||
+-- loop-b-20260122-abc123.json # Master state
|
||||
+-- loop-b-20260122-abc123.workers/
|
||||
| +-- init.output.json
|
||||
| +-- develop.output.json
|
||||
| +-- debug.output.json
|
||||
| +-- validate.output.json
|
||||
| +-- complete.output.json
|
||||
+-- loop-b-20260122-abc123.progress/
|
||||
+-- develop.md
|
||||
+-- debug.md
|
||||
+-- validate.md
|
||||
+-- summary.md
|
||||
```
|
||||
383
.codex/skills/ccw-loop-b/specs/action-catalog.md
Normal file
383
.codex/skills/ccw-loop-b/specs/action-catalog.md
Normal file
@@ -0,0 +1,383 @@
|
||||
# Action Catalog (CCW Loop-B)
|
||||
|
||||
Complete reference of worker actions and their capabilities.
|
||||
|
||||
## Action Matrix
|
||||
|
||||
| Action | Worker Agent | Purpose | Input Requirements | Output |
|
||||
|--------|--------------|---------|-------------------|--------|
|
||||
| init | ccw-loop-b-init.md | Session initialization | Task description | Task breakdown + execution plan |
|
||||
| develop | ccw-loop-b-develop.md | Code implementation | Task list | Code changes + progress update |
|
||||
| debug | ccw-loop-b-debug.md | Problem diagnosis | Issue description | Root cause analysis + fix suggestions |
|
||||
| validate | ccw-loop-b-validate.md | Testing and verification | Files to test | Test results + coverage report |
|
||||
| complete | ccw-loop-b-complete.md | Session finalization | All worker outputs | Summary + commit message |
|
||||
|
||||
## Detailed Action Specifications
|
||||
|
||||
### INIT
|
||||
|
||||
**Purpose**: Parse requirements, create execution plan
|
||||
|
||||
**Preconditions**:
|
||||
- `status === 'running'`
|
||||
- `skill_state === null` (first time)
|
||||
|
||||
**Input**:
|
||||
```
|
||||
- Task description (text)
|
||||
- Project context files
|
||||
```
|
||||
|
||||
**Execution**:
|
||||
1. Read `.workflow/project-tech.json`
|
||||
2. Read `.workflow/project-guidelines.json`
|
||||
3. Parse task into phases
|
||||
4. Create task breakdown
|
||||
5. Generate execution plan
|
||||
|
||||
**Output**:
|
||||
```
|
||||
WORKER_RESULT:
|
||||
- action: init
|
||||
- status: success
|
||||
- summary: "Initialized with 5 tasks"
|
||||
- next_suggestion: develop
|
||||
|
||||
TASK_BREAKDOWN:
|
||||
- T1: Create auth module
|
||||
- T2: Implement JWT utils
|
||||
- T3: Write tests
|
||||
- T4: Validate implementation
|
||||
- T5: Documentation
|
||||
|
||||
EXECUTION_PLAN:
|
||||
1. Develop (T1-T2)
|
||||
2. Validate (T3-T4)
|
||||
3. Complete (T5)
|
||||
```
|
||||
|
||||
**Effects**:
|
||||
- `skill_state.pending_tasks` populated
|
||||
- Progress structure created
|
||||
- Ready for develop phase
|
||||
|
||||
---
|
||||
|
||||
### DEVELOP
|
||||
|
||||
**Purpose**: Implement code, create/modify files
|
||||
|
||||
**Preconditions**:
|
||||
- `skill_state.pending_tasks.length > 0`
|
||||
- `status === 'running'`
|
||||
|
||||
**Input**:
|
||||
```
|
||||
- Task list from state
|
||||
- Project conventions
|
||||
- Existing code patterns
|
||||
```
|
||||
|
||||
**Execution**:
|
||||
1. Load pending tasks
|
||||
2. Find existing patterns
|
||||
3. Implement tasks one by one
|
||||
4. Update progress file
|
||||
5. Mark tasks completed
|
||||
|
||||
**Output**:
|
||||
```
|
||||
WORKER_RESULT:
|
||||
- action: develop
|
||||
- status: success
|
||||
- summary: "Implemented 3 tasks"
|
||||
- files_changed: ["src/auth.ts", "src/utils.ts"]
|
||||
- next_suggestion: validate
|
||||
|
||||
DETAILED_OUTPUT:
|
||||
tasks_completed: [T1, T2]
|
||||
metrics:
|
||||
lines_added: 180
|
||||
lines_removed: 15
|
||||
```
|
||||
|
||||
**Effects**:
|
||||
- Files created/modified
|
||||
- `skill_state.completed_tasks` updated
|
||||
- Progress documented
|
||||
|
||||
**Failure Modes**:
|
||||
- Pattern unclear → suggest debug
|
||||
- Task blocked → mark blocked, continue
|
||||
- Partial completion → set `loop_back_to: "develop"`
|
||||
|
||||
---
|
||||
|
||||
### DEBUG
|
||||
|
||||
**Purpose**: Diagnose issues, root cause analysis
|
||||
|
||||
**Preconditions**:
|
||||
- Issue exists (test failure, bug report, etc.)
|
||||
- `status === 'running'`
|
||||
|
||||
**Input**:
|
||||
```
|
||||
- Issue description
|
||||
- Error messages
|
||||
- Stack traces
|
||||
- Reproduction steps
|
||||
```
|
||||
|
||||
**Execution**:
|
||||
1. Understand problem symptoms
|
||||
2. Gather evidence from code
|
||||
3. Form hypothesis
|
||||
4. Test hypothesis
|
||||
5. Document root cause
|
||||
6. Suggest fixes
|
||||
|
||||
**Output**:
|
||||
```
|
||||
WORKER_RESULT:
|
||||
- action: debug
|
||||
- status: success
|
||||
- summary: "Root cause: memory leak in event listeners"
|
||||
- next_suggestion: develop (apply fixes)
|
||||
|
||||
ROOT_CAUSE_ANALYSIS:
|
||||
hypothesis: "Listener accumulation"
|
||||
confidence: high
|
||||
evidence: [...]
|
||||
mechanism: "Detailed explanation"
|
||||
|
||||
FIX_RECOMMENDATIONS:
|
||||
1. Add removeAllListeners() on disconnect
|
||||
2. Verification: Monitor memory usage
|
||||
```
|
||||
|
||||
**Effects**:
|
||||
- `skill_state.findings` updated
|
||||
- Fix recommendations documented
|
||||
- Ready for develop to apply fixes
|
||||
|
||||
**Failure Modes**:
|
||||
- Insufficient info → request more data
|
||||
- Multiple hypotheses → rank by likelihood
|
||||
- Inconclusive → suggest investigation areas
|
||||
|
||||
---
|
||||
|
||||
### VALIDATE
|
||||
|
||||
**Purpose**: Run tests, check coverage, quality gates
|
||||
|
||||
**Preconditions**:
|
||||
- Code exists to validate
|
||||
- `status === 'running'`
|
||||
|
||||
**Input**:
|
||||
```
|
||||
- Files to test
|
||||
- Test configuration
|
||||
- Coverage requirements
|
||||
```
|
||||
|
||||
**Execution**:
|
||||
1. Identify test framework
|
||||
2. Run unit tests
|
||||
3. Run integration tests
|
||||
4. Measure coverage
|
||||
5. Check quality (lint, types, security)
|
||||
6. Generate report
|
||||
|
||||
**Output**:
|
||||
```
|
||||
WORKER_RESULT:
|
||||
- action: validate
|
||||
- status: success
|
||||
- summary: "113 tests pass, coverage 95%"
|
||||
- next_suggestion: complete (all pass) | develop (fix failures)
|
||||
|
||||
TEST_RESULTS:
|
||||
unit_tests: { passed: 98, failed: 0 }
|
||||
integration_tests: { passed: 15, failed: 0 }
|
||||
coverage: "95%"
|
||||
|
||||
QUALITY_CHECKS:
|
||||
lint: ✓ Pass
|
||||
types: ✓ Pass
|
||||
security: ✓ Pass
|
||||
```
|
||||
|
||||
**Effects**:
|
||||
- Test results documented
|
||||
- Coverage measured
|
||||
- Quality gates verified
|
||||
|
||||
**Failure Modes**:
|
||||
- Tests fail → document failures, suggest fixes
|
||||
- Coverage low → identify gaps
|
||||
- Quality issues → flag problems
|
||||
|
||||
---
|
||||
|
||||
### COMPLETE
|
||||
|
||||
**Purpose**: Finalize session, generate summary, commit
|
||||
|
||||
**Preconditions**:
|
||||
- All tasks completed
|
||||
- Tests passing
|
||||
- `status === 'running'`
|
||||
|
||||
**Input**:
|
||||
```
|
||||
- All worker outputs
|
||||
- Progress files
|
||||
- Current state
|
||||
```
|
||||
|
||||
**Execution**:
|
||||
1. Read all worker outputs
|
||||
2. Consolidate achievements
|
||||
3. Verify completeness
|
||||
4. Generate summary
|
||||
5. Prepare commit message
|
||||
6. Cleanup and archive
|
||||
|
||||
**Output**:
|
||||
```
|
||||
WORKER_RESULT:
|
||||
- action: complete
|
||||
- status: success
|
||||
- summary: "Session completed successfully"
|
||||
- next_suggestion: null
|
||||
|
||||
SESSION_SUMMARY:
|
||||
achievements: [...]
|
||||
files_changed: [...]
|
||||
test_results: { ... }
|
||||
quality_checks: { ... }
|
||||
|
||||
COMMIT_SUGGESTION:
|
||||
message: "feat: ..."
|
||||
files: [...]
|
||||
ready_for_pr: true
|
||||
```
|
||||
|
||||
**Effects**:
|
||||
- `status` → 'completed'
|
||||
- Summary file created
|
||||
- Progress archived
|
||||
- Commit message ready
|
||||
|
||||
**Failure Modes**:
|
||||
- Pending tasks remain → mark partial
|
||||
- Quality gates fail → list failures
|
||||
|
||||
---
|
||||
|
||||
## Action Flow Diagrams
|
||||
|
||||
### Interactive Mode Flow
|
||||
|
||||
```
|
||||
+------+
|
||||
| INIT |
|
||||
+------+
|
||||
|
|
||||
v
|
||||
+------+ user selects
|
||||
| MENU |-------------+
|
||||
+------+ |
|
||||
^ v
|
||||
| +--------------+
|
||||
| | spawn worker |
|
||||
| +--------------+
|
||||
| |
|
||||
| v
|
||||
| +------+-------+
|
||||
+---------| wait result |
|
||||
+------+-------+
|
||||
|
|
||||
v
|
||||
+------+-------+
|
||||
| update state |
|
||||
+--------------+
|
||||
|
|
||||
v
|
||||
[completed?] --no--> [back to MENU]
|
||||
|
|
||||
yes
|
||||
v
|
||||
+----------+
|
||||
| COMPLETE |
|
||||
+----------+
|
||||
```
|
||||
|
||||
### Auto Mode Flow
|
||||
|
||||
```
|
||||
+------+ +---------+ +-------+ +----------+ +----------+
|
||||
| INIT | ---> | DEVELOP | ---> | DEBUG | ---> | VALIDATE | ---> | COMPLETE |
|
||||
+------+ +---------+ +-------+ +----------+ +----------+
|
||||
^ | |
|
||||
| +--- [issues] |
|
||||
+--------------------------------+
|
||||
[tests fail]
|
||||
```
|
||||
|
||||
### Parallel Mode Flow
|
||||
|
||||
```
|
||||
+------+
|
||||
| INIT |
|
||||
+------+
|
||||
|
|
||||
v
|
||||
+---------------------+
|
||||
| spawn all workers |
|
||||
| [develop, debug, |
|
||||
| validate] |
|
||||
+---------------------+
|
||||
|
|
||||
v
|
||||
+---------------------+
|
||||
| wait({ ids: all }) |
|
||||
+---------------------+
|
||||
|
|
||||
v
|
||||
+---------------------+
|
||||
| merge results |
|
||||
+---------------------+
|
||||
|
|
||||
v
|
||||
+---------------------+
|
||||
| coordinator decides |
|
||||
+---------------------+
|
||||
|
|
||||
v
|
||||
+----------+
|
||||
| COMPLETE |
|
||||
+----------+
|
||||
```
|
||||
|
||||
## Worker Coordination
|
||||
|
||||
| Scenario | Worker Sequence | Mode |
|
||||
|----------|-----------------|------|
|
||||
| Simple task | init → develop → validate → complete | Auto |
|
||||
| Complex task | init → develop → debug → develop → validate → complete | Auto |
|
||||
| Bug fix | init → debug → develop → validate → complete | Auto |
|
||||
| Analysis | init → [develop \|\| debug \|\| validate] → complete | Parallel |
|
||||
| Interactive | init → menu → user selects → worker → menu → ... | Interactive |
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Init always first**: Parse requirements before execution
|
||||
2. **Validate often**: After each develop phase
|
||||
3. **Debug when needed**: Don't skip diagnosis
|
||||
4. **Complete always last**: Ensure proper cleanup
|
||||
5. **Use parallel wisely**: For independent analysis tasks
|
||||
6. **Follow sequence**: In auto mode, respect dependencies
|
||||
171
.codex/skills/ccw-loop/README.md
Normal file
171
.codex/skills/ccw-loop/README.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# CCW Loop Skill (Codex Version)
|
||||
|
||||
Stateless iterative development loop workflow using Codex subagent pattern.
|
||||
|
||||
## Overview
|
||||
|
||||
CCW Loop is an autonomous development workflow that supports:
|
||||
- **Develop**: Task decomposition -> Code implementation -> Progress tracking
|
||||
- **Debug**: Hypothesis generation -> Evidence collection -> Root cause analysis
|
||||
- **Validate**: Test execution -> Coverage check -> Quality assessment
|
||||
|
||||
## Subagent 机制
|
||||
|
||||
核心 API: `spawn_agent` / `wait` / `send_input` / `close_agent`
|
||||
|
||||
可用模式: 单 agent 深度交互 / 多 agent 并行 / 混合模式
|
||||
|
||||
## Installation
|
||||
|
||||
Files are in `.codex/skills/ccw-loop/`:
|
||||
|
||||
```
|
||||
.codex/skills/ccw-loop/
|
||||
+-- SKILL.md # Main skill definition
|
||||
+-- README.md # This file
|
||||
+-- phases/
|
||||
| +-- orchestrator.md # Orchestration logic
|
||||
| +-- state-schema.md # State structure
|
||||
| +-- actions/
|
||||
| +-- action-init.md # Initialize session
|
||||
| +-- action-develop.md # Development task
|
||||
| +-- action-debug.md # Hypothesis debugging
|
||||
| +-- action-validate.md # Test validation
|
||||
| +-- action-complete.md # Complete loop
|
||||
| +-- action-menu.md # Interactive menu
|
||||
+-- specs/
|
||||
| +-- action-catalog.md # Action catalog
|
||||
+-- templates/
|
||||
+-- (templates)
|
||||
|
||||
.codex/agents/
|
||||
+-- ccw-loop-executor.md # Executor agent role
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Start New Loop
|
||||
|
||||
```bash
|
||||
# Direct call with task description
|
||||
/ccw-loop TASK="Implement user authentication"
|
||||
|
||||
# Auto-cycle mode
|
||||
/ccw-loop --auto TASK="Fix login bug and add tests"
|
||||
```
|
||||
|
||||
### Continue Existing Loop
|
||||
|
||||
```bash
|
||||
# Resume from loop ID
|
||||
/ccw-loop --loop-id=loop-v2-20260122-abc123
|
||||
|
||||
# API triggered (from Dashboard)
|
||||
/ccw-loop --loop-id=loop-v2-20260122-abc123 --auto
|
||||
```
|
||||
|
||||
## Execution Flow
|
||||
|
||||
```
|
||||
1. Parse arguments (task or --loop-id)
|
||||
2. Create/read state from .workflow/.loop/{loopId}.json
|
||||
3. spawn_agent with ccw-loop-executor role
|
||||
4. Main loop:
|
||||
a. wait() for agent output
|
||||
b. Parse ACTION_RESULT
|
||||
c. Handle outcome:
|
||||
- COMPLETED/PAUSED/STOPPED: exit loop
|
||||
- WAITING_INPUT: collect user input, send_input
|
||||
- Next action: send_input to continue
|
||||
d. Update state file
|
||||
5. close_agent when done
|
||||
```
|
||||
|
||||
## Session Files
|
||||
|
||||
```
|
||||
.workflow/.loop/
|
||||
+-- {loopId}.json # Master state (API + Skill)
|
||||
+-- {loopId}.progress/
|
||||
+-- develop.md # Development timeline
|
||||
+-- debug.md # Understanding evolution
|
||||
+-- validate.md # Validation report
|
||||
+-- changes.log # Code changes (NDJSON)
|
||||
+-- debug.log # Debug log (NDJSON)
|
||||
+-- summary.md # Completion summary
|
||||
```
|
||||
|
||||
## Codex Pattern Highlights
|
||||
|
||||
### Single Agent Deep Interaction
|
||||
|
||||
Instead of creating multiple agents, use `send_input` for multi-phase:
|
||||
|
||||
```javascript
|
||||
const agent = spawn_agent({ message: role + task })
|
||||
|
||||
// Phase 1: INIT
|
||||
const initResult = wait({ ids: [agent] })
|
||||
|
||||
// Phase 2: DEVELOP (via send_input, same agent)
|
||||
send_input({ id: agent, message: 'Execute DEVELOP' })
|
||||
const devResult = wait({ ids: [agent] })
|
||||
|
||||
// Phase 3: VALIDATE (via send_input, same agent)
|
||||
send_input({ id: agent, message: 'Execute VALIDATE' })
|
||||
const valResult = wait({ ids: [agent] })
|
||||
|
||||
// Only close when all done
|
||||
close_agent({ id: agent })
|
||||
```
|
||||
|
||||
### Role Path Passing
|
||||
|
||||
Agent reads role file itself (no content embedding):
|
||||
|
||||
```javascript
|
||||
spawn_agent({
|
||||
message: `
|
||||
### MANDATORY FIRST STEPS
|
||||
1. **Read role definition**: ~/.codex/agents/ccw-loop-executor.md
|
||||
2. Read: .workflow/project-tech.json
|
||||
...
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
### Explicit Lifecycle Management
|
||||
|
||||
- Always use `wait({ ids })` to get results
|
||||
- Never assume `close_agent` returns results
|
||||
- Only `close_agent` when confirming no more interaction needed
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Situation | Action |
|
||||
|-----------|--------|
|
||||
| Agent timeout | `send_input` requesting convergence |
|
||||
| Session not found | Create new session |
|
||||
| State corrupted | Rebuild from progress files |
|
||||
| Tests fail | Loop back to DEBUG |
|
||||
| >10 iterations | Warn and suggest break |
|
||||
|
||||
## Integration
|
||||
|
||||
### Dashboard Integration
|
||||
|
||||
Works with CCW Dashboard Loop Monitor:
|
||||
- Dashboard creates loop via API
|
||||
- API triggers this skill with `--loop-id`
|
||||
- Skill reads/writes `.workflow/.loop/{loopId}.json`
|
||||
- Dashboard polls state for real-time updates
|
||||
|
||||
### Control Signals
|
||||
|
||||
- `paused`: Skill exits gracefully, waits for resume
|
||||
- `failed`: Skill terminates
|
||||
- `running`: Skill continues execution
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
350
.codex/skills/ccw-loop/SKILL.md
Normal file
350
.codex/skills/ccw-loop/SKILL.md
Normal file
@@ -0,0 +1,350 @@
|
||||
---
|
||||
name: CCW Loop
|
||||
description: Stateless iterative development loop workflow with documented progress. Supports develop, debug, and validate phases with file-based state tracking. Triggers on "ccw-loop", "dev loop", "development loop", "开发循环", "迭代开发".
|
||||
argument-hint: TASK="<task description>" [--loop-id=<id>] [--auto]
|
||||
---
|
||||
|
||||
# CCW Loop - Codex Stateless Iterative Development Workflow
|
||||
|
||||
Stateless iterative development loop using Codex subagent pattern. Supports develop, debug, and validate phases with file-based state tracking.
|
||||
|
||||
## Arguments
|
||||
|
||||
| Arg | Required | Description |
|
||||
|-----|----------|-------------|
|
||||
| TASK | No | Task description (for new loop, mutually exclusive with --loop-id) |
|
||||
| --loop-id | No | Existing loop ID to continue (from API or previous session) |
|
||||
| --auto | No | Auto-cycle mode (develop -> debug -> validate -> complete) |
|
||||
|
||||
## Unified Architecture (Codex Subagent Pattern)
|
||||
|
||||
```
|
||||
+-------------------------------------------------------------+
|
||||
| Dashboard (UI) |
|
||||
| [Create] [Start] [Pause] [Resume] [Stop] [View Progress] |
|
||||
+-------------------------------------------------------------+
|
||||
|
|
||||
v
|
||||
+-------------------------------------------------------------+
|
||||
| loop-v2-routes.ts (Control Plane) |
|
||||
| |
|
||||
| State: .workflow/.loop/{loopId}.json (MASTER) |
|
||||
| Tasks: .workflow/.loop/{loopId}.tasks.jsonl |
|
||||
| |
|
||||
| /start -> Trigger ccw-loop skill with --loop-id |
|
||||
| /pause -> Set status='paused' (skill checks before action) |
|
||||
| /stop -> Set status='failed' (skill terminates) |
|
||||
| /resume -> Set status='running' (skill continues) |
|
||||
+-------------------------------------------------------------+
|
||||
|
|
||||
v
|
||||
+-------------------------------------------------------------+
|
||||
| ccw-loop Skill (Execution Plane) |
|
||||
| |
|
||||
| Codex Pattern: spawn_agent -> wait -> send_input -> close |
|
||||
| |
|
||||
| Reads/Writes: .workflow/.loop/{loopId}.json (unified state) |
|
||||
| Writes: .workflow/.loop/{loopId}.progress/* (progress files) |
|
||||
| |
|
||||
| BEFORE each action: |
|
||||
| -> Check status: paused/stopped -> exit gracefully |
|
||||
| -> running -> continue with action |
|
||||
| |
|
||||
| Actions: init -> develop -> debug -> validate -> complete |
|
||||
+-------------------------------------------------------------+
|
||||
```
|
||||
|
||||
## Key Design Principles (Codex Adaptation)
|
||||
|
||||
1. **Unified State**: API and Skill share `.workflow/.loop/{loopId}.json` state file
|
||||
2. **Control Signals**: Skill checks status field before each action (paused/stopped)
|
||||
3. **File-Driven**: All progress documented in `.workflow/.loop/{loopId}.progress/`
|
||||
4. **Resumable**: Continue any loop with `--loop-id`
|
||||
5. **Dual Trigger**: Supports API trigger (`--loop-id`) and direct call (task description)
|
||||
6. **Single Agent Deep Interaction**: Use send_input for multi-phase execution instead of multiple agents
|
||||
|
||||
## Subagent 机制
|
||||
|
||||
### 核心 API
|
||||
|
||||
| API | 作用 |
|
||||
|-----|------|
|
||||
| `spawn_agent({ message })` | 创建 subagent,返回 `agent_id` |
|
||||
| `wait({ ids, timeout_ms })` | 等待结果(唯一取结果入口) |
|
||||
| `send_input({ id, message })` | 继续交互/追问 |
|
||||
| `close_agent({ id })` | 关闭回收(不可逆) |
|
||||
|
||||
### 可用模式
|
||||
|
||||
- **单 Agent 深度交互**: 一个 agent 多阶段,`send_input` 继续
|
||||
- **多 Agent 并行**: 主协调器 + 多 worker,`wait({ ids: [...] })` 批量等待
|
||||
- **混合模式**: 按需组合
|
||||
|
||||
## Execution Modes
|
||||
|
||||
### Mode 1: Interactive
|
||||
|
||||
User manually selects each action, suitable for complex tasks.
|
||||
|
||||
```
|
||||
User -> Select action -> Execute -> View results -> Select next action
|
||||
```
|
||||
|
||||
### Mode 2: Auto-Loop
|
||||
|
||||
Automatic execution in preset order, suitable for standard development flow.
|
||||
|
||||
```
|
||||
Develop -> Debug -> Validate -> (if issues) -> Develop -> ...
|
||||
```
|
||||
|
||||
## Session Structure (Unified Location)
|
||||
|
||||
```
|
||||
.workflow/.loop/
|
||||
+-- {loopId}.json # Master state file (API + Skill shared)
|
||||
+-- {loopId}.tasks.jsonl # Task list (API managed)
|
||||
+-- {loopId}.progress/ # Skill progress files
|
||||
+-- develop.md # Development progress timeline
|
||||
+-- debug.md # Understanding evolution document
|
||||
+-- validate.md # Validation report
|
||||
+-- changes.log # Code changes log (NDJSON)
|
||||
+-- debug.log # Debug log (NDJSON)
|
||||
```
|
||||
|
||||
## Implementation (Codex Subagent Pattern)
|
||||
|
||||
### Session Setup
|
||||
|
||||
```javascript
|
||||
// Helper: Get UTC+8 (China Standard Time) ISO string
|
||||
const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString()
|
||||
|
||||
// loopId source:
|
||||
// 1. API trigger: from --loop-id parameter
|
||||
// 2. Direct call: generate new loop-v2-{timestamp}-{random}
|
||||
|
||||
const loopId = args['--loop-id'] || (() => {
|
||||
const timestamp = getUtc8ISOString().replace(/[-:]/g, '').split('.')[0]
|
||||
const random = Math.random().toString(36).substring(2, 10)
|
||||
return `loop-v2-${timestamp}-${random}`
|
||||
})()
|
||||
|
||||
const loopFile = `.workflow/.loop/${loopId}.json`
|
||||
const progressDir = `.workflow/.loop/${loopId}.progress`
|
||||
|
||||
// Create progress directory
|
||||
mkdir -p "${progressDir}"
|
||||
```
|
||||
|
||||
### Main Execution Flow (Single Agent Deep Interaction)
|
||||
|
||||
```javascript
|
||||
// ==================== CODEX CCW-LOOP: SINGLE AGENT ORCHESTRATOR ====================
|
||||
|
||||
// Step 1: Read or create initial state
|
||||
let state = null
|
||||
if (existingLoopId) {
|
||||
state = JSON.parse(Read(`.workflow/.loop/${loopId}.json`))
|
||||
if (!state) {
|
||||
console.error(`Loop not found: ${loopId}`)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
state = createInitialState(loopId, taskDescription)
|
||||
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
||||
}
|
||||
|
||||
// Step 2: Create orchestrator agent (single agent handles all phases)
|
||||
const agent = spawn_agent({
|
||||
message: `
|
||||
## TASK ASSIGNMENT
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
1. **Read role definition**: ~/.codex/agents/ccw-loop-executor.md (MUST read first)
|
||||
2. Read: .workflow/project-tech.json
|
||||
3. Read: .workflow/project-guidelines.json
|
||||
|
||||
---
|
||||
|
||||
## LOOP CONTEXT
|
||||
|
||||
- **Loop ID**: ${loopId}
|
||||
- **State File**: .workflow/.loop/${loopId}.json
|
||||
- **Progress Dir**: ${progressDir}
|
||||
- **Mode**: ${mode} // 'interactive' or 'auto'
|
||||
|
||||
## CURRENT STATE
|
||||
|
||||
${JSON.stringify(state, null, 2)}
|
||||
|
||||
## TASK DESCRIPTION
|
||||
|
||||
${taskDescription}
|
||||
|
||||
## EXECUTION INSTRUCTIONS
|
||||
|
||||
You are executing CCW Loop orchestrator. Your job:
|
||||
|
||||
1. **Check Control Signals**
|
||||
- Read .workflow/.loop/${loopId}.json
|
||||
- If status === 'paused' -> Output "PAUSED" and stop
|
||||
- If status === 'failed' -> Output "STOPPED" and stop
|
||||
- If status === 'running' -> Continue
|
||||
|
||||
2. **Select Next Action**
|
||||
Based on skill_state:
|
||||
- If not initialized -> Execute INIT
|
||||
- If mode === 'interactive' -> Output MENU and wait for input
|
||||
- If mode === 'auto' -> Auto-select based on state
|
||||
|
||||
3. **Execute Action**
|
||||
- Follow action instructions from ~/.codex/skills/ccw-loop/phases/actions/
|
||||
- Update progress files in ${progressDir}/
|
||||
- Update state in .workflow/.loop/${loopId}.json
|
||||
|
||||
4. **Output Format**
|
||||
\`\`\`
|
||||
ACTION_RESULT:
|
||||
- action: {action_name}
|
||||
- status: success | failed | needs_input
|
||||
- message: {user message}
|
||||
- state_updates: {JSON of skill_state updates}
|
||||
|
||||
NEXT_ACTION_NEEDED: {action_name} | WAITING_INPUT | COMPLETED | PAUSED
|
||||
\`\`\`
|
||||
|
||||
## FIRST ACTION
|
||||
|
||||
${!state.skill_state ? 'Execute: INIT' : mode === 'auto' ? 'Auto-select next action' : 'Show MENU'}
|
||||
`
|
||||
})
|
||||
|
||||
// Step 3: Main orchestration loop
|
||||
let iteration = 0
|
||||
const maxIterations = state.max_iterations || 10
|
||||
|
||||
while (iteration < maxIterations) {
|
||||
iteration++
|
||||
|
||||
// Wait for agent output
|
||||
const result = wait({ ids: [agent], timeout_ms: 600000 })
|
||||
const output = result.status[agent].completed
|
||||
|
||||
// Parse action result
|
||||
const actionResult = parseActionResult(output)
|
||||
|
||||
// Handle different outcomes
|
||||
switch (actionResult.next_action) {
|
||||
case 'COMPLETED':
|
||||
case 'PAUSED':
|
||||
case 'STOPPED':
|
||||
close_agent({ id: agent })
|
||||
return actionResult
|
||||
|
||||
case 'WAITING_INPUT':
|
||||
// Interactive mode: display menu, get user choice
|
||||
const userChoice = await displayMenuAndGetChoice(actionResult)
|
||||
|
||||
// Send user choice back to agent
|
||||
send_input({
|
||||
id: agent,
|
||||
message: `
|
||||
## USER INPUT RECEIVED
|
||||
|
||||
Action selected: ${userChoice.action}
|
||||
${userChoice.data ? `Additional data: ${JSON.stringify(userChoice.data)}` : ''}
|
||||
|
||||
## EXECUTE SELECTED ACTION
|
||||
|
||||
Follow instructions for: ${userChoice.action}
|
||||
Update state and progress files accordingly.
|
||||
`
|
||||
})
|
||||
break
|
||||
|
||||
default:
|
||||
// Auto mode: agent continues to next action
|
||||
// Check if we need to prompt for continuation
|
||||
if (actionResult.next_action && actionResult.next_action !== 'NONE') {
|
||||
send_input({
|
||||
id: agent,
|
||||
message: `
|
||||
## CONTINUE EXECUTION
|
||||
|
||||
Previous action completed: ${actionResult.action}
|
||||
Result: ${actionResult.status}
|
||||
|
||||
## EXECUTE NEXT ACTION
|
||||
|
||||
Continue with: ${actionResult.next_action}
|
||||
`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Update iteration count in state
|
||||
const currentState = JSON.parse(Read(`.workflow/.loop/${loopId}.json`))
|
||||
currentState.current_iteration = iteration
|
||||
currentState.updated_at = getUtc8ISOString()
|
||||
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(currentState, null, 2))
|
||||
}
|
||||
|
||||
// Step 4: Cleanup
|
||||
close_agent({ id: agent })
|
||||
```
|
||||
|
||||
## Action Catalog
|
||||
|
||||
| Action | Purpose | Output Files | Trigger |
|
||||
|--------|---------|--------------|---------|
|
||||
| [action-init](phases/actions/action-init.md) | Initialize loop session | meta.json, state.json | First run |
|
||||
| [action-develop](phases/actions/action-develop.md) | Execute development task | progress.md, tasks.json | Has pending tasks |
|
||||
| [action-debug](phases/actions/action-debug.md) | Hypothesis-driven debug | understanding.md, hypotheses.json | Needs debugging |
|
||||
| [action-validate](phases/actions/action-validate.md) | Test and validate | validation.md, test-results.json | Needs validation |
|
||||
| [action-complete](phases/actions/action-complete.md) | Complete loop | summary.md | All done |
|
||||
| [action-menu](phases/actions/action-menu.md) | Display action menu | - | Interactive mode |
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Start new loop (direct call)
|
||||
/ccw-loop TASK="Implement user authentication"
|
||||
|
||||
# Continue existing loop (API trigger or manual resume)
|
||||
/ccw-loop --loop-id=loop-v2-20260122-abc123
|
||||
|
||||
# Auto-cycle mode
|
||||
/ccw-loop --auto TASK="Fix login bug and add tests"
|
||||
|
||||
# API triggered auto-cycle
|
||||
/ccw-loop --loop-id=loop-v2-20260122-abc123 --auto
|
||||
```
|
||||
|
||||
## Reference Documents
|
||||
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| [phases/orchestrator.md](phases/orchestrator.md) | Orchestrator: state reading + action selection |
|
||||
| [phases/state-schema.md](phases/state-schema.md) | State structure definition |
|
||||
| [specs/loop-requirements.md](specs/loop-requirements.md) | Loop requirements specification |
|
||||
| [specs/action-catalog.md](specs/action-catalog.md) | Action catalog |
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Situation | Action |
|
||||
|-----------|--------|
|
||||
| Session not found | Create new session |
|
||||
| State file corrupted | Rebuild from file contents |
|
||||
| Agent timeout | send_input to request convergence |
|
||||
| Agent unexpectedly closed | Re-spawn, paste previous output |
|
||||
| Tests fail | Loop back to develop/debug |
|
||||
| >10 iterations | Warn user, suggest break |
|
||||
|
||||
## Codex Best Practices Applied
|
||||
|
||||
1. **Role Path Passing**: Agent reads role file itself (no content embedding)
|
||||
2. **Single Agent Deep Interaction**: Use send_input for multi-phase instead of multiple agents
|
||||
3. **Delayed close_agent**: Only close after confirming no more interaction needed
|
||||
4. **Context Reuse**: Same agent maintains all exploration context automatically
|
||||
5. **Explicit wait()**: Always use wait({ ids }) to get results, not close_agent
|
||||
269
.codex/skills/ccw-loop/phases/actions/action-complete.md
Normal file
269
.codex/skills/ccw-loop/phases/actions/action-complete.md
Normal file
@@ -0,0 +1,269 @@
|
||||
# Action: COMPLETE
|
||||
|
||||
Complete CCW Loop session and generate summary report.
|
||||
|
||||
## Purpose
|
||||
|
||||
- Generate completion report
|
||||
- Aggregate all phase results
|
||||
- Provide follow-up recommendations
|
||||
- Offer expansion to issues
|
||||
- Mark status as completed
|
||||
|
||||
## Preconditions
|
||||
|
||||
- [ ] state.status === 'running'
|
||||
- [ ] state.skill_state !== null
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1: Verify Control Signals
|
||||
|
||||
```javascript
|
||||
const state = JSON.parse(Read(`.workflow/.loop/${loopId}.json`))
|
||||
|
||||
if (state.status !== 'running') {
|
||||
return {
|
||||
action: 'COMPLETE',
|
||||
status: 'failed',
|
||||
message: `Cannot complete: status is ${state.status}`,
|
||||
next_action: state.status === 'paused' ? 'PAUSED' : 'STOPPED'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Aggregate Statistics
|
||||
|
||||
```javascript
|
||||
const stats = {
|
||||
// Time statistics
|
||||
duration: Date.now() - new Date(state.created_at).getTime(),
|
||||
iterations: state.current_iteration,
|
||||
|
||||
// Development statistics
|
||||
develop: {
|
||||
total_tasks: state.skill_state.develop.total,
|
||||
completed_tasks: state.skill_state.develop.completed,
|
||||
completion_rate: state.skill_state.develop.total > 0
|
||||
? ((state.skill_state.develop.completed / state.skill_state.develop.total) * 100).toFixed(1)
|
||||
: 0
|
||||
},
|
||||
|
||||
// Debug statistics
|
||||
debug: {
|
||||
iterations: state.skill_state.debug.iteration,
|
||||
hypotheses_tested: state.skill_state.debug.hypotheses.length,
|
||||
root_cause_found: state.skill_state.debug.confirmed_hypothesis !== null
|
||||
},
|
||||
|
||||
// Validation statistics
|
||||
validate: {
|
||||
runs: state.skill_state.validate.test_results.length,
|
||||
passed: state.skill_state.validate.passed,
|
||||
coverage: state.skill_state.validate.coverage,
|
||||
failed_tests: state.skill_state.validate.failed_tests.length
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Generate Summary Report
|
||||
|
||||
```javascript
|
||||
const timestamp = getUtc8ISOString()
|
||||
|
||||
const summaryReport = `# CCW Loop Session Summary
|
||||
|
||||
**Loop ID**: ${loopId}
|
||||
**Task**: ${state.description}
|
||||
**Started**: ${state.created_at}
|
||||
**Completed**: ${timestamp}
|
||||
**Duration**: ${formatDuration(stats.duration)}
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
${state.skill_state.validate.passed
|
||||
? 'All tests passed, validation successful'
|
||||
: state.skill_state.develop.completed === state.skill_state.develop.total
|
||||
? 'Development complete, validation not passed - needs debugging'
|
||||
: 'Task partially complete - pending items remain'}
|
||||
|
||||
---
|
||||
|
||||
## Development Phase
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Total Tasks | ${stats.develop.total_tasks} |
|
||||
| Completed | ${stats.develop.completed_tasks} |
|
||||
| Completion Rate | ${stats.develop.completion_rate}% |
|
||||
|
||||
### Completed Tasks
|
||||
|
||||
${state.skill_state.develop.tasks.filter(t => t.status === 'completed').map(t => `
|
||||
- ${t.description}
|
||||
- Files: ${t.files_changed?.join(', ') || 'N/A'}
|
||||
- Completed: ${t.completed_at}
|
||||
`).join('\n')}
|
||||
|
||||
### Pending Tasks
|
||||
|
||||
${state.skill_state.develop.tasks.filter(t => t.status !== 'completed').map(t => `
|
||||
- ${t.description}
|
||||
`).join('\n') || '_None_'}
|
||||
|
||||
---
|
||||
|
||||
## Debug Phase
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Iterations | ${stats.debug.iterations} |
|
||||
| Hypotheses Tested | ${stats.debug.hypotheses_tested} |
|
||||
| Root Cause Found | ${stats.debug.root_cause_found ? 'Yes' : 'No'} |
|
||||
|
||||
${stats.debug.root_cause_found ? `
|
||||
### Confirmed Root Cause
|
||||
|
||||
${state.skill_state.debug.confirmed_hypothesis}: ${state.skill_state.debug.hypotheses.find(h => h.id === state.skill_state.debug.confirmed_hypothesis)?.description}
|
||||
` : ''}
|
||||
|
||||
---
|
||||
|
||||
## Validation Phase
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Test Runs | ${stats.validate.runs} |
|
||||
| Status | ${stats.validate.passed ? 'PASSED' : 'FAILED'} |
|
||||
| Coverage | ${stats.validate.coverage || 'N/A'}% |
|
||||
| Failed Tests | ${stats.validate.failed_tests} |
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
${generateRecommendations(stats, state)}
|
||||
|
||||
---
|
||||
|
||||
## Session Artifacts
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| \`develop.md\` | Development progress timeline |
|
||||
| \`debug.md\` | Debug exploration and learnings |
|
||||
| \`validate.md\` | Validation report |
|
||||
| \`test-results.json\` | Test execution results |
|
||||
|
||||
---
|
||||
|
||||
*Generated by CCW Loop at ${timestamp}*
|
||||
`
|
||||
|
||||
Write(`${progressDir}/summary.md`, summaryReport)
|
||||
```
|
||||
|
||||
### Step 4: Update State to Completed
|
||||
|
||||
```javascript
|
||||
state.status = 'completed'
|
||||
state.completed_at = timestamp
|
||||
state.updated_at = timestamp
|
||||
state.skill_state.last_action = 'COMPLETE'
|
||||
state.skill_state.summary = stats
|
||||
|
||||
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
ACTION_RESULT:
|
||||
- action: COMPLETE
|
||||
- status: success
|
||||
- message: Loop completed. Duration: {duration}, Iterations: {N}
|
||||
- state_updates: {
|
||||
"status": "completed",
|
||||
"completed_at": "{timestamp}"
|
||||
}
|
||||
|
||||
FILES_UPDATED:
|
||||
- .workflow/.loop/{loopId}.json: Status set to completed
|
||||
- .workflow/.loop/{loopId}.progress/summary.md: Summary report generated
|
||||
|
||||
NEXT_ACTION_NEEDED: COMPLETED
|
||||
```
|
||||
|
||||
## Expansion Options
|
||||
|
||||
After completion, offer expansion to issues:
|
||||
|
||||
```
|
||||
## Expansion Options
|
||||
|
||||
Would you like to create follow-up issues?
|
||||
|
||||
1. [test] Add more test cases
|
||||
2. [enhance] Feature enhancements
|
||||
3. [refactor] Code refactoring
|
||||
4. [doc] Documentation updates
|
||||
5. [none] No expansion needed
|
||||
|
||||
Select options (comma-separated) or 'none':
|
||||
```
|
||||
|
||||
## Helper Functions
|
||||
|
||||
```javascript
|
||||
function formatDuration(ms) {
|
||||
const seconds = Math.floor(ms / 1000)
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const hours = Math.floor(minutes / 60)
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}h ${minutes % 60}m`
|
||||
} else if (minutes > 0) {
|
||||
return `${minutes}m ${seconds % 60}s`
|
||||
} else {
|
||||
return `${seconds}s`
|
||||
}
|
||||
}
|
||||
|
||||
function generateRecommendations(stats, state) {
|
||||
const recommendations = []
|
||||
|
||||
if (stats.develop.completion_rate < 100) {
|
||||
recommendations.push('- Complete remaining development tasks')
|
||||
}
|
||||
|
||||
if (!stats.validate.passed) {
|
||||
recommendations.push('- Fix failing tests')
|
||||
}
|
||||
|
||||
if (stats.validate.coverage && stats.validate.coverage < 80) {
|
||||
recommendations.push(`- Improve test coverage (current: ${stats.validate.coverage}%)`)
|
||||
}
|
||||
|
||||
if (recommendations.length === 0) {
|
||||
recommendations.push('- Consider code review')
|
||||
recommendations.push('- Update documentation')
|
||||
recommendations.push('- Prepare for deployment')
|
||||
}
|
||||
|
||||
return recommendations.join('\n')
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error Type | Recovery |
|
||||
|------------|----------|
|
||||
| Report generation failed | Show basic stats, skip file write |
|
||||
| Issue creation failed | Log error, continue completion |
|
||||
|
||||
## Next Actions
|
||||
|
||||
- None (terminal state)
|
||||
- To continue: Use `/ccw-loop --loop-id={loopId}` to reopen (will set status back to running)
|
||||
286
.codex/skills/ccw-loop/phases/actions/action-debug.md
Normal file
286
.codex/skills/ccw-loop/phases/actions/action-debug.md
Normal file
@@ -0,0 +1,286 @@
|
||||
# Action: DEBUG
|
||||
|
||||
Hypothesis-driven debugging with understanding evolution documentation.
|
||||
|
||||
## Purpose
|
||||
|
||||
- Locate error source
|
||||
- Generate testable hypotheses
|
||||
- Add NDJSON instrumentation
|
||||
- Analyze log evidence
|
||||
- Correct understanding based on evidence
|
||||
- Apply fixes
|
||||
|
||||
## Preconditions
|
||||
|
||||
- [ ] state.status === 'running'
|
||||
- [ ] state.skill_state !== null
|
||||
|
||||
## Mode Detection
|
||||
|
||||
```javascript
|
||||
const understandingPath = `${progressDir}/debug.md`
|
||||
const debugLogPath = `${progressDir}/debug.log`
|
||||
|
||||
const understandingExists = fs.existsSync(understandingPath)
|
||||
const logHasContent = fs.existsSync(debugLogPath) && fs.statSync(debugLogPath).size > 0
|
||||
|
||||
const debugMode = logHasContent ? 'analyze' : (understandingExists ? 'continue' : 'explore')
|
||||
```
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Mode: Explore (First Debug)
|
||||
|
||||
#### Step E1: Get Bug Description
|
||||
|
||||
```javascript
|
||||
// From test failures or user input
|
||||
const bugDescription = state.skill_state.validate?.failed_tests?.[0]
|
||||
|| await getUserInput('Describe the bug:')
|
||||
```
|
||||
|
||||
#### Step E2: Search Codebase
|
||||
|
||||
```javascript
|
||||
// Use ACE search_context to find related code
|
||||
const searchResults = mcp__ace-tool__search_context({
|
||||
project_root_path: '.',
|
||||
query: `code related to: ${bugDescription}`
|
||||
})
|
||||
```
|
||||
|
||||
#### Step E3: Generate Hypotheses
|
||||
|
||||
```javascript
|
||||
const hypotheses = [
|
||||
{
|
||||
id: 'H1',
|
||||
description: 'Most likely cause',
|
||||
testable_condition: 'What to check',
|
||||
logging_point: 'file.ts:functionName:42',
|
||||
evidence_criteria: {
|
||||
confirm: 'If we see X, hypothesis confirmed',
|
||||
reject: 'If we see Y, hypothesis rejected'
|
||||
},
|
||||
likelihood: 1,
|
||||
status: 'pending',
|
||||
evidence: null,
|
||||
verdict_reason: null
|
||||
},
|
||||
// H2, H3...
|
||||
]
|
||||
```
|
||||
|
||||
#### Step E4: Create Understanding Document
|
||||
|
||||
```javascript
|
||||
const initialUnderstanding = `# Understanding Document
|
||||
|
||||
**Loop ID**: ${loopId}
|
||||
**Bug Description**: ${bugDescription}
|
||||
**Started**: ${getUtc8ISOString()}
|
||||
|
||||
---
|
||||
|
||||
## Exploration Timeline
|
||||
|
||||
### Iteration 1 - Initial Exploration (${getUtc8ISOString()})
|
||||
|
||||
#### Current Understanding
|
||||
|
||||
Based on bug description and code search:
|
||||
|
||||
- Error pattern: [identified pattern]
|
||||
- Affected areas: [files/modules]
|
||||
- Initial hypothesis: [first thoughts]
|
||||
|
||||
#### Evidence from Code Search
|
||||
|
||||
[Search results summary]
|
||||
|
||||
#### Hypotheses
|
||||
|
||||
${hypotheses.map(h => `
|
||||
**${h.id}**: ${h.description}
|
||||
- Testable condition: ${h.testable_condition}
|
||||
- Logging point: ${h.logging_point}
|
||||
- Likelihood: ${h.likelihood}
|
||||
`).join('\n')}
|
||||
|
||||
---
|
||||
|
||||
## Current Consolidated Understanding
|
||||
|
||||
[Summary of what we know so far]
|
||||
`
|
||||
|
||||
Write(understandingPath, initialUnderstanding)
|
||||
Write(`${progressDir}/hypotheses.json`, JSON.stringify({ hypotheses, iteration: 1 }, null, 2))
|
||||
```
|
||||
|
||||
#### Step E5: Add NDJSON Logging Points
|
||||
|
||||
```javascript
|
||||
// For each hypothesis, add instrumentation
|
||||
for (const hypothesis of hypotheses) {
|
||||
const [file, func, line] = hypothesis.logging_point.split(':')
|
||||
|
||||
const logStatement = `console.log(JSON.stringify({
|
||||
hid: "${hypothesis.id}",
|
||||
ts: Date.now(),
|
||||
func: "${func}",
|
||||
data: { /* relevant context */ }
|
||||
}))`
|
||||
|
||||
// Add to file using Edit tool
|
||||
}
|
||||
```
|
||||
|
||||
### Mode: Analyze (Has Logs)
|
||||
|
||||
#### Step A1: Parse Debug Log
|
||||
|
||||
```javascript
|
||||
const logContent = Read(debugLogPath)
|
||||
const entries = logContent.split('\n')
|
||||
.filter(l => l.trim())
|
||||
.map(l => JSON.parse(l))
|
||||
|
||||
// Group by hypothesis ID
|
||||
const byHypothesis = entries.reduce((acc, e) => {
|
||||
acc[e.hid] = acc[e.hid] || []
|
||||
acc[e.hid].push(e)
|
||||
return acc
|
||||
}, {})
|
||||
```
|
||||
|
||||
#### Step A2: Evaluate Evidence
|
||||
|
||||
```javascript
|
||||
const hypothesesData = JSON.parse(Read(`${progressDir}/hypotheses.json`))
|
||||
|
||||
for (const hypothesis of hypothesesData.hypotheses) {
|
||||
const evidence = byHypothesis[hypothesis.id] || []
|
||||
|
||||
// Evaluate against criteria
|
||||
if (matchesConfirmCriteria(evidence, hypothesis.evidence_criteria.confirm)) {
|
||||
hypothesis.status = 'confirmed'
|
||||
hypothesis.evidence = evidence
|
||||
hypothesis.verdict_reason = 'Evidence matches confirm criteria'
|
||||
} else if (matchesRejectCriteria(evidence, hypothesis.evidence_criteria.reject)) {
|
||||
hypothesis.status = 'rejected'
|
||||
hypothesis.evidence = evidence
|
||||
hypothesis.verdict_reason = 'Evidence matches reject criteria'
|
||||
} else {
|
||||
hypothesis.status = 'inconclusive'
|
||||
hypothesis.evidence = evidence
|
||||
hypothesis.verdict_reason = 'Insufficient evidence'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Step A3: Update Understanding
|
||||
|
||||
```javascript
|
||||
const iteration = hypothesesData.iteration + 1
|
||||
const timestamp = getUtc8ISOString()
|
||||
|
||||
const analysisEntry = `
|
||||
### Iteration ${iteration} - Evidence Analysis (${timestamp})
|
||||
|
||||
#### Log Analysis Results
|
||||
|
||||
${hypothesesData.hypotheses.map(h => `
|
||||
**${h.id}**: ${h.status.toUpperCase()}
|
||||
- Evidence: ${JSON.stringify(h.evidence?.slice(0, 3))}
|
||||
- Reasoning: ${h.verdict_reason}
|
||||
`).join('\n')}
|
||||
|
||||
#### Corrected Understanding
|
||||
|
||||
[Any corrections to previous assumptions]
|
||||
|
||||
${confirmedHypothesis ? `
|
||||
#### Root Cause Identified
|
||||
|
||||
**${confirmedHypothesis.id}**: ${confirmedHypothesis.description}
|
||||
` : `
|
||||
#### Next Steps
|
||||
|
||||
[What to investigate next]
|
||||
`}
|
||||
|
||||
---
|
||||
`
|
||||
|
||||
const existingUnderstanding = Read(understandingPath)
|
||||
Write(understandingPath, existingUnderstanding + analysisEntry)
|
||||
```
|
||||
|
||||
### Step: Update State
|
||||
|
||||
```javascript
|
||||
state.skill_state.debug.active_bug = bugDescription
|
||||
state.skill_state.debug.hypotheses = hypothesesData.hypotheses
|
||||
state.skill_state.debug.hypotheses_count = hypothesesData.hypotheses.length
|
||||
state.skill_state.debug.iteration = iteration
|
||||
state.skill_state.debug.last_analysis_at = timestamp
|
||||
|
||||
if (confirmedHypothesis) {
|
||||
state.skill_state.debug.confirmed_hypothesis = confirmedHypothesis.id
|
||||
}
|
||||
|
||||
state.skill_state.last_action = 'DEBUG'
|
||||
state.updated_at = timestamp
|
||||
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
ACTION_RESULT:
|
||||
- action: DEBUG
|
||||
- status: success
|
||||
- message: {Mode description} - {result summary}
|
||||
- state_updates: {
|
||||
"debug.iteration": {N},
|
||||
"debug.confirmed_hypothesis": "{id or null}"
|
||||
}
|
||||
|
||||
FILES_UPDATED:
|
||||
- .workflow/.loop/{loopId}.progress/debug.md: Understanding updated
|
||||
- .workflow/.loop/{loopId}.progress/hypotheses.json: Hypotheses updated
|
||||
- [Source files]: Instrumentation added
|
||||
|
||||
NEXT_ACTION_NEEDED: {DEBUG | VALIDATE | DEVELOP | MENU}
|
||||
```
|
||||
|
||||
## Next Action Selection
|
||||
|
||||
```javascript
|
||||
if (confirmedHypothesis) {
|
||||
// Root cause found, apply fix and validate
|
||||
return 'VALIDATE'
|
||||
} else if (allRejected) {
|
||||
// Generate new hypotheses
|
||||
return 'DEBUG'
|
||||
} else {
|
||||
// Need more evidence - prompt user to reproduce bug
|
||||
return 'WAITING_INPUT' // User needs to trigger bug
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error Type | Recovery |
|
||||
|------------|----------|
|
||||
| Empty debug.log | Prompt user to reproduce bug |
|
||||
| All hypotheses rejected | Generate new hypotheses |
|
||||
| >5 iterations | Suggest escalation |
|
||||
|
||||
## Next Actions
|
||||
|
||||
- Root cause found: `VALIDATE`
|
||||
- Need more evidence: `DEBUG` (after reproduction)
|
||||
- All rejected: `DEBUG` (new hypotheses)
|
||||
183
.codex/skills/ccw-loop/phases/actions/action-develop.md
Normal file
183
.codex/skills/ccw-loop/phases/actions/action-develop.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# Action: DEVELOP
|
||||
|
||||
Execute development task and record progress to develop.md.
|
||||
|
||||
## Purpose
|
||||
|
||||
- Execute next pending development task
|
||||
- Implement code changes
|
||||
- Record progress to Markdown file
|
||||
- Update task status in state
|
||||
|
||||
## Preconditions
|
||||
|
||||
- [ ] state.status === 'running'
|
||||
- [ ] state.skill_state !== null
|
||||
- [ ] state.skill_state.develop.tasks.some(t => t.status === 'pending')
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1: Verify Control Signals
|
||||
|
||||
```javascript
|
||||
const state = JSON.parse(Read(`.workflow/.loop/${loopId}.json`))
|
||||
|
||||
if (state.status !== 'running') {
|
||||
return {
|
||||
action: 'DEVELOP',
|
||||
status: 'failed',
|
||||
message: `Cannot develop: status is ${state.status}`,
|
||||
next_action: state.status === 'paused' ? 'PAUSED' : 'STOPPED'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Find Next Pending Task
|
||||
|
||||
```javascript
|
||||
const tasks = state.skill_state.develop.tasks
|
||||
const currentTask = tasks.find(t => t.status === 'pending')
|
||||
|
||||
if (!currentTask) {
|
||||
return {
|
||||
action: 'DEVELOP',
|
||||
status: 'success',
|
||||
message: 'All development tasks completed',
|
||||
next_action: mode === 'auto' ? 'VALIDATE' : 'MENU'
|
||||
}
|
||||
}
|
||||
|
||||
// Mark as in_progress
|
||||
currentTask.status = 'in_progress'
|
||||
```
|
||||
|
||||
### Step 3: Execute Development Task
|
||||
|
||||
```javascript
|
||||
console.log(`Executing task: ${currentTask.description}`)
|
||||
|
||||
// Use appropriate tools based on task type
|
||||
// - ACE search_context for finding patterns
|
||||
// - Read for loading files
|
||||
// - Edit/Write for making changes
|
||||
|
||||
// Record files changed
|
||||
const filesChanged = []
|
||||
|
||||
// Implementation logic...
|
||||
```
|
||||
|
||||
### Step 4: Record Changes to Log (NDJSON)
|
||||
|
||||
```javascript
|
||||
const changesLogPath = `${progressDir}/changes.log`
|
||||
const timestamp = getUtc8ISOString()
|
||||
|
||||
const changeEntry = {
|
||||
timestamp: timestamp,
|
||||
task_id: currentTask.id,
|
||||
description: currentTask.description,
|
||||
files_changed: filesChanged,
|
||||
result: 'success'
|
||||
}
|
||||
|
||||
// Append to NDJSON log
|
||||
const existingLog = Read(changesLogPath) || ''
|
||||
Write(changesLogPath, existingLog + JSON.stringify(changeEntry) + '\n')
|
||||
```
|
||||
|
||||
### Step 5: Update Progress Document
|
||||
|
||||
```javascript
|
||||
const progressPath = `${progressDir}/develop.md`
|
||||
const iteration = state.skill_state.develop.completed + 1
|
||||
|
||||
const progressEntry = `
|
||||
### Iteration ${iteration} - ${currentTask.description} (${timestamp})
|
||||
|
||||
#### Task Details
|
||||
|
||||
- **ID**: ${currentTask.id}
|
||||
- **Tool**: ${currentTask.tool}
|
||||
- **Mode**: ${currentTask.mode}
|
||||
|
||||
#### Implementation Summary
|
||||
|
||||
[Implementation description]
|
||||
|
||||
#### Files Changed
|
||||
|
||||
${filesChanged.map(f => `- \`${f}\``).join('\n') || '- No files changed'}
|
||||
|
||||
#### Status: COMPLETED
|
||||
|
||||
---
|
||||
|
||||
`
|
||||
|
||||
const existingProgress = Read(progressPath)
|
||||
Write(progressPath, existingProgress + progressEntry)
|
||||
```
|
||||
|
||||
### Step 6: Update State
|
||||
|
||||
```javascript
|
||||
currentTask.status = 'completed'
|
||||
currentTask.completed_at = timestamp
|
||||
currentTask.files_changed = filesChanged
|
||||
|
||||
state.skill_state.develop.completed += 1
|
||||
state.skill_state.develop.current_task = null
|
||||
state.skill_state.develop.last_progress_at = timestamp
|
||||
state.skill_state.last_action = 'DEVELOP'
|
||||
state.skill_state.completed_actions.push('DEVELOP')
|
||||
state.updated_at = timestamp
|
||||
|
||||
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
ACTION_RESULT:
|
||||
- action: DEVELOP
|
||||
- status: success
|
||||
- message: Task completed: {task_description}
|
||||
- state_updates: {
|
||||
"develop.completed": {N},
|
||||
"develop.last_progress_at": "{timestamp}"
|
||||
}
|
||||
|
||||
FILES_UPDATED:
|
||||
- .workflow/.loop/{loopId}.json: Task status updated
|
||||
- .workflow/.loop/{loopId}.progress/develop.md: Progress entry added
|
||||
- .workflow/.loop/{loopId}.progress/changes.log: Change entry added
|
||||
|
||||
NEXT_ACTION_NEEDED: {DEVELOP | DEBUG | VALIDATE | MENU}
|
||||
```
|
||||
|
||||
## Auto Mode Next Action Selection
|
||||
|
||||
```javascript
|
||||
const pendingTasks = tasks.filter(t => t.status === 'pending')
|
||||
|
||||
if (pendingTasks.length > 0) {
|
||||
return 'DEVELOP' // More tasks to do
|
||||
} else {
|
||||
return 'DEBUG' // All done, check for issues
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error Type | Recovery |
|
||||
|------------|----------|
|
||||
| Task execution failed | Mark task as failed, continue to next |
|
||||
| File write failed | Retry once, then report error |
|
||||
| All tasks done | Move to DEBUG or VALIDATE |
|
||||
|
||||
## Next Actions
|
||||
|
||||
- More pending tasks: `DEVELOP`
|
||||
- All tasks complete: `DEBUG` (auto) or `MENU` (interactive)
|
||||
- Task failed: `DEVELOP` (retry) or `DEBUG` (investigate)
|
||||
164
.codex/skills/ccw-loop/phases/actions/action-init.md
Normal file
164
.codex/skills/ccw-loop/phases/actions/action-init.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# Action: INIT
|
||||
|
||||
Initialize CCW Loop session, create directory structure and initial state.
|
||||
|
||||
## Purpose
|
||||
|
||||
- Create session directory structure
|
||||
- Initialize state file with skill_state
|
||||
- Analyze task description to generate development tasks
|
||||
- Prepare execution environment
|
||||
|
||||
## Preconditions
|
||||
|
||||
- [ ] state.status === 'running'
|
||||
- [ ] state.skill_state === null
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1: Verify Control Signals
|
||||
|
||||
```javascript
|
||||
const state = JSON.parse(Read(`.workflow/.loop/${loopId}.json`))
|
||||
|
||||
if (state.status !== 'running') {
|
||||
return {
|
||||
action: 'INIT',
|
||||
status: 'failed',
|
||||
message: `Cannot init: status is ${state.status}`,
|
||||
next_action: state.status === 'paused' ? 'PAUSED' : 'STOPPED'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Create Directory Structure
|
||||
|
||||
```javascript
|
||||
const progressDir = `.workflow/.loop/${loopId}.progress`
|
||||
|
||||
// Directories created by orchestrator, verify they exist
|
||||
// mkdir -p ${progressDir}
|
||||
```
|
||||
|
||||
### Step 3: Analyze Task and Generate Tasks
|
||||
|
||||
```javascript
|
||||
// Analyze task description
|
||||
const taskDescription = state.description
|
||||
|
||||
// Generate 3-7 development tasks based on analysis
|
||||
// Use ACE search or smart_search to find relevant patterns
|
||||
|
||||
const tasks = [
|
||||
{
|
||||
id: 'task-001',
|
||||
description: 'Task description based on analysis',
|
||||
tool: 'gemini',
|
||||
mode: 'write',
|
||||
status: 'pending',
|
||||
priority: 1,
|
||||
files: [],
|
||||
created_at: getUtc8ISOString(),
|
||||
completed_at: null
|
||||
}
|
||||
// ... more tasks
|
||||
]
|
||||
```
|
||||
|
||||
### Step 4: Initialize Progress Document
|
||||
|
||||
```javascript
|
||||
const progressPath = `${progressDir}/develop.md`
|
||||
|
||||
const progressInitial = `# Development Progress
|
||||
|
||||
**Loop ID**: ${loopId}
|
||||
**Task**: ${taskDescription}
|
||||
**Started**: ${getUtc8ISOString()}
|
||||
|
||||
---
|
||||
|
||||
## Task List
|
||||
|
||||
${tasks.map((t, i) => `${i + 1}. [ ] ${t.description}`).join('\n')}
|
||||
|
||||
---
|
||||
|
||||
## Progress Timeline
|
||||
|
||||
`
|
||||
|
||||
Write(progressPath, progressInitial)
|
||||
```
|
||||
|
||||
### Step 5: Update State
|
||||
|
||||
```javascript
|
||||
const skillState = {
|
||||
current_action: 'init',
|
||||
last_action: null,
|
||||
completed_actions: [],
|
||||
mode: mode,
|
||||
|
||||
develop: {
|
||||
total: tasks.length,
|
||||
completed: 0,
|
||||
current_task: null,
|
||||
tasks: tasks,
|
||||
last_progress_at: null
|
||||
},
|
||||
|
||||
debug: {
|
||||
active_bug: null,
|
||||
hypotheses_count: 0,
|
||||
hypotheses: [],
|
||||
confirmed_hypothesis: null,
|
||||
iteration: 0,
|
||||
last_analysis_at: null
|
||||
},
|
||||
|
||||
validate: {
|
||||
pass_rate: 0,
|
||||
coverage: 0,
|
||||
test_results: [],
|
||||
passed: false,
|
||||
failed_tests: [],
|
||||
last_run_at: null
|
||||
},
|
||||
|
||||
errors: []
|
||||
}
|
||||
|
||||
state.skill_state = skillState
|
||||
state.updated_at = getUtc8ISOString()
|
||||
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
ACTION_RESULT:
|
||||
- action: INIT
|
||||
- status: success
|
||||
- message: Session initialized with {N} development tasks
|
||||
|
||||
FILES_UPDATED:
|
||||
- .workflow/.loop/{loopId}.json: skill_state initialized
|
||||
- .workflow/.loop/{loopId}.progress/develop.md: Progress document created
|
||||
|
||||
NEXT_ACTION_NEEDED: {DEVELOP (auto) | MENU (interactive)}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error Type | Recovery |
|
||||
|------------|----------|
|
||||
| Directory creation failed | Report error, stop |
|
||||
| Task analysis failed | Create single generic task |
|
||||
| State write failed | Retry once, then stop |
|
||||
|
||||
## Next Actions
|
||||
|
||||
- Success (auto mode): `DEVELOP`
|
||||
- Success (interactive): `MENU`
|
||||
- Failed: Report error
|
||||
205
.codex/skills/ccw-loop/phases/actions/action-menu.md
Normal file
205
.codex/skills/ccw-loop/phases/actions/action-menu.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# Action: MENU
|
||||
|
||||
Display interactive action menu for user selection.
|
||||
|
||||
## Purpose
|
||||
|
||||
- Show current state summary
|
||||
- Display available actions
|
||||
- Wait for user selection
|
||||
- Return selected action
|
||||
|
||||
## Preconditions
|
||||
|
||||
- [ ] state.status === 'running'
|
||||
- [ ] state.skill_state !== null
|
||||
- [ ] mode === 'interactive'
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1: Verify Control Signals
|
||||
|
||||
```javascript
|
||||
const state = JSON.parse(Read(`.workflow/.loop/${loopId}.json`))
|
||||
|
||||
if (state.status !== 'running') {
|
||||
return {
|
||||
action: 'MENU',
|
||||
status: 'failed',
|
||||
message: `Cannot show menu: status is ${state.status}`,
|
||||
next_action: state.status === 'paused' ? 'PAUSED' : 'STOPPED'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Generate Status Summary
|
||||
|
||||
```javascript
|
||||
// Development progress
|
||||
const developProgress = state.skill_state.develop.total > 0
|
||||
? `${state.skill_state.develop.completed}/${state.skill_state.develop.total} (${((state.skill_state.develop.completed / state.skill_state.develop.total) * 100).toFixed(0)}%)`
|
||||
: 'Not started'
|
||||
|
||||
// Debug status
|
||||
const debugStatus = state.skill_state.debug.confirmed_hypothesis
|
||||
? 'Root cause found'
|
||||
: state.skill_state.debug.iteration > 0
|
||||
? `Iteration ${state.skill_state.debug.iteration}`
|
||||
: 'Not started'
|
||||
|
||||
// Validation status
|
||||
const validateStatus = state.skill_state.validate.passed
|
||||
? 'PASSED'
|
||||
: state.skill_state.validate.test_results.length > 0
|
||||
? `FAILED (${state.skill_state.validate.failed_tests.length} failures)`
|
||||
: 'Not run'
|
||||
```
|
||||
|
||||
### Step 3: Display Menu
|
||||
|
||||
```javascript
|
||||
const menuDisplay = `
|
||||
================================================================
|
||||
CCW Loop - ${loopId}
|
||||
================================================================
|
||||
|
||||
Task: ${state.description}
|
||||
Iteration: ${state.current_iteration}
|
||||
|
||||
+-----------------------------------------------------+
|
||||
| Phase | Status |
|
||||
+-----------------------------------------------------+
|
||||
| Develop | ${developProgress.padEnd(35)}|
|
||||
| Debug | ${debugStatus.padEnd(35)}|
|
||||
| Validate | ${validateStatus.padEnd(35)}|
|
||||
+-----------------------------------------------------+
|
||||
|
||||
================================================================
|
||||
|
||||
MENU_OPTIONS:
|
||||
1. [develop] Continue Development - ${state.skill_state.develop.total - state.skill_state.develop.completed} tasks pending
|
||||
2. [debug] Start Debugging - ${debugStatus}
|
||||
3. [validate] Run Validation - ${validateStatus}
|
||||
4. [status] View Detailed Status
|
||||
5. [complete] Complete Loop
|
||||
6. [exit] Exit (save and quit)
|
||||
`
|
||||
|
||||
console.log(menuDisplay)
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
ACTION_RESULT:
|
||||
- action: MENU
|
||||
- status: success
|
||||
- message: ${menuDisplay}
|
||||
|
||||
MENU_OPTIONS:
|
||||
1. [develop] Continue Development - {N} tasks pending
|
||||
2. [debug] Start Debugging - {status}
|
||||
3. [validate] Run Validation - {status}
|
||||
4. [status] View Detailed Status
|
||||
5. [complete] Complete Loop
|
||||
6. [exit] Exit (save and quit)
|
||||
|
||||
NEXT_ACTION_NEEDED: WAITING_INPUT
|
||||
```
|
||||
|
||||
## User Input Handling
|
||||
|
||||
When user provides input, orchestrator sends it back via `send_input`:
|
||||
|
||||
```javascript
|
||||
// User selects "develop"
|
||||
send_input({
|
||||
id: agent,
|
||||
message: `
|
||||
## USER INPUT RECEIVED
|
||||
|
||||
Action selected: develop
|
||||
|
||||
## EXECUTE SELECTED ACTION
|
||||
|
||||
Execute DEVELOP action.
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
## Status Detail View
|
||||
|
||||
If user selects "status":
|
||||
|
||||
```javascript
|
||||
const detailView = `
|
||||
## Detailed Status
|
||||
|
||||
### Development Progress
|
||||
|
||||
${Read(`${progressDir}/develop.md`)?.substring(0, 1000) || 'No progress recorded'}
|
||||
|
||||
### Debug Status
|
||||
|
||||
${state.skill_state.debug.hypotheses.length > 0
|
||||
? state.skill_state.debug.hypotheses.map(h => ` ${h.id}: ${h.status} - ${h.description.substring(0, 50)}...`).join('\n')
|
||||
: ' No debugging started'}
|
||||
|
||||
### Validation Results
|
||||
|
||||
${state.skill_state.validate.test_results.length > 0
|
||||
? ` Last run: ${state.skill_state.validate.last_run_at}
|
||||
Pass rate: ${state.skill_state.validate.pass_rate}%`
|
||||
: ' No validation run yet'}
|
||||
`
|
||||
|
||||
console.log(detailView)
|
||||
|
||||
// Return to menu
|
||||
return {
|
||||
action: 'MENU',
|
||||
status: 'success',
|
||||
message: detailView,
|
||||
next_action: 'MENU' // Show menu again
|
||||
}
|
||||
```
|
||||
|
||||
## Exit Handling
|
||||
|
||||
If user selects "exit":
|
||||
|
||||
```javascript
|
||||
// Save current state
|
||||
state.status = 'user_exit'
|
||||
state.updated_at = getUtc8ISOString()
|
||||
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
||||
|
||||
return {
|
||||
action: 'MENU',
|
||||
status: 'success',
|
||||
message: 'Session saved. Use --loop-id to resume.',
|
||||
next_action: 'COMPLETED'
|
||||
}
|
||||
```
|
||||
|
||||
## Action Mapping
|
||||
|
||||
| User Selection | Next Action |
|
||||
|----------------|-------------|
|
||||
| develop | DEVELOP |
|
||||
| debug | DEBUG |
|
||||
| validate | VALIDATE |
|
||||
| status | MENU (after showing details) |
|
||||
| complete | COMPLETE |
|
||||
| exit | COMPLETED (save and exit) |
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error Type | Recovery |
|
||||
|------------|----------|
|
||||
| Invalid selection | Show menu again |
|
||||
| User cancels | Return to menu |
|
||||
|
||||
## Next Actions
|
||||
|
||||
Based on user selection - forwarded via `send_input` by orchestrator.
|
||||
250
.codex/skills/ccw-loop/phases/actions/action-validate.md
Normal file
250
.codex/skills/ccw-loop/phases/actions/action-validate.md
Normal file
@@ -0,0 +1,250 @@
|
||||
# Action: VALIDATE
|
||||
|
||||
Run tests and verify implementation, record results to validate.md.
|
||||
|
||||
## Purpose
|
||||
|
||||
- Run unit tests
|
||||
- Run integration tests
|
||||
- Check code coverage
|
||||
- Generate validation report
|
||||
- Determine pass/fail status
|
||||
|
||||
## Preconditions
|
||||
|
||||
- [ ] state.status === 'running'
|
||||
- [ ] state.skill_state !== null
|
||||
- [ ] (develop.completed > 0) OR (debug.confirmed_hypothesis !== null)
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1: Verify Control Signals
|
||||
|
||||
```javascript
|
||||
const state = JSON.parse(Read(`.workflow/.loop/${loopId}.json`))
|
||||
|
||||
if (state.status !== 'running') {
|
||||
return {
|
||||
action: 'VALIDATE',
|
||||
status: 'failed',
|
||||
message: `Cannot validate: status is ${state.status}`,
|
||||
next_action: state.status === 'paused' ? 'PAUSED' : 'STOPPED'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Detect Test Framework
|
||||
|
||||
```javascript
|
||||
const packageJson = JSON.parse(Read('package.json') || '{}')
|
||||
const testScript = packageJson.scripts?.test || 'npm test'
|
||||
const coverageScript = packageJson.scripts?.['test:coverage']
|
||||
```
|
||||
|
||||
### Step 3: Run Tests
|
||||
|
||||
```javascript
|
||||
const testResult = await Bash({
|
||||
command: testScript,
|
||||
timeout: 300000 // 5 minutes
|
||||
})
|
||||
|
||||
// Parse test output based on framework
|
||||
const testResults = parseTestOutput(testResult.stdout, testResult.stderr)
|
||||
```
|
||||
|
||||
### Step 4: Run Coverage (if available)
|
||||
|
||||
```javascript
|
||||
let coverageData = null
|
||||
|
||||
if (coverageScript) {
|
||||
const coverageResult = await Bash({
|
||||
command: coverageScript,
|
||||
timeout: 300000
|
||||
})
|
||||
|
||||
coverageData = parseCoverageReport(coverageResult.stdout)
|
||||
Write(`${progressDir}/coverage.json`, JSON.stringify(coverageData, null, 2))
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Generate Validation Report
|
||||
|
||||
```javascript
|
||||
const timestamp = getUtc8ISOString()
|
||||
const iteration = (state.skill_state.validate.test_results?.length || 0) + 1
|
||||
|
||||
const validationReport = `# Validation Report
|
||||
|
||||
**Loop ID**: ${loopId}
|
||||
**Task**: ${state.description}
|
||||
**Validated**: ${timestamp}
|
||||
|
||||
---
|
||||
|
||||
## Iteration ${iteration} - Validation Run
|
||||
|
||||
### Test Execution Summary
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Total Tests | ${testResults.total} |
|
||||
| Passed | ${testResults.passed} |
|
||||
| Failed | ${testResults.failed} |
|
||||
| Skipped | ${testResults.skipped} |
|
||||
| Duration | ${testResults.duration_ms}ms |
|
||||
| **Pass Rate** | **${((testResults.passed / testResults.total) * 100).toFixed(1)}%** |
|
||||
|
||||
### Coverage Report
|
||||
|
||||
${coverageData ? `
|
||||
| File | Statements | Branches | Functions | Lines |
|
||||
|------|------------|----------|-----------|-------|
|
||||
${coverageData.files.map(f => `| ${f.path} | ${f.statements}% | ${f.branches}% | ${f.functions}% | ${f.lines}% |`).join('\n')}
|
||||
|
||||
**Overall Coverage**: ${coverageData.overall.statements}%
|
||||
` : '_No coverage data available_'}
|
||||
|
||||
### Failed Tests
|
||||
|
||||
${testResults.failed > 0 ? testResults.failures.map(f => `
|
||||
#### ${f.test_name}
|
||||
|
||||
- **Suite**: ${f.suite}
|
||||
- **Error**: ${f.error_message}
|
||||
`).join('\n') : '_All tests passed_'}
|
||||
|
||||
---
|
||||
|
||||
## Validation Decision
|
||||
|
||||
**Result**: ${testResults.failed === 0 ? 'PASS' : 'FAIL'}
|
||||
|
||||
${testResults.failed > 0 ? `
|
||||
### Next Actions
|
||||
|
||||
1. Review failed tests
|
||||
2. Debug failures using DEBUG action
|
||||
3. Fix issues and re-run validation
|
||||
` : `
|
||||
### Next Actions
|
||||
|
||||
1. Consider code review
|
||||
2. Complete loop
|
||||
`}
|
||||
`
|
||||
|
||||
Write(`${progressDir}/validate.md`, validationReport)
|
||||
```
|
||||
|
||||
### Step 6: Save Test Results
|
||||
|
||||
```javascript
|
||||
const testResultsData = {
|
||||
iteration,
|
||||
timestamp,
|
||||
summary: {
|
||||
total: testResults.total,
|
||||
passed: testResults.passed,
|
||||
failed: testResults.failed,
|
||||
skipped: testResults.skipped,
|
||||
pass_rate: ((testResults.passed / testResults.total) * 100).toFixed(1),
|
||||
duration_ms: testResults.duration_ms
|
||||
},
|
||||
tests: testResults.tests,
|
||||
failures: testResults.failures,
|
||||
coverage: coverageData?.overall || null
|
||||
}
|
||||
|
||||
Write(`${progressDir}/test-results.json`, JSON.stringify(testResultsData, null, 2))
|
||||
```
|
||||
|
||||
### Step 7: Update State
|
||||
|
||||
```javascript
|
||||
const validationPassed = testResults.failed === 0 && testResults.passed > 0
|
||||
|
||||
state.skill_state.validate.test_results.push(testResultsData)
|
||||
state.skill_state.validate.pass_rate = parseFloat(testResultsData.summary.pass_rate)
|
||||
state.skill_state.validate.coverage = coverageData?.overall?.statements || 0
|
||||
state.skill_state.validate.passed = validationPassed
|
||||
state.skill_state.validate.failed_tests = testResults.failures.map(f => f.test_name)
|
||||
state.skill_state.validate.last_run_at = timestamp
|
||||
|
||||
state.skill_state.last_action = 'VALIDATE'
|
||||
state.updated_at = timestamp
|
||||
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
ACTION_RESULT:
|
||||
- action: VALIDATE
|
||||
- status: success
|
||||
- message: Validation {PASSED | FAILED} - {pass_count}/{total_count} tests passed
|
||||
- state_updates: {
|
||||
"validate.passed": {true | false},
|
||||
"validate.pass_rate": {N},
|
||||
"validate.failed_tests": [{list}]
|
||||
}
|
||||
|
||||
FILES_UPDATED:
|
||||
- .workflow/.loop/{loopId}.progress/validate.md: Validation report created
|
||||
- .workflow/.loop/{loopId}.progress/test-results.json: Test results saved
|
||||
- .workflow/.loop/{loopId}.progress/coverage.json: Coverage data saved (if available)
|
||||
|
||||
NEXT_ACTION_NEEDED: {COMPLETE | DEBUG | DEVELOP | MENU}
|
||||
```
|
||||
|
||||
## Next Action Selection
|
||||
|
||||
```javascript
|
||||
if (validationPassed) {
|
||||
const pendingTasks = state.skill_state.develop.tasks.filter(t => t.status === 'pending')
|
||||
if (pendingTasks.length === 0) {
|
||||
return 'COMPLETE'
|
||||
} else {
|
||||
return 'DEVELOP'
|
||||
}
|
||||
} else {
|
||||
// Tests failed - need debugging
|
||||
return 'DEBUG'
|
||||
}
|
||||
```
|
||||
|
||||
## Test Output Parsers
|
||||
|
||||
### Jest/Vitest Parser
|
||||
|
||||
```javascript
|
||||
function parseJestOutput(stdout) {
|
||||
const summaryMatch = stdout.match(/Tests:\s+(\d+)\s+passed.*?(\d+)\s+failed.*?(\d+)\s+total/)
|
||||
// ... implementation
|
||||
}
|
||||
```
|
||||
|
||||
### Pytest Parser
|
||||
|
||||
```javascript
|
||||
function parsePytestOutput(stdout) {
|
||||
const summaryMatch = stdout.match(/(\d+)\s+passed.*?(\d+)\s+failed/)
|
||||
// ... implementation
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error Type | Recovery |
|
||||
|------------|----------|
|
||||
| Tests don't run | Check test script config, report error |
|
||||
| All tests fail | Suggest DEBUG action |
|
||||
| Coverage tool missing | Skip coverage, run tests only |
|
||||
| Timeout | Increase timeout or split tests |
|
||||
|
||||
## Next Actions
|
||||
|
||||
- Validation passed, no pending: `COMPLETE`
|
||||
- Validation passed, has pending: `DEVELOP`
|
||||
- Validation failed: `DEBUG`
|
||||
416
.codex/skills/ccw-loop/phases/orchestrator.md
Normal file
416
.codex/skills/ccw-loop/phases/orchestrator.md
Normal file
@@ -0,0 +1,416 @@
|
||||
# Orchestrator (Codex Pattern)
|
||||
|
||||
Orchestrate CCW Loop using Codex subagent pattern: `spawn_agent -> wait -> send_input -> close_agent`.
|
||||
|
||||
## Role
|
||||
|
||||
Check control signals -> Read file state -> Select action -> Execute via agent -> Update files -> Loop until complete or paused/stopped.
|
||||
|
||||
## Codex Pattern Overview
|
||||
|
||||
```
|
||||
+-- spawn_agent (ccw-loop-executor role) --+
|
||||
| |
|
||||
| Phase 1: INIT or first action |
|
||||
| | |
|
||||
| v |
|
||||
| wait() -> get result |
|
||||
| | |
|
||||
| v |
|
||||
| [If needs input] Collect user input |
|
||||
| | |
|
||||
| v |
|
||||
| send_input(user choice + next action) |
|
||||
| | |
|
||||
| v |
|
||||
| wait() -> get result |
|
||||
| | |
|
||||
| v |
|
||||
| [Loop until COMPLETED/PAUSED/STOPPED] |
|
||||
| | |
|
||||
+----------v-------------------------------+
|
||||
|
|
||||
close_agent()
|
||||
```
|
||||
|
||||
## State Management (Unified Location)
|
||||
|
||||
### Read State
|
||||
|
||||
```javascript
|
||||
const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString()
|
||||
|
||||
/**
|
||||
* Read loop state (unified location)
|
||||
* @param loopId - Loop ID (e.g., "loop-v2-20260122-abc123")
|
||||
*/
|
||||
function readLoopState(loopId) {
|
||||
const stateFile = `.workflow/.loop/${loopId}.json`
|
||||
|
||||
if (!fs.existsSync(stateFile)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const state = JSON.parse(Read(stateFile))
|
||||
return state
|
||||
}
|
||||
```
|
||||
|
||||
### Create New Loop State (Direct Call)
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Create new loop state (only for direct calls, API triggers have existing state)
|
||||
*/
|
||||
function createLoopState(loopId, taskDescription) {
|
||||
const stateFile = `.workflow/.loop/${loopId}.json`
|
||||
const now = getUtc8ISOString()
|
||||
|
||||
const state = {
|
||||
// API compatible fields
|
||||
loop_id: loopId,
|
||||
title: taskDescription.substring(0, 100),
|
||||
description: taskDescription,
|
||||
max_iterations: 10,
|
||||
status: 'running', // Direct call sets to running
|
||||
current_iteration: 0,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
|
||||
// Skill extension fields
|
||||
skill_state: null // Initialized by INIT action
|
||||
}
|
||||
|
||||
// Ensure directories exist
|
||||
mkdir -p ".loop"
|
||||
mkdir -p ".workflow/.loop/${loopId}.progress"
|
||||
|
||||
Write(stateFile, JSON.stringify(state, null, 2))
|
||||
return state
|
||||
}
|
||||
```
|
||||
|
||||
## Main Execution Flow (Codex Subagent)
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Run CCW Loop orchestrator using Codex subagent pattern
|
||||
* @param options.loopId - Existing Loop ID (API trigger)
|
||||
* @param options.task - Task description (direct call)
|
||||
* @param options.mode - 'interactive' | 'auto'
|
||||
*/
|
||||
async function runOrchestrator(options = {}) {
|
||||
const { loopId: existingLoopId, task, mode = 'interactive' } = options
|
||||
|
||||
console.log('=== CCW Loop Orchestrator (Codex) Started ===')
|
||||
|
||||
// 1. Determine loopId and initial state
|
||||
let loopId
|
||||
let state
|
||||
|
||||
if (existingLoopId) {
|
||||
// API trigger: use existing loopId
|
||||
loopId = existingLoopId
|
||||
state = readLoopState(loopId)
|
||||
|
||||
if (!state) {
|
||||
console.error(`Loop not found: ${loopId}`)
|
||||
return { status: 'error', message: 'Loop not found' }
|
||||
}
|
||||
|
||||
console.log(`Resuming loop: ${loopId}`)
|
||||
console.log(`Status: ${state.status}`)
|
||||
|
||||
} else if (task) {
|
||||
// Direct call: create new loopId
|
||||
const timestamp = getUtc8ISOString().replace(/[-:]/g, '').split('.')[0]
|
||||
const random = Math.random().toString(36).substring(2, 10)
|
||||
loopId = `loop-v2-${timestamp}-${random}`
|
||||
|
||||
console.log(`Creating new loop: ${loopId}`)
|
||||
console.log(`Task: ${task}`)
|
||||
|
||||
state = createLoopState(loopId, task)
|
||||
|
||||
} else {
|
||||
console.error('Either --loop-id or task description is required')
|
||||
return { status: 'error', message: 'Missing loopId or task' }
|
||||
}
|
||||
|
||||
const progressDir = `.workflow/.loop/${loopId}.progress`
|
||||
|
||||
// 2. Create executor agent (single agent for entire loop)
|
||||
const agent = spawn_agent({
|
||||
message: `
|
||||
## TASK ASSIGNMENT
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
1. **Read role definition**: ~/.codex/agents/ccw-loop-executor.md (MUST read first)
|
||||
2. Read: .workflow/project-tech.json (if exists)
|
||||
3. Read: .workflow/project-guidelines.json (if exists)
|
||||
|
||||
---
|
||||
|
||||
## LOOP CONTEXT
|
||||
|
||||
- **Loop ID**: ${loopId}
|
||||
- **State File**: .workflow/.loop/${loopId}.json
|
||||
- **Progress Dir**: ${progressDir}
|
||||
- **Mode**: ${mode}
|
||||
|
||||
## CURRENT STATE
|
||||
|
||||
${JSON.stringify(state, null, 2)}
|
||||
|
||||
## TASK DESCRIPTION
|
||||
|
||||
${state.description || task}
|
||||
|
||||
## FIRST ACTION
|
||||
|
||||
${!state.skill_state ? 'Execute: INIT' : mode === 'auto' ? 'Auto-select next action' : 'Show MENU'}
|
||||
|
||||
Read the role definition first, then execute the appropriate action.
|
||||
`
|
||||
})
|
||||
|
||||
// 3. Main orchestration loop
|
||||
let iteration = state.current_iteration || 0
|
||||
const maxIterations = state.max_iterations || 10
|
||||
let continueLoop = true
|
||||
|
||||
while (continueLoop && iteration < maxIterations) {
|
||||
iteration++
|
||||
|
||||
// Wait for agent output
|
||||
const result = wait({ ids: [agent], timeout_ms: 600000 })
|
||||
|
||||
// Check for timeout
|
||||
if (result.timed_out) {
|
||||
console.log('Agent timeout, requesting convergence...')
|
||||
send_input({
|
||||
id: agent,
|
||||
message: `
|
||||
## TIMEOUT NOTIFICATION
|
||||
|
||||
Execution timeout reached. Please:
|
||||
1. Output current progress
|
||||
2. Save any pending state updates
|
||||
3. Return ACTION_RESULT with current status
|
||||
`
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
const output = result.status[agent].completed
|
||||
|
||||
// Parse action result
|
||||
const actionResult = parseActionResult(output)
|
||||
|
||||
console.log(`\n[Iteration ${iteration}] Action: ${actionResult.action}, Status: ${actionResult.status}`)
|
||||
|
||||
// Update iteration in state
|
||||
state = readLoopState(loopId)
|
||||
state.current_iteration = iteration
|
||||
state.updated_at = getUtc8ISOString()
|
||||
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
||||
|
||||
// Handle different outcomes
|
||||
switch (actionResult.next_action) {
|
||||
case 'COMPLETED':
|
||||
console.log('Loop completed successfully')
|
||||
continueLoop = false
|
||||
break
|
||||
|
||||
case 'PAUSED':
|
||||
console.log('Loop paused by API, exiting gracefully')
|
||||
continueLoop = false
|
||||
break
|
||||
|
||||
case 'STOPPED':
|
||||
console.log('Loop stopped by API')
|
||||
continueLoop = false
|
||||
break
|
||||
|
||||
case 'WAITING_INPUT':
|
||||
// Interactive mode: display menu, get user choice
|
||||
if (mode === 'interactive') {
|
||||
const userChoice = await displayMenuAndGetChoice(actionResult)
|
||||
|
||||
// Send user choice back to agent
|
||||
send_input({
|
||||
id: agent,
|
||||
message: `
|
||||
## USER INPUT RECEIVED
|
||||
|
||||
Action selected: ${userChoice.action}
|
||||
${userChoice.data ? `Additional data: ${JSON.stringify(userChoice.data)}` : ''}
|
||||
|
||||
## EXECUTE SELECTED ACTION
|
||||
|
||||
Read action instructions and execute: ${userChoice.action}
|
||||
Update state and progress files accordingly.
|
||||
Output ACTION_RESULT when complete.
|
||||
`
|
||||
})
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
// Continue with next action
|
||||
if (actionResult.next_action && actionResult.next_action !== 'NONE') {
|
||||
send_input({
|
||||
id: agent,
|
||||
message: `
|
||||
## CONTINUE EXECUTION
|
||||
|
||||
Previous action completed: ${actionResult.action}
|
||||
Result: ${actionResult.status}
|
||||
${actionResult.message ? `Message: ${actionResult.message}` : ''}
|
||||
|
||||
## EXECUTE NEXT ACTION
|
||||
|
||||
Continue with: ${actionResult.next_action}
|
||||
Read action instructions and execute.
|
||||
Output ACTION_RESULT when complete.
|
||||
`
|
||||
})
|
||||
} else {
|
||||
// No next action specified, check if should continue
|
||||
if (actionResult.status === 'failed') {
|
||||
console.log(`Action failed: ${actionResult.message}`)
|
||||
}
|
||||
continueLoop = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Check iteration limit
|
||||
if (iteration >= maxIterations) {
|
||||
console.log(`\nReached maximum iterations (${maxIterations})`)
|
||||
console.log('Consider breaking down the task or taking a break.')
|
||||
}
|
||||
|
||||
// 5. Cleanup
|
||||
close_agent({ id: agent })
|
||||
|
||||
console.log('\n=== CCW Loop Orchestrator (Codex) Finished ===')
|
||||
|
||||
// Return final state
|
||||
const finalState = readLoopState(loopId)
|
||||
return {
|
||||
status: finalState.status,
|
||||
loop_id: loopId,
|
||||
iterations: iteration,
|
||||
final_state: finalState
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse action result from agent output
|
||||
*/
|
||||
function parseActionResult(output) {
|
||||
const result = {
|
||||
action: 'unknown',
|
||||
status: 'unknown',
|
||||
message: '',
|
||||
state_updates: {},
|
||||
next_action: 'NONE'
|
||||
}
|
||||
|
||||
// Parse ACTION_RESULT block
|
||||
const actionMatch = output.match(/ACTION_RESULT:\s*([\s\S]*?)(?:FILES_UPDATED:|NEXT_ACTION_NEEDED:|$)/)
|
||||
if (actionMatch) {
|
||||
const lines = actionMatch[1].split('\n')
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^-\s*(\w+):\s*(.+)$/)
|
||||
if (match) {
|
||||
const [, key, value] = match
|
||||
if (key === 'state_updates') {
|
||||
try {
|
||||
result.state_updates = JSON.parse(value)
|
||||
} catch (e) {
|
||||
// Try parsing multi-line JSON
|
||||
}
|
||||
} else {
|
||||
result[key] = value.trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse NEXT_ACTION_NEEDED
|
||||
const nextMatch = output.match(/NEXT_ACTION_NEEDED:\s*(\S+)/)
|
||||
if (nextMatch) {
|
||||
result.next_action = nextMatch[1]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Display menu and get user choice (interactive mode)
|
||||
*/
|
||||
async function displayMenuAndGetChoice(actionResult) {
|
||||
// Parse MENU_OPTIONS from output
|
||||
const menuMatch = actionResult.message.match(/MENU_OPTIONS:\s*([\s\S]*?)(?:WAITING_INPUT:|$)/)
|
||||
|
||||
if (menuMatch) {
|
||||
console.log('\n' + menuMatch[1])
|
||||
}
|
||||
|
||||
// Use AskUserQuestion to get choice
|
||||
const response = await AskUserQuestion({
|
||||
questions: [{
|
||||
question: "Select next action:",
|
||||
header: "Action",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "develop", description: "Continue development" },
|
||||
{ label: "debug", description: "Start debugging" },
|
||||
{ label: "validate", description: "Run validation" },
|
||||
{ label: "complete", description: "Complete loop" },
|
||||
{ label: "exit", description: "Exit and save" }
|
||||
]
|
||||
}]
|
||||
})
|
||||
|
||||
return { action: response["Action"] }
|
||||
}
|
||||
```
|
||||
|
||||
## Action Catalog
|
||||
|
||||
| Action | Purpose | Preconditions | Effects |
|
||||
|--------|---------|---------------|---------|
|
||||
| INIT | Initialize session | status=running, skill_state=null | skill_state initialized |
|
||||
| MENU | Display menu | skill_state != null, mode=interactive | Wait for user input |
|
||||
| DEVELOP | Execute dev task | pending tasks > 0 | Update progress.md |
|
||||
| DEBUG | Hypothesis debug | needs debugging | Update understanding.md |
|
||||
| VALIDATE | Run tests | needs validation | Update validation.md |
|
||||
| COMPLETE | Finish loop | all done | status=completed |
|
||||
|
||||
## Termination Conditions
|
||||
|
||||
1. **API Paused**: `state.status === 'paused'` (Skill exits, wait for resume)
|
||||
2. **API Stopped**: `state.status === 'failed'` (Skill terminates)
|
||||
3. **Task Complete**: `NEXT_ACTION_NEEDED === 'COMPLETED'`
|
||||
4. **Iteration Limit**: `current_iteration >= max_iterations`
|
||||
5. **User Exit**: User selects 'exit' in interactive mode
|
||||
|
||||
## Error Recovery
|
||||
|
||||
| Error Type | Recovery Strategy |
|
||||
|------------|-------------------|
|
||||
| Agent timeout | send_input requesting convergence |
|
||||
| Action failed | Log error, continue or prompt user |
|
||||
| State corrupted | Rebuild from progress files |
|
||||
| Agent closed unexpectedly | Re-spawn with previous output in message |
|
||||
|
||||
## Codex Best Practices Applied
|
||||
|
||||
1. **Single Agent Pattern**: One agent handles entire loop lifecycle
|
||||
2. **Deep Interaction via send_input**: Multi-phase without context loss
|
||||
3. **Delayed close_agent**: Only after confirming no more interaction
|
||||
4. **Explicit wait()**: Always get results before proceeding
|
||||
5. **Role Path Passing**: Agent reads role file, no content embedding
|
||||
388
.codex/skills/ccw-loop/phases/state-schema.md
Normal file
388
.codex/skills/ccw-loop/phases/state-schema.md
Normal file
@@ -0,0 +1,388 @@
|
||||
# State Schema (Codex Version)
|
||||
|
||||
CCW Loop state structure definition for Codex subagent pattern.
|
||||
|
||||
## State File
|
||||
|
||||
**Location**: `.workflow/.loop/{loopId}.json` (unified location, API + Skill shared)
|
||||
|
||||
## Structure Definition
|
||||
|
||||
### Unified Loop State Interface
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Unified Loop State - API and Skill shared state structure
|
||||
* API (loop-v2-routes.ts) owns state control
|
||||
* Skill (ccw-loop) reads and updates this state via subagent
|
||||
*/
|
||||
interface LoopState {
|
||||
// =====================================================
|
||||
// API FIELDS (from loop-v2-routes.ts)
|
||||
// These fields are managed by API, Skill read-only
|
||||
// =====================================================
|
||||
|
||||
loop_id: string // Loop ID, e.g., "loop-v2-20260122-abc123"
|
||||
title: string // Loop title
|
||||
description: string // Loop description
|
||||
max_iterations: number // Maximum iteration count
|
||||
status: 'created' | 'running' | 'paused' | 'completed' | 'failed' | 'user_exit'
|
||||
current_iteration: number // Current iteration count
|
||||
created_at: string // Creation time (ISO8601)
|
||||
updated_at: string // Last update time (ISO8601)
|
||||
completed_at?: string // Completion time (ISO8601)
|
||||
failure_reason?: string // Failure reason
|
||||
|
||||
// =====================================================
|
||||
// SKILL EXTENSION FIELDS
|
||||
// These fields are managed by Skill executor agent
|
||||
// =====================================================
|
||||
|
||||
skill_state?: {
|
||||
// Current execution action
|
||||
current_action: 'init' | 'develop' | 'debug' | 'validate' | 'complete' | null
|
||||
last_action: string | null
|
||||
completed_actions: string[]
|
||||
mode: 'interactive' | 'auto'
|
||||
|
||||
// === Development Phase ===
|
||||
develop: {
|
||||
total: number
|
||||
completed: number
|
||||
current_task?: string
|
||||
tasks: DevelopTask[]
|
||||
last_progress_at: string | null
|
||||
}
|
||||
|
||||
// === Debug Phase ===
|
||||
debug: {
|
||||
active_bug?: string
|
||||
hypotheses_count: number
|
||||
hypotheses: Hypothesis[]
|
||||
confirmed_hypothesis: string | null
|
||||
iteration: number
|
||||
last_analysis_at: string | null
|
||||
}
|
||||
|
||||
// === Validation Phase ===
|
||||
validate: {
|
||||
pass_rate: number // Test pass rate (0-100)
|
||||
coverage: number // Coverage (0-100)
|
||||
test_results: TestResult[]
|
||||
passed: boolean
|
||||
failed_tests: string[]
|
||||
last_run_at: string | null
|
||||
}
|
||||
|
||||
// === Error Tracking ===
|
||||
errors: Array<{
|
||||
action: string
|
||||
message: string
|
||||
timestamp: string
|
||||
}>
|
||||
|
||||
// === Summary (after completion) ===
|
||||
summary?: {
|
||||
duration: number
|
||||
iterations: number
|
||||
develop: object
|
||||
debug: object
|
||||
validate: object
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface DevelopTask {
|
||||
id: string
|
||||
description: string
|
||||
tool: 'gemini' | 'qwen' | 'codex' | 'bash'
|
||||
mode: 'analysis' | 'write'
|
||||
status: 'pending' | 'in_progress' | 'completed' | 'failed'
|
||||
files_changed: string[]
|
||||
created_at: string
|
||||
completed_at: string | null
|
||||
}
|
||||
|
||||
interface Hypothesis {
|
||||
id: string // H1, H2, ...
|
||||
description: string
|
||||
testable_condition: string
|
||||
logging_point: string
|
||||
evidence_criteria: {
|
||||
confirm: string
|
||||
reject: string
|
||||
}
|
||||
likelihood: number // 1 = most likely
|
||||
status: 'pending' | 'confirmed' | 'rejected' | 'inconclusive'
|
||||
evidence: Record<string, any> | null
|
||||
verdict_reason: string | null
|
||||
}
|
||||
|
||||
interface TestResult {
|
||||
test_name: string
|
||||
suite: string
|
||||
status: 'passed' | 'failed' | 'skipped'
|
||||
duration_ms: number
|
||||
error_message: string | null
|
||||
stack_trace: string | null
|
||||
}
|
||||
```
|
||||
|
||||
## Initial State
|
||||
|
||||
### Created by API (Dashboard Trigger)
|
||||
|
||||
```json
|
||||
{
|
||||
"loop_id": "loop-v2-20260122-abc123",
|
||||
"title": "Implement user authentication",
|
||||
"description": "Add login/logout functionality",
|
||||
"max_iterations": 10,
|
||||
"status": "created",
|
||||
"current_iteration": 0,
|
||||
"created_at": "2026-01-22T10:00:00+08:00",
|
||||
"updated_at": "2026-01-22T10:00:00+08:00"
|
||||
}
|
||||
```
|
||||
|
||||
### After Skill Initialization (INIT action)
|
||||
|
||||
```json
|
||||
{
|
||||
"loop_id": "loop-v2-20260122-abc123",
|
||||
"title": "Implement user authentication",
|
||||
"description": "Add login/logout functionality",
|
||||
"max_iterations": 10,
|
||||
"status": "running",
|
||||
"current_iteration": 0,
|
||||
"created_at": "2026-01-22T10:00:00+08:00",
|
||||
"updated_at": "2026-01-22T10:00:05+08:00",
|
||||
|
||||
"skill_state": {
|
||||
"current_action": "init",
|
||||
"last_action": null,
|
||||
"completed_actions": [],
|
||||
"mode": "auto",
|
||||
|
||||
"develop": {
|
||||
"total": 3,
|
||||
"completed": 0,
|
||||
"current_task": null,
|
||||
"tasks": [
|
||||
{ "id": "task-001", "description": "Create auth component", "status": "pending" }
|
||||
],
|
||||
"last_progress_at": null
|
||||
},
|
||||
|
||||
"debug": {
|
||||
"active_bug": null,
|
||||
"hypotheses_count": 0,
|
||||
"hypotheses": [],
|
||||
"confirmed_hypothesis": null,
|
||||
"iteration": 0,
|
||||
"last_analysis_at": null
|
||||
},
|
||||
|
||||
"validate": {
|
||||
"pass_rate": 0,
|
||||
"coverage": 0,
|
||||
"test_results": [],
|
||||
"passed": false,
|
||||
"failed_tests": [],
|
||||
"last_run_at": null
|
||||
},
|
||||
|
||||
"errors": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Control Signal Checking (Codex Pattern)
|
||||
|
||||
Agent checks control signals at start of every action:
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Check API control signals
|
||||
* MUST be called at start of every action
|
||||
* @returns { continue: boolean, action: 'pause_exit' | 'stop_exit' | 'continue' }
|
||||
*/
|
||||
function checkControlSignals(loopId) {
|
||||
const state = JSON.parse(Read(`.workflow/.loop/${loopId}.json`))
|
||||
|
||||
switch (state.status) {
|
||||
case 'paused':
|
||||
// API paused the loop, Skill should exit and wait for resume
|
||||
return { continue: false, action: 'pause_exit' }
|
||||
|
||||
case 'failed':
|
||||
// API stopped the loop (user manual stop)
|
||||
return { continue: false, action: 'stop_exit' }
|
||||
|
||||
case 'running':
|
||||
// Normal continue
|
||||
return { continue: true, action: 'continue' }
|
||||
|
||||
default:
|
||||
// Abnormal status
|
||||
return { continue: false, action: 'stop_exit' }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## State Transitions
|
||||
|
||||
### 1. Initialization (INIT action)
|
||||
|
||||
```javascript
|
||||
{
|
||||
status: 'created' -> 'running', // Or keep 'running' if API already set
|
||||
updated_at: timestamp,
|
||||
|
||||
skill_state: {
|
||||
current_action: 'init',
|
||||
mode: 'auto',
|
||||
develop: {
|
||||
tasks: [...parsed_tasks],
|
||||
total: N,
|
||||
completed: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Development (DEVELOP action)
|
||||
|
||||
```javascript
|
||||
{
|
||||
updated_at: timestamp,
|
||||
current_iteration: state.current_iteration + 1,
|
||||
|
||||
skill_state: {
|
||||
current_action: 'develop',
|
||||
last_action: 'DEVELOP',
|
||||
completed_actions: [..., 'DEVELOP'],
|
||||
develop: {
|
||||
current_task: 'task-xxx',
|
||||
completed: N+1,
|
||||
last_progress_at: timestamp
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Debugging (DEBUG action)
|
||||
|
||||
```javascript
|
||||
{
|
||||
updated_at: timestamp,
|
||||
current_iteration: state.current_iteration + 1,
|
||||
|
||||
skill_state: {
|
||||
current_action: 'debug',
|
||||
last_action: 'DEBUG',
|
||||
debug: {
|
||||
active_bug: '...',
|
||||
hypotheses_count: N,
|
||||
hypotheses: [...new_hypotheses],
|
||||
iteration: N+1,
|
||||
last_analysis_at: timestamp
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Validation (VALIDATE action)
|
||||
|
||||
```javascript
|
||||
{
|
||||
updated_at: timestamp,
|
||||
current_iteration: state.current_iteration + 1,
|
||||
|
||||
skill_state: {
|
||||
current_action: 'validate',
|
||||
last_action: 'VALIDATE',
|
||||
validate: {
|
||||
test_results: [...results],
|
||||
pass_rate: 95.5,
|
||||
coverage: 85.0,
|
||||
passed: true | false,
|
||||
failed_tests: ['test1', 'test2'],
|
||||
last_run_at: timestamp
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Completion (COMPLETE action)
|
||||
|
||||
```javascript
|
||||
{
|
||||
status: 'running' -> 'completed',
|
||||
completed_at: timestamp,
|
||||
updated_at: timestamp,
|
||||
|
||||
skill_state: {
|
||||
current_action: 'complete',
|
||||
last_action: 'COMPLETE',
|
||||
summary: { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## File Sync
|
||||
|
||||
### Unified Location
|
||||
|
||||
State-to-file mapping:
|
||||
|
||||
| State Field | Sync File | Sync Timing |
|
||||
|-------------|-----------|-------------|
|
||||
| Entire LoopState | `.workflow/.loop/{loopId}.json` | Every state change (master) |
|
||||
| `skill_state.develop` | `.workflow/.loop/{loopId}.progress/develop.md` | After each dev operation |
|
||||
| `skill_state.debug` | `.workflow/.loop/{loopId}.progress/debug.md` | After each debug operation |
|
||||
| `skill_state.validate` | `.workflow/.loop/{loopId}.progress/validate.md` | After each validation |
|
||||
| Code changes log | `.workflow/.loop/{loopId}.progress/changes.log` | Each file modification (NDJSON) |
|
||||
| Debug log | `.workflow/.loop/{loopId}.progress/debug.log` | Each debug log (NDJSON) |
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
.workflow/.loop/
|
||||
+-- loop-v2-20260122-abc123.json # Master state file (API + Skill)
|
||||
+-- loop-v2-20260122-abc123.tasks.jsonl # Task list (API managed)
|
||||
+-- loop-v2-20260122-abc123.progress/ # Skill progress files
|
||||
+-- develop.md # Development progress
|
||||
+-- debug.md # Debug understanding
|
||||
+-- validate.md # Validation report
|
||||
+-- changes.log # Code changes (NDJSON)
|
||||
+-- debug.log # Debug log (NDJSON)
|
||||
+-- summary.md # Completion summary
|
||||
```
|
||||
|
||||
## State Recovery
|
||||
|
||||
If master state file corrupted, rebuild skill_state from progress files:
|
||||
|
||||
```javascript
|
||||
function rebuildSkillStateFromProgress(loopId) {
|
||||
const progressDir = `.workflow/.loop/${loopId}.progress`
|
||||
|
||||
// Parse progress files to rebuild state
|
||||
const skill_state = {
|
||||
develop: parseProgressFile(`${progressDir}/develop.md`),
|
||||
debug: parseProgressFile(`${progressDir}/debug.md`),
|
||||
validate: parseProgressFile(`${progressDir}/validate.md`)
|
||||
}
|
||||
|
||||
return skill_state
|
||||
}
|
||||
```
|
||||
|
||||
## Codex Pattern Notes
|
||||
|
||||
1. **Agent reads state**: Agent reads `.workflow/.loop/{loopId}.json` at action start
|
||||
2. **Agent writes state**: Agent updates state after action completion
|
||||
3. **Orchestrator tracks iterations**: Main loop tracks `current_iteration`
|
||||
4. **Single agent context**: All state updates in same agent conversation via send_input
|
||||
5. **No context serialization loss**: State transitions happen in-memory within agent
|
||||
182
.codex/skills/ccw-loop/specs/action-catalog.md
Normal file
182
.codex/skills/ccw-loop/specs/action-catalog.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# Action Catalog (Codex Version)
|
||||
|
||||
CCW Loop available actions and their specifications.
|
||||
|
||||
## Available Actions
|
||||
|
||||
| Action | Purpose | Preconditions | Effects | Output |
|
||||
|--------|---------|---------------|---------|--------|
|
||||
| INIT | Initialize session | status=running, skill_state=null | skill_state initialized | progress/*.md created |
|
||||
| MENU | Display action menu | skill_state!=null, mode=interactive | Wait for user input | WAITING_INPUT |
|
||||
| DEVELOP | Execute dev task | pending tasks > 0 | Update progress.md | develop.md updated |
|
||||
| DEBUG | Hypothesis debug | needs debugging | Update understanding.md | debug.md updated |
|
||||
| VALIDATE | Run tests | needs validation | Update validation.md | validate.md updated |
|
||||
| COMPLETE | Finish loop | all done | status=completed | summary.md created |
|
||||
|
||||
## Action Flow (Codex Pattern)
|
||||
|
||||
```
|
||||
spawn_agent (ccw-loop-executor)
|
||||
|
|
||||
v
|
||||
+-------+
|
||||
| INIT | (if skill_state is null)
|
||||
+-------+
|
||||
|
|
||||
v
|
||||
+-------+ send_input
|
||||
| MENU | <------------- (user selection in interactive mode)
|
||||
+-------+
|
||||
|
|
||||
+---+---+---+---+
|
||||
| | | | |
|
||||
v v v v v
|
||||
DEV DBG VAL CMP EXIT
|
||||
|
|
||||
v
|
||||
wait() -> get result
|
||||
|
|
||||
v
|
||||
[Loop continues via send_input]
|
||||
|
|
||||
v
|
||||
close_agent()
|
||||
```
|
||||
|
||||
## Action Execution Pattern
|
||||
|
||||
### Single Agent Deep Interaction
|
||||
|
||||
All actions executed within same agent via `send_input`:
|
||||
|
||||
```javascript
|
||||
// Initial spawn
|
||||
const agent = spawn_agent({ message: role + initial_task })
|
||||
|
||||
// Execute INIT
|
||||
const initResult = wait({ ids: [agent] })
|
||||
|
||||
// Continue with DEVELOP via send_input
|
||||
send_input({ id: agent, message: 'Execute DEVELOP' })
|
||||
const devResult = wait({ ids: [agent] })
|
||||
|
||||
// Continue with VALIDATE via send_input
|
||||
send_input({ id: agent, message: 'Execute VALIDATE' })
|
||||
const valResult = wait({ ids: [agent] })
|
||||
|
||||
// Only close when done
|
||||
close_agent({ id: agent })
|
||||
```
|
||||
|
||||
### Action Output Format (Standardized)
|
||||
|
||||
Every action MUST output:
|
||||
|
||||
```
|
||||
ACTION_RESULT:
|
||||
- action: {ACTION_NAME}
|
||||
- status: success | failed | needs_input
|
||||
- message: {user-facing message}
|
||||
- state_updates: { ... }
|
||||
|
||||
FILES_UPDATED:
|
||||
- {file_path}: {description}
|
||||
|
||||
NEXT_ACTION_NEEDED: {NEXT_ACTION} | WAITING_INPUT | COMPLETED | PAUSED
|
||||
```
|
||||
|
||||
## Action Selection Logic
|
||||
|
||||
### Auto Mode
|
||||
|
||||
```javascript
|
||||
function selectNextAction(state) {
|
||||
const skillState = state.skill_state
|
||||
|
||||
// 1. Terminal conditions
|
||||
if (state.status === 'completed') return null
|
||||
if (state.status === 'failed') return null
|
||||
if (state.current_iteration >= state.max_iterations) return 'COMPLETE'
|
||||
|
||||
// 2. Initialization check
|
||||
if (!skillState) return 'INIT'
|
||||
|
||||
// 3. Auto selection based on state
|
||||
const hasPendingDevelop = skillState.develop.tasks.some(t => t.status === 'pending')
|
||||
|
||||
if (hasPendingDevelop) {
|
||||
return 'DEVELOP'
|
||||
}
|
||||
|
||||
if (skillState.last_action === 'DEVELOP') {
|
||||
const needsDebug = skillState.develop.completed < skillState.develop.total
|
||||
if (needsDebug) return 'DEBUG'
|
||||
}
|
||||
|
||||
if (skillState.last_action === 'DEBUG' || skillState.debug.confirmed_hypothesis) {
|
||||
return 'VALIDATE'
|
||||
}
|
||||
|
||||
if (skillState.last_action === 'VALIDATE') {
|
||||
if (!skillState.validate.passed) return 'DEVELOP'
|
||||
}
|
||||
|
||||
if (skillState.validate.passed && !hasPendingDevelop) {
|
||||
return 'COMPLETE'
|
||||
}
|
||||
|
||||
return 'DEVELOP'
|
||||
}
|
||||
```
|
||||
|
||||
### Interactive Mode
|
||||
|
||||
Returns `MENU` action, which displays options and waits for user input.
|
||||
|
||||
## Action Dependencies
|
||||
|
||||
| Action | Depends On | Leads To |
|
||||
|--------|------------|----------|
|
||||
| INIT | - | MENU or DEVELOP |
|
||||
| MENU | INIT | User selection |
|
||||
| DEVELOP | INIT | DEVELOP, DEBUG, VALIDATE |
|
||||
| DEBUG | INIT | DEVELOP, VALIDATE |
|
||||
| VALIDATE | DEVELOP or DEBUG | COMPLETE, DEBUG, DEVELOP |
|
||||
| COMPLETE | - | Terminal |
|
||||
|
||||
## Action Sequences
|
||||
|
||||
### Happy Path (Auto Mode)
|
||||
|
||||
```
|
||||
INIT -> DEVELOP -> DEVELOP -> DEVELOP -> VALIDATE (pass) -> COMPLETE
|
||||
```
|
||||
|
||||
### Debug Iteration Path
|
||||
|
||||
```
|
||||
INIT -> DEVELOP -> VALIDATE (fail) -> DEBUG -> DEBUG -> VALIDATE (pass) -> COMPLETE
|
||||
```
|
||||
|
||||
### Interactive Path
|
||||
|
||||
```
|
||||
INIT -> MENU -> (user: develop) -> DEVELOP -> MENU -> (user: validate) -> VALIDATE -> MENU -> (user: complete) -> COMPLETE
|
||||
```
|
||||
|
||||
## Error Recovery
|
||||
|
||||
| Error | Recovery |
|
||||
|-------|----------|
|
||||
| Action timeout | send_input requesting convergence |
|
||||
| Action failed | Log error, continue or retry |
|
||||
| Agent closed unexpectedly | Re-spawn with previous output |
|
||||
| State corrupted | Rebuild from progress files |
|
||||
|
||||
## Codex Best Practices
|
||||
|
||||
1. **Single agent for all actions**: No need to spawn new agent for each action
|
||||
2. **Deep interaction via send_input**: Continue conversation in same context
|
||||
3. **Delayed close_agent**: Only close after all actions complete
|
||||
4. **Structured output**: Always use ACTION_RESULT format for parsing
|
||||
5. **Control signal checking**: Check state.status before every action
|
||||
@@ -1,385 +0,0 @@
|
||||
# Parallel Dev Cycle Skill
|
||||
|
||||
Multi-agent parallel development cycle using Codex subagent pattern with continuous iteration support.
|
||||
|
||||
## Overview
|
||||
|
||||
This skill implements a **single-file-per-agent** development workflow:
|
||||
|
||||
- **RA**: `requirements.md` (all requirements + edge cases + history)
|
||||
- **EP**: `exploration.md`, `architecture.md`, `plan.json` (codebase exploration + architecture + structured tasks)
|
||||
- **CD**: `implementation.md` (progress + files + decisions + testing)
|
||||
- **VAS**: `summary.md` (validation + test results + recommendations)
|
||||
|
||||
Each file is **completely rewritten** on each iteration, with old versions auto-archived to `history/`.
|
||||
|
||||
## Installation
|
||||
|
||||
Files are in `.codex/skills/parallel-dev-cycle/`:
|
||||
|
||||
```
|
||||
.codex/skills/parallel-dev-cycle/
|
||||
├── SKILL.md # Main skill definition
|
||||
├── README.md # This file
|
||||
├── phases/
|
||||
│ ├── orchestrator.md # Multi-agent coordination
|
||||
│ ├── state-schema.md # Unified state structure
|
||||
│ └── agents/
|
||||
│ ├── requirements-analyst.md # RA role
|
||||
│ ├── exploration-planner.md # EP role
|
||||
│ ├── code-developer.md # CD role
|
||||
│ └── validation-archivist.md # VAS role
|
||||
└── specs/
|
||||
├── coordination-protocol.md # Agent communication
|
||||
└── versioning-strategy.md # Version management
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Launch New Cycle
|
||||
|
||||
```bash
|
||||
/parallel-dev-cycle TASK="Implement OAuth authentication"
|
||||
```
|
||||
|
||||
Creates:
|
||||
```
|
||||
.workflow/.cycle/cycle-v1-20260122-abc123.progress/
|
||||
├── ra/
|
||||
│ ├── requirements.md (v1.0.0)
|
||||
│ └── changes.log (NDJSON)
|
||||
├── ep/
|
||||
│ ├── exploration.md (v1.0.0)
|
||||
│ ├── architecture.md (v1.0.0)
|
||||
│ ├── plan.json (v1.0.0)
|
||||
│ └── changes.log (NDJSON)
|
||||
├── cd/
|
||||
│ ├── implementation.md (v1.0.0)
|
||||
│ └── changes.log (NDJSON)
|
||||
└── vas/
|
||||
├── summary.md (v1.0.0)
|
||||
└── changes.log (NDJSON)
|
||||
```
|
||||
|
||||
### Continue With Extension (XX-1 Pattern)
|
||||
|
||||
User adds requirement: "Also support Google OAuth"
|
||||
|
||||
```bash
|
||||
/parallel-dev-cycle --cycle-id=cycle-v1-20260122-abc123 --extend="Add Google OAuth"
|
||||
```
|
||||
|
||||
Automatically:
|
||||
1. Archives old `requirements.md (v1.0.0)` → `history/requirements-v1.0.0.md`
|
||||
2. Rewrites `requirements.md (v1.1.0)` - complete file replacement
|
||||
3. Appends change to `changes.log` (NDJSON audit trail)
|
||||
|
||||
### Next Iteration (XX-2)
|
||||
|
||||
```bash
|
||||
/parallel-dev-cycle --cycle-id=cycle-v1-20260122-abc123 --extend="Add GitHub provider"
|
||||
```
|
||||
|
||||
All files update to v1.2.0, previous versions archived.
|
||||
|
||||
## Execution Flow
|
||||
|
||||
### Phase 1: Parallel Agent Execution
|
||||
|
||||
```
|
||||
Time RA EP CD VAS
|
||||
──── ── ── ── ──
|
||||
0ms [spawned] [spawned] [spawned] [spawned]
|
||||
↓ ↓ ↓ ↓
|
||||
Analyzing Exploring Reading plan Waiting
|
||||
task codebase from EP...
|
||||
↓
|
||||
5min Outputs req. Outputs plan Requirements
|
||||
v1.0.0 ✓ v1.0.0 ✓ unclear - BLOCKED
|
||||
|
||||
10min Clarifies req Updates plan ✓ Ready
|
||||
v1.0.1 ✓ v1.0.1 ✓ Implementing...
|
||||
↓
|
||||
15min ✓ Complete ✓ Complete ✓ Code done [waiting for CD]
|
||||
|
||||
20min [starts tests]
|
||||
↓
|
||||
25min Outputs summary
|
||||
v1.0.0 ✓
|
||||
```
|
||||
|
||||
### Phase 2: Version Transition
|
||||
|
||||
When iteration completes, next extends to v1.1.0:
|
||||
|
||||
```
|
||||
Current State (v1.0.0)
|
||||
├── requirements.md (v1.0.0)
|
||||
├── plan.json (v1.0.0)
|
||||
├── implementation.md (v1.0.0)
|
||||
└── summary.md (v1.0.0)
|
||||
|
||||
User: "Add GitHub provider"
|
||||
↓
|
||||
Archive Old Write New
|
||||
├── history/requirements-v1.0.0.md → requirements.md (v1.1.0) - REWRITTEN
|
||||
├── history/plan-v1.0.0.json → plan.json (v1.1.0) - REWRITTEN
|
||||
├── history/impl-v1.0.0.md → implementation.md (v1.1.0) - REWRITTEN
|
||||
└── history/summary-v1.0.0.md → summary.md (v1.1.0) - REWRITTEN
|
||||
↓
|
||||
Append to changes.log (NDJSON)
|
||||
```
|
||||
|
||||
## Session Files
|
||||
|
||||
```
|
||||
.workflow/.cycle/{cycleId}.progress/
|
||||
|
||||
ra/ - Requirements Analyst
|
||||
├── requirements.md # v1.2.0 (current, complete rewrite)
|
||||
├── changes.log # NDJSON audit trail
|
||||
└── history/
|
||||
├── requirements-v1.0.0.md
|
||||
└── requirements-v1.1.0.md
|
||||
|
||||
ep/ - Exploration & Planning
|
||||
├── exploration.md # v1.2.0 (codebase exploration)
|
||||
├── architecture.md # v1.2.0 (architecture design)
|
||||
├── plan.json # v1.2.0 (structured task list, current)
|
||||
├── changes.log # NDJSON audit trail
|
||||
└── history/
|
||||
├── plan-v1.0.0.json
|
||||
└── plan-v1.1.0.json
|
||||
|
||||
cd/ - Code Developer
|
||||
├── implementation.md # v1.2.0 (current)
|
||||
├── changes.log # NDJSON audit trail
|
||||
└── history/
|
||||
├── implementation-v1.0.0.md
|
||||
└── implementation-v1.1.0.md
|
||||
|
||||
vas/ - Validation & Archival
|
||||
├── summary.md # v1.2.0 (current)
|
||||
├── changes.log # NDJSON audit trail
|
||||
└── history/
|
||||
├── summary-v1.0.0.md
|
||||
└── summary-v1.1.0.md
|
||||
```
|
||||
|
||||
## Versioning Strategy
|
||||
|
||||
### Semantic Versioning
|
||||
|
||||
- **1.0.0**: Initial cycle
|
||||
- **1.1.0**: User extends with new requirement
|
||||
- **1.2.0**: Another iteration with more requirements
|
||||
|
||||
### What Gets Versioned
|
||||
|
||||
✅ **Main Document File**
|
||||
- Completely rewritten each iteration
|
||||
- Auto-archived to `history/`
|
||||
- No inline version history (stays clean)
|
||||
|
||||
✅ **Changes.log (NDJSON)**
|
||||
- Append-only (never deleted)
|
||||
- Complete audit trail of all changes
|
||||
- Used to trace requirement origins
|
||||
|
||||
✅ **Historical Snapshots**
|
||||
- Auto-created in `history/` directory
|
||||
- Keep last N versions (default: 5)
|
||||
- For reference when needed
|
||||
|
||||
### Key Principle
|
||||
|
||||
> **主文档简洁清晰** ← Agent 只关注当前版本
|
||||
>
|
||||
> **完整历史记录** ← Changes.log 保留每个变更
|
||||
>
|
||||
> **版本快照归档** ← History/ 备份旧版本
|
||||
|
||||
## File Maintenance
|
||||
|
||||
### Each Agent
|
||||
|
||||
| Agent | File | Contains | Size |
|
||||
|-------|------|----------|------|
|
||||
| **RA** | requirements.md | All FR, NFR, edge cases, history summary | ~2-5KB |
|
||||
| **EP** | exploration.md + architecture.md + plan.json | Codebase exploration, architecture design, structured task list | ~5-10KB total |
|
||||
| **CD** | implementation.md | Completed tasks, files changed, decisions, tests | ~4-10KB |
|
||||
| **VAS** | summary.md | Test results, coverage, issues, recommendations | ~5-12KB |
|
||||
|
||||
### Changes.log (Shared)
|
||||
|
||||
NDJSON format - one line per change:
|
||||
|
||||
```jsonl
|
||||
{"timestamp":"2026-01-22T10:00:00+08:00","version":"1.0.0","agent":"ra","action":"create","change":"Initial requirements","iteration":1}
|
||||
{"timestamp":"2026-01-22T11:00:00+08:00","version":"1.1.0","agent":"ra","action":"update","change":"Added Google OAuth","iteration":2}
|
||||
{"timestamp":"2026-01-22T12:00:00+08:00","version":"1.2.0","agent":"ra","action":"update","change":"Added GitHub, MFA","iteration":3}
|
||||
```
|
||||
|
||||
## Accessing History
|
||||
|
||||
### Current Version
|
||||
|
||||
```bash
|
||||
# View latest requirements
|
||||
cat .workflow/.cycle/cycle-xxx.progress/ra/requirements.md
|
||||
|
||||
# Quick check - version is in header
|
||||
head -5 requirements.md # "# Requirements Specification - v1.2.0"
|
||||
```
|
||||
|
||||
### Version History
|
||||
|
||||
```bash
|
||||
# View previous version
|
||||
cat .workflow/.cycle/cycle-xxx.progress/ra/history/requirements-v1.1.0.md
|
||||
|
||||
# Audit trail - all changes
|
||||
cat .workflow/.cycle/cycle-xxx.progress/ra/changes.log | jq .
|
||||
|
||||
# Changes in specific iteration
|
||||
cat changes.log | jq 'select(.iteration==2)'
|
||||
|
||||
# Trace requirement history
|
||||
cat changes.log | jq 'select(.change | contains("OAuth"))'
|
||||
```
|
||||
|
||||
## Codex Pattern Implementation
|
||||
|
||||
### Multi-Agent Parallel
|
||||
|
||||
```javascript
|
||||
// Create 4 agents in parallel
|
||||
const agents = {
|
||||
ra: spawn_agent({ message: raRoleAndTask }),
|
||||
ep: spawn_agent({ message: epRoleAndTask }),
|
||||
cd: spawn_agent({ message: cdRoleAndTask }),
|
||||
vas: spawn_agent({ message: vasRoleAndTask })
|
||||
}
|
||||
|
||||
// Wait for all 4 in parallel
|
||||
const results = wait({ ids: [agents.ra, agents.ep, agents.cd, agents.vas] })
|
||||
```
|
||||
|
||||
### Role Path Passing
|
||||
|
||||
Each agent reads its own role definition:
|
||||
|
||||
```javascript
|
||||
spawn_agent({
|
||||
message: `
|
||||
## MANDATORY FIRST STEPS
|
||||
1. Read role: ~/.codex/agents/requirements-analyst.md
|
||||
2. Read: .workflow/project-tech.json
|
||||
3. Read: .workflow/project-guidelines.json
|
||||
|
||||
## TASK
|
||||
${taskDescription}
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
### Deep Interaction
|
||||
|
||||
Use `send_input` for iteration refinement:
|
||||
|
||||
```javascript
|
||||
// First output
|
||||
const initial = wait({ ids: [agent] })
|
||||
|
||||
// User feedback
|
||||
send_input({
|
||||
id: agent,
|
||||
message: `
|
||||
## Feedback
|
||||
|
||||
${feedback}
|
||||
|
||||
## Next Steps
|
||||
Update ${filename} based on feedback. Increment version.
|
||||
Output PHASE_RESULT when complete.
|
||||
`
|
||||
})
|
||||
|
||||
// Updated output
|
||||
const revised = wait({ ids: [agent] })
|
||||
|
||||
// Only close when done
|
||||
close_agent({ id: agent })
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Situation | Recovery |
|
||||
|-----------|----------|
|
||||
| Agent timeout | send_input requesting convergence or retry |
|
||||
| State corrupted | Rebuild from changes.log NDJSON |
|
||||
| Version mismatch | Agent checks version in state before reading |
|
||||
| Blocked dependency | Orchestrator sends updated file path |
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Let agents rewrite** - Don't maintain incremental history in main doc
|
||||
2. **Trust changes.log** - NDJSON is the source of truth for history
|
||||
3. **Archive on version bump** - Automatic, no manual versioning needed
|
||||
4. **Keep files focused** - Each file should be readable in 5 minutes
|
||||
5. **Version header always present** - Makes version obvious at a glance
|
||||
|
||||
## Integration
|
||||
|
||||
This skill works standalone or integrated with:
|
||||
- Dashboard Loop Monitor (API triggers)
|
||||
- CCW workflow system
|
||||
- Custom orchestration
|
||||
|
||||
### API Trigger
|
||||
|
||||
```bash
|
||||
POST /api/cycles/start
|
||||
{
|
||||
"task": "Implement OAuth",
|
||||
"mode": "auto"
|
||||
}
|
||||
→ Returns cycle_id
|
||||
|
||||
GET /api/cycles/{cycle_id}/status
|
||||
→ Returns agents status and progress
|
||||
```
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
```
|
||||
User Task
|
||||
↓
|
||||
Orchestrator (main coordinator)
|
||||
├─→ spawn_agent(RA)
|
||||
├─→ spawn_agent(EP)
|
||||
├─→ spawn_agent(CD)
|
||||
└─→ spawn_agent(VAS)
|
||||
↓
|
||||
wait({ ids: [all 4] })
|
||||
↓
|
||||
All write to:
|
||||
- requirements.md (v1.x.0)
|
||||
- exploration.md, architecture.md, plan.json (v1.x.0)
|
||||
- implementation.md (v1.x.0)
|
||||
- summary.md (v1.x.0)
|
||||
- changes.log (NDJSON append)
|
||||
↓
|
||||
[Automatic archival]
|
||||
- history/requirements-v1.{x-1}.0.md
|
||||
- history/plan-v1.{x-1}.0.json
|
||||
- etc...
|
||||
↓
|
||||
Orchestrator: Next iteration?
|
||||
- Yes: send_input with feedback
|
||||
- No: close_agent, report summary
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -1,28 +1,18 @@
|
||||
---
|
||||
name: Parallel Dev Cycle
|
||||
description: Multi-agent parallel development cycle with requirement analysis, exploration planning, code development, and validation. Supports continuous iteration with markdown progress documentation.
|
||||
argument-hint: TASK="<task description>" | --cycle-id=<id> [--extend="<extension>"] [--auto] [--parallel=<count>]
|
||||
name: parallel-dev-cycle
|
||||
description: Multi-agent parallel development cycle with requirement analysis, exploration planning, code development, and validation. Supports continuous iteration with markdown progress documentation. Triggers on "parallel-dev-cycle".
|
||||
allowed-tools: Task, AskUserQuestion, TodoWrite, Read, Write, Edit, Bash, Glob, Grep
|
||||
---
|
||||
|
||||
# Parallel Dev Cycle - Multi-Agent Development Workflow
|
||||
# Parallel Dev Cycle
|
||||
|
||||
Multi-agent parallel development cycle using Codex subagent pattern with four specialized workers:
|
||||
1. **Requirements Analysis & Extension** (RA) - Requirement analysis and self-enhancement
|
||||
2. **Exploration & Planning** (EP) - Exploration and planning
|
||||
2. **Exploration & Planning** (EP) - Codebase exploration and implementation planning
|
||||
3. **Code Development** (CD) - Code development with debug strategy support
|
||||
4. **Validation & Archival Summary** (VAS) - Validation and archival summary
|
||||
|
||||
Each agent **maintains one main document** (e.g., requirements.md, plan.json, implementation.md) that is completely rewritten per iteration, plus auxiliary logs (changes.log, debug-log.ndjson) that are append-only. Supports versioning, automatic archival, and complete history tracking.
|
||||
|
||||
## Arguments
|
||||
|
||||
| Arg | Required | Description |
|
||||
|-----|----------|-------------|
|
||||
| TASK | One of TASK or --cycle-id | Task description (for new cycle, mutually exclusive with --cycle-id) |
|
||||
| --cycle-id | One of TASK or --cycle-id | Existing cycle ID to continue (from API or previous session) |
|
||||
| --extend | No | Extension description (only valid with --cycle-id) |
|
||||
| --auto | No | Auto-cycle mode (run all phases sequentially) |
|
||||
| --parallel | No | Number of parallel agents (default: 4, max: 4) |
|
||||
Each agent **maintains one main document** (e.g., requirements.md, plan.json, implementation.md) that is completely rewritten per iteration, plus auxiliary logs (changes.log, debug-log.ndjson) that are append-only.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
@@ -70,93 +60,273 @@ Each agent **maintains one main document** (e.g., requirements.md, plan.json, im
|
||||
6. **File References**: Use short file paths instead of content passing
|
||||
7. **Self-Enhancement**: RA agent proactively extends requirements based on context
|
||||
|
||||
## Arguments
|
||||
|
||||
| Arg | Required | Description |
|
||||
|-----|----------|-------------|
|
||||
| TASK | One of TASK or --cycle-id | Task description (for new cycle, mutually exclusive with --cycle-id) |
|
||||
| --cycle-id | One of TASK or --cycle-id | Existing cycle ID to continue (from API or previous session) |
|
||||
| --extend | No | Extension description (only valid with --cycle-id) |
|
||||
| --auto | No | Auto-cycle mode (run all phases sequentially without user confirmation) |
|
||||
| --parallel | No | Number of parallel agents (default: 4, max: 4) |
|
||||
|
||||
## Auto Mode
|
||||
|
||||
When `--auto`: Run all phases sequentially without user confirmation between iterations. Use recommended defaults for all decisions. Automatically continue iteration loop until tests pass or max iterations reached.
|
||||
|
||||
## Execution Flow
|
||||
|
||||
```
|
||||
Input Parsing:
|
||||
└─ Parse arguments (TASK | --cycle-id + --extend)
|
||||
└─ Convert to structured context (cycleId, state, progressDir)
|
||||
|
||||
Phase 1: Session Initialization
|
||||
└─ Ref: phases/01-session-init.md
|
||||
├─ Create new cycle OR resume existing cycle
|
||||
├─ Initialize state file and directory structure
|
||||
└─ Output: cycleId, state, progressDir
|
||||
|
||||
Phase 2: Agent Execution (Parallel)
|
||||
└─ Ref: phases/02-agent-execution.md
|
||||
├─ Tasks attached: Spawn RA → Spawn EP → Spawn CD → Spawn VAS → Wait all
|
||||
├─ Spawn RA, EP, CD, VAS agents in parallel
|
||||
├─ Wait for all agents with timeout handling
|
||||
└─ Output: agentOutputs (4 agent results)
|
||||
|
||||
Phase 3: Result Aggregation & Iteration
|
||||
└─ Ref: phases/03-result-aggregation.md
|
||||
├─ Parse PHASE_RESULT from each agent
|
||||
├─ Detect issues (test failures, blockers)
|
||||
├─ Decision: Issues found AND iteration < max?
|
||||
│ ├─ Yes → Send feedback via send_input, loop back to Phase 2
|
||||
│ └─ No → Proceed to Phase 4
|
||||
└─ Output: parsedResults, iteration status
|
||||
|
||||
Phase 4: Completion & Summary
|
||||
└─ Ref: phases/04-completion-summary.md
|
||||
├─ Generate unified summary report
|
||||
├─ Update final state
|
||||
├─ Close all agents
|
||||
└─ Output: final cycle report with continuation instructions
|
||||
```
|
||||
|
||||
**Phase Reference Documents** (read on-demand when phase executes):
|
||||
|
||||
| Phase | Document | Purpose |
|
||||
|-------|----------|---------|
|
||||
| 1 | [phases/01-session-init.md](phases/01-session-init.md) | Session creation/resume and state initialization |
|
||||
| 2 | [phases/02-agent-execution.md](phases/02-agent-execution.md) | Parallel agent spawning and execution |
|
||||
| 3 | [phases/03-result-aggregation.md](phases/03-result-aggregation.md) | Result parsing, feedback generation, iteration handling |
|
||||
| 4 | [phases/04-completion-summary.md](phases/04-completion-summary.md) | Final summary generation and cleanup |
|
||||
|
||||
## Data Flow
|
||||
|
||||
```
|
||||
User Input (TASK | --cycle-id + --extend)
|
||||
↓
|
||||
[Parse Arguments]
|
||||
↓ cycleId, state, progressDir
|
||||
|
||||
Phase 1: Session Initialization
|
||||
↓ cycleId, state, progressDir (initialized/resumed)
|
||||
|
||||
Phase 2: Agent Execution
|
||||
↓ agentOutputs {ra, ep, cd, vas}
|
||||
|
||||
Phase 3: Result Aggregation
|
||||
↓ parsedResults, hasIssues, iteration count
|
||||
↓ [Loop back to Phase 2 if issues and iteration < max]
|
||||
|
||||
Phase 4: Completion & Summary
|
||||
↓ finalState, summaryReport
|
||||
|
||||
Return: cycle_id, iterations, final_state
|
||||
```
|
||||
|
||||
## Session Structure
|
||||
|
||||
```
|
||||
.workflow/.cycle/
|
||||
+-- {cycleId}.json # Master state file
|
||||
+-- {cycleId}.progress/
|
||||
+-- ra/
|
||||
| +-- requirements.md # Current version (complete rewrite)
|
||||
| +-- changes.log # NDJSON complete history (append-only)
|
||||
| └-- history/
|
||||
| +-- requirements-v1.0.0.md # Archived snapshot
|
||||
| +-- requirements-v1.1.0.md # Archived snapshot
|
||||
+-- ep/
|
||||
| +-- exploration.md # Codebase exploration report
|
||||
| +-- architecture.md # Architecture design
|
||||
| +-- plan.json # Structured task list (current version)
|
||||
| +-- changes.log # NDJSON complete history
|
||||
| └-- history/
|
||||
| +-- plan-v1.0.0.json
|
||||
| +-- plan-v1.1.0.json
|
||||
+-- cd/
|
||||
| +-- implementation.md # Current version
|
||||
| +-- debug-log.ndjson # Debug hypothesis tracking
|
||||
| +-- changes.log # NDJSON complete history
|
||||
| └-- history/
|
||||
| +-- implementation-v1.0.0.md
|
||||
| +-- implementation-v1.1.0.md
|
||||
+-- vas/
|
||||
| +-- summary.md # Current version
|
||||
| +-- changes.log # NDJSON complete history
|
||||
| └-- history/
|
||||
| +-- summary-v1.0.0.md
|
||||
| +-- summary-v1.1.0.md
|
||||
└-- coordination/
|
||||
+-- timeline.md # Execution timeline
|
||||
+-- decisions.log # Decision log
|
||||
├── {cycleId}.json # Master state file
|
||||
├── {cycleId}.progress/
|
||||
├── ra/
|
||||
│ ├── requirements.md # Current version (complete rewrite)
|
||||
│ ├── changes.log # NDJSON complete history (append-only)
|
||||
│ └── history/ # Archived snapshots
|
||||
├── ep/
|
||||
│ ├── exploration.md # Codebase exploration report
|
||||
│ ├── architecture.md # Architecture design
|
||||
│ ├── plan.json # Structured task list (current version)
|
||||
│ ├── changes.log # NDJSON complete history
|
||||
│ └── history/
|
||||
├── cd/
|
||||
│ ├── implementation.md # Current version
|
||||
│ ├── debug-log.ndjson # Debug hypothesis tracking
|
||||
│ ├── changes.log # NDJSON complete history
|
||||
│ └── history/
|
||||
├── vas/
|
||||
│ ├── summary.md # Current version
|
||||
│ ├── changes.log # NDJSON complete history
|
||||
│ └── history/
|
||||
└── coordination/
|
||||
├── timeline.md # Execution timeline
|
||||
└── decisions.log # Decision log
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
State schema is defined in [phases/state-schema.md](phases/state-schema.md). The master state file (`{cycleId}.json`) tracks:
|
||||
Master state file: `.workflow/.cycle/{cycleId}.json`
|
||||
|
||||
- Cycle metadata (id, title, status, iterations)
|
||||
- Agent states (status, output files, version)
|
||||
- Shared context (requirements, plan, changes, test results)
|
||||
- Coordination data (feedback log, decisions, blockers)
|
||||
|
||||
## Versioning Workflow
|
||||
|
||||
### Initial Version (v1.0.0)
|
||||
|
||||
```bash
|
||||
/parallel-dev-cycle TASK="Implement OAuth login"
|
||||
```json
|
||||
{
|
||||
"cycle_id": "cycle-v1-20260122T100000-abc123",
|
||||
"title": "Task title",
|
||||
"description": "Full task description",
|
||||
"status": "created | running | paused | completed | failed",
|
||||
"created_at": "ISO8601", "updated_at": "ISO8601",
|
||||
"max_iterations": 5, "current_iteration": 0,
|
||||
"agents": {
|
||||
"ra": { "status": "idle | running | completed | failed", "output_files": [] },
|
||||
"ep": { "status": "idle", "output_files": [] },
|
||||
"cd": { "status": "idle", "output_files": [] },
|
||||
"vas": { "status": "idle", "output_files": [] }
|
||||
},
|
||||
"current_phase": "init | ra | ep | cd | vas | aggregation | complete",
|
||||
"completed_phases": [],
|
||||
"requirements": null, "plan": null, "changes": [], "test_results": null,
|
||||
"coordination": { "feedback_log": [], "blockers": [] }
|
||||
}
|
||||
```
|
||||
|
||||
Generates:
|
||||
```
|
||||
requirements.md (v1.0.0)
|
||||
exploration.md (v1.0.0)
|
||||
architecture.md (v1.0.0)
|
||||
plan.json (v1.0.0)
|
||||
implementation.md (v1.0.0) - if applicable
|
||||
summary.md (v1.0.0) - if applicable
|
||||
**Recovery**: If state corrupted, rebuild from `.progress/` markdown files and changes.log.
|
||||
|
||||
## TodoWrite Pattern
|
||||
|
||||
### Phase-Level Tracking (Tasks Attached)
|
||||
|
||||
```json
|
||||
[
|
||||
{"content": "Phase 1: Session Initialization", "status": "completed"},
|
||||
{"content": "Phase 2: Agent Execution", "status": "in_progress"},
|
||||
{"content": " → Spawn RA Agent", "status": "completed"},
|
||||
{"content": " → Spawn EP Agent", "status": "completed"},
|
||||
{"content": " → Spawn CD Agent", "status": "in_progress"},
|
||||
{"content": " → Spawn VAS Agent", "status": "pending"},
|
||||
{"content": "Phase 3: Result Aggregation", "status": "pending"},
|
||||
{"content": "Phase 4: Completion & Summary", "status": "pending"}
|
||||
]
|
||||
```
|
||||
|
||||
### Iteration Versions (v1.1.0, v1.2.0)
|
||||
### Phase-Level Tracking (Collapsed)
|
||||
|
||||
```bash
|
||||
/parallel-dev-cycle --cycle-id=cycle-v1-xxx --extend="Add GitHub support"
|
||||
```json
|
||||
[
|
||||
{"content": "Phase 1: Session Initialization", "status": "completed"},
|
||||
{"content": "Phase 2: Agent Execution (4 agents completed)", "status": "completed"},
|
||||
{"content": "Phase 3: Result Aggregation", "status": "in_progress"},
|
||||
{"content": "Phase 4: Completion & Summary", "status": "pending"}
|
||||
]
|
||||
```
|
||||
|
||||
**Automatic handling**:
|
||||
1. Read current `requirements.md (v1.0.0)`
|
||||
2. Auto-archive to `history/requirements-v1.0.0.md`
|
||||
3. Recreate `requirements.md (v1.1.0)` - complete overwrite
|
||||
4. Append changes to `changes.log` (NDJSON)
|
||||
### Iteration Loop Tracking
|
||||
|
||||
## Changes.log Format (NDJSON)
|
||||
|
||||
Permanent audit log (append-only, never deleted):
|
||||
|
||||
```jsonl
|
||||
{"timestamp":"2026-01-22T10:00:00+08:00","version":"1.0.0","agent":"ra","action":"create","change":"Initial requirements","iteration":1}
|
||||
{"timestamp":"2026-01-22T11:00:00+08:00","version":"1.1.0","agent":"ra","action":"update","change":"Added Google OAuth requirement","iteration":2}
|
||||
{"timestamp":"2026-01-22T11:30:00+08:00","version":"1.0.0","agent":"ep","action":"create","change":"Initial implementation plan","iteration":1}
|
||||
```json
|
||||
[
|
||||
{"content": "Phase 1: Session Initialization", "status": "completed"},
|
||||
{"content": "Iteration 1: Agent Execution + Aggregation", "status": "completed"},
|
||||
{"content": "Iteration 2: Feedback → Re-execution → Aggregation", "status": "in_progress"},
|
||||
{"content": "Phase 4: Completion & Summary", "status": "pending"}
|
||||
]
|
||||
```
|
||||
|
||||
## Versioning
|
||||
|
||||
- **1.0.0**: Initial cycle → **1.x.0**: Each iteration (minor bump)
|
||||
- Each iteration: archive old → complete rewrite → append changes.log
|
||||
|
||||
```
|
||||
Archive: copy requirements.md → history/requirements-v1.0.0.md
|
||||
Rewrite: overwrite requirements.md with v1.1.0 (complete new content)
|
||||
Append: changes.log ← {"timestamp","version":"1.1.0","action":"update","description":"..."}
|
||||
```
|
||||
|
||||
| Agent Output | Rewrite (per iteration) | Append-only |
|
||||
|-------------|------------------------|-------------|
|
||||
| RA | requirements.md | changes.log |
|
||||
| EP | exploration.md, architecture.md, plan.json | changes.log |
|
||||
| CD | implementation.md, issues.md | changes.log, debug-log.ndjson |
|
||||
| VAS | summary.md, test-results.json | changes.log |
|
||||
|
||||
## Coordination Protocol
|
||||
|
||||
**Execution Order**: RA → EP → CD → VAS (dependency chain, all spawned in parallel but block on dependencies)
|
||||
|
||||
**Agent → Orchestrator**: Each agent outputs `PHASE_RESULT:` block:
|
||||
```
|
||||
PHASE_RESULT:
|
||||
- phase: ra | ep | cd | vas
|
||||
- status: success | failed | partial
|
||||
- files_written: [list]
|
||||
- summary: one-line summary
|
||||
- issues: []
|
||||
```
|
||||
|
||||
**Orchestrator → Agent**: Feedback via `send_input` (file refs + issue summary, never full content):
|
||||
```
|
||||
## FEEDBACK FROM [Source]
|
||||
[Issue summary with file:line references]
|
||||
## Reference
|
||||
- File: .progress/vas/test-results.json (v1.0.0)
|
||||
## Actions Required
|
||||
1. [Specific fix]
|
||||
```
|
||||
|
||||
**Rules**: Only orchestrator writes state file. Agents read state, write to own `.progress/{agent}/` directory only.
|
||||
|
||||
## Core Rules
|
||||
|
||||
1. **Start Immediately**: First action is TodoWrite initialization, then Phase 1 execution
|
||||
2. **Progressive Phase Loading**: Read phase docs ONLY when that phase is about to execute
|
||||
3. **Parse Every Output**: Extract PHASE_RESULT data from each agent for next phase
|
||||
4. **Auto-Continue**: After each phase, execute next pending phase automatically
|
||||
5. **Track Progress**: Update TodoWrite dynamically with attachment/collapse pattern
|
||||
6. **Single Writer**: Only orchestrator writes to master state file; agents report via PHASE_RESULT
|
||||
7. **File References**: Pass file paths between agents, not content
|
||||
8. **DO NOT STOP**: Continuous execution until all phases complete or max iterations reached
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error Type | Recovery |
|
||||
|------------|----------|
|
||||
| Agent timeout | send_input requesting convergence, then retry |
|
||||
| State corrupted | Rebuild from progress markdown files and changes.log |
|
||||
| Agent failed | Re-spawn agent with previous context |
|
||||
| Conflicting results | Orchestrator sends reconciliation request |
|
||||
| Missing files | RA/EP agents identify and request clarification |
|
||||
| Max iterations reached | Generate summary with remaining issues documented |
|
||||
|
||||
## Coordinator Checklist
|
||||
|
||||
### Before Each Phase
|
||||
|
||||
- [ ] Read phase reference document
|
||||
- [ ] Check current state for dependencies
|
||||
- [ ] Update TodoWrite with phase tasks
|
||||
|
||||
### After Each Phase
|
||||
|
||||
- [ ] Parse agent outputs (PHASE_RESULT)
|
||||
- [ ] Update master state file
|
||||
- [ ] Collapse TodoWrite sub-tasks
|
||||
- [ ] Determine next action (continue / iterate / complete)
|
||||
|
||||
## Reference Documents
|
||||
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| [roles/](roles/) | Agent role definitions (RA, EP, CD, VAS) |
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
@@ -172,23 +342,3 @@ Permanent audit log (append-only, never deleted):
|
||||
# Auto mode
|
||||
/parallel-dev-cycle --auto TASK="Add OAuth authentication"
|
||||
```
|
||||
|
||||
## Key Benefits
|
||||
|
||||
- **Simple**: Each agent maintains only 1 file + changes.log
|
||||
- **Efficient**: Version rewrite without complex version marking
|
||||
- **Traceable**: Complete history in `history/` and `changes.log`
|
||||
- **Fast**: Agent reads current version quickly (no history parsing needed)
|
||||
- **Auditable**: NDJSON changes.log fully traces every change
|
||||
- **Self-Enhancing**: RA agent proactively extends requirements
|
||||
- **Debug-Ready**: CD agent supports hypothesis-driven debugging
|
||||
|
||||
## Reference Documents
|
||||
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| [phases/orchestrator.md](phases/orchestrator.md) | Orchestrator logic |
|
||||
| [phases/state-schema.md](phases/state-schema.md) | State structure definition |
|
||||
| [phases/agents/](phases/agents/) | Four agent role definitions |
|
||||
| [specs/coordination-protocol.md](specs/coordination-protocol.md) | Communication protocol |
|
||||
| [specs/versioning-strategy.md](specs/versioning-strategy.md) | Version management |
|
||||
|
||||
159
.codex/skills/parallel-dev-cycle/phases/01-session-init.md
Normal file
159
.codex/skills/parallel-dev-cycle/phases/01-session-init.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# Phase 1: Session Initialization
|
||||
|
||||
Create or resume a development cycle, initialize state file and directory structure.
|
||||
|
||||
## Objective
|
||||
|
||||
- Parse user arguments (TASK, --cycle-id, --extend, --auto, --parallel)
|
||||
- Create new cycle with unique ID OR resume existing cycle
|
||||
- Initialize directory structure for all agents
|
||||
- Create master state file
|
||||
- Output: cycleId, state, progressDir
|
||||
|
||||
## Execution
|
||||
|
||||
### Step 1.1: Parse Arguments
|
||||
|
||||
```javascript
|
||||
const { cycleId: existingCycleId, task, mode = 'interactive', extension } = options
|
||||
|
||||
// Validate mutual exclusivity
|
||||
if (!existingCycleId && !task) {
|
||||
console.error('Either --cycle-id or task description is required')
|
||||
return { status: 'error', message: 'Missing cycleId or task' }
|
||||
}
|
||||
```
|
||||
|
||||
### Step 1.2: Utility Functions
|
||||
|
||||
```javascript
|
||||
const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString()
|
||||
|
||||
function readCycleState(cycleId) {
|
||||
const stateFile = `.workflow/.cycle/${cycleId}.json`
|
||||
if (!fs.existsSync(stateFile)) {
|
||||
return null
|
||||
}
|
||||
return JSON.parse(Read(stateFile))
|
||||
}
|
||||
```
|
||||
|
||||
### Step 1.3: New Cycle Creation
|
||||
|
||||
When `TASK` is provided (no `--cycle-id`):
|
||||
|
||||
```javascript
|
||||
// Generate unique cycle ID
|
||||
const timestamp = getUtc8ISOString().replace(/[-:]/g, '').split('.')[0]
|
||||
const random = Math.random().toString(36).substring(2, 10)
|
||||
const cycleId = `cycle-v1-${timestamp}-${random}`
|
||||
|
||||
console.log(`Creating new cycle: ${cycleId}`)
|
||||
```
|
||||
|
||||
#### Create Directory Structure
|
||||
|
||||
```bash
|
||||
mkdir -p .workflow/.cycle/${cycleId}.progress/{ra,ep,cd,vas,coordination}
|
||||
mkdir -p .workflow/.cycle/${cycleId}.progress/ra/history
|
||||
mkdir -p .workflow/.cycle/${cycleId}.progress/ep/history
|
||||
mkdir -p .workflow/.cycle/${cycleId}.progress/cd/history
|
||||
mkdir -p .workflow/.cycle/${cycleId}.progress/vas/history
|
||||
```
|
||||
|
||||
#### Initialize State File
|
||||
|
||||
```javascript
|
||||
function createCycleState(cycleId, taskDescription) {
|
||||
const stateFile = `.workflow/.cycle/${cycleId}.json`
|
||||
const now = getUtc8ISOString()
|
||||
|
||||
const state = {
|
||||
// Metadata
|
||||
cycle_id: cycleId,
|
||||
title: taskDescription.substring(0, 100),
|
||||
description: taskDescription,
|
||||
max_iterations: 5,
|
||||
status: 'running',
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
|
||||
// Agent tracking
|
||||
agents: {
|
||||
ra: { status: 'idle', output_files: [] },
|
||||
ep: { status: 'idle', output_files: [] },
|
||||
cd: { status: 'idle', output_files: [] },
|
||||
vas: { status: 'idle', output_files: [] }
|
||||
},
|
||||
|
||||
// Phase tracking
|
||||
current_phase: 'init',
|
||||
completed_phases: [],
|
||||
current_iteration: 0,
|
||||
|
||||
// Shared context (populated by agents)
|
||||
requirements: null,
|
||||
exploration: null,
|
||||
plan: null,
|
||||
changes: [],
|
||||
test_results: null
|
||||
}
|
||||
|
||||
Write(stateFile, JSON.stringify(state, null, 2))
|
||||
return state
|
||||
}
|
||||
```
|
||||
|
||||
### Step 1.4: Resume Existing Cycle
|
||||
|
||||
When `--cycle-id` is provided:
|
||||
|
||||
```javascript
|
||||
const cycleId = existingCycleId
|
||||
const state = readCycleState(cycleId)
|
||||
|
||||
if (!state) {
|
||||
console.error(`Cycle not found: ${cycleId}`)
|
||||
return { status: 'error', message: 'Cycle not found' }
|
||||
}
|
||||
|
||||
console.log(`Resuming cycle: ${cycleId}`)
|
||||
|
||||
// Apply extension if provided
|
||||
if (extension) {
|
||||
console.log(`Extension: ${extension}`)
|
||||
state.description += `\n\n--- ITERATION ${state.current_iteration + 1} ---\n${extension}`
|
||||
}
|
||||
```
|
||||
|
||||
### Step 1.5: Control Signal Check
|
||||
|
||||
Before proceeding, verify cycle status allows continuation:
|
||||
|
||||
```javascript
|
||||
function checkControlSignals(cycleId) {
|
||||
const state = readCycleState(cycleId)
|
||||
|
||||
switch (state?.status) {
|
||||
case 'paused':
|
||||
return { continue: false, action: 'pause_exit' }
|
||||
case 'failed':
|
||||
return { continue: false, action: 'stop_exit' }
|
||||
case 'running':
|
||||
return { continue: true, action: 'continue' }
|
||||
default:
|
||||
return { continue: false, action: 'stop_exit' }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
- **Variable**: `cycleId` - Unique cycle identifier
|
||||
- **Variable**: `state` - Initialized or resumed cycle state object
|
||||
- **Variable**: `progressDir` - `.workflow/.cycle/${cycleId}.progress`
|
||||
- **TodoWrite**: Mark Phase 1 completed, Phase 2 in_progress
|
||||
|
||||
## Next Phase
|
||||
|
||||
Return to orchestrator, then auto-continue to [Phase 2: Agent Execution](02-agent-execution.md).
|
||||
309
.codex/skills/parallel-dev-cycle/phases/02-agent-execution.md
Normal file
309
.codex/skills/parallel-dev-cycle/phases/02-agent-execution.md
Normal file
@@ -0,0 +1,309 @@
|
||||
# Phase 2: Agent Execution (Parallel)
|
||||
|
||||
Spawn four specialized agents in parallel and wait for all to complete with timeout handling.
|
||||
|
||||
## Objective
|
||||
|
||||
- Spawn RA, EP, CD, VAS agents simultaneously using Codex subagent pattern
|
||||
- Pass cycle context and role references to each agent
|
||||
- Wait for all agents with configurable timeout
|
||||
- Handle timeout with convergence request
|
||||
- Output: agentOutputs from all 4 agents
|
||||
|
||||
## Agent Role References
|
||||
|
||||
Each agent reads its detailed role definition at execution time:
|
||||
|
||||
| Agent | Role File | Main Output |
|
||||
|-------|-----------|-------------|
|
||||
| RA | [roles/requirements-analyst.md](../roles/requirements-analyst.md) | requirements.md |
|
||||
| EP | [roles/exploration-planner.md](../roles/exploration-planner.md) | exploration.md, architecture.md, plan.json |
|
||||
| CD | [roles/code-developer.md](../roles/code-developer.md) | implementation.md |
|
||||
| VAS | [roles/validation-archivist.md](../roles/validation-archivist.md) | summary.md |
|
||||
|
||||
## Execution
|
||||
|
||||
### Step 2.1: Spawn RA Agent (Requirements Analyst)
|
||||
|
||||
```javascript
|
||||
function spawnRAAgent(cycleId, state, progressDir) {
|
||||
return spawn_agent({
|
||||
message: `
|
||||
## TASK ASSIGNMENT
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
1. **Read role definition**: ~/.codex/agents/requirements-analyst.md
|
||||
2. Read: .workflow/project-tech.json (if exists)
|
||||
3. Read: .workflow/project-guidelines.json (if exists)
|
||||
4. Read: .workflow/.cycle/${cycleId}.progress/coordination/feedback.md (if exists)
|
||||
|
||||
---
|
||||
|
||||
## CYCLE CONTEXT
|
||||
|
||||
- **Cycle ID**: ${cycleId}
|
||||
- **Progress Dir**: ${progressDir}/ra/
|
||||
- **Current Iteration**: ${state.current_iteration}
|
||||
- **Task Description**: ${state.description}
|
||||
|
||||
## CURRENT REQUIREMENTS STATE
|
||||
|
||||
${state.requirements ? JSON.stringify(state.requirements, null, 2) : 'No previous requirements'}
|
||||
|
||||
## YOUR ROLE
|
||||
|
||||
Requirements Analyst - Analyze and refine requirements throughout the cycle.
|
||||
|
||||
## RESPONSIBILITIES
|
||||
|
||||
1. Analyze initial task description
|
||||
2. Generate comprehensive requirements specification
|
||||
3. Identify edge cases and implicit requirements
|
||||
4. Track requirement changes across iterations
|
||||
5. Maintain requirements.md and changes.log
|
||||
|
||||
## DELIVERABLES
|
||||
|
||||
Write files to ${progressDir}/ra/:
|
||||
- requirements.md: Full requirements specification
|
||||
- edge-cases.md: Edge case analysis
|
||||
- changes.log: NDJSON format change tracking
|
||||
|
||||
## OUTPUT FORMAT
|
||||
|
||||
\`\`\`
|
||||
PHASE_RESULT:
|
||||
- phase: ra
|
||||
- status: success | failed
|
||||
- files_written: [list]
|
||||
- summary: one-line summary
|
||||
- issues: []
|
||||
\`\`\`
|
||||
`
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2.2: Spawn EP Agent (Exploration & Planning)
|
||||
|
||||
```javascript
|
||||
function spawnEPAgent(cycleId, state, progressDir) {
|
||||
return spawn_agent({
|
||||
message: `
|
||||
## TASK ASSIGNMENT
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
1. **Read role definition**: ~/.codex/agents/exploration-planner.md
|
||||
2. Read: .workflow/project-tech.json
|
||||
3. Read: .workflow/project-guidelines.json
|
||||
4. Read: ${progressDir}/ra/requirements.md
|
||||
|
||||
---
|
||||
|
||||
## CYCLE CONTEXT
|
||||
|
||||
- **Cycle ID**: ${cycleId}
|
||||
- **Progress Dir**: ${progressDir}/ep/
|
||||
- **Requirements**: See requirements.md
|
||||
- **Current Plan**: ${state.plan ? 'Existing' : 'None - first iteration'}
|
||||
|
||||
## YOUR ROLE
|
||||
|
||||
Exploration & Planning Agent - Explore architecture and generate implementation plan.
|
||||
|
||||
## RESPONSIBILITIES
|
||||
|
||||
1. Explore codebase architecture
|
||||
2. Map integration points
|
||||
3. Design implementation approach
|
||||
4. Generate plan.json with task breakdown
|
||||
5. Update or iterate on existing plan
|
||||
|
||||
## DELIVERABLES
|
||||
|
||||
Write files to ${progressDir}/ep/:
|
||||
- exploration.md: Codebase exploration findings
|
||||
- architecture.md: Architecture design
|
||||
- plan.json: Implementation plan (structured)
|
||||
|
||||
## OUTPUT FORMAT
|
||||
|
||||
\`\`\`
|
||||
PHASE_RESULT:
|
||||
- phase: ep
|
||||
- status: success | failed
|
||||
- files_written: [list]
|
||||
- summary: one-line summary
|
||||
- plan_version: X.Y.Z
|
||||
\`\`\`
|
||||
`
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2.3: Spawn CD Agent (Code Developer)
|
||||
|
||||
```javascript
|
||||
function spawnCDAgent(cycleId, state, progressDir) {
|
||||
return spawn_agent({
|
||||
message: `
|
||||
## TASK ASSIGNMENT
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
1. **Read role definition**: ~/.codex/agents/code-developer.md
|
||||
2. Read: ${progressDir}/ep/plan.json
|
||||
3. Read: ${progressDir}/ra/requirements.md
|
||||
|
||||
---
|
||||
|
||||
## CYCLE CONTEXT
|
||||
|
||||
- **Cycle ID**: ${cycleId}
|
||||
- **Progress Dir**: ${progressDir}/cd/
|
||||
- **Plan Version**: ${state.plan?.version || 'N/A'}
|
||||
- **Previous Changes**: ${state.changes?.length || 0} files
|
||||
|
||||
## YOUR ROLE
|
||||
|
||||
Code Developer - Implement features based on plan and requirements.
|
||||
|
||||
## RESPONSIBILITIES
|
||||
|
||||
1. Implement features from plan
|
||||
2. Track code changes
|
||||
3. Handle integration issues
|
||||
4. Maintain code quality
|
||||
5. Report implementation progress and issues
|
||||
|
||||
## DELIVERABLES
|
||||
|
||||
Write files to ${progressDir}/cd/:
|
||||
- implementation.md: Implementation progress and decisions
|
||||
- changes.log: NDJSON format, each line: {file, action, timestamp}
|
||||
- issues.md: Development issues and blockers
|
||||
|
||||
## OUTPUT FORMAT
|
||||
|
||||
\`\`\`
|
||||
PHASE_RESULT:
|
||||
- phase: cd
|
||||
- status: success | failed | partial
|
||||
- files_changed: [count]
|
||||
- summary: one-line summary
|
||||
- blockers: []
|
||||
\`\`\`
|
||||
`
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2.4: Spawn VAS Agent (Validation & Archival)
|
||||
|
||||
```javascript
|
||||
function spawnVASAgent(cycleId, state, progressDir) {
|
||||
return spawn_agent({
|
||||
message: `
|
||||
## TASK ASSIGNMENT
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
1. **Read role definition**: ~/.codex/agents/validation-archivist.md
|
||||
2. Read: ${progressDir}/cd/changes.log
|
||||
|
||||
---
|
||||
|
||||
## CYCLE CONTEXT
|
||||
|
||||
- **Cycle ID**: ${cycleId}
|
||||
- **Progress Dir**: ${progressDir}/vas/
|
||||
- **Changes Count**: ${state.changes?.length || 0}
|
||||
- **Iteration**: ${state.current_iteration}
|
||||
|
||||
## YOUR ROLE
|
||||
|
||||
Validation & Archival Specialist - Validate quality and create documentation.
|
||||
|
||||
## RESPONSIBILITIES
|
||||
|
||||
1. Run tests on implemented features
|
||||
2. Generate coverage reports
|
||||
3. Create archival documentation
|
||||
4. Summarize cycle results
|
||||
5. Generate version history
|
||||
|
||||
## DELIVERABLES
|
||||
|
||||
Write files to ${progressDir}/vas/:
|
||||
- validation.md: Test validation results
|
||||
- test-results.json: Detailed test results
|
||||
- coverage.md: Coverage report
|
||||
- summary.md: Cycle summary and recommendations
|
||||
|
||||
## OUTPUT FORMAT
|
||||
|
||||
\`\`\`
|
||||
PHASE_RESULT:
|
||||
- phase: vas
|
||||
- status: success | failed
|
||||
- test_pass_rate: X%
|
||||
- coverage: X%
|
||||
- issues: []
|
||||
\`\`\`
|
||||
`
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2.5: Launch All Agents & Wait
|
||||
|
||||
```javascript
|
||||
// Spawn all 4 agents in parallel
|
||||
console.log('Spawning agents...')
|
||||
|
||||
const agents = {
|
||||
ra: spawnRAAgent(cycleId, state, progressDir),
|
||||
ep: spawnEPAgent(cycleId, state, progressDir),
|
||||
cd: spawnCDAgent(cycleId, state, progressDir),
|
||||
vas: spawnVASAgent(cycleId, state, progressDir)
|
||||
}
|
||||
|
||||
// Wait for all agents to complete
|
||||
console.log('Waiting for all agents...')
|
||||
const results = wait({
|
||||
ids: [agents.ra, agents.ep, agents.cd, agents.vas],
|
||||
timeout_ms: 1800000 // 30 minutes
|
||||
})
|
||||
```
|
||||
|
||||
### Step 2.6: Timeout Handling
|
||||
|
||||
```javascript
|
||||
if (results.timed_out) {
|
||||
console.log('Some agents timed out, sending convergence request...')
|
||||
Object.entries(agents).forEach(([name, id]) => {
|
||||
if (!results.status[id].completed) {
|
||||
send_input({
|
||||
id: id,
|
||||
message: `
|
||||
## TIMEOUT NOTIFICATION
|
||||
|
||||
Execution timeout reached. Please:
|
||||
1. Output current progress to markdown file
|
||||
2. Save all state updates
|
||||
3. Return completion status
|
||||
`
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
- **Variable**: `agents` - Map of agent names to agent IDs
|
||||
- **Variable**: `results` - Wait results with completion status for each agent
|
||||
- **Variable**: `agentOutputs` - Collected outputs from all 4 agents
|
||||
- **TodoWrite**: Mark Phase 2 completed, Phase 3 in_progress
|
||||
|
||||
## Next Phase
|
||||
|
||||
Return to orchestrator, then auto-continue to [Phase 3: Result Aggregation & Iteration](03-result-aggregation.md).
|
||||
230
.codex/skills/parallel-dev-cycle/phases/03-result-aggregation.md
Normal file
230
.codex/skills/parallel-dev-cycle/phases/03-result-aggregation.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# Phase 3: Result Aggregation & Iteration
|
||||
|
||||
Parse agent outputs, detect issues, generate feedback, and manage the iteration loop.
|
||||
|
||||
## Objective
|
||||
|
||||
- Parse PHASE_RESULT from each agent's output
|
||||
- Aggregate results into unified state
|
||||
- Detect issues (test failures, blockers)
|
||||
- Generate targeted feedback for affected agents
|
||||
- Manage iteration loop (continue or proceed to completion)
|
||||
- Output: parsedResults, iteration decision
|
||||
|
||||
## Execution
|
||||
|
||||
### Step 3.1: Collect Agent Outputs
|
||||
|
||||
```javascript
|
||||
// Collect outputs from all 4 agents
|
||||
const agentOutputs = {
|
||||
ra: results.status[agents.ra].completed,
|
||||
ep: results.status[agents.ep].completed,
|
||||
cd: results.status[agents.cd].completed,
|
||||
vas: results.status[agents.vas].completed
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3.2: Parse PHASE_RESULT
|
||||
|
||||
Each agent outputs a structured PHASE_RESULT block. Parse it to extract status and data:
|
||||
|
||||
```javascript
|
||||
function parseAgentOutputs(agentOutputs) {
|
||||
const results = {
|
||||
ra: parseOutput(agentOutputs.ra, 'ra'),
|
||||
ep: parseOutput(agentOutputs.ep, 'ep'),
|
||||
cd: parseOutput(agentOutputs.cd, 'cd'),
|
||||
vas: parseOutput(agentOutputs.vas, 'vas')
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
function parseOutput(output, agent) {
|
||||
const result = {
|
||||
agent: agent,
|
||||
status: 'unknown',
|
||||
data: {}
|
||||
}
|
||||
|
||||
// Parse PHASE_RESULT block
|
||||
const match = output.match(/PHASE_RESULT:\s*([\s\S]*?)(?:\n\n|$)/)
|
||||
if (match) {
|
||||
const lines = match[1].split('\n')
|
||||
for (const line of lines) {
|
||||
const m = line.match(/^-\s*(\w+):\s*(.+)$/)
|
||||
if (m) {
|
||||
result[m[1]] = m[2].trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3.3: Update State with Results
|
||||
|
||||
```javascript
|
||||
// Update agent states
|
||||
state.agents.ra.status = 'completed'
|
||||
state.agents.ep.status = 'completed'
|
||||
state.agents.cd.status = 'completed'
|
||||
state.agents.vas.status = 'completed'
|
||||
|
||||
// Update shared context from parsed results
|
||||
state.requirements = parsedResults.ra.requirements
|
||||
state.exploration = parsedResults.ep.exploration
|
||||
state.plan = parsedResults.ep.plan
|
||||
state.changes = parsedResults.cd.changes
|
||||
state.test_results = parsedResults.vas.test_results
|
||||
|
||||
state.completed_phases.push(...['ra', 'ep', 'cd', 'vas'])
|
||||
state.updated_at = getUtc8ISOString()
|
||||
|
||||
// Persist state
|
||||
Write(`.workflow/.cycle/${cycleId}.json`, JSON.stringify(state, null, 2))
|
||||
```
|
||||
|
||||
### Step 3.4: Issue Detection
|
||||
|
||||
```javascript
|
||||
const hasIssues = parsedResults.vas.test_results?.passed === false ||
|
||||
parsedResults.cd.issues?.length > 0
|
||||
|
||||
if (hasIssues && iteration < maxIterations) {
|
||||
console.log('Issues detected, preparing for next iteration...')
|
||||
// → Proceed to Step 3.5 (Feedback Generation)
|
||||
} else if (!hasIssues) {
|
||||
console.log('All phases completed successfully')
|
||||
// → Proceed to Phase 4
|
||||
} else if (iteration >= maxIterations) {
|
||||
console.log(`Reached maximum iterations (${maxIterations})`)
|
||||
// → Proceed to Phase 4 with issues documented
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3.5: Feedback Generation
|
||||
|
||||
Generate targeted feedback based on issue type:
|
||||
|
||||
```javascript
|
||||
function generateFeedback(parsedResults) {
|
||||
const feedback = {}
|
||||
|
||||
// Check VAS results → feedback to CD
|
||||
if (parsedResults.vas.test_pass_rate < 100) {
|
||||
feedback.cd = `
|
||||
## FEEDBACK FROM VALIDATION
|
||||
|
||||
Test pass rate: ${parsedResults.vas.test_pass_rate}%
|
||||
|
||||
## ISSUES TO FIX
|
||||
|
||||
${parsedResults.vas.data.issues || 'See test-results.json for details'}
|
||||
|
||||
## NEXT STEP
|
||||
|
||||
Fix failing tests and update implementation.md with resolution.
|
||||
`
|
||||
}
|
||||
|
||||
// Check CD blockers → feedback to RA
|
||||
if (parsedResults.cd.blockers?.length > 0) {
|
||||
feedback.ra = `
|
||||
## FEEDBACK FROM DEVELOPMENT
|
||||
|
||||
Blockers encountered:
|
||||
${parsedResults.cd.blockers.map(b => `- ${b}`).join('\n')}
|
||||
|
||||
## NEXT STEP
|
||||
|
||||
Clarify requirements or identify alternative approaches.
|
||||
Update requirements.md if needed.
|
||||
`
|
||||
}
|
||||
|
||||
return feedback
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3.6: Send Feedback via send_input
|
||||
|
||||
```javascript
|
||||
const feedback = generateFeedback(parsedResults)
|
||||
|
||||
// Send feedback to relevant agents
|
||||
if (feedback.ra) {
|
||||
send_input({
|
||||
id: agents.ra,
|
||||
message: feedback.ra
|
||||
})
|
||||
}
|
||||
|
||||
if (feedback.cd) {
|
||||
send_input({
|
||||
id: agents.cd,
|
||||
message: feedback.cd
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for agents to process feedback and update
|
||||
const updatedResults = wait({
|
||||
ids: [agents.ra, agents.cd].filter(Boolean),
|
||||
timeout_ms: 900000 // 15 minutes for fixes
|
||||
})
|
||||
|
||||
console.log('Agents updated, continuing...')
|
||||
```
|
||||
|
||||
### Step 3.7: Iteration Loop Decision
|
||||
|
||||
```javascript
|
||||
// After feedback processing, decide next action:
|
||||
//
|
||||
// Option A: Issues remain AND iteration < max
|
||||
// → Loop back to Phase 2 (re-spawn or continue agents)
|
||||
//
|
||||
// Option B: No issues remaining
|
||||
// → Proceed to Phase 4 (Completion)
|
||||
//
|
||||
// Option C: Max iterations reached
|
||||
// → Proceed to Phase 4 with issues documented
|
||||
|
||||
if (hasIssues && iteration < maxIterations) {
|
||||
// Continue iteration loop
|
||||
iteration++
|
||||
state.current_iteration = iteration
|
||||
// → Back to Phase 2
|
||||
} else {
|
||||
// Exit loop → Phase 4
|
||||
continueLoop = false
|
||||
}
|
||||
```
|
||||
|
||||
## Iteration Flow Diagram
|
||||
|
||||
```
|
||||
Phase 2: Agent Execution
|
||||
↓
|
||||
Phase 3: Result Aggregation
|
||||
↓
|
||||
┌─ Issues detected?
|
||||
│ ├─ No → Phase 4 (Complete)
|
||||
│ └─ Yes
|
||||
│ ├─ iteration < max?
|
||||
│ │ ├─ Yes → Generate feedback → send_input → Wait → Back to Phase 2
|
||||
│ │ └─ No → Phase 4 (Complete with issues)
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
- **Variable**: `parsedResults` - Parsed results from all 4 agents
|
||||
- **Variable**: `hasIssues` - Boolean indicating if issues were found
|
||||
- **Variable**: `continueLoop` - Boolean indicating if iteration should continue
|
||||
- **TodoWrite**: Mark Phase 3 completed, Phase 4 in_progress (or loop)
|
||||
|
||||
## Next Phase
|
||||
|
||||
If iteration continues: Return to Phase 2.
|
||||
If iteration completes: Return to orchestrator, then auto-continue to [Phase 4: Completion & Summary](04-completion-summary.md).
|
||||
109
.codex/skills/parallel-dev-cycle/phases/04-completion-summary.md
Normal file
109
.codex/skills/parallel-dev-cycle/phases/04-completion-summary.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Phase 4: Completion & Summary
|
||||
|
||||
Generate unified summary report, update final state, close all agents, and provide continuation instructions.
|
||||
|
||||
## Objective
|
||||
|
||||
- Generate comprehensive cycle summary report
|
||||
- Update master state file with final status
|
||||
- Close all agent sessions
|
||||
- Provide continuation instructions for future iterations
|
||||
- Output: final cycle report
|
||||
|
||||
## Execution
|
||||
|
||||
### Step 4.1: Generate Final Summary
|
||||
|
||||
```javascript
|
||||
function generateFinalSummary(cycleId, state) {
|
||||
const summaryFile = `.workflow/.cycle/${cycleId}.progress/coordination/summary.md`
|
||||
|
||||
const summary = `# Cycle Summary - ${cycleId}
|
||||
|
||||
## Metadata
|
||||
- Cycle ID: ${cycleId}
|
||||
- Started: ${state.created_at}
|
||||
- Completed: ${state.completed_at}
|
||||
- Iterations: ${state.current_iteration}
|
||||
- Status: ${state.status}
|
||||
|
||||
## Phase Results
|
||||
- Requirements Analysis: ✓ Completed
|
||||
- Exploration & Planning: ✓ Completed
|
||||
- Code Development: ✓ Completed
|
||||
- Validation & Archival: ✓ Completed
|
||||
|
||||
## Key Deliverables
|
||||
- Requirements: ${state.requirements ? '✓' : '✗'}
|
||||
- Architecture Plan: ${state.plan ? '✓' : '✗'}
|
||||
- Code Changes: ${state.changes?.length || 0} files
|
||||
- Test Results: ${state.test_results?.pass_rate || '0'}% passing
|
||||
|
||||
## Generated Files
|
||||
- .workflow/.cycle/${cycleId}.progress/ra/requirements.md
|
||||
- .workflow/.cycle/${cycleId}.progress/ep/plan.json
|
||||
- .workflow/.cycle/${cycleId}.progress/cd/changes.log
|
||||
- .workflow/.cycle/${cycleId}.progress/vas/summary.md
|
||||
|
||||
## Continuation Instructions
|
||||
|
||||
To extend this cycle:
|
||||
|
||||
\`\`\`bash
|
||||
/parallel-dev-cycle --cycle-id=${cycleId} --extend="New requirement or feedback"
|
||||
\`\`\`
|
||||
|
||||
This will spawn agents for iteration ${state.current_iteration + 1}.
|
||||
`
|
||||
|
||||
Write(summaryFile, summary)
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4.2: Update Final State
|
||||
|
||||
```javascript
|
||||
state.status = 'completed'
|
||||
state.completed_at = getUtc8ISOString()
|
||||
Write(`.workflow/.cycle/${cycleId}.json`, JSON.stringify(state, null, 2))
|
||||
```
|
||||
|
||||
### Step 4.3: Close All Agents
|
||||
|
||||
```javascript
|
||||
Object.values(agents).forEach(id => {
|
||||
try {
|
||||
close_agent({ id })
|
||||
} catch (e) {
|
||||
console.warn(`Failed to close agent ${id}`)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Step 4.4: Return Result
|
||||
|
||||
```javascript
|
||||
console.log('\n=== Parallel Dev Cycle Orchestrator Finished ===')
|
||||
|
||||
return {
|
||||
status: 'completed',
|
||||
cycle_id: cycleId,
|
||||
iterations: iteration,
|
||||
final_state: state
|
||||
}
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
- **File**: `.workflow/.cycle/{cycleId}.progress/coordination/summary.md`
|
||||
- **File**: `.workflow/.cycle/{cycleId}.json` (final state)
|
||||
- **TodoWrite**: Mark Phase 4 completed (all tasks done)
|
||||
|
||||
## Completion
|
||||
|
||||
Parallel Dev Cycle has completed. The cycle report is at `.workflow/.cycle/{cycleId}.progress/coordination/summary.md`.
|
||||
|
||||
To continue iterating:
|
||||
```bash
|
||||
/parallel-dev-cycle --cycle-id={cycleId} --extend="Additional requirements or feedback"
|
||||
```
|
||||
@@ -1,696 +0,0 @@
|
||||
# Orchestrator - Multi-Agent Coordination (Codex Pattern)
|
||||
|
||||
Orchestrate parallel dev cycle using Codex subagent pattern with continuous iteration support.
|
||||
|
||||
## Role
|
||||
|
||||
Coordinate four specialized agents → Manage state → Support continuous iteration → Generate unified documentation.
|
||||
|
||||
## Codex Pattern Overview
|
||||
|
||||
```
|
||||
Main Orchestrator Flow:
|
||||
|
||||
┌─── spawn_agent (orchestrator role) ────────────────────────────┐
|
||||
│ │
|
||||
│ Phase 1: INIT (Check control signals) │
|
||||
│ ↓ │
|
||||
│ wait() → Parse cycle state │
|
||||
│ ↓ │
|
||||
│ Phase 2: AGENT ORCHESTRATION │
|
||||
│ ↓ │
|
||||
│ spawn_agent(RA) | spawn_agent(EP) │
|
||||
│ spawn_agent(CD) | spawn_agent(VAS) │
|
||||
│ ↓ │
|
||||
│ wait({ ids: [RA, EP, CD, VAS] }) → Collect all results │
|
||||
│ ↓ │
|
||||
│ Phase 3: ITERATION HANDLING │
|
||||
│ ↓ │
|
||||
│ [If extension needed] │
|
||||
│ send_input to affected agents │
|
||||
│ wait() for updated results │
|
||||
│ ↓ │
|
||||
│ Phase 4: AGGREGATION │
|
||||
│ ↓ │
|
||||
│ Merge all outputs → Generate unified documentation │
|
||||
│ ↓ │
|
||||
│ Update cycle state │
|
||||
│ ↓ │
|
||||
│ [Loop if more iterations] │
|
||||
│ ↓ │
|
||||
│ close_agent() when complete │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
### Read Cycle State
|
||||
|
||||
```javascript
|
||||
const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString()
|
||||
|
||||
function readCycleState(cycleId) {
|
||||
const stateFile = `.workflow/.cycle/${cycleId}.json`
|
||||
if (!fs.existsSync(stateFile)) {
|
||||
return null
|
||||
}
|
||||
return JSON.parse(Read(stateFile))
|
||||
}
|
||||
```
|
||||
|
||||
### Create New Cycle State
|
||||
|
||||
```javascript
|
||||
function createCycleState(cycleId, taskDescription) {
|
||||
const stateFile = `.workflow/.cycle/${cycleId}.json`
|
||||
const now = getUtc8ISOString()
|
||||
|
||||
const state = {
|
||||
// Metadata
|
||||
cycle_id: cycleId,
|
||||
title: taskDescription.substring(0, 100),
|
||||
description: taskDescription,
|
||||
max_iterations: 5,
|
||||
status: 'running',
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
|
||||
// Agent tracking
|
||||
agents: {
|
||||
ra: { status: 'idle', output_files: [] },
|
||||
ep: { status: 'idle', output_files: [] },
|
||||
cd: { status: 'idle', output_files: [] },
|
||||
vas: { status: 'idle', output_files: [] }
|
||||
},
|
||||
|
||||
// Phase tracking
|
||||
current_phase: 'init',
|
||||
completed_phases: [],
|
||||
current_iteration: 0,
|
||||
|
||||
// Shared context (populated by agents)
|
||||
requirements: null,
|
||||
exploration: null,
|
||||
plan: null,
|
||||
changes: [],
|
||||
test_results: null
|
||||
}
|
||||
|
||||
// Create directories
|
||||
mkdir -p `.workflow/.cycle/${cycleId}.progress/{ra,ep,cd,vas,coordination}`
|
||||
|
||||
Write(stateFile, JSON.stringify(state, null, 2))
|
||||
return state
|
||||
}
|
||||
```
|
||||
|
||||
## Main Execution Flow (Codex Subagent)
|
||||
|
||||
```javascript
|
||||
async function runOrchestrator(options = {}) {
|
||||
const { cycleId: existingCycleId, task, mode = 'interactive', extension } = options
|
||||
|
||||
console.log('=== Parallel Dev Cycle Orchestrator Started ===')
|
||||
|
||||
// 1. Determine cycleId and initial state
|
||||
let cycleId
|
||||
let state
|
||||
|
||||
if (existingCycleId) {
|
||||
// Continue existing cycle
|
||||
cycleId = existingCycleId
|
||||
state = readCycleState(cycleId)
|
||||
|
||||
if (!state) {
|
||||
console.error(`Cycle not found: ${cycleId}`)
|
||||
return { status: 'error', message: 'Cycle not found' }
|
||||
}
|
||||
|
||||
console.log(`Resuming cycle: ${cycleId}`)
|
||||
if (extension) {
|
||||
console.log(`Extension: ${extension}`)
|
||||
state.description += `\n\n--- ITERATION ${state.current_iteration + 1} ---\n${extension}`
|
||||
}
|
||||
|
||||
} else if (task) {
|
||||
// Create new cycle
|
||||
const timestamp = getUtc8ISOString().replace(/[-:]/g, '').split('.')[0]
|
||||
const random = Math.random().toString(36).substring(2, 10)
|
||||
cycleId = `cycle-v1-${timestamp}-${random}`
|
||||
|
||||
console.log(`Creating new cycle: ${cycleId}`)
|
||||
state = createCycleState(cycleId, task)
|
||||
|
||||
} else {
|
||||
console.error('Either --cycle-id or task description is required')
|
||||
return { status: 'error', message: 'Missing cycleId or task' }
|
||||
}
|
||||
|
||||
const progressDir = `.workflow/.cycle/${cycleId}.progress`
|
||||
|
||||
// 2. Main orchestration loop
|
||||
let iteration = state.current_iteration || 0
|
||||
const maxIterations = state.max_iterations || 5
|
||||
let continueLoop = true
|
||||
|
||||
while (continueLoop && iteration < maxIterations) {
|
||||
iteration++
|
||||
state.current_iteration = iteration
|
||||
|
||||
console.log(`\n========== ITERATION ${iteration} ==========`)
|
||||
|
||||
// 3. Spawn four agents in parallel
|
||||
console.log('Spawning agents...')
|
||||
|
||||
const agents = {
|
||||
ra: spawnRAAgent(cycleId, state, progressDir),
|
||||
ep: spawnEPAgent(cycleId, state, progressDir),
|
||||
cd: spawnCDAgent(cycleId, state, progressDir),
|
||||
vas: spawnVASAgent(cycleId, state, progressDir)
|
||||
}
|
||||
|
||||
// 4. Wait for all agents to complete
|
||||
console.log('Waiting for all agents...')
|
||||
const results = wait({
|
||||
ids: [agents.ra, agents.ep, agents.cd, agents.vas],
|
||||
timeout_ms: 1800000 // 30 minutes
|
||||
})
|
||||
|
||||
if (results.timed_out) {
|
||||
console.log('Some agents timed out, sending convergence request...')
|
||||
Object.entries(agents).forEach(([name, id]) => {
|
||||
if (!results.status[id].completed) {
|
||||
send_input({
|
||||
id: id,
|
||||
message: `
|
||||
## TIMEOUT NOTIFICATION
|
||||
|
||||
Execution timeout reached. Please:
|
||||
1. Output current progress to markdown file
|
||||
2. Save all state updates
|
||||
3. Return completion status
|
||||
`
|
||||
})
|
||||
}
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// 5. Collect all agent outputs
|
||||
const agentOutputs = {
|
||||
ra: results.status[agents.ra].completed,
|
||||
ep: results.status[agents.ep].completed,
|
||||
cd: results.status[agents.cd].completed,
|
||||
vas: results.status[agents.vas].completed
|
||||
}
|
||||
|
||||
// 6. Parse and aggregate results
|
||||
const parsedResults = parseAgentOutputs(agentOutputs)
|
||||
|
||||
// Update state with agent results
|
||||
state.agents.ra.status = 'completed'
|
||||
state.agents.ep.status = 'completed'
|
||||
state.agents.cd.status = 'completed'
|
||||
state.agents.vas.status = 'completed'
|
||||
|
||||
state.requirements = parsedResults.ra.requirements
|
||||
state.exploration = parsedResults.ep.exploration
|
||||
state.plan = parsedResults.ep.plan
|
||||
state.changes = parsedResults.cd.changes
|
||||
state.test_results = parsedResults.vas.test_results
|
||||
|
||||
state.completed_phases.push(...['ra', 'ep', 'cd', 'vas'])
|
||||
state.updated_at = getUtc8ISOString()
|
||||
|
||||
// Save state
|
||||
Write(`.workflow/.cycle/${cycleId}.json`, JSON.stringify(state, null, 2))
|
||||
|
||||
// 7. Check for issues and determine next iteration
|
||||
const hasIssues = parsedResults.vas.test_results?.passed === false ||
|
||||
parsedResults.cd.issues?.length > 0
|
||||
|
||||
if (hasIssues && iteration < maxIterations) {
|
||||
console.log('Issues detected, preparing for next iteration...')
|
||||
|
||||
// Generate feedback for agents
|
||||
const feedback = generateFeedback(parsedResults)
|
||||
|
||||
// Send feedback to relevant agents
|
||||
if (feedback.ra) {
|
||||
send_input({
|
||||
id: agents.ra,
|
||||
message: feedback.ra
|
||||
})
|
||||
}
|
||||
|
||||
if (feedback.cd) {
|
||||
send_input({
|
||||
id: agents.cd,
|
||||
message: feedback.cd
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for updates
|
||||
const updatedResults = wait({
|
||||
ids: [agents.ra, agents.cd].filter(Boolean),
|
||||
timeout_ms: 900000
|
||||
})
|
||||
|
||||
console.log('Agents updated, continuing...')
|
||||
|
||||
} else if (!hasIssues) {
|
||||
console.log('All phases completed successfully')
|
||||
continueLoop = false
|
||||
|
||||
} else if (iteration >= maxIterations) {
|
||||
console.log(`Reached maximum iterations (${maxIterations})`)
|
||||
continueLoop = false
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Generate unified summary
|
||||
console.log('Generating final summary...')
|
||||
generateFinalSummary(cycleId, state)
|
||||
|
||||
// 9. Update final state
|
||||
state.status = 'completed'
|
||||
state.completed_at = getUtc8ISOString()
|
||||
Write(`.workflow/.cycle/${cycleId}.json`, JSON.stringify(state, null, 2))
|
||||
|
||||
// 10. Cleanup
|
||||
Object.values(agents).forEach(id => {
|
||||
try {
|
||||
close_agent({ id })
|
||||
} catch (e) {
|
||||
console.warn(`Failed to close agent ${id}`)
|
||||
}
|
||||
})
|
||||
|
||||
console.log('\n=== Parallel Dev Cycle Orchestrator Finished ===')
|
||||
|
||||
return {
|
||||
status: 'completed',
|
||||
cycle_id: cycleId,
|
||||
iterations: iteration,
|
||||
final_state: state
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Agent Spawning Functions
|
||||
|
||||
### Spawn RA Agent
|
||||
|
||||
```javascript
|
||||
function spawnRAAgent(cycleId, state, progressDir) {
|
||||
return spawn_agent({
|
||||
message: `
|
||||
## TASK ASSIGNMENT
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
1. **Read role definition**: ~/.codex/agents/requirements-analyst.md
|
||||
2. Read: .workflow/project-tech.json (if exists)
|
||||
3. Read: .workflow/project-guidelines.json (if exists)
|
||||
4. Read: .workflow/.cycle/${cycleId}.progress/coordination/feedback.md (if exists)
|
||||
|
||||
---
|
||||
|
||||
## CYCLE CONTEXT
|
||||
|
||||
- **Cycle ID**: ${cycleId}
|
||||
- **Progress Dir**: ${progressDir}/ra/
|
||||
- **Current Iteration**: ${state.current_iteration}
|
||||
- **Task Description**: ${state.description}
|
||||
|
||||
## CURRENT REQUIREMENTS STATE
|
||||
|
||||
${state.requirements ? JSON.stringify(state.requirements, null, 2) : 'No previous requirements'}
|
||||
|
||||
## YOUR ROLE
|
||||
|
||||
Requirements Analyst - Analyze and refine requirements throughout the cycle.
|
||||
|
||||
## RESPONSIBILITIES
|
||||
|
||||
1. Analyze initial task description
|
||||
2. Generate comprehensive requirements specification
|
||||
3. Identify edge cases and implicit requirements
|
||||
4. Track requirement changes across iterations
|
||||
5. Maintain requirements.md and changes.log
|
||||
|
||||
## DELIVERABLES
|
||||
|
||||
Write files to ${progressDir}/ra/:
|
||||
- requirements.md: Full requirements specification
|
||||
- edge-cases.md: Edge case analysis
|
||||
- changes.log: NDJSON format change tracking
|
||||
|
||||
## OUTPUT FORMAT
|
||||
|
||||
\`\`\`
|
||||
PHASE_RESULT:
|
||||
- phase: ra
|
||||
- status: success | failed
|
||||
- files_written: [list]
|
||||
- summary: one-line summary
|
||||
- issues: []
|
||||
\`\`\`
|
||||
`
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Spawn EP Agent
|
||||
|
||||
```javascript
|
||||
function spawnEPAgent(cycleId, state, progressDir) {
|
||||
return spawn_agent({
|
||||
message: `
|
||||
## TASK ASSIGNMENT
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
1. **Read role definition**: ~/.codex/agents/exploration-planner.md
|
||||
2. Read: .workflow/project-tech.json
|
||||
3. Read: .workflow/project-guidelines.json
|
||||
4. Read: ${progressDir}/ra/requirements.md
|
||||
|
||||
---
|
||||
|
||||
## CYCLE CONTEXT
|
||||
|
||||
- **Cycle ID**: ${cycleId}
|
||||
- **Progress Dir**: ${progressDir}/ep/
|
||||
- **Requirements**: See requirements.md
|
||||
- **Current Plan**: ${state.plan ? 'Existing' : 'None - first iteration'}
|
||||
|
||||
## YOUR ROLE
|
||||
|
||||
Exploration & Planning Agent - Explore architecture and generate implementation plan.
|
||||
|
||||
## RESPONSIBILITIES
|
||||
|
||||
1. Explore codebase architecture
|
||||
2. Map integration points
|
||||
3. Design implementation approach
|
||||
4. Generate plan.json with task breakdown
|
||||
5. Update or iterate on existing plan
|
||||
|
||||
## DELIVERABLES
|
||||
|
||||
Write files to ${progressDir}/ep/:
|
||||
- exploration.md: Codebase exploration findings
|
||||
- architecture.md: Architecture design
|
||||
- plan.json: Implementation plan (structured)
|
||||
|
||||
## OUTPUT FORMAT
|
||||
|
||||
\`\`\`
|
||||
PHASE_RESULT:
|
||||
- phase: ep
|
||||
- status: success | failed
|
||||
- files_written: [list]
|
||||
- summary: one-line summary
|
||||
- plan_version: X.Y.Z
|
||||
\`\`\`
|
||||
`
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Spawn CD Agent
|
||||
|
||||
```javascript
|
||||
function spawnCDAgent(cycleId, state, progressDir) {
|
||||
return spawn_agent({
|
||||
message: `
|
||||
## TASK ASSIGNMENT
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
1. **Read role definition**: ~/.codex/agents/code-developer.md
|
||||
2. Read: ${progressDir}/ep/plan.json
|
||||
3. Read: ${progressDir}/ra/requirements.md
|
||||
|
||||
---
|
||||
|
||||
## CYCLE CONTEXT
|
||||
|
||||
- **Cycle ID**: ${cycleId}
|
||||
- **Progress Dir**: ${progressDir}/cd/
|
||||
- **Plan Version**: ${state.plan?.version || 'N/A'}
|
||||
- **Previous Changes**: ${state.changes?.length || 0} files
|
||||
|
||||
## YOUR ROLE
|
||||
|
||||
Code Developer - Implement features based on plan and requirements.
|
||||
|
||||
## RESPONSIBILITIES
|
||||
|
||||
1. Implement features from plan
|
||||
2. Track code changes
|
||||
3. Handle integration issues
|
||||
4. Maintain code quality
|
||||
5. Report implementation progress and issues
|
||||
|
||||
## DELIVERABLES
|
||||
|
||||
Write files to ${progressDir}/cd/:
|
||||
- implementation.md: Implementation progress and decisions
|
||||
- changes.log: NDJSON format, each line: {file, action, timestamp}
|
||||
- issues.md: Development issues and blockers
|
||||
|
||||
## OUTPUT FORMAT
|
||||
|
||||
\`\`\`
|
||||
PHASE_RESULT:
|
||||
- phase: cd
|
||||
- status: success | failed | partial
|
||||
- files_changed: [count]
|
||||
- summary: one-line summary
|
||||
- blockers: []
|
||||
\`\`\`
|
||||
`
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Spawn VAS Agent
|
||||
|
||||
```javascript
|
||||
function spawnVASAgent(cycleId, state, progressDir) {
|
||||
return spawn_agent({
|
||||
message: `
|
||||
## TASK ASSIGNMENT
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
1. **Read role definition**: ~/.codex/agents/validation-archivist.md
|
||||
2. Read: ${progressDir}/cd/changes.log
|
||||
|
||||
---
|
||||
|
||||
## CYCLE CONTEXT
|
||||
|
||||
- **Cycle ID**: ${cycleId}
|
||||
- **Progress Dir**: ${progressDir}/vas/
|
||||
- **Changes Count**: ${state.changes?.length || 0}
|
||||
- **Iteration**: ${state.current_iteration}
|
||||
|
||||
## YOUR ROLE
|
||||
|
||||
Validation & Archival Specialist - Validate quality and create documentation.
|
||||
|
||||
## RESPONSIBILITIES
|
||||
|
||||
1. Run tests on implemented features
|
||||
2. Generate coverage reports
|
||||
3. Create archival documentation
|
||||
4. Summarize cycle results
|
||||
5. Generate version history
|
||||
|
||||
## DELIVERABLES
|
||||
|
||||
Write files to ${progressDir}/vas/:
|
||||
- validation.md: Test validation results
|
||||
- test-results.json: Detailed test results
|
||||
- coverage.md: Coverage report
|
||||
- summary.md: Cycle summary and recommendations
|
||||
|
||||
## OUTPUT FORMAT
|
||||
|
||||
\`\`\`
|
||||
PHASE_RESULT:
|
||||
- phase: vas
|
||||
- status: success | failed
|
||||
- test_pass_rate: X%
|
||||
- coverage: X%
|
||||
- issues: []
|
||||
\`\`\`
|
||||
`
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Result Parsing
|
||||
|
||||
```javascript
|
||||
function parseAgentOutputs(agentOutputs) {
|
||||
const results = {
|
||||
ra: parseOutput(agentOutputs.ra, 'ra'),
|
||||
ep: parseOutput(agentOutputs.ep, 'ep'),
|
||||
cd: parseOutput(agentOutputs.cd, 'cd'),
|
||||
vas: parseOutput(agentOutputs.vas, 'vas')
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
function parseOutput(output, agent) {
|
||||
const result = {
|
||||
agent: agent,
|
||||
status: 'unknown',
|
||||
data: {}
|
||||
}
|
||||
|
||||
// Parse PHASE_RESULT block
|
||||
const match = output.match(/PHASE_RESULT:\s*([\s\S]*?)(?:\n\n|$)/)
|
||||
if (match) {
|
||||
const lines = match[1].split('\n')
|
||||
for (const line of lines) {
|
||||
const m = line.match(/^-\s*(\w+):\s*(.+)$/)
|
||||
if (m) {
|
||||
result[m[1]] = m[2].trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
## Feedback Generation
|
||||
|
||||
```javascript
|
||||
function generateFeedback(parsedResults) {
|
||||
const feedback = {}
|
||||
|
||||
// Check VAS results
|
||||
if (parsedResults.vas.test_pass_rate < 100) {
|
||||
feedback.cd = `
|
||||
## FEEDBACK FROM VALIDATION
|
||||
|
||||
Test pass rate: ${parsedResults.vas.test_pass_rate}%
|
||||
|
||||
## ISSUES TO FIX
|
||||
|
||||
${parsedResults.vas.data.issues || 'See test-results.json for details'}
|
||||
|
||||
## NEXT STEP
|
||||
|
||||
Fix failing tests and update implementation.md with resolution.
|
||||
`
|
||||
}
|
||||
|
||||
// Check CD blockers
|
||||
if (parsedResults.cd.blockers?.length > 0) {
|
||||
feedback.ra = `
|
||||
## FEEDBACK FROM DEVELOPMENT
|
||||
|
||||
Blockers encountered:
|
||||
${parsedResults.cd.blockers.map(b => `- ${b}`).join('\n')}
|
||||
|
||||
## NEXT STEP
|
||||
|
||||
Clarify requirements or identify alternative approaches.
|
||||
Update requirements.md if needed.
|
||||
`
|
||||
}
|
||||
|
||||
return feedback
|
||||
}
|
||||
```
|
||||
|
||||
## Summary Generation
|
||||
|
||||
```javascript
|
||||
function generateFinalSummary(cycleId, state) {
|
||||
const summaryFile = `.workflow/.cycle/${cycleId}.progress/coordination/summary.md`
|
||||
|
||||
const summary = `# Cycle Summary - ${cycleId}
|
||||
|
||||
## Metadata
|
||||
- Cycle ID: ${cycleId}
|
||||
- Started: ${state.created_at}
|
||||
- Completed: ${state.completed_at}
|
||||
- Iterations: ${state.current_iteration}
|
||||
- Status: ${state.status}
|
||||
|
||||
## Phase Results
|
||||
- Requirements Analysis: ✓ Completed
|
||||
- Exploration & Planning: ✓ Completed
|
||||
- Code Development: ✓ Completed
|
||||
- Validation & Archival: ✓ Completed
|
||||
|
||||
## Key Deliverables
|
||||
- Requirements: ${state.requirements ? '✓' : '✗'}
|
||||
- Architecture Plan: ${state.plan ? '✓' : '✗'}
|
||||
- Code Changes: ${state.changes?.length || 0} files
|
||||
- Test Results: ${state.test_results?.pass_rate || '0'}% passing
|
||||
|
||||
## Generated Files
|
||||
- .workflow/.cycle/${cycleId}.progress/ra/requirements.md
|
||||
- .workflow/.cycle/${cycleId}.progress/ep/plan.json
|
||||
- .workflow/.cycle/${cycleId}.progress/cd/changes.log
|
||||
- .workflow/.cycle/${cycleId}.progress/vas/summary.md
|
||||
|
||||
## Continuation Instructions
|
||||
|
||||
To extend this cycle:
|
||||
|
||||
\`\`\`bash
|
||||
/parallel-dev-cycle --cycle-id=${cycleId} --extend="New requirement or feedback"
|
||||
\`\`\`
|
||||
|
||||
This will spawn agents for iteration ${state.current_iteration + 1}.
|
||||
`
|
||||
|
||||
Write(summaryFile, summary)
|
||||
}
|
||||
```
|
||||
|
||||
## Control Signal Checking
|
||||
|
||||
```javascript
|
||||
function checkControlSignals(cycleId) {
|
||||
const state = readCycleState(cycleId)
|
||||
|
||||
switch (state?.status) {
|
||||
case 'paused':
|
||||
return { continue: false, action: 'pause_exit' }
|
||||
case 'failed':
|
||||
return { continue: false, action: 'stop_exit' }
|
||||
case 'running':
|
||||
return { continue: true, action: 'continue' }
|
||||
default:
|
||||
return { continue: false, action: 'stop_exit' }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Recovery Strategies
|
||||
|
||||
| Error Type | Recovery |
|
||||
|------------|----------|
|
||||
| Agent timeout | send_input requesting convergence |
|
||||
| State corrupted | Rebuild from progress markdown files |
|
||||
| Agent failed | Re-spawn agent with previous context |
|
||||
| Conflicting results | Orchestrator sends reconciliation request |
|
||||
| Missing files | RA/EP agents identify and request clarification |
|
||||
|
||||
## Codex Best Practices Applied
|
||||
|
||||
1. **Single Orchestrator**: One main agent manages all phases
|
||||
2. **Parallel Workers**: Four specialized agents execute simultaneously
|
||||
3. **Batch wait()**: Wait for all agents with `wait({ ids: [...] })`
|
||||
4. **Deep Interaction**: Use send_input for iteration and refinement
|
||||
5. **Delayed close_agent**: Only after all phases and iterations complete
|
||||
6. **Role Path Passing**: Each agent reads its own role definition
|
||||
7. **Persistent Context**: Cycle state shared across all agents
|
||||
@@ -1,436 +0,0 @@
|
||||
# State Schema - Parallel Dev Cycle
|
||||
|
||||
Unified cycle state structure for multi-agent coordination and iteration support.
|
||||
|
||||
## State File Location
|
||||
|
||||
**Location**: `.workflow/.cycle/{cycleId}.json` (unified state, all agents access)
|
||||
|
||||
**Format**: JSON
|
||||
|
||||
## Cycle State Interface
|
||||
|
||||
```typescript
|
||||
interface CycleState {
|
||||
// =====================================================
|
||||
// CORE METADATA
|
||||
// =====================================================
|
||||
|
||||
cycle_id: string // Unique cycle identifier
|
||||
title: string // Task title (first 100 chars)
|
||||
description: string // Full task description
|
||||
task_history: string[] // All task descriptions across iterations
|
||||
|
||||
// =====================================================
|
||||
// STATUS & TIMING
|
||||
// =====================================================
|
||||
|
||||
status: 'created' | 'running' | 'paused' | 'completed' | 'failed'
|
||||
created_at: string // ISO8601 format
|
||||
updated_at: string // ISO8601 format
|
||||
completed_at?: string // ISO8601 format
|
||||
|
||||
max_iterations: number // Maximum iteration limit
|
||||
current_iteration: number // Current iteration count
|
||||
failure_reason?: string // If failed, why
|
||||
|
||||
// =====================================================
|
||||
// MULTI-AGENT TRACKING
|
||||
// =====================================================
|
||||
|
||||
agents: {
|
||||
ra: AgentState // Requirements Analyst
|
||||
ep: AgentState // Exploration Planner
|
||||
cd: AgentState // Code Developer
|
||||
vas: AgentState // Validation Archivist
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// PHASE TRACKING
|
||||
// =====================================================
|
||||
|
||||
current_phase: 'init' | 'ra' | 'ep' | 'cd' | 'vas' | 'aggregation' | 'complete'
|
||||
completed_phases: string[]
|
||||
phase_errors: Array<{
|
||||
phase: string
|
||||
error: string
|
||||
timestamp: string
|
||||
}>
|
||||
|
||||
// =====================================================
|
||||
// SHARED CONTEXT (Populated by agents)
|
||||
// =====================================================
|
||||
|
||||
requirements?: {
|
||||
version: string // e.g., "1.0.0", "1.1.0"
|
||||
specification: string // Full spec from requirements.md
|
||||
edge_cases: string[]
|
||||
last_updated: string
|
||||
}
|
||||
|
||||
exploration?: {
|
||||
version: string
|
||||
architecture_summary: string
|
||||
integration_points: string[]
|
||||
identified_risks: string[]
|
||||
last_updated: string
|
||||
}
|
||||
|
||||
plan?: {
|
||||
version: string
|
||||
tasks: PlanTask[]
|
||||
total_estimated_effort: string
|
||||
critical_path: string[]
|
||||
last_updated: string
|
||||
}
|
||||
|
||||
changes?: {
|
||||
total_files: number
|
||||
changes: ChangeLog[]
|
||||
iteration_markers: Record<number, string> // Iteration timestamps
|
||||
}
|
||||
|
||||
test_results?: {
|
||||
version: string
|
||||
pass_rate: number // 0-100
|
||||
coverage: number // 0-100
|
||||
failed_tests: string[]
|
||||
total_tests: number
|
||||
last_run: string
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// ITERATION TRACKING
|
||||
// =====================================================
|
||||
|
||||
iterations: IterationRecord[]
|
||||
|
||||
// =====================================================
|
||||
// COORDINATION DATA
|
||||
// =====================================================
|
||||
|
||||
coordination: {
|
||||
feedback_log: FeedbackEntry[]
|
||||
pending_decisions: Decision[]
|
||||
blockers: Blocker[]
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// SUPPORTING TYPES
|
||||
// =====================================================
|
||||
|
||||
interface AgentState {
|
||||
status: 'idle' | 'running' | 'waiting' | 'completed' | 'failed'
|
||||
started_at?: string
|
||||
completed_at?: string
|
||||
output_files: string[]
|
||||
last_message?: string
|
||||
error?: string
|
||||
iterations_completed: number
|
||||
}
|
||||
|
||||
interface PlanTask {
|
||||
id: string // e.g., "TASK-001"
|
||||
description: string
|
||||
effort: 'small' | 'medium' | 'large'
|
||||
depends_on: string[]
|
||||
status: 'pending' | 'in_progress' | 'completed' | 'blocked'
|
||||
assigned_to?: string // Agent name
|
||||
files: string[]
|
||||
}
|
||||
|
||||
interface ChangeLog {
|
||||
timestamp: string
|
||||
file: string
|
||||
action: 'create' | 'modify' | 'delete'
|
||||
iteration: number
|
||||
agent: string // which agent made change
|
||||
description: string
|
||||
}
|
||||
|
||||
interface IterationRecord {
|
||||
number: number
|
||||
extension?: string // User feedback/extension for this iteration
|
||||
started_at: string
|
||||
completed_at: string
|
||||
agent_results: Record<string, {
|
||||
status: string
|
||||
files_modified: number
|
||||
}>
|
||||
issues_found: string[]
|
||||
resolved: boolean
|
||||
}
|
||||
|
||||
interface FeedbackEntry {
|
||||
timestamp: string
|
||||
source: string // Agent or 'user'
|
||||
target: string // Recipient agent
|
||||
content: string
|
||||
type: 'requirement_update' | 'bug_report' | 'issue_fix' | 'clarification'
|
||||
}
|
||||
|
||||
interface Decision {
|
||||
id: string
|
||||
description: string
|
||||
options: string[]
|
||||
made_by?: string
|
||||
chosen_option?: string
|
||||
status: 'pending' | 'made' | 'implemented'
|
||||
}
|
||||
|
||||
interface Blocker {
|
||||
id: string
|
||||
description: string
|
||||
reported_by: string
|
||||
status: 'open' | 'resolved' | 'workaround'
|
||||
resolution?: string
|
||||
}
|
||||
```
|
||||
|
||||
## Initial State (New Cycle)
|
||||
|
||||
When creating a new cycle:
|
||||
|
||||
```json
|
||||
{
|
||||
"cycle_id": "cycle-v1-20260122T100000-abc123",
|
||||
"title": "Implement OAuth authentication",
|
||||
"description": "Add OAuth2 login support with Google and GitHub providers",
|
||||
"task_history": [
|
||||
"Implement OAuth authentication"
|
||||
],
|
||||
"status": "created",
|
||||
"created_at": "2026-01-22T10:00:00+08:00",
|
||||
"updated_at": "2026-01-22T10:00:00+08:00",
|
||||
"max_iterations": 5,
|
||||
"current_iteration": 0,
|
||||
"agents": {
|
||||
"ra": { "status": "idle", "output_files": [], "iterations_completed": 0 },
|
||||
"ep": { "status": "idle", "output_files": [], "iterations_completed": 0 },
|
||||
"cd": { "status": "idle", "output_files": [], "iterations_completed": 0 },
|
||||
"vas": { "status": "idle", "output_files": [], "iterations_completed": 0 }
|
||||
},
|
||||
"current_phase": "init",
|
||||
"completed_phases": [],
|
||||
"phase_errors": [],
|
||||
"iterations": [],
|
||||
"coordination": {
|
||||
"feedback_log": [],
|
||||
"pending_decisions": [],
|
||||
"blockers": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## State Transitions
|
||||
|
||||
### Iteration 1: Initial Execution
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "running",
|
||||
"current_iteration": 1,
|
||||
"current_phase": "ra",
|
||||
"agents": {
|
||||
"ra": { "status": "running", "started_at": "2026-01-22T10:05:00+08:00" },
|
||||
"ep": { "status": "idle" },
|
||||
"cd": { "status": "idle" },
|
||||
"vas": { "status": "idle" }
|
||||
},
|
||||
"requirements": {
|
||||
"version": "1.0.0",
|
||||
"specification": "...",
|
||||
"edge_cases": ["OAuth timeout handling", "PKCE validation"],
|
||||
"last_updated": "2026-01-22T10:15:00+08:00"
|
||||
},
|
||||
"iterations": [{
|
||||
"number": 1,
|
||||
"started_at": "2026-01-22T10:00:00+08:00",
|
||||
"agent_results": {
|
||||
"ra": { "status": "completed", "files_modified": 3 },
|
||||
"ep": { "status": "completed", "files_modified": 2 },
|
||||
"cd": { "status": "partial", "files_modified": 5 },
|
||||
"vas": { "status": "pending", "files_modified": 0 }
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### After Phase Completion
|
||||
|
||||
```json
|
||||
{
|
||||
"current_phase": "aggregation",
|
||||
"completed_phases": ["ra", "ep", "cd", "vas"],
|
||||
"plan": {
|
||||
"version": "1.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"id": "TASK-001",
|
||||
"description": "Setup OAuth application credentials",
|
||||
"effort": "small",
|
||||
"status": "completed",
|
||||
"files": ["src/config/oauth.ts"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"changes": {
|
||||
"total_files": 12,
|
||||
"iteration_markers": {
|
||||
"1": "2026-01-22T10:30:00+08:00"
|
||||
}
|
||||
},
|
||||
"test_results": {
|
||||
"version": "1.0.0",
|
||||
"pass_rate": 85,
|
||||
"coverage": 78,
|
||||
"failed_tests": ["test: OAuth timeout retry"],
|
||||
"total_tests": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Iteration 2: User Extension
|
||||
|
||||
User provides feedback: "Also add multi-factor authentication"
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "running",
|
||||
"current_iteration": 2,
|
||||
"task_history": [
|
||||
"Implement OAuth authentication",
|
||||
"Also add multi-factor authentication"
|
||||
],
|
||||
"description": "Add OAuth2 login support with Google and GitHub providers\n\n--- ITERATION 2 ---\nAlso add multi-factor authentication",
|
||||
"agents": {
|
||||
"ra": { "status": "running", "iterations_completed": 1 },
|
||||
"ep": { "status": "idle", "iterations_completed": 1 },
|
||||
"cd": { "status": "idle", "iterations_completed": 1 },
|
||||
"vas": { "status": "idle", "iterations_completed": 1 }
|
||||
},
|
||||
"requirements": {
|
||||
"version": "1.1.0",
|
||||
"specification": "...",
|
||||
"last_updated": "2026-01-22T11:00:00+08:00"
|
||||
},
|
||||
"iterations": [
|
||||
{ "number": 1, "completed_at": "..." },
|
||||
{
|
||||
"number": 2,
|
||||
"extension": "Also add multi-factor authentication",
|
||||
"started_at": "2026-01-22T10:45:00+08:00",
|
||||
"agent_results": {}
|
||||
}
|
||||
],
|
||||
"coordination": {
|
||||
"feedback_log": [{
|
||||
"timestamp": "2026-01-22T10:45:00+08:00",
|
||||
"source": "user",
|
||||
"target": "ra",
|
||||
"content": "Add multi-factor authentication to requirements",
|
||||
"type": "requirement_update"
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Version Tracking
|
||||
|
||||
Each component tracks its version:
|
||||
|
||||
- **Requirements**: `1.0.0` → `1.1.0` → `1.2.0` (each iteration)
|
||||
- **Plan**: `1.0.0` → `1.1.0` (updated based on requirements)
|
||||
- **Code**: Changes appended with iteration markers
|
||||
- **Tests**: Results tracked per iteration
|
||||
|
||||
## File Sync Protocol
|
||||
|
||||
State changes trigger file writes:
|
||||
|
||||
| State Change | File Sync |
|
||||
|--------------|-----------|
|
||||
| `requirements` updated | `.progress/ra/requirements.md` + version bump |
|
||||
| `plan` updated | `.progress/ep/plan.json` + version bump |
|
||||
| `changes` appended | `.progress/cd/changes.log` + iteration marker |
|
||||
| `test_results` updated | `.progress/vas/test-results.json` + version bump |
|
||||
| Full iteration done | `.progress/coordination/timeline.md` appended |
|
||||
|
||||
## Control Signal Checking
|
||||
|
||||
Agents check status before each action:
|
||||
|
||||
```javascript
|
||||
function checkControlSignals(cycleId) {
|
||||
const state = JSON.parse(Read(`.workflow/.cycle/${cycleId}.json`))
|
||||
|
||||
if (state.status === 'paused') {
|
||||
return { continue: false, action: 'pause' }
|
||||
}
|
||||
if (state.status === 'failed') {
|
||||
return { continue: false, action: 'stop' }
|
||||
}
|
||||
if (state.status === 'running') {
|
||||
return { continue: true, action: 'continue' }
|
||||
}
|
||||
|
||||
return { continue: false, action: 'unknown' }
|
||||
}
|
||||
```
|
||||
|
||||
## State Persistence
|
||||
|
||||
### Write Operations
|
||||
|
||||
After each agent completes or phase transitions:
|
||||
|
||||
```javascript
|
||||
Write(
|
||||
`.workflow/.cycle/${cycleId}.json`,
|
||||
JSON.stringify(state, null, 2)
|
||||
)
|
||||
```
|
||||
|
||||
### Read Operations
|
||||
|
||||
Agents always read fresh state before executing:
|
||||
|
||||
```javascript
|
||||
const currentState = JSON.parse(
|
||||
Read(`.workflow/.cycle/${cycleId}.json`)
|
||||
)
|
||||
```
|
||||
|
||||
## State Rebuild (Recovery)
|
||||
|
||||
If master state corrupted, rebuild from markdown files:
|
||||
|
||||
```javascript
|
||||
function rebuildState(cycleId) {
|
||||
const progressDir = `.workflow/.cycle/${cycleId}.progress`
|
||||
|
||||
// Read markdown files
|
||||
const raMarkdown = Read(`${progressDir}/ra/requirements.md`)
|
||||
const epMarkdown = Read(`${progressDir}/ep/plan.json`)
|
||||
const cdChanges = Read(`${progressDir}/cd/changes.log`)
|
||||
const vasResults = Read(`${progressDir}/vas/test-results.json`)
|
||||
|
||||
// Reconstruct state from files
|
||||
return {
|
||||
requirements: parseMarkdown(raMarkdown),
|
||||
plan: JSON.parse(epMarkdown),
|
||||
changes: parseNDJSON(cdChanges),
|
||||
test_results: JSON.parse(vasResults)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Immutable Reads**: Never modify state during read
|
||||
2. **Version Bumps**: Increment version on each iteration
|
||||
3. **Timestamp Accuracy**: Use UTC+8 consistently
|
||||
4. **Append-Only Logs**: Never delete history
|
||||
5. **Atomic Writes**: Write complete state, not partial updates
|
||||
6. **Coordination Tracking**: Log all inter-agent communication
|
||||
@@ -1,423 +0,0 @@
|
||||
# Agent Communication Optimization
|
||||
|
||||
优化 agent 通信机制:使用简短的产出文件引用而不是内容传递。
|
||||
|
||||
## 背景
|
||||
|
||||
在多 agent 系统中,传递完整的文件内容会导致:
|
||||
- 消息体积过大
|
||||
- 上下文使用量增加
|
||||
- 通信效率低下
|
||||
- 容易引入上下文断层
|
||||
|
||||
**优化方案**: 使用文件路径引用,让 agent 自动读取需要的文件。
|
||||
|
||||
## 优化原则
|
||||
|
||||
### 原则 1: 文件引用而非内容传递
|
||||
|
||||
❌ **错误做法**(传递内容):
|
||||
```javascript
|
||||
send_input({
|
||||
id: agents.cd,
|
||||
message: `
|
||||
Requirements:
|
||||
${requirements_content} // 完整内容 - 浪费空间
|
||||
|
||||
Plan:
|
||||
${plan_json} // 完整 JSON - 重复信息
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
✅ **正确做法**(引用文件):
|
||||
```javascript
|
||||
send_input({
|
||||
id: agents.cd,
|
||||
message: `
|
||||
## Feedback from Validation
|
||||
|
||||
Test failures found. Review these outputs:
|
||||
|
||||
## Reference
|
||||
- Requirements: .workflow/.cycle/${cycleId}.progress/ra/requirements.md (v1.0.0)
|
||||
- Plan: .workflow/.cycle/${cycleId}.progress/ep/plan.json (v1.0.0)
|
||||
- Test Results: .workflow/.cycle/${cycleId}.progress/vas/test-results.json
|
||||
|
||||
## Issues Found
|
||||
${summary_of_issues} // 只传递摘要
|
||||
|
||||
## Actions Required
|
||||
1. Fix OAuth token refresh (test line 45)
|
||||
2. Update implementation.md with fixes
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
### 原则 2: 摘要而非全文
|
||||
|
||||
❌ **错误**:
|
||||
```javascript
|
||||
// 传递所有文件内容
|
||||
RA输出: "requirements.md (2000 lines) + edge-cases.md (1000 lines) + changes.log (500 lines)"
|
||||
|
||||
EP读取: 全文解析所有内容(浪费token)
|
||||
```
|
||||
|
||||
✅ **正确**:
|
||||
```javascript
|
||||
// 只传递关键摘要
|
||||
RA输出:
|
||||
- 10个功能需求
|
||||
- 5个非功能需求
|
||||
- 8个边界场景
|
||||
- 文件路径用于完整查看
|
||||
|
||||
EP读取: 读取摘要 + 需要时查看完整文件(高效)
|
||||
```
|
||||
|
||||
### 原则 3: 文件版本跟踪
|
||||
|
||||
每个引用必须包含版本:
|
||||
|
||||
```javascript
|
||||
send_input({
|
||||
id: agents.cd,
|
||||
message: `
|
||||
Requirements: .workflow/.cycle/${cycleId}.progress/ra/requirements.md (v1.1.0)
|
||||
^^^^^^^ 版本号
|
||||
|
||||
Plan: .workflow/.cycle/${cycleId}.progress/ep/plan.json (v1.0.0)
|
||||
^^^^^^^ 版本号
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
**好处**:
|
||||
- 避免使用过期信息
|
||||
- 自动检测版本不匹配
|
||||
- 支持多版本迭代
|
||||
|
||||
## 实现模式
|
||||
|
||||
### Pattern 1: 通知 + 引用
|
||||
|
||||
Agent 向其他 agent 通知输出,而非传递内容:
|
||||
|
||||
```javascript
|
||||
// RA 输出摘要
|
||||
const raSummary = {
|
||||
requirements_count: 10,
|
||||
edge_cases_count: 8,
|
||||
version: "1.0.0",
|
||||
output_file: ".workflow/.cycle/${cycleId}.progress/ra/requirements.md",
|
||||
key_requirements: [
|
||||
"FR-001: OAuth authentication",
|
||||
"FR-002: Multi-provider support",
|
||||
"..." // 只列出标题,不传递完整内容
|
||||
]
|
||||
}
|
||||
|
||||
// 更新状态,让其他 agent 读取
|
||||
state.requirements = {
|
||||
version: raSummary.version,
|
||||
output_file: raSummary.output_file,
|
||||
summary: raSummary.key_requirements
|
||||
}
|
||||
|
||||
// EP agent 从状态读取
|
||||
const requiredDetails = state.requirements
|
||||
const outputFile = requiredDetails.output_file
|
||||
const requirements = JSON.parse(Read(outputFile)) // EP 自己读取完整文件
|
||||
```
|
||||
|
||||
### Pattern 2: 反馈通知
|
||||
|
||||
Orchestrator 发送反馈时只传递摘要和行号:
|
||||
|
||||
```javascript
|
||||
// ❌ 错误:传递完整测试结果
|
||||
send_input({
|
||||
id: agents.cd,
|
||||
message: `
|
||||
Test Results:
|
||||
${entire_test_results_json} // 完整 JSON - 太大
|
||||
`
|
||||
})
|
||||
|
||||
// ✅ 正确:引用文件 + 问题摘要
|
||||
send_input({
|
||||
id: agents.cd,
|
||||
message: `
|
||||
## Test Failures
|
||||
|
||||
Full results: .workflow/.cycle/${cycleId}.progress/vas/test-results.json (v1.0.0)
|
||||
|
||||
## Quick Summary
|
||||
- Failed: oauth-refresh (line 45, expected token refresh)
|
||||
- Failed: concurrent-login (line 78, race condition)
|
||||
|
||||
## Fix Instructions
|
||||
1. Review test cases at referenced lines
|
||||
2. Fix implementation
|
||||
3. Re-run tests
|
||||
4. Update implementation.md
|
||||
|
||||
Reference previous file paths if you need full details.
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
### Pattern 3: 依赖链路
|
||||
|
||||
Agent 通过文件引用获取依赖:
|
||||
|
||||
```javascript
|
||||
// EP agent: 从状态读取 RA 输出路径
|
||||
const raOutputPath = state.requirements?.output_file
|
||||
if (raOutputPath && exists(raOutputPath)) {
|
||||
const requirements = Read(raOutputPath)
|
||||
// 使用 requirements 生成计划
|
||||
}
|
||||
|
||||
// CD agent: 从状态读取 EP 输出路径
|
||||
const epPlanPath = state.plan?.output_file
|
||||
if (epPlanPath && exists(epPlanPath)) {
|
||||
const plan = JSON.parse(Read(epPlanPath))
|
||||
// 根据 plan 实现功能
|
||||
}
|
||||
|
||||
// VAS agent: 从状态读取 CD 输出路径
|
||||
const cdChangesPath = state.changes?.output_file
|
||||
if (cdChangesPath && exists(cdChangesPath)) {
|
||||
const changes = readNDJSON(cdChangesPath)
|
||||
// 根据 changes 生成测试
|
||||
}
|
||||
```
|
||||
|
||||
## 状态文件引用结构
|
||||
|
||||
优化后的状态文件应该包含文件路径而不是内容:
|
||||
|
||||
```json
|
||||
{
|
||||
"cycle_id": "cycle-v1-20260122-abc123",
|
||||
|
||||
"requirements": {
|
||||
"version": "1.0.0",
|
||||
"output_files": {
|
||||
"specification": ".workflow/.cycle/cycle-v1-20260122-abc123.progress/ra/requirements.md",
|
||||
"edge_cases": ".workflow/.cycle/cycle-v1-20260122-abc123.progress/ra/edge-cases.md",
|
||||
"changes_log": ".workflow/.cycle/cycle-v1-20260122-abc123.progress/ra/changes.log"
|
||||
},
|
||||
"summary": {
|
||||
"functional_requirements": 10,
|
||||
"edge_cases": 8,
|
||||
"constraints": 5
|
||||
}
|
||||
},
|
||||
|
||||
"exploration": {
|
||||
"version": "1.0.0",
|
||||
"output_files": {
|
||||
"exploration": ".workflow/.cycle/cycle-v1-20260122-abc123.progress/ep/exploration.md",
|
||||
"architecture": ".workflow/.cycle/cycle-v1-20260122-abc123.progress/ep/architecture.md"
|
||||
},
|
||||
"summary": {
|
||||
"key_components": ["Auth Module", "User Service"],
|
||||
"integration_points": 5,
|
||||
"identified_risks": 3
|
||||
}
|
||||
},
|
||||
|
||||
"plan": {
|
||||
"version": "1.0.0",
|
||||
"output_file": ".workflow/.cycle/cycle-v1-20260122-abc123.progress/ep/plan.json",
|
||||
"summary": {
|
||||
"total_tasks": 8,
|
||||
"critical_path": ["TASK-001", "TASK-003", "TASK-004"],
|
||||
"estimated_hours": 16
|
||||
}
|
||||
},
|
||||
|
||||
"implementation": {
|
||||
"version": "1.0.0",
|
||||
"output_files": {
|
||||
"progress": ".workflow/.cycle/cycle-v1-20260122-abc123.progress/cd/implementation.md",
|
||||
"changes": ".workflow/.cycle/cycle-v1-20260122-abc123.progress/cd/changes.log",
|
||||
"issues": ".workflow/.cycle/cycle-v1-20260122-abc123.progress/cd/issues.md"
|
||||
},
|
||||
"summary": {
|
||||
"tasks_completed": 3,
|
||||
"files_modified": 5,
|
||||
"blockers": 0
|
||||
}
|
||||
},
|
||||
|
||||
"validation": {
|
||||
"version": "1.0.0",
|
||||
"output_files": {
|
||||
"validation": ".workflow/.cycle/cycle-v1-20260122-abc123.progress/vas/validation.md",
|
||||
"test_results": ".workflow/.cycle/cycle-v1-20260122-abc123.progress/vas/test-results.json",
|
||||
"coverage": ".workflow/.cycle/cycle-v1-20260122-abc123.progress/vas/coverage.md"
|
||||
},
|
||||
"summary": {
|
||||
"pass_rate": 92,
|
||||
"coverage": 87,
|
||||
"failures": 4
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Agent 通信模板优化
|
||||
|
||||
### 优化前: 完整内容传递
|
||||
|
||||
```javascript
|
||||
send_input({
|
||||
id: agents.cd,
|
||||
message: `
|
||||
## Requirements (Complete Content)
|
||||
|
||||
${fs.readFileSync(requirementsFile, 'utf8')} // 2000+ lines
|
||||
|
||||
## Plan (Complete JSON)
|
||||
|
||||
${fs.readFileSync(planFile, 'utf8')} // 1000+ lines
|
||||
|
||||
## Test Results (Complete)
|
||||
|
||||
${fs.readFileSync(testResultsFile, 'utf8')} // 500+ lines
|
||||
|
||||
## Your Task
|
||||
|
||||
Fix the implementation...
|
||||
` // 总消息体: 4000+ 行
|
||||
})
|
||||
```
|
||||
|
||||
### 优化后: 文件引用 + 摘要
|
||||
|
||||
```javascript
|
||||
send_input({
|
||||
id: agents.cd,
|
||||
message: `
|
||||
## Test Failures - Action Required
|
||||
|
||||
Full Test Report: .workflow/.cycle/${cycleId}.progress/vas/test-results.json (v1.0.0)
|
||||
|
||||
## Summary of Failures
|
||||
- oauth-refresh: Expected token refresh, got error (test line 45)
|
||||
- concurrent-login: Race condition in session writes (test line 78)
|
||||
|
||||
## Implementation Reference
|
||||
- Current Code: .workflow/.cycle/${cycleId}.progress/cd/implementation.md (v1.0.0)
|
||||
- Code Changes: .workflow/.cycle/${cycleId}.progress/cd/changes.log (v1.0.0)
|
||||
|
||||
## Action Required
|
||||
1. Review failing tests in referenced test results file
|
||||
2. Fix root causes (race condition, token handling)
|
||||
3. Update implementation.md with fixes
|
||||
4. Re-run tests
|
||||
|
||||
## Context
|
||||
- Requirement: .workflow/.cycle/${cycleId}.progress/ra/requirements.md (v1.0.0)
|
||||
- Plan: .workflow/.cycle/${cycleId}.progress/ep/plan.json (v1.0.0)
|
||||
|
||||
Output PHASE_RESULT when complete.
|
||||
` // 总消息体: <500 行,高效传递
|
||||
})
|
||||
```
|
||||
|
||||
## 版本控制最佳实践
|
||||
|
||||
### 版本不匹配检测
|
||||
|
||||
```javascript
|
||||
function validateVersionConsistency(state) {
|
||||
const versions = {
|
||||
ra: state.requirements?.version,
|
||||
ep: state.plan?.version,
|
||||
cd: state.implementation?.version,
|
||||
vas: state.validation?.version
|
||||
}
|
||||
|
||||
// 检查版本一致性
|
||||
const allVersions = Object.values(versions).filter(v => v)
|
||||
const unique = new Set(allVersions)
|
||||
|
||||
if (unique.size > 1) {
|
||||
console.warn('Version mismatch detected:')
|
||||
console.warn(versions)
|
||||
// 返回版本差异,让 orchestrator 决定是否继续
|
||||
}
|
||||
|
||||
return unique.size === 1
|
||||
}
|
||||
```
|
||||
|
||||
### 文件存在性检查
|
||||
|
||||
```javascript
|
||||
function validateReferences(state, cycleId) {
|
||||
const checks = []
|
||||
|
||||
// 检查所有引用的文件是否存在
|
||||
for (const [agent, data] of Object.entries(state)) {
|
||||
if (data?.output_files) {
|
||||
for (const [name, path] of Object.entries(data.output_files)) {
|
||||
if (!fs.existsSync(path)) {
|
||||
checks.push({
|
||||
agent: agent,
|
||||
file: name,
|
||||
path: path,
|
||||
status: 'missing'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return checks
|
||||
}
|
||||
```
|
||||
|
||||
## 好处总结
|
||||
|
||||
| 方面 | 改进 |
|
||||
|------|------|
|
||||
| 消息体积 | 减少 80-90% |
|
||||
| Token 使用 | 减少 60-70% |
|
||||
| 读取速度 | 无需解析冗余内容 |
|
||||
| 版本控制 | 清晰的版本跟踪 |
|
||||
| 上下文清晰 | 不会混淆版本 |
|
||||
| 可维护性 | 文件变更不需要修改消息 |
|
||||
|
||||
## 迁移建议
|
||||
|
||||
### 第一步: 更新状态结构
|
||||
|
||||
```json
|
||||
// 从这样:
|
||||
"requirements": "完整内容"
|
||||
|
||||
// 改为这样:
|
||||
"requirements": {
|
||||
"version": "1.0.0",
|
||||
"output_file": "path/to/file",
|
||||
"summary": {...}
|
||||
}
|
||||
```
|
||||
|
||||
### 第二步: 更新通信模板
|
||||
|
||||
所有 `send_input` 消息改为引用路径。
|
||||
|
||||
### 第三步: Agent 自动读取
|
||||
|
||||
Agent 从引用路径自动读取所需文件。
|
||||
|
||||
### 第四步: 测试版本检测
|
||||
|
||||
确保版本不匹配时有警告。
|
||||
@@ -1,406 +0,0 @@
|
||||
# Coordination Protocol - Multi-Agent Communication
|
||||
|
||||
Inter-agent communication protocols and patterns for parallel-dev-cycle skill.
|
||||
|
||||
## Overview
|
||||
|
||||
The coordination protocol enables four parallel agents (RA, EP, CD, VAS) to communicate efficiently while maintaining clear responsibilities and avoiding conflicts.
|
||||
|
||||
## Communication Channels
|
||||
|
||||
### 1. Shared State File (Primary)
|
||||
|
||||
**Location**: `.workflow/.cycle/{cycleId}.json`
|
||||
|
||||
**Access Pattern**:
|
||||
- **Agents**: READ ONLY - check dependencies and status
|
||||
- **Orchestrator**: READ-WRITE - updates state after each phase
|
||||
|
||||
```javascript
|
||||
// Every agent: Read state to check dependencies
|
||||
const state = JSON.parse(Read(`.workflow/.cycle/${cycleId}.json`))
|
||||
const canProceed = checkDependencies(state)
|
||||
|
||||
// Agent outputs PHASE_RESULT (reports to orchestrator, NOT writes directly)
|
||||
console.log("PHASE_RESULT: ...")
|
||||
|
||||
// Only Orchestrator writes to state file after receiving PHASE_RESULT
|
||||
// Write(`.workflow/.cycle/${cycleId}.json`, JSON.stringify(updatedState, null, 2))
|
||||
```
|
||||
|
||||
**Protocol**:
|
||||
- Only orchestrator writes to state file (no concurrent writes, no lock needed)
|
||||
- Agents read state to understand dependencies
|
||||
- Timestamp all orchestrator updates with ISO8601 format
|
||||
- Never delete existing data, only append
|
||||
|
||||
### 2. Progress Markdown Files (Async Log)
|
||||
|
||||
**Location**: `.workflow/.cycle/{cycleId}.progress/{agent}/`
|
||||
|
||||
Each agent writes progress to dedicated markdown files:
|
||||
|
||||
| Agent | Main Documents (Rewrite) | Logs (Append-Only) |
|
||||
|-------|--------------------------|-------------------|
|
||||
| RA | requirements.md | changes.log |
|
||||
| EP | exploration.md, architecture.md, plan.json | changes.log |
|
||||
| CD | implementation.md, issues.md | changes.log, debug-log.ndjson |
|
||||
| VAS | validation.md, summary.md, test-results.json | changes.log |
|
||||
|
||||
**Protocol**:
|
||||
- **Main documents**: Complete rewrite per iteration, archived to `history/`
|
||||
- **Log files**: Append-only (changes.log, debug-log.ndjson) - never delete
|
||||
- **Version synchronization**: All main documents share same version (e.g., all v1.1.0 in iteration 2)
|
||||
- Include timestamp on each update
|
||||
|
||||
### 3. Orchestrator send_input (Synchronous)
|
||||
|
||||
**When**: Orchestrator needs to send feedback or corrections
|
||||
|
||||
```javascript
|
||||
// Example: CD agent receives test failure feedback
|
||||
send_input({
|
||||
id: agents.cd,
|
||||
message: `
|
||||
## FEEDBACK FROM VALIDATION
|
||||
|
||||
Test failures detected: ${failures}
|
||||
|
||||
## REQUIRED ACTION
|
||||
|
||||
Fix the following:
|
||||
${actionItems}
|
||||
|
||||
## NEXT STEP
|
||||
Update implementation.md with fixes, then re-run tests.
|
||||
Output PHASE_RESULT when complete.
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
**Protocol**:
|
||||
- Only orchestrator initiates send_input
|
||||
- Clear action items and expected output
|
||||
- Single message per iteration (no rapid-fire sends)
|
||||
|
||||
### 4. Coordination Log
|
||||
|
||||
**Location**: `.workflow/.cycle/{cycleId}.progress/coordination/`
|
||||
|
||||
Centralized log for inter-agent decisions and communication:
|
||||
|
||||
**feedback.md**:
|
||||
```markdown
|
||||
# Feedback & Coordination Log - Version X.Y.Z
|
||||
|
||||
## Timeline
|
||||
- [10:00:00] Orchestrator: Created cycle
|
||||
- [10:05:00] RA: Requirements analysis started
|
||||
- [10:10:00] RA: Requirements completed, v1.0.0
|
||||
- [10:10:01] EP: Starting exploration (depends on RA output)
|
||||
- [10:15:00] EP: Architecture designed, plan.json v1.0.0
|
||||
- [10:15:01] CD: Starting implementation (depends on EP plan)
|
||||
- [10:30:00] CD: Implementation progressing, found blocker
|
||||
- [10:31:00] RA: Clarified requirement after CD blocker
|
||||
- [10:31:01] CD: Continuing with clarification
|
||||
- [10:40:00] CD: Implementation complete
|
||||
- [10:40:01] VAS: Starting validation
|
||||
- [10:45:00] VAS: Testing complete, found failures
|
||||
- [10:45:01] Orchestrator: Sending feedback to CD
|
||||
- [10:46:00] CD: Fixed issues
|
||||
- [10:50:00] VAS: Re-validation, all passing
|
||||
- [10:50:01] Orchestrator: Cycle complete
|
||||
|
||||
## Decision Records
|
||||
- [10:31:00] RA Clarification: OAuth optional vs required?
|
||||
- Decision: Optional (can use password)
|
||||
- Rationale: More flexible for users
|
||||
- Impact: Affects FR-003 implementation
|
||||
|
||||
## Blockers & Resolutions
|
||||
- [10:30:00] Blocker: Database migration for existing users
|
||||
- Reported by: CD
|
||||
- Resolution: Set oauth_id = null for existing users
|
||||
- Status: Resolved
|
||||
|
||||
## Cross-Agent Dependencies
|
||||
- EP depends on: RA requirements (v1.0.0)
|
||||
- CD depends on: EP plan (v1.0.0)
|
||||
- VAS depends on: CD code changes
|
||||
```
|
||||
|
||||
## Message Formats
|
||||
|
||||
### Agent Status Update
|
||||
|
||||
Each agent updates state with its status:
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"ra": {
|
||||
"status": "completed",
|
||||
"started_at": "2026-01-22T10:05:00+08:00",
|
||||
"completed_at": "2026-01-22T10:15:00+08:00",
|
||||
"output_files": [
|
||||
".workflow/.cycle/cycle-xxx.progress/ra/requirements.md",
|
||||
".workflow/.cycle/cycle-xxx.progress/ra/edge-cases.md",
|
||||
".workflow/.cycle/cycle-xxx.progress/ra/changes.log"
|
||||
],
|
||||
"iterations_completed": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Feedback Message Format
|
||||
|
||||
When orchestrator sends feedback via send_input:
|
||||
|
||||
```text
|
||||
## FEEDBACK FROM [Agent Name]
|
||||
|
||||
[Summary of findings or issues]
|
||||
|
||||
## REFERENCED OUTPUT
|
||||
File: [path to agent output]
|
||||
Version: [X.Y.Z]
|
||||
|
||||
## REQUIRED ACTION
|
||||
|
||||
1. [Action 1 with specific details]
|
||||
2. [Action 2 with specific details]
|
||||
|
||||
## SUCCESS CRITERIA
|
||||
|
||||
- [ ] Item 1
|
||||
- [ ] Item 2
|
||||
|
||||
## NEXT STEP
|
||||
[What agent should do next]
|
||||
Output PHASE_RESULT when complete.
|
||||
|
||||
## CONTEXT
|
||||
|
||||
Previous iteration: [N]
|
||||
Current iteration: [N+1]
|
||||
```
|
||||
|
||||
### Phase Result Format
|
||||
|
||||
Every agent outputs PHASE_RESULT:
|
||||
|
||||
```text
|
||||
PHASE_RESULT:
|
||||
- phase: [ra|ep|cd|vas]
|
||||
- status: success | failed | partial
|
||||
- files_written: [list of files]
|
||||
- summary: [one-line summary]
|
||||
- [agent-specific fields]
|
||||
- issues: [list of issues if any]
|
||||
|
||||
PHASE_DETAILS:
|
||||
[Additional details or metrics]
|
||||
```
|
||||
|
||||
## Dependency Resolution
|
||||
|
||||
**Execution Model**: All four agents are spawned in parallel, but execution blocks based on dependencies. Orchestrator manages dependency resolution via shared state.
|
||||
|
||||
### Build Order (Default)
|
||||
|
||||
```
|
||||
RA (Requirements) → EP (Planning) → CD (Development) → VAS (Validation)
|
||||
↓ ↓ ↓ ↓
|
||||
Block EP Block CD Block VAS Block completion
|
||||
```
|
||||
|
||||
**Explanation**:
|
||||
- All agents spawned simultaneously
|
||||
- Each agent checks dependencies in shared state before proceeding
|
||||
- Blocked agents wait for dependency completion
|
||||
- Orchestrator uses `send_input` to notify dependent agents when ready
|
||||
|
||||
### Parallel Opportunities
|
||||
|
||||
Some phases can run in parallel:
|
||||
|
||||
```
|
||||
RA + FrontendCode (independent)
|
||||
EP + RA (not blocking)
|
||||
CD.Task1 + CD.Task2 (if no dependencies)
|
||||
```
|
||||
|
||||
### Dependency Tracking
|
||||
|
||||
State file tracks dependencies:
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"ep": {
|
||||
"depends_on": ["ra"],
|
||||
"ready": true, // RA completed
|
||||
"can_start": true
|
||||
},
|
||||
"cd": {
|
||||
"depends_on": ["ep"],
|
||||
"ready": true, // EP completed
|
||||
"can_start": true
|
||||
},
|
||||
"vas": {
|
||||
"depends_on": ["cd"],
|
||||
"ready": false, // CD not yet complete
|
||||
"can_start": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Iteration Flow with Communication
|
||||
|
||||
### Iteration 1: Initial Execution
|
||||
|
||||
```
|
||||
Time Agent Action State Update
|
||||
──────────────────────────────────────────────────────
|
||||
10:00 Init Create cycle status: running
|
||||
10:05 RA Start analysis agents.ra.status: running
|
||||
10:10 RA Complete (v1.0.0) agents.ra.status: completed
|
||||
10:10 EP Start planning agents.ep.status: running
|
||||
(depends on RA completion)
|
||||
10:15 EP Complete (v1.0.0) agents.ep.status: completed
|
||||
10:15 CD Start development agents.cd.status: running
|
||||
(depends on EP completion)
|
||||
10:30 CD Found blocker coordination.blockers.add()
|
||||
10:31 RA Clarify blocker requirements.v1.1.0 created
|
||||
10:35 CD Continue (with fix) agents.cd.status: running
|
||||
10:40 CD Complete agents.cd.status: completed
|
||||
10:40 VAS Start validation agents.vas.status: running
|
||||
(depends on CD completion)
|
||||
10:45 VAS Tests failing coordination.feedback_log.add()
|
||||
10:45 Orch Send feedback agents.cd.message: "Fix these tests"
|
||||
10:46 CD Resume (send_input) agents.cd.status: running
|
||||
10:48 CD Fix complete agents.cd.status: completed
|
||||
10:50 VAS Re-validate agents.vas.status: running
|
||||
10:55 VAS All pass agents.vas.status: completed
|
||||
11:00 Orch Complete cycle status: completed
|
||||
```
|
||||
|
||||
## Conflict Resolution
|
||||
|
||||
### Conflict Type 1: Unclear Requirement
|
||||
|
||||
**Scenario**: CD needs clarification on FR-X
|
||||
|
||||
**Resolution Flow**:
|
||||
1. CD reports blocker in issues.md
|
||||
2. Orchestrator extracts blocker
|
||||
3. Orchestrator sends message to RA
|
||||
4. RA updates requirements with clarification
|
||||
5. RA outputs new requirements.md (v1.1.0)
|
||||
6. Orchestrator sends message to CD with clarification
|
||||
7. CD resumes and continues
|
||||
|
||||
### Conflict Type 2: Test Failure
|
||||
|
||||
**Scenario**: VAS finds test failures
|
||||
|
||||
**Resolution Flow**:
|
||||
1. VAS reports failures in validation.md
|
||||
2. VAS outputs test-results.json with details
|
||||
3. Orchestrator extracts failure details
|
||||
4. Orchestrator categorizes failures
|
||||
5. If blocker: Orchestrator sends to CD/RA for fixes
|
||||
6. CD/RA fix and report completion
|
||||
7. Orchestrator sends CD/VAS to retry
|
||||
8. VAS re-validates
|
||||
|
||||
### Conflict Type 3: Plan Mismatch
|
||||
|
||||
**Scenario**: CD realizes plan tasks are incomplete
|
||||
|
||||
**Resolution Flow**:
|
||||
1. CD reports in issues.md
|
||||
2. Orchestrator extracts issue
|
||||
3. Orchestrator sends to EP to revise plan
|
||||
4. EP updates plan.json (v1.1.0)
|
||||
5. EP adds new tasks or dependencies
|
||||
6. Orchestrator sends to CD with updated plan
|
||||
7. CD implements remaining tasks
|
||||
|
||||
## Escalation Path
|
||||
|
||||
For issues that block resolution:
|
||||
|
||||
```
|
||||
Agent Issue
|
||||
↓
|
||||
Agent reports blocker
|
||||
↓
|
||||
Orchestrator analyzes
|
||||
↓
|
||||
Can fix automatically?
|
||||
├─ Yes: send_input to agent with fix
|
||||
└─ No: Escalate to user
|
||||
↓
|
||||
User provides guidance
|
||||
↓
|
||||
Orchestrator applies guidance
|
||||
↓
|
||||
Resume agents
|
||||
```
|
||||
|
||||
## Communication Best Practices
|
||||
|
||||
1. **Clear Timestamps**: All events timestamped ISO8601 format
|
||||
2. **Structured Messages**: Use consistent format for feedback
|
||||
3. **Version Tracking**: Always include version numbers
|
||||
4. **Audit Trail**: Maintain complete log of decisions
|
||||
5. **No Direct Agent Communication**: All communication via orchestrator
|
||||
6. **Document Decisions**: Record why decisions were made
|
||||
7. **Append-Only Logs**: Never delete history
|
||||
|
||||
## State Consistency Rules
|
||||
|
||||
1. **Single Writer Per Field**: Only one agent updates each field
|
||||
- RA writes: requirements, edge_cases
|
||||
- EP writes: exploration, plan
|
||||
- CD writes: changes, implementation
|
||||
- VAS writes: test_results, summary
|
||||
|
||||
2. **Read-Write Serialization**: Orchestrator ensures no conflicts
|
||||
|
||||
3. **Version Synchronization**: All versions increment together
|
||||
- v1.0.0 → v1.1.0 (all docs updated)
|
||||
|
||||
4. **Timestamp Consistency**: All timestamps in state file UTC+8
|
||||
|
||||
## Monitoring & Debugging
|
||||
|
||||
### State Inspection
|
||||
|
||||
```javascript
|
||||
// Check agent status
|
||||
const state = JSON.parse(Read(`.workflow/.cycle/${cycleId}.json`))
|
||||
console.log(state.agents) // See status of all agents
|
||||
|
||||
// Check for blockers
|
||||
console.log(state.coordination.blockers)
|
||||
|
||||
// Check feedback history
|
||||
console.log(state.coordination.feedback_log)
|
||||
```
|
||||
|
||||
### Log Analysis
|
||||
|
||||
```bash
|
||||
# Check RA progress
|
||||
tail .workflow/.cycle/cycle-xxx.progress/ra/changes.log
|
||||
|
||||
# Check CD changes
|
||||
grep "TASK-001" .workflow/.cycle/cycle-xxx.progress/cd/changes.log
|
||||
|
||||
# Check coordination timeline
|
||||
tail -50 .workflow/.cycle/cycle-xxx.progress/coordination/feedback.md
|
||||
```
|
||||
@@ -1,331 +0,0 @@
|
||||
# Document Versioning Strategy
|
||||
|
||||
Document version management strategy: Complete Rewrite + Archive History
|
||||
|
||||
## Recommended Approach: Complete Rewrite + Archive History
|
||||
|
||||
For each iteration, **completely rewrite** the main document, and automatically archive the old version to the `history/` directory.
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
.workflow/.cycle/cycle-v1-20260122-abc123.progress/
|
||||
├── ra/
|
||||
│ ├── requirements.md # v1.2.0 (current version, complete rewrite)
|
||||
│ ├── edge-cases.md # v1.2.0 (current version, complete rewrite)
|
||||
│ ├── changes.log # NDJSON complete change history (append-only)
|
||||
│ └── history/
|
||||
│ ├── requirements-v1.0.0.md (archived)
|
||||
│ ├── requirements-v1.1.0.md (archived)
|
||||
│ ├── edge-cases-v1.0.0.md (archived)
|
||||
│ └── edge-cases-v1.1.0.md (archived)
|
||||
├── ep/
|
||||
│ ├── exploration.md # v1.2.0 (current)
|
||||
│ ├── architecture.md # v1.2.0 (current)
|
||||
│ ├── plan.json # v1.2.0 (current)
|
||||
│ └── history/
|
||||
│ ├── plan-v1.0.0.json
|
||||
│ └── plan-v1.1.0.json
|
||||
├── cd/
|
||||
│ ├── implementation.md # v1.2.0 (current)
|
||||
│ ├── changes.log # NDJSON complete history
|
||||
│ ├── debug-log.ndjson # Debug hypothesis tracking
|
||||
│ ├── issues.md # Current unresolved issues
|
||||
│ └── history/
|
||||
│ ├── implementation-v1.0.0.md
|
||||
│ └── implementation-v1.1.0.md
|
||||
└── vas/
|
||||
├── validation.md # v1.2.0 (current)
|
||||
├── test-results.json # v1.2.0 (current)
|
||||
├── summary.md # v1.2.0 (current)
|
||||
└── history/
|
||||
├── validation-v1.0.0.md
|
||||
└── test-results-v1.0.0.json
|
||||
```
|
||||
|
||||
## Optimized Document Template
|
||||
|
||||
### Requirements.md (Complete Rewrite Version)
|
||||
|
||||
```markdown
|
||||
# Requirements Specification - v1.2.0
|
||||
|
||||
## Document Metadata
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Version | 1.2.0 |
|
||||
| Previous | 1.1.0 (Added Google OAuth) |
|
||||
| Changes | Added MFA, GitHub provider |
|
||||
| Date | 2026-01-23T10:00:00+08:00 |
|
||||
| Cycle | cycle-v1-20260122-abc123 |
|
||||
| Iteration | 3 |
|
||||
|
||||
---
|
||||
|
||||
## Functional Requirements
|
||||
|
||||
### FR-001: OAuth Authentication
|
||||
**Description**: Users can log in using OAuth providers.
|
||||
|
||||
**Supported Providers**: Google, GitHub
|
||||
|
||||
**Priority**: High
|
||||
|
||||
**Status**: ✓ Implemented (v1.0.0), Enhanced (v1.1.0, v1.2.0)
|
||||
|
||||
**Success Criteria**:
|
||||
- User can click provider button
|
||||
- Redirect to provider
|
||||
- Return with valid token
|
||||
- Session created
|
||||
|
||||
---
|
||||
|
||||
### FR-002: Multi-Provider Support
|
||||
**Description**: System supports multiple OAuth providers simultaneously.
|
||||
|
||||
**Providers**:
|
||||
- Google (v1.1.0)
|
||||
- GitHub (v1.2.0)
|
||||
|
||||
**Priority**: High
|
||||
|
||||
**Status**: ✓ Implemented
|
||||
|
||||
---
|
||||
|
||||
### FR-003: Multi-Factor Authentication
|
||||
**Description**: Optional MFA for enhanced security.
|
||||
|
||||
**Method**: TOTP (Time-based One-Time Password)
|
||||
|
||||
**Priority**: Medium
|
||||
|
||||
**Status**: 🆕 New in v1.2.0
|
||||
|
||||
**Success Criteria**:
|
||||
- User can enable MFA in settings
|
||||
- TOTP QR code generated
|
||||
- Verification on login
|
||||
|
||||
---
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
### NFR-001: Performance
|
||||
Response time < 500ms for all OAuth flows.
|
||||
|
||||
**Status**: ✓ Met (v1.0.0)
|
||||
|
||||
---
|
||||
|
||||
## Edge Cases
|
||||
|
||||
### EC-001: OAuth Provider Timeout
|
||||
**Scenario**: Provider doesn't respond in 5 seconds
|
||||
|
||||
**Expected**: Display error, offer retry
|
||||
|
||||
**Status**: ✓ Handled
|
||||
|
||||
---
|
||||
|
||||
### EC-002: Invalid MFA Code (NEW v1.2.0)
|
||||
**Scenario**: User enters incorrect TOTP code
|
||||
|
||||
**Expected**: Display error, max 3 attempts, lock after
|
||||
|
||||
**Status**: 🔄 To be implemented
|
||||
|
||||
---
|
||||
|
||||
## Constraints
|
||||
- Must use existing JWT session management
|
||||
- No new database servers
|
||||
- Compatible with existing user table
|
||||
|
||||
---
|
||||
|
||||
## Assumptions
|
||||
- Users have access to authenticator app for MFA
|
||||
- OAuth providers are always available
|
||||
|
||||
---
|
||||
|
||||
## Version History Summary
|
||||
|
||||
| Version | Date | Summary |
|
||||
|---------|------|---------|
|
||||
| 1.0.0 | 2026-01-22 | Initial OAuth login (Google only implicit) |
|
||||
| 1.1.0 | 2026-01-22 | + Explicit Google OAuth support |
|
||||
| 1.2.0 | 2026-01-23 | + GitHub provider, + MFA (current) |
|
||||
|
||||
**Detailed History**: See `history/` directory and `changes.log`
|
||||
```
|
||||
|
||||
### Changes.log (NDJSON - Complete History)
|
||||
|
||||
```jsonl
|
||||
{"timestamp":"2026-01-22T10:00:00+08:00","iteration":1,"version":"1.0.0","action":"create","type":"requirement","id":"FR-001","description":"Initial OAuth requirement"}
|
||||
{"timestamp":"2026-01-22T10:05:00+08:00","iteration":1,"version":"1.0.0","action":"create","type":"requirement","id":"NFR-001","description":"Performance requirement"}
|
||||
{"timestamp":"2026-01-22T11:00:00+08:00","iteration":2,"version":"1.1.0","action":"update","type":"requirement","id":"FR-001","description":"Clarified Google OAuth support"}
|
||||
{"timestamp":"2026-01-22T11:05:00+08:00","iteration":2,"version":"1.1.0","action":"create","type":"requirement","id":"FR-002","description":"Multi-provider support"}
|
||||
{"timestamp":"2026-01-23T10:00:00+08:00","iteration":3,"version":"1.2.0","action":"create","type":"requirement","id":"FR-003","description":"MFA requirement"}
|
||||
{"timestamp":"2026-01-23T10:05:00+08:00","iteration":3,"version":"1.2.0","action":"update","type":"requirement","id":"FR-002","description":"Added GitHub provider"}
|
||||
```
|
||||
|
||||
## Implementation Flow
|
||||
|
||||
### Agent Workflow (RA Example)
|
||||
|
||||
```javascript
|
||||
// ==================== RA Agent Iteration Flow ====================
|
||||
|
||||
// Read current state
|
||||
const state = JSON.parse(Read(`.workflow/.cycle/${cycleId}.json`))
|
||||
const currentVersion = state.requirements?.version || "0.0.0"
|
||||
const iteration = state.current_iteration
|
||||
|
||||
// If iteration (old version exists)
|
||||
if (currentVersion !== "0.0.0") {
|
||||
// 1. Archive old version
|
||||
const oldFile = `.workflow/.cycle/${cycleId}.progress/ra/requirements.md`
|
||||
const archiveFile = `.workflow/.cycle/${cycleId}.progress/ra/history/requirements-v${currentVersion}.md`
|
||||
|
||||
Copy(oldFile, archiveFile) // Archive
|
||||
|
||||
// 2. Read old version (optional, for context understanding)
|
||||
const oldRequirements = Read(oldFile)
|
||||
|
||||
// 3. Read change history
|
||||
const changesLog = readNDJSON(`.workflow/.cycle/${cycleId}.progress/ra/changes.log`)
|
||||
}
|
||||
|
||||
// 4. Generate new version number
|
||||
const newVersion = bumpVersion(currentVersion, 'minor') // 1.1.0 -> 1.2.0
|
||||
|
||||
// 5. Generate new document (complete rewrite)
|
||||
const newRequirements = generateRequirements({
|
||||
version: newVersion,
|
||||
previousVersion: currentVersion,
|
||||
previousSummary: "Added Google OAuth support",
|
||||
currentChanges: "Added MFA and GitHub provider",
|
||||
iteration: iteration,
|
||||
taskDescription: state.description,
|
||||
changesLog: changesLog // For understanding history
|
||||
})
|
||||
|
||||
// 6. Write new document (overwrite old)
|
||||
Write(`.workflow/.cycle/${cycleId}.progress/ra/requirements.md`, newRequirements)
|
||||
|
||||
// 7. Append change to changes.log
|
||||
appendNDJSON(`.workflow/.cycle/${cycleId}.progress/ra/changes.log`, {
|
||||
timestamp: getUtc8ISOString(),
|
||||
iteration: iteration,
|
||||
version: newVersion,
|
||||
action: "create",
|
||||
type: "requirement",
|
||||
id: "FR-003",
|
||||
description: "Added MFA requirement"
|
||||
})
|
||||
|
||||
// 8. Update state
|
||||
state.requirements = {
|
||||
version: newVersion,
|
||||
output_file: `.workflow/.cycle/${cycleId}.progress/ra/requirements.md`,
|
||||
summary: {
|
||||
functional_requirements: 3,
|
||||
edge_cases: 2,
|
||||
constraints: 3
|
||||
}
|
||||
}
|
||||
|
||||
Write(`.workflow/.cycle/${cycleId}.json`, JSON.stringify(state, null, 2))
|
||||
```
|
||||
|
||||
## Advantages Comparison
|
||||
|
||||
| Aspect | Incremental Update | Complete Rewrite + Archive |
|
||||
|--------|-------------------|---------------------------|
|
||||
| **Document Conciseness** | ❌ Gets longer | ✅ Always concise |
|
||||
| **Agent Parsing** | ❌ Must parse history | ✅ Only read current version |
|
||||
| **Maintenance Complexity** | ❌ High (version marking) | ✅ Low (direct rewrite) |
|
||||
| **File Size** | ❌ Bloats | ✅ Fixed |
|
||||
| **History Tracking** | ✅ In main document | ✅ In history/ + changes.log |
|
||||
| **Human Readability** | ❌ Must skip history | ✅ Direct current view |
|
||||
| **Token Usage** | ❌ More (read complete history) | ✅ Less (only read current) |
|
||||
|
||||
## Archive Strategy
|
||||
|
||||
### Auto-Archive Trigger
|
||||
|
||||
```javascript
|
||||
function shouldArchive(currentVersion, state) {
|
||||
// Archive on each version update
|
||||
return currentVersion !== state.requirements?.version
|
||||
}
|
||||
|
||||
function archiveOldVersion(cycleId, agent, filename, currentVersion) {
|
||||
const currentFile = `.workflow/.cycle/${cycleId}.progress/${agent}/${filename}`
|
||||
const archiveDir = `.workflow/.cycle/${cycleId}.progress/${agent}/history`
|
||||
const archiveFile = `${archiveDir}/${filename.replace('.', `-v${currentVersion}.`)}`
|
||||
|
||||
// Ensure archive directory exists
|
||||
mkdir -p ${archiveDir}
|
||||
|
||||
// Copy (not move, keep current file until new version written)
|
||||
Copy(currentFile, archiveFile)
|
||||
|
||||
console.log(`Archived ${filename} v${currentVersion} to history/`)
|
||||
}
|
||||
```
|
||||
|
||||
### Cleanup Strategy (Optional)
|
||||
|
||||
Keep most recent N versions, delete older archives:
|
||||
|
||||
```javascript
|
||||
function cleanupArchives(cycleId, agent, keepVersions = 3) {
|
||||
const historyDir = `.workflow/.cycle/${cycleId}.progress/${agent}/history`
|
||||
const archives = listFiles(historyDir)
|
||||
|
||||
// Sort by version number
|
||||
archives.sort((a, b) => compareVersions(extractVersion(a), extractVersion(b)))
|
||||
|
||||
// Delete oldest versions (keep most recent N)
|
||||
if (archives.length > keepVersions) {
|
||||
const toDelete = archives.slice(0, archives.length - keepVersions)
|
||||
toDelete.forEach(file => Delete(`${historyDir}/${file}`))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Importance of Changes.log
|
||||
|
||||
Although main document is completely rewritten, **changes.log (NDJSON) permanently preserves complete history**:
|
||||
|
||||
```bash
|
||||
# View all changes
|
||||
cat .workflow/.cycle/cycle-xxx.progress/ra/changes.log | jq .
|
||||
|
||||
# View history of specific requirement
|
||||
cat .workflow/.cycle/cycle-xxx.progress/ra/changes.log | jq 'select(.id=="FR-001")'
|
||||
|
||||
# View changes by iteration
|
||||
cat .workflow/.cycle/cycle-xxx.progress/ra/changes.log | jq 'select(.iteration==2)'
|
||||
```
|
||||
|
||||
This way:
|
||||
- **Main Document**: Clear and concise (current state)
|
||||
- **Changes.log**: Complete traceability (all history)
|
||||
- **History/**: Snapshot backups (view on demand)
|
||||
|
||||
## Recommended Implementation
|
||||
|
||||
1. ✅ Adopt "Complete Rewrite" strategy
|
||||
2. ✅ Main document only keeps "previous version summary"
|
||||
3. ✅ Auto-archive to `history/` directory
|
||||
4. ✅ Changes.log (NDJSON) preserves complete history
|
||||
5. ✅ Optional: Keep most recent 3-5 historical versions
|
||||
|
||||
This approach keeps documents concise (agent-friendly) while preserving complete history (audit-friendly).
|
||||
@@ -1,84 +1 @@
|
||||
{
|
||||
"theme.AnnouncementBar.closeButtonAriaLabel": "关闭",
|
||||
"theme.BackToTopButton.buttonAriaLabel": "回到顶部",
|
||||
"theme.CodeBlock.copied": "复制成功",
|
||||
"theme.CodeBlock.copy": "复制",
|
||||
"theme.CodeBlock.copyButtonAriaLabel": "复制代码到剪贴板",
|
||||
"theme.CodeBlock.wordWrapToggle": "切换自动换行",
|
||||
"theme.DocSidebarItem.collapseCategoryAriaLabel": "折叠侧边栏分类 '{label}'",
|
||||
"theme.DocSidebarItem.expandCategoryAriaLabel": "展开侧边栏分类 '{label}'",
|
||||
"theme.ErrorPageContent.title": "页面已崩溃。",
|
||||
"theme.ErrorPageContent.tryAgain": "重试",
|
||||
"theme.IconExternalLink.ariaLabel": "(opens in new tab)",
|
||||
"theme.NavBar.navAriaLabel": "主导航",
|
||||
"theme.NotFound.p1": "我们找不到您要找的页面。",
|
||||
"theme.NotFound.p2": "请联系原始链接来源网站的所有者,并告知他们链接已损坏。",
|
||||
"theme.NotFound.title": "找不到页面",
|
||||
"theme.TOCCollapsible.toggleButtonLabel": "本页总览",
|
||||
"theme.admonition.caution": "警告",
|
||||
"theme.admonition.danger": "危险",
|
||||
"theme.admonition.info": "信息",
|
||||
"theme.admonition.note": "备注",
|
||||
"theme.admonition.tip": "提示",
|
||||
"theme.admonition.warning": "注意",
|
||||
"theme.blog.archive.description": "历史博文",
|
||||
"theme.blog.archive.title": "历史博文",
|
||||
"theme.blog.author.noPosts": "该作者尚未撰写任何文章。",
|
||||
"theme.blog.author.pageTitle": "{authorName} - {nPosts}",
|
||||
"theme.blog.authorsList.pageTitle": "作者",
|
||||
"theme.blog.authorsList.viewAll": "查看所有作者",
|
||||
"theme.blog.paginator.navAriaLabel": "博文列表分页导航",
|
||||
"theme.blog.paginator.newerEntries": "较新的博文",
|
||||
"theme.blog.paginator.olderEntries": "较旧的博文",
|
||||
"theme.blog.post.paginator.navAriaLabel": "博文分页导航",
|
||||
"theme.blog.post.paginator.newerPost": "较新一篇",
|
||||
"theme.blog.post.paginator.olderPost": "较旧一篇",
|
||||
"theme.blog.post.plurals": "{count} 篇博文",
|
||||
"theme.blog.post.readMore": "阅读更多",
|
||||
"theme.blog.post.readMoreLabel": "阅读 {title} 的全文",
|
||||
"theme.blog.post.readingTime.plurals": "阅读需 {readingTime} 分钟",
|
||||
"theme.blog.sidebar.navAriaLabel": "最近博文导航",
|
||||
"theme.blog.tagTitle": "{nPosts} 含有标签「{tagName}」",
|
||||
"theme.colorToggle.ariaLabel": "切换浅色/暗黑模式(当前为{mode})",
|
||||
"theme.colorToggle.ariaLabel.mode.dark": "暗黑模式",
|
||||
"theme.colorToggle.ariaLabel.mode.light": "浅色模式",
|
||||
"theme.colorToggle.ariaLabel.mode.system": "system mode",
|
||||
"theme.common.editThisPage": "编辑此页",
|
||||
"theme.common.headingLinkTitle": "{heading}的直接链接",
|
||||
"theme.common.skipToMainContent": "跳到主要内容",
|
||||
"theme.contentVisibility.draftBanner.message": "此页面是草稿,仅在开发环境中可见,不会包含在正式版本中。",
|
||||
"theme.contentVisibility.draftBanner.title": "草稿页",
|
||||
"theme.contentVisibility.unlistedBanner.message": "此页面未列出。搜索引擎不会对其索引,只有拥有直接链接的用户才能访问。",
|
||||
"theme.contentVisibility.unlistedBanner.title": "未列出页",
|
||||
"theme.docs.DocCard.categoryDescription.plurals": "{count} 个项目",
|
||||
"theme.docs.breadcrumbs.home": "主页面",
|
||||
"theme.docs.breadcrumbs.navAriaLabel": "页面路径",
|
||||
"theme.docs.paginator.navAriaLabel": "文件选项卡",
|
||||
"theme.docs.paginator.next": "下一页",
|
||||
"theme.docs.paginator.previous": "上一页",
|
||||
"theme.docs.sidebar.closeSidebarButtonAriaLabel": "关闭导航栏",
|
||||
"theme.docs.sidebar.collapseButtonAriaLabel": "收起侧边栏",
|
||||
"theme.docs.sidebar.collapseButtonTitle": "收起侧边栏",
|
||||
"theme.docs.sidebar.expandButtonAriaLabel": "展开侧边栏",
|
||||
"theme.docs.sidebar.expandButtonTitle": "展开侧边栏",
|
||||
"theme.docs.sidebar.navAriaLabel": "文档侧边栏",
|
||||
"theme.docs.sidebar.toggleSidebarButtonAriaLabel": "切换导航栏",
|
||||
"theme.docs.tagDocListPageTitle": "{nDocsTagged}「{tagName}」",
|
||||
"theme.docs.tagDocListPageTitle.nDocsTagged": "{count} 篇文档带有标签",
|
||||
"theme.docs.versionBadge.label": "版本:{versionLabel}",
|
||||
"theme.docs.versions.latestVersionLinkLabel": "最新版本",
|
||||
"theme.docs.versions.latestVersionSuggestionLabel": "最新的文档请参阅 {latestVersionLink} ({versionLabel})。",
|
||||
"theme.docs.versions.unmaintainedVersionLabel": "此为 {siteTitle} {versionLabel} 版的文档,现已不再积极维护。",
|
||||
"theme.docs.versions.unreleasedVersionLabel": "此为 {siteTitle} {versionLabel} 版尚未发行的文档。",
|
||||
"theme.lastUpdated.atDate": "于 {date} ",
|
||||
"theme.lastUpdated.byUser": "由 {user} ",
|
||||
"theme.lastUpdated.lastUpdatedAtBy": "最后{byUser}{atDate}更新",
|
||||
"theme.navbar.mobileDropdown.collapseButton.collapseAriaLabel": "Collapse the dropdown",
|
||||
"theme.navbar.mobileDropdown.collapseButton.expandAriaLabel": "Expand the dropdown",
|
||||
"theme.navbar.mobileLanguageDropdown.label": "选择语言",
|
||||
"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": "← 回到主菜单",
|
||||
"theme.navbar.mobileVersionsDropdown.label": "选择版本",
|
||||
"theme.tags.tagsListLabel": "标签:",
|
||||
"theme.tags.tagsPageLink": "查看所有标签",
|
||||
"theme.tags.tagsPageTitle": "标签"
|
||||
}
|
||||
{}
|
||||
@@ -1 +1 @@
|
||||
{"options":{"path":"docs","routeBasePath":"/","sidebarPath":"D:\\Claude_dms3\\ccw\\docs-site\\sidebars.ts","editUrl":"https://github.com/ccw/docs/tree/main/","editCurrentVersion":false,"editLocalizedFiles":false,"tagsBasePath":"tags","include":["**/*.{md,mdx}"],"exclude":["**/_*.{js,jsx,ts,tsx,md,mdx}","**/_*/**","**/*.test.{js,jsx,ts,tsx}","**/__tests__/**"],"sidebarCollapsible":true,"sidebarCollapsed":true,"docsRootComponent":"@theme/DocsRoot","docVersionRootComponent":"@theme/DocVersionRoot","docRootComponent":"@theme/DocRoot","docItemComponent":"@theme/DocItem","docTagsListComponent":"@theme/DocTagsListPage","docTagDocListComponent":"@theme/DocTagDocListPage","docCategoryGeneratedIndexComponent":"@theme/DocCategoryGeneratedIndexPage","remarkPlugins":[],"rehypePlugins":[],"recmaPlugins":[],"beforeDefaultRemarkPlugins":[],"beforeDefaultRehypePlugins":[],"admonitions":true,"showLastUpdateTime":false,"showLastUpdateAuthor":false,"includeCurrentVersion":true,"disableVersioning":false,"versions":{},"breadcrumbs":true,"onInlineTags":"warn","id":"default"},"versionsMetadata":[{"versionName":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","path":"/docs/zh/","tagsPath":"/docs/zh/tags","editUrl":"https://github.com/ccw/docs/tree/main/docs","editUrlLocalized":"https://github.com/ccw/docs/tree/main/i18n/zh/docusaurus-plugin-content-docs/current","isLast":true,"routePriority":-1,"sidebarFilePath":"D:\\Claude_dms3\\ccw\\docs-site\\sidebars.ts","contentPath":"D:\\Claude_dms3\\ccw\\docs-site\\docs","contentPathLocalized":"D:\\Claude_dms3\\ccw\\docs-site\\i18n\\zh\\docusaurus-plugin-content-docs\\current"}]}
|
||||
{"options":{"path":"docs","routeBasePath":"/","sidebarPath":"D:\\Claude_dms3\\ccw\\docs-site\\sidebars.ts","editUrl":"https://github.com/ccw/docs/tree/main/","editCurrentVersion":false,"editLocalizedFiles":false,"tagsBasePath":"tags","include":["**/*.{md,mdx}"],"exclude":["**/_*.{js,jsx,ts,tsx,md,mdx}","**/_*/**","**/*.test.{js,jsx,ts,tsx}","**/__tests__/**"],"sidebarCollapsible":true,"sidebarCollapsed":true,"docsRootComponent":"@theme/DocsRoot","docVersionRootComponent":"@theme/DocVersionRoot","docRootComponent":"@theme/DocRoot","docItemComponent":"@theme/DocItem","docTagsListComponent":"@theme/DocTagsListPage","docTagDocListComponent":"@theme/DocTagDocListPage","docCategoryGeneratedIndexComponent":"@theme/DocCategoryGeneratedIndexPage","remarkPlugins":[],"rehypePlugins":[],"recmaPlugins":[],"beforeDefaultRemarkPlugins":[],"beforeDefaultRehypePlugins":[],"admonitions":true,"showLastUpdateTime":false,"showLastUpdateAuthor":false,"includeCurrentVersion":true,"disableVersioning":false,"versions":{},"breadcrumbs":true,"onInlineTags":"warn","id":"default"},"versionsMetadata":[{"versionName":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","path":"/docs/","tagsPath":"/docs/tags","editUrl":"https://github.com/ccw/docs/tree/main/docs","isLast":true,"routePriority":-1,"sidebarFilePath":"D:\\Claude_dms3\\ccw\\docs-site\\sidebars.ts","contentPath":"D:\\Claude_dms3\\ccw\\docs-site\\docs"}]}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/cli/cli-init.mdx",
|
||||
"sourceDirName": "commands/cli",
|
||||
"slug": "/commands/cli/cli-init",
|
||||
"permalink": "/docs/zh/commands/cli/cli-init",
|
||||
"permalink": "/docs/commands/cli/cli-init",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/cli/cli-init.mdx",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "issue:convert-to-plan",
|
||||
"permalink": "/docs/zh/commands/issue/issue-convert-to-plan"
|
||||
"permalink": "/docs/commands/issue/issue-convert-to-plan"
|
||||
},
|
||||
"next": {
|
||||
"title": "/cli:codex-review",
|
||||
"permalink": "/docs/zh/commands/cli/codex-review"
|
||||
"permalink": "/docs/commands/cli/codex-review"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/cli/codex-review.mdx",
|
||||
"sourceDirName": "commands/cli",
|
||||
"slug": "/commands/cli/codex-review",
|
||||
"permalink": "/docs/zh/commands/cli/codex-review",
|
||||
"permalink": "/docs/commands/cli/codex-review",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/cli/codex-review.mdx",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "/cli:cli-init",
|
||||
"permalink": "/docs/zh/commands/cli/cli-init"
|
||||
"permalink": "/docs/commands/cli/cli-init"
|
||||
},
|
||||
"next": {
|
||||
"title": "/memory:update-full",
|
||||
"permalink": "/docs/zh/commands/memory/memory-update-full"
|
||||
"permalink": "/docs/commands/memory/memory-update-full"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/general/ccw-coordinator.mdx",
|
||||
"sourceDirName": "commands/general",
|
||||
"slug": "/commands/general/ccw-coordinator",
|
||||
"permalink": "/docs/zh/commands/general/ccw-coordinator",
|
||||
"permalink": "/docs/commands/general/ccw-coordinator",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/general/ccw-coordinator.mdx",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "/ccw-test",
|
||||
"permalink": "/docs/zh/commands/general/ccw-test"
|
||||
"permalink": "/docs/commands/general/ccw-test"
|
||||
},
|
||||
"next": {
|
||||
"title": "/ccw-debug",
|
||||
"permalink": "/docs/zh/commands/general/ccw-debug"
|
||||
"permalink": "/docs/commands/general/ccw-debug"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/general/ccw-debug.mdx",
|
||||
"sourceDirName": "commands/general",
|
||||
"slug": "/commands/general/ccw-debug",
|
||||
"permalink": "/docs/zh/commands/general/ccw-debug",
|
||||
"permalink": "/docs/commands/general/ccw-debug",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/general/ccw-debug.mdx",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "/ccw-coordinator",
|
||||
"permalink": "/docs/zh/commands/general/ccw-coordinator"
|
||||
"permalink": "/docs/commands/general/ccw-coordinator"
|
||||
},
|
||||
"next": {
|
||||
"title": "/flow-create",
|
||||
"permalink": "/docs/zh/commands/general/flow-create"
|
||||
"permalink": "/docs/commands/general/flow-create"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/general/ccw.mdx",
|
||||
"sourceDirName": "commands/general",
|
||||
"slug": "/commands/general/ccw",
|
||||
"permalink": "/docs/zh/commands/general/ccw",
|
||||
"permalink": "/docs/commands/general/ccw",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/general/ccw.mdx",
|
||||
@@ -20,11 +20,11 @@
|
||||
},
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "概览",
|
||||
"permalink": "/docs/zh/overview"
|
||||
"title": "Overview",
|
||||
"permalink": "/docs/overview"
|
||||
},
|
||||
"next": {
|
||||
"title": "/ccw-plan",
|
||||
"permalink": "/docs/zh/commands/general/ccw-plan"
|
||||
"permalink": "/docs/commands/general/ccw-plan"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/general/ccw-plan.mdx",
|
||||
"sourceDirName": "commands/general",
|
||||
"slug": "/commands/general/ccw-plan",
|
||||
"permalink": "/docs/zh/commands/general/ccw-plan",
|
||||
"permalink": "/docs/commands/general/ccw-plan",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/general/ccw-plan.mdx",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "/ccw",
|
||||
"permalink": "/docs/zh/commands/general/ccw"
|
||||
"permalink": "/docs/commands/general/ccw"
|
||||
},
|
||||
"next": {
|
||||
"title": "/ccw-test",
|
||||
"permalink": "/docs/zh/commands/general/ccw-test"
|
||||
"permalink": "/docs/commands/general/ccw-test"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/general/ccw-test.mdx",
|
||||
"sourceDirName": "commands/general",
|
||||
"slug": "/commands/general/ccw-test",
|
||||
"permalink": "/docs/zh/commands/general/ccw-test",
|
||||
"permalink": "/docs/commands/general/ccw-test",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/general/ccw-test.mdx",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "/ccw-plan",
|
||||
"permalink": "/docs/zh/commands/general/ccw-plan"
|
||||
"permalink": "/docs/commands/general/ccw-plan"
|
||||
},
|
||||
"next": {
|
||||
"title": "/ccw-coordinator",
|
||||
"permalink": "/docs/zh/commands/general/ccw-coordinator"
|
||||
"permalink": "/docs/commands/general/ccw-coordinator"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/general/codex-coordinator.mdx",
|
||||
"sourceDirName": "commands/general",
|
||||
"slug": "/commands/general/codex-coordinator",
|
||||
"permalink": "/docs/zh/commands/general/codex-coordinator",
|
||||
"permalink": "/docs/commands/general/codex-coordinator",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/general/codex-coordinator.mdx",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "/flow-create",
|
||||
"permalink": "/docs/zh/commands/general/flow-create"
|
||||
"permalink": "/docs/commands/general/flow-create"
|
||||
},
|
||||
"next": {
|
||||
"title": "issue:new",
|
||||
"permalink": "/docs/zh/commands/issue/issue-new"
|
||||
"permalink": "/docs/commands/issue/issue-new"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/general/flow-create.mdx",
|
||||
"sourceDirName": "commands/general",
|
||||
"slug": "/commands/general/flow-create",
|
||||
"permalink": "/docs/zh/commands/general/flow-create",
|
||||
"permalink": "/docs/commands/general/flow-create",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/general/flow-create.mdx",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "/ccw-debug",
|
||||
"permalink": "/docs/zh/commands/general/ccw-debug"
|
||||
"permalink": "/docs/commands/general/ccw-debug"
|
||||
},
|
||||
"next": {
|
||||
"title": "/codex-coordinator",
|
||||
"permalink": "/docs/zh/commands/general/codex-coordinator"
|
||||
"permalink": "/docs/commands/general/codex-coordinator"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/issue/issue-convert-to-plan.md",
|
||||
"sourceDirName": "commands/issue",
|
||||
"slug": "/commands/issue/issue-convert-to-plan",
|
||||
"permalink": "/docs/zh/commands/issue/issue-convert-to-plan",
|
||||
"permalink": "/docs/commands/issue/issue-convert-to-plan",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/issue/issue-convert-to-plan.md",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "issue:from-brainstorm",
|
||||
"permalink": "/docs/zh/commands/issue/issue-from-brainstorm"
|
||||
"permalink": "/docs/commands/issue/issue-from-brainstorm"
|
||||
},
|
||||
"next": {
|
||||
"title": "/cli:cli-init",
|
||||
"permalink": "/docs/zh/commands/cli/cli-init"
|
||||
"permalink": "/docs/commands/cli/cli-init"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/issue/issue-discover.md",
|
||||
"sourceDirName": "commands/issue",
|
||||
"slug": "/commands/issue/issue-discover",
|
||||
"permalink": "/docs/zh/commands/issue/issue-discover",
|
||||
"permalink": "/docs/commands/issue/issue-discover",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/issue/issue-discover.md",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "issue:new",
|
||||
"permalink": "/docs/zh/commands/issue/issue-new"
|
||||
"permalink": "/docs/commands/issue/issue-new"
|
||||
},
|
||||
"next": {
|
||||
"title": "issue:plan",
|
||||
"permalink": "/docs/zh/commands/issue/issue-plan"
|
||||
"permalink": "/docs/commands/issue/issue-plan"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/issue/issue-execute.md",
|
||||
"sourceDirName": "commands/issue",
|
||||
"slug": "/commands/issue/issue-execute",
|
||||
"permalink": "/docs/zh/commands/issue/issue-execute",
|
||||
"permalink": "/docs/commands/issue/issue-execute",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/issue/issue-execute.md",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "issue:queue",
|
||||
"permalink": "/docs/zh/commands/issue/issue-queue"
|
||||
"permalink": "/docs/commands/issue/issue-queue"
|
||||
},
|
||||
"next": {
|
||||
"title": "issue:from-brainstorm",
|
||||
"permalink": "/docs/zh/commands/issue/issue-from-brainstorm"
|
||||
"permalink": "/docs/commands/issue/issue-from-brainstorm"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/issue/issue-from-brainstorm.md",
|
||||
"sourceDirName": "commands/issue",
|
||||
"slug": "/commands/issue/issue-from-brainstorm",
|
||||
"permalink": "/docs/zh/commands/issue/issue-from-brainstorm",
|
||||
"permalink": "/docs/commands/issue/issue-from-brainstorm",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/issue/issue-from-brainstorm.md",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "issue:execute",
|
||||
"permalink": "/docs/zh/commands/issue/issue-execute"
|
||||
"permalink": "/docs/commands/issue/issue-execute"
|
||||
},
|
||||
"next": {
|
||||
"title": "issue:convert-to-plan",
|
||||
"permalink": "/docs/zh/commands/issue/issue-convert-to-plan"
|
||||
"permalink": "/docs/commands/issue/issue-convert-to-plan"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/issue/issue-new.md",
|
||||
"sourceDirName": "commands/issue",
|
||||
"slug": "/commands/issue/issue-new",
|
||||
"permalink": "/docs/zh/commands/issue/issue-new",
|
||||
"permalink": "/docs/commands/issue/issue-new",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/issue/issue-new.md",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "/codex-coordinator",
|
||||
"permalink": "/docs/zh/commands/general/codex-coordinator"
|
||||
"permalink": "/docs/commands/general/codex-coordinator"
|
||||
},
|
||||
"next": {
|
||||
"title": "issue:discover",
|
||||
"permalink": "/docs/zh/commands/issue/issue-discover"
|
||||
"permalink": "/docs/commands/issue/issue-discover"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/issue/issue-plan.md",
|
||||
"sourceDirName": "commands/issue",
|
||||
"slug": "/commands/issue/issue-plan",
|
||||
"permalink": "/docs/zh/commands/issue/issue-plan",
|
||||
"permalink": "/docs/commands/issue/issue-plan",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/issue/issue-plan.md",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "issue:discover",
|
||||
"permalink": "/docs/zh/commands/issue/issue-discover"
|
||||
"permalink": "/docs/commands/issue/issue-discover"
|
||||
},
|
||||
"next": {
|
||||
"title": "issue:queue",
|
||||
"permalink": "/docs/zh/commands/issue/issue-queue"
|
||||
"permalink": "/docs/commands/issue/issue-queue"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/issue/issue-queue.md",
|
||||
"sourceDirName": "commands/issue",
|
||||
"slug": "/commands/issue/issue-queue",
|
||||
"permalink": "/docs/zh/commands/issue/issue-queue",
|
||||
"permalink": "/docs/commands/issue/issue-queue",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/issue/issue-queue.md",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "issue:plan",
|
||||
"permalink": "/docs/zh/commands/issue/issue-plan"
|
||||
"permalink": "/docs/commands/issue/issue-plan"
|
||||
},
|
||||
"next": {
|
||||
"title": "issue:execute",
|
||||
"permalink": "/docs/zh/commands/issue/issue-execute"
|
||||
"permalink": "/docs/commands/issue/issue-execute"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/memory/memory-compact.mdx",
|
||||
"sourceDirName": "commands/memory",
|
||||
"slug": "/commands/memory/memory-compact",
|
||||
"permalink": "/docs/zh/commands/memory/memory-compact",
|
||||
"permalink": "/docs/commands/memory/memory-compact",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/memory/memory-compact.mdx",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "/memory:docs-related-cli",
|
||||
"permalink": "/docs/zh/commands/memory/memory-docs-related-cli"
|
||||
"permalink": "/docs/commands/memory/memory-docs-related-cli"
|
||||
},
|
||||
"next": {
|
||||
"title": "Introduction",
|
||||
"permalink": "/docs/zh/workflows/introduction"
|
||||
"permalink": "/docs/workflows/introduction"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/memory/memory-docs-full-cli.mdx",
|
||||
"sourceDirName": "commands/memory",
|
||||
"slug": "/commands/memory/memory-docs-full-cli",
|
||||
"permalink": "/docs/zh/commands/memory/memory-docs-full-cli",
|
||||
"permalink": "/docs/commands/memory/memory-docs-full-cli",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/memory/memory-docs-full-cli.mdx",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "/memory:load",
|
||||
"permalink": "/docs/zh/commands/memory/memory-load"
|
||||
"permalink": "/docs/commands/memory/memory-load"
|
||||
},
|
||||
"next": {
|
||||
"title": "/memory:docs-related-cli",
|
||||
"permalink": "/docs/zh/commands/memory/memory-docs-related-cli"
|
||||
"permalink": "/docs/commands/memory/memory-docs-related-cli"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/memory/memory-docs-related-cli.mdx",
|
||||
"sourceDirName": "commands/memory",
|
||||
"slug": "/commands/memory/memory-docs-related-cli",
|
||||
"permalink": "/docs/zh/commands/memory/memory-docs-related-cli",
|
||||
"permalink": "/docs/commands/memory/memory-docs-related-cli",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/memory/memory-docs-related-cli.mdx",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "/memory:docs-full-cli",
|
||||
"permalink": "/docs/zh/commands/memory/memory-docs-full-cli"
|
||||
"permalink": "/docs/commands/memory/memory-docs-full-cli"
|
||||
},
|
||||
"next": {
|
||||
"title": "/memory:compact",
|
||||
"permalink": "/docs/zh/commands/memory/memory-compact"
|
||||
"permalink": "/docs/commands/memory/memory-compact"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/memory/memory-load.mdx",
|
||||
"sourceDirName": "commands/memory",
|
||||
"slug": "/commands/memory/memory-load",
|
||||
"permalink": "/docs/zh/commands/memory/memory-load",
|
||||
"permalink": "/docs/commands/memory/memory-load",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/memory/memory-load.mdx",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "/memory:update-related",
|
||||
"permalink": "/docs/zh/commands/memory/memory-update-related"
|
||||
"permalink": "/docs/commands/memory/memory-update-related"
|
||||
},
|
||||
"next": {
|
||||
"title": "/memory:docs-full-cli",
|
||||
"permalink": "/docs/zh/commands/memory/memory-docs-full-cli"
|
||||
"permalink": "/docs/commands/memory/memory-docs-full-cli"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/memory/memory-update-full.mdx",
|
||||
"sourceDirName": "commands/memory",
|
||||
"slug": "/commands/memory/memory-update-full",
|
||||
"permalink": "/docs/zh/commands/memory/memory-update-full",
|
||||
"permalink": "/docs/commands/memory/memory-update-full",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/memory/memory-update-full.mdx",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "/cli:codex-review",
|
||||
"permalink": "/docs/zh/commands/cli/codex-review"
|
||||
"permalink": "/docs/commands/cli/codex-review"
|
||||
},
|
||||
"next": {
|
||||
"title": "/memory:update-related",
|
||||
"permalink": "/docs/zh/commands/memory/memory-update-related"
|
||||
"permalink": "/docs/commands/memory/memory-update-related"
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"source": "@site/docs/commands/memory/memory-update-related.mdx",
|
||||
"sourceDirName": "commands/memory",
|
||||
"slug": "/commands/memory/memory-update-related",
|
||||
"permalink": "/docs/zh/commands/memory/memory-update-related",
|
||||
"permalink": "/docs/commands/memory/memory-update-related",
|
||||
"draft": false,
|
||||
"unlisted": false,
|
||||
"editUrl": "https://github.com/ccw/docs/tree/main/docs/commands/memory/memory-update-related.mdx",
|
||||
@@ -21,10 +21,10 @@
|
||||
"sidebar": "docs",
|
||||
"previous": {
|
||||
"title": "/memory:update-full",
|
||||
"permalink": "/docs/zh/commands/memory/memory-update-full"
|
||||
"permalink": "/docs/commands/memory/memory-update-full"
|
||||
},
|
||||
"next": {
|
||||
"title": "/memory:load",
|
||||
"permalink": "/docs/zh/commands/memory/memory-load"
|
||||
"permalink": "/docs/commands/memory/memory-load"
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ export default {
|
||||
"tagline": "Professional Workflow Automation Platform",
|
||||
"favicon": "img/favicon.svg",
|
||||
"url": "http://localhost:3001",
|
||||
"baseUrl": "/docs/zh/",
|
||||
"baseUrl": "/docs/",
|
||||
"organizationName": "ccw",
|
||||
"projectName": "docs",
|
||||
"trailingSlash": false,
|
||||
@@ -48,9 +48,9 @@ export default {
|
||||
],
|
||||
"themeConfig": {
|
||||
"navbar": {
|
||||
"title": "CCW 帮助",
|
||||
"title": "CCW Help",
|
||||
"logo": {
|
||||
"alt": "CCW 标志",
|
||||
"alt": "CCW Logo",
|
||||
"src": "img/logo.svg"
|
||||
},
|
||||
"items": [
|
||||
@@ -65,7 +65,7 @@ export default {
|
||||
},
|
||||
"footer": {
|
||||
"style": "dark",
|
||||
"copyright": "版权 © 2026 CCW。使用 Docusaurus 构建。",
|
||||
"copyright": "Copyright © 2026 CCW. Built with Docusaurus.",
|
||||
"links": []
|
||||
},
|
||||
"prism": {
|
||||
|
||||
@@ -1,172 +1,172 @@
|
||||
{
|
||||
"docusaurus-plugin-content-docs": {
|
||||
"default": {
|
||||
"path": "/docs/zh/",
|
||||
"path": "/docs/",
|
||||
"versions": [
|
||||
{
|
||||
"name": "current",
|
||||
"label": "当前",
|
||||
"label": "Next",
|
||||
"isLast": true,
|
||||
"path": "/docs/zh/",
|
||||
"path": "/docs/",
|
||||
"mainDocId": "index",
|
||||
"docs": [
|
||||
{
|
||||
"id": "commands/cli/cli-init",
|
||||
"path": "/docs/zh/commands/cli/cli-init",
|
||||
"path": "/docs/commands/cli/cli-init",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/cli/codex-review",
|
||||
"path": "/docs/zh/commands/cli/codex-review",
|
||||
"path": "/docs/commands/cli/codex-review",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/general/ccw",
|
||||
"path": "/docs/zh/commands/general/ccw",
|
||||
"path": "/docs/commands/general/ccw",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/general/ccw-coordinator",
|
||||
"path": "/docs/zh/commands/general/ccw-coordinator",
|
||||
"path": "/docs/commands/general/ccw-coordinator",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/general/ccw-debug",
|
||||
"path": "/docs/zh/commands/general/ccw-debug",
|
||||
"path": "/docs/commands/general/ccw-debug",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/general/ccw-plan",
|
||||
"path": "/docs/zh/commands/general/ccw-plan",
|
||||
"path": "/docs/commands/general/ccw-plan",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/general/ccw-test",
|
||||
"path": "/docs/zh/commands/general/ccw-test",
|
||||
"path": "/docs/commands/general/ccw-test",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/general/codex-coordinator",
|
||||
"path": "/docs/zh/commands/general/codex-coordinator",
|
||||
"path": "/docs/commands/general/codex-coordinator",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/general/flow-create",
|
||||
"path": "/docs/zh/commands/general/flow-create",
|
||||
"path": "/docs/commands/general/flow-create",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/issue/issue-convert-to-plan",
|
||||
"path": "/docs/zh/commands/issue/issue-convert-to-plan",
|
||||
"path": "/docs/commands/issue/issue-convert-to-plan",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/issue/issue-discover",
|
||||
"path": "/docs/zh/commands/issue/issue-discover",
|
||||
"path": "/docs/commands/issue/issue-discover",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/issue/issue-execute",
|
||||
"path": "/docs/zh/commands/issue/issue-execute",
|
||||
"path": "/docs/commands/issue/issue-execute",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/issue/issue-from-brainstorm",
|
||||
"path": "/docs/zh/commands/issue/issue-from-brainstorm",
|
||||
"path": "/docs/commands/issue/issue-from-brainstorm",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/issue/issue-new",
|
||||
"path": "/docs/zh/commands/issue/issue-new",
|
||||
"path": "/docs/commands/issue/issue-new",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/issue/issue-plan",
|
||||
"path": "/docs/zh/commands/issue/issue-plan",
|
||||
"path": "/docs/commands/issue/issue-plan",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/issue/issue-queue",
|
||||
"path": "/docs/zh/commands/issue/issue-queue",
|
||||
"path": "/docs/commands/issue/issue-queue",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/memory/memory-compact",
|
||||
"path": "/docs/zh/commands/memory/memory-compact",
|
||||
"path": "/docs/commands/memory/memory-compact",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/memory/memory-docs-full-cli",
|
||||
"path": "/docs/zh/commands/memory/memory-docs-full-cli",
|
||||
"path": "/docs/commands/memory/memory-docs-full-cli",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/memory/memory-docs-related-cli",
|
||||
"path": "/docs/zh/commands/memory/memory-docs-related-cli",
|
||||
"path": "/docs/commands/memory/memory-docs-related-cli",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/memory/memory-load",
|
||||
"path": "/docs/zh/commands/memory/memory-load",
|
||||
"path": "/docs/commands/memory/memory-load",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/memory/memory-update-full",
|
||||
"path": "/docs/zh/commands/memory/memory-update-full",
|
||||
"path": "/docs/commands/memory/memory-update-full",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "commands/memory/memory-update-related",
|
||||
"path": "/docs/zh/commands/memory/memory-update-related",
|
||||
"path": "/docs/commands/memory/memory-update-related",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "faq",
|
||||
"path": "/docs/zh/faq",
|
||||
"path": "/docs/faq",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "index",
|
||||
"path": "/docs/zh/",
|
||||
"path": "/docs/",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "overview",
|
||||
"path": "/docs/zh/overview",
|
||||
"path": "/docs/overview",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "workflows/faq",
|
||||
"path": "/docs/zh/workflows/faq"
|
||||
"path": "/docs/workflows/faq"
|
||||
},
|
||||
{
|
||||
"id": "workflows/introduction",
|
||||
"path": "/docs/zh/workflows/introduction",
|
||||
"path": "/docs/workflows/introduction",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "workflows/level-1-ultra-lightweight",
|
||||
"path": "/docs/zh/workflows/level-1-ultra-lightweight",
|
||||
"path": "/docs/workflows/level-1-ultra-lightweight",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "workflows/level-2-rapid",
|
||||
"path": "/docs/zh/workflows/level-2-rapid",
|
||||
"path": "/docs/workflows/level-2-rapid",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "workflows/level-3-standard",
|
||||
"path": "/docs/zh/workflows/level-3-standard",
|
||||
"path": "/docs/workflows/level-3-standard",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "workflows/level-4-brainstorm",
|
||||
"path": "/docs/zh/workflows/level-4-brainstorm",
|
||||
"path": "/docs/workflows/level-4-brainstorm",
|
||||
"sidebar": "docs"
|
||||
},
|
||||
{
|
||||
"id": "workflows/level-5-intelligent",
|
||||
"path": "/docs/zh/workflows/level-5-intelligent",
|
||||
"path": "/docs/workflows/level-5-intelligent",
|
||||
"sidebar": "docs"
|
||||
}
|
||||
],
|
||||
@@ -174,7 +174,7 @@
|
||||
"sidebars": {
|
||||
"docs": {
|
||||
"link": {
|
||||
"path": "/docs/zh/",
|
||||
"path": "/docs/",
|
||||
"label": "Home"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"zh"
|
||||
],
|
||||
"path": "i18n",
|
||||
"currentLocale": "zh",
|
||||
"currentLocale": "en",
|
||||
"localeConfigs": {
|
||||
"en": {
|
||||
"label": "English",
|
||||
|
||||
@@ -1,39 +1,47 @@
|
||||
export default {
|
||||
"04db0a2e": [() => import(/* webpackChunkName: "04db0a2e" */ "@site/docs/commands/general/ccw-plan.mdx"), "@site/docs/commands/general/ccw-plan.mdx", require.resolveWeak("@site/docs/commands/general/ccw-plan.mdx")],
|
||||
"05467734": [() => import(/* webpackChunkName: "05467734" */ "@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/level-2-rapid.mdx"), "@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/level-2-rapid.mdx", require.resolveWeak("@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/level-2-rapid.mdx")],
|
||||
"0566a0a8": [() => import(/* webpackChunkName: "0566a0a8" */ "@site/docs/commands/cli/cli-init.mdx"), "@site/docs/commands/cli/cli-init.mdx", require.resolveWeak("@site/docs/commands/cli/cli-init.mdx")],
|
||||
"157db180": [() => import(/* webpackChunkName: "157db180" */ "@site/docs/commands/memory/memory-load.mdx"), "@site/docs/commands/memory/memory-load.mdx", require.resolveWeak("@site/docs/commands/memory/memory-load.mdx")],
|
||||
"17896441": [() => import(/* webpackChunkName: "17896441" */ "@theme/DocItem"), "@theme/DocItem", require.resolveWeak("@theme/DocItem")],
|
||||
"1bac9067": [() => import(/* webpackChunkName: "1bac9067" */ "@site/docs/commands/issue/issue-queue.md"), "@site/docs/commands/issue/issue-queue.md", require.resolveWeak("@site/docs/commands/issue/issue-queue.md")],
|
||||
"1e3006f3": [() => import(/* webpackChunkName: "1e3006f3" */ "@site/docs/commands/issue/issue-discover.md"), "@site/docs/commands/issue/issue-discover.md", require.resolveWeak("@site/docs/commands/issue/issue-discover.md")],
|
||||
"2a5e3eff": [() => import(/* webpackChunkName: "2a5e3eff" */ "@site/i18n/zh/docusaurus-plugin-content-docs/current/faq.mdx"), "@site/i18n/zh/docusaurus-plugin-content-docs/current/faq.mdx", require.resolveWeak("@site/i18n/zh/docusaurus-plugin-content-docs/current/faq.mdx")],
|
||||
"2ecf8b4a": [() => import(/* webpackChunkName: "2ecf8b4a" */ "@site/docs/commands/issue/issue-from-brainstorm.md"), "@site/docs/commands/issue/issue-from-brainstorm.md", require.resolveWeak("@site/docs/commands/issue/issue-from-brainstorm.md")],
|
||||
"3f1fe4a1": [() => import(/* webpackChunkName: "3f1fe4a1" */ "@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/level-3-standard.mdx"), "@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/level-3-standard.mdx", require.resolveWeak("@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/level-3-standard.mdx")],
|
||||
"46f40178": [() => import(/* webpackChunkName: "46f40178" */ "@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/faq.mdx"), "@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/faq.mdx", require.resolveWeak("@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/faq.mdx")],
|
||||
"4ad7db0f": [() => import(/* webpackChunkName: "4ad7db0f" */ "@site/docs/commands/issue/issue-new.md"), "@site/docs/commands/issue/issue-new.md", require.resolveWeak("@site/docs/commands/issue/issue-new.md")],
|
||||
"4cc74730": [() => import(/* webpackChunkName: "4cc74730" */ "@site/docs/commands/memory/memory-docs-full-cli.mdx"), "@site/docs/commands/memory/memory-docs-full-cli.mdx", require.resolveWeak("@site/docs/commands/memory/memory-docs-full-cli.mdx")],
|
||||
"562bb8cb": [() => import(/* webpackChunkName: "562bb8cb" */ "@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/level-5-intelligent.mdx"), "@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/level-5-intelligent.mdx", require.resolveWeak("@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/level-5-intelligent.mdx")],
|
||||
"5c7b2278": [() => import(/* webpackChunkName: "5c7b2278" */ "@site/docs/commands/issue/issue-convert-to-plan.md"), "@site/docs/commands/issue/issue-convert-to-plan.md", require.resolveWeak("@site/docs/commands/issue/issue-convert-to-plan.md")],
|
||||
"5e95c892": [() => import(/* webpackChunkName: "5e95c892" */ "@theme/DocsRoot"), "@theme/DocsRoot", require.resolveWeak("@theme/DocsRoot")],
|
||||
"60eef997": [() => import(/* webpackChunkName: "60eef997" */ "@site/docs/commands/memory/memory-docs-related-cli.mdx"), "@site/docs/commands/memory/memory-docs-related-cli.mdx", require.resolveWeak("@site/docs/commands/memory/memory-docs-related-cli.mdx")],
|
||||
"611877e1": [() => import(/* webpackChunkName: "611877e1" */ "@site/docs/commands/memory/memory-update-related.mdx"), "@site/docs/commands/memory/memory-update-related.mdx", require.resolveWeak("@site/docs/commands/memory/memory-update-related.mdx")],
|
||||
"666bb1bf": [() => import(/* webpackChunkName: "666bb1bf" */ "@site/docs/commands/memory/memory-update-full.mdx"), "@site/docs/commands/memory/memory-update-full.mdx", require.resolveWeak("@site/docs/commands/memory/memory-update-full.mdx")],
|
||||
"6ab014e9": [() => import(/* webpackChunkName: "6ab014e9" */ "@site/i18n/zh/docusaurus-plugin-content-docs/current/index.mdx"), "@site/i18n/zh/docusaurus-plugin-content-docs/current/index.mdx", require.resolveWeak("@site/i18n/zh/docusaurus-plugin-content-docs/current/index.mdx")],
|
||||
"775938bf": [() => import(/* webpackChunkName: "775938bf" */ "@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/level-4-brainstorm.mdx"), "@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/level-4-brainstorm.mdx", require.resolveWeak("@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/level-4-brainstorm.mdx")],
|
||||
"7a1ee27c": [() => import(/* webpackChunkName: "7a1ee27c" */ "@site/docs/commands/memory/memory-compact.mdx"), "@site/docs/commands/memory/memory-compact.mdx", require.resolveWeak("@site/docs/commands/memory/memory-compact.mdx")],
|
||||
"8a7e39ed": [() => import(/* webpackChunkName: "8a7e39ed" */ "@site/i18n/zh/docusaurus-plugin-content-docs/current/overview.mdx"), "@site/i18n/zh/docusaurus-plugin-content-docs/current/overview.mdx", require.resolveWeak("@site/i18n/zh/docusaurus-plugin-content-docs/current/overview.mdx")],
|
||||
"97c6e66a": [() => import(/* webpackChunkName: "97c6e66a" */ "@site/docs/commands/general/ccw-debug.mdx"), "@site/docs/commands/general/ccw-debug.mdx", require.resolveWeak("@site/docs/commands/general/ccw-debug.mdx")],
|
||||
"9cf7cb6b": [() => import(/* webpackChunkName: "9cf7cb6b" */ "@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/level-1-ultra-lightweight.mdx"), "@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/level-1-ultra-lightweight.mdx", require.resolveWeak("@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/level-1-ultra-lightweight.mdx")],
|
||||
"a6c3df16": [() => import(/* webpackChunkName: "a6c3df16" */ "@site/docs/commands/issue/issue-plan.md"), "@site/docs/commands/issue/issue-plan.md", require.resolveWeak("@site/docs/commands/issue/issue-plan.md")],
|
||||
"a7bd4aaa": [() => import(/* webpackChunkName: "a7bd4aaa" */ "@theme/DocVersionRoot"), "@theme/DocVersionRoot", require.resolveWeak("@theme/DocVersionRoot")],
|
||||
"a94703ab": [() => import(/* webpackChunkName: "a94703ab" */ "@theme/DocRoot"), "@theme/DocRoot", require.resolveWeak("@theme/DocRoot")],
|
||||
"aba21aa0": [() => import(/* webpackChunkName: "aba21aa0" */ "@generated/docusaurus-plugin-content-docs/default/__plugin.json"), "@generated/docusaurus-plugin-content-docs/default/__plugin.json", require.resolveWeak("@generated/docusaurus-plugin-content-docs/default/__plugin.json")],
|
||||
"b17e4002": [() => import(/* webpackChunkName: "b17e4002" */ "@generated/docusaurus-plugin-content-docs/default/p/docs-zh-d2a.json"), "@generated/docusaurus-plugin-content-docs/default/p/docs-zh-d2a.json", require.resolveWeak("@generated/docusaurus-plugin-content-docs/default/p/docs-zh-d2a.json")],
|
||||
"ccef5d0f": [() => import(/* webpackChunkName: "ccef5d0f" */ "@site/docs/commands/general/ccw-test.mdx"), "@site/docs/commands/general/ccw-test.mdx", require.resolveWeak("@site/docs/commands/general/ccw-test.mdx")],
|
||||
"d550a629": [() => import(/* webpackChunkName: "d550a629" */ "@site/docs/commands/general/ccw-coordinator.mdx"), "@site/docs/commands/general/ccw-coordinator.mdx", require.resolveWeak("@site/docs/commands/general/ccw-coordinator.mdx")],
|
||||
"e5f6eee3": [() => import(/* webpackChunkName: "e5f6eee3" */ "@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/introduction.mdx"), "@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/introduction.mdx", require.resolveWeak("@site/i18n/zh/docusaurus-plugin-content-docs/current/workflows/introduction.mdx")],
|
||||
"f1bf82ec": [() => import(/* webpackChunkName: "f1bf82ec" */ "@site/docs/commands/cli/codex-review.mdx"), "@site/docs/commands/cli/codex-review.mdx", require.resolveWeak("@site/docs/commands/cli/codex-review.mdx")],
|
||||
"f4817052": [() => import(/* webpackChunkName: "f4817052" */ "@site/docs/commands/general/ccw.mdx"), "@site/docs/commands/general/ccw.mdx", require.resolveWeak("@site/docs/commands/general/ccw.mdx")],
|
||||
"f9222419": [() => import(/* webpackChunkName: "f9222419" */ "@site/docs/commands/general/codex-coordinator.mdx"), "@site/docs/commands/general/codex-coordinator.mdx", require.resolveWeak("@site/docs/commands/general/codex-coordinator.mdx")],
|
||||
"fabaf1c8": [() => import(/* webpackChunkName: "fabaf1c8" */ "@site/docs/commands/general/flow-create.mdx"), "@site/docs/commands/general/flow-create.mdx", require.resolveWeak("@site/docs/commands/general/flow-create.mdx")],
|
||||
"fe8e3dcf": [() => import(/* webpackChunkName: "fe8e3dcf" */ "@site/docs/commands/issue/issue-execute.md"), "@site/docs/commands/issue/issue-execute.md", require.resolveWeak("@site/docs/commands/issue/issue-execute.md")],};
|
||||
"__comp---theme-debug-config-23-a-2ff": [() => import(/* webpackChunkName: "__comp---theme-debug-config-23-a-2ff" */ "@theme/DebugConfig"), "@theme/DebugConfig", require.resolveWeak("@theme/DebugConfig")],
|
||||
"__comp---theme-debug-contentba-8-ce7": [() => import(/* webpackChunkName: "__comp---theme-debug-contentba-8-ce7" */ "@theme/DebugContent"), "@theme/DebugContent", require.resolveWeak("@theme/DebugContent")],
|
||||
"__comp---theme-debug-global-dataede-0fa": [() => import(/* webpackChunkName: "__comp---theme-debug-global-dataede-0fa" */ "@theme/DebugGlobalData"), "@theme/DebugGlobalData", require.resolveWeak("@theme/DebugGlobalData")],
|
||||
"__comp---theme-debug-registry-679-501": [() => import(/* webpackChunkName: "__comp---theme-debug-registry-679-501" */ "@theme/DebugRegistry"), "@theme/DebugRegistry", require.resolveWeak("@theme/DebugRegistry")],
|
||||
"__comp---theme-debug-routes-946-699": [() => import(/* webpackChunkName: "__comp---theme-debug-routes-946-699" */ "@theme/DebugRoutes"), "@theme/DebugRoutes", require.resolveWeak("@theme/DebugRoutes")],
|
||||
"__comp---theme-debug-site-metadata-68-e-3d4": [() => import(/* webpackChunkName: "__comp---theme-debug-site-metadata-68-e-3d4" */ "@theme/DebugSiteMetadata"), "@theme/DebugSiteMetadata", require.resolveWeak("@theme/DebugSiteMetadata")],
|
||||
"__comp---theme-doc-item-178-a40": [() => import(/* webpackChunkName: "__comp---theme-doc-item-178-a40" */ "@theme/DocItem"), "@theme/DocItem", require.resolveWeak("@theme/DocItem")],
|
||||
"__comp---theme-doc-roota-94-67a": [() => import(/* webpackChunkName: "__comp---theme-doc-roota-94-67a" */ "@theme/DocRoot"), "@theme/DocRoot", require.resolveWeak("@theme/DocRoot")],
|
||||
"__comp---theme-doc-version-roota-7-b-5de": [() => import(/* webpackChunkName: "__comp---theme-doc-version-roota-7-b-5de" */ "@theme/DocVersionRoot"), "@theme/DocVersionRoot", require.resolveWeak("@theme/DocVersionRoot")],
|
||||
"__comp---theme-docs-root-5-e-9-0b6": [() => import(/* webpackChunkName: "__comp---theme-docs-root-5-e-9-0b6" */ "@theme/DocsRoot"), "@theme/DocsRoot", require.resolveWeak("@theme/DocsRoot")],
|
||||
"__props---docs-11-b-f70": [() => import(/* webpackChunkName: "__props---docs-11-b-f70" */ "@generated/docusaurus-plugin-content-docs/default/p/docs-7fc.json"), "@generated/docusaurus-plugin-content-docs/default/p/docs-7fc.json", require.resolveWeak("@generated/docusaurus-plugin-content-docs/default/p/docs-7fc.json")],
|
||||
"__props---docs-docusaurus-debug-content-344-8d5": [() => import(/* webpackChunkName: "__props---docs-docusaurus-debug-content-344-8d5" */ "@generated/docusaurus-plugin-debug/default/p/docs-docusaurus-debug-content-a52.json"), "@generated/docusaurus-plugin-debug/default/p/docs-docusaurus-debug-content-a52.json", require.resolveWeak("@generated/docusaurus-plugin-debug/default/p/docs-docusaurus-debug-content-a52.json")],
|
||||
"content---docs-4-ed-831": [() => import(/* webpackChunkName: "content---docs-4-ed-831" */ "@site/docs/index.mdx"), "@site/docs/index.mdx", require.resolveWeak("@site/docs/index.mdx")],
|
||||
"content---docs-commands-cli-cli-init-056-ce1": [() => import(/* webpackChunkName: "content---docs-commands-cli-cli-init-056-ce1" */ "@site/docs/commands/cli/cli-init.mdx"), "@site/docs/commands/cli/cli-init.mdx", require.resolveWeak("@site/docs/commands/cli/cli-init.mdx")],
|
||||
"content---docs-commands-cli-codex-reviewf-1-b-55f": [() => import(/* webpackChunkName: "content---docs-commands-cli-codex-reviewf-1-b-55f" */ "@site/docs/commands/cli/codex-review.mdx"), "@site/docs/commands/cli/codex-review.mdx", require.resolveWeak("@site/docs/commands/cli/codex-review.mdx")],
|
||||
"content---docs-commands-general-ccw-coordinatord-55-c6b": [() => import(/* webpackChunkName: "content---docs-commands-general-ccw-coordinatord-55-c6b" */ "@site/docs/commands/general/ccw-coordinator.mdx"), "@site/docs/commands/general/ccw-coordinator.mdx", require.resolveWeak("@site/docs/commands/general/ccw-coordinator.mdx")],
|
||||
"content---docs-commands-general-ccw-debug-97-c-a72": [() => import(/* webpackChunkName: "content---docs-commands-general-ccw-debug-97-c-a72" */ "@site/docs/commands/general/ccw-debug.mdx"), "@site/docs/commands/general/ccw-debug.mdx", require.resolveWeak("@site/docs/commands/general/ccw-debug.mdx")],
|
||||
"content---docs-commands-general-ccw-plan-04-d-fe0": [() => import(/* webpackChunkName: "content---docs-commands-general-ccw-plan-04-d-fe0" */ "@site/docs/commands/general/ccw-plan.mdx"), "@site/docs/commands/general/ccw-plan.mdx", require.resolveWeak("@site/docs/commands/general/ccw-plan.mdx")],
|
||||
"content---docs-commands-general-ccw-testcce-912": [() => import(/* webpackChunkName: "content---docs-commands-general-ccw-testcce-912" */ "@site/docs/commands/general/ccw-test.mdx"), "@site/docs/commands/general/ccw-test.mdx", require.resolveWeak("@site/docs/commands/general/ccw-test.mdx")],
|
||||
"content---docs-commands-general-ccwf-48-8c4": [() => import(/* webpackChunkName: "content---docs-commands-general-ccwf-48-8c4" */ "@site/docs/commands/general/ccw.mdx"), "@site/docs/commands/general/ccw.mdx", require.resolveWeak("@site/docs/commands/general/ccw.mdx")],
|
||||
"content---docs-commands-general-codex-coordinatorf-92-1dc": [() => import(/* webpackChunkName: "content---docs-commands-general-codex-coordinatorf-92-1dc" */ "@site/docs/commands/general/codex-coordinator.mdx"), "@site/docs/commands/general/codex-coordinator.mdx", require.resolveWeak("@site/docs/commands/general/codex-coordinator.mdx")],
|
||||
"content---docs-commands-general-flow-createfab-98a": [() => import(/* webpackChunkName: "content---docs-commands-general-flow-createfab-98a" */ "@site/docs/commands/general/flow-create.mdx"), "@site/docs/commands/general/flow-create.mdx", require.resolveWeak("@site/docs/commands/general/flow-create.mdx")],
|
||||
"content---docs-commands-issue-issue-convert-to-plan-5-c-7-184": [() => import(/* webpackChunkName: "content---docs-commands-issue-issue-convert-to-plan-5-c-7-184" */ "@site/docs/commands/issue/issue-convert-to-plan.md"), "@site/docs/commands/issue/issue-convert-to-plan.md", require.resolveWeak("@site/docs/commands/issue/issue-convert-to-plan.md")],
|
||||
"content---docs-commands-issue-issue-discover-1-e-3-569": [() => import(/* webpackChunkName: "content---docs-commands-issue-issue-discover-1-e-3-569" */ "@site/docs/commands/issue/issue-discover.md"), "@site/docs/commands/issue/issue-discover.md", require.resolveWeak("@site/docs/commands/issue/issue-discover.md")],
|
||||
"content---docs-commands-issue-issue-executefe-8-c03": [() => import(/* webpackChunkName: "content---docs-commands-issue-issue-executefe-8-c03" */ "@site/docs/commands/issue/issue-execute.md"), "@site/docs/commands/issue/issue-execute.md", require.resolveWeak("@site/docs/commands/issue/issue-execute.md")],
|
||||
"content---docs-commands-issue-issue-from-brainstorm-2-ec-eeb": [() => import(/* webpackChunkName: "content---docs-commands-issue-issue-from-brainstorm-2-ec-eeb" */ "@site/docs/commands/issue/issue-from-brainstorm.md"), "@site/docs/commands/issue/issue-from-brainstorm.md", require.resolveWeak("@site/docs/commands/issue/issue-from-brainstorm.md")],
|
||||
"content---docs-commands-issue-issue-new-4-ad-3f0": [() => import(/* webpackChunkName: "content---docs-commands-issue-issue-new-4-ad-3f0" */ "@site/docs/commands/issue/issue-new.md"), "@site/docs/commands/issue/issue-new.md", require.resolveWeak("@site/docs/commands/issue/issue-new.md")],
|
||||
"content---docs-commands-issue-issue-plana-6-c-fbd": [() => import(/* webpackChunkName: "content---docs-commands-issue-issue-plana-6-c-fbd" */ "@site/docs/commands/issue/issue-plan.md"), "@site/docs/commands/issue/issue-plan.md", require.resolveWeak("@site/docs/commands/issue/issue-plan.md")],
|
||||
"content---docs-commands-issue-issue-queue-1-ba-55f": [() => import(/* webpackChunkName: "content---docs-commands-issue-issue-queue-1-ba-55f" */ "@site/docs/commands/issue/issue-queue.md"), "@site/docs/commands/issue/issue-queue.md", require.resolveWeak("@site/docs/commands/issue/issue-queue.md")],
|
||||
"content---docs-commands-memory-memory-compact-7-a-1-41c": [() => import(/* webpackChunkName: "content---docs-commands-memory-memory-compact-7-a-1-41c" */ "@site/docs/commands/memory/memory-compact.mdx"), "@site/docs/commands/memory/memory-compact.mdx", require.resolveWeak("@site/docs/commands/memory/memory-compact.mdx")],
|
||||
"content---docs-commands-memory-memory-docs-full-cli-4-cc-96f": [() => import(/* webpackChunkName: "content---docs-commands-memory-memory-docs-full-cli-4-cc-96f" */ "@site/docs/commands/memory/memory-docs-full-cli.mdx"), "@site/docs/commands/memory/memory-docs-full-cli.mdx", require.resolveWeak("@site/docs/commands/memory/memory-docs-full-cli.mdx")],
|
||||
"content---docs-commands-memory-memory-docs-related-cli-60-e-dd0": [() => import(/* webpackChunkName: "content---docs-commands-memory-memory-docs-related-cli-60-e-dd0" */ "@site/docs/commands/memory/memory-docs-related-cli.mdx"), "@site/docs/commands/memory/memory-docs-related-cli.mdx", require.resolveWeak("@site/docs/commands/memory/memory-docs-related-cli.mdx")],
|
||||
"content---docs-commands-memory-memory-load-157-952": [() => import(/* webpackChunkName: "content---docs-commands-memory-memory-load-157-952" */ "@site/docs/commands/memory/memory-load.mdx"), "@site/docs/commands/memory/memory-load.mdx", require.resolveWeak("@site/docs/commands/memory/memory-load.mdx")],
|
||||
"content---docs-commands-memory-memory-update-full-666-002": [() => import(/* webpackChunkName: "content---docs-commands-memory-memory-update-full-666-002" */ "@site/docs/commands/memory/memory-update-full.mdx"), "@site/docs/commands/memory/memory-update-full.mdx", require.resolveWeak("@site/docs/commands/memory/memory-update-full.mdx")],
|
||||
"content---docs-commands-memory-memory-update-related-611-8d3": [() => import(/* webpackChunkName: "content---docs-commands-memory-memory-update-related-611-8d3" */ "@site/docs/commands/memory/memory-update-related.mdx"), "@site/docs/commands/memory/memory-update-related.mdx", require.resolveWeak("@site/docs/commands/memory/memory-update-related.mdx")],
|
||||
"content---docs-faqea-3-888": [() => import(/* webpackChunkName: "content---docs-faqea-3-888" */ "@site/docs/faq.mdx"), "@site/docs/faq.mdx", require.resolveWeak("@site/docs/faq.mdx")],
|
||||
"content---docs-overview-188-429": [() => import(/* webpackChunkName: "content---docs-overview-188-429" */ "@site/docs/overview.mdx"), "@site/docs/overview.mdx", require.resolveWeak("@site/docs/overview.mdx")],
|
||||
"content---docs-workflows-faqbcf-045": [() => import(/* webpackChunkName: "content---docs-workflows-faqbcf-045" */ "@site/docs/workflows/faq.mdx"), "@site/docs/workflows/faq.mdx", require.resolveWeak("@site/docs/workflows/faq.mdx")],
|
||||
"content---docs-workflows-introduction-9-f-4-275": [() => import(/* webpackChunkName: "content---docs-workflows-introduction-9-f-4-275" */ "@site/docs/workflows/introduction.mdx"), "@site/docs/workflows/introduction.mdx", require.resolveWeak("@site/docs/workflows/introduction.mdx")],
|
||||
"content---docs-workflows-level-1-ultra-lightweightc-5-a-5db": [() => import(/* webpackChunkName: "content---docs-workflows-level-1-ultra-lightweightc-5-a-5db" */ "@site/docs/workflows/level-1-ultra-lightweight.mdx"), "@site/docs/workflows/level-1-ultra-lightweight.mdx", require.resolveWeak("@site/docs/workflows/level-1-ultra-lightweight.mdx")],
|
||||
"content---docs-workflows-level-2-rapid-19-b-095": [() => import(/* webpackChunkName: "content---docs-workflows-level-2-rapid-19-b-095" */ "@site/docs/workflows/level-2-rapid.mdx"), "@site/docs/workflows/level-2-rapid.mdx", require.resolveWeak("@site/docs/workflows/level-2-rapid.mdx")],
|
||||
"content---docs-workflows-level-3-standardbdb-61a": [() => import(/* webpackChunkName: "content---docs-workflows-level-3-standardbdb-61a" */ "@site/docs/workflows/level-3-standard.mdx"), "@site/docs/workflows/level-3-standard.mdx", require.resolveWeak("@site/docs/workflows/level-3-standard.mdx")],
|
||||
"content---docs-workflows-level-4-brainstormd-04-14f": [() => import(/* webpackChunkName: "content---docs-workflows-level-4-brainstormd-04-14f" */ "@site/docs/workflows/level-4-brainstorm.mdx"), "@site/docs/workflows/level-4-brainstorm.mdx", require.resolveWeak("@site/docs/workflows/level-4-brainstorm.mdx")],
|
||||
"content---docs-workflows-level-5-intelligent-186-b05": [() => import(/* webpackChunkName: "content---docs-workflows-level-5-intelligent-186-b05" */ "@site/docs/workflows/level-5-intelligent.mdx"), "@site/docs/workflows/level-5-intelligent.mdx", require.resolveWeak("@site/docs/workflows/level-5-intelligent.mdx")],
|
||||
"plugin---docs-aba-4f5": [() => import(/* webpackChunkName: "plugin---docs-aba-4f5" */ "@generated/docusaurus-plugin-content-docs/default/__plugin.json"), "@generated/docusaurus-plugin-content-docs/default/__plugin.json", require.resolveWeak("@generated/docusaurus-plugin-content-docs/default/__plugin.json")],
|
||||
"plugin---docs-docusaurus-debugb-38-c84": [() => import(/* webpackChunkName: "plugin---docs-docusaurus-debugb-38-c84" */ "@generated/docusaurus-plugin-debug/default/__plugin.json"), "@generated/docusaurus-plugin-debug/default/__plugin.json", require.resolveWeak("@generated/docusaurus-plugin-debug/default/__plugin.json")],};
|
||||
|
||||
@@ -3,205 +3,240 @@ import ComponentCreator from '@docusaurus/ComponentCreator';
|
||||
|
||||
export default [
|
||||
{
|
||||
path: '/docs/zh/',
|
||||
component: ComponentCreator('/docs/zh/', 'b34'),
|
||||
path: '/docs/__docusaurus/debug',
|
||||
component: ComponentCreator('/docs/__docusaurus/debug', 'e58'),
|
||||
exact: true
|
||||
},
|
||||
{
|
||||
path: '/docs/__docusaurus/debug/config',
|
||||
component: ComponentCreator('/docs/__docusaurus/debug/config', '2ce'),
|
||||
exact: true
|
||||
},
|
||||
{
|
||||
path: '/docs/__docusaurus/debug/content',
|
||||
component: ComponentCreator('/docs/__docusaurus/debug/content', '11b'),
|
||||
exact: true
|
||||
},
|
||||
{
|
||||
path: '/docs/__docusaurus/debug/globalData',
|
||||
component: ComponentCreator('/docs/__docusaurus/debug/globalData', 'f13'),
|
||||
exact: true
|
||||
},
|
||||
{
|
||||
path: '/docs/__docusaurus/debug/metadata',
|
||||
component: ComponentCreator('/docs/__docusaurus/debug/metadata', 'bff'),
|
||||
exact: true
|
||||
},
|
||||
{
|
||||
path: '/docs/__docusaurus/debug/registry',
|
||||
component: ComponentCreator('/docs/__docusaurus/debug/registry', '830'),
|
||||
exact: true
|
||||
},
|
||||
{
|
||||
path: '/docs/__docusaurus/debug/routes',
|
||||
component: ComponentCreator('/docs/__docusaurus/debug/routes', '13e'),
|
||||
exact: true
|
||||
},
|
||||
{
|
||||
path: '/docs/',
|
||||
component: ComponentCreator('/docs/', 'a3f'),
|
||||
routes: [
|
||||
{
|
||||
path: '/docs/zh/',
|
||||
component: ComponentCreator('/docs/zh/', 'a8e'),
|
||||
path: '/docs/',
|
||||
component: ComponentCreator('/docs/', 'fa7'),
|
||||
routes: [
|
||||
{
|
||||
path: '/docs/zh/',
|
||||
component: ComponentCreator('/docs/zh/', '632'),
|
||||
path: '/docs/',
|
||||
component: ComponentCreator('/docs/', '294'),
|
||||
routes: [
|
||||
{
|
||||
path: '/docs/zh/commands/cli/cli-init',
|
||||
component: ComponentCreator('/docs/zh/commands/cli/cli-init', 'fe3'),
|
||||
path: '/docs/commands/cli/cli-init',
|
||||
component: ComponentCreator('/docs/commands/cli/cli-init', '159'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/cli/codex-review',
|
||||
component: ComponentCreator('/docs/zh/commands/cli/codex-review', 'e65'),
|
||||
path: '/docs/commands/cli/codex-review',
|
||||
component: ComponentCreator('/docs/commands/cli/codex-review', 'c66'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/general/ccw',
|
||||
component: ComponentCreator('/docs/zh/commands/general/ccw', '83a'),
|
||||
path: '/docs/commands/general/ccw',
|
||||
component: ComponentCreator('/docs/commands/general/ccw', '3c1'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/general/ccw-coordinator',
|
||||
component: ComponentCreator('/docs/zh/commands/general/ccw-coordinator', 'f35'),
|
||||
path: '/docs/commands/general/ccw-coordinator',
|
||||
component: ComponentCreator('/docs/commands/general/ccw-coordinator', '3b4'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/general/ccw-debug',
|
||||
component: ComponentCreator('/docs/zh/commands/general/ccw-debug', 'b0a'),
|
||||
path: '/docs/commands/general/ccw-debug',
|
||||
component: ComponentCreator('/docs/commands/general/ccw-debug', 'e0c'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/general/ccw-plan',
|
||||
component: ComponentCreator('/docs/zh/commands/general/ccw-plan', '39d'),
|
||||
path: '/docs/commands/general/ccw-plan',
|
||||
component: ComponentCreator('/docs/commands/general/ccw-plan', '9ae'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/general/ccw-test',
|
||||
component: ComponentCreator('/docs/zh/commands/general/ccw-test', '765'),
|
||||
path: '/docs/commands/general/ccw-test',
|
||||
component: ComponentCreator('/docs/commands/general/ccw-test', 'e6f'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/general/codex-coordinator',
|
||||
component: ComponentCreator('/docs/zh/commands/general/codex-coordinator', '486'),
|
||||
path: '/docs/commands/general/codex-coordinator',
|
||||
component: ComponentCreator('/docs/commands/general/codex-coordinator', 'e7d'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/general/flow-create',
|
||||
component: ComponentCreator('/docs/zh/commands/general/flow-create', 'd53'),
|
||||
path: '/docs/commands/general/flow-create',
|
||||
component: ComponentCreator('/docs/commands/general/flow-create', '507'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/issue/issue-convert-to-plan',
|
||||
component: ComponentCreator('/docs/zh/commands/issue/issue-convert-to-plan', '0df'),
|
||||
path: '/docs/commands/issue/issue-convert-to-plan',
|
||||
component: ComponentCreator('/docs/commands/issue/issue-convert-to-plan', 'a36'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/issue/issue-discover',
|
||||
component: ComponentCreator('/docs/zh/commands/issue/issue-discover', '9b4'),
|
||||
path: '/docs/commands/issue/issue-discover',
|
||||
component: ComponentCreator('/docs/commands/issue/issue-discover', '5ae'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/issue/issue-execute',
|
||||
component: ComponentCreator('/docs/zh/commands/issue/issue-execute', 'cfd'),
|
||||
path: '/docs/commands/issue/issue-execute',
|
||||
component: ComponentCreator('/docs/commands/issue/issue-execute', '20b'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/issue/issue-from-brainstorm',
|
||||
component: ComponentCreator('/docs/zh/commands/issue/issue-from-brainstorm', 'd2f'),
|
||||
path: '/docs/commands/issue/issue-from-brainstorm',
|
||||
component: ComponentCreator('/docs/commands/issue/issue-from-brainstorm', '10c'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/issue/issue-new',
|
||||
component: ComponentCreator('/docs/zh/commands/issue/issue-new', '7f9'),
|
||||
path: '/docs/commands/issue/issue-new',
|
||||
component: ComponentCreator('/docs/commands/issue/issue-new', 'abb'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/issue/issue-plan',
|
||||
component: ComponentCreator('/docs/zh/commands/issue/issue-plan', 'ed4'),
|
||||
path: '/docs/commands/issue/issue-plan',
|
||||
component: ComponentCreator('/docs/commands/issue/issue-plan', '57f'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/issue/issue-queue',
|
||||
component: ComponentCreator('/docs/zh/commands/issue/issue-queue', 'a4b'),
|
||||
path: '/docs/commands/issue/issue-queue',
|
||||
component: ComponentCreator('/docs/commands/issue/issue-queue', '316'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/memory/memory-compact',
|
||||
component: ComponentCreator('/docs/zh/commands/memory/memory-compact', '8dc'),
|
||||
path: '/docs/commands/memory/memory-compact',
|
||||
component: ComponentCreator('/docs/commands/memory/memory-compact', 'fbd'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/memory/memory-docs-full-cli',
|
||||
component: ComponentCreator('/docs/zh/commands/memory/memory-docs-full-cli', '1a7'),
|
||||
path: '/docs/commands/memory/memory-docs-full-cli',
|
||||
component: ComponentCreator('/docs/commands/memory/memory-docs-full-cli', '8b8'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/memory/memory-docs-related-cli',
|
||||
component: ComponentCreator('/docs/zh/commands/memory/memory-docs-related-cli', 'f28'),
|
||||
path: '/docs/commands/memory/memory-docs-related-cli',
|
||||
component: ComponentCreator('/docs/commands/memory/memory-docs-related-cli', '707'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/memory/memory-load',
|
||||
component: ComponentCreator('/docs/zh/commands/memory/memory-load', 'aee'),
|
||||
path: '/docs/commands/memory/memory-load',
|
||||
component: ComponentCreator('/docs/commands/memory/memory-load', '1db'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/memory/memory-update-full',
|
||||
component: ComponentCreator('/docs/zh/commands/memory/memory-update-full', '2a1'),
|
||||
path: '/docs/commands/memory/memory-update-full',
|
||||
component: ComponentCreator('/docs/commands/memory/memory-update-full', '3fa'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/commands/memory/memory-update-related',
|
||||
component: ComponentCreator('/docs/zh/commands/memory/memory-update-related', '991'),
|
||||
path: '/docs/commands/memory/memory-update-related',
|
||||
component: ComponentCreator('/docs/commands/memory/memory-update-related', 'c50'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/faq',
|
||||
component: ComponentCreator('/docs/zh/faq', 'd6c'),
|
||||
path: '/docs/faq',
|
||||
component: ComponentCreator('/docs/faq', '296'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/overview',
|
||||
component: ComponentCreator('/docs/zh/overview', '2d1'),
|
||||
path: '/docs/overview',
|
||||
component: ComponentCreator('/docs/overview', 'f90'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/workflows/faq',
|
||||
component: ComponentCreator('/docs/zh/workflows/faq', '319'),
|
||||
path: '/docs/workflows/faq',
|
||||
component: ComponentCreator('/docs/workflows/faq', '58c'),
|
||||
exact: true
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/workflows/introduction',
|
||||
component: ComponentCreator('/docs/zh/workflows/introduction', 'dc8'),
|
||||
path: '/docs/workflows/introduction',
|
||||
component: ComponentCreator('/docs/workflows/introduction', '702'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/workflows/level-1-ultra-lightweight',
|
||||
component: ComponentCreator('/docs/zh/workflows/level-1-ultra-lightweight', '4d3'),
|
||||
path: '/docs/workflows/level-1-ultra-lightweight',
|
||||
component: ComponentCreator('/docs/workflows/level-1-ultra-lightweight', 'b4b'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/workflows/level-2-rapid',
|
||||
component: ComponentCreator('/docs/zh/workflows/level-2-rapid', 'e2a'),
|
||||
path: '/docs/workflows/level-2-rapid',
|
||||
component: ComponentCreator('/docs/workflows/level-2-rapid', 'fe1'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/workflows/level-3-standard',
|
||||
component: ComponentCreator('/docs/zh/workflows/level-3-standard', '936'),
|
||||
path: '/docs/workflows/level-3-standard',
|
||||
component: ComponentCreator('/docs/workflows/level-3-standard', '65f'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/workflows/level-4-brainstorm',
|
||||
component: ComponentCreator('/docs/zh/workflows/level-4-brainstorm', '87d'),
|
||||
path: '/docs/workflows/level-4-brainstorm',
|
||||
component: ComponentCreator('/docs/workflows/level-4-brainstorm', 'fae'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/workflows/level-5-intelligent',
|
||||
component: ComponentCreator('/docs/zh/workflows/level-5-intelligent', 'b09'),
|
||||
path: '/docs/workflows/level-5-intelligent',
|
||||
component: ComponentCreator('/docs/workflows/level-5-intelligent', 'fa9'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
},
|
||||
{
|
||||
path: '/docs/zh/',
|
||||
component: ComponentCreator('/docs/zh/', '0e3'),
|
||||
path: '/docs/',
|
||||
component: ComponentCreator('/docs/', '6df'),
|
||||
exact: true,
|
||||
sidebar: "docs"
|
||||
}
|
||||
|
||||
@@ -1,143 +1,186 @@
|
||||
{
|
||||
"/docs/zh/-b34": {
|
||||
"__comp": "5e95c892",
|
||||
"/docs/__docusaurus/debug-e58": {
|
||||
"__comp": "__comp---theme-debug-config-23-a-2ff",
|
||||
"__context": {
|
||||
"plugin": "aba21aa0"
|
||||
"plugin": "plugin---docs-docusaurus-debugb-38-c84"
|
||||
}
|
||||
},
|
||||
"/docs/zh/-a8e": {
|
||||
"__comp": "a7bd4aaa",
|
||||
"__props": "b17e4002"
|
||||
"/docs/__docusaurus/debug/config-2ce": {
|
||||
"__comp": "__comp---theme-debug-config-23-a-2ff",
|
||||
"__context": {
|
||||
"plugin": "plugin---docs-docusaurus-debugb-38-c84"
|
||||
}
|
||||
},
|
||||
"/docs/zh/-632": {
|
||||
"__comp": "a94703ab"
|
||||
"/docs/__docusaurus/debug/content-11b": {
|
||||
"__comp": "__comp---theme-debug-contentba-8-ce7",
|
||||
"__context": {
|
||||
"plugin": "plugin---docs-docusaurus-debugb-38-c84"
|
||||
},
|
||||
"__props": "__props---docs-docusaurus-debug-content-344-8d5"
|
||||
},
|
||||
"/docs/zh/commands/cli/cli-init-fe3": {
|
||||
"__comp": "17896441",
|
||||
"content": "0566a0a8"
|
||||
"/docs/__docusaurus/debug/globalData-f13": {
|
||||
"__comp": "__comp---theme-debug-global-dataede-0fa",
|
||||
"__context": {
|
||||
"plugin": "plugin---docs-docusaurus-debugb-38-c84"
|
||||
}
|
||||
},
|
||||
"/docs/zh/commands/cli/codex-review-e65": {
|
||||
"__comp": "17896441",
|
||||
"content": "f1bf82ec"
|
||||
"/docs/__docusaurus/debug/metadata-bff": {
|
||||
"__comp": "__comp---theme-debug-site-metadata-68-e-3d4",
|
||||
"__context": {
|
||||
"plugin": "plugin---docs-docusaurus-debugb-38-c84"
|
||||
}
|
||||
},
|
||||
"/docs/zh/commands/general/ccw-83a": {
|
||||
"__comp": "17896441",
|
||||
"content": "f4817052"
|
||||
"/docs/__docusaurus/debug/registry-830": {
|
||||
"__comp": "__comp---theme-debug-registry-679-501",
|
||||
"__context": {
|
||||
"plugin": "plugin---docs-docusaurus-debugb-38-c84"
|
||||
}
|
||||
},
|
||||
"/docs/zh/commands/general/ccw-coordinator-f35": {
|
||||
"__comp": "17896441",
|
||||
"content": "d550a629"
|
||||
"/docs/__docusaurus/debug/routes-13e": {
|
||||
"__comp": "__comp---theme-debug-routes-946-699",
|
||||
"__context": {
|
||||
"plugin": "plugin---docs-docusaurus-debugb-38-c84"
|
||||
}
|
||||
},
|
||||
"/docs/zh/commands/general/ccw-debug-b0a": {
|
||||
"__comp": "17896441",
|
||||
"content": "97c6e66a"
|
||||
"/docs/-a3f": {
|
||||
"__comp": "__comp---theme-docs-root-5-e-9-0b6",
|
||||
"__context": {
|
||||
"plugin": "plugin---docs-aba-4f5"
|
||||
}
|
||||
},
|
||||
"/docs/zh/commands/general/ccw-plan-39d": {
|
||||
"__comp": "17896441",
|
||||
"content": "04db0a2e"
|
||||
"/docs/-fa7": {
|
||||
"__comp": "__comp---theme-doc-version-roota-7-b-5de",
|
||||
"__props": "__props---docs-11-b-f70"
|
||||
},
|
||||
"/docs/zh/commands/general/ccw-test-765": {
|
||||
"__comp": "17896441",
|
||||
"content": "ccef5d0f"
|
||||
"/docs/-294": {
|
||||
"__comp": "__comp---theme-doc-roota-94-67a"
|
||||
},
|
||||
"/docs/zh/commands/general/codex-coordinator-486": {
|
||||
"__comp": "17896441",
|
||||
"content": "f9222419"
|
||||
"/docs/commands/cli/cli-init-159": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-cli-cli-init-056-ce1"
|
||||
},
|
||||
"/docs/zh/commands/general/flow-create-d53": {
|
||||
"__comp": "17896441",
|
||||
"content": "fabaf1c8"
|
||||
"/docs/commands/cli/codex-review-c66": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-cli-codex-reviewf-1-b-55f"
|
||||
},
|
||||
"/docs/zh/commands/issue/issue-convert-to-plan-0df": {
|
||||
"__comp": "17896441",
|
||||
"content": "5c7b2278"
|
||||
"/docs/commands/general/ccw-3c1": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-general-ccwf-48-8c4"
|
||||
},
|
||||
"/docs/zh/commands/issue/issue-discover-9b4": {
|
||||
"__comp": "17896441",
|
||||
"content": "1e3006f3"
|
||||
"/docs/commands/general/ccw-coordinator-3b4": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-general-ccw-coordinatord-55-c6b"
|
||||
},
|
||||
"/docs/zh/commands/issue/issue-execute-cfd": {
|
||||
"__comp": "17896441",
|
||||
"content": "fe8e3dcf"
|
||||
"/docs/commands/general/ccw-debug-e0c": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-general-ccw-debug-97-c-a72"
|
||||
},
|
||||
"/docs/zh/commands/issue/issue-from-brainstorm-d2f": {
|
||||
"__comp": "17896441",
|
||||
"content": "2ecf8b4a"
|
||||
"/docs/commands/general/ccw-plan-9ae": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-general-ccw-plan-04-d-fe0"
|
||||
},
|
||||
"/docs/zh/commands/issue/issue-new-7f9": {
|
||||
"__comp": "17896441",
|
||||
"content": "4ad7db0f"
|
||||
"/docs/commands/general/ccw-test-e6f": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-general-ccw-testcce-912"
|
||||
},
|
||||
"/docs/zh/commands/issue/issue-plan-ed4": {
|
||||
"__comp": "17896441",
|
||||
"content": "a6c3df16"
|
||||
"/docs/commands/general/codex-coordinator-e7d": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-general-codex-coordinatorf-92-1dc"
|
||||
},
|
||||
"/docs/zh/commands/issue/issue-queue-a4b": {
|
||||
"__comp": "17896441",
|
||||
"content": "1bac9067"
|
||||
"/docs/commands/general/flow-create-507": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-general-flow-createfab-98a"
|
||||
},
|
||||
"/docs/zh/commands/memory/memory-compact-8dc": {
|
||||
"__comp": "17896441",
|
||||
"content": "7a1ee27c"
|
||||
"/docs/commands/issue/issue-convert-to-plan-a36": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-issue-issue-convert-to-plan-5-c-7-184"
|
||||
},
|
||||
"/docs/zh/commands/memory/memory-docs-full-cli-1a7": {
|
||||
"__comp": "17896441",
|
||||
"content": "4cc74730"
|
||||
"/docs/commands/issue/issue-discover-5ae": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-issue-issue-discover-1-e-3-569"
|
||||
},
|
||||
"/docs/zh/commands/memory/memory-docs-related-cli-f28": {
|
||||
"__comp": "17896441",
|
||||
"content": "60eef997"
|
||||
"/docs/commands/issue/issue-execute-20b": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-issue-issue-executefe-8-c03"
|
||||
},
|
||||
"/docs/zh/commands/memory/memory-load-aee": {
|
||||
"__comp": "17896441",
|
||||
"content": "157db180"
|
||||
"/docs/commands/issue/issue-from-brainstorm-10c": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-issue-issue-from-brainstorm-2-ec-eeb"
|
||||
},
|
||||
"/docs/zh/commands/memory/memory-update-full-2a1": {
|
||||
"__comp": "17896441",
|
||||
"content": "666bb1bf"
|
||||
"/docs/commands/issue/issue-new-abb": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-issue-issue-new-4-ad-3f0"
|
||||
},
|
||||
"/docs/zh/commands/memory/memory-update-related-991": {
|
||||
"__comp": "17896441",
|
||||
"content": "611877e1"
|
||||
"/docs/commands/issue/issue-plan-57f": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-issue-issue-plana-6-c-fbd"
|
||||
},
|
||||
"/docs/zh/faq-d6c": {
|
||||
"__comp": "17896441",
|
||||
"content": "2a5e3eff"
|
||||
"/docs/commands/issue/issue-queue-316": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-issue-issue-queue-1-ba-55f"
|
||||
},
|
||||
"/docs/zh/overview-2d1": {
|
||||
"__comp": "17896441",
|
||||
"content": "8a7e39ed"
|
||||
"/docs/commands/memory/memory-compact-fbd": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-memory-memory-compact-7-a-1-41c"
|
||||
},
|
||||
"/docs/zh/workflows/faq-319": {
|
||||
"__comp": "17896441",
|
||||
"content": "46f40178"
|
||||
"/docs/commands/memory/memory-docs-full-cli-8b8": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-memory-memory-docs-full-cli-4-cc-96f"
|
||||
},
|
||||
"/docs/zh/workflows/introduction-dc8": {
|
||||
"__comp": "17896441",
|
||||
"content": "e5f6eee3"
|
||||
"/docs/commands/memory/memory-docs-related-cli-707": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-memory-memory-docs-related-cli-60-e-dd0"
|
||||
},
|
||||
"/docs/zh/workflows/level-1-ultra-lightweight-4d3": {
|
||||
"__comp": "17896441",
|
||||
"content": "9cf7cb6b"
|
||||
"/docs/commands/memory/memory-load-1db": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-memory-memory-load-157-952"
|
||||
},
|
||||
"/docs/zh/workflows/level-2-rapid-e2a": {
|
||||
"__comp": "17896441",
|
||||
"content": "05467734"
|
||||
"/docs/commands/memory/memory-update-full-3fa": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-memory-memory-update-full-666-002"
|
||||
},
|
||||
"/docs/zh/workflows/level-3-standard-936": {
|
||||
"__comp": "17896441",
|
||||
"content": "3f1fe4a1"
|
||||
"/docs/commands/memory/memory-update-related-c50": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-commands-memory-memory-update-related-611-8d3"
|
||||
},
|
||||
"/docs/zh/workflows/level-4-brainstorm-87d": {
|
||||
"__comp": "17896441",
|
||||
"content": "775938bf"
|
||||
"/docs/faq-296": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-faqea-3-888"
|
||||
},
|
||||
"/docs/zh/workflows/level-5-intelligent-b09": {
|
||||
"__comp": "17896441",
|
||||
"content": "562bb8cb"
|
||||
"/docs/overview-f90": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-overview-188-429"
|
||||
},
|
||||
"/docs/zh/-0e3": {
|
||||
"__comp": "17896441",
|
||||
"content": "6ab014e9"
|
||||
"/docs/workflows/faq-58c": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-workflows-faqbcf-045"
|
||||
},
|
||||
"/docs/workflows/introduction-702": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-workflows-introduction-9-f-4-275"
|
||||
},
|
||||
"/docs/workflows/level-1-ultra-lightweight-b4b": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-workflows-level-1-ultra-lightweightc-5-a-5db"
|
||||
},
|
||||
"/docs/workflows/level-2-rapid-fe1": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-workflows-level-2-rapid-19-b-095"
|
||||
},
|
||||
"/docs/workflows/level-3-standard-65f": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-workflows-level-3-standardbdb-61a"
|
||||
},
|
||||
"/docs/workflows/level-4-brainstorm-fae": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-workflows-level-4-brainstormd-04-14f"
|
||||
},
|
||||
"/docs/workflows/level-5-intelligent-fa9": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-workflows-level-5-intelligent-186-b05"
|
||||
},
|
||||
"/docs/-6df": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-4-ed-831"
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,9 @@
|
||||
"name": "@docusaurus/plugin-content-pages",
|
||||
"version": "3.9.2"
|
||||
},
|
||||
"docusaurus-plugin-sitemap": {
|
||||
"docusaurus-plugin-debug": {
|
||||
"type": "package",
|
||||
"name": "@docusaurus/plugin-sitemap",
|
||||
"name": "@docusaurus/plugin-debug",
|
||||
"version": "3.9.2"
|
||||
},
|
||||
"docusaurus-plugin-svgr": {
|
||||
|
||||
480
ccw/frontend/tests/e2e/api-settings.spec.ts
Normal file
480
ccw/frontend/tests/e2e/api-settings.spec.ts
Normal file
@@ -0,0 +1,480 @@
|
||||
// ========================================
|
||||
// E2E Tests: API Settings - CLI Provider Configuration
|
||||
// ========================================
|
||||
// End-to-end tests for API provider management and testing
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { setupEnhancedMonitoring, switchLanguageAndVerify } from './helpers/i18n-helpers';
|
||||
|
||||
test.describe('[API Settings] - CLI Provider Configuration Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/api-settings', { waitUntil: 'networkidle' as const });
|
||||
});
|
||||
|
||||
test('L3.21 - Page loads and displays current configuration', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API response for current settings
|
||||
await page.route('**/api/settings/cli', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
providers: [
|
||||
{
|
||||
id: 'provider-1',
|
||||
name: 'Gemini',
|
||||
endpoint: 'https://api.example.com',
|
||||
enabled: true
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Reload to trigger API
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for API settings form
|
||||
const settingsForm = page.getByTestId('api-settings-form').or(
|
||||
page.locator('form').filter({ hasText: /api|settings|provider/i })
|
||||
);
|
||||
|
||||
const isFormVisible = await settingsForm.isVisible().catch(() => false);
|
||||
|
||||
if (isFormVisible) {
|
||||
// Verify provider list is displayed
|
||||
const providerList = page.getByTestId('provider-list').or(
|
||||
settingsForm.locator('*').filter({ hasText: /provider|gemini|cli/i })
|
||||
);
|
||||
|
||||
const hasProviders = await providerList.isVisible().catch(() => false);
|
||||
expect(hasProviders).toBe(true);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.22 - Add new CLI provider', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for adding provider
|
||||
await page.route('**/api/settings/cli', (route) => {
|
||||
if (route.request().method() === 'POST') {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
provider: {
|
||||
id: 'new-provider',
|
||||
name: 'Test Provider',
|
||||
endpoint: 'https://test.api.com'
|
||||
}
|
||||
})
|
||||
});
|
||||
} else {
|
||||
route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Look for add provider button
|
||||
const addButton = page.getByRole('button', { name: /add|create|new provider/i }).or(
|
||||
page.getByTestId('provider-add-button')
|
||||
);
|
||||
|
||||
const hasAddButton = await addButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasAddButton) {
|
||||
await addButton.click();
|
||||
|
||||
// Look for provider form
|
||||
const form = page.getByTestId('provider-form').or(
|
||||
page.getByRole('dialog').locator('form')
|
||||
);
|
||||
|
||||
const hasForm = await form.isVisible().catch(() => false);
|
||||
|
||||
if (hasForm) {
|
||||
// Fill in provider details
|
||||
const nameInput = form.getByRole('textbox', { name: /name/i }).or(
|
||||
form.getByLabel(/name/i)
|
||||
);
|
||||
|
||||
const hasNameInput = await nameInput.isVisible().catch(() => false);
|
||||
|
||||
if (hasNameInput) {
|
||||
await nameInput.fill('E2E Test Provider');
|
||||
|
||||
const endpointInput = form.getByRole('textbox', { name: /endpoint|url|api/i });
|
||||
const hasEndpointInput = await endpointInput.isVisible().catch(() => false);
|
||||
|
||||
if (hasEndpointInput) {
|
||||
await endpointInput.fill('https://e2e-test.api.com');
|
||||
|
||||
// Submit form
|
||||
const saveButton = form.getByRole('button', { name: /save|create|submit/i });
|
||||
await saveButton.click();
|
||||
|
||||
// Verify success
|
||||
const successMessage = page.getByText(/added|created|success/i);
|
||||
const hasSuccess = await successMessage.isVisible().catch(() => false);
|
||||
expect(hasSuccess).toBe(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.23 - Edit existing provider', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for editing provider
|
||||
await page.route('**/api/settings/cli', (route) => {
|
||||
if (route.request().method() === 'POST') {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ success: true })
|
||||
});
|
||||
} else {
|
||||
route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Look for existing provider
|
||||
const providerItems = page.getByTestId(/provider-item|provider-card/).or(
|
||||
page.locator('.provider-item')
|
||||
);
|
||||
|
||||
const providerCount = await providerItems.count();
|
||||
|
||||
if (providerCount > 0) {
|
||||
const firstProvider = providerItems.first();
|
||||
|
||||
// Look for edit button
|
||||
const editButton = firstProvider.getByRole('button', { name: /edit|modify/i }).or(
|
||||
firstProvider.getByTestId('edit-button')
|
||||
);
|
||||
|
||||
const hasEditButton = await editButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasEditButton) {
|
||||
await editButton.click();
|
||||
|
||||
// Edit provider details
|
||||
const nameInput = page.getByRole('textbox', { name: /name/i });
|
||||
const hasNameInput = await nameInput.isVisible().catch(() => false);
|
||||
|
||||
if (hasNameInput) {
|
||||
await nameInput.fill('Updated Provider Name');
|
||||
|
||||
const saveButton = page.getByRole('button', { name: /save|update/i });
|
||||
await saveButton.click();
|
||||
|
||||
// Verify success
|
||||
const successMessage = page.getByText(/updated|saved|success/i);
|
||||
const hasSuccess = await successMessage.isVisible().catch(() => false);
|
||||
expect(hasSuccess).toBe(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.24 - Delete provider', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for deleting provider
|
||||
await page.route('**/api/settings/cli/*', (route) => {
|
||||
if (route.request().method() === 'DELETE') {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ success: true })
|
||||
});
|
||||
} else {
|
||||
route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Look for existing provider
|
||||
const providerItems = page.getByTestId(/provider-item|provider-card/).or(
|
||||
page.locator('.provider-item')
|
||||
);
|
||||
|
||||
const providerCount = await providerItems.count();
|
||||
|
||||
if (providerCount > 0) {
|
||||
const firstProvider = providerItems.first();
|
||||
|
||||
// Look for delete button
|
||||
const deleteButton = firstProvider.getByRole('button', { name: /delete|remove/i }).or(
|
||||
firstProvider.getByTestId('delete-button')
|
||||
);
|
||||
|
||||
const hasDeleteButton = await deleteButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasDeleteButton) {
|
||||
await deleteButton.click();
|
||||
|
||||
// Confirm deletion if dialog appears
|
||||
const confirmDialog = page.getByRole('dialog').filter({ hasText: /delete|confirm/i });
|
||||
const hasDialog = await confirmDialog.isVisible().catch(() => false);
|
||||
|
||||
if (hasDialog) {
|
||||
const confirmButton = confirmDialog.getByRole('button', { name: /delete|confirm|yes/i });
|
||||
await confirmButton.click();
|
||||
}
|
||||
|
||||
// Verify success
|
||||
const successMessage = page.getByText(/deleted|removed|success/i);
|
||||
const hasSuccess = await successMessage.isVisible().catch(() => false);
|
||||
expect(hasSuccess).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.25 - Test API connection', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for connection test
|
||||
await page.route('**/api/settings/test', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
latency: 45,
|
||||
status: 'connected'
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Look for test connection button
|
||||
const testButton = page.getByRole('button', { name: /test|check connection/i }).or(
|
||||
page.getByTestId('connection-test-button')
|
||||
);
|
||||
|
||||
const hasTestButton = await testButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasTestButton) {
|
||||
await testButton.click();
|
||||
|
||||
// Wait for test to complete
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Verify connection result
|
||||
const successMessage = page.getByText(/connected|success|available/i);
|
||||
const hasSuccess = await successMessage.isVisible().catch(() => false);
|
||||
expect(hasSuccess).toBe(true);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.26 - Save configuration persists', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for saving
|
||||
await page.route('**/api/settings/cli', (route) => {
|
||||
if (route.request().method() === 'POST') {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ success: true, persisted: true })
|
||||
});
|
||||
} else {
|
||||
route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Look for save button
|
||||
const saveButton = page.getByRole('button', { name: /save|apply/i }).or(
|
||||
page.getByTestId('provider-save-button')
|
||||
);
|
||||
|
||||
const hasSaveButton = await saveButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasSaveButton) {
|
||||
await saveButton.click();
|
||||
|
||||
// Verify persistence via localStorage
|
||||
const configStore = await page.evaluate(() => {
|
||||
const storage = localStorage.getItem('ccw-config-store');
|
||||
return storage ? JSON.parse(storage) : null;
|
||||
});
|
||||
|
||||
const hasPersisted = configStore !== null;
|
||||
expect(hasPersisted).toBe(true);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.27 - i18n - Form labels in EN/ZH', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Get language switcher
|
||||
const languageSwitcher = page.getByRole('combobox', { name: /select language|language/i }).first();
|
||||
|
||||
const hasLanguageSwitcher = await languageSwitcher.isVisible().catch(() => false);
|
||||
|
||||
if (hasLanguageSwitcher) {
|
||||
// Switch to Chinese
|
||||
await switchLanguageAndVerify(page, 'zh', languageSwitcher);
|
||||
|
||||
// Verify form is visible in Chinese
|
||||
const form = page.getByTestId('api-settings-form').or(
|
||||
page.locator('form')
|
||||
);
|
||||
|
||||
const isFormVisible = await form.isVisible().catch(() => false);
|
||||
expect(isFormVisible).toBe(true);
|
||||
|
||||
// Check for Chinese text
|
||||
const pageContent = await page.content();
|
||||
const hasChineseText = /[\u4e00-\u9fa5]/.test(pageContent);
|
||||
expect(hasChineseText).toBe(true);
|
||||
|
||||
// Switch back to English
|
||||
await switchLanguageAndVerify(page, 'en', languageSwitcher);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.28 - Error - Invalid API endpoint', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API error for invalid endpoint
|
||||
await page.route('**/api/settings/test', (route) => {
|
||||
route.fulfill({
|
||||
status: 400,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
error: 'Invalid endpoint URL',
|
||||
details: 'Endpoint must be a valid HTTPS URL'
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Look for endpoint input
|
||||
const endpointInput = page.getByRole('textbox', { name: /endpoint|url|api/i }).or(
|
||||
page.getByLabel(/endpoint|url/i)
|
||||
);
|
||||
|
||||
const hasEndpointInput = await endpointInput.isVisible().catch(() => false);
|
||||
|
||||
if (hasEndpointInput) {
|
||||
await endpointInput.fill('not-a-valid-url');
|
||||
|
||||
const testButton = page.getByRole('button', { name: /test|validate/i });
|
||||
const hasTestButton = await testButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasTestButton) {
|
||||
await testButton.click();
|
||||
|
||||
// Verify error message
|
||||
const errorMessage = page.getByText(/invalid|error|url/i);
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/settings/test'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.29 - Error - Connection timeout', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock timeout
|
||||
await page.route('**/api/settings/test', (route) => {
|
||||
// Simulate timeout by never responding
|
||||
setTimeout(() => {
|
||||
route.abort('timedout');
|
||||
}, 35000);
|
||||
});
|
||||
|
||||
// Look for test connection button
|
||||
const testButton = page.getByRole('button', { name: /test|check/i });
|
||||
|
||||
const hasTestButton = await testButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasTestButton) {
|
||||
await testButton.click();
|
||||
|
||||
// Wait for timeout message
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Verify timeout error
|
||||
const timeoutMessage = page.getByText(/timeout|timed out|unavailable/i);
|
||||
const hasTimeout = await timeoutMessage.isVisible().catch(() => false);
|
||||
// Timeout message may or may not appear
|
||||
}
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/settings/test'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.30 - Edge - Maximum providers limit', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to enforce limit
|
||||
await page.route('**/api/settings/cli', (route) => {
|
||||
if (route.request().method() === 'POST') {
|
||||
route.fulfill({
|
||||
status: 409,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
error: 'Maximum provider limit reached',
|
||||
limit: 10
|
||||
})
|
||||
});
|
||||
} else {
|
||||
route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Look for add provider button
|
||||
const addButton = page.getByRole('button', { name: /add|create/i });
|
||||
|
||||
const hasAddButton = await addButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasAddButton) {
|
||||
await addButton.click();
|
||||
|
||||
// Try to add provider
|
||||
const nameInput = page.getByRole('textbox', { name: /name/i });
|
||||
const hasNameInput = await nameInput.isVisible().catch(() => false);
|
||||
|
||||
if (hasNameInput) {
|
||||
await nameInput.fill('Test Provider');
|
||||
|
||||
const saveButton = page.getByRole('button', { name: /save|create/i });
|
||||
await saveButton.click();
|
||||
|
||||
// Look for limit error
|
||||
const limitMessage = page.getByText(/limit|maximum|too many/i);
|
||||
const hasLimitMessage = await limitMessage.isVisible().catch(() => false);
|
||||
// Limit message may or may not appear
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/settings/cli'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
});
|
||||
@@ -18,7 +18,7 @@ test.describe('[Dashboard Charts] - Chart Rendering & Interaction Tests', () =>
|
||||
await waitForDashboardLoad(page);
|
||||
});
|
||||
|
||||
describe('Pie Chart Rendering', () => {
|
||||
test.describe('Pie Chart Rendering', () => {
|
||||
test('DC-1.1 - should render workflow status pie chart with data', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
@@ -84,7 +84,7 @@ test.describe('[Dashboard Charts] - Chart Rendering & Interaction Tests', () =>
|
||||
});
|
||||
});
|
||||
|
||||
describe('Line Chart Rendering', () => {
|
||||
test.describe('Line Chart Rendering', () => {
|
||||
test('DC-2.1 - should render activity timeline line chart', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
@@ -167,7 +167,7 @@ test.describe('[Dashboard Charts] - Chart Rendering & Interaction Tests', () =>
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bar Chart Rendering', () => {
|
||||
test.describe('Bar Chart Rendering', () => {
|
||||
test('DC-3.1 - should render task type bar chart', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
@@ -232,7 +232,7 @@ test.describe('[Dashboard Charts] - Chart Rendering & Interaction Tests', () =>
|
||||
});
|
||||
});
|
||||
|
||||
describe('Chart Responsiveness', () => {
|
||||
test.describe('Chart Responsiveness', () => {
|
||||
test('DC-4.1 - should resize charts on mobile viewport', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
@@ -290,7 +290,7 @@ test.describe('[Dashboard Charts] - Chart Rendering & Interaction Tests', () =>
|
||||
});
|
||||
});
|
||||
|
||||
describe('Chart Empty States', () => {
|
||||
test.describe('Chart Empty States', () => {
|
||||
test('DC-5.1 - should display empty state when no data available', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
@@ -342,7 +342,7 @@ test.describe('[Dashboard Charts] - Chart Rendering & Interaction Tests', () =>
|
||||
});
|
||||
});
|
||||
|
||||
describe('Chart Legend Interaction', () => {
|
||||
test.describe('Chart Legend Interaction', () => {
|
||||
test('DC-6.1 - should toggle line visibility when clicking legend', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
@@ -373,7 +373,7 @@ test.describe('[Dashboard Charts] - Chart Rendering & Interaction Tests', () =>
|
||||
});
|
||||
});
|
||||
|
||||
describe('Chart Performance', () => {
|
||||
test.describe('Chart Performance', () => {
|
||||
test('DC-7.1 - should render all charts within performance budget', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ test.describe('[Dashboard Redesign] - Navigation & Layout Tests', () => {
|
||||
await waitForDashboardLoad(page);
|
||||
});
|
||||
|
||||
describe('Navigation Grouping', () => {
|
||||
test.describe('Navigation Grouping', () => {
|
||||
test('DR-1.1 - should display all 6 navigation groups', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
@@ -146,7 +146,7 @@ test.describe('[Dashboard Redesign] - Navigation & Layout Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dashboard Loading', () => {
|
||||
test.describe('Dashboard Loading', () => {
|
||||
test('DR-2.1 - should load all 5 widgets successfully', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
@@ -213,7 +213,7 @@ test.describe('[Dashboard Redesign] - Navigation & Layout Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Drag-Drop Persistence', () => {
|
||||
test.describe('Drag-Drop Persistence', () => {
|
||||
test('DR-3.1 - should allow dragging widgets to new positions', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
@@ -293,7 +293,7 @@ test.describe('[Dashboard Redesign] - Navigation & Layout Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Ticker Real-time Updates', () => {
|
||||
test.describe('Ticker Real-time Updates', () => {
|
||||
test('DR-4.1 - should display ticker marquee component', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
@@ -374,7 +374,7 @@ test.describe('[Dashboard Redesign] - Navigation & Layout Tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Responsive Layout', () => {
|
||||
test.describe('Responsive Layout', () => {
|
||||
test('DR-5.1 - should adapt layout for mobile viewport', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
|
||||
205
ccw/frontend/tests/e2e/explorer.spec.ts
Normal file
205
ccw/frontend/tests/e2e/explorer.spec.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
// ========================================
|
||||
// E2E Tests: Explorer - File System Navigation
|
||||
// ========================================
|
||||
// End-to-end tests for file explorer with tree navigation and file details
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { setupEnhancedMonitoring, switchLanguageAndVerify } from './helpers/i18n-helpers';
|
||||
|
||||
test.describe('[Explorer] - File System Navigation Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/explorer', { waitUntil: 'networkidle' as const });
|
||||
});
|
||||
|
||||
test('L3.45 - Page loads and displays file tree', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for file tree
|
||||
await page.route('**/api/explorer', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
files: [
|
||||
{
|
||||
id: 'file-1',
|
||||
name: 'src',
|
||||
type: 'directory',
|
||||
children: [
|
||||
{ id: 'file-2', name: 'index.ts', type: 'file' }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Reload to trigger API
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for file tree
|
||||
const fileTree = page.getByTestId('file-tree').or(
|
||||
page.locator('.file-tree')
|
||||
);
|
||||
|
||||
const isTreeVisible = await fileTree.isVisible().catch(() => false);
|
||||
|
||||
if (isTreeVisible) {
|
||||
// Verify tree nodes are displayed
|
||||
const treeNodes = page.getByTestId(/tree-node|file-node|directory-node/).or(
|
||||
page.locator('.tree-node')
|
||||
);
|
||||
|
||||
const nodeCount = await treeNodes.count();
|
||||
expect(nodeCount).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.46 - Navigate directories', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Look for directory nodes
|
||||
const directoryNodes = page.getByTestId(/directory-node|folder-node/).or(
|
||||
page.locator('.directory-node')
|
||||
);
|
||||
|
||||
const nodeCount = await directoryNodes.count();
|
||||
|
||||
if (nodeCount > 0) {
|
||||
const firstDirectory = directoryNodes.first();
|
||||
|
||||
// Click to expand
|
||||
await firstDirectory.click();
|
||||
|
||||
// Wait for children to load
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Verify directory state changed
|
||||
const isExpanded = await firstDirectory.getAttribute('aria-expanded');
|
||||
expect(isExpanded).toBe('true');
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.47 - View file details', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Look for file nodes
|
||||
const fileNodes = page.getByTestId(/file-node/).filter({ hasText: /\.(ts|tsx|js|jsx)$/i }).or(
|
||||
page.locator('.file-node').filter({ hasText: /\.(ts|tsx|js|jsx)$/i })
|
||||
);
|
||||
|
||||
const fileCount = await fileNodes.count();
|
||||
|
||||
if (fileCount > 0) {
|
||||
const firstFile = fileNodes.first();
|
||||
|
||||
// Click to view details
|
||||
await firstFile.click();
|
||||
|
||||
// Look for file details panel
|
||||
const detailsPanel = page.getByTestId('file-details-panel').or(
|
||||
page.locator('.file-details')
|
||||
);
|
||||
|
||||
const hasDetails = await detailsPanel.isVisible().catch(() => false);
|
||||
|
||||
if (hasDetails) {
|
||||
expect(detailsPanel).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.48 - i18n - File tree labels in EN/ZH', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Get language switcher
|
||||
const languageSwitcher = page.getByRole('combobox', { name: /select language|language/i }).first();
|
||||
|
||||
const hasLanguageSwitcher = await languageSwitcher.isVisible().catch(() => false);
|
||||
|
||||
if (hasLanguageSwitcher) {
|
||||
// Switch to Chinese
|
||||
await switchLanguageAndVerify(page, 'zh', languageSwitcher);
|
||||
|
||||
// Verify file tree content
|
||||
const fileTree = page.getByTestId('file-tree').or(
|
||||
page.locator('.file-tree')
|
||||
);
|
||||
|
||||
const isTreeVisible = await fileTree.isVisible().catch(() => false);
|
||||
expect(isTreeVisible).toBe(true);
|
||||
|
||||
// Switch back to English
|
||||
await switchLanguageAndVerify(page, 'en', languageSwitcher);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.49 - Error - Directory not found', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API error
|
||||
await page.route('**/api/explorer', (route) => {
|
||||
route.fulfill({
|
||||
status: 404,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Directory not found' })
|
||||
});
|
||||
});
|
||||
|
||||
// Reload to trigger API
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for error indicator
|
||||
const errorIndicator = page.getByText(/not found|error|unable/i).or(
|
||||
page.getByTestId('error-state')
|
||||
);
|
||||
|
||||
const hasError = await errorIndicator.isVisible().catch(() => false);
|
||||
|
||||
if (hasError) {
|
||||
expect(errorIndicator).toBeVisible();
|
||||
}
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/explorer'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.50 - Edge - Empty directory', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for empty directory
|
||||
await page.route('**/api/explorer', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ files: [] })
|
||||
});
|
||||
});
|
||||
|
||||
// Reload to trigger API
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for empty state
|
||||
const emptyState = page.getByText(/empty|no files|directory is empty/i);
|
||||
const hasEmptyState = await emptyState.isVisible().catch(() => false);
|
||||
|
||||
if (hasEmptyState) {
|
||||
expect(emptyState).toBeVisible();
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
});
|
||||
198
ccw/frontend/tests/e2e/graph.spec.ts
Normal file
198
ccw/frontend/tests/e2e/graph.spec.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
// ========================================
|
||||
// E2E Tests: Graph Explorer - Code Relationship Visualization
|
||||
// ========================================
|
||||
// End-to-end tests for code graph visualization with node relationships
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { setupEnhancedMonitoring, switchLanguageAndVerify } from './helpers/i18n-helpers';
|
||||
|
||||
test.describe('[Graph Explorer] - Code Relationship Visualization Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/graph', { waitUntil: 'networkidle' as const });
|
||||
});
|
||||
|
||||
test('L3.51 - Page loads and displays code graph', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for graph data
|
||||
await page.route('**/api/graph', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
nodes: [
|
||||
{ id: 'node-1', label: 'Component', type: 'component' },
|
||||
{ id: 'node-2', label: 'Service', type: 'service' }
|
||||
],
|
||||
edges: [
|
||||
{ id: 'edge-1', source: 'node-1', target: 'node-2', label: 'imports' }
|
||||
]
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Reload to trigger API
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for code graph
|
||||
const codeGraph = page.getByTestId('code-graph').or(
|
||||
page.locator('.code-graph')
|
||||
);
|
||||
|
||||
const isGraphVisible = await codeGraph.isVisible().catch(() => false);
|
||||
|
||||
if (isGraphVisible) {
|
||||
// Verify graph has nodes
|
||||
const graphNodes = page.getByTestId(/graph-node|node-/).or(
|
||||
page.locator('.graph-node')
|
||||
);
|
||||
|
||||
const nodeCount = await graphNodes.count();
|
||||
expect(nodeCount).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.52 - Navigate graph nodes', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Look for graph nodes
|
||||
const graphNodes = page.getByTestId(/graph-node|node-/).or(
|
||||
page.locator('.graph-node')
|
||||
);
|
||||
|
||||
const nodeCount = await graphNodes.count();
|
||||
|
||||
if (nodeCount > 0) {
|
||||
const firstNode = graphNodes.first();
|
||||
|
||||
// Click to select node
|
||||
await firstNode.click();
|
||||
|
||||
// Verify node is selected
|
||||
const isSelected = await firstNode.getAttribute('aria-selected');
|
||||
expect(isSelected).toBe('true');
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.53 - Expand/collapse node relationships', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Look for expandable nodes
|
||||
const expandableNodes = page.getByTestId(/graph-node/).filter({ hasText: /\+/ });
|
||||
|
||||
const nodeCount = await expandableNodes.count();
|
||||
|
||||
if (nodeCount > 0) {
|
||||
const firstNode = expandableNodes.first();
|
||||
|
||||
// Click to expand
|
||||
await firstNode.click();
|
||||
|
||||
// Wait for expansion
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Look for expanded children
|
||||
const childNodes = page.getByTestId(/graph-node/);
|
||||
const nodeCountAfter = await childNodes.count();
|
||||
|
||||
expect(nodeCountAfter).toBeGreaterThanOrEqual(0);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.54 - i18n - Graph labels in EN/ZH', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Get language switcher
|
||||
const languageSwitcher = page.getByRole('combobox', { name: /select language|language/i }).first();
|
||||
|
||||
const hasLanguageSwitcher = await languageSwitcher.isVisible().catch(() => false);
|
||||
|
||||
if (hasLanguageSwitcher) {
|
||||
// Switch to Chinese
|
||||
await switchLanguageAndVerify(page, 'zh', languageSwitcher);
|
||||
|
||||
// Verify graph is visible
|
||||
const codeGraph = page.getByTestId('code-graph').or(
|
||||
page.locator('.code-graph')
|
||||
);
|
||||
|
||||
const isGraphVisible = await codeGraph.isVisible().catch(() => false);
|
||||
expect(isGraphVisible).toBe(true);
|
||||
|
||||
// Switch back to English
|
||||
await switchLanguageAndVerify(page, 'en', languageSwitcher);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.55 - Error - Graph generation fails', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API error
|
||||
await page.route('**/api/graph', (route) => {
|
||||
route.fulfill({
|
||||
status: 500,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Failed to generate graph' })
|
||||
});
|
||||
});
|
||||
|
||||
// Reload to trigger API
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for error indicator
|
||||
const errorIndicator = page.getByText(/error|failed|unable/i).or(
|
||||
page.getByTestId('error-state')
|
||||
);
|
||||
|
||||
const hasError = await errorIndicator.isVisible().catch(() => false);
|
||||
|
||||
if (hasError) {
|
||||
expect(errorIndicator).toBeVisible();
|
||||
}
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/graph'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.56 - Edge - No relationships found', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for empty graph
|
||||
await page.route('**/api/graph', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
nodes: [],
|
||||
edges: []
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Reload to trigger API
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for empty state
|
||||
const emptyState = page.getByText(/no relationships|empty|no data/i);
|
||||
const hasEmptyState = await emptyState.isVisible().catch(() => false);
|
||||
|
||||
if (hasEmptyState) {
|
||||
expect(emptyState).toBeVisible();
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
});
|
||||
356
ccw/frontend/tests/e2e/history.spec.ts
Normal file
356
ccw/frontend/tests/e2e/history.spec.ts
Normal file
@@ -0,0 +1,356 @@
|
||||
// ========================================
|
||||
// E2E Tests: History - Archived Session Management
|
||||
// ========================================
|
||||
// End-to-end tests for history page with search, filter, restore operations
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { setupEnhancedMonitoring, switchLanguageAndVerify } from './helpers/i18n-helpers';
|
||||
|
||||
test.describe('[History] - Archived Session Management Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/history', { waitUntil: 'networkidle' as const });
|
||||
});
|
||||
|
||||
test('L3.31 - Page loads and displays archived sessions', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for history
|
||||
await page.route('**/api/history', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
sessions: [
|
||||
{
|
||||
id: 'session-1',
|
||||
title: 'Archived Session 1',
|
||||
archivedAt: Date.now(),
|
||||
status: 'completed'
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Reload to trigger API
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for history list
|
||||
const historyList = page.getByTestId('history-list').or(
|
||||
page.locator('.history-list')
|
||||
);
|
||||
|
||||
const isListVisible = await historyList.isVisible().catch(() => false);
|
||||
|
||||
if (isListVisible) {
|
||||
// Verify session items are displayed
|
||||
const sessionItems = page.getByTestId(/session-item|history-item/).or(
|
||||
page.locator('.history-item')
|
||||
);
|
||||
|
||||
const itemCount = await sessionItems.count();
|
||||
expect(itemCount).toBeGreaterThanOrEqual(0);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.32 - Search archived sessions', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for search
|
||||
await page.route('**/api/history?q=**', (route) => {
|
||||
const requestUrl = route.request().url();
|
||||
const url = new URL(requestUrl);
|
||||
const query = url.searchParams.get('q');
|
||||
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
sessions: [
|
||||
{
|
||||
id: 'session-search',
|
||||
title: `Search Result for ${query}`,
|
||||
archivedAt: Date.now()
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Look for search input
|
||||
const searchInput = page.getByRole('textbox', { name: /search|find/i }).or(
|
||||
page.getByTestId('search-input')
|
||||
);
|
||||
|
||||
const hasSearchInput = await searchInput.isVisible().catch(() => false);
|
||||
|
||||
if (hasSearchInput) {
|
||||
await searchInput.fill('test');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify search was performed
|
||||
const searchResults = page.getByTestId(/history-item|session-item/);
|
||||
const resultCount = await searchResults.count();
|
||||
expect(resultCount).toBeGreaterThanOrEqual(0);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.33 - Filter by date range', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for date filter
|
||||
await page.route('**/api/history**', (route) => {
|
||||
const requestUrl = route.request().url();
|
||||
const url = new URL(requestUrl);
|
||||
const fromDate = url.searchParams.get('from');
|
||||
const toDate = url.searchParams.get('to');
|
||||
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
sessions: [
|
||||
{
|
||||
id: 'session-filtered',
|
||||
title: 'Filtered by Date',
|
||||
archivedAt: Date.now()
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Look for date filter controls
|
||||
const dateFilter = page.getByTestId('date-filter').or(
|
||||
page.locator('*').filter({ hasText: /date|filter/i })
|
||||
);
|
||||
|
||||
const hasDateFilter = await dateFilter.isVisible().catch(() => false);
|
||||
|
||||
if (hasDateFilter) {
|
||||
const fromDateInput = page.getByRole('textbox', { name: /from|start/i });
|
||||
const hasFromDate = await fromDateInput.isVisible().catch(() => false);
|
||||
|
||||
if (hasFromDate) {
|
||||
await fromDateInput.fill('2024-01-01');
|
||||
|
||||
const toDateInput = page.getByRole('textbox', { name: /to|end/i });
|
||||
const hasToDate = await toDateInput.isVisible().catch(() => false);
|
||||
|
||||
if (hasToDate) {
|
||||
await toDateInput.fill('2024-12-31');
|
||||
|
||||
// Look for apply button
|
||||
const applyButton = page.getByRole('button', { name: /apply|filter/i });
|
||||
const hasApplyButton = await applyButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasApplyButton) {
|
||||
await applyButton.click();
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.34 - Restore archived session', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for restore
|
||||
await page.route('**/api/history/*/restore', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ success: true, sessionId: 'restored-session' })
|
||||
});
|
||||
});
|
||||
|
||||
// Look for archived session
|
||||
const sessionItems = page.getByTestId(/session-item|history-item/).or(
|
||||
page.locator('.history-item')
|
||||
);
|
||||
|
||||
const itemCount = await sessionItems.count();
|
||||
|
||||
if (itemCount > 0) {
|
||||
const firstSession = sessionItems.first();
|
||||
|
||||
// Look for restore button
|
||||
const restoreButton = firstSession.getByRole('button', { name: /restore|reload/i }).or(
|
||||
firstSession.getByTestId('restore-button')
|
||||
);
|
||||
|
||||
const hasRestoreButton = await restoreButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasRestoreButton) {
|
||||
await restoreButton.click();
|
||||
|
||||
// Verify success message
|
||||
const successMessage = page.getByText(/restored|success/i);
|
||||
const hasSuccess = await successMessage.isVisible().catch(() => false);
|
||||
expect(hasSuccess).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.35 - Delete archived session', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for delete
|
||||
await page.route('**/api/history/*', (route) => {
|
||||
if (route.request().method() === 'DELETE') {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ success: true })
|
||||
});
|
||||
} else {
|
||||
route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Look for archived session
|
||||
const sessionItems = page.getByTestId(/session-item|history-item/).or(
|
||||
page.locator('.history-item')
|
||||
);
|
||||
|
||||
const itemCount = await sessionItems.count();
|
||||
|
||||
if (itemCount > 0) {
|
||||
const firstSession = sessionItems.first();
|
||||
|
||||
// Look for delete button
|
||||
const deleteButton = firstSession.getByRole('button', { name: /delete|remove/i }).or(
|
||||
firstSession.getByTestId('delete-button')
|
||||
);
|
||||
|
||||
const hasDeleteButton = await deleteButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasDeleteButton) {
|
||||
await deleteButton.click();
|
||||
|
||||
// Confirm deletion if dialog appears
|
||||
const confirmDialog = page.getByRole('dialog').filter({ hasText: /delete|confirm/i });
|
||||
const hasDialog = await confirmDialog.isVisible().catch(() => false);
|
||||
|
||||
if (hasDialog) {
|
||||
const confirmButton = confirmDialog.getByRole('button', { name: /delete|confirm|yes/i });
|
||||
await confirmButton.click();
|
||||
}
|
||||
|
||||
// Verify success message
|
||||
const successMessage = page.getByText(/deleted|success/i);
|
||||
const hasSuccess = await successMessage.isVisible().catch(() => false);
|
||||
expect(hasSuccess).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.36 - i18n - Archive messages in EN/ZH', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Get language switcher
|
||||
const languageSwitcher = page.getByRole('combobox', { name: /select language|language/i }).first();
|
||||
|
||||
const hasLanguageSwitcher = await languageSwitcher.isVisible().catch(() => false);
|
||||
|
||||
if (hasLanguageSwitcher) {
|
||||
// Switch to Chinese
|
||||
await switchLanguageAndVerify(page, 'zh', languageSwitcher);
|
||||
|
||||
// Verify history content is in Chinese
|
||||
const pageContent = await page.content();
|
||||
const hasChineseText = /[\u4e00-\u9fa5]/.test(pageContent);
|
||||
expect(hasChineseText).toBe(true);
|
||||
|
||||
// Switch back to English
|
||||
await switchLanguageAndVerify(page, 'en', languageSwitcher);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.37 - Error - Restore fails', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API error for restore
|
||||
await page.route('**/api/history/*/restore', (route) => {
|
||||
route.fulfill({
|
||||
status: 500,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Failed to restore session' })
|
||||
});
|
||||
});
|
||||
|
||||
// Look for archived session
|
||||
const sessionItems = page.getByTestId(/session-item|history-item/).or(
|
||||
page.locator('.history-item')
|
||||
);
|
||||
|
||||
const itemCount = await sessionItems.count();
|
||||
|
||||
if (itemCount > 0) {
|
||||
const firstSession = sessionItems.first();
|
||||
|
||||
// Look for restore button
|
||||
const restoreButton = firstSession.getByRole('button', { name: /restore|reload/i });
|
||||
|
||||
const hasRestoreButton = await restoreButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasRestoreButton) {
|
||||
await restoreButton.click();
|
||||
|
||||
// Verify error message
|
||||
const errorMessage = page.getByText(/error|failed|unable/i);
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/history'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.38 - Edge - Empty archive state', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for empty history
|
||||
await page.route('**/api/history', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ sessions: [] })
|
||||
});
|
||||
});
|
||||
|
||||
// Reload to trigger API
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for empty state
|
||||
const emptyState = page.getByTestId('empty-state').or(
|
||||
page.getByText(/no history|empty|no sessions/i)
|
||||
);
|
||||
|
||||
const hasEmptyState = await emptyState.isVisible().catch(() => false);
|
||||
expect(hasEmptyState).toBe(true);
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
});
|
||||
@@ -1,273 +1,197 @@
|
||||
// ========================================
|
||||
// E2E Tests: Loops Management
|
||||
// E2E Tests: Loops Monitor - Real-time Workflow Execution
|
||||
// ========================================
|
||||
// End-to-end tests for loop CRUD operations and controls
|
||||
// End-to-end tests for loop monitoring with WebSocket mocking
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { setupEnhancedMonitoring } from './helpers/i18n-helpers';
|
||||
import { setupEnhancedMonitoring, switchLanguageAndVerify } from './helpers/i18n-helpers';
|
||||
|
||||
test.describe('[Loops] - Loop Management Tests', () => {
|
||||
test.describe('[Loops Monitor] - Real-time Loop Tracking Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/', { waitUntil: 'networkidle' as const });
|
||||
// Mock WebSocket connection for real-time updates
|
||||
await page.route('**/ws/loops**', (route) => {
|
||||
route.fulfill({
|
||||
status: 101,
|
||||
headers: {
|
||||
'Connection': 'Upgrade',
|
||||
'Upgrade': 'websocket'
|
||||
},
|
||||
body: ''
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('L3.1 - should display loops list', async ({ page }) => {
|
||||
test('L3.13 - Page loads and displays active loops', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Navigate to loops page
|
||||
// Mock API for loops list
|
||||
await page.route('**/api/loops', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
loops: [
|
||||
{
|
||||
id: 'loop-1',
|
||||
name: 'Test Loop',
|
||||
status: 'running',
|
||||
progress: 50,
|
||||
startedAt: Date.now()
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/loops', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for loops list container
|
||||
// Look for loops list
|
||||
const loopsList = page.getByTestId('loops-list').or(
|
||||
page.locator('.loops-list')
|
||||
);
|
||||
|
||||
const isVisible = await loopsList.isVisible().catch(() => false);
|
||||
const isListVisible = await loopsList.isVisible().catch(() => false);
|
||||
|
||||
if (isVisible) {
|
||||
// Verify loop items exist or empty state is shown
|
||||
if (isListVisible) {
|
||||
// Verify loop items are displayed
|
||||
const loopItems = page.getByTestId(/loop-item|loop-card/).or(
|
||||
page.locator('.loop-item')
|
||||
);
|
||||
|
||||
const itemCount = await loopItems.count();
|
||||
|
||||
if (itemCount === 0) {
|
||||
const emptyState = page.getByTestId('empty-state').or(
|
||||
page.getByText(/no loops/i)
|
||||
);
|
||||
const hasEmptyState = await emptyState.isVisible().catch(() => false);
|
||||
expect(hasEmptyState).toBe(true);
|
||||
}
|
||||
expect(itemCount).toBeGreaterThanOrEqual(0);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.2 - should create new loop', async ({ page }) => {
|
||||
test('L3.14 - Real-time loop status updates (mock WS)', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Navigate to loops page
|
||||
await page.goto('/loops', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for create loop button
|
||||
const createButton = page.getByRole('button', { name: /create|new|add loop/i }).or(
|
||||
page.getByTestId('create-loop-button')
|
||||
// Inject mock WebSocket message for status update
|
||||
await page.evaluate(() => {
|
||||
const event = new MessageEvent('message', {
|
||||
data: JSON.stringify({
|
||||
type: 'loop-update',
|
||||
loopId: 'loop-1',
|
||||
status: 'running',
|
||||
progress: 75
|
||||
})
|
||||
});
|
||||
|
||||
window.dispatchEvent(event);
|
||||
});
|
||||
|
||||
// Wait for update to be processed
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Look for updated status display
|
||||
const statusBadge = page.getByTestId('loop-status-badge').or(
|
||||
page.locator('*').filter({ hasText: /running|75%/i })
|
||||
);
|
||||
|
||||
const hasCreateButton = await createButton.isVisible().catch(() => false);
|
||||
const hasStatus = await statusBadge.isVisible().catch(() => false);
|
||||
// Status update may or may not be visible
|
||||
|
||||
if (hasCreateButton) {
|
||||
await createButton.click();
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
// Look for create loop dialog/form
|
||||
const dialog = page.getByRole('dialog').filter({ hasText: /create loop|new loop/i });
|
||||
const form = page.getByTestId('create-loop-form');
|
||||
test('L3.15 - Filter loops by status', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
const hasDialog = await dialog.isVisible().catch(() => false);
|
||||
const hasForm = await form.isVisible().catch(() => false);
|
||||
// Mock API for filtered loops
|
||||
await page.route('**/api/loops?status=**', (route) => {
|
||||
const requestUrl = route.request().url();
|
||||
const url = new URL(requestUrl);
|
||||
const status = url.searchParams.get('status');
|
||||
|
||||
if (hasDialog || hasForm) {
|
||||
// Fill in loop details
|
||||
const promptInput = page.getByRole('textbox', { name: /prompt|description/i }).or(
|
||||
page.getByLabel(/prompt|description/i)
|
||||
);
|
||||
|
||||
const hasPromptInput = await promptInput.isVisible().catch(() => false);
|
||||
|
||||
if (hasPromptInput) {
|
||||
await promptInput.fill('E2E Test Loop prompt');
|
||||
|
||||
// Select tool if available
|
||||
const toolSelect = page.getByRole('combobox', { name: /tool/i });
|
||||
const hasToolSelect = await toolSelect.isVisible().catch(() => false);
|
||||
|
||||
if (hasToolSelect) {
|
||||
const toolOptions = await toolSelect.locator('option').count();
|
||||
if (toolOptions > 0) {
|
||||
await toolSelect.selectOption({ index: 0 });
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
loops: [
|
||||
{
|
||||
id: `loop-${status}`,
|
||||
name: `Filtered ${status} Loop`,
|
||||
status: status
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
const submitButton = page.getByRole('button', { name: /create|save|submit|start/i });
|
||||
await submitButton.click();
|
||||
await page.goto('/loops', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Verify loop was created
|
||||
// Look for status filter
|
||||
const statusFilter = page.getByRole('combobox', { name: /status|filter/i }).or(
|
||||
page.getByTestId('status-filter')
|
||||
);
|
||||
|
||||
const successMessage = page.getByText(/created|started|success/i).or(
|
||||
page.getByTestId('success-message')
|
||||
);
|
||||
const hasFilter = await statusFilter.isVisible().catch(() => false);
|
||||
|
||||
const hasSuccess = await successMessage.isVisible().catch(() => false);
|
||||
expect(hasSuccess).toBe(true);
|
||||
}
|
||||
}
|
||||
if (hasFilter) {
|
||||
await statusFilter.selectOption('running');
|
||||
|
||||
// Wait for filtered results
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify filter applied
|
||||
const currentUrl = page.url();
|
||||
const hasFilterParam = currentUrl.includes('status=');
|
||||
// URL may or may not have filter parameter
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.3 - should pause running loop', async ({ page }) => {
|
||||
test('L3.16 - Terminate running loop', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Navigate to loops page
|
||||
// Mock API for termination
|
||||
await page.route('**/api/loops/*/terminate', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ success: true, status: 'terminated' })
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/loops', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for running loop
|
||||
const runningLoops = page.getByTestId(/loop-item|loop-card/).filter({ hasText: /running|active/i }).or(
|
||||
page.locator('.loop-item').filter({ hasText: /running|active/i })
|
||||
);
|
||||
const runningLoops = page.locator('.loop-item').filter({ hasText: /running|active/i });
|
||||
|
||||
const count = await runningLoops.count();
|
||||
|
||||
if (count > 0) {
|
||||
const firstLoop = runningLoops.first();
|
||||
|
||||
// Look for pause button
|
||||
const pauseButton = firstLoop.getByRole('button', { name: /pause/i }).or(
|
||||
firstLoop.getByTestId('pause-button')
|
||||
// Look for terminate button
|
||||
const terminateButton = firstLoop.getByRole('button', { name: /terminate|stop|end/i }).or(
|
||||
firstLoop.getByTestId('loop-terminate-button')
|
||||
);
|
||||
|
||||
const hasPauseButton = await pauseButton.isVisible().catch(() => false);
|
||||
const hasTerminateButton = await terminateButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasPauseButton) {
|
||||
await pauseButton.click();
|
||||
if (hasTerminateButton) {
|
||||
await terminateButton.click();
|
||||
|
||||
// Verify loop is paused
|
||||
|
||||
const pausedIndicator = firstLoop.getByText(/paused/i);
|
||||
const hasPaused = await pausedIndicator.isVisible().catch(() => false);
|
||||
expect(hasPaused).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.4 - should resume paused loop', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Navigate to loops page
|
||||
await page.goto('/loops', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for paused loop
|
||||
const pausedLoops = page.getByTestId(/loop-item|loop-card/).filter({ hasText: /paused/i }).or(
|
||||
page.locator('.loop-item').filter({ hasText: /paused/i })
|
||||
);
|
||||
|
||||
const count = await pausedLoops.count();
|
||||
|
||||
if (count > 0) {
|
||||
const firstLoop = pausedLoops.first();
|
||||
|
||||
// Look for resume button
|
||||
const resumeButton = firstLoop.getByRole('button', { name: /resume|continue/i }).or(
|
||||
firstLoop.getByTestId('resume-button')
|
||||
);
|
||||
|
||||
const hasResumeButton = await resumeButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasResumeButton) {
|
||||
await resumeButton.click();
|
||||
|
||||
// Verify loop is resumed
|
||||
|
||||
const runningIndicator = firstLoop.getByText(/running|active/i);
|
||||
const hasRunning = await runningIndicator.isVisible().catch(() => false);
|
||||
expect(hasRunning).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.5 - should stop loop', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Navigate to loops page
|
||||
await page.goto('/loops', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for active/paused loop
|
||||
const activeLoops = page.locator('.loop-item').filter({ hasText: /running|paused|active/i });
|
||||
|
||||
const count = await activeLoops.count();
|
||||
|
||||
if (count > 0) {
|
||||
const firstLoop = activeLoops.first();
|
||||
|
||||
// Look for stop button
|
||||
const stopButton = firstLoop.getByRole('button', { name: /stop/i }).or(
|
||||
firstLoop.getByTestId('stop-button')
|
||||
);
|
||||
|
||||
const hasStopButton = await stopButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasStopButton) {
|
||||
await stopButton.click();
|
||||
|
||||
// Confirm stop if dialog appears
|
||||
const confirmDialog = page.getByRole('dialog').filter({ hasText: /stop|confirm/i });
|
||||
// Confirm if dialog appears
|
||||
const confirmDialog = page.getByRole('dialog').filter({ hasText: /terminate|confirm/i });
|
||||
const hasDialog = await confirmDialog.isVisible().catch(() => false);
|
||||
|
||||
if (hasDialog) {
|
||||
const confirmButton = page.getByRole('button', { name: /stop|confirm|yes/i });
|
||||
const confirmButton = confirmDialog.getByRole('button', { name: /terminate|confirm|yes/i });
|
||||
await confirmButton.click();
|
||||
}
|
||||
|
||||
// Verify loop is stopped
|
||||
|
||||
const stoppedIndicator = firstLoop.getByText(/stopped|completed/i);
|
||||
const hasStopped = await stoppedIndicator.isVisible().catch(() => false);
|
||||
expect(hasStopped).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.6 - should delete loop', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Navigate to loops page
|
||||
await page.goto('/loops', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for existing loop
|
||||
const loopItems = page.getByTestId(/loop-item|loop-card/).or(
|
||||
page.locator('.loop-item')
|
||||
);
|
||||
|
||||
const itemCount = await loopItems.count();
|
||||
|
||||
if (itemCount > 0) {
|
||||
const firstLoop = loopItems.first();
|
||||
|
||||
// Look for delete button
|
||||
const deleteButton = firstLoop.getByRole('button', { name: /delete|remove/i }).or(
|
||||
firstLoop.getByTestId('delete-button')
|
||||
);
|
||||
|
||||
const hasDeleteButton = await deleteButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasDeleteButton) {
|
||||
await deleteButton.click();
|
||||
|
||||
// Confirm delete if dialog appears
|
||||
const confirmDialog = page.getByRole('dialog').filter({ hasText: /delete|confirm/i });
|
||||
const hasDialog = await confirmDialog.isVisible().catch(() => false);
|
||||
|
||||
if (hasDialog) {
|
||||
const confirmButton = page.getByRole('button', { name: /delete|confirm|yes/i });
|
||||
await confirmButton.click();
|
||||
}
|
||||
|
||||
// Verify success message
|
||||
|
||||
const successMessage = page.getByText(/deleted|success/i);
|
||||
// Verify success
|
||||
const successMessage = page.getByText(/terminated|stopped|success/i);
|
||||
const hasSuccess = await successMessage.isVisible().catch(() => false);
|
||||
expect(hasSuccess).toBe(true);
|
||||
}
|
||||
@@ -277,10 +201,23 @@ test.describe('[Loops] - Loop Management Tests', () => {
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.7 - should display loop status correctly', async ({ page }) => {
|
||||
test('L3.17 - View loop execution logs', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Navigate to loops page
|
||||
// Mock API for logs
|
||||
await page.route('**/api/loops/*/logs', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
logs: [
|
||||
{ timestamp: Date.now(), level: 'info', message: 'Loop started' },
|
||||
{ timestamp: Date.now(), level: 'info', message: 'Processing step 1' }
|
||||
]
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/loops', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for loop items
|
||||
@@ -290,51 +227,26 @@ test.describe('[Loops] - Loop Management Tests', () => {
|
||||
|
||||
const itemCount = await loopItems.count();
|
||||
|
||||
if (itemCount > 0) {
|
||||
// Check each loop has a status indicator
|
||||
for (let i = 0; i < Math.min(itemCount, 3); i++) {
|
||||
const loop = loopItems.nth(i);
|
||||
|
||||
// Look for status indicator
|
||||
const statusIndicator = loop.getByTestId('loop-status').or(
|
||||
loop.locator('*').filter({ hasText: /running|paused|stopped|completed|created/i })
|
||||
);
|
||||
|
||||
const hasStatus = await statusIndicator.isVisible().catch(() => false);
|
||||
expect(hasStatus).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.8 - should display loop progress', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Navigate to loops page
|
||||
await page.goto('/loops', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for loop items with progress
|
||||
const loopItems = page.getByTestId(/loop-item|loop-card/).or(
|
||||
page.locator('.loop-item')
|
||||
);
|
||||
|
||||
const itemCount = await loopItems.count();
|
||||
|
||||
if (itemCount > 0) {
|
||||
const firstLoop = loopItems.first();
|
||||
|
||||
// Look for progress bar or step indicator
|
||||
const progressBar = firstLoop.getByTestId('loop-progress').or(
|
||||
firstLoop.locator('*').filter({ hasText: /\d+\/\d+|step/i })
|
||||
// Look for logs button/panel
|
||||
const logsButton = firstLoop.getByRole('button', { name: /logs|view logs/i }).or(
|
||||
firstLoop.getByTestId('view-logs-button')
|
||||
);
|
||||
|
||||
const hasProgress = await progressBar.isVisible().catch(() => false);
|
||||
const hasLogsButton = await logsButton.isVisible().catch(() => false);
|
||||
|
||||
// Progress is optional but if present should be visible
|
||||
if (hasProgress) {
|
||||
expect(progressBar).toBeVisible();
|
||||
if (hasLogsButton) {
|
||||
await logsButton.click();
|
||||
|
||||
// Look for logs panel
|
||||
const logsPanel = page.getByTestId('loop-logs-panel').or(
|
||||
page.getByRole('dialog').filter({ hasText: /logs/i })
|
||||
);
|
||||
|
||||
const hasLogsPanel = await logsPanel.isVisible().catch(() => false);
|
||||
expect(hasLogsPanel).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,80 +254,96 @@ test.describe('[Loops] - Loop Management Tests', () => {
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.9 - should handle loop creation errors gracefully', async ({ page }) => {
|
||||
test('L3.18 - i18n - Loop status in EN/ZH', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API failure
|
||||
await page.goto('/loops', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Get language switcher
|
||||
const languageSwitcher = page.getByRole('combobox', { name: /select language|language/i }).first();
|
||||
|
||||
const hasLanguageSwitcher = await languageSwitcher.isVisible().catch(() => false);
|
||||
|
||||
if (hasLanguageSwitcher) {
|
||||
// Switch to Chinese
|
||||
await switchLanguageAndVerify(page, 'zh', languageSwitcher);
|
||||
|
||||
// Verify loop status in Chinese
|
||||
const statusText = page.getByText(/运行|停止|完成/i);
|
||||
const hasChineseStatus = await statusText.isVisible().catch(() => false);
|
||||
|
||||
// Chinese status may or may not be visible depending on active loops
|
||||
if (hasChineseStatus) {
|
||||
expect(hasChineseStatus).toBe(true);
|
||||
}
|
||||
|
||||
// Switch back to English
|
||||
await switchLanguageAndVerify(page, 'en', languageSwitcher);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.19 - Error - Failed loop displays error', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for failed loop
|
||||
await page.route('**/api/loops', (route) => {
|
||||
route.fulfill({
|
||||
status: 500,
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Internal Server Error' }),
|
||||
body: JSON.stringify({
|
||||
loops: [
|
||||
{
|
||||
id: 'loop-failed',
|
||||
name: 'Failed Loop',
|
||||
status: 'failed',
|
||||
error: 'Connection timeout'
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Navigate to loops page
|
||||
await page.goto('/loops', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for create button
|
||||
const createButton = page.getByRole('button', { name: /create|new|add/i });
|
||||
const hasCreateButton = await createButton.isVisible().catch(() => false);
|
||||
// Look for error indicator
|
||||
const errorIndicator = page.getByText(/failed|error|timeout/i).or(
|
||||
page.getByTestId('loop-error')
|
||||
);
|
||||
|
||||
if (hasCreateButton) {
|
||||
await createButton.click();
|
||||
const hasError = await errorIndicator.isVisible().catch(() => false);
|
||||
|
||||
// Try to create loop
|
||||
const promptInput = page.getByRole('textbox', { name: /prompt|description/i });
|
||||
const hasPromptInput = await promptInput.isVisible().catch(() => false);
|
||||
|
||||
if (hasPromptInput) {
|
||||
await promptInput.fill('Test prompt');
|
||||
|
||||
const submitButton = page.getByRole('button', { name: /create|save|submit/i });
|
||||
await submitButton.click();
|
||||
|
||||
// Look for error message
|
||||
|
||||
const errorMessage = page.getByText(/error|failed|unable/i);
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
expect(hasError).toBe(true);
|
||||
}
|
||||
if (hasError) {
|
||||
expect(errorIndicator).toBeVisible();
|
||||
}
|
||||
|
||||
// Restore routing
|
||||
await page.unroute('**/api/loops');
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/loops'], allowWarnings: true });
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.10 - should support batch operations on loops', async ({ page }) => {
|
||||
test('L3.20 - Edge - No loops available', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Navigate to loops page
|
||||
// Mock API for empty loops
|
||||
await page.route('**/api/loops', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ loops: [] })
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto('/loops', { waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for batch operation controls
|
||||
const selectAllCheckbox = page.getByRole('checkbox', { name: /select all/i }).or(
|
||||
page.getByTestId('select-all-loops')
|
||||
// Look for empty state
|
||||
const emptyState = page.getByTestId('empty-state').or(
|
||||
page.getByText(/no loops|empty|get started/i)
|
||||
);
|
||||
|
||||
const hasSelectAll = await selectAllCheckbox.isVisible().catch(() => false);
|
||||
|
||||
if (hasSelectAll) {
|
||||
await selectAllCheckbox.check();
|
||||
|
||||
// Look for batch action buttons
|
||||
const batchStopButton = page.getByRole('button', { name: /stop selected|stop all/i }).or(
|
||||
page.getByTestId('batch-stop-button')
|
||||
);
|
||||
|
||||
const hasBatchStop = await batchStopButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasBatchStop) {
|
||||
expect(batchStopButton).toBeVisible();
|
||||
}
|
||||
}
|
||||
const hasEmptyState = await emptyState.isVisible().catch(() => false);
|
||||
expect(hasEmptyState).toBe(true);
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
|
||||
563
ccw/frontend/tests/e2e/orchestrator.spec.ts
Normal file
563
ccw/frontend/tests/e2e/orchestrator.spec.ts
Normal file
@@ -0,0 +1,563 @@
|
||||
// ========================================
|
||||
// E2E Tests: Orchestrator - Workflow Canvas
|
||||
// ========================================
|
||||
// End-to-end tests for workflow orchestration with @xyflow/react canvas
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { setupEnhancedMonitoring, switchLanguageAndVerify } from './helpers/i18n-helpers';
|
||||
|
||||
test.describe('[Orchestrator] - Workflow Canvas Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/orchestrator', { waitUntil: 'networkidle' as const });
|
||||
});
|
||||
|
||||
test('L3.01 - Canvas loads and displays nodes', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API response for workflows
|
||||
await page.route('**/api/workflows', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
workflows: [
|
||||
{
|
||||
id: 'wf-1',
|
||||
name: 'Test Workflow',
|
||||
nodes: [
|
||||
{ id: 'node-1', type: 'start', position: { x: 100, y: 100 } },
|
||||
{ id: 'node-2', type: 'action', position: { x: 300, y: 100 } }
|
||||
],
|
||||
edges: [
|
||||
{ id: 'edge-1', source: 'node-1', target: 'node-2' }
|
||||
]
|
||||
}
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
limit: 10
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Reload page to trigger API call
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for workflow canvas
|
||||
const canvas = page.getByTestId('workflow-canvas').or(
|
||||
page.locator('.react-flow')
|
||||
);
|
||||
|
||||
const isCanvasVisible = await canvas.isVisible().catch(() => false);
|
||||
|
||||
if (isCanvasVisible) {
|
||||
// Verify nodes are displayed
|
||||
const nodes = page.locator('.react-flow-node').or(
|
||||
page.getByTestId(/node-/)
|
||||
);
|
||||
|
||||
const nodeCount = await nodes.count();
|
||||
expect(nodeCount).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.02 - Create new node via drag-drop', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for node creation
|
||||
await page.route('**/api/workflows', (route) => {
|
||||
if (route.request().method() === 'POST') {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ success: true, id: 'new-node-1' })
|
||||
});
|
||||
} else {
|
||||
route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Look for node library or create button
|
||||
const nodeLibrary = page.getByTestId('node-library').or(
|
||||
page.getByTestId('node-create-button')
|
||||
);
|
||||
|
||||
const hasLibrary = await nodeLibrary.isVisible().catch(() => false);
|
||||
|
||||
if (hasLibrary) {
|
||||
// Find a draggable node type
|
||||
const nodeType = nodeLibrary.locator('[data-node-type]').first();
|
||||
const hasNodeType = await nodeType.isVisible().catch(() => false);
|
||||
|
||||
if (hasNodeType) {
|
||||
const canvas = page.getByTestId('workflow-canvas').or(
|
||||
page.locator('.react-flow')
|
||||
);
|
||||
|
||||
const canvasBox = await canvas.boundingBox();
|
||||
if (canvasBox) {
|
||||
// Simulate drag-drop
|
||||
await nodeType.dragTo(canvas, {
|
||||
targetPosition: { x: canvasBox.x + 200, y: canvasBox.y + 200 }
|
||||
});
|
||||
|
||||
// Wait for node to appear
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify new node exists
|
||||
const newNode = page.locator('.react-flow-node').or(
|
||||
page.getByTestId(/node-/)
|
||||
);
|
||||
|
||||
const nodeCount = await newNode.count();
|
||||
expect(nodeCount).toBeGreaterThan(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.03 - Connect nodes with edges', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for edge creation
|
||||
await page.route('**/api/workflows/*', (route) => {
|
||||
if (route.request().method() === 'PUT') {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ success: true })
|
||||
});
|
||||
} else {
|
||||
route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Look for existing nodes
|
||||
const nodes = page.locator('.react-flow-node').or(
|
||||
page.getByTestId(/node-/)
|
||||
);
|
||||
|
||||
const nodeCount = await nodes.count();
|
||||
|
||||
if (nodeCount >= 2) {
|
||||
const sourceNode = nodes.first();
|
||||
const targetNode = nodes.nth(1);
|
||||
|
||||
// Get node positions
|
||||
const sourceBox = await sourceNode.boundingBox();
|
||||
const targetBox = await targetNode.boundingBox();
|
||||
|
||||
if (sourceBox && targetBox) {
|
||||
// Click and drag from source to target to create edge
|
||||
await page.mouse.move(sourceBox.x + sourceBox.width, sourceBox.y + sourceBox.height / 2);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(targetBox.x, targetBox.y + targetBox.height / 2);
|
||||
await page.mouse.up();
|
||||
|
||||
// Wait for edge to be created
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Verify edge exists
|
||||
const edges = page.locator('.react-flow-edge').or(
|
||||
page.getByTestId(/edge-/)
|
||||
);
|
||||
|
||||
const edgeCount = await edges.count();
|
||||
expect(edgeCount).toBeGreaterThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.04 - Delete node and verify edge removal', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for node deletion
|
||||
await page.route('**/api/workflows/*', (route) => {
|
||||
if (route.request().method() === 'DELETE') {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ success: true })
|
||||
});
|
||||
} else {
|
||||
route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Look for nodes
|
||||
const nodes = page.locator('.react-flow-node').or(
|
||||
page.getByTestId(/node-/)
|
||||
);
|
||||
|
||||
const nodeCount = await nodes.count();
|
||||
|
||||
if (nodeCount > 0) {
|
||||
const firstNode = nodes.first();
|
||||
|
||||
// Get initial edge count
|
||||
const edgesBefore = await page.locator('.react-flow-edge').count();
|
||||
|
||||
// Select node and look for delete button
|
||||
await firstNode.click();
|
||||
|
||||
const deleteButton = page.getByRole('button', { name: /delete|remove/i }).or(
|
||||
page.getByTestId('node-delete-button')
|
||||
);
|
||||
|
||||
const hasDeleteButton = await deleteButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasDeleteButton) {
|
||||
await deleteButton.click();
|
||||
|
||||
// Wait for node to be removed
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Verify node count decreased
|
||||
const nodesAfter = await page.locator('.react-flow-node').count();
|
||||
expect(nodesAfter).toBeLessThan(nodeCount);
|
||||
|
||||
// Verify edges connected to deleted node are removed
|
||||
const edgesAfter = await page.locator('.react-flow-edge').count();
|
||||
expect(edgesAfter).toBeLessThanOrEqual(edgesBefore);
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.05 - Zoom in/out functionality', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Look for zoom controls
|
||||
const zoomControls = page.getByTestId('zoom-controls').or(
|
||||
page.locator('.react-flow-controls')
|
||||
);
|
||||
|
||||
const hasZoomControls = await zoomControls.isVisible().catch(() => false);
|
||||
|
||||
if (hasZoomControls) {
|
||||
const zoomInButton = zoomControls.getByRole('button').first();
|
||||
const zoomOutButton = zoomControls.getByRole('button').nth(1);
|
||||
|
||||
// Get initial zoom level
|
||||
const initialZoom = await page.evaluate(() => {
|
||||
const container = document.querySelector('.react-flow');
|
||||
return container ? getComputedStyle(container).transform : 'none';
|
||||
});
|
||||
|
||||
// Click zoom in
|
||||
await zoomInButton.click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// Click zoom out
|
||||
await zoomOutButton.click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// Verify controls are still functional
|
||||
const isStillVisible = await zoomControls.isVisible();
|
||||
expect(isStillVisible).toBe(true);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.06 - Pan canvas functionality', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Look for canvas
|
||||
const canvas = page.getByTestId('workflow-canvas').or(
|
||||
page.locator('.react-flow')
|
||||
);
|
||||
|
||||
const isCanvasVisible = await canvas.isVisible().catch(() => false);
|
||||
|
||||
if (isCanvasVisible) {
|
||||
const canvasBox = await canvas.boundingBox();
|
||||
if (canvasBox) {
|
||||
// Simulate panning by clicking and dragging on canvas
|
||||
await page.mouse.move(canvasBox.x + 100, canvasBox.y + 100);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(canvasBox.x + 200, canvasBox.y + 150);
|
||||
await page.mouse.up();
|
||||
|
||||
// Wait for pan to complete
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Verify canvas is still visible after pan
|
||||
const isStillVisible = await canvas.isVisible();
|
||||
expect(isStillVisible).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.07 - Save workflow state', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for saving workflow
|
||||
await page.route('**/api/workflows/*', (route) => {
|
||||
if (route.request().method() === 'PUT') {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ success: true, saved: true })
|
||||
});
|
||||
} else {
|
||||
route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Look for save button
|
||||
const saveButton = page.getByRole('button', { name: /save/i }).or(
|
||||
page.getByTestId('workflow-save-button')
|
||||
);
|
||||
|
||||
const hasSaveButton = await saveButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasSaveButton) {
|
||||
await saveButton.click();
|
||||
|
||||
// Look for success indicator
|
||||
const successMessage = page.getByText(/saved|success/i).or(
|
||||
page.getByTestId('save-success')
|
||||
);
|
||||
|
||||
const hasSuccess = await successMessage.isVisible().catch(() => false);
|
||||
expect(hasSuccess).toBe(true);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.08 - Load existing workflow', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for loading workflows
|
||||
await page.route('**/api/workflows', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
workflows: [
|
||||
{
|
||||
id: 'wf-existing',
|
||||
name: 'Existing Workflow',
|
||||
nodes: [
|
||||
{ id: 'node-1', type: 'start', position: { x: 100, y: 100 } }
|
||||
],
|
||||
edges: []
|
||||
}
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
limit: 10
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Reload to trigger API
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for workflow list selector
|
||||
const workflowSelector = page.getByRole('combobox', { name: /workflow|select/i }).or(
|
||||
page.getByTestId('workflow-selector')
|
||||
);
|
||||
|
||||
const hasSelector = await workflowSelector.isVisible().catch(() => false);
|
||||
|
||||
if (hasSelector) {
|
||||
const options = await workflowSelector.locator('option').count();
|
||||
if (options > 0) {
|
||||
await workflowSelector.selectOption({ index: 0 });
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify canvas has loaded content
|
||||
const canvas = page.getByTestId('workflow-canvas').or(
|
||||
page.locator('.react-flow')
|
||||
);
|
||||
|
||||
const isCanvasVisible = await canvas.isVisible();
|
||||
expect(isCanvasVisible).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.09 - Export workflow configuration', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for export
|
||||
await page.route('**/api/workflows/*/export', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
id: 'wf-1',
|
||||
name: 'Exported Workflow',
|
||||
nodes: [],
|
||||
edges: []
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Look for export button
|
||||
const exportButton = page.getByRole('button', { name: /export/i }).or(
|
||||
page.getByTestId('workflow-export-button')
|
||||
);
|
||||
|
||||
const hasExportButton = await exportButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasExportButton) {
|
||||
await exportButton.click();
|
||||
|
||||
// Look for export dialog or download
|
||||
const exportDialog = page.getByRole('dialog').filter({ hasText: /export/i });
|
||||
const hasDialog = await exportDialog.isVisible().catch(() => false);
|
||||
|
||||
if (hasDialog) {
|
||||
const confirmButton = exportDialog.getByRole('button', { name: /export|download|save/i });
|
||||
await confirmButton.click();
|
||||
}
|
||||
|
||||
// Verify some indication of export
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.10 - i18n - Node labels in EN/ZH', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Get language switcher
|
||||
const languageSwitcher = page.getByRole('combobox', { name: /select language|language/i }).first();
|
||||
|
||||
const hasLanguageSwitcher = await languageSwitcher.isVisible().catch(() => false);
|
||||
|
||||
if (hasLanguageSwitcher) {
|
||||
// Switch to Chinese
|
||||
await switchLanguageAndVerify(page, 'zh', languageSwitcher);
|
||||
|
||||
// Verify canvas elements exist in Chinese context
|
||||
const canvas = page.getByTestId('workflow-canvas').or(
|
||||
page.locator('.react-flow')
|
||||
);
|
||||
|
||||
const isCanvasVisible = await canvas.isVisible().catch(() => false);
|
||||
expect(isCanvasVisible).toBe(true);
|
||||
|
||||
// Switch back to English
|
||||
await switchLanguageAndVerify(page, 'en', languageSwitcher);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.11 - Error - Node with invalid configuration', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API error response
|
||||
await page.route('**/api/workflows', (route) => {
|
||||
if (route.request().method() === 'POST') {
|
||||
route.fulfill({
|
||||
status: 400,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Invalid node configuration' })
|
||||
});
|
||||
} else {
|
||||
route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Look for create button
|
||||
const createButton = page.getByRole('button', { name: /create|add node/i }).or(
|
||||
page.getByTestId('node-create-button')
|
||||
);
|
||||
|
||||
const hasCreateButton = await createButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasCreateButton) {
|
||||
await createButton.click();
|
||||
|
||||
// Try to create node without required fields (this should trigger error)
|
||||
const submitButton = page.getByRole('button', { name: /create|save|submit/i });
|
||||
const hasSubmit = await submitButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasSubmit) {
|
||||
await submitButton.click();
|
||||
|
||||
// Look for error message
|
||||
const errorMessage = page.getByText(/invalid|error|required/i).or(
|
||||
page.getByTestId('error-message')
|
||||
);
|
||||
|
||||
const hasError = await errorMessage.isVisible().catch(() => false);
|
||||
// Error message may or may not appear depending on validation
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/workflows'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.12 - Edge - Maximum nodes limit', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API to enforce limit
|
||||
await page.route('**/api/workflows', (route) => {
|
||||
if (route.request().method() === 'POST') {
|
||||
route.fulfill({
|
||||
status: 409,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Maximum node limit reached' })
|
||||
});
|
||||
} else {
|
||||
route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Try to create multiple nodes rapidly
|
||||
const createButton = page.getByRole('button', { name: /create|add/i }).or(
|
||||
page.getByTestId('node-create-button')
|
||||
);
|
||||
|
||||
const hasCreateButton = await createButton.isVisible().catch(() => false);
|
||||
|
||||
if (hasCreateButton) {
|
||||
// Attempt multiple creates
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await createButton.click();
|
||||
await page.waitForTimeout(100);
|
||||
}
|
||||
|
||||
// Look for limit error message
|
||||
const limitMessage = page.getByText(/limit|maximum|too many/i).or(
|
||||
page.getByTestId('limit-message')
|
||||
);
|
||||
|
||||
const hasLimitMessage = await limitMessage.isVisible().catch(() => false);
|
||||
// Limit message may or may not appear
|
||||
}
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/workflows'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
});
|
||||
192
ccw/frontend/tests/e2e/project.spec.ts
Normal file
192
ccw/frontend/tests/e2e/project.spec.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
// ========================================
|
||||
// E2E Tests: Project - Development Timeline and Statistics
|
||||
// ========================================
|
||||
// End-to-end tests for project overview with statistics and timeline
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { setupEnhancedMonitoring, switchLanguageAndVerify } from './helpers/i18n-helpers';
|
||||
|
||||
test.describe('[Project] - Development Statistics Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/project', { waitUntil: 'networkidle' as const });
|
||||
});
|
||||
|
||||
test('L3.39 - Page loads and displays project statistics', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for project stats
|
||||
await page.route('**/api/project', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
stats: {
|
||||
totalCommits: 150,
|
||||
totalFiles: 450,
|
||||
totalLines: 25000,
|
||||
languages: ['TypeScript', 'JavaScript', 'CSS'],
|
||||
contributors: 5
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Reload to trigger API
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for project stats
|
||||
const projectStats = page.getByTestId('project-stats').or(
|
||||
page.locator('.project-stats')
|
||||
);
|
||||
|
||||
const isStatsVisible = await projectStats.isVisible().catch(() => false);
|
||||
|
||||
if (isStatsVisible) {
|
||||
// Verify stat items are displayed
|
||||
const statItems = page.getByTestId(/stat-|stat-card/).or(
|
||||
page.locator('.stat-item')
|
||||
);
|
||||
|
||||
const statCount = await statItems.count();
|
||||
expect(statCount).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.40 - View development timeline', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Look for timeline section
|
||||
const timeline = page.getByTestId('timeline-chart').or(
|
||||
page.getByText(/timeline|activity|history/i)
|
||||
);
|
||||
|
||||
const hasTimeline = await timeline.isVisible().catch(() => false);
|
||||
|
||||
if (hasTimeline) {
|
||||
// Verify timeline has content
|
||||
const timelineContent = timeline.locator('*').filter({ hasText: /\d+|commit|activity/i });
|
||||
const hasContent = await timelineContent.isVisible().catch(() => false);
|
||||
|
||||
if (hasContent) {
|
||||
expect(timelineContent).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.41 - View contribution graph', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Look for contribution graph
|
||||
const contributionGraph = page.getByTestId('contribution-graph').or(
|
||||
page.locator('.contribution-graph')
|
||||
);
|
||||
|
||||
const hasGraph = await contributionGraph.isVisible().catch(() => false);
|
||||
|
||||
if (hasGraph) {
|
||||
// Verify graph has visualization elements
|
||||
const graphElements = contributionGraph.locator('*').filter({ hasText: /\d+/ });
|
||||
const elementCount = await graphElements.count();
|
||||
expect(elementCount).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.42 - i18n - Project stats in EN/ZH', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Get language switcher
|
||||
const languageSwitcher = page.getByRole('combobox', { name: /select language|language/i }).first();
|
||||
|
||||
const hasLanguageSwitcher = await languageSwitcher.isVisible().catch(() => false);
|
||||
|
||||
if (hasLanguageSwitcher) {
|
||||
// Switch to Chinese
|
||||
await switchLanguageAndVerify(page, 'zh', languageSwitcher);
|
||||
|
||||
// Verify project stats are in Chinese
|
||||
const pageContent = await page.content();
|
||||
const hasChineseText = /[\u4e00-\u9fa5]/.test(pageContent);
|
||||
expect(hasChineseText).toBe(true);
|
||||
|
||||
// Switch back to English
|
||||
await switchLanguageAndVerify(page, 'en', languageSwitcher);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.43 - Error - Failed to load project data', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API error
|
||||
await page.route('**/api/project', (route) => {
|
||||
route.fulfill({
|
||||
status: 500,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'Failed to load project data' })
|
||||
});
|
||||
});
|
||||
|
||||
// Reload to trigger API
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for error indicator
|
||||
const errorIndicator = page.getByText(/error|failed|unable to load/i).or(
|
||||
page.getByTestId('error-state')
|
||||
);
|
||||
|
||||
const hasError = await errorIndicator.isVisible().catch(() => false);
|
||||
|
||||
if (hasError) {
|
||||
expect(errorIndicator).toBeVisible();
|
||||
}
|
||||
|
||||
monitoring.assertClean({ ignoreAPIPatterns: ['/api/project'], allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.44 - Edge - No commits state', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Mock API for no commits
|
||||
await page.route('**/api/project', (route) => {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
stats: {
|
||||
totalCommits: 0,
|
||||
totalFiles: 0,
|
||||
totalLines: 0,
|
||||
languages: [],
|
||||
contributors: 0
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Reload to trigger API
|
||||
await page.reload({ waitUntil: 'networkidle' as const });
|
||||
|
||||
// Look for empty state or zero stats
|
||||
const emptyState = page.getByText(/no commits|no activity|empty/i);
|
||||
const hasEmptyState = await emptyState.isVisible().catch(() => false);
|
||||
|
||||
if (hasEmptyState) {
|
||||
expect(emptyState).toBeVisible();
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user