Files
Claude-Code-Workflow/.codex/skills/team-planex/agents/planex-executor.md
catlog22 ba1f99f858 feat(codex): add execution logging to planex-executor agents
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.
2026-02-23 23:25:16 +08:00

16 KiB
Raw Blame History

name, description, color, skill
name description color skill
planex-executor 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. green 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

// sessionFolder 从 TASK ASSIGNMENT 中的 session_dir 获取,或使用默认路径
const sessionFolder = taskAssignment.session_dir
  || `.workflow/.team/PEX-wave${waveNum}-${new Date().toISOString().slice(0,10)}`

execution.md — 执行概览

在开始实现前初始化,任务完成/失败时更新状态。

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 实时追加记录。

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.

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

// ── 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

function resolveExecutor(method, taskCount) {
  if (method.toLowerCase() === 'auto') {
    return taskCount <= 3 ? 'agent' : 'codex'
  }
  return method.toLowerCase() // 'agent' | 'codex' | 'gemini'
}

Execution Prompt Builder

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)

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-developeragent 后端)
  • 运行测试验证实现

MUST NOT

  • 创建 issueplanner 职责)
  • 修改 solution 或 queueplanner 职责)
  • Spawn issue-plan-agent 或 issue-queue-agent
  • 处理非当前 wave 的任务
  • 跳过测试验证直接 commit

Topological Sort

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