name: skill-planner description: 从研究发现创建分阶段实现计划。在任务需要实现计划时调用。 allowed-tools: Task, Bash, Edit, Read, Write, Read(/tmp/.json), Bash(rm:)
原始上下文(现由子代理加载):
- .opencode/context/core/formats/plan-format.md
- .opencode/context/core/workflows/task-breakdown.md
原始工具(现由子代理使用):
- Read, Write, Edit, Glob, Grep
规划器技能
薄包装器,将计划创建委托给 planner-agent 子代理。
重要:此技能实现技能内部后置飞行模式。在子代理返回后,此技能处理所有后置操作(状态更新、工件链接、git提交)然后返回。这消除了技能返回和编排器之间的“继续”提示问题。
上下文引用
引用(不要急切加载):
- 路径:
.opencode/context/core/formats/return-metadata-file.md- 元数据文件架构 - 路径:
.opencode/context/core/patterns/postflight-control.md- 标记文件协议 - 路径:
.opencode/context/core/patterns/file-metadata-exchange.md- 文件 I/O 助手 - 路径:
.opencode/context/core/patterns/jq-escaping-workarounds.md- jq 转义模式(问题 #1132)
注意:此技能是一个带有内部后置飞行的薄包装器。上下文由委托代理加载。
触发条件
此技能在以下情况激活:
- 任务状态允许规划(未开始、已研究)
- 调用 /plan 命令
- 需要正式化实现方法
执行流程
阶段 1:输入验证
验证必需输入:
task_number- 必须提供并存在于 state.json 中- 任务状态必须允许规划
# 查找任务
task_data=$(jq -r --argjson num "$task_number" \
'.active_projects[] | select(.project_number == $num)' \
specs/state.json)
# 验证存在
if [ -z "$task_data" ]; then
return error "任务 $task_number 未找到"
fi
# 提取字段
language=$(echo "$task_data" | jq -r '.language // "general"')
status=$(echo "$task_data" | jq -r '.status')
project_name=$(echo "$task_data" | jq -r '.project_name')
description=$(echo "$task_data" | jq -r '.description // ""')
# 验证状态
if [ "$status" = "completed" ]; then
return error "任务已完成"
fi
阶段 2:预飞行状态更新
在调用子代理前更新任务状态为“planning”。
更新 state.json:
jq --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--arg status "planning" \
--arg sid "$session_id" \
'(.active_projects[] | select(.project_number == '$task_number')) |= . + {
status: $status,
last_updated: $ts,
session_id: $sid
}' specs/state.json > /tmp/state.json && mv /tmp/state.json specs/state.json
更新 TODO.md:使用 Edit 工具将状态标记从 [RESEARCHED] 或 [NOT STARTED] 改为 [PLANNING]。
阶段 3:创建后置飞行标记
创建标记文件以防止过早终止:
# 确保任务目录存在
padded_num=$(printf "%03d" "$task_number")
mkdir -p "specs/${padded_num}_${project_name}"
cat > "specs/${padded_num}_${project_name}/.postflight-pending" << EOF
{
"session_id": "${session_id}",
"skill": "skill-planner",
"task_number": ${task_number},
"operation": "plan",
"reason": "后置飞行待处理:状态更新、工件链接、git 提交",
"created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"stop_hook_active": false
}
EOF
阶段 4:准备委托上下文
为子代理准备委托上下文:
{
"session_id": "sess_{timestamp}_{random}",
"delegation_depth": 1,
"delegation_path": ["orchestrator", "plan", "skill-planner"],
"timeout": 1800,
"task_context": {
"task_number": N,
"task_name": "{project_name}",
"description": "{description}",
"language": "{language}"
},
"research_path": "{如果存在,研究报告的路径}",
"metadata_file_path": "specs/{NNN}_{SLUG}/.return-meta.json"
}
阶段 5:调用子代理
关键:必须使用 Task 工具来生成子代理。
必需工具调用:
工具:Task(非 Skill)
参数:
- subagent_type: "planner-agent"
- prompt: [包括 task_context, delegation_context, research_path, metadata_file_path]
- description: "为任务 {N} 执行规划"
不要使用 Skill(planner-agent) - 这会失败。
子代理将:
- 加载规划上下文文件
- 分析任务需求和研究
- 分解为逻辑阶段
- 识别风险和缓解措施
- 在
specs/{NNN}_{SLUG}/plans/中创建计划 - 将元数据写入
specs/{NNN}_{SLUG}/.return-meta.json - 返回简要文本摘要(非 JSON)
阶段 6:解析子代理返回(读取元数据文件)
子代理返回后,读取元数据文件:
metadata_file="specs/${padded_num}_${project_name}/.return-meta.json"
if [ -f "$metadata_file" ] && jq empty "$metadata_file" 2>/dev/null; then
status=$(jq -r '.status' "$metadata_file")
artifact_path=$(jq -r '.artifacts[0].path // ""' "$metadata_file")
artifact_type=$(jq -r '.artifacts[0].type // ""' "$metadata_file")
artifact_summary=$(jq -r '.artifacts[0].summary // ""' "$metadata_file")
else
echo "错误:无效或缺失元数据文件"
status="failed"
fi
阶段 7:更新任务状态(后置飞行)
如果状态是“planned”,更新 state.json 和 TODO.md:
更新 state.json:
jq --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--arg status "planned" \
'(.active_projects[] | select(.project_number == '$task_number')) |= . + {
status: $status,
last_updated: $ts,
planned: $ts
}' specs/state.json > /tmp/state.json && mv /tmp/state.json specs/state.json
更新 TODO.md:使用 Edit 工具将状态标记从 [PLANNING] 改为 [PLANNED]。
部分/失败时:保持状态为“planning”以便恢复。
阶段 8:链接工件
添加工件到 state.json 并附带摘要。
重要:使用两步 jq 模式以避免问题 #1132 转义错误。参考 jq-escaping-workarounds.md。
if [ -n "$artifact_path" ]; then
# 步骤 1:过滤掉现有计划工件(使用“| not”模式以避免 != 转义 - 问题 #1132)
jq '(.active_projects[] | select(.project_number == '$task_number')).artifacts =
[(.active_projects[] | select(.project_number == '$task_number')).artifacts // [] | .[] | select(.type == "plan" | not)]' \
specs/state.json > /tmp/state.json && mv /tmp/state.json specs/state.json
# 步骤 2:添加新计划工件
jq --arg path "$artifact_path" \
--arg type "$artifact_type" \
--arg summary "$artifact_summary" \
'(.active_projects[] | select(.project_number == '$task_number')).artifacts += [{"path": $path, "type": $type, "summary": $summary}]' \
specs/state.json > /tmp/state.json && mv /tmp/state.json specs/state.json
fi
更新 TODO.md:添加工件链接。
为 TODO.md 剥离 specs/ 前缀(TODO.md 在 specs/ 内): todo_link_path="${artifact_path#specs/}"
- **计划**:[implementation-{NNN}.md]({todo_link_path})
阶段 9:Git 提交
提交更改并附带会话 ID:
git add -A
git commit -m "任务 ${task_number}:创建实现计划
会话:${session_id}
共同作者:Claude Opus 4.5 <noreply@anthropic.com>"
阶段 10:清理
移除标记和元数据文件:
rm -f "specs/${padded_num}_${project_name}/.postflight-pending"
rm -f "specs/${padded_num}_${project_name}/.postflight-loop-guard"
rm -f "specs/${padded_num}_${project_name}/.return-meta.json"
阶段 11:返回简要摘要
返回简要文本摘要(非 JSON)。示例:
为任务 {N} 创建计划:
- 定义了 {phase_count} 个阶段,估计 {estimated_hours} 小时
- 关键阶段:{阶段名称}
- 计划创建于 specs/{NNN}_{SLUG}/plans/implementation-{NNN}.md
- 状态更新为 [PLANNED]
- 更改已提交
错误处理
输入验证错误
如果任务未找到或状态无效,立即返回错误消息。
元数据文件缺失
如果子代理未写入元数据文件:
- 保持状态为“planning”
- 不清理后置飞行标记
- 向用户报告错误
Git 提交失败
非阻塞:记录失败但继续成功响应。
jq 解析失败
如果 jq 命令失败并出现 INVALID_CHARACTER 或语法错误(问题 #1132):
- 记录到 errors.json:
jq --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--arg sid "$session_id" \
--arg msg "后置飞行工件链接中的 jq 解析错误" \
--argjson task "$task_number" \
'.errors += [{
"id": ("err_" + ($ts | gsub("[^0-9]"; ""))),
"timestamp": $ts,
"type": "jq_parse_failure",
"severity": "medium",
"message": $msg,
"context": {"session_id": $sid, "command": "/plan", "task": $task, "checkpoint": "GATE_OUT"},
"recovery": {"suggested_action": "使用来自 jq-escaping-workarounds.md 的两步 jq 模式", "auto_recoverable": true},
"fix_status": "unfixed"
}]' specs/errors.json > /tmp/errors.json && mv /tmp/errors.json specs/errors.json
- 使用两步模式重试(已在阶段 8 中实现)
子代理超时
如果子代理超时(默认 1800 秒),返回部分状态。 保持状态为“planning”以便恢复。
返回格式
此技能返回简要文本摘要(非 JSON)。JSON 元数据被写入文件并在内部处理。
成功返回示例:
为任务 414 创建计划:
- 定义了 5 个阶段,估计 2.5 小时
- 覆盖:代理结构、执行流程、错误处理、示例、验证
- 计划创建于 specs/414_create_planner_agent/plans/implementation-001.md
- 状态更新为 [PLANNED]
- 更改已提交,会话 sess_1736700000_abc123
部分返回示例:
为任务 414 部分创建计划:
- 在超时前定义了 3 个阶段,共 5 个
- 部分计划保存于 specs/414_create_planner_agent/plans/implementation-001.md
- 状态保持 [PLANNING] - 运行 /plan 414 以完成