Wire the knowledge file adapter and a runtime path selector

main constructs the FileSource (default via ANTIDRIFT_KNOWLEDGE_FILE)
and injects it. Adds POST /knowledge/path to repoint the profile file at
runtime, and web tests asserting the planning payload carries the
knowledge object (status + path, never the text) and that path selection
reloads.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-01 08:06:10 -04:00
parent 8bd37ed46d
commit b1b807590c
3 changed files with 83 additions and 1 deletions
+56 -1
View File
@@ -12,6 +12,7 @@ import (
"antidrift/internal/ai"
"antidrift/internal/domain"
"antidrift/internal/evidence"
"antidrift/internal/knowledge"
"antidrift/internal/session"
"antidrift/internal/tasks"
@@ -108,7 +109,7 @@ type stubCoach struct {
err error
}
func (s stubCoach) Coach(ctx context.Context, intent string) (ai.Proposal, error) {
func (s stubCoach) Coach(ctx context.Context, intent, grounding string) (ai.Proposal, error) {
return s.prop, s.err
}
@@ -281,3 +282,57 @@ func TestCommitmentCarriesAllowedClassesAndRoutesWork(t *testing.T) {
t.Fatalf("refocus while Active: want 200, got %d", w.Code)
}
}
type stubSource struct {
profile knowledge.Profile
}
func (s stubSource) Load(ctx context.Context, path string) (knowledge.Profile, error) {
if path != "" {
return knowledge.Profile{Text: "from " + path, Path: path}, nil
}
return s.profile, nil
}
func TestPlanningStatePayloadCarriesKnowledge(t *testing.T) {
s := newTestServer(t)
s.ctrl.SetKnowledge(stubSource{profile: knowledge.Profile{Text: "I value small diffs.", Path: "/home/u/.antidrift/knowledge.md"}})
r := s.Router()
if w := post(t, r, "/planning", ""); w.Code != http.StatusOK {
t.Fatalf("/planning code %d", w.Code)
}
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
if kv := s.ctrl.State().Knowledge; kv != nil && kv.Status == "ready" {
break
}
time.Sleep(5 * time.Millisecond)
}
js := s.stateJSON()
if !strings.Contains(js, `"knowledge"`) || !strings.Contains(js, `"status":"ready"`) {
t.Fatalf("planning payload missing knowledge object: %s", js)
}
if strings.Contains(js, "small diffs") {
t.Fatalf("profile text must NOT cross the wire: %s", js)
}
}
func TestKnowledgePathSelectionReloads(t *testing.T) {
s := newTestServer(t)
s.ctrl.SetKnowledge(stubSource{profile: knowledge.Profile{Path: "/default"}})
r := s.Router()
if w := post(t, r, "/planning", ""); w.Code != http.StatusOK {
t.Fatalf("/planning code %d", w.Code)
}
if w := post(t, r, "/knowledge/path", `{"path":"/custom/profile.md"}`); w.Code != http.StatusOK {
t.Fatalf("/knowledge/path code %d body %s", w.Code, w.Body.String())
}
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
if kv := s.ctrl.State().Knowledge; kv != nil && kv.Path == "/custom/profile.md" {
return
}
time.Sleep(5 * time.Millisecond)
}
t.Fatalf("path selection did not reload: %+v", s.ctrl.State().Knowledge)
}