package ai import "context" // Coach turns a free-text intent into a validated Proposal. type Coach interface { Coach(ctx context.Context, intent string) (Proposal, error) } // Backend is one way to reach an LLM CLI. Adapters differ only in the command // and arguments they run; each returns the model's text answer. type Backend interface { Run(ctx context.Context, prompt string) (string, error) Name() string } // Service implements Coach over any Backend. type Service struct { backend Backend } func NewService(b Backend) *Service { return &Service{backend: b} } func (s *Service) Coach(ctx context.Context, intent string) (Proposal, error) { out, err := s.backend.Run(ctx, buildPrompt(intent)) if err != nil { return Proposal{}, err } return parseProposal(out) } func buildPrompt(intent string) string { return `You are a focus coach. The user gives a rough intent for a work session. Turn it into ONE concrete, actionable commitment. Respond with ONLY a JSON object, no prose and no code fences, exactly this shape: {"next_action": "", "success_condition": "", "timebox_minutes": } Rules: - next_action: a single concrete imperative action, doable now. - success_condition: observable and verifiable; how you'd know it is done. - timebox_minutes: a realistic integer number of minutes for the action. User intent: ` + intent }