0f1790c8d5
The ai.Coach interface gains a grounding parameter carrying standing context about the user (the knowledge port). buildPrompt injects an "About the user" block only when grounding is non-empty, so an empty grounding produces a byte-for-byte unchanged prompt. Drift judge and nudge are untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
70 lines
2.2 KiB
Go
70 lines
2.2 KiB
Go
package ai
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type fakeBackend struct {
|
|
out string
|
|
err error
|
|
gotPrompt string
|
|
}
|
|
|
|
func (f *fakeBackend) Run(ctx context.Context, prompt string) (string, error) {
|
|
f.gotPrompt = prompt
|
|
return f.out, f.err
|
|
}
|
|
func (f *fakeBackend) Name() string { return "fake" }
|
|
|
|
func TestServiceCoachSuccess(t *testing.T) {
|
|
fb := &fakeBackend{out: `here you go {"next_action":"Write tests","success_condition":"green","timebox_minutes":30}`}
|
|
svc := NewService(fb)
|
|
p, err := svc.Coach(context.Background(), "write the tests", "")
|
|
if err != nil {
|
|
t.Fatalf("coach: %v", err)
|
|
}
|
|
if p.NextAction != "Write tests" || p.TimeboxSecs != 1800 {
|
|
t.Fatalf("bad proposal: %+v", p)
|
|
}
|
|
if !strings.Contains(fb.gotPrompt, "write the tests") {
|
|
t.Fatalf("prompt should embed the intent, got: %s", fb.gotPrompt)
|
|
}
|
|
}
|
|
|
|
func TestServiceCoachBackendError(t *testing.T) {
|
|
fb := &fakeBackend{err: errors.New("boom")}
|
|
if _, err := NewService(fb).Coach(context.Background(), "x", ""); err == nil {
|
|
t.Fatal("want backend error")
|
|
}
|
|
}
|
|
|
|
func TestServiceCoachUnparseable(t *testing.T) {
|
|
fb := &fakeBackend{out: "I cannot help with that."}
|
|
if _, err := NewService(fb).Coach(context.Background(), "x", ""); !errors.Is(err, ErrNoJSON) {
|
|
t.Fatalf("want ErrNoJSON, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCoachPromptIncludesGrounding(t *testing.T) {
|
|
fb := &fakeBackend{out: `{"next_action":"a","success_condition":"b","timebox_minutes":20}`}
|
|
if _, err := NewService(fb).Coach(context.Background(), "ship it", "I value small diffs."); err != nil {
|
|
t.Fatalf("coach: %v", err)
|
|
}
|
|
if !strings.Contains(fb.gotPrompt, "About the user") || !strings.Contains(fb.gotPrompt, "small diffs") {
|
|
t.Fatalf("prompt missing grounding block: %s", fb.gotPrompt)
|
|
}
|
|
}
|
|
|
|
func TestCoachEmptyGroundingUnchanged(t *testing.T) {
|
|
withFb := &fakeBackend{out: `{"next_action":"a","success_condition":"b","timebox_minutes":20}`}
|
|
_, _ = NewService(withFb).Coach(context.Background(), "ship it", "")
|
|
// The empty-grounding prompt must equal buildPrompt(intent) of the old shape:
|
|
// it must NOT contain the grounding header.
|
|
if strings.Contains(withFb.gotPrompt, "About the user") {
|
|
t.Fatalf("empty grounding leaked a header: %s", withFb.gotPrompt)
|
|
}
|
|
}
|