Enhance spec-driven coding workflow

This commit is contained in:
lpf
2026-03-09 13:24:55 +08:00
parent d1abd73e63
commit 6089f4e7c4
26 changed files with 1388 additions and 717 deletions

View File

@@ -0,0 +1,91 @@
package agent
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestLoadProjectPlanningFilesIncludesSpecDocs(t *testing.T) {
root := t.TempDir()
for name, body := range map[string]string{
"spec.md": "# spec\nscope",
"tasks.md": "# tasks\n- [ ] one",
"checklist.md": "# checklist\n- [ ] verify",
} {
if err := os.WriteFile(filepath.Join(root, name), []byte(body), 0644); err != nil {
t.Fatalf("write %s: %v", name, err)
}
}
cb := &ContextBuilder{workspace: t.TempDir(), cwd: root}
got := cb.LoadProjectPlanningFiles()
if !strings.Contains(got, "spec.md") || !strings.Contains(got, "tasks.md") || !strings.Contains(got, "checklist.md") {
t.Fatalf("expected project planning files in output, got:\n%s", got)
}
}
func TestLoadProjectPlanningFilesTruncatesLargeDocs(t *testing.T) {
root := t.TempDir()
large := strings.Repeat("x", 4500)
if err := os.WriteFile(filepath.Join(root, "spec.md"), []byte(large), 0644); err != nil {
t.Fatalf("write spec.md: %v", err)
}
cb := &ContextBuilder{workspace: t.TempDir(), cwd: root}
got := cb.LoadProjectPlanningFiles()
if !strings.Contains(got, "[TRUNCATED]") {
t.Fatalf("expected truncation marker, got:\n%s", got)
}
}
func TestBuildMessagesOnlyLoadsProjectPlanningForCodingTasks(t *testing.T) {
root := t.TempDir()
if err := os.WriteFile(filepath.Join(root, "spec.md"), []byte("# spec\ncoding plan"), 0644); err != nil {
t.Fatalf("write spec.md: %v", err)
}
cb := NewContextBuilder(t.TempDir(), nil)
cb.cwd = root
coding := cb.BuildMessagesWithMemoryNamespace(nil, "", "请实现一个登录功能", nil, "cli", "direct", "", "main")
if len(coding) == 0 || strings.Contains(coding[0].Content, "Active Project Planning") {
t.Fatalf("did not expect small coding task to include project planning by default, got:\n%s", coding[0].Content)
}
heavyCoding := cb.BuildMessagesWithMemoryNamespace(nil, "", "请实现一个完整登录注册模块,涉及多文件改动并补测试", nil, "cli", "direct", "", "main")
if len(heavyCoding) == 0 || !strings.Contains(heavyCoding[0].Content, "Active Project Planning") {
t.Fatalf("expected substantial coding task to include project planning, got:\n%s", heavyCoding[0].Content)
}
explicitSpec := cb.BuildMessagesWithMemoryNamespace(nil, "", "这个改动不大,但请用 spec coding 流程来做", nil, "cli", "direct", "", "main")
if len(explicitSpec) == 0 || !strings.Contains(explicitSpec[0].Content, "Active Project Planning") {
t.Fatalf("expected explicit spec request to include project planning, got:\n%s", explicitSpec[0].Content)
}
nonCoding := cb.BuildMessagesWithMemoryNamespace(nil, "", "帮我总结一下今天的工作", nil, "cli", "direct", "", "main")
if len(nonCoding) == 0 {
t.Fatalf("expected system message")
}
if strings.Contains(nonCoding[0].Content, "Active Project Planning") {
t.Fatalf("did not expect non-coding task to include project planning, got:\n%s", nonCoding[0].Content)
}
}
func TestShouldUseSpecCodingRequiresExplicitAndNonTrivialCodingIntent(t *testing.T) {
cb := &ContextBuilder{}
cases := []struct {
message string
want bool
}{
{message: "请实现一个完整支付模块,涉及多文件改动并补测试", want: true},
{message: "修一下这个小 bug顺手改一行就行", want: false},
{message: "帮我总结这个接口问题", want: false},
{message: "这个改动不大,但请用 spec coding 流程来做", want: true},
}
for _, tc := range cases {
if got := cb.shouldUseSpecCoding(tc.message); got != tc.want {
t.Fatalf("shouldUseSpecCoding(%q) = %v, want %v", tc.message, got, tc.want)
}
}
}