You've already forked agentic-coding-workflow
136 lines
4.6 KiB
Go
136 lines
4.6 KiB
Go
// planner.go 是纯函数(无 I/O)的阶段规划逻辑:阶段→agent-kind 解析、阶段→prompt
|
|
// 组装、网关结果→下一阶段。可独立单测(模仿 acp.ResolveBranch 的取舍)。
|
|
package orchestrator
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/yan1h/agent-coding-workflow/internal/infra/errs"
|
|
"github.com/yan1h/agent-coding-workflow/internal/project"
|
|
)
|
|
|
|
// phaseOrder 是 6 阶段的线性推进顺序,对应 project.AllPhases。
|
|
var phaseOrder = project.AllPhases
|
|
|
|
// NextPhase 根据当前阶段与网关结果决定下一阶段。
|
|
//
|
|
// - 网关未通过(!g.Pass)→ 回环本阶段(next==current,terminal=false),让 step
|
|
// 在同阶段重试。
|
|
// - 网关通过且 current==done → 终态(terminal=true),run 成功。
|
|
// - 网关通过且 current!=done → 线性推进到下一阶段。
|
|
//
|
|
// current 非法(不在枚举内)时视为终态,避免无限循环。
|
|
func NextPhase(current project.Phase, g GateResult) (next project.Phase, terminal bool) {
|
|
if !g.Pass {
|
|
return current, false
|
|
}
|
|
if current == project.PhaseDone {
|
|
return project.PhaseDone, true
|
|
}
|
|
for i, p := range phaseOrder {
|
|
if p == current {
|
|
if i+1 >= len(phaseOrder) {
|
|
// current 已是最后一个阶段(done)——由上面的分支处理;兜底终止。
|
|
return project.PhaseDone, true
|
|
}
|
|
// 推进到下一阶段。即便下一阶段是 done,也非终态:done 仍要跑一个 step,
|
|
// 只有 done 阶段网关通过后(current==done 分支)run 才成功。
|
|
return phaseOrder[i+1], false
|
|
}
|
|
}
|
|
// 未知阶段:保守终止。
|
|
return current, true
|
|
}
|
|
|
|
// PhaseAgentKind 解析某阶段应使用的 agent-kind:优先 run.config 覆盖,否则退回
|
|
// defaults(app 层从配置注入的"阶段→默认 agent-kind"映射)。两者皆缺则报错。
|
|
func PhaseAgentKind(cfg RunConfig, phase project.Phase, defaults map[project.Phase]uuid.UUID) (uuid.UUID, error) {
|
|
if cfg.PhaseAgentKinds != nil {
|
|
if id, ok := cfg.PhaseAgentKinds[phase]; ok && id != uuid.Nil {
|
|
return id, nil
|
|
}
|
|
}
|
|
if defaults != nil {
|
|
if id, ok := defaults[phase]; ok && id != uuid.Nil {
|
|
return id, nil
|
|
}
|
|
}
|
|
return uuid.Nil, errs.New(errs.CodeInvalidInput,
|
|
fmt.Sprintf("no agent kind configured for phase %q", phase))
|
|
}
|
|
|
|
// BuildPrompt 组装某阶段发给 agent 的 prompt。config 提供覆盖模板时优先用覆盖
|
|
// (仍前置需求/产物上下文);否则用内置的 per-phase 默认指令。纯字符串拼接,无 I/O。
|
|
func BuildPrompt(phase project.Phase, req *project.Requirement, artifacts []*project.Artifact, override string) string {
|
|
var b strings.Builder
|
|
|
|
if req != nil {
|
|
fmt.Fprintf(&b, "Requirement #%d: %s\n", req.Number, req.Title)
|
|
if strings.TrimSpace(req.Description) != "" {
|
|
fmt.Fprintf(&b, "\nDescription:\n%s\n", strings.TrimSpace(req.Description))
|
|
}
|
|
}
|
|
|
|
if len(artifacts) > 0 {
|
|
b.WriteString("\nPrior artifacts:\n")
|
|
for _, a := range artifacts {
|
|
if a == nil {
|
|
continue
|
|
}
|
|
fmt.Fprintf(&b, "- [%s v%d] %s\n", a.Phase, a.Version, summarizeArtifact(a))
|
|
}
|
|
}
|
|
|
|
b.WriteString("\nCurrent phase: ")
|
|
b.WriteString(string(phase))
|
|
b.WriteString("\n\n")
|
|
|
|
if strings.TrimSpace(override) != "" {
|
|
b.WriteString(strings.TrimSpace(override))
|
|
b.WriteString("\n")
|
|
return b.String()
|
|
}
|
|
|
|
b.WriteString(defaultPhaseInstruction(phase))
|
|
b.WriteString("\n")
|
|
return b.String()
|
|
}
|
|
|
|
// summarizeArtifact 取产物内容首行(截断)作为简介,避免 prompt 过长。
|
|
func summarizeArtifact(a *project.Artifact) string {
|
|
content := strings.TrimSpace(a.Content)
|
|
if content == "" {
|
|
return "(empty)"
|
|
}
|
|
if idx := strings.IndexByte(content, '\n'); idx >= 0 {
|
|
content = content[:idx]
|
|
}
|
|
if len(content) > 120 {
|
|
content = content[:120] + "..."
|
|
}
|
|
return content
|
|
}
|
|
|
|
// defaultPhaseInstruction 是每个阶段的内置默认指令。
|
|
func defaultPhaseInstruction(phase project.Phase) string {
|
|
switch phase {
|
|
case project.PhasePlanning:
|
|
return "Produce a clear plan for this requirement and record it as a planning artifact."
|
|
case project.PhasePrototyping:
|
|
return "Build a prototype validating the approach and record a prototyping artifact."
|
|
case project.PhaseAuditing:
|
|
return "Audit the plan/prototype for risks and gaps; record an auditing artifact."
|
|
case project.PhaseImplementing:
|
|
return "Implement the requirement on this worktree; commit your changes."
|
|
case project.PhaseReviewing:
|
|
return "Review the implementation, fix issues, and record a reviewing artifact."
|
|
case project.PhaseDone:
|
|
return "Summarize the completed work; no further action is required."
|
|
default:
|
|
return "Proceed with the current phase."
|
|
}
|
|
}
|