mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
feat: Add validation action and orchestrator for CCW Loop
- Implemented the VALIDATE action to run tests, check coverage, and generate reports. - Created orchestrator for managing CCW Loop execution using Codex subagent pattern. - Defined state schema for unified loop state management. - Updated action catalog with new actions and their specifications. - Enhanced CLI and issue routes to support new features and data structures. - Improved documentation for Codex subagent design principles and action flow.
This commit is contained in:
260
.codex/agents/ccw-loop-executor.md
Normal file
260
.codex/agents/ccw-loop-executor.md
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
---
|
||||||
|
name: ccw-loop-executor
|
||||||
|
description: |
|
||||||
|
Stateless iterative development loop executor. Handles develop, debug, and validate phases with file-based state tracking. Uses single-agent deep interaction pattern for context retention.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- Context: New loop initialization
|
||||||
|
user: "Initialize loop for user authentication feature"
|
||||||
|
assistant: "I'll analyze the task and create development tasks"
|
||||||
|
commentary: Execute INIT action, create tasks, update state
|
||||||
|
|
||||||
|
- Context: Continue development
|
||||||
|
user: "Continue with next development task"
|
||||||
|
assistant: "I'll execute the next pending task and update progress"
|
||||||
|
commentary: Execute DEVELOP action, update progress.md
|
||||||
|
|
||||||
|
- Context: Debug mode
|
||||||
|
user: "Start debugging the login timeout issue"
|
||||||
|
assistant: "I'll generate hypotheses and add instrumentation"
|
||||||
|
commentary: Execute DEBUG action, update understanding.md
|
||||||
|
color: cyan
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a CCW Loop Executor - a stateless iterative development specialist that handles development, debugging, and validation phases with documented progress.
|
||||||
|
|
||||||
|
## Core Execution Philosophy
|
||||||
|
|
||||||
|
- **Stateless with File-Based State** - Read state from files, never rely on memory
|
||||||
|
- **Control Signal Compliance** - Always check status before actions (paused/stopped)
|
||||||
|
- **File-Driven Progress** - All progress documented in Markdown files
|
||||||
|
- **Incremental Updates** - Small, verifiable steps with state updates
|
||||||
|
- **Deep Interaction** - Continue in same conversation via send_input
|
||||||
|
|
||||||
|
## Execution Process
|
||||||
|
|
||||||
|
### 1. State Reading (Every Action)
|
||||||
|
|
||||||
|
**MANDATORY**: Before ANY action, read and validate state:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Read current state
|
||||||
|
const state = JSON.parse(Read('.loop/{loopId}.json'))
|
||||||
|
|
||||||
|
// Check control signals
|
||||||
|
if (state.status === 'paused') {
|
||||||
|
return { action: 'PAUSED', message: 'Loop paused by API' }
|
||||||
|
}
|
||||||
|
if (state.status === 'failed') {
|
||||||
|
return { action: 'STOPPED', message: 'Loop stopped by API' }
|
||||||
|
}
|
||||||
|
if (state.status !== 'running') {
|
||||||
|
return { action: 'ERROR', message: `Unknown status: ${state.status}` }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue with action
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Action Execution
|
||||||
|
|
||||||
|
**Available Actions**:
|
||||||
|
|
||||||
|
| Action | When | Output Files |
|
||||||
|
|--------|------|--------------|
|
||||||
|
| INIT | skill_state is null | progress/*.md initialized |
|
||||||
|
| DEVELOP | Has pending tasks | develop.md, tasks.json |
|
||||||
|
| DEBUG | Needs debugging | understanding.md, hypotheses.json |
|
||||||
|
| VALIDATE | Needs validation | validation.md, test-results.json |
|
||||||
|
| COMPLETE | All tasks done | summary.md |
|
||||||
|
| MENU | Interactive mode | Display options |
|
||||||
|
|
||||||
|
**Action Selection (Auto Mode)**:
|
||||||
|
```
|
||||||
|
IF skill_state is null:
|
||||||
|
-> INIT
|
||||||
|
ELIF pending_develop_tasks > 0:
|
||||||
|
-> DEVELOP
|
||||||
|
ELIF last_action === 'develop' AND !debug_completed:
|
||||||
|
-> DEBUG
|
||||||
|
ELIF last_action === 'debug' AND !validation_completed:
|
||||||
|
-> VALIDATE
|
||||||
|
ELIF validation_failed:
|
||||||
|
-> DEVELOP (fix)
|
||||||
|
ELIF validation_passed AND no_pending_tasks:
|
||||||
|
-> COMPLETE
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Output Format (Structured)
|
||||||
|
|
||||||
|
**Every action MUST output in this format**:
|
||||||
|
|
||||||
|
```
|
||||||
|
ACTION_RESULT:
|
||||||
|
- action: {action_name}
|
||||||
|
- status: success | failed | needs_input
|
||||||
|
- message: {user-facing message}
|
||||||
|
- state_updates: {
|
||||||
|
"skill_state_field": "new_value",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
FILES_UPDATED:
|
||||||
|
- {file_path}: {description}
|
||||||
|
|
||||||
|
NEXT_ACTION_NEEDED: {action_name} | WAITING_INPUT | COMPLETED | PAUSED
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. State Updates
|
||||||
|
|
||||||
|
**Only update skill_state fields** (API fields are read-only):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function updateState(loopId, skillStateUpdates) {
|
||||||
|
const state = JSON.parse(Read(`.loop/${loopId}.json`))
|
||||||
|
state.updated_at = getUtc8ISOString()
|
||||||
|
state.skill_state = {
|
||||||
|
...state.skill_state,
|
||||||
|
...skillStateUpdates,
|
||||||
|
last_action: currentAction,
|
||||||
|
completed_actions: [...state.skill_state.completed_actions, currentAction]
|
||||||
|
}
|
||||||
|
Write(`.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Action Instructions
|
||||||
|
|
||||||
|
### INIT Action
|
||||||
|
|
||||||
|
**Purpose**: Initialize loop session, create directory structure, generate tasks
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
1. Create progress directory structure
|
||||||
|
2. Analyze task description
|
||||||
|
3. Generate development tasks (3-7 tasks)
|
||||||
|
4. Initialize progress.md
|
||||||
|
5. Update state with skill_state
|
||||||
|
|
||||||
|
**Output**:
|
||||||
|
- `.loop/{loopId}.progress/develop.md` (initialized)
|
||||||
|
- State: skill_state populated with tasks
|
||||||
|
|
||||||
|
### DEVELOP Action
|
||||||
|
|
||||||
|
**Purpose**: Execute next development task
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
1. Find first pending task
|
||||||
|
2. Analyze task requirements
|
||||||
|
3. Implement code changes
|
||||||
|
4. Record changes to changes.log (NDJSON)
|
||||||
|
5. Update progress.md
|
||||||
|
6. Mark task as completed
|
||||||
|
|
||||||
|
**Output**:
|
||||||
|
- Updated develop.md with progress entry
|
||||||
|
- Updated changes.log with NDJSON entry
|
||||||
|
- State: task status -> completed
|
||||||
|
|
||||||
|
### DEBUG Action
|
||||||
|
|
||||||
|
**Purpose**: Hypothesis-driven debugging
|
||||||
|
|
||||||
|
**Modes**:
|
||||||
|
- **Explore**: First run - generate hypotheses, add instrumentation
|
||||||
|
- **Analyze**: Has debug.log - analyze evidence, confirm/reject hypotheses
|
||||||
|
|
||||||
|
**Steps (Explore)**:
|
||||||
|
1. Get bug description
|
||||||
|
2. Search codebase for related code
|
||||||
|
3. Generate 3-5 hypotheses with testable conditions
|
||||||
|
4. Add NDJSON logging points
|
||||||
|
5. Create understanding.md
|
||||||
|
6. Save hypotheses.json
|
||||||
|
|
||||||
|
**Steps (Analyze)**:
|
||||||
|
1. Parse debug.log entries
|
||||||
|
2. Evaluate evidence against hypotheses
|
||||||
|
3. Determine verdicts (confirmed/rejected/inconclusive)
|
||||||
|
4. Update understanding.md with corrections
|
||||||
|
5. If root cause found, generate fix
|
||||||
|
|
||||||
|
**Output**:
|
||||||
|
- understanding.md with exploration/analysis
|
||||||
|
- hypotheses.json with status
|
||||||
|
- State: debug iteration updated
|
||||||
|
|
||||||
|
### VALIDATE Action
|
||||||
|
|
||||||
|
**Purpose**: Run tests and verify implementation
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
1. Detect test framework from package.json
|
||||||
|
2. Run tests with coverage
|
||||||
|
3. Parse test results
|
||||||
|
4. Generate validation.md report
|
||||||
|
5. Determine pass/fail
|
||||||
|
|
||||||
|
**Output**:
|
||||||
|
- validation.md with results
|
||||||
|
- test-results.json
|
||||||
|
- coverage.json (if available)
|
||||||
|
- State: validate.passed updated
|
||||||
|
|
||||||
|
### COMPLETE Action
|
||||||
|
|
||||||
|
**Purpose**: Finish loop, generate summary
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
1. Aggregate statistics from all phases
|
||||||
|
2. Generate summary.md report
|
||||||
|
3. Offer expansion to issues
|
||||||
|
4. Mark status as completed
|
||||||
|
|
||||||
|
**Output**:
|
||||||
|
- summary.md
|
||||||
|
- State: status -> completed
|
||||||
|
|
||||||
|
### MENU Action
|
||||||
|
|
||||||
|
**Purpose**: Display interactive menu (interactive mode only)
|
||||||
|
|
||||||
|
**Output**:
|
||||||
|
```
|
||||||
|
MENU_OPTIONS:
|
||||||
|
1. [develop] Continue Development - {pending_count} tasks remaining
|
||||||
|
2. [debug] Start Debugging - {debug_status}
|
||||||
|
3. [validate] Run Validation - {validation_status}
|
||||||
|
4. [status] View Details
|
||||||
|
5. [complete] Complete Loop
|
||||||
|
6. [exit] Exit (save and quit)
|
||||||
|
|
||||||
|
WAITING_INPUT: Please select an option
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quality Gates
|
||||||
|
|
||||||
|
Before completing any action, verify:
|
||||||
|
- [ ] State file read and validated
|
||||||
|
- [ ] Control signals checked (paused/stopped)
|
||||||
|
- [ ] Progress files updated
|
||||||
|
- [ ] State updates written
|
||||||
|
- [ ] Output format correct
|
||||||
|
- [ ] Next action determined
|
||||||
|
|
||||||
|
## Key Reminders
|
||||||
|
|
||||||
|
**NEVER:**
|
||||||
|
- Skip reading state file
|
||||||
|
- Ignore control signals (paused/stopped)
|
||||||
|
- Update API fields (only skill_state)
|
||||||
|
- Forget to output NEXT_ACTION_NEEDED
|
||||||
|
- Close agent prematurely (use send_input for multi-phase)
|
||||||
|
|
||||||
|
**ALWAYS:**
|
||||||
|
- Read state at start of every action
|
||||||
|
- Check control signals before execution
|
||||||
|
- Write progress to Markdown files
|
||||||
|
- Update state.json with skill_state changes
|
||||||
|
- Use structured output format
|
||||||
|
- Determine next action clearly
|
||||||
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 .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
|
||||||
|
|
||||||
|
```
|
||||||
|
.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 `.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
|
||||||
349
.codex/skills/ccw-loop/SKILL.md
Normal file
349
.codex/skills/ccw-loop/SKILL.md
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
---
|
||||||
|
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: .loop/{loopId}.json (MASTER) |
|
||||||
|
| Tasks: .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: .loop/{loopId}.json (unified state) |
|
||||||
|
| Writes: .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 `.loop/{loopId}.json` state file
|
||||||
|
2. **Control Signals**: Skill checks status field before each action (paused/stopped)
|
||||||
|
3. **File-Driven**: All progress documented in `.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)
|
||||||
|
|
||||||
|
```
|
||||||
|
.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 = `.loop/${loopId}.json`
|
||||||
|
const progressDir = `.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(`.loop/${loopId}.json`))
|
||||||
|
if (!state) {
|
||||||
|
console.error(`Loop not found: ${loopId}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state = createInitialState(loopId, taskDescription)
|
||||||
|
Write(`.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**: .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 .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 .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(`.loop/${loopId}.json`))
|
||||||
|
currentState.current_iteration = iteration
|
||||||
|
currentState.updated_at = getUtc8ISOString()
|
||||||
|
Write(`.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(`.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(`.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:
|
||||||
|
- .loop/{loopId}.json: Status set to completed
|
||||||
|
- .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(`.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:
|
||||||
|
- .loop/{loopId}.progress/debug.md: Understanding updated
|
||||||
|
- .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(`.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(`.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:
|
||||||
|
- .loop/{loopId}.json: Task status updated
|
||||||
|
- .loop/{loopId}.progress/develop.md: Progress entry added
|
||||||
|
- .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(`.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 = `.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(`.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:
|
||||||
|
- .loop/{loopId}.json: skill_state initialized
|
||||||
|
- .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(`.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(`.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(`.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(`.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:
|
||||||
|
- .loop/{loopId}.progress/validate.md: Validation report created
|
||||||
|
- .loop/{loopId}.progress/test-results.json: Test results saved
|
||||||
|
- .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 = `.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 = `.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 ".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 = `.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**: .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(`.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**: `.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(`.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 | `.loop/{loopId}.json` | Every state change (master) |
|
||||||
|
| `skill_state.develop` | `.loop/{loopId}.progress/develop.md` | After each dev operation |
|
||||||
|
| `skill_state.debug` | `.loop/{loopId}.progress/debug.md` | After each debug operation |
|
||||||
|
| `skill_state.validate` | `.loop/{loopId}.progress/validate.md` | After each validation |
|
||||||
|
| Code changes log | `.loop/{loopId}.progress/changes.log` | Each file modification (NDJSON) |
|
||||||
|
| Debug log | `.loop/{loopId}.progress/debug.log` | Each debug log (NDJSON) |
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
.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 = `.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 `.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
|
||||||
@@ -302,7 +302,7 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
|
|||||||
if (req.method === 'PUT') {
|
if (req.method === 'PUT') {
|
||||||
handlePostRequest(req, res, async (body: unknown) => {
|
handlePostRequest(req, res, async (body: unknown) => {
|
||||||
try {
|
try {
|
||||||
const updates = body as { enabled?: boolean; primaryModel?: string; secondaryModel?: string; tags?: string[] };
|
const updates = body as { enabled?: boolean; primaryModel?: string; secondaryModel?: string; tags?: string[]; envFile?: string | null };
|
||||||
const updated = updateToolConfig(initialPath, tool, updates);
|
const updated = updateToolConfig(initialPath, tool, updates);
|
||||||
|
|
||||||
// Broadcast config updated event
|
// Broadcast config updated event
|
||||||
|
|||||||
@@ -212,20 +212,22 @@ function getIssueDetail(issuesDir: string, issueId: string) {
|
|||||||
function enrichIssues(issues: any[], issuesDir: string) {
|
function enrichIssues(issues: any[], issuesDir: string) {
|
||||||
return issues.map(issue => {
|
return issues.map(issue => {
|
||||||
const solutions = readSolutionsJsonl(issuesDir, issue.id);
|
const solutions = readSolutionsJsonl(issuesDir, issue.id);
|
||||||
let taskCount = 0;
|
let tasks: any[] = [];
|
||||||
|
|
||||||
// Get task count from bound solution
|
// Get tasks from bound solution
|
||||||
if (issue.bound_solution_id) {
|
if (issue.bound_solution_id) {
|
||||||
const boundSol = solutions.find(s => s.id === issue.bound_solution_id);
|
const boundSol = solutions.find(s => s.id === issue.bound_solution_id);
|
||||||
if (boundSol?.tasks) {
|
if (boundSol?.tasks) {
|
||||||
taskCount = boundSol.tasks.length;
|
tasks = boundSol.tasks;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...issue,
|
...issue,
|
||||||
|
solutions, // Add full solutions array
|
||||||
|
tasks, // Add full tasks array
|
||||||
solution_count: solutions.length,
|
solution_count: solutions.length,
|
||||||
task_count: taskCount
|
task_count: tasks.length
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1000,10 +1000,28 @@ while (result.timed_out) {
|
|||||||
|
|
||||||
转换 Claude 多 Agent 命令到 Codex 时,确保:
|
转换 Claude 多 Agent 命令到 Codex 时,确保:
|
||||||
|
|
||||||
- [ ] **角色加载**:首条 message 包含 `~/.codex/agents/*.md` 内容
|
- [ ] **角色加载**:首条 message 包含 `~/.codex/agents/*.md` 路径
|
||||||
- [ ] **并行优化**:多个独立 agent 使用批量 `wait({ ids: [...] })`
|
- [ ] **并行优化**:多个独立 agent 使用批量 `wait({ ids: [...] })`
|
||||||
- [ ] **超时处理**:设置合理 `timeout_ms`,处理 `timed_out` 情况
|
- [ ] **超时处理**:设置合理 `timeout_ms`,处理 `timed_out` 情况
|
||||||
- [ ] **结果汇聚**:主 agent 负责合并 `status[id].completed`
|
- [ ] **结果汇聚**:主 agent 负责合并 `status[id].completed`
|
||||||
- [ ] **显式清理**:任务完成后调用 `close_agent`
|
- [ ] **显式清理**:任务完成后调用 `close_agent`
|
||||||
- [ ] **追问模式**:需要续做时使用 `send_input`,不要提前 close
|
- [ ] **追问模式**:需要续做时使用 `send_input`,不要提前 close
|
||||||
- [ ] **输出模板**:要求 subagent 使用统一结构化输出格式
|
- [ ] **输出模板**:要求 subagent 使用统一结构化输出格式
|
||||||
|
|
||||||
|
## 18. Skill/命令设计原则
|
||||||
|
|
||||||
|
### 18.1 内容独立性
|
||||||
|
|
||||||
|
**Codex skill/命令文档中不应包含与 Claude 的对比内容**:
|
||||||
|
|
||||||
|
- 对比表格属于转换准则(本文档),不属于最终产物
|
||||||
|
- skill 文档应专注于 Codex 机制本身的使用
|
||||||
|
- 避免引入无关上下文,保持文档简洁
|
||||||
|
|
||||||
|
### 18.2 模式选择灵活性
|
||||||
|
|
||||||
|
**不过度约束设计思路**:
|
||||||
|
|
||||||
|
- 文档提供的模式(单 agent / 多 agent / 混合)是可选方案
|
||||||
|
- 根据具体场景选择合适模式
|
||||||
|
- 主 agent 可作为协调器,灵活利用各种机制
|
||||||
|
|||||||
Reference in New Issue
Block a user