diff --git a/.codex/agents/ccw-loop-executor.md b/.codex/agents/ccw-loop-executor.md new file mode 100644 index 00000000..4b040a52 --- /dev/null +++ b/.codex/agents/ccw-loop-executor.md @@ -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 diff --git a/.codex/skills/ccw-loop/README.md b/.codex/skills/ccw-loop/README.md new file mode 100644 index 00000000..4af1f973 --- /dev/null +++ b/.codex/skills/ccw-loop/README.md @@ -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 diff --git a/.codex/skills/ccw-loop/SKILL.md b/.codex/skills/ccw-loop/SKILL.md new file mode 100644 index 00000000..b69fb653 --- /dev/null +++ b/.codex/skills/ccw-loop/SKILL.md @@ -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="" [--loop-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 diff --git a/.codex/skills/ccw-loop/phases/actions/action-complete.md b/.codex/skills/ccw-loop/phases/actions/action-complete.md new file mode 100644 index 00000000..df4f9a63 --- /dev/null +++ b/.codex/skills/ccw-loop/phases/actions/action-complete.md @@ -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) diff --git a/.codex/skills/ccw-loop/phases/actions/action-debug.md b/.codex/skills/ccw-loop/phases/actions/action-debug.md new file mode 100644 index 00000000..6102e76a --- /dev/null +++ b/.codex/skills/ccw-loop/phases/actions/action-debug.md @@ -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) diff --git a/.codex/skills/ccw-loop/phases/actions/action-develop.md b/.codex/skills/ccw-loop/phases/actions/action-develop.md new file mode 100644 index 00000000..8a7322ea --- /dev/null +++ b/.codex/skills/ccw-loop/phases/actions/action-develop.md @@ -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) diff --git a/.codex/skills/ccw-loop/phases/actions/action-init.md b/.codex/skills/ccw-loop/phases/actions/action-init.md new file mode 100644 index 00000000..49bb11dd --- /dev/null +++ b/.codex/skills/ccw-loop/phases/actions/action-init.md @@ -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 diff --git a/.codex/skills/ccw-loop/phases/actions/action-menu.md b/.codex/skills/ccw-loop/phases/actions/action-menu.md new file mode 100644 index 00000000..825791f1 --- /dev/null +++ b/.codex/skills/ccw-loop/phases/actions/action-menu.md @@ -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. diff --git a/.codex/skills/ccw-loop/phases/actions/action-validate.md b/.codex/skills/ccw-loop/phases/actions/action-validate.md new file mode 100644 index 00000000..fd34dd48 --- /dev/null +++ b/.codex/skills/ccw-loop/phases/actions/action-validate.md @@ -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` diff --git a/.codex/skills/ccw-loop/phases/orchestrator.md b/.codex/skills/ccw-loop/phases/orchestrator.md new file mode 100644 index 00000000..e357a9d3 --- /dev/null +++ b/.codex/skills/ccw-loop/phases/orchestrator.md @@ -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 diff --git a/.codex/skills/ccw-loop/phases/state-schema.md b/.codex/skills/ccw-loop/phases/state-schema.md new file mode 100644 index 00000000..e1576b5f --- /dev/null +++ b/.codex/skills/ccw-loop/phases/state-schema.md @@ -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 | 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 diff --git a/.codex/skills/ccw-loop/specs/action-catalog.md b/.codex/skills/ccw-loop/specs/action-catalog.md new file mode 100644 index 00000000..8d325dbe --- /dev/null +++ b/.codex/skills/ccw-loop/specs/action-catalog.md @@ -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 diff --git a/ccw/src/core/routes/cli-routes.ts b/ccw/src/core/routes/cli-routes.ts index d15db144..29a53a65 100644 --- a/ccw/src/core/routes/cli-routes.ts +++ b/ccw/src/core/routes/cli-routes.ts @@ -302,7 +302,7 @@ export async function handleCliRoutes(ctx: RouteContext): Promise { if (req.method === 'PUT') { handlePostRequest(req, res, async (body: unknown) => { 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); // Broadcast config updated event diff --git a/ccw/src/core/routes/issue-routes.ts b/ccw/src/core/routes/issue-routes.ts index 09c41bdd..912ec848 100644 --- a/ccw/src/core/routes/issue-routes.ts +++ b/ccw/src/core/routes/issue-routes.ts @@ -212,20 +212,22 @@ function getIssueDetail(issuesDir: string, issueId: string) { function enrichIssues(issues: any[], issuesDir: string) { return issues.map(issue => { 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) { const boundSol = solutions.find(s => s.id === issue.bound_solution_id); if (boundSol?.tasks) { - taskCount = boundSol.tasks.length; + tasks = boundSol.tasks; } } return { ...issue, + solutions, // Add full solutions array + tasks, // Add full tasks array solution_count: solutions.length, - task_count: taskCount + task_count: tasks.length }; }); } diff --git a/codex SUBAGENT 策略补充.md b/codex SUBAGENT 策略补充.md index 7acf6d30..0a3be2c5 100644 --- a/codex SUBAGENT 策略补充.md +++ b/codex SUBAGENT 策略补充.md @@ -1000,10 +1000,28 @@ while (result.timed_out) { 转换 Claude 多 Agent 命令到 Codex 时,确保: -- [ ] **角色加载**:首条 message 包含 `~/.codex/agents/*.md` 内容 +- [ ] **角色加载**:首条 message 包含 `~/.codex/agents/*.md` 路径 - [ ] **并行优化**:多个独立 agent 使用批量 `wait({ ids: [...] })` - [ ] **超时处理**:设置合理 `timeout_ms`,处理 `timed_out` 情况 - [ ] **结果汇聚**:主 agent 负责合并 `status[id].completed` - [ ] **显式清理**:任务完成后调用 `close_agent` - [ ] **追问模式**:需要续做时使用 `send_input`,不要提前 close - [ ] **输出模板**:要求 subagent 使用统一结构化输出格式 + +## 18. Skill/命令设计原则 + +### 18.1 内容独立性 + +**Codex skill/命令文档中不应包含与 Claude 的对比内容**: + +- 对比表格属于转换准则(本文档),不属于最终产物 +- skill 文档应专注于 Codex 机制本身的使用 +- 避免引入无关上下文,保持文档简洁 + +### 18.2 模式选择灵活性 + +**不过度约束设计思路**: + +- 文档提供的模式(单 agent / 多 agent / 混合)是可选方案 +- 根据具体场景选择合适模式 +- 主 agent 可作为协调器,灵活利用各种机制