From 2690fdd513e997949d3d7f369bdf3c6e5b8e2a7c Mon Sep 17 00:00:00 2001 From: Felix Martin Date: Fri, 5 Jun 2026 21:28:21 -0400 Subject: [PATCH] refactor(offscreen): assemble frame via shared internal/frame --- internal/mode/offscreen/collect.go | 144 ++++------------------------- 1 file changed, 19 insertions(+), 125 deletions(-) diff --git a/internal/mode/offscreen/collect.go b/internal/mode/offscreen/collect.go index 2e4fcec..78d0bcf 100644 --- a/internal/mode/offscreen/collect.go +++ b/internal/mode/offscreen/collect.go @@ -1,134 +1,41 @@ +// internal/mode/offscreen/collect.go package offscreen import ( "context" "fmt" - "os" - "path/filepath" "strings" "time" - "unicode/utf8" + + "keel/internal/frame" ) -// goalsPath is the standing-goals note loaded for the off-screen brief. -const goalsPath = "~/owc/goals-2026.md" - -// maxBriefBytes caps the assembled brief so the propose prompt stays bounded. -const maxBriefBytes = 8 * 1024 - -// bugGlob matches the life-domain ("life-bug") notes under ~/owc/resources. -const bugGlob = "bug-*.md" - const ( recentProposals = 5 // how many recent proposals to show outcomeScan = 50 // how many outcome events to scan for correlation recentWindow = 7 * 24 * time.Hour ) -// assembleBrief builds the off-screen prompt context from today's tasks, the -// standing goals note, and the life-domain notes. It is best-effort: it never -// panics and never returns an error, and it tolerates nil ports and missing -// files (degrading to a short, mostly-empty brief). +// assembleBrief builds the off-screen prompt context: the shared frame brief +// (today's tasks + ~/owc goals and life-domains) plus off-screen's own recent +// proposal history. Best-effort: never panics, never errors, tolerates nil +// ports and missing files. func (m *Mode) assembleBrief() string { + base := frame.Assemble(context.Background(), m.know, m.tasks) + + hist := m.briefHistory() + if hist == "" { + return base + } + var b strings.Builder - - b.WriteString("## Today's tasks\n") - b.WriteString(m.briefTasks()) - b.WriteString("\n") - - if hist := m.briefHistory(); hist != "" { - b.WriteString("## Recently proposed\n") - b.WriteString(hist) + b.WriteString(base) + if !strings.HasSuffix(base, "\n") { b.WriteString("\n") } - - if goals := m.briefGoals(); goals != "" { - b.WriteString("## Goals\n") - b.WriteString(goals) - b.WriteString("\n\n") - } - - if domains := m.briefLifeDomains(); domains != "" { - b.WriteString("## Life domains\n") - b.WriteString(domains) - b.WriteString("\n") - } - - return truncateBrief(b.String(), maxBriefBytes) -} - -// briefTasks lists today's task titles, or "(none)" when the port is absent, -// errors, or returns nothing. -func (m *Mode) briefTasks() string { - if m.tasks == nil { - return "(none)\n" - } - list, err := m.tasks.Today(context.Background()) - if err != nil || len(list) == 0 { - return "(none)\n" - } - var b strings.Builder - for _, t := range list { - title := strings.TrimSpace(t.Title) - if title == "" { - continue - } - b.WriteString("- ") - b.WriteString(title) - b.WriteString("\n") - } - if b.Len() == 0 { - return "(none)\n" - } - return b.String() -} - -// briefGoals returns the goals note text, or "" when the port is absent or the -// file is missing/empty. -func (m *Mode) briefGoals() string { - if m.know == nil { - return "" - } - p, err := m.know.Load(context.Background(), goalsPath) - if err != nil { - return "" - } - return strings.TrimSpace(p.Text) -} - -// briefLifeDomains loads every ~/owc/resources/bug-*.md note via the knowledge -// port (so reads honor its truncation) and concatenates their text under one -// section. Returns "" when the port is absent or no notes are readable. File -// access stays behind the port; when the port is nil it is skipped entirely. -func (m *Mode) briefLifeDomains() string { - if m.know == nil { - return "" - } - home, err := os.UserHomeDir() - if err != nil { - return "" - } - matches, err := filepath.Glob(filepath.Join(home, "owc", "resources", bugGlob)) - if err != nil || len(matches) == 0 { - return "" - } - var b strings.Builder - for _, path := range matches { - p, err := m.know.Load(context.Background(), path) - if err != nil { - continue - } - text := strings.TrimSpace(p.Text) - if text == "" { - continue - } - b.WriteString("### ") - b.WriteString(filepath.Base(path)) - b.WriteString("\n") - b.WriteString(text) - b.WriteString("\n\n") - } - return strings.TrimRight(b.String(), "\n") + b.WriteString("\n## Recently proposed\n") + b.WriteString(hist) + return frame.Truncate(b.String(), frame.MaxBriefBytes) } // briefHistory renders recent proposals with their outcome, newest first and @@ -195,16 +102,3 @@ func relativeAge(d time.Duration) string { return fmt.Sprintf("%dd ago", int(d.Hours()/24)) } } - -// truncateBrief clips s to at most max bytes with a marker, keeping the prompt -// bounded. It backs up to a rune boundary so it never splits a multibyte rune. -func truncateBrief(s string, max int) string { - if len(s) <= max { - return s - } - cut := max - for cut > 0 && !utf8.RuneStart(s[cut]) { - cut-- - } - return s[:cut] + "\n…(truncated)" -}