mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-10 02:24:35 +08:00
Add tests for CLI command generation and model alias resolution
- Implement `test-cli-command-gen.js` to verify the logic of `buildCliCommand` function. - Create `test-e2e-model-alias.js` for end-to-end testing of model alias resolution in `ccw cli`. - Add `test-model-alias.js` to test model alias resolution for different models. - Introduce `test-model-alias.txt` for prompt testing with model alias. - Develop `test-update-claude-command.js` to test command generation for `update_module_claude`. - Create a test file in `test-update-claude/src` for future tests.
This commit is contained in:
@@ -1,173 +0,0 @@
|
||||
# Action: Complete
|
||||
|
||||
完成工作流并生成最终报告。
|
||||
|
||||
## Purpose
|
||||
|
||||
序列化最终状态,生成执行摘要,清理临时文件。
|
||||
|
||||
## Preconditions
|
||||
|
||||
- [ ] `state.status === "running"`
|
||||
- [ ] 所有 issues 已处理或错误限制达到
|
||||
|
||||
## Execution
|
||||
|
||||
```javascript
|
||||
async function execute(state) {
|
||||
const workDir = state.work_dir;
|
||||
const issues = state.issues || {};
|
||||
|
||||
console.log("\n=== Finalizing Workflow ===");
|
||||
|
||||
// 1. 生成统计信息
|
||||
const totalIssues = Object.keys(issues).length;
|
||||
const completedCount = Object.values(issues).filter(i => i.status === "completed").length;
|
||||
const failedCount = Object.values(issues).filter(i => i.status === "failed").length;
|
||||
const pendingCount = totalIssues - completedCount - failedCount;
|
||||
|
||||
const stats = {
|
||||
total_issues: totalIssues,
|
||||
completed: completedCount,
|
||||
failed: failedCount,
|
||||
pending: pendingCount,
|
||||
success_rate: totalIssues > 0 ? ((completedCount / totalIssues) * 100).toFixed(1) : 0,
|
||||
duration_ms: new Date() - new Date(state.created_at)
|
||||
};
|
||||
|
||||
console.log("\n=== Summary ===");
|
||||
console.log(`Total Issues: ${stats.total_issues}`);
|
||||
console.log(`✓ Completed: ${stats.completed}`);
|
||||
console.log(`✗ Failed: ${stats.failed}`);
|
||||
console.log(`○ Pending: ${stats.pending}`);
|
||||
console.log(`Success Rate: ${stats.success_rate}%`);
|
||||
console.log(`Duration: ${(stats.duration_ms / 1000).toFixed(1)}s`);
|
||||
|
||||
// 2. 生成详细报告
|
||||
const reportLines = [
|
||||
"# Execution Report",
|
||||
"",
|
||||
`## Summary`,
|
||||
`- Total Issues: ${stats.total_issues}`,
|
||||
`- Completed: ${stats.completed}`,
|
||||
`- Failed: ${stats.failed}`,
|
||||
`- Pending: ${stats.pending}`,
|
||||
`- Success Rate: ${stats.success_rate}%`,
|
||||
`- Duration: ${(stats.duration_ms / 1000).toFixed(1)}s`,
|
||||
"",
|
||||
"## Results by Issue"
|
||||
];
|
||||
|
||||
Object.values(issues).forEach((issue, index) => {
|
||||
const status = issue.status === "completed" ? "✓" : issue.status === "failed" ? "✗" : "○";
|
||||
reportLines.push(`### ${status} [${index + 1}] ${issue.id}: ${issue.title}`);
|
||||
reportLines.push(`- Status: ${issue.status}`);
|
||||
if (issue.solution_id) {
|
||||
reportLines.push(`- Solution: ${issue.solution_id}`);
|
||||
}
|
||||
if (issue.planned_at) {
|
||||
reportLines.push(`- Planned: ${issue.planned_at}`);
|
||||
}
|
||||
if (issue.executed_at) {
|
||||
reportLines.push(`- Executed: ${issue.executed_at}`);
|
||||
}
|
||||
if (issue.error) {
|
||||
reportLines.push(`- Error: ${issue.error}`);
|
||||
}
|
||||
reportLines.push("");
|
||||
});
|
||||
|
||||
if (state.errors && state.errors.length > 0) {
|
||||
reportLines.push("## Errors");
|
||||
state.errors.forEach(error => {
|
||||
reportLines.push(`- [${error.timestamp}] ${error.action}: ${error.message}`);
|
||||
});
|
||||
reportLines.push("");
|
||||
}
|
||||
|
||||
reportLines.push("## Files Generated");
|
||||
reportLines.push(`- Work Directory: ${workDir}`);
|
||||
reportLines.push(`- State File: ${workDir}/state.json`);
|
||||
reportLines.push(`- Execution Results: ${workDir}/execution-results.json`);
|
||||
reportLines.push(`- Solutions: ${workDir}/solutions/`);
|
||||
reportLines.push(`- Snapshots: ${workDir}/snapshots/`);
|
||||
|
||||
// 3. 保存报告
|
||||
const reportPath = `${workDir}/final-report.md`;
|
||||
Write(reportPath, reportLines.join("\n"));
|
||||
|
||||
// 4. 保存最终状态
|
||||
const finalState = {
|
||||
...state,
|
||||
status: "completed",
|
||||
phase: "completed",
|
||||
completed_at: new Date().toISOString(),
|
||||
completed_actions: [...state.completed_actions, "action-complete"],
|
||||
context: {
|
||||
...state.context,
|
||||
...stats
|
||||
}
|
||||
};
|
||||
|
||||
Write(`${workDir}/state.json`, JSON.stringify(finalState, null, 2));
|
||||
|
||||
// 5. 保存汇总 JSON
|
||||
Write(`${workDir}/summary.json`, JSON.stringify({
|
||||
status: "completed",
|
||||
stats: stats,
|
||||
report_file: reportPath,
|
||||
work_dir: workDir,
|
||||
completed_at: new Date().toISOString()
|
||||
}, null, 2));
|
||||
|
||||
// 6. 输出完成消息
|
||||
console.log(`\n✓ Workflow completed`);
|
||||
console.log(`📄 Report: ${reportPath}`);
|
||||
console.log(`📁 Working directory: ${workDir}`);
|
||||
|
||||
return {
|
||||
stateUpdates: {
|
||||
status: "completed",
|
||||
phase: "completed",
|
||||
completed_at: new Date().toISOString(),
|
||||
completed_actions: [...state.completed_actions, "action-complete"],
|
||||
context: finalState.context
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## State Updates
|
||||
|
||||
```javascript
|
||||
return {
|
||||
stateUpdates: {
|
||||
status: "completed",
|
||||
phase: "completed",
|
||||
completed_at: timestamp,
|
||||
completed_actions: [...state.completed_actions, "action-complete"],
|
||||
context: {
|
||||
total_issues: stats.total_issues,
|
||||
completed_count: stats.completed,
|
||||
failed_count: stats.failed,
|
||||
success_rate: stats.success_rate
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error Type | Recovery |
|
||||
|------------|----------|
|
||||
| 报告生成失败 | 输出文本摘要到控制台 |
|
||||
| 文件写入失败 | 继续完成,允许手动保存 |
|
||||
| 权限错误 | 使用替代目录 |
|
||||
|
||||
## Next Actions (Hints)
|
||||
|
||||
- 无(终止状态)
|
||||
- 用户可选择:
|
||||
- 查看报告:`cat {report_path}`
|
||||
- 恢复并重试失败的 issues:`codex issue:plan-execute --resume {work_dir}`
|
||||
- 清理临时文件:`rm -rf {work_dir}`
|
||||
@@ -1,220 +0,0 @@
|
||||
# Action: Execute Solutions
|
||||
|
||||
按队列顺序执行已规划的解决方案。
|
||||
|
||||
## Purpose
|
||||
|
||||
加载计划的解决方案并使用 subagent 执行所有任务、提交更改。
|
||||
|
||||
## Preconditions
|
||||
|
||||
- [ ] `state.status === "running"`
|
||||
- [ ] `issues with solution_id` exist (来自规划阶段)
|
||||
|
||||
## Execution
|
||||
|
||||
```javascript
|
||||
async function execute(state) {
|
||||
const workDir = state.work_dir;
|
||||
const issues = state.issues || {};
|
||||
const queue = state.queue || [];
|
||||
|
||||
// 1. 构建执行队列(来自已规划的 issues)
|
||||
const plannedIssues = Object.values(issues).filter(i => i.status === "planned");
|
||||
|
||||
if (plannedIssues.length === 0) {
|
||||
console.log("No planned solutions to execute");
|
||||
return { stateUpdates: { queue } };
|
||||
}
|
||||
|
||||
console.log(`\n=== Executing ${plannedIssues.length} Solutions ===`);
|
||||
|
||||
// 2. 序列化执行每个解决方案
|
||||
const executionResults = [];
|
||||
|
||||
for (let i = 0; i < plannedIssues.length; i++) {
|
||||
const issue = plannedIssues[i];
|
||||
const solutionId = issue.solution_id;
|
||||
|
||||
console.log(`\n[${i + 1}/${plannedIssues.length}] Executing: ${solutionId}`);
|
||||
|
||||
try {
|
||||
// 创建快照(便于恢复)
|
||||
const beforeSnapshot = {
|
||||
timestamp: new Date().toISOString(),
|
||||
phase: "before-execute",
|
||||
issue_id: issue.id,
|
||||
solution_id: solutionId,
|
||||
state: { ...state }
|
||||
};
|
||||
Write(`${workDir}/snapshots/snapshot-before-execute-${i}.json`, JSON.stringify(beforeSnapshot, null, 2));
|
||||
|
||||
// 执行 subagent
|
||||
const executionPrompt = `
|
||||
## TASK ASSIGNMENT
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
1. **Read role definition**: ~/.codex/agents/issue-execute-agent.md (MUST read first)
|
||||
2. Read: .workflow/project-tech.json
|
||||
3. Read: .workflow/project-guidelines.json
|
||||
|
||||
---
|
||||
|
||||
Goal: Execute solution "${solutionId}" for issue "${issue.id}"
|
||||
|
||||
Scope:
|
||||
- CAN DO: Implement tasks, run tests, commit code
|
||||
- CANNOT DO: Push to remote or create PRs without approval
|
||||
- Directory: ${process.cwd()}
|
||||
|
||||
Solution ID: ${solutionId}
|
||||
|
||||
Load solution details:
|
||||
- Read: ${workDir}/solutions/${issue.id}-plan.json
|
||||
|
||||
Execution steps:
|
||||
1. Parse all tasks from solution
|
||||
2. Execute each task: implement → test → verify
|
||||
3. Commit once for all tasks with formatted summary
|
||||
4. Report completion
|
||||
|
||||
Quality bar:
|
||||
- All acceptance criteria verified
|
||||
- Tests passing
|
||||
- Commit message follows conventions
|
||||
|
||||
Return: JSON with files_modified[], commit_hash, status
|
||||
`;
|
||||
|
||||
const result = await Task({
|
||||
subagent_type: "universal-executor",
|
||||
run_in_background: false,
|
||||
description: `Execute solution ${solutionId}`,
|
||||
prompt: executionPrompt
|
||||
});
|
||||
|
||||
// 解析执行结果
|
||||
let execResult;
|
||||
try {
|
||||
execResult = typeof result === "string" ? JSON.parse(result) : result;
|
||||
} catch {
|
||||
execResult = { status: "executed", commit_hash: "unknown" };
|
||||
}
|
||||
|
||||
// 保存执行结果
|
||||
Write(`${workDir}/solutions/${issue.id}-execution.json`, JSON.stringify({
|
||||
solution_id: solutionId,
|
||||
issue_id: issue.id,
|
||||
status: "completed",
|
||||
executed_at: new Date().toISOString(),
|
||||
execution_result: execResult
|
||||
}, null, 2));
|
||||
|
||||
// 更新 issue 状态
|
||||
issues[issue.id].status = "completed";
|
||||
issues[issue.id].executed_at = new Date().toISOString();
|
||||
|
||||
// 更新队列项
|
||||
const queueIndex = queue.findIndex(q => q.solution_id === solutionId);
|
||||
if (queueIndex >= 0) {
|
||||
queue[queueIndex].status = "completed";
|
||||
}
|
||||
|
||||
// 更新 ccw
|
||||
try {
|
||||
Bash(`ccw issue update ${issue.id} --status completed`);
|
||||
} catch (error) {
|
||||
console.log(`Note: Could not update ccw status (${error.message})`);
|
||||
}
|
||||
|
||||
console.log(`✓ ${solutionId} completed`);
|
||||
executionResults.push({
|
||||
issue_id: issue.id,
|
||||
solution_id: solutionId,
|
||||
status: "completed",
|
||||
commit: execResult.commit_hash
|
||||
});
|
||||
|
||||
state.context.completed_count++;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`✗ Execution failed for ${solutionId}: ${error.message}`);
|
||||
|
||||
// 更新失败状态
|
||||
issues[issue.id].status = "failed";
|
||||
issues[issue.id].error = error.message;
|
||||
|
||||
state.context.failed_count++;
|
||||
|
||||
executionResults.push({
|
||||
issue_id: issue.id,
|
||||
solution_id: solutionId,
|
||||
status: "failed",
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 保存执行结果摘要
|
||||
Write(`${workDir}/execution-results.json`, JSON.stringify({
|
||||
total: plannedIssues.length,
|
||||
completed: state.context.completed_count,
|
||||
failed: state.context.failed_count,
|
||||
results: executionResults,
|
||||
timestamp: new Date().toISOString()
|
||||
}, null, 2));
|
||||
|
||||
return {
|
||||
stateUpdates: {
|
||||
issues: issues,
|
||||
queue: queue,
|
||||
context: state.context,
|
||||
completed_actions: [...state.completed_actions, "action-execute"]
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## State Updates
|
||||
|
||||
```javascript
|
||||
return {
|
||||
stateUpdates: {
|
||||
issues: {
|
||||
[issue.id]: {
|
||||
...issue,
|
||||
status: "completed|failed",
|
||||
executed_at: timestamp,
|
||||
error: errorMessage
|
||||
}
|
||||
},
|
||||
queue: [
|
||||
...queue.map(item =>
|
||||
item.solution_id === solutionId
|
||||
? { ...item, status: "completed|failed" }
|
||||
: item
|
||||
)
|
||||
],
|
||||
context: {
|
||||
...state.context,
|
||||
completed_count: newCompletedCount,
|
||||
failed_count: newFailedCount
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error Type | Recovery |
|
||||
|------------|----------|
|
||||
| 任务执行失败 | 标记为失败,继续下一个 |
|
||||
| 测试失败 | 不提交,标记为失败 |
|
||||
| 提交失败 | 保存快照便于恢复 |
|
||||
| Subagent 超时 | 记录超时,继续 |
|
||||
|
||||
## Next Actions (Hints)
|
||||
|
||||
- 执行完成:转入 action-complete 阶段
|
||||
- 有失败项:用户选择是否重试
|
||||
- 全部完成:生成最终报告
|
||||
@@ -1,86 +0,0 @@
|
||||
# Action: Initialize
|
||||
|
||||
初始化 Skill 执行状态和工作目录。
|
||||
|
||||
## Purpose
|
||||
|
||||
设置初始状态,创建工作目录,准备执行环境。
|
||||
|
||||
## Preconditions
|
||||
|
||||
- [ ] `state.status === "pending"`
|
||||
|
||||
## Execution
|
||||
|
||||
```javascript
|
||||
async function execute(state) {
|
||||
// 创建工作目录
|
||||
const timestamp = new Date().toISOString().slice(0,19).replace(/[-:T]/g, '');
|
||||
const workDir = `.workflow/.scratchpad/codex-issue-${timestamp}`;
|
||||
|
||||
Bash(`mkdir -p "${workDir}/solutions" "${workDir}/snapshots"`);
|
||||
|
||||
// 初始化状态
|
||||
const initialState = {
|
||||
status: "running",
|
||||
phase: "initialized",
|
||||
work_dir: workDir,
|
||||
issues: {},
|
||||
queue: [],
|
||||
completed_actions: ["action-init"],
|
||||
context: {
|
||||
total_issues: 0,
|
||||
completed_count: 0,
|
||||
failed_count: 0
|
||||
},
|
||||
errors: [],
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
// 保存初始状态
|
||||
Write(`${workDir}/state.json`, JSON.stringify(initialState, null, 2));
|
||||
Write(`${workDir}/state-history.json`, JSON.stringify([{
|
||||
timestamp: initialState.created_at,
|
||||
phase: "init",
|
||||
completed_actions: 1,
|
||||
issues_count: 0
|
||||
}], null, 2));
|
||||
|
||||
console.log(`✓ Initialized: ${workDir}`);
|
||||
|
||||
return {
|
||||
stateUpdates: {
|
||||
status: "running",
|
||||
phase: "initialized",
|
||||
work_dir: workDir,
|
||||
completed_actions: ["action-init"]
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## State Updates
|
||||
|
||||
```javascript
|
||||
return {
|
||||
stateUpdates: {
|
||||
status: "running",
|
||||
phase: "initialized",
|
||||
work_dir: workDir,
|
||||
completed_actions: ["action-init"]
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error Type | Recovery |
|
||||
|------------|----------|
|
||||
| 目录创建失败 | 检查权限,使用临时目录 |
|
||||
| 文件写入失败 | 重试或切换存储位置 |
|
||||
|
||||
## Next Actions (Hints)
|
||||
|
||||
- 成功:进入 listing phase,执行 action-list
|
||||
- 失败:中止工作流
|
||||
@@ -1,165 +0,0 @@
|
||||
# Action: List Issues
|
||||
|
||||
列出 issues 并支持用户交互选择。
|
||||
|
||||
## Purpose
|
||||
|
||||
展示当前所有 issues 的状态,收集用户的规划/执行意图。
|
||||
|
||||
## Preconditions
|
||||
|
||||
- [ ] `state.status === "running"`
|
||||
|
||||
## Execution
|
||||
|
||||
```javascript
|
||||
async function execute(state) {
|
||||
// 1. 加载或初始化 issues
|
||||
let issues = state.issues || {};
|
||||
|
||||
// 2. 从 ccw issue list 或提供的参数加载 issues
|
||||
// 这取决于用户是否在命令行提供了 issue IDs
|
||||
// 示例:ccw codex issue:plan-execute ISS-001,ISS-002
|
||||
|
||||
// 对于本次演示,我们假设从 issues.jsonl 加载
|
||||
try {
|
||||
const issuesListOutput = Bash("ccw issue list --status registered,planned --json").output;
|
||||
const issuesList = JSON.parse(issuesListOutput);
|
||||
|
||||
issuesList.forEach(issue => {
|
||||
if (!issues[issue.id]) {
|
||||
issues[issue.id] = {
|
||||
id: issue.id,
|
||||
title: issue.title,
|
||||
status: "registered",
|
||||
solution_id: null,
|
||||
planned_at: null,
|
||||
executed_at: null,
|
||||
error: null
|
||||
};
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Note: Could not load issues from ccw issue list");
|
||||
// 使用来自参数的 issues,或者空列表
|
||||
}
|
||||
|
||||
// 3. 显示当前状态
|
||||
const totalIssues = Object.keys(issues).length;
|
||||
const registeredCount = Object.values(issues).filter(i => i.status === "registered").length;
|
||||
const plannedCount = Object.values(issues).filter(i => i.status === "planned").length;
|
||||
const completedCount = Object.values(issues).filter(i => i.status === "completed").length;
|
||||
|
||||
console.log("\n=== Issue Status ===");
|
||||
console.log(`Total: ${totalIssues} | Registered: ${registeredCount} | Planned: ${plannedCount} | Completed: ${completedCount}`);
|
||||
|
||||
if (totalIssues === 0) {
|
||||
console.log("\nNo issues found. Please create issues first using 'ccw issue init'");
|
||||
return {
|
||||
stateUpdates: {
|
||||
context: {
|
||||
...state.context,
|
||||
total_issues: 0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 4. 显示详细列表
|
||||
console.log("\n=== Issue Details ===");
|
||||
Object.values(issues).forEach((issue, index) => {
|
||||
const status = issue.status === "completed" ? "✓" : issue.status === "planned" ? "→" : "○";
|
||||
console.log(`${status} [${index + 1}] ${issue.id}: ${issue.title} (${issue.status})`);
|
||||
});
|
||||
|
||||
// 5. 询问用户下一步
|
||||
const issueIds = Object.keys(issues);
|
||||
const pendingIds = issueIds.filter(id => issues[id].status === "registered");
|
||||
|
||||
if (pendingIds.length === 0) {
|
||||
console.log("\nNo unplanned issues. Ready to execute planned solutions.");
|
||||
return {
|
||||
stateUpdates: {
|
||||
context: {
|
||||
...state.context,
|
||||
total_issues: totalIssues
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 6. 显示选项
|
||||
console.log("\nNext action:");
|
||||
console.log("- Enter 'p' to PLAN selected issues");
|
||||
console.log("- Enter 'x' to EXECUTE planned solutions");
|
||||
console.log("- Enter 'a' to plan ALL pending issues");
|
||||
console.log("- Enter 'q' to QUIT");
|
||||
|
||||
const response = await AskUserQuestion({
|
||||
questions: [{
|
||||
question: "Select issues to plan (comma-separated numbers, or 'all'):",
|
||||
header: "Selection",
|
||||
multiSelect: false,
|
||||
options: pendingIds.slice(0, 4).map(id => ({
|
||||
label: `${issues[id].id}: ${issues[id].title}`,
|
||||
description: `Current status: ${issues[id].status}`
|
||||
}))
|
||||
}]
|
||||
});
|
||||
|
||||
// 7. 更新 issues 状态为 "planning"
|
||||
const selectedIds = [];
|
||||
if (response.Selection === "all") {
|
||||
selectedIds.push(...pendingIds);
|
||||
} else {
|
||||
// 解析用户选择
|
||||
selectedIds.push(response.Selection);
|
||||
}
|
||||
|
||||
selectedIds.forEach(issueId => {
|
||||
if (issues[issueId]) {
|
||||
issues[issueId].status = "planning";
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
stateUpdates: {
|
||||
issues: issues,
|
||||
context: {
|
||||
...state.context,
|
||||
total_issues: totalIssues
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## State Updates
|
||||
|
||||
```javascript
|
||||
return {
|
||||
stateUpdates: {
|
||||
issues: issues,
|
||||
context: {
|
||||
total_issues: Object.keys(issues).length,
|
||||
registered_count: registeredCount,
|
||||
planned_count: plannedCount,
|
||||
completed_count: completedCount
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error Type | Recovery |
|
||||
|------------|----------|
|
||||
| Issues 加载失败 | 使用空列表继续 |
|
||||
| 用户输入无效 | 要求重新选择 |
|
||||
| 列表显示异常 | 使用 JSON 格式输出 |
|
||||
|
||||
## Next Actions (Hints)
|
||||
|
||||
- 有 "planning" issues:执行 action-plan
|
||||
- 无 pending issues:执行 action-execute
|
||||
- 用户取消:中止
|
||||
@@ -1,170 +0,0 @@
|
||||
# Action: Plan Solutions
|
||||
|
||||
为选中的 issues 生成执行方案。
|
||||
|
||||
## Purpose
|
||||
|
||||
使用 subagent 分析 issues 并生成解决方案,支持多解决方案选择和自动绑定。
|
||||
|
||||
## Preconditions
|
||||
|
||||
- [ ] `state.status === "running"`
|
||||
- [ ] `issues with status === "planning"` exist
|
||||
|
||||
## Execution
|
||||
|
||||
```javascript
|
||||
async function execute(state) {
|
||||
const workDir = state.work_dir;
|
||||
const issues = state.issues || {};
|
||||
|
||||
// 1. 识别需要规划的 issues
|
||||
const planningIssues = Object.values(issues).filter(i => i.status === "planning");
|
||||
|
||||
if (planningIssues.length === 0) {
|
||||
console.log("No issues to plan");
|
||||
return { stateUpdates: { issues } };
|
||||
}
|
||||
|
||||
console.log(`\n=== Planning ${planningIssues.length} Issues ===`);
|
||||
|
||||
// 2. 为每个 issue 生成规划 subagent
|
||||
const planningAgents = planningIssues.map(issue => ({
|
||||
issue_id: issue.id,
|
||||
issue_title: issue.title,
|
||||
prompt: `
|
||||
## TASK ASSIGNMENT
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
1. **Read role definition**: ~/.codex/agents/issue-plan-agent.md (MUST read first)
|
||||
2. Read: .workflow/project-tech.json
|
||||
3. Read: .workflow/project-guidelines.json
|
||||
4. Read schema: ~/.claude/workflows/cli-templates/schemas/solution-schema.json
|
||||
|
||||
---
|
||||
|
||||
Goal: Plan solution for issue "${issue.id}: ${issue.title}"
|
||||
|
||||
Scope:
|
||||
- CAN DO: Explore codebase, design solutions, create tasks
|
||||
- CANNOT DO: Execute solutions, modify production code
|
||||
- Directory: ${process.cwd()}
|
||||
|
||||
Task Description:
|
||||
${issue.title}
|
||||
|
||||
Deliverables:
|
||||
- Create ONE primary solution
|
||||
- Write to: ${workDir}/solutions/${issue.id}-plan.json
|
||||
- Format: JSON following solution-schema.json
|
||||
|
||||
Quality bar:
|
||||
- Tasks have quantified acceptance.criteria
|
||||
- Each task includes test.commands
|
||||
- Solution follows schema exactly
|
||||
|
||||
Return: JSON with solution_id, task_count, status
|
||||
`
|
||||
}));
|
||||
|
||||
// 3. 执行规划(串行执行避免竞争)
|
||||
for (const agent of planningAgents) {
|
||||
console.log(`\n→ Planning: ${agent.issue_id}`);
|
||||
|
||||
try {
|
||||
// 对于 Codex,这里应该使用 spawn_agent
|
||||
// 对于 Claude Code Task,使用 Task()
|
||||
|
||||
// 模拟 Task 调用 (实际应该是 spawn_agent 对于 Codex)
|
||||
const result = await Task({
|
||||
subagent_type: "universal-executor",
|
||||
run_in_background: false,
|
||||
description: `Plan solution for ${agent.issue_id}`,
|
||||
prompt: agent.prompt
|
||||
});
|
||||
|
||||
// 解析结果
|
||||
let planResult;
|
||||
try {
|
||||
planResult = typeof result === "string" ? JSON.parse(result) : result;
|
||||
} catch {
|
||||
planResult = { status: "executed", solution_id: `SOL-${agent.issue_id}-1` };
|
||||
}
|
||||
|
||||
// 更新 issue 状态
|
||||
issues[agent.issue_id].status = "planned";
|
||||
issues[agent.issue_id].solution_id = planResult.solution_id || `SOL-${agent.issue_id}-1`;
|
||||
issues[agent.issue_id].planned_at = new Date().toISOString();
|
||||
|
||||
console.log(`✓ ${agent.issue_id} → ${issues[agent.issue_id].solution_id}`);
|
||||
|
||||
// 绑定解决方案
|
||||
try {
|
||||
Bash(`ccw issue bind ${agent.issue_id} ${issues[agent.issue_id].solution_id}`);
|
||||
} catch (error) {
|
||||
console.log(`Note: Could not bind solution (${error.message})`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`✗ Planning failed for ${agent.issue_id}: ${error.message}`);
|
||||
issues[agent.issue_id].status = "registered"; // 回退
|
||||
issues[agent.issue_id].error = error.message;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 更新 issue 状态到 ccw
|
||||
try {
|
||||
Bash(`ccw issue update --from-planning`);
|
||||
} catch {
|
||||
console.log("Note: Could not update issue status");
|
||||
}
|
||||
|
||||
return {
|
||||
stateUpdates: {
|
||||
issues: issues,
|
||||
completed_actions: [...state.completed_actions, "action-plan"]
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## State Updates
|
||||
|
||||
```javascript
|
||||
return {
|
||||
stateUpdates: {
|
||||
issues: {
|
||||
[issue.id]: {
|
||||
...issue,
|
||||
status: "planned",
|
||||
solution_id: solutionId,
|
||||
planned_at: timestamp
|
||||
}
|
||||
},
|
||||
queue: [
|
||||
...state.queue,
|
||||
{
|
||||
item_id: `S-${index}`,
|
||||
issue_id: issue.id,
|
||||
solution_id: solutionId,
|
||||
status: "pending"
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error Type | Recovery |
|
||||
|------------|----------|
|
||||
| Subagent 超时 | 标记为失败,继续下一个 |
|
||||
| 无效解决方案 | 回退到 registered 状态 |
|
||||
| 绑定失败 | 记录警告,但继续 |
|
||||
| 文件写入失败 | 重试 3 次 |
|
||||
|
||||
## Next Actions (Hints)
|
||||
|
||||
- 所有 issues 规划完成:执行 action-execute
|
||||
- 部分失败:用户选择是否继续或重试
|
||||
- 全部失败:返回 action-list 重新选择
|
||||
Reference in New Issue
Block a user