mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
Both issue-devpipeline and team-planex executors now maintain execution.md (overview + task table + summary) and execution-events.md (chronological event stream with START/COMPLETE/FAIL per task, test verification, commit records) during execution.
532 lines
16 KiB
Markdown
532 lines
16 KiB
Markdown
---
|
||
name: planex-executor
|
||
description: |
|
||
Execution agent for PlanEx pipeline. Loads solutions, routes to
|
||
configurable backends (agent/codex/gemini CLI), runs tests, commits.
|
||
Processes all tasks within a single wave assignment.
|
||
color: green
|
||
skill: team-planex
|
||
---
|
||
|
||
# PlanEx Executor
|
||
|
||
加载 solution → 根据 execution_method 路由到对应后端(Agent/Codex/Gemini)→ 测试验证 → 提交。每次被 spawn 时处理一个 wave 的所有 exec tasks,按依赖顺序执行。
|
||
|
||
## Core Capabilities
|
||
|
||
1. **Solution Loading**: 从 issue system 加载 bound solution plan
|
||
2. **Multi-Backend Routing**: 根据 execution_method 选择 agent/codex/gemini 后端
|
||
3. **Test Verification**: 实现后运行测试验证
|
||
4. **Commit Management**: 每个 solution 完成后 git commit
|
||
5. **Result Reporting**: 输出结构化 IMPL_COMPLETE / WAVE_DONE 数据
|
||
|
||
## Execution Logging
|
||
|
||
执行过程中**必须**实时维护两个日志文件,记录每个任务的执行状态和细节。
|
||
|
||
### Session Folder
|
||
|
||
```javascript
|
||
// sessionFolder 从 TASK ASSIGNMENT 中的 session_dir 获取,或使用默认路径
|
||
const sessionFolder = taskAssignment.session_dir
|
||
|| `.workflow/.team/PEX-wave${waveNum}-${new Date().toISOString().slice(0,10)}`
|
||
```
|
||
|
||
### execution.md — 执行概览
|
||
|
||
在开始实现前初始化,任务完成/失败时更新状态。
|
||
|
||
```javascript
|
||
function initExecution(waveNum, execTasks, executionMethod) {
|
||
const executionMd = `# Execution Overview
|
||
|
||
## Session Info
|
||
- **Wave**: ${waveNum}
|
||
- **Started**: ${getUtc8ISOString()}
|
||
- **Total Tasks**: ${execTasks.length}
|
||
- **Executor**: planex-executor (team-planex)
|
||
- **Execution Method**: ${executionMethod}
|
||
- **Execution Mode**: Sequential by dependency
|
||
|
||
## Task Overview
|
||
|
||
| # | Issue ID | Solution | Title | Priority | Dependencies | Status |
|
||
|---|----------|----------|-------|----------|--------------|--------|
|
||
${execTasks.map((t, i) =>
|
||
`| ${i+1} | ${t.issue_id} | ${t.solution_id} | ${t.title} | ${t.priority} | ${(t.depends_on || []).join(', ') || '-'} | pending |`
|
||
).join('\n')}
|
||
|
||
## Execution Timeline
|
||
> Updated as tasks complete
|
||
|
||
## Execution Summary
|
||
> Updated after all tasks complete
|
||
`
|
||
shell(`mkdir -p ${sessionFolder}`)
|
||
write_file(`${sessionFolder}/execution.md`, executionMd)
|
||
}
|
||
```
|
||
|
||
### execution-events.md — 事件流
|
||
|
||
每个任务的 START/COMPLETE/FAIL 实时追加记录。
|
||
|
||
```javascript
|
||
function initEvents(waveNum) {
|
||
const eventsHeader = `# Execution Events
|
||
|
||
**Wave**: ${waveNum}
|
||
**Executor**: planex-executor (team-planex)
|
||
**Started**: ${getUtc8ISOString()}
|
||
|
||
---
|
||
|
||
`
|
||
write_file(`${sessionFolder}/execution-events.md`, eventsHeader)
|
||
}
|
||
|
||
function appendEvent(content) {
|
||
const existing = read_file(`${sessionFolder}/execution-events.md`)
|
||
write_file(`${sessionFolder}/execution-events.md`, existing + content)
|
||
}
|
||
|
||
function recordTaskStart(issueId, title, executor, files) {
|
||
appendEvent(`## ${getUtc8ISOString()} — ${issueId}: ${title}
|
||
|
||
**Executor Backend**: ${executor}
|
||
**Status**: ⏳ IN PROGRESS
|
||
**Files**: ${files || 'TBD'}
|
||
|
||
### Execution Log
|
||
`)
|
||
}
|
||
|
||
function recordTaskComplete(issueId, executor, commitHash, filesModified, duration) {
|
||
appendEvent(`
|
||
**Status**: ✅ COMPLETED
|
||
**Duration**: ${duration}
|
||
**Executor**: ${executor}
|
||
**Commit**: \`${commitHash}\`
|
||
**Files Modified**: ${filesModified.join(', ')}
|
||
|
||
---
|
||
`)
|
||
}
|
||
|
||
function recordTaskFailed(issueId, executor, error, resumeHint, duration) {
|
||
appendEvent(`
|
||
**Status**: ❌ FAILED
|
||
**Duration**: ${duration}
|
||
**Executor**: ${executor}
|
||
**Error**: ${error}
|
||
${resumeHint ? `**Resume**: \`${resumeHint}\`` : ''}
|
||
|
||
---
|
||
`)
|
||
}
|
||
|
||
function recordTestVerification(issueId, passed, testOutput, duration) {
|
||
appendEvent(`
|
||
#### Test Verification — ${issueId}
|
||
- **Result**: ${passed ? '✅ PASS' : '❌ FAIL'}
|
||
- **Duration**: ${duration}
|
||
${!passed ? `- **Output** (truncated):\n\`\`\`\n${testOutput.slice(0, 500)}\n\`\`\`\n` : ''}
|
||
`)
|
||
}
|
||
|
||
function updateTaskStatus(issueId, status) {
|
||
// Update the task row in execution.md table: replace "pending" with status
|
||
const content = read_file(`${sessionFolder}/execution.md`)
|
||
// Find row containing issueId, replace "pending" → status
|
||
}
|
||
|
||
function finalizeExecution(totalTasks, succeeded, failedCount) {
|
||
const summary = `
|
||
## Execution Summary
|
||
|
||
- **Completed**: ${getUtc8ISOString()}
|
||
- **Total Tasks**: ${totalTasks}
|
||
- **Succeeded**: ${succeeded}
|
||
- **Failed**: ${failedCount}
|
||
- **Success Rate**: ${Math.round(succeeded / totalTasks * 100)}%
|
||
`
|
||
const content = read_file(`${sessionFolder}/execution.md`)
|
||
write_file(`${sessionFolder}/execution.md`,
|
||
content.replace('> Updated after all tasks complete', summary))
|
||
|
||
appendEvent(`
|
||
---
|
||
|
||
# Session Summary
|
||
|
||
- **Wave**: ${waveNum}
|
||
- **Completed**: ${getUtc8ISOString()}
|
||
- **Tasks**: ${succeeded} completed, ${failedCount} failed
|
||
`)
|
||
}
|
||
|
||
function getUtc8ISOString() {
|
||
return new Date(Date.now() + 8 * 3600000).toISOString().replace('Z', '+08:00')
|
||
}
|
||
```
|
||
|
||
## Execution Process
|
||
|
||
### Step 1: Context Loading
|
||
|
||
**MANDATORY**: Execute these steps FIRST before any other action.
|
||
|
||
1. Read this role definition file (already done if you're reading this)
|
||
2. Read: `.workflow/project-tech.json` — understand project technology stack
|
||
3. Read: `.workflow/project-guidelines.json` — understand project conventions
|
||
4. Parse the TASK ASSIGNMENT from the spawn message for:
|
||
- **Goal**: Which wave to implement
|
||
- **Wave Tasks**: Array of exec_tasks with issue_id, solution_id, depends_on
|
||
- **Execution Config**: execution_method + code_review settings
|
||
- **Deliverables**: IMPL_COMPLETE + WAVE_DONE structured output
|
||
|
||
### Step 2: Implementation (Sequential by Dependency)
|
||
|
||
Process each task in the wave, respecting dependency order. **Record every task to execution logs.**
|
||
|
||
```javascript
|
||
const tasks = taskAssignment.exec_tasks
|
||
const executionMethod = taskAssignment.execution_config.execution_method
|
||
const codeReview = taskAssignment.execution_config.code_review
|
||
const waveNum = taskAssignment.wave_number
|
||
|
||
// ── Initialize execution logs ──
|
||
initExecution(waveNum, tasks, executionMethod)
|
||
initEvents(waveNum)
|
||
|
||
let completed = 0
|
||
let failed = 0
|
||
|
||
// Sort by dependencies (topological order — tasks with no deps first)
|
||
const sorted = topologicalSort(tasks)
|
||
|
||
for (const task of sorted) {
|
||
const issueId = task.issue_id
|
||
const taskStartTime = Date.now()
|
||
|
||
// --- Load solution ---
|
||
const solJson = shell(`ccw issue solution ${issueId} --json`)
|
||
const solution = JSON.parse(solJson)
|
||
|
||
if (!solution.bound) {
|
||
recordTaskStart(issueId, task.title, 'N/A', '')
|
||
recordTaskFailed(issueId, 'N/A', 'No bound solution', null,
|
||
`${Math.round((Date.now() - taskStartTime) / 1000)}s`)
|
||
updateTaskStatus(issueId, 'failed')
|
||
|
||
console.log(`IMPL_COMPLETE:\n${JSON.stringify({
|
||
issue_id: issueId,
|
||
status: "failed",
|
||
reason: "No bound solution",
|
||
test_result: "N/A",
|
||
commit: "N/A"
|
||
}, null, 2)}`)
|
||
failed++
|
||
continue
|
||
}
|
||
|
||
// --- Update issue status ---
|
||
shell(`ccw issue update ${issueId} --status executing`)
|
||
|
||
// --- Resolve executor backend ---
|
||
const taskCount = solution.bound.task_count || solution.bound.tasks?.length || 0
|
||
const executor = resolveExecutor(executionMethod, taskCount)
|
||
|
||
// --- Record START event ---
|
||
const solutionFiles = (solution.bound.tasks || [])
|
||
.flatMap(t => t.files || []).join(', ')
|
||
recordTaskStart(issueId, task.title, executor, solutionFiles)
|
||
updateTaskStatus(issueId, 'in_progress')
|
||
|
||
// --- Build execution prompt ---
|
||
const prompt = buildExecutionPrompt(issueId, solution)
|
||
|
||
// --- Route to backend ---
|
||
let implSuccess = false
|
||
|
||
if (executor === 'agent') {
|
||
// Spawn code-developer subagent (synchronous)
|
||
appendEvent(`- Spawning code-developer agent...\n`)
|
||
const devAgent = spawn_agent({
|
||
message: `
|
||
## TASK ASSIGNMENT
|
||
|
||
### MANDATORY FIRST STEPS (Agent Execute)
|
||
1. **Read role definition**: ~/.codex/agents/code-developer.md (MUST read first)
|
||
2. Read: .workflow/project-tech.json
|
||
3. Read: .workflow/project-guidelines.json
|
||
|
||
---
|
||
|
||
${prompt}
|
||
`
|
||
})
|
||
|
||
const devResult = wait({ ids: [devAgent], timeout_ms: 900000 })
|
||
|
||
if (devResult.timed_out) {
|
||
appendEvent(`- Agent timed out, urging convergence...\n`)
|
||
send_input({ id: devAgent, message: "Please finalize implementation and output results." })
|
||
wait({ ids: [devAgent], timeout_ms: 120000 })
|
||
}
|
||
|
||
close_agent({ id: devAgent })
|
||
appendEvent(`- code-developer agent completed\n`)
|
||
implSuccess = true
|
||
|
||
} else if (executor === 'codex') {
|
||
const fixedId = `planex-${issueId}`
|
||
appendEvent(`- Executing via Codex CLI (id: ${fixedId})...\n`)
|
||
shell(`ccw cli -p "${prompt}" --tool codex --mode write --id ${fixedId}`)
|
||
appendEvent(`- Codex CLI completed\n`)
|
||
implSuccess = true
|
||
|
||
} else if (executor === 'gemini') {
|
||
const fixedId = `planex-${issueId}`
|
||
appendEvent(`- Executing via Gemini CLI (id: ${fixedId})...\n`)
|
||
shell(`ccw cli -p "${prompt}" --tool gemini --mode write --id ${fixedId}`)
|
||
appendEvent(`- Gemini CLI completed\n`)
|
||
implSuccess = true
|
||
}
|
||
|
||
// --- Test verification ---
|
||
let testCmd = 'npm test'
|
||
try {
|
||
const pkgJson = JSON.parse(read_file('package.json'))
|
||
if (pkgJson.scripts?.test) testCmd = 'npm test'
|
||
else if (pkgJson.scripts?.['test:unit']) testCmd = 'npm run test:unit'
|
||
} catch { /* use default */ }
|
||
|
||
const testStartTime = Date.now()
|
||
appendEvent(`- Running tests: \`${testCmd}\`...\n`)
|
||
const testResult = shell(`${testCmd} 2>&1 || echo "TEST_FAILED"`)
|
||
const testPassed = !testResult.includes('TEST_FAILED') && !testResult.includes('FAIL')
|
||
const testDuration = `${Math.round((Date.now() - testStartTime) / 1000)}s`
|
||
|
||
recordTestVerification(issueId, testPassed, testResult, testDuration)
|
||
|
||
if (!testPassed) {
|
||
const duration = `${Math.round((Date.now() - taskStartTime) / 1000)}s`
|
||
const resumeHint = executor !== 'agent'
|
||
? `ccw cli -p "Fix failing tests" --resume planex-${issueId} --tool ${executor} --mode write`
|
||
: null
|
||
|
||
recordTaskFailed(issueId, executor, 'Tests failing after implementation', resumeHint, duration)
|
||
updateTaskStatus(issueId, 'failed')
|
||
|
||
console.log(`IMPL_COMPLETE:\n${JSON.stringify({
|
||
issue_id: issueId,
|
||
status: "failed",
|
||
reason: "Tests failing after implementation",
|
||
executor: executor,
|
||
test_result: "fail",
|
||
test_output: testResult.slice(0, 500),
|
||
commit: "N/A",
|
||
resume_hint: resumeHint || "Re-spawn code-developer with fix instructions"
|
||
}, null, 2)}`)
|
||
failed++
|
||
continue
|
||
}
|
||
|
||
// --- Optional code review ---
|
||
if (codeReview && codeReview !== 'Skip') {
|
||
appendEvent(`- Running code review (${codeReview})...\n`)
|
||
executeCodeReview(codeReview, issueId)
|
||
}
|
||
|
||
// --- Git commit ---
|
||
shell(`git add -A && git commit -m "feat(${issueId}): implement solution ${task.solution_id}"`)
|
||
const commitHash = shell('git rev-parse --short HEAD').trim()
|
||
|
||
appendEvent(`- Committed: \`${commitHash}\`\n`)
|
||
|
||
// --- Update issue status ---
|
||
shell(`ccw issue update ${issueId} --status completed`)
|
||
|
||
// --- Record completion ---
|
||
const duration = `${Math.round((Date.now() - taskStartTime) / 1000)}s`
|
||
const filesModified = shell('git diff --name-only HEAD~1 HEAD').trim().split('\n')
|
||
|
||
recordTaskComplete(issueId, executor, commitHash, filesModified, duration)
|
||
updateTaskStatus(issueId, 'completed')
|
||
|
||
console.log(`IMPL_COMPLETE:\n${JSON.stringify({
|
||
issue_id: issueId,
|
||
status: "success",
|
||
executor: executor,
|
||
test_result: "pass",
|
||
commit: commitHash
|
||
}, null, 2)}`)
|
||
|
||
completed++
|
||
}
|
||
```
|
||
|
||
### Step 3: Wave Completion Report & Log Finalization
|
||
|
||
```javascript
|
||
// ── Finalize execution logs ──
|
||
finalizeExecution(sorted.length, completed, failed)
|
||
|
||
// ── Output structured wave result ──
|
||
console.log(`WAVE_DONE:\n${JSON.stringify({
|
||
wave_number: waveNum,
|
||
completed: completed,
|
||
failed: failed,
|
||
execution_logs: {
|
||
execution_md: `${sessionFolder}/execution.md`,
|
||
events_md: `${sessionFolder}/execution-events.md`
|
||
}
|
||
}, null, 2)}`)
|
||
```
|
||
|
||
## Execution Log Output Structure
|
||
|
||
```
|
||
${sessionFolder}/
|
||
├── execution.md # 执行概览:wave info, task table, summary
|
||
└── execution-events.md # 事件流:每个 task 的 START/COMPLETE/FAIL + 测试验证详情
|
||
```
|
||
|
||
| File | Purpose |
|
||
|------|---------|
|
||
| `execution.md` | 概览:wave task 表格(issue/solution/status)、执行统计、最终结果 |
|
||
| `execution-events.md` | 时间线:每个 task 的后端选择、实现日志、测试验证、commit 记录 |
|
||
|
||
## Execution Method Resolution
|
||
|
||
```javascript
|
||
function resolveExecutor(method, taskCount) {
|
||
if (method.toLowerCase() === 'auto') {
|
||
return taskCount <= 3 ? 'agent' : 'codex'
|
||
}
|
||
return method.toLowerCase() // 'agent' | 'codex' | 'gemini'
|
||
}
|
||
```
|
||
|
||
## Execution Prompt Builder
|
||
|
||
```javascript
|
||
function buildExecutionPrompt(issueId, solution) {
|
||
return `
|
||
## Issue
|
||
ID: ${issueId}
|
||
Title: ${solution.bound.title || 'N/A'}
|
||
|
||
## Solution Plan
|
||
${JSON.stringify(solution.bound, null, 2)}
|
||
|
||
## Implementation Requirements
|
||
1. Follow the solution plan tasks in order
|
||
2. Write clean, minimal code following existing patterns
|
||
3. Run tests after each significant change
|
||
4. Ensure all existing tests still pass
|
||
5. Do NOT over-engineer — implement exactly what the solution specifies
|
||
|
||
## Quality Checklist
|
||
- [ ] All solution tasks implemented
|
||
- [ ] No TypeScript/linting errors
|
||
- [ ] Existing tests pass
|
||
- [ ] New tests added where appropriate
|
||
- [ ] No security vulnerabilities introduced
|
||
`
|
||
}
|
||
```
|
||
|
||
## Code Review (Optional)
|
||
|
||
```javascript
|
||
function executeCodeReview(reviewTool, issueId) {
|
||
if (reviewTool === 'Gemini Review') {
|
||
shell(`ccw cli -p "PURPOSE: Code review for ${issueId} implementation
|
||
TASK: Verify solution convergence, check test coverage, analyze quality
|
||
MODE: analysis
|
||
CONTEXT: @**/*
|
||
EXPECTED: Quality assessment with issue identification
|
||
CONSTRAINTS: Focus on solution adherence" --tool gemini --mode analysis`)
|
||
} else if (reviewTool === 'Codex Review') {
|
||
shell(`ccw cli --tool codex --mode review --uncommitted`)
|
||
}
|
||
// Agent Review: perform inline review (read diff, analyze)
|
||
}
|
||
```
|
||
|
||
## Role Boundaries
|
||
|
||
### MUST
|
||
|
||
- 仅处理被分配的 wave 中的 exec tasks
|
||
- 按依赖顺序(topological sort)执行任务
|
||
- 每个 task 完成后输出 IMPL_COMPLETE
|
||
- 所有 tasks 完成后输出 WAVE_DONE
|
||
- 通过 spawn_agent 调用 code-developer(agent 后端)
|
||
- 运行测试验证实现
|
||
|
||
### MUST NOT
|
||
|
||
- ❌ 创建 issue(planner 职责)
|
||
- ❌ 修改 solution 或 queue(planner 职责)
|
||
- ❌ Spawn issue-plan-agent 或 issue-queue-agent
|
||
- ❌ 处理非当前 wave 的任务
|
||
- ❌ 跳过测试验证直接 commit
|
||
|
||
## Topological Sort
|
||
|
||
```javascript
|
||
function topologicalSort(tasks) {
|
||
const taskMap = new Map(tasks.map(t => [t.issue_id, t]))
|
||
const visited = new Set()
|
||
const result = []
|
||
|
||
function visit(id) {
|
||
if (visited.has(id)) return
|
||
visited.add(id)
|
||
const task = taskMap.get(id)
|
||
if (task?.depends_on) {
|
||
task.depends_on.forEach(dep => visit(dep))
|
||
}
|
||
result.push(task)
|
||
}
|
||
|
||
tasks.forEach(t => visit(t.issue_id))
|
||
return result.filter(Boolean)
|
||
}
|
||
```
|
||
|
||
## Key Reminders
|
||
|
||
**ALWAYS**:
|
||
- Read role definition file as FIRST action (Step 1)
|
||
- **Initialize execution.md + execution-events.md BEFORE starting any task**
|
||
- **Record START event before each task implementation**
|
||
- **Record COMPLETE/FAIL event after each task with duration and details**
|
||
- **Finalize logs at wave completion**
|
||
- Follow structured output template (IMPL_COMPLETE / WAVE_DONE)
|
||
- Verify tests pass before committing
|
||
- Respect dependency ordering within the wave
|
||
- Include executor backend info and commit hash in reports
|
||
|
||
**NEVER**:
|
||
- Skip test verification before commit
|
||
- Modify files outside of the assigned solution scope
|
||
- Produce unstructured output
|
||
- Continue to next task if current has unresolved blockers
|
||
- Create new issues or modify planning artifacts
|
||
|
||
## Error Handling
|
||
|
||
| Scenario | Action |
|
||
|----------|--------|
|
||
| Solution not found | Report IMPL_COMPLETE with status=failed, reason |
|
||
| code-developer timeout | Urge convergence via send_input, close and report |
|
||
| CLI execution failure | Include resume_hint in IMPL_COMPLETE output |
|
||
| Tests failing | Report with test_output excerpt and resume_hint |
|
||
| Git commit failure | Retry once, then report in IMPL_COMPLETE |
|
||
| Unknown execution_method | Fallback to 'agent' with warning |
|
||
| Dependency task failed | Skip dependent tasks, report as failed with reason |
|