diff --git a/.claude/skills/workflow-wave-plan/SKILL.md b/.claude/skills/workflow-wave-plan/SKILL.md index 09b32d75..d9a5e54c 100644 --- a/.claude/skills/workflow-wave-plan/SKILL.md +++ b/.claude/skills/workflow-wave-plan/SKILL.md @@ -86,13 +86,20 @@ Two context channels: |--------|------|--------|-------------| | `id` | string | Planner | T1, T2, ... | | `title` | string | Planner | Task title | -| `description` | string | Planner | Self-contained task description | +| `description` | string | Planner | Self-contained task description — what to implement | +| `test` | string | Planner | Test cases: what tests to write and how to verify (unit/integration/edge) | +| `acceptance_criteria` | string | Planner | Measurable conditions that define "done" | +| `scope` | string | Planner | Target file/directory glob — constrains agent write area, prevents cross-task file conflicts | +| `hints` | string | Planner | Implementation tips + reference files. Format: `tips text \|\| file1;file2`. Either part is optional | +| `execution_directives` | string | Planner | Execution constraints: commands to run for verification, tool restrictions | | `deps` | string | Planner | Dependency task IDs: T1;T2 | | `context_from` | string | Planner | Context source IDs: **E1;E2;T1** | | `wave` | integer | Wave Engine | Wave number (computed from deps) | | `status` | enum | Agent | pending / completed / failed / skipped | | `findings` | string | Agent | Execution findings (max 500 chars) | | `files_modified` | string | Agent | Files modified (semicolon-separated) | +| `tests_passed` | boolean | Agent | Whether all defined test cases passed (true/false) | +| `acceptance_met` | string | Agent | Summary of which acceptance criteria were met/unmet | | `error` | string | Agent | Error if failed | **context_from prefix convention**: `E*` → explore.csv lookup, `T*` → tasks.csv lookup. @@ -261,12 +268,19 @@ function buildExplorePrompt(row, requirement, sessionFolder) { **Requirement**: ${requirement} **Focus**: ${row.focus} +### MANDATORY FIRST STEPS +1. Read shared discoveries: ${sessionFolder}/discoveries.ndjson (if exists, skip if not) +2. Read project context: .workflow/project-tech.json (if exists) + +--- + ## Instructions Explore the codebase from the **${row.angle}** perspective: 1. Discover relevant files, modules, and patterns 2. Identify integration points and dependencies 3. Note constraints, risks, and conventions 4. Find existing patterns to follow +5. Share discoveries: append findings to ${sessionFolder}/discoveries.ndjson ## Output Write findings to: ${sessionFolder}/explore-results/${row.id}.json @@ -354,22 +368,27 @@ Decompose into execution tasks based on synthesized exploration: // 5. Prefer parallel (minimize deps) // 6. Use exploration findings: key_files → target files, patterns → references, // integration_points → dependency relationships, constraints → included in description +// 7. Each task MUST include: test (how to verify), acceptance_criteria (what defines done) +// 8. scope must not overlap between tasks in the same wave +// 9. hints = implementation tips + reference files (format: tips || file1;file2) +// 10. execution_directives = commands to run for verification, tool restrictions const tasks = [] // Claude decomposes requirement using exploration synthesis // Example: -// tasks.push({ id: 'T1', title: 'Setup types', description: '...', deps: '', context_from: 'E1;E2' }) -// tasks.push({ id: 'T2', title: 'Implement core', description: '...', deps: 'T1', context_from: 'E1;E2;T1' }) -// tasks.push({ id: 'T3', title: 'Add tests', description: '...', deps: 'T2', context_from: 'E3;T2' }) +// tasks.push({ id: 'T1', title: 'Setup types', description: '...', test: 'Verify types compile', acceptance_criteria: 'All interfaces exported', scope: 'src/types/**', hints: 'Follow existing type patterns || src/types/index.ts', execution_directives: 'tsc --noEmit', deps: '', context_from: 'E1;E2' }) +// tasks.push({ id: 'T2', title: 'Implement core', description: '...', test: 'Unit test: core logic', acceptance_criteria: 'All functions pass tests', scope: 'src/core/**', hints: 'Reuse BaseService || src/services/Base.ts', execution_directives: 'npm test -- --grep core', deps: 'T1', context_from: 'E1;E2;T1' }) +// tasks.push({ id: 'T3', title: 'Add tests', description: '...', test: 'Integration test suite', acceptance_criteria: '>80% coverage', scope: 'tests/**', hints: 'Follow existing test patterns || tests/auth.test.ts', execution_directives: 'npm test', deps: 'T2', context_from: 'E3;T2' }) // Compute waves const waves = computeWaves(tasks) tasks.forEach(t => { t.wave = waves[t.id] }) // Write tasks.csv -const header = 'id,title,description,deps,context_from,wave,status,findings,files_modified,error' +const header = 'id,title,description,test,acceptance_criteria,scope,hints,execution_directives,deps,context_from,wave,status,findings,files_modified,tests_passed,acceptance_met,error' const rows = tasks.map(t => - `"${t.id}","${escCSV(t.title)}","${escCSV(t.description)}","${t.deps}","${t.context_from}",${t.wave},"pending","","",""` + [t.id, escCSV(t.title), escCSV(t.description), escCSV(t.test), escCSV(t.acceptance_criteria), escCSV(t.scope), escCSV(t.hints), escCSV(t.execution_directives), t.deps, t.context_from, t.wave, 'pending', '', '', '', '', ''] + .map(v => `"${v}"`).join(',') ) Write(`${sessionFolder}/tasks.csv`, [header, ...rows].join('\n')) @@ -478,6 +497,8 @@ for (let wave = 1; wave <= maxWave; wave++) { row.files_modified = Array.isArray(result.files_modified) ? result.files_modified.join(';') : (result.files_modified || '') + row.tests_passed = String(result.tests_passed ?? '') + row.acceptance_met = result.acceptance_met || '' row.error = result.error || '' } else { row.status = 'completed' @@ -533,33 +554,69 @@ function buildExecutePrompt(row, requirement, sessionFolder) { **ID**: ${row.id} **Goal**: ${requirement} +**Scope**: ${row.scope || 'Not specified'} ## Description ${row.description} +### Implementation Hints & Reference Files +${row.hints || 'None'} + +> Format: \`tips text || file1;file2\`. Read ALL reference files (after ||) before starting. Apply tips (before ||) as guidance. + +### Execution Directives +${row.execution_directives || 'None'} + +> Commands to run for verification, tool restrictions, or environment requirements. + +### Test Cases +${row.test || 'None specified'} + +### Acceptance Criteria +${row.acceptance_criteria || 'None specified'} + ## Previous Context (from exploration and predecessor tasks) ${row._prev_context} -## Discovery Board -Read shared discoveries first: ${sessionFolder}/discoveries.ndjson (if exists) -After execution, append any discoveries: -echo '{"ts":"","worker":"${row.id}","type":"","data":{...}}' >> ${sessionFolder}/discoveries.ndjson +### MANDATORY FIRST STEPS +1. Read shared discoveries: ${sessionFolder}/discoveries.ndjson (if exists, skip if not) +2. Read project context: .workflow/project-tech.json (if exists) -## Instructions -1. Read the relevant files identified in the context above -2. Implement changes described in the task description -3. Ensure changes are consistent with exploration findings -4. Test changes if applicable +--- + +## Execution Protocol + +1. **Read references**: Parse hints — read all files listed after \`||\` to understand existing patterns +2. **Read discoveries**: Load ${sessionFolder}/discoveries.ndjson for shared exploration findings +3. **Use context**: Apply previous tasks' findings from prev_context above +4. **Stay in scope**: ONLY create/modify files within ${row.scope || 'project'} — do NOT touch files outside this boundary +5. **Apply hints**: Follow implementation tips from hints (before \`||\`) +6. **Implement**: Execute changes described in the task description +7. **Write tests**: Implement the test cases defined above +8. **Run directives**: Execute commands from execution_directives to verify your work +9. **Verify acceptance**: Ensure all acceptance criteria are met before reporting completion +10. **Share discoveries**: Append exploration findings to shared board: + \\\`\\\`\\\`bash + echo '{"ts":"","worker":"${row.id}","type":"","data":{...}}' >> ${sessionFolder}/discoveries.ndjson + \\\`\\\`\\\` +11. **Report result**: Write JSON to output file ## Output Write results to: ${sessionFolder}/task-results/${row.id}.json { - "status": "completed", + "status": "completed" | "failed", "findings": "What was done (max 500 chars)", "files_modified": ["file1.ts", "file2.ts"], + "tests_passed": true | false, + "acceptance_met": "Summary of which acceptance criteria were met/unmet", "error": "" -}` +} + +**IMPORTANT**: Set status to "completed" ONLY if: +- All test cases pass +- All acceptance criteria are met +Otherwise set status to "failed" with details in error field.` } ``` @@ -610,11 +667,30 @@ Key files: ${e.key_files || 'none'}`).join('\n\n')} ## Task Results ${finalTasks.map(t => `### ${t.id}: ${t.title} (${t.status}) -- Context from: ${t.context_from || 'none'} -- Wave: ${t.wave} -- Findings: ${t.findings || 'N/A'} -- Files: ${t.files_modified || 'none'} -${t.error ? `- Error: ${t.error}` : ''}`).join('\n\n')} + +| Field | Value | +|-------|-------| +| Wave | ${t.wave} | +| Scope | ${t.scope || 'none'} | +| Dependencies | ${t.deps || 'none'} | +| Context From | ${t.context_from || 'none'} | +| Tests Passed | ${t.tests_passed || 'N/A'} | +| Acceptance Met | ${t.acceptance_met || 'N/A'} | +| Error | ${t.error || 'none'} | + +**Description**: ${t.description} + +**Test Cases**: ${t.test || 'N/A'} + +**Acceptance Criteria**: ${t.acceptance_criteria || 'N/A'} + +**Hints**: ${t.hints || 'N/A'} + +**Execution Directives**: ${t.execution_directives || 'N/A'} + +**Findings**: ${t.findings || 'N/A'} + +**Files Modified**: ${t.files_modified || 'none'}`).join('\n\n---\n\n')} ## All Modified Files @@ -739,13 +815,34 @@ function truncate(s, max) { Shared `discoveries.ndjson` — append-only NDJSON accessible to all agents across all phases. +**Lifecycle**: +- Created by the first agent to write a discovery +- Carries over across all phases and waves — never cleared +- Agents append via `echo '...' >> discoveries.ndjson` + +**Format**: NDJSON, each line is a self-contained JSON: + ```jsonl {"ts":"...","worker":"E1","type":"code_pattern","data":{"name":"repo-pattern","file":"src/repos/Base.ts"}} {"ts":"...","worker":"T2","type":"integration_point","data":{"file":"src/auth/index.ts","exports":["auth"]}} ``` -**Types**: `code_pattern`, `integration_point`, `convention`, `blocker`, `tech_stack` -**Rules**: Read first → write immediately → deduplicate → append-only +**Discovery Types**: + +| type | Dedup Key | Description | +|------|-----------|-------------| +| `code_pattern` | `data.name` | Reusable code pattern found | +| `integration_point` | `data.file` | Module connection point | +| `convention` | singleton | Code style conventions | +| `blocker` | `data.issue` | Blocking issue encountered | +| `tech_stack` | singleton | Project technology stack | +| `test_command` | singleton | Test commands discovered | + +**Protocol Rules**: +1. Read board before own exploration → skip covered areas +2. Write discoveries immediately via `echo >>` → don't batch +3. Deduplicate — check existing entries; skip if same type + dedup key exists +4. Append-only — never modify or delete existing lines --- @@ -754,11 +851,12 @@ Shared `discoveries.ndjson` — append-only NDJSON accessible to all agents acro | Error | Resolution | |-------|------------| | Explore agent failure | Mark as failed in explore.csv, exclude from planning | +| All explores failed | Fallback: plan directly from requirement without exploration | | Execute agent failure | Mark as failed, skip dependents (cascade) | +| Agent timeout | Mark as failed in results, continue with wave | | Circular dependency | Abort wave computation, report cycle | -| All explores failed | Fallback: plan directly from requirement | -| CSV parse error | Re-validate format | -| discoveries.ndjson corrupt | Ignore malformed lines | +| CSV parse error | Validate CSV format before execution, show line number | +| discoveries.ndjson corrupt | Ignore malformed lines, continue with valid entries | --- @@ -772,3 +870,27 @@ Shared `discoveries.ndjson` — append-only NDJSON accessible to all agents acro 6. **Discovery Board Append-Only**: Never clear or modify discoveries.ndjson 7. **Explore Before Execute**: Phase 2 completes before Phase 4 starts 8. **DO NOT STOP**: Continuous execution until all waves complete or remaining skipped + +--- + +## Best Practices + +1. **Exploration Angles**: 1 for simple, 3-4 for complex; avoid redundant angles +2. **Context Linking**: Link every task to at least one explore row (E*) — exploration was done for a reason +3. **Task Granularity**: 3-10 tasks optimal; too many = overhead, too few = no parallelism +4. **Minimize Cross-Wave Deps**: More tasks in wave 1 = more parallelism +5. **Specific Descriptions**: Agent sees only its CSV row + prev_context — make description self-contained +6. **Non-Overlapping Scopes**: Same-wave tasks must not write to the same files +7. **Context From ≠ Deps**: `deps` = execution order constraint; `context_from` = information flow + +--- + +## Usage Recommendations + +| Scenario | Recommended Approach | +|----------|---------------------| +| Complex feature (unclear architecture) | `workflow:wave-plan` — explore first, then plan | +| Simple known-pattern task | `$csv-wave-pipeline` — skip exploration, direct execution | +| Independent parallel tasks | `$csv-wave-pipeline -c 8` — single wave, max parallelism | +| Diamond dependency (A→B,C→D) | `workflow:wave-plan` — 3 waves with context propagation | +| Unknown codebase | `workflow:wave-plan` — exploration phase is essential | diff --git a/.codex/skills/csv-wave-pipeline/SKILL.md b/.codex/skills/csv-wave-pipeline/SKILL.md index aedb4844..668a11c3 100644 --- a/.codex/skills/csv-wave-pipeline/SKILL.md +++ b/.codex/skills/csv-wave-pipeline/SKILL.md @@ -73,11 +73,11 @@ Wave-based batch execution using `spawn_agents_on_csv` with **cross-wave context ### tasks.csv (Master State) ```csv -id,title,description,deps,context_from,wave,status,findings,files_modified,error -1,Setup auth module,Create auth directory structure and base files,,,1,,,, -2,Implement OAuth,Add OAuth provider integration with Google and GitHub,1,1,2,,,, -3,Add JWT tokens,Implement JWT generation and validation,1,1,2,,,, -4,Setup 2FA,Add TOTP-based 2FA with QR code generation,2;3,1;2;3,3,,,, +id,title,description,test,acceptance_criteria,scope,hints,execution_directives,deps,context_from,wave,status,findings,files_modified,tests_passed,acceptance_met,error +"1","Setup auth module","Create auth directory structure and base files","Verify directory exists and base files export expected interfaces","auth/ dir created; index.ts and types.ts export AuthProvider interface","src/auth/**","Follow monorepo module pattern || package.json;src/shared/types.ts","","","","1","","","","","","" +"2","Implement OAuth","Add OAuth provider integration with Google and GitHub","Unit test: mock OAuth callback returns valid token; Integration test: verify redirect URL generation","OAuth login redirects to provider; callback returns JWT; supports Google and GitHub","src/auth/oauth/**","Use passport.js strategy pattern || src/auth/index.ts;docs/oauth-flow.md","Run npm test -- --grep oauth before completion","1","1","2","","","","","","" +"3","Add JWT tokens","Implement JWT generation and validation","Unit test: sign/verify round-trip; Edge test: expired token returns 401","generateToken() returns valid JWT; verifyToken() rejects expired/tampered tokens","src/auth/jwt/**","Use jsonwebtoken library; Set default expiry 1h || src/config/auth.ts","Ensure tsc --noEmit passes","1","1","2","","","","","","" +"4","Setup 2FA","Add TOTP-based 2FA with QR code generation","Unit test: TOTP verify with correct code; Test: QR data URL is valid","QR code generates scannable image; TOTP verification succeeds within time window","src/auth/2fa/**","Use speakeasy + qrcode libraries || src/auth/oauth/strategy.ts;src/auth/jwt/token.ts","Run full test suite: npm test","2;3","1;2;3","3","","","","","","" ``` **Columns**: @@ -86,13 +86,20 @@ id,title,description,deps,context_from,wave,status,findings,files_modified,error |--------|-------|-------------| | `id` | Input | Unique task identifier (string) | | `title` | Input | Short task title | -| `description` | Input | Detailed task description | +| `description` | Input | Detailed task description — what to implement | +| `test` | Input | Test cases: what tests to write and how to verify (unit/integration/edge) | +| `acceptance_criteria` | Input | Acceptance criteria: measurable conditions that define "done" | +| `scope` | Input | Target file/directory glob — constrains agent work area, prevents cross-task file conflicts | +| `hints` | Input | Implementation tips + reference files. Format: `tips text \|\| file1;file2`. Before `\|\|` = how to implement; after `\|\|` = existing files to read before starting. Either part is optional | +| `execution_directives` | Input | Execution constraints: commands to run for verification, tool restrictions, environment requirements | | `deps` | Input | Semicolon-separated dependency task IDs (empty = no deps) | | `context_from` | Input | Semicolon-separated task IDs whose findings this task needs | | `wave` | Computed | Wave number (computed by topological sort, 1-based) | | `status` | Output | `pending` → `completed` / `failed` / `skipped` | | `findings` | Output | Key discoveries or implementation notes (max 500 chars) | | `files_modified` | Output | Semicolon-separated file paths | +| `tests_passed` | Output | Whether all defined test cases passed (true/false) | +| `acceptance_met` | Output | Summary of which acceptance criteria were met/unmet | | `error` | Output | Error message if failed (empty if success) | ### Per-Wave CSV (Temporary) @@ -100,9 +107,9 @@ id,title,description,deps,context_from,wave,status,findings,files_modified,error Each wave generates a temporary `wave-{N}.csv` with an extra `prev_context` column: ```csv -id,title,description,deps,context_from,wave,prev_context -2,Implement OAuth,Add OAuth integration,1,1,2,"[Task 1] Created auth/ with index.ts and types.ts" -3,Add JWT tokens,Implement JWT,1,1,2,"[Task 1] Created auth/ with index.ts and types.ts" +id,title,description,test,acceptance_criteria,scope,hints,execution_directives,deps,context_from,wave,prev_context +"2","Implement OAuth","Add OAuth integration","Unit test: mock OAuth callback returns valid token","OAuth login redirects to provider; callback returns JWT","src/auth/oauth/**","Use passport.js strategy pattern || src/auth/index.ts;docs/oauth-flow.md","Run npm test -- --grep oauth","1","1","2","[Task 1] Created auth/ with index.ts and types.ts" +"3","Add JWT tokens","Implement JWT","Unit test: sign/verify round-trip; Edge test: expired token returns 401","generateToken() returns valid JWT; verifyToken() rejects expired/tampered tokens","src/auth/jwt/**","Use jsonwebtoken library; Set default expiry 1h || src/config/auth.ts","Ensure tsc --noEmit passes","1","1","2","[Task 1] Created auth/ with index.ts and types.ts" ``` The `prev_context` column is built from `context_from` by looking up completed tasks' `findings` in the master CSV. @@ -187,16 +194,26 @@ Bash(`mkdir -p ${sessionFolder}`) ```javascript // Use ccw cli to decompose requirement into subtasks Bash({ - command: `ccw cli -p "PURPOSE: Decompose requirement into 3-10 atomic tasks for batch agent execution. + command: `ccw cli -p "PURPOSE: Decompose requirement into 3-10 atomic tasks for batch agent execution. Each task must include implementation description, test cases, and acceptance criteria. TASK: • Parse requirement into independent subtasks • Identify dependencies between tasks (which must complete before others) • Identify context flow (which tasks need previous tasks' findings) + • For each task, define concrete test cases (unit/integration/edge) + • For each task, define measurable acceptance criteria (what defines 'done') • Each task must be executable by a single agent with file read/write access MODE: analysis CONTEXT: @**/* -EXPECTED: JSON object with tasks array. Each task: {id: string, title: string, description: string, deps: string[], context_from: string[]}. deps = task IDs that must complete first. context_from = task IDs whose findings are needed. -CONSTRAINTS: 3-10 tasks | Each task is atomic | No circular deps | description must be specific enough for an agent to execute independently +EXPECTED: JSON object with tasks array. Each task: {id: string, title: string, description: string, test: string, acceptance_criteria: string, scope: string, hints: string, execution_directives: string, deps: string[], context_from: string[]}. + - description: what to implement (specific enough for an agent to execute independently) + - test: what tests to write and how to verify (e.g. 'Unit test: X returns Y; Edge test: handles Z') + - acceptance_criteria: measurable conditions that define done (e.g. 'API returns 200; token expires after 1h') + - scope: target file/directory glob (e.g. 'src/auth/**') — tasks in same wave MUST have non-overlapping scopes + - hints: implementation tips + reference files, format ' || ;' (e.g. 'Use strategy pattern || src/base/Strategy.ts;docs/design.md') + - execution_directives: commands to run for verification or tool constraints (e.g. 'Run npm test --bail; Ensure tsc passes') + - deps: task IDs that must complete first + - context_from: task IDs whose findings are needed +CONSTRAINTS: 3-10 tasks | Each task is atomic | No circular deps | test and acceptance_criteria must be concrete and verifiable | Same-wave tasks must have non-overlapping scopes REQUIREMENT: ${requirement}" --tool gemini --mode analysis --rule planning-breakdown-task-steps`, run_in_background: true @@ -266,19 +283,26 @@ REQUIREMENT: ${requirement}" --tool gemini --mode analysis --rule planning-break 3. **Generate tasks.csv** ```javascript - const header = 'id,title,description,deps,context_from,wave,status,findings,files_modified,error' + const header = 'id,title,description,test,acceptance_criteria,scope,hints,execution_directives,deps,context_from,wave,status,findings,files_modified,tests_passed,acceptance_met,error' const rows = decomposedTasks.map(task => { const wave = waveAssignment.get(task.id) return [ task.id, csvEscape(task.title), csvEscape(task.description), + csvEscape(task.test), + csvEscape(task.acceptance_criteria), + csvEscape(task.scope), + csvEscape(task.hints), + csvEscape(task.execution_directives), task.deps.join(';'), task.context_from.join(';'), wave, 'pending', // status '', // findings '', // files_modified + '', // tests_passed + '', // acceptance_met '' // error ].map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(',') }) @@ -387,9 +411,9 @@ REQUIREMENT: ${requirement}" --tool gemini --mode analysis --rule planning-break } // 5. Write wave CSV - const waveHeader = 'id,title,description,deps,context_from,wave,prev_context' + const waveHeader = 'id,title,description,test,acceptance_criteria,scope,hints,execution_directives,deps,context_from,wave,prev_context' const waveRows = executableTasks.map(t => - [t.id, t.title, t.description, t.deps, t.context_from, t.wave, t.prev_context] + [t.id, t.title, t.description, t.test, t.acceptance_criteria, t.scope, t.hints, t.execution_directives, t.deps, t.context_from, t.wave, t.prev_context] .map(cell => `"${String(cell).replace(/"/g, '""')}"`) .join(',') ) @@ -412,9 +436,11 @@ REQUIREMENT: ${requirement}" --tool gemini --mode analysis --rule planning-break status: { type: "string", enum: ["completed", "failed"] }, findings: { type: "string" }, files_modified: { type: "array", items: { type: "string" } }, + tests_passed: { type: "boolean" }, + acceptance_met: { type: "string" }, error: { type: "string" } }, - required: ["id", "status", "findings"] + required: ["id", "status", "findings", "tests_passed"] } }) // ↑ Blocks until all agents in this wave complete @@ -426,6 +452,8 @@ REQUIREMENT: ${requirement}" --tool gemini --mode analysis --rule planning-break status: result.status, findings: result.findings || '', files_modified: (result.files_modified || []).join(';'), + tests_passed: String(result.tests_passed ?? ''), + acceptance_met: result.acceptance_met || '', error: result.error || '' }) @@ -462,6 +490,23 @@ REQUIREMENT: ${requirement}" --tool gemini --mode analysis --rule planning-break **Task ID**: {id} **Title**: {title} **Description**: {description} +**Scope**: {scope} + +### Implementation Hints & Reference Files +{hints} + +> Format: \` || ;\`. Read ALL reference files (after ||) before starting implementation. Apply tips (before ||) as implementation guidance. + +### Execution Directives +{execution_directives} + +> Commands to run for verification, tool restrictions, or environment requirements. Follow these constraints during and after implementation. + +### Test Cases +{test} + +### Acceptance Criteria +{acceptance_criteria} ### Previous Tasks' Findings (Context) {prev_context} @@ -470,14 +515,20 @@ REQUIREMENT: ${requirement}" --tool gemini --mode analysis --rule planning-break ## Execution Protocol -1. **Read discoveries**: Load ${sessionFolder}/discoveries.ndjson for shared exploration findings -2. **Use context**: Apply previous tasks' findings from prev_context above -3. **Execute**: Implement the task as described -4. **Share discoveries**: Append exploration findings to shared board: +1. **Read references**: Parse {hints} — read all files listed after \`||\` to understand existing patterns +2. **Read discoveries**: Load ${sessionFolder}/discoveries.ndjson for shared exploration findings +3. **Use context**: Apply previous tasks' findings from prev_context above +4. **Stay in scope**: ONLY create/modify files within {scope} — do NOT touch files outside this boundary +5. **Apply hints**: Follow implementation tips from {hints} (before \`||\`) +6. **Execute**: Implement the task as described +7. **Write tests**: Implement the test cases defined above +8. **Run directives**: Execute commands from {execution_directives} to verify your work +9. **Verify acceptance**: Ensure all acceptance criteria are met before reporting completion +10. **Share discoveries**: Append exploration findings to shared board: \`\`\`bash echo '{"ts":"","worker":"{id}","type":"","data":{...}}' >> ${sessionFolder}/discoveries.ndjson \`\`\` -5. **Report result**: Return JSON via report_agent_job_result +11. **Report result**: Return JSON via report_agent_job_result ### Discovery Types to Share - \`code_pattern\`: {name, file, description} — reusable patterns found @@ -495,8 +546,15 @@ Return JSON: "status": "completed" | "failed", "findings": "Key discoveries and implementation notes (max 500 chars)", "files_modified": ["path1", "path2"], + "tests_passed": true | false, + "acceptance_met": "Summary of which acceptance criteria were met/unmet", "error": "" } + +**IMPORTANT**: Set status to "completed" ONLY if: +- All test cases pass +- All acceptance criteria are met +Otherwise set status to "failed" with details in error field. ` } ``` @@ -576,6 +634,7 @@ Return JSON: | Completed | ${completed.length} | | Failed | ${failed.length} | | Skipped | ${skipped.length} | +| Waves | ${maxWave} | --- @@ -584,7 +643,7 @@ Return JSON: ${Array.from({ length: maxWave }, (_, i) => i + 1).map(w => { const waveTasks = tasks.filter(t => parseInt(t.wave) === w) return `### Wave ${w} -${waveTasks.map(t => `- **[${t.id}] ${t.title}**: ${t.status}${t.error ? ' — ' + t.error : ''} +${waveTasks.map(t => `- **[${t.id}] ${t.title}**: ${t.status}${t.tests_passed ? ' ✓tests' : ''}${t.error ? ' — ' + t.error : ''} ${t.findings ? 'Findings: ' + t.findings : ''}`).join('\n')}` }).join('\n\n')} @@ -598,10 +657,23 @@ ${tasks.map(t => `### ${t.id}: ${t.title} |-------|-------| | Status | ${t.status} | | Wave | ${t.wave} | +| Scope | ${t.scope || 'none'} | | Dependencies | ${t.deps || 'none'} | | Context From | ${t.context_from || 'none'} | +| Tests Passed | ${t.tests_passed || 'N/A'} | +| Acceptance Met | ${t.acceptance_met || 'N/A'} | | Error | ${t.error || 'none'} | +**Description**: ${t.description} + +**Test Cases**: ${t.test || 'N/A'} + +**Acceptance Criteria**: ${t.acceptance_criteria || 'N/A'} + +**Hints**: ${t.hints || 'N/A'} + +**Execution Directives**: ${t.execution_directives || 'N/A'} + **Findings**: ${t.findings || 'N/A'} **Files Modified**: ${t.files_modified || 'none'} diff --git a/.codex/skills/wave-plan-pipeline/SKILL.md b/.codex/skills/wave-plan-pipeline/SKILL.md new file mode 100644 index 00000000..61f14385 --- /dev/null +++ b/.codex/skills/wave-plan-pipeline/SKILL.md @@ -0,0 +1,1141 @@ +--- +name: wave-plan-pipeline +description: Explore-first wave pipeline. Decomposes requirement into exploration angles, runs wave exploration via spawn_agents_on_csv, synthesizes findings into execution tasks with cross-phase context linking (E*→T*), then wave-executes via spawn_agents_on_csv. +argument-hint: "[-y|--yes] [-c|--concurrency N] [--continue] \"requirement description\"" +allowed-tools: spawn_agents_on_csv, Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion +--- + +## Auto Mode + +When `--yes` or `-y`: Auto-confirm decomposition, skip interactive validation, use defaults. + +# Wave Plan Pipeline + +## Usage + +```bash +$wave-plan-pipeline "Implement user authentication with OAuth, JWT, and 2FA" +$wave-plan-pipeline -c 4 "Refactor payment module with Stripe and PayPal" +$wave-plan-pipeline -y "Build notification system with email and SMS" +$wave-plan-pipeline --continue "auth-20260228" +``` + +**Flags**: +- `-y, --yes`: Skip all confirmations (auto mode) +- `-c, --concurrency N`: Max concurrent agents within each wave (default: 4) +- `--continue`: Resume existing session + +**Output Directory**: `.workflow/.wave-plan/{session-id}/` + +--- + +## Overview + +Explore-first wave-based pipeline using `spawn_agents_on_csv`. Two-stage CSV execution: **explore.csv** (codebase discovery) → **tasks.csv** (implementation), with cross-phase context propagation via `context_from` linking (`E*` → `T*`). + +**Core workflow**: Decompose → Wave Explore → Synthesize & Plan → Wave Execute → Aggregate + +``` +┌──────────────────────────────────────────────────────────────────────┐ +│ WAVE PLAN PIPELINE │ +├──────────────────────────────────────────────────────────────────────┤ +│ │ +│ Phase 1: Requirement → explore.csv │ +│ ├─ Analyze complexity → select exploration angles (1-4) │ +│ ├─ Generate explore.csv (1 row per angle) │ +│ └─ User validates (skip if -y) │ +│ │ +│ Phase 2: Wave Explore (spawn_agents_on_csv) │ +│ ├─ For each explore wave: │ +│ │ ├─ Build wave CSV from explore.csv │ +│ │ ├─ spawn_agents_on_csv(explore instruction template) │ +│ │ └─ Merge findings/key_files into explore.csv │ +│ └─ discoveries.ndjson shared across agents │ +│ │ +│ Phase 3: Synthesize & Plan → tasks.csv │ +│ ├─ Read all explore findings → cross-reference │ +│ ├─ Resolve conflicts between angles │ +│ ├─ Decompose into execution tasks with context_from: E*;T* │ +│ ├─ Compute dependency waves (topological sort) │ +│ └─ User validates (skip if -y) │ +│ │ +│ Phase 4: Wave Execute (spawn_agents_on_csv) │ +│ ├─ For each task wave: │ +│ │ ├─ Build prev_context from explore.csv + tasks.csv │ +│ │ ├─ Build wave CSV with prev_context column │ +│ │ ├─ spawn_agents_on_csv(execute instruction template) │ +│ │ └─ Merge results into tasks.csv │ +│ └─ discoveries.ndjson carries across all waves │ +│ │ +│ Phase 5: Aggregate │ +│ ├─ Export results.csv │ +│ ├─ Generate context.md with all findings │ +│ └─ Display summary │ +│ │ +└──────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Context Flow + +``` +explore.csv tasks.csv +┌──────────┐ ┌──────────┐ +│ E1: arch │──────────→│ T1: setup│ context_from: E1;E2 +│ findings │ │ prev_ctx │← E1+E2 findings +├──────────┤ ├──────────┤ +│ E2: deps │──────────→│ T2: impl │ context_from: E1;T1 +│ findings │ │ prev_ctx │← E1+T1 findings +├──────────┤ ├──────────┤ +│ E3: test │──┐ ┌───→│ T3: test │ context_from: E3;T2 +│ findings │ └───┘ │ prev_ctx │← E3+T2 findings +└──────────┘ └──────────┘ + +Two context channels: +1. Directed: context_from → prev_context (CSV findings lookup) +2. Broadcast: discoveries.ndjson (append-only shared board) + +context_from prefix: E* → explore.csv lookup, T* → tasks.csv lookup +``` + +--- + +## CSV Schemas + +### explore.csv + +```csv +id,angle,description,focus,deps,wave,status,findings,key_files,error +"E1","architecture","Explore codebase architecture for: auth system","architecture","","1","pending","","","" +"E2","dependencies","Explore dependency landscape for: auth system","dependencies","","1","pending","","","" +"E3","testing","Explore test infrastructure for: auth system","testing","","1","pending","","","" +``` + +**Columns**: + +| Column | Phase | Description | +|--------|-------|-------------| +| `id` | Input | Exploration ID: E1, E2, ... | +| `angle` | Input | Exploration angle name | +| `description` | Input | What to explore from this angle | +| `focus` | Input | Keywords and focus areas | +| `deps` | Input | Semicolon-separated dep IDs (usually empty — all wave 1) | +| `wave` | Computed | Wave number (usually 1 for all explorations) | +| `status` | Output | `pending` → `completed` / `failed` | +| `findings` | Output | Discoveries (max 800 chars) | +| `key_files` | Output | Relevant files (semicolon-separated) | +| `error` | Output | Error message if failed | + +### tasks.csv + +```csv +id,title,description,test,acceptance_criteria,scope,hints,execution_directives,deps,context_from,wave,status,findings,files_modified,tests_passed,acceptance_met,error +"T1","Setup types","Create type definitions","Verify types compile with tsc","All interfaces exported","src/types/**","Follow existing patterns || src/types/index.ts","tsc --noEmit","","E1;E2","1","pending","","","","","" +"T2","Implement core","Implement core auth logic","Unit test: login returns token","Login flow works end-to-end","src/auth/**","Reuse BaseService || src/services/Base.ts","npm test -- --grep auth","T1","E1;E2;T1","2","pending","","","","","" +``` + +**Columns**: + +| Column | Phase | Description | +|--------|-------|-------------| +| `id` | Input | Task ID: T1, T2, ... | +| `title` | Input | Short task title | +| `description` | Input | Self-contained task description — what to implement | +| `test` | Input | Test cases: what tests to write and how to verify (unit/integration/edge) | +| `acceptance_criteria` | Input | Measurable conditions that define "done" | +| `scope` | Input | Target file/directory glob — constrains agent write area, prevents cross-task file conflicts | +| `hints` | Input | Implementation tips + reference files. Format: `tips text \|\| file1;file2`. Either part is optional | +| `execution_directives` | Input | Execution constraints: commands to run for verification, tool restrictions | +| `deps` | Input | Dependency task IDs: T1;T2 (semicolon-separated) | +| `context_from` | Input | Context source IDs: **E1;E2;T1** — `E*` lookups in explore.csv, `T*` in tasks.csv | +| `wave` | Computed | Wave number (computed by topological sort, 1-based) | +| `status` | Output | `pending` → `completed` / `failed` / `skipped` | +| `findings` | Output | Execution findings (max 500 chars) | +| `files_modified` | Output | Semicolon-separated file paths | +| `tests_passed` | Output | Whether all defined test cases passed (true/false) | +| `acceptance_met` | Output | Summary of which acceptance criteria were met/unmet | +| `error` | Output | Error message if failed (empty if success) | + +### Per-Wave CSV (Temporary) + +Each wave generates a temporary CSV with an extra `prev_context` column. + +**Explore wave**: `explore-wave-{N}.csv` — same columns as explore.csv (no prev_context, explorations are independent). + +**Execute wave**: `task-wave-{N}.csv` — all task columns + `prev_context`: + +```csv +id,title,description,test,acceptance_criteria,scope,hints,execution_directives,deps,context_from,wave,prev_context +"T2","Implement core","Implement core auth logic","Unit test: login returns token","Login flow works end-to-end","src/auth/**","Reuse BaseService || src/services/Base.ts","npm test -- --grep auth","T1","E1;E2;T1","2","[Explore architecture] Found BaseService pattern in src/services/\n[Task T1] Created types at src/types/auth.ts" +``` + +The `prev_context` column is built from `context_from` by looking up completed rows' `findings` in both explore.csv (`E*`) and tasks.csv (`T*`). + +--- + +## Output Artifacts + +| File | Purpose | Lifecycle | +|------|---------|-----------| +| `explore.csv` | Exploration state — angles with findings/key_files | Updated after Phase 2 | +| `tasks.csv` | Execution state — tasks with results | Updated after each wave in Phase 4 | +| `explore-wave-{N}.csv` | Per-wave explore input (temporary) | Created before wave, deleted after | +| `task-wave-{N}.csv` | Per-wave execute input (temporary) | Created before wave, deleted after | +| `results.csv` | Final results export | Created in Phase 5 | +| `discoveries.ndjson` | Shared discovery board (all agents, all phases) | Append-only | +| `context.md` | Human-readable execution report | Created in Phase 5 | + +--- + +## Session Structure + +``` +.workflow/.wave-plan/{session-id}/ +├── explore.csv # Exploration state +├── tasks.csv # Execution state +├── results.csv # Final results export +├── discoveries.ndjson # Shared discovery board +├── context.md # Full context summary +├── explore-wave-{N}.csv # Temporary per-wave explore input (cleaned up) +└── task-wave-{N}.csv # Temporary per-wave execute input (cleaned up) +``` + +--- + +## Implementation + +### Session Initialization + +```javascript +const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString() + +// Parse flags +const AUTO_YES = $ARGUMENTS.includes('--yes') || $ARGUMENTS.includes('-y') +const continueMode = $ARGUMENTS.includes('--continue') +const concurrencyMatch = $ARGUMENTS.match(/(?:--concurrency|-c)\s+(\d+)/) +const maxConcurrency = concurrencyMatch ? parseInt(concurrencyMatch[1]) : 4 + +const requirement = $ARGUMENTS + .replace(/--yes|-y|--continue|--concurrency\s+\d+|-c\s+\d+/g, '') + .trim() + +const slug = requirement.toLowerCase() + .replace(/[^a-z0-9\u4e00-\u9fa5]+/g, '-') + .substring(0, 40) +const dateStr = getUtc8ISOString().substring(0, 10).replace(/-/g, '') +const sessionId = `wpp-${slug}-${dateStr}` +const sessionFolder = `.workflow/.wave-plan/${sessionId}` + +// Continue mode: find existing session +if (continueMode) { + const existing = Bash(`ls -t .workflow/.wave-plan/ 2>/dev/null | head -1`).trim() + if (existing) { + sessionId = existing + sessionFolder = `.workflow/.wave-plan/${sessionId}` + // Check which phase to resume: if tasks.csv exists → Phase 4, else → Phase 2 + } +} + +Bash(`mkdir -p ${sessionFolder}`) +``` + +--- + +### Phase 1: Requirement → explore.csv + +**Objective**: Analyze requirement complexity, select exploration angles, generate explore.csv. + +**Steps**: + +1. **Analyze & Decompose** + + ```javascript + Bash({ + command: `ccw cli -p "PURPOSE: Analyze requirement complexity and select 1-4 exploration angles for codebase discovery before implementation. +TASK: + • Classify requirement type (feature/bugfix/refactor/security/performance) + • Assess complexity (Low: 1 angle, Medium: 2-3, High: 3-4) + • Select exploration angles from: architecture, dependencies, integration-points, testing, patterns, security, performance, state-management, error-handling, edge-cases + • For each angle, define focus keywords and what to discover +MODE: analysis +CONTEXT: @**/* +EXPECTED: JSON object: {type: string, complexity: string, angles: [{id: string, angle: string, description: string, focus: string}]}. Each angle id = E1, E2, etc. +CONSTRAINTS: 1-4 angles | Angles must be distinct | Each angle must have clear focus + +REQUIREMENT: ${requirement}" --tool gemini --mode analysis --rule planning-breakdown-task-steps`, + run_in_background: true + }) + // Wait for CLI completion via hook callback + // Parse JSON from CLI output → { type, complexity, angles[] } + ``` + +2. **Generate explore.csv** + + ```javascript + const header = 'id,angle,description,focus,deps,wave,status,findings,key_files,error' + const rows = angles.map(a => + [a.id, a.angle, a.description, a.focus, '', '1', 'pending', '', '', ''] + .map(v => `"${String(v).replace(/"/g, '""')}"`) + .join(',') + ) + + Write(`${sessionFolder}/explore.csv`, [header, ...rows].join('\n')) + ``` + +3. **User Validation** (skip if AUTO_YES) + + ```javascript + if (!AUTO_YES) { + console.log(`\n## Exploration Plan (${angles.length} angles)\n`) + angles.forEach(a => console.log(` - [${a.id}] ${a.angle}: ${a.focus}`)) + + const answer = AskUserQuestion({ + questions: [{ + question: "Approve exploration angles?", + header: "Validation", + multiSelect: false, + options: [ + { label: "Approve", description: "Proceed with wave exploration" }, + { label: "Modify", description: `Edit ${sessionFolder}/explore.csv manually, then --continue` }, + { label: "Cancel", description: "Abort" } + ] + }] + }) + + if (answer.Validation === "Modify") { + console.log(`Edit: ${sessionFolder}/explore.csv\nResume: $wave-plan-pipeline --continue`) + return + } else if (answer.Validation === "Cancel") { + return + } + } + ``` + +**Success Criteria**: +- explore.csv created with 1-4 exploration angles +- User approved (or AUTO_YES) + +--- + +### Phase 2: Wave Explore (spawn_agents_on_csv) + +**Objective**: Execute exploration via `spawn_agents_on_csv`. Each angle produces findings and key_files. + +**Steps**: + +1. **Explore Wave Loop** + + ```javascript + const exploreCSV = parseCsv(Read(`${sessionFolder}/explore.csv`)) + const maxExploreWave = Math.max(...exploreCSV.map(r => parseInt(r.wave))) + + for (let wave = 1; wave <= maxExploreWave; wave++) { + const waveTasks = exploreCSV.filter(r => + parseInt(r.wave) === wave && r.status === 'pending' + ) + if (waveTasks.length === 0) continue + + // Skip rows with failed dependencies + const executableTasks = [] + for (const task of waveTasks) { + const deps = (task.deps || '').split(';').filter(Boolean) + if (deps.some(d => { + const dep = exploreCSV.find(r => r.id === d) + return !dep || dep.status !== 'completed' + })) { + task.status = 'skipped' + task.error = 'Dependency failed/skipped' + continue + } + executableTasks.push(task) + } + + if (executableTasks.length === 0) continue + + // Write explore wave CSV + const waveHeader = 'id,angle,description,focus,deps,wave' + const waveRows = executableTasks.map(t => + [t.id, t.angle, t.description, t.focus, t.deps, t.wave] + .map(v => `"${String(v).replace(/"/g, '""')}"`) + .join(',') + ) + Write(`${sessionFolder}/explore-wave-${wave}.csv`, [waveHeader, ...waveRows].join('\n')) + + // Execute explore wave + console.log(` Exploring ${executableTasks.length} angles (wave ${wave})...`) + + spawn_agents_on_csv({ + csv_path: `${sessionFolder}/explore-wave-${wave}.csv`, + id_column: "id", + instruction: buildExploreInstruction(sessionFolder), + max_concurrency: maxConcurrency, + max_runtime_seconds: 300, + output_csv_path: `${sessionFolder}/explore-wave-${wave}-results.csv`, + output_schema: { + type: "object", + properties: { + id: { type: "string" }, + status: { type: "string", enum: ["completed", "failed"] }, + findings: { type: "string" }, + key_files: { type: "array", items: { type: "string" } }, + error: { type: "string" } + }, + required: ["id", "status", "findings"] + } + }) + + // Merge results into explore.csv + const waveResults = parseCsv(Read(`${sessionFolder}/explore-wave-${wave}-results.csv`)) + for (const result of waveResults) { + updateMasterCsvRow(`${sessionFolder}/explore.csv`, result.id, { + status: result.status, + findings: result.findings || '', + key_files: Array.isArray(result.key_files) ? result.key_files.join(';') : (result.key_files || ''), + error: result.error || '' + }) + } + + // Cleanup temporary wave CSV + Bash(`rm -f "${sessionFolder}/explore-wave-${wave}.csv" "${sessionFolder}/explore-wave-${wave}-results.csv"`) + } + ``` + +2. **Explore Instruction Template** + + ```javascript + function buildExploreInstruction(sessionFolder) { + return ` +## EXPLORATION ASSIGNMENT + +### MANDATORY FIRST STEPS +1. Read shared discoveries: ${sessionFolder}/discoveries.ndjson (if exists, skip if not) +2. Read project context: .workflow/project-tech.json (if exists) + +--- + +## Your Exploration + +**Exploration ID**: {id} +**Angle**: {angle} +**Description**: {description} +**Focus**: {focus} + +--- + +## Exploration Protocol + +1. **Read discoveries**: Load ${sessionFolder}/discoveries.ndjson for shared findings +2. **Explore**: Search the codebase from the {angle} perspective +3. **Discover**: Find relevant files, patterns, integration points, constraints +4. **Share discoveries**: Append findings to shared board: + \`\`\`bash + echo '{"ts":"","worker":"{id}","type":"","data":{...}}' >> ${sessionFolder}/discoveries.ndjson + \`\`\` +5. **Report result**: Return JSON via report_agent_job_result + +### What to Look For +- Existing patterns and conventions to follow +- Integration points and module boundaries +- Dependencies and constraints +- Test infrastructure and coverage +- Risks and potential blockers + +### Discovery Types to Share +- \`code_pattern\`: {name, file, description} — reusable patterns found +- \`integration_point\`: {file, description, exports[]} — module connection points +- \`convention\`: {naming, imports, formatting} — code style conventions +- \`tech_stack\`: {framework, version, config} — technology stack details + +--- + +## Output (report_agent_job_result) + +Return JSON: +{ + "id": "{id}", + "status": "completed" | "failed", + "findings": "Concise summary of ${'{'}angle{'}'} discoveries (max 800 chars)", + "key_files": ["relevant/file1.ts", "relevant/file2.ts"], + "error": "" +} +` + } + ``` + +**Success Criteria**: +- All explore angles executed +- explore.csv updated with findings and key_files +- discoveries.ndjson accumulated + +--- + +### Phase 3: Synthesize & Plan → tasks.csv + +**Objective**: Read exploration findings, cross-reference, resolve conflicts, generate tasks.csv with context_from linking to E* rows. + +**Steps**: + +1. **Synthesize Exploration Findings** + + ```javascript + const exploreCSV = parseCsv(Read(`${sessionFolder}/explore.csv`)) + const completed = exploreCSV.filter(r => r.status === 'completed') + + // Cross-reference: find shared files across angles + const fileRefs = {} + completed.forEach(r => { + (r.key_files || '').split(';').filter(Boolean).forEach(f => { + if (!fileRefs[f]) fileRefs[f] = [] + fileRefs[f].push({ angle: r.angle, id: r.id }) + }) + }) + const sharedFiles = Object.entries(fileRefs).filter(([_, refs]) => refs.length > 1) + + // Build synthesis context for task decomposition + const synthesisContext = completed.map(r => + `[${r.id}: ${r.angle}] ${r.findings}\n Key files: ${r.key_files || 'none'}` + ).join('\n\n') + + const sharedFilesContext = sharedFiles.length > 0 + ? `\nShared files (referenced by multiple angles):\n${sharedFiles.map(([f, refs]) => + ` ${f} ← ${refs.map(r => r.id).join(', ')}` + ).join('\n')}` + : '' + ``` + +2. **Decompose into Tasks** + + ```javascript + Bash({ + command: `ccw cli -p "PURPOSE: Based on exploration findings, decompose requirement into 3-10 atomic execution tasks. Each task must include test cases, acceptance criteria, and link to relevant exploration findings. +TASK: + • Use exploration findings to inform task decomposition + • Each task must be self-contained with specific implementation instructions + • Link tasks to exploration rows via context_from (E1, E2, etc.) + • Define dependencies between tasks (T1 must finish before T2, etc.) + • For each task: define test cases, acceptance criteria, scope, hints, and execution directives + • Ensure same-wave tasks have non-overlapping scopes +MODE: analysis +CONTEXT: @**/* +EXPECTED: JSON object with tasks array. Each task: {id: string, title: string, description: string, test: string, acceptance_criteria: string, scope: string, hints: string, execution_directives: string, deps: string[], context_from: string[]}. + - id: T1, T2, etc. + - description: what to implement (specific enough for an agent) + - test: what tests to write (e.g. 'Unit test: X returns Y') + - acceptance_criteria: what defines done (e.g. 'API returns 200') + - scope: target glob (e.g. 'src/auth/**') — non-overlapping within same wave + - hints: tips + ref files (format: 'tips || file1;file2') + - execution_directives: verification commands (e.g. 'npm test --bail') + - deps: task IDs that must complete first (T*) + - context_from: explore (E*) and task (T*) IDs whose findings are needed +CONSTRAINTS: 3-10 tasks | Atomic | No circular deps | Concrete test/acceptance_criteria | Non-overlapping scopes per wave + +EXPLORATION FINDINGS: +${synthesisContext} +${sharedFilesContext} + +REQUIREMENT: ${requirement}" --tool gemini --mode analysis --rule planning-breakdown-task-steps`, + run_in_background: true + }) + // Wait for CLI completion → decomposedTasks[] + ``` + +3. **Compute Waves & Write tasks.csv** + + ```javascript + const { waveAssignment, maxWave } = computeWaves(decomposedTasks) + + const header = 'id,title,description,test,acceptance_criteria,scope,hints,execution_directives,deps,context_from,wave,status,findings,files_modified,tests_passed,acceptance_met,error' + const rows = decomposedTasks.map(task => { + const wave = waveAssignment.get(task.id) + return [ + task.id, + csvEscape(task.title), + csvEscape(task.description), + csvEscape(task.test), + csvEscape(task.acceptance_criteria), + csvEscape(task.scope), + csvEscape(task.hints), + csvEscape(task.execution_directives), + task.deps.join(';'), + task.context_from.join(';'), + wave, + 'pending', '', '', '', '', '' + ].map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(',') + }) + + Write(`${sessionFolder}/tasks.csv`, [header, ...rows].join('\n')) + ``` + +4. **User Validation** (skip if AUTO_YES) + + ```javascript + if (!AUTO_YES) { + console.log(` +## Execution Plan + +Explore: ${completed.length} angles completed +Shared files: ${sharedFiles.length} +Tasks: ${decomposedTasks.length} across ${maxWave} waves + +${Array.from({length: maxWave}, (_, i) => i + 1).map(w => { + const wt = decomposedTasks.filter(t => waveAssignment.get(t.id) === w) + return `### Wave ${w} (${wt.length} tasks, concurrent) +${wt.map(t => ` - [${t.id}] ${t.title} (scope: ${t.scope}, from: ${t.context_from.join(';')})`).join('\n')}` +}).join('\n')} + `) + + const answer = AskUserQuestion({ + questions: [{ + question: `Proceed with ${decomposedTasks.length} tasks across ${maxWave} waves?`, + header: "Confirm", + multiSelect: false, + options: [ + { label: "Execute", description: "Proceed with wave execution" }, + { label: "Modify", description: `Edit ${sessionFolder}/tasks.csv then --continue` }, + { label: "Cancel", description: "Abort" } + ] + }] + }) + + if (answer.Confirm === "Modify") { + console.log(`Edit: ${sessionFolder}/tasks.csv\nResume: $wave-plan-pipeline --continue`) + return + } else if (answer.Confirm === "Cancel") { + return + } + } + ``` + +**Success Criteria**: +- tasks.csv created with context_from linking to E* rows +- No circular dependencies +- User approved (or AUTO_YES) + +--- + +### Phase 4: Wave Execute (spawn_agents_on_csv) + +**Objective**: Execute tasks wave-by-wave via `spawn_agents_on_csv`. Each wave's prev_context is built from both explore.csv and tasks.csv. + +**Steps**: + +1. **Wave Loop** + + ```javascript + const exploreCSV = parseCsv(Read(`${sessionFolder}/explore.csv`)) + const failedIds = new Set() + const skippedIds = new Set() + + for (let wave = 1; wave <= maxWave; wave++) { + console.log(`\n## Wave ${wave}/${maxWave}\n`) + + // Re-read master CSV + const masterCsv = parseCsv(Read(`${sessionFolder}/tasks.csv`)) + const waveTasks = masterCsv.filter(row => parseInt(row.wave) === wave) + + // Skip tasks whose deps failed + const executableTasks = [] + for (const task of waveTasks) { + const deps = (task.deps || '').split(';').filter(Boolean) + if (deps.some(d => failedIds.has(d) || skippedIds.has(d))) { + skippedIds.add(task.id) + updateMasterCsvRow(`${sessionFolder}/tasks.csv`, task.id, { + status: 'skipped', + error: 'Dependency failed or skipped' + }) + console.log(` [${task.id}] ${task.title} → SKIPPED (dependency failed)`) + continue + } + executableTasks.push(task) + } + + if (executableTasks.length === 0) { + console.log(` No executable tasks in wave ${wave}`) + continue + } + + // Build prev_context for each task (cross-phase: E* + T*) + for (const task of executableTasks) { + task.prev_context = buildPrevContext(task.context_from, exploreCSV, masterCsv) + } + + // Write wave CSV + const waveHeader = 'id,title,description,test,acceptance_criteria,scope,hints,execution_directives,deps,context_from,wave,prev_context' + const waveRows = executableTasks.map(t => + [t.id, t.title, t.description, t.test, t.acceptance_criteria, t.scope, t.hints, t.execution_directives, t.deps, t.context_from, t.wave, t.prev_context] + .map(cell => `"${String(cell).replace(/"/g, '""')}"`) + .join(',') + ) + Write(`${sessionFolder}/task-wave-${wave}.csv`, [waveHeader, ...waveRows].join('\n')) + + // Execute wave + console.log(` Executing ${executableTasks.length} tasks (concurrency: ${maxConcurrency})...`) + + spawn_agents_on_csv({ + csv_path: `${sessionFolder}/task-wave-${wave}.csv`, + id_column: "id", + instruction: buildExecuteInstruction(sessionFolder, wave), + max_concurrency: maxConcurrency, + max_runtime_seconds: 600, + output_csv_path: `${sessionFolder}/task-wave-${wave}-results.csv`, + output_schema: { + type: "object", + properties: { + id: { type: "string" }, + status: { type: "string", enum: ["completed", "failed"] }, + findings: { type: "string" }, + files_modified: { type: "array", items: { type: "string" } }, + tests_passed: { type: "boolean" }, + acceptance_met: { type: "string" }, + error: { type: "string" } + }, + required: ["id", "status", "findings", "tests_passed"] + } + }) + + // Merge results into master CSV + const waveResults = parseCsv(Read(`${sessionFolder}/task-wave-${wave}-results.csv`)) + for (const result of waveResults) { + updateMasterCsvRow(`${sessionFolder}/tasks.csv`, result.id, { + status: result.status, + findings: result.findings || '', + files_modified: Array.isArray(result.files_modified) ? result.files_modified.join(';') : (result.files_modified || ''), + tests_passed: String(result.tests_passed ?? ''), + acceptance_met: result.acceptance_met || '', + error: result.error || '' + }) + + if (result.status === 'failed') { + failedIds.add(result.id) + console.log(` [${result.id}] → FAILED: ${result.error}`) + } else { + console.log(` [${result.id}] → COMPLETED${result.tests_passed ? ' ✓tests' : ''}`) + } + } + + // Cleanup + Bash(`rm -f "${sessionFolder}/task-wave-${wave}.csv" "${sessionFolder}/task-wave-${wave}-results.csv"`) + + console.log(` Wave ${wave} done: ${waveResults.filter(r => r.status === 'completed').length} completed, ${waveResults.filter(r => r.status === 'failed').length} failed`) + } + ``` + +2. **prev_context Builder (Cross-Phase)** + + The key function linking exploration context to execution: + + ```javascript + function buildPrevContext(contextFrom, exploreCSV, tasksCSV) { + if (!contextFrom) return 'No previous context available' + + const ids = contextFrom.split(';').filter(Boolean) + const entries = [] + + ids.forEach(id => { + if (id.startsWith('E')) { + // ← Look up in explore.csv (cross-phase link) + const row = exploreCSV.find(r => r.id === id) + if (row && row.status === 'completed' && row.findings) { + entries.push(`[Explore ${row.angle}] ${row.findings}`) + if (row.key_files) entries.push(` Key files: ${row.key_files}`) + } + } else if (id.startsWith('T')) { + // ← Look up in tasks.csv (same-phase link) + const row = tasksCSV.find(r => r.id === id) + if (row && row.status === 'completed' && row.findings) { + entries.push(`[Task ${row.id}: ${row.title}] ${row.findings}`) + if (row.files_modified) entries.push(` Modified: ${row.files_modified}`) + } + } + }) + + return entries.length > 0 ? entries.join('\n') : 'No previous context available' + } + ``` + +3. **Execute Instruction Template** + + ```javascript + function buildExecuteInstruction(sessionFolder, wave) { + return ` +## TASK ASSIGNMENT + +### MANDATORY FIRST STEPS +1. Read shared discoveries: ${sessionFolder}/discoveries.ndjson (if exists, skip if not) +2. Read project context: .workflow/project-tech.json (if exists) + +--- + +## Your Task + +**Task ID**: {id} +**Title**: {title} +**Description**: {description} +**Scope**: {scope} + +### Implementation Hints & Reference Files +{hints} + +> Format: \`tips text || file1;file2\`. Read ALL reference files (after ||) before starting. Apply tips (before ||) as guidance. + +### Execution Directives +{execution_directives} + +> Commands to run for verification, tool restrictions, or environment requirements. + +### Test Cases +{test} + +### Acceptance Criteria +{acceptance_criteria} + +### Previous Context (from exploration and predecessor tasks) +{prev_context} + +--- + +## Execution Protocol + +1. **Read references**: Parse {hints} — read all files listed after \`||\` to understand existing patterns +2. **Read discoveries**: Load ${sessionFolder}/discoveries.ndjson for shared exploration findings +3. **Use context**: Apply previous tasks' findings from prev_context above +4. **Stay in scope**: ONLY create/modify files within {scope} — do NOT touch files outside this boundary +5. **Apply hints**: Follow implementation tips from {hints} (before \`||\`) +6. **Execute**: Implement the task as described +7. **Write tests**: Implement the test cases defined above +8. **Run directives**: Execute commands from {execution_directives} to verify your work +9. **Verify acceptance**: Ensure all acceptance criteria are met before reporting completion +10. **Share discoveries**: Append exploration findings to shared board: + \`\`\`bash + echo '{"ts":"","worker":"{id}","type":"","data":{...}}' >> ${sessionFolder}/discoveries.ndjson + \`\`\` +11. **Report result**: Return JSON via report_agent_job_result + +### Discovery Types to Share +- \`code_pattern\`: {name, file, description} — reusable patterns found +- \`integration_point\`: {file, description, exports[]} — module connection points +- \`convention\`: {naming, imports, formatting} — code style conventions +- \`blocker\`: {issue, severity, impact} — blocking issues encountered + +--- + +## Output (report_agent_job_result) + +Return JSON: +{ + "id": "{id}", + "status": "completed" | "failed", + "findings": "Key discoveries and implementation notes (max 500 chars)", + "files_modified": ["path1", "path2"], + "tests_passed": true | false, + "acceptance_met": "Summary of which acceptance criteria were met/unmet", + "error": "" +} + +**IMPORTANT**: Set status to "completed" ONLY if: +- All test cases pass +- All acceptance criteria are met +Otherwise set status to "failed" with details in error field. +` + } + ``` + +4. **Master CSV Update Helper** + + ```javascript + function updateMasterCsvRow(csvPath, taskId, updates) { + const content = Read(csvPath) + const lines = content.split('\n') + const header = lines[0].split(',') + + for (let i = 1; i < lines.length; i++) { + const cells = parseCsvLine(lines[i]) + if (cells[0] === taskId || cells[0] === `"${taskId}"`) { + for (const [col, val] of Object.entries(updates)) { + const colIdx = header.indexOf(col) + if (colIdx >= 0) { + cells[colIdx] = `"${String(val).replace(/"/g, '""')}"` + } + } + lines[i] = cells.join(',') + break + } + } + + Write(csvPath, lines.join('\n')) + } + ``` + +**Success Criteria**: +- All waves executed in order +- Each wave's results merged into master CSV before next wave starts +- Dependent tasks skipped when predecessor failed +- discoveries.ndjson accumulated across all phases + +--- + +### Phase 5: Results Aggregation + +**Objective**: Generate final results and human-readable report. + +**Steps**: + +1. **Export results.csv** + + ```javascript + const masterCsv = Read(`${sessionFolder}/tasks.csv`) + Write(`${sessionFolder}/results.csv`, masterCsv) + ``` + +2. **Generate context.md** + + ```javascript + const finalTasks = parseCsv(masterCsv) + const exploreCSV = parseCsv(Read(`${sessionFolder}/explore.csv`)) + const completed = finalTasks.filter(t => t.status === 'completed') + const failed = finalTasks.filter(t => t.status === 'failed') + const skipped = finalTasks.filter(t => t.status === 'skipped') + + const contextContent = `# Wave Plan Execution Report + +**Session**: ${sessionId} +**Requirement**: ${requirement} +**Completed**: ${getUtc8ISOString()} +**Waves**: ${maxWave} | **Concurrency**: ${maxConcurrency} + +--- + +## Summary + +| Metric | Count | +|--------|-------| +| Explore Angles | ${exploreCSV.length} | +| Total Tasks | ${finalTasks.length} | +| Completed | ${completed.length} | +| Failed | ${failed.length} | +| Skipped | ${skipped.length} | +| Waves | ${maxWave} | + +--- + +## Exploration Results + +${exploreCSV.map(e => `### ${e.id}: ${e.angle} (${e.status}) +${e.findings || 'N/A'} +Key files: ${e.key_files || 'none'}`).join('\n\n')} + +--- + +## Task Results + +${finalTasks.map(t => `### ${t.id}: ${t.title} (${t.status}) + +| Field | Value | +|-------|-------| +| Wave | ${t.wave} | +| Scope | ${t.scope || 'none'} | +| Dependencies | ${t.deps || 'none'} | +| Context From | ${t.context_from || 'none'} | +| Tests Passed | ${t.tests_passed || 'N/A'} | +| Acceptance Met | ${t.acceptance_met || 'N/A'} | +| Error | ${t.error || 'none'} | + +**Description**: ${t.description} + +**Test Cases**: ${t.test || 'N/A'} + +**Acceptance Criteria**: ${t.acceptance_criteria || 'N/A'} + +**Hints**: ${t.hints || 'N/A'} + +**Execution Directives**: ${t.execution_directives || 'N/A'} + +**Findings**: ${t.findings || 'N/A'} + +**Files Modified**: ${t.files_modified || 'none'}`).join('\n\n---\n\n')} + +--- + +## All Modified Files + +${[...new Set(finalTasks.flatMap(t => (t.files_modified || '').split(';')).filter(Boolean))].map(f => '- ' + f).join('\n') || 'None'} +` + + Write(`${sessionFolder}/context.md`, contextContent) + ``` + +3. **Display Summary** + + ```javascript + console.log(` +## Wave Plan Complete + +- **Session**: ${sessionId} +- **Explore**: ${exploreCSV.filter(r => r.status === 'completed').length}/${exploreCSV.length} angles +- **Tasks**: ${completed.length}/${finalTasks.length} completed, ${failed.length} failed, ${skipped.length} skipped +- **Waves**: ${maxWave} + +**Results**: ${sessionFolder}/results.csv +**Report**: ${sessionFolder}/context.md +**Discoveries**: ${sessionFolder}/discoveries.ndjson + `) + ``` + +4. **Offer Next Steps** (skip if AUTO_YES) + + ```javascript + if (!AUTO_YES && failed.length > 0) { + const answer = AskUserQuestion({ + questions: [{ + question: `${failed.length} tasks failed. Next action?`, + header: "Next Step", + multiSelect: false, + options: [ + { label: "Retry Failed", description: `Re-execute ${failed.length} failed tasks with updated context` }, + { label: "View Report", description: "Display context.md" }, + { label: "Done", description: "Complete session" } + ] + }] + }) + + if (answer['Next Step'] === "Retry Failed") { + for (const task of failed) { + updateMasterCsvRow(`${sessionFolder}/tasks.csv`, task.id, { status: 'pending', error: '' }) + } + for (const task of skipped) { + updateMasterCsvRow(`${sessionFolder}/tasks.csv`, task.id, { status: 'pending', error: '' }) + } + // Re-execute Phase 4 + } else if (answer['Next Step'] === "View Report") { + console.log(Read(`${sessionFolder}/context.md`)) + } + } + ``` + +**Success Criteria**: +- results.csv exported +- context.md generated with full field coverage +- Summary displayed to user + +--- + +## Wave Computation (Kahn's BFS) + +```javascript +function computeWaves(tasks) { + const taskMap = new Map(tasks.map(t => [t.id, t])) + const inDegree = new Map(tasks.map(t => [t.id, 0])) + const adjList = new Map(tasks.map(t => [t.id, []])) + + for (const task of tasks) { + for (const dep of task.deps) { + if (taskMap.has(dep)) { + adjList.get(dep).push(task.id) + inDegree.set(task.id, inDegree.get(task.id) + 1) + } + } + } + + const queue = [] + const waveAssignment = new Map() + + for (const [id, deg] of inDegree) { + if (deg === 0) { + queue.push([id, 1]) + waveAssignment.set(id, 1) + } + } + + let maxWave = 1 + let idx = 0 + while (idx < queue.length) { + const [current, depth] = queue[idx++] + for (const next of adjList.get(current)) { + const newDeg = inDegree.get(next) - 1 + inDegree.set(next, newDeg) + const nextDepth = Math.max(waveAssignment.get(next) || 0, depth + 1) + waveAssignment.set(next, nextDepth) + if (newDeg === 0) { + queue.push([next, nextDepth]) + maxWave = Math.max(maxWave, nextDepth) + } + } + } + + for (const task of tasks) { + if (!waveAssignment.has(task.id)) { + throw new Error(`Circular dependency detected involving task ${task.id}`) + } + } + + return { waveAssignment, maxWave } +} +``` + +--- + +## Shared Discovery Board Protocol + +All agents across all phases share `discoveries.ndjson`. This eliminates redundant codebase exploration. + +```jsonl +{"ts":"2026-02-28T10:00:00+08:00","worker":"E1","type":"code_pattern","data":{"name":"repository-pattern","file":"src/repos/Base.ts","description":"Abstract CRUD repository"}} +{"ts":"2026-02-28T10:01:00+08:00","worker":"T2","type":"integration_point","data":{"file":"src/auth/index.ts","description":"Auth module entry","exports":["authenticate","authorize"]}} +``` + +**Types**: `code_pattern`, `integration_point`, `convention`, `blocker`, `tech_stack`, `test_command` +**Rules**: Read first → write immediately → deduplicate → append-only + +--- + +## Error Handling + +| Error | Resolution | +|-------|------------| +| Explore agent failure | Mark as failed in explore.csv, exclude from planning | +| All explores failed | Fallback: plan directly from requirement without exploration | +| Circular dependency | Abort wave computation, report cycle | +| Execute agent timeout | Mark as failed in results, continue with wave | +| Execute agent failed | Mark as failed, skip dependent tasks in later waves | +| CSV parse error | Validate CSV format before execution, show line number | +| discoveries.ndjson corrupt | Ignore malformed lines, continue with valid entries | +| Continue mode: no session | List available sessions, prompt user to select | + +--- + +## Core Rules + +1. **Explore Before Execute**: Phase 2 completes before Phase 4 starts +2. **Wave Order is Sacred**: Never execute wave N before wave N-1 completes and results are merged +3. **CSV is Source of Truth**: Master CSVs hold all state — always read before wave, always write after +4. **Cross-Phase Context**: prev_context built from both explore.csv (E*) and tasks.csv (T*), not from memory +5. **E* ↔ T* Linking**: tasks.csv `context_from` references explore.csv rows for cross-phase context +6. **Discovery Board is Append-Only**: Never clear, modify, or recreate discoveries.ndjson +7. **Skip on Failure**: If a dependency failed, skip the dependent task (cascade) +8. **Cleanup Temp Files**: Remove wave CSVs after results are merged +9. **DO NOT STOP**: Continuous execution until all waves complete or all remaining tasks are skipped + +--- + +## Best Practices + +1. **Exploration Angles**: 1 for simple, 3-4 for complex; avoid redundant angles +2. **Context Linking**: Link every task to at least one explore row (E*) — exploration was done for a reason +3. **Task Granularity**: 3-10 tasks optimal; too many = overhead, too few = no parallelism +4. **Minimize Cross-Wave Deps**: More tasks in wave 1 = more parallelism +5. **Specific Descriptions**: Agent sees only its CSV row + prev_context — make description self-contained +6. **Non-Overlapping Scopes**: Same-wave tasks must not write to the same files +7. **Concurrency Tuning**: `-c 1` for serial (max context sharing); `-c 8` for I/O-bound tasks + +--- + +## Usage Recommendations + +| Scenario | Recommended Approach | +|----------|---------------------| +| Complex feature (unclear architecture) | `$wave-plan-pipeline` — explore first, then plan | +| Simple known-pattern task | `$csv-wave-pipeline` — skip exploration, direct execution | +| Independent parallel tasks | `$csv-wave-pipeline -c 8` — single wave, max parallelism | +| Diamond dependency (A→B,C→D) | `$wave-plan-pipeline` — 3 waves with context propagation | +| Unknown codebase | `$wave-plan-pipeline` — exploration phase is essential |