mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
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.
This commit is contained in:
@@ -18,6 +18,147 @@ skill: issue-devpipeline
|
|||||||
3. **测试验证**: 运行相关测试确保变更正确且不破坏现有功能
|
3. **测试验证**: 运行相关测试确保变更正确且不破坏现有功能
|
||||||
4. **变更提交**: 将实现的代码 commit 到 git
|
4. **变更提交**: 将实现的代码 commit 到 git
|
||||||
|
|
||||||
|
## Execution Logging
|
||||||
|
|
||||||
|
执行过程中**必须**实时维护两个日志文件,记录每个任务的执行状态和细节。
|
||||||
|
|
||||||
|
### Session Folder
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// sessionFolder 从 TASK ASSIGNMENT 中的 session_dir 获取,或使用默认路径
|
||||||
|
const sessionFolder = taskAssignment.session_dir || `.workflow/.team/PEX-${issueId}`
|
||||||
|
```
|
||||||
|
|
||||||
|
### execution.md — 执行概览
|
||||||
|
|
||||||
|
在开始实现前初始化,任务完成/失败时更新状态。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function initExecution(issueId, solution) {
|
||||||
|
const executionMd = `# Execution Overview
|
||||||
|
|
||||||
|
## Session Info
|
||||||
|
- **Issue**: ${issueId}
|
||||||
|
- **Solution**: ${solution.bound?.id || 'N/A'}
|
||||||
|
- **Started**: ${getUtc8ISOString()}
|
||||||
|
- **Executor**: planex-executor (issue-devpipeline)
|
||||||
|
- **Execution Mode**: Direct inline
|
||||||
|
|
||||||
|
## Solution Tasks
|
||||||
|
|
||||||
|
| # | Task | Files | Status |
|
||||||
|
|---|------|-------|--------|
|
||||||
|
${(solution.bound?.tasks || []).map((t, i) =>
|
||||||
|
`| ${i+1} | ${t.title || t.description || 'Task ' + (i+1)} | ${(t.files || []).join(', ') || '-'} | pending |`
|
||||||
|
).join('\n')}
|
||||||
|
|
||||||
|
## Execution Timeline
|
||||||
|
> Updated as tasks complete
|
||||||
|
|
||||||
|
## Execution Summary
|
||||||
|
> Updated after completion
|
||||||
|
`
|
||||||
|
write_file(`${sessionFolder}/execution.md`, executionMd)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### execution-events.md — 事件流
|
||||||
|
|
||||||
|
每个任务的 START/COMPLETE/FAIL 实时追加记录。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function initEvents(issueId) {
|
||||||
|
const eventsHeader = `# Execution Events
|
||||||
|
|
||||||
|
**Issue**: ${issueId}
|
||||||
|
**Executor**: planex-executor (issue-devpipeline)
|
||||||
|
**Started**: ${getUtc8ISOString()}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`
|
||||||
|
write_file(`${sessionFolder}/execution-events.md`, eventsHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendEvent(content) {
|
||||||
|
// Append to execution-events.md
|
||||||
|
const existing = read_file(`${sessionFolder}/execution-events.md`)
|
||||||
|
write_file(`${sessionFolder}/execution-events.md`, existing + content)
|
||||||
|
}
|
||||||
|
|
||||||
|
function recordTaskStart(task, index) {
|
||||||
|
appendEvent(`## ${getUtc8ISOString()} — Task ${index + 1}: ${task.title || task.description || 'Unnamed'}
|
||||||
|
|
||||||
|
**Status**: ⏳ IN PROGRESS
|
||||||
|
**Files**: ${(task.files || []).join(', ') || 'TBD'}
|
||||||
|
|
||||||
|
### Execution Log
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function recordTaskComplete(task, index, filesModified, changeSummary, duration) {
|
||||||
|
appendEvent(`
|
||||||
|
**Status**: ✅ COMPLETED
|
||||||
|
**Duration**: ${duration}
|
||||||
|
**Files Modified**: ${filesModified.join(', ')}
|
||||||
|
|
||||||
|
#### Changes Summary
|
||||||
|
${changeSummary}
|
||||||
|
|
||||||
|
---
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function recordTaskFailed(task, index, error, duration) {
|
||||||
|
appendEvent(`
|
||||||
|
**Status**: ❌ FAILED
|
||||||
|
**Duration**: ${duration}
|
||||||
|
**Error**: ${error}
|
||||||
|
|
||||||
|
---
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTaskStatus(taskIndex, status) {
|
||||||
|
// Update the task row in execution.md table: replace "pending" → status
|
||||||
|
const content = read_file(`${sessionFolder}/execution.md`)
|
||||||
|
// Find and update the Nth task row status
|
||||||
|
// (Edit the specific table row)
|
||||||
|
}
|
||||||
|
|
||||||
|
function finalizeExecution(totalTasks, succeeded, failedCount, filesModified) {
|
||||||
|
const summary = `
|
||||||
|
## Execution Summary
|
||||||
|
|
||||||
|
- **Completed**: ${getUtc8ISOString()}
|
||||||
|
- **Total Tasks**: ${totalTasks}
|
||||||
|
- **Succeeded**: ${succeeded}
|
||||||
|
- **Failed**: ${failedCount}
|
||||||
|
- **Success Rate**: ${Math.round(succeeded / totalTasks * 100)}%
|
||||||
|
- **Files Modified**: ${filesModified.join(', ')}
|
||||||
|
`
|
||||||
|
// Append summary to execution.md
|
||||||
|
const content = read_file(`${sessionFolder}/execution.md`)
|
||||||
|
write_file(`${sessionFolder}/execution.md`,
|
||||||
|
content.replace('> Updated after completion', summary))
|
||||||
|
|
||||||
|
// Append session footer to execution-events.md
|
||||||
|
appendEvent(`
|
||||||
|
---
|
||||||
|
|
||||||
|
# Session Summary
|
||||||
|
|
||||||
|
- **Issue**: ${issueId}
|
||||||
|
- **Completed**: ${getUtc8ISOString()}
|
||||||
|
- **Tasks**: ${succeeded} completed, ${failedCount} failed
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUtc8ISOString() {
|
||||||
|
return new Date(Date.now() + 8 * 3600000).toISOString().replace('Z', '+08:00')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Execution Process
|
## Execution Process
|
||||||
|
|
||||||
### Step 1: Context Loading
|
### Step 1: Context Loading
|
||||||
@@ -32,6 +173,7 @@ skill: issue-devpipeline
|
|||||||
- **Issue ID**: 目标 issue 标识
|
- **Issue ID**: 目标 issue 标识
|
||||||
- **Solution ID**: 绑定的 solution 标识
|
- **Solution ID**: 绑定的 solution 标识
|
||||||
- **Dependencies**: 依赖的其他 issues(应已完成)
|
- **Dependencies**: 依赖的其他 issues(应已完成)
|
||||||
|
- **Session Dir**: 日志文件存放路径
|
||||||
- **Deliverables**: Expected JSON output format
|
- **Deliverables**: Expected JSON output format
|
||||||
|
|
||||||
### Step 2: Solution Loading & Implementation
|
### Step 2: Solution Loading & Implementation
|
||||||
@@ -43,23 +185,55 @@ const solJson = shell(`ccw issue solutions ${issueId} --json`)
|
|||||||
const solution = JSON.parse(solJson)
|
const solution = JSON.parse(solJson)
|
||||||
|
|
||||||
if (!solution.bound) {
|
if (!solution.bound) {
|
||||||
// No bound solution — report error
|
|
||||||
outputError(`No bound solution for ${issueId}`)
|
outputError(`No bound solution for ${issueId}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Initialize execution logs ──
|
||||||
|
shell(`mkdir -p ${sessionFolder}`)
|
||||||
|
initExecution(issueId, solution)
|
||||||
|
initEvents(issueId)
|
||||||
|
|
||||||
// Update issue status
|
// Update issue status
|
||||||
shell(`ccw issue update ${issueId} --status in-progress`)
|
shell(`ccw issue update ${issueId} --status in-progress`)
|
||||||
|
|
||||||
// ── Implement according to solution plan ──
|
// ── Implement according to solution plan ──
|
||||||
const plan = solution.bound
|
const plan = solution.bound
|
||||||
const tasks = plan.tasks || []
|
const tasks = plan.tasks || []
|
||||||
|
let succeeded = 0, failedCount = 0
|
||||||
|
const allFilesModified = []
|
||||||
|
|
||||||
for (const task of tasks) {
|
for (let i = 0; i < tasks.length; i++) {
|
||||||
|
const task = tasks[i]
|
||||||
|
const startTime = Date.now()
|
||||||
|
|
||||||
|
// Record START event
|
||||||
|
recordTaskStart(task, i)
|
||||||
|
|
||||||
|
try {
|
||||||
// 1. Read target files
|
// 1. Read target files
|
||||||
// 2. Apply changes following existing patterns
|
// 2. Apply changes following existing patterns
|
||||||
// 3. Write/Edit files
|
// 3. Write/Edit files
|
||||||
// 4. Verify no syntax errors
|
// 4. Verify no syntax errors
|
||||||
|
|
||||||
|
const endTime = Date.now()
|
||||||
|
const duration = `${Math.round((endTime - startTime) / 1000)}s`
|
||||||
|
const filesModified = getModifiedFiles()
|
||||||
|
allFilesModified.push(...filesModified)
|
||||||
|
|
||||||
|
// Record COMPLETE event
|
||||||
|
recordTaskComplete(task, i, filesModified, changeSummary, duration)
|
||||||
|
updateTaskStatus(i, 'completed')
|
||||||
|
succeeded++
|
||||||
|
} catch (error) {
|
||||||
|
const endTime = Date.now()
|
||||||
|
const duration = `${Math.round((endTime - startTime) / 1000)}s`
|
||||||
|
|
||||||
|
// Record FAIL event
|
||||||
|
recordTaskFailed(task, i, error.message, duration)
|
||||||
|
updateTaskStatus(i, 'failed')
|
||||||
|
failedCount++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -69,7 +243,7 @@ for (const task of tasks) {
|
|||||||
- 最小化变更,不做超出 solution 范围的修改
|
- 最小化变更,不做超出 solution 范围的修改
|
||||||
- 每个 task 完成后验证无语法错误
|
- 每个 task 完成后验证无语法错误
|
||||||
|
|
||||||
### Step 3: Testing & Commit
|
### Step 3: Testing, Commit & Finalize Logs
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// ── Detect test command ──
|
// ── Detect test command ──
|
||||||
@@ -79,40 +253,78 @@ try {
|
|||||||
if (pkgJson.scripts?.test) testCmd = 'npm test'
|
if (pkgJson.scripts?.test) testCmd = 'npm test'
|
||||||
else if (pkgJson.scripts?.['test:unit']) testCmd = 'npm run test:unit'
|
else if (pkgJson.scripts?.['test:unit']) testCmd = 'npm run test:unit'
|
||||||
} catch {
|
} catch {
|
||||||
// Try common test runners
|
|
||||||
if (fileExists('pytest.ini') || fileExists('setup.py')) testCmd = 'pytest'
|
if (fileExists('pytest.ini') || fileExists('setup.py')) testCmd = 'pytest'
|
||||||
else if (fileExists('Cargo.toml')) testCmd = 'cargo test'
|
else if (fileExists('Cargo.toml')) testCmd = 'cargo test'
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Run tests ──
|
// ── Run tests ──
|
||||||
|
const testStartTime = Date.now()
|
||||||
|
appendEvent(`## ${getUtc8ISOString()} — Integration Test Verification
|
||||||
|
|
||||||
|
**Status**: ⏳ IN PROGRESS
|
||||||
|
**Command**: \`${testCmd}\`
|
||||||
|
|
||||||
|
### Test Log
|
||||||
|
`)
|
||||||
|
|
||||||
const testResult = shell(`${testCmd} 2>&1`)
|
const testResult = shell(`${testCmd} 2>&1`)
|
||||||
const testsPassed = !testResult.includes('FAIL') && testResult.exitCode === 0
|
let testsPassed = !testResult.includes('FAIL') && testResult.exitCode === 0
|
||||||
|
|
||||||
if (!testsPassed) {
|
if (!testsPassed) {
|
||||||
// Attempt fix: analyze failures, apply fix, re-test (max 2 retries)
|
|
||||||
let retries = 0
|
let retries = 0
|
||||||
while (retries < 2 && !testsPassed) {
|
while (retries < 2 && !testsPassed) {
|
||||||
// Analyze test output, identify failure cause, apply fix
|
appendEvent(`- Retry ${retries + 1}: fixing test failures...\n`)
|
||||||
retries++
|
retries++
|
||||||
const retestResult = shell(`${testCmd} 2>&1`)
|
const retestResult = shell(`${testCmd} 2>&1`)
|
||||||
testsPassed = !retestResult.includes('FAIL') && retestResult.exitCode === 0
|
testsPassed = !retestResult.includes('FAIL') && retestResult.exitCode === 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const testDuration = `${Math.round((Date.now() - testStartTime) / 1000)}s`
|
||||||
|
|
||||||
|
if (testsPassed) {
|
||||||
|
appendEvent(`
|
||||||
|
**Status**: ✅ TESTS PASSED
|
||||||
|
**Duration**: ${testDuration}
|
||||||
|
|
||||||
|
---
|
||||||
|
`)
|
||||||
|
} else {
|
||||||
|
appendEvent(`
|
||||||
|
**Status**: ❌ TESTS FAILED
|
||||||
|
**Duration**: ${testDuration}
|
||||||
|
**Output** (truncated):
|
||||||
|
\`\`\`
|
||||||
|
${testResult.slice(0, 500)}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
---
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
// ── Commit if tests pass ──
|
// ── Commit if tests pass ──
|
||||||
let commitHash = null
|
let commitHash = null
|
||||||
let committed = false
|
let committed = false
|
||||||
|
|
||||||
if (testsPassed) {
|
if (testsPassed) {
|
||||||
// Stage changed files
|
|
||||||
shell('git add -A')
|
shell('git add -A')
|
||||||
shell(`git commit -m "feat(${issueId}): implement solution ${solution.bound.id}"`)
|
shell(`git commit -m "feat(${issueId}): implement solution ${solution.bound.id}"`)
|
||||||
commitHash = shell('git rev-parse --short HEAD').trim()
|
commitHash = shell('git rev-parse --short HEAD').trim()
|
||||||
committed = true
|
committed = true
|
||||||
|
|
||||||
// Update issue status
|
appendEvent(`## ${getUtc8ISOString()} — Git Commit
|
||||||
|
|
||||||
|
**Commit**: \`${commitHash}\`
|
||||||
|
**Message**: feat(${issueId}): implement solution ${solution.bound.id}
|
||||||
|
|
||||||
|
---
|
||||||
|
`)
|
||||||
|
|
||||||
shell(`ccw issue update ${issueId} --status resolved`)
|
shell(`ccw issue update ${issueId} --status resolved`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Finalize execution logs ──
|
||||||
|
finalizeExecution(tasks.length, succeeded, failedCount, [...new Set(allFilesModified)])
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 4: Output Delivery
|
### Step 4: Output Delivery
|
||||||
@@ -131,7 +343,11 @@ if (testsPassed) {
|
|||||||
"committed": true,
|
"committed": true,
|
||||||
"commit_hash": "abc1234",
|
"commit_hash": "abc1234",
|
||||||
"error": null,
|
"error": null,
|
||||||
"summary": "实现用户登录功能,添加 2 个文件,通过所有测试"
|
"summary": "实现用户登录功能,添加 2 个文件,通过所有测试",
|
||||||
|
"execution_logs": {
|
||||||
|
"execution_md": "${sessionFolder}/execution.md",
|
||||||
|
"events_md": "${sessionFolder}/execution-events.md"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -146,10 +362,27 @@ if (testsPassed) {
|
|||||||
"committed": false,
|
"committed": false,
|
||||||
"commit_hash": null,
|
"commit_hash": null,
|
||||||
"error": "Tests failing: login.test.ts:42 - Expected 200 got 401",
|
"error": "Tests failing: login.test.ts:42 - Expected 200 got 401",
|
||||||
"summary": "代码实现完成但测试未通过,需要 solution 修订"
|
"summary": "代码实现完成但测试未通过,需要 solution 修订",
|
||||||
|
"execution_logs": {
|
||||||
|
"execution_md": "${sessionFolder}/execution.md",
|
||||||
|
"events_md": "${sessionFolder}/execution-events.md"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Execution Log Output Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
${sessionFolder}/
|
||||||
|
├── execution.md # 执行概览:session info, task table, summary
|
||||||
|
└── execution-events.md # 事件流:每个 task 的 START/COMPLETE/FAIL 详情
|
||||||
|
```
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `execution.md` | 概览:solution tasks 表格、执行统计、最终结果 |
|
||||||
|
| `execution-events.md` | 时间线:每个任务和测试验证的详细事件记录 |
|
||||||
|
|
||||||
## Role Boundaries
|
## Role Boundaries
|
||||||
|
|
||||||
### MUST
|
### MUST
|
||||||
@@ -174,6 +407,10 @@ if (testsPassed) {
|
|||||||
|
|
||||||
**ALWAYS**:
|
**ALWAYS**:
|
||||||
- Read role definition file as FIRST action (Step 1)
|
- Read role definition file as FIRST action (Step 1)
|
||||||
|
- **Initialize execution.md + execution-events.md BEFORE starting implementation**
|
||||||
|
- **Record START event before each solution task**
|
||||||
|
- **Record COMPLETE/FAIL event after each task with duration and details**
|
||||||
|
- **Finalize logs after testing and commit**
|
||||||
- Load solution plan before implementing
|
- Load solution plan before implementing
|
||||||
- Follow existing code patterns in the project
|
- Follow existing code patterns in the project
|
||||||
- Run tests before committing
|
- Run tests before committing
|
||||||
|
|||||||
@@ -20,6 +20,156 @@ skill: team-planex
|
|||||||
4. **Commit Management**: 每个 solution 完成后 git commit
|
4. **Commit Management**: 每个 solution 完成后 git commit
|
||||||
5. **Result Reporting**: 输出结构化 IMPL_COMPLETE / WAVE_DONE 数据
|
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
|
## Execution Process
|
||||||
|
|
||||||
### Step 1: Context Loading
|
### Step 1: Context Loading
|
||||||
@@ -37,7 +187,7 @@ skill: team-planex
|
|||||||
|
|
||||||
### Step 2: Implementation (Sequential by Dependency)
|
### Step 2: Implementation (Sequential by Dependency)
|
||||||
|
|
||||||
Process each task in the wave, respecting dependency order.
|
Process each task in the wave, respecting dependency order. **Record every task to execution logs.**
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const tasks = taskAssignment.exec_tasks
|
const tasks = taskAssignment.exec_tasks
|
||||||
@@ -45,6 +195,10 @@ const executionMethod = taskAssignment.execution_config.execution_method
|
|||||||
const codeReview = taskAssignment.execution_config.code_review
|
const codeReview = taskAssignment.execution_config.code_review
|
||||||
const waveNum = taskAssignment.wave_number
|
const waveNum = taskAssignment.wave_number
|
||||||
|
|
||||||
|
// ── Initialize execution logs ──
|
||||||
|
initExecution(waveNum, tasks, executionMethod)
|
||||||
|
initEvents(waveNum)
|
||||||
|
|
||||||
let completed = 0
|
let completed = 0
|
||||||
let failed = 0
|
let failed = 0
|
||||||
|
|
||||||
@@ -53,12 +207,18 @@ const sorted = topologicalSort(tasks)
|
|||||||
|
|
||||||
for (const task of sorted) {
|
for (const task of sorted) {
|
||||||
const issueId = task.issue_id
|
const issueId = task.issue_id
|
||||||
|
const taskStartTime = Date.now()
|
||||||
|
|
||||||
// --- Load solution ---
|
// --- Load solution ---
|
||||||
const solJson = shell(`ccw issue solution ${issueId} --json`)
|
const solJson = shell(`ccw issue solution ${issueId} --json`)
|
||||||
const solution = JSON.parse(solJson)
|
const solution = JSON.parse(solJson)
|
||||||
|
|
||||||
if (!solution.bound) {
|
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({
|
console.log(`IMPL_COMPLETE:\n${JSON.stringify({
|
||||||
issue_id: issueId,
|
issue_id: issueId,
|
||||||
status: "failed",
|
status: "failed",
|
||||||
@@ -77,6 +237,12 @@ for (const task of sorted) {
|
|||||||
const taskCount = solution.bound.task_count || solution.bound.tasks?.length || 0
|
const taskCount = solution.bound.task_count || solution.bound.tasks?.length || 0
|
||||||
const executor = resolveExecutor(executionMethod, taskCount)
|
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 ---
|
// --- Build execution prompt ---
|
||||||
const prompt = buildExecutionPrompt(issueId, solution)
|
const prompt = buildExecutionPrompt(issueId, solution)
|
||||||
|
|
||||||
@@ -85,6 +251,7 @@ for (const task of sorted) {
|
|||||||
|
|
||||||
if (executor === 'agent') {
|
if (executor === 'agent') {
|
||||||
// Spawn code-developer subagent (synchronous)
|
// Spawn code-developer subagent (synchronous)
|
||||||
|
appendEvent(`- Spawning code-developer agent...\n`)
|
||||||
const devAgent = spawn_agent({
|
const devAgent = spawn_agent({
|
||||||
message: `
|
message: `
|
||||||
## TASK ASSIGNMENT
|
## TASK ASSIGNMENT
|
||||||
@@ -103,24 +270,27 @@ ${prompt}
|
|||||||
const devResult = wait({ ids: [devAgent], timeout_ms: 900000 })
|
const devResult = wait({ ids: [devAgent], timeout_ms: 900000 })
|
||||||
|
|
||||||
if (devResult.timed_out) {
|
if (devResult.timed_out) {
|
||||||
|
appendEvent(`- Agent timed out, urging convergence...\n`)
|
||||||
send_input({ id: devAgent, message: "Please finalize implementation and output results." })
|
send_input({ id: devAgent, message: "Please finalize implementation and output results." })
|
||||||
wait({ ids: [devAgent], timeout_ms: 120000 })
|
wait({ ids: [devAgent], timeout_ms: 120000 })
|
||||||
}
|
}
|
||||||
|
|
||||||
close_agent({ id: devAgent })
|
close_agent({ id: devAgent })
|
||||||
implSuccess = true // will verify with tests below
|
appendEvent(`- code-developer agent completed\n`)
|
||||||
|
implSuccess = true
|
||||||
|
|
||||||
} else if (executor === 'codex') {
|
} else if (executor === 'codex') {
|
||||||
// Codex CLI execution
|
|
||||||
const fixedId = `planex-${issueId}`
|
const fixedId = `planex-${issueId}`
|
||||||
|
appendEvent(`- Executing via Codex CLI (id: ${fixedId})...\n`)
|
||||||
shell(`ccw cli -p "${prompt}" --tool codex --mode write --id ${fixedId}`)
|
shell(`ccw cli -p "${prompt}" --tool codex --mode write --id ${fixedId}`)
|
||||||
// Wait for CLI completion (synchronous in agent context)
|
appendEvent(`- Codex CLI completed\n`)
|
||||||
implSuccess = true
|
implSuccess = true
|
||||||
|
|
||||||
} else if (executor === 'gemini') {
|
} else if (executor === 'gemini') {
|
||||||
// Gemini CLI execution
|
|
||||||
const fixedId = `planex-${issueId}`
|
const fixedId = `planex-${issueId}`
|
||||||
|
appendEvent(`- Executing via Gemini CLI (id: ${fixedId})...\n`)
|
||||||
shell(`ccw cli -p "${prompt}" --tool gemini --mode write --id ${fixedId}`)
|
shell(`ccw cli -p "${prompt}" --tool gemini --mode write --id ${fixedId}`)
|
||||||
|
appendEvent(`- Gemini CLI completed\n`)
|
||||||
implSuccess = true
|
implSuccess = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,10 +302,23 @@ ${prompt}
|
|||||||
else if (pkgJson.scripts?.['test:unit']) testCmd = 'npm run test:unit'
|
else if (pkgJson.scripts?.['test:unit']) testCmd = 'npm run test:unit'
|
||||||
} catch { /* use default */ }
|
} catch { /* use default */ }
|
||||||
|
|
||||||
|
const testStartTime = Date.now()
|
||||||
|
appendEvent(`- Running tests: \`${testCmd}\`...\n`)
|
||||||
const testResult = shell(`${testCmd} 2>&1 || echo "TEST_FAILED"`)
|
const testResult = shell(`${testCmd} 2>&1 || echo "TEST_FAILED"`)
|
||||||
const testPassed = !testResult.includes('TEST_FAILED') && !testResult.includes('FAIL')
|
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) {
|
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({
|
console.log(`IMPL_COMPLETE:\n${JSON.stringify({
|
||||||
issue_id: issueId,
|
issue_id: issueId,
|
||||||
status: "failed",
|
status: "failed",
|
||||||
@@ -144,9 +327,7 @@ ${prompt}
|
|||||||
test_result: "fail",
|
test_result: "fail",
|
||||||
test_output: testResult.slice(0, 500),
|
test_output: testResult.slice(0, 500),
|
||||||
commit: "N/A",
|
commit: "N/A",
|
||||||
resume_hint: executor !== 'agent'
|
resume_hint: resumeHint || "Re-spawn code-developer with fix instructions"
|
||||||
? `ccw cli -p "Fix failing tests" --resume planex-${issueId} --tool ${executor} --mode write`
|
|
||||||
: "Re-spawn code-developer with fix instructions"
|
|
||||||
}, null, 2)}`)
|
}, null, 2)}`)
|
||||||
failed++
|
failed++
|
||||||
continue
|
continue
|
||||||
@@ -154,6 +335,7 @@ ${prompt}
|
|||||||
|
|
||||||
// --- Optional code review ---
|
// --- Optional code review ---
|
||||||
if (codeReview && codeReview !== 'Skip') {
|
if (codeReview && codeReview !== 'Skip') {
|
||||||
|
appendEvent(`- Running code review (${codeReview})...\n`)
|
||||||
executeCodeReview(codeReview, issueId)
|
executeCodeReview(codeReview, issueId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,10 +343,18 @@ ${prompt}
|
|||||||
shell(`git add -A && git commit -m "feat(${issueId}): implement solution ${task.solution_id}"`)
|
shell(`git add -A && git commit -m "feat(${issueId}): implement solution ${task.solution_id}"`)
|
||||||
const commitHash = shell('git rev-parse --short HEAD').trim()
|
const commitHash = shell('git rev-parse --short HEAD').trim()
|
||||||
|
|
||||||
|
appendEvent(`- Committed: \`${commitHash}\`\n`)
|
||||||
|
|
||||||
// --- Update issue status ---
|
// --- Update issue status ---
|
||||||
shell(`ccw issue update ${issueId} --status completed`)
|
shell(`ccw issue update ${issueId} --status completed`)
|
||||||
|
|
||||||
// --- Report completion ---
|
// --- 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({
|
console.log(`IMPL_COMPLETE:\n${JSON.stringify({
|
||||||
issue_id: issueId,
|
issue_id: issueId,
|
||||||
status: "success",
|
status: "success",
|
||||||
@@ -177,16 +367,37 @@ ${prompt}
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 3: Wave Completion Report
|
### Step 3: Wave Completion Report & Log Finalization
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
// ── Finalize execution logs ──
|
||||||
|
finalizeExecution(sorted.length, completed, failed)
|
||||||
|
|
||||||
|
// ── Output structured wave result ──
|
||||||
console.log(`WAVE_DONE:\n${JSON.stringify({
|
console.log(`WAVE_DONE:\n${JSON.stringify({
|
||||||
wave_number: waveNum,
|
wave_number: waveNum,
|
||||||
completed: completed,
|
completed: completed,
|
||||||
failed: failed
|
failed: failed,
|
||||||
|
execution_logs: {
|
||||||
|
execution_md: `${sessionFolder}/execution.md`,
|
||||||
|
events_md: `${sessionFolder}/execution-events.md`
|
||||||
|
}
|
||||||
}, null, 2)}`)
|
}, 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
|
## Execution Method Resolution
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
@@ -291,6 +502,10 @@ function topologicalSort(tasks) {
|
|||||||
|
|
||||||
**ALWAYS**:
|
**ALWAYS**:
|
||||||
- Read role definition file as FIRST action (Step 1)
|
- 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)
|
- Follow structured output template (IMPL_COMPLETE / WAVE_DONE)
|
||||||
- Verify tests pass before committing
|
- Verify tests pass before committing
|
||||||
- Respect dependency ordering within the wave
|
- Respect dependency ordering within the wave
|
||||||
|
|||||||
Reference in New Issue
Block a user