Extend coach proposal with allowed window classes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -33,12 +33,13 @@ 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": "<one concrete action to start now>", "success_condition": "<observable, verifiable done state>", "timebox_minutes": <integer, typically 15-50>}
|
||||
{"next_action": "<one concrete action to start now>", "success_condition": "<observable, verifiable done state>", "timebox_minutes": <integer, typically 15-50>, "allowed_window_classes": ["<app/window class that is on-task, e.g. code, firefox>"]}
|
||||
|
||||
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.
|
||||
- allowed_window_classes: a short list of window/application class names that count as on-task for this action (lowercase, e.g. "code", "firefox"). May be empty.
|
||||
|
||||
User intent: ` + intent
|
||||
}
|
||||
|
||||
+14
-1
@@ -16,6 +16,7 @@ type Proposal struct {
|
||||
NextAction string
|
||||
SuccessCondition string
|
||||
TimeboxSecs int64
|
||||
AllowedWindowClasses []string
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -64,6 +65,7 @@ type rawProposal struct {
|
||||
NextAction string `json:"next_action"`
|
||||
SuccessCondition string `json:"success_condition"`
|
||||
TimeboxMinutes int64 `json:"timebox_minutes"`
|
||||
AllowedWindowClasses []string `json:"allowed_window_classes"`
|
||||
}
|
||||
|
||||
// parseProposal extracts and validates a Proposal from raw CLI output.
|
||||
@@ -91,5 +93,16 @@ func parseProposal(s string) (Proposal, error) {
|
||||
if na == "" || sc == "" || raw.TimeboxMinutes <= 0 {
|
||||
return Proposal{}, ErrInvalidProposal
|
||||
}
|
||||
return Proposal{NextAction: na, SuccessCondition: sc, TimeboxSecs: raw.TimeboxMinutes * 60}, nil
|
||||
classes := make([]string, 0, len(raw.AllowedWindowClasses))
|
||||
for _, cls := range raw.AllowedWindowClasses {
|
||||
if t := strings.TrimSpace(cls); t != "" {
|
||||
classes = append(classes, t)
|
||||
}
|
||||
}
|
||||
return Proposal{
|
||||
NextAction: na,
|
||||
SuccessCondition: sc,
|
||||
TimeboxSecs: raw.TimeboxMinutes * 60,
|
||||
AllowedWindowClasses: classes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -69,3 +69,23 @@ func TestParseProposal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseProposalReadsAllowedClasses(t *testing.T) {
|
||||
p, err := parseProposal(`{"next_action":"a","success_condition":"b","timebox_minutes":20,"allowed_window_classes":["code"," firefox ",""]}`)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if len(p.AllowedWindowClasses) != 2 || p.AllowedWindowClasses[0] != "code" || p.AllowedWindowClasses[1] != "firefox" {
|
||||
t.Fatalf("classes wrong (blanks should drop, trim applied): %#v", p.AllowedWindowClasses)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseProposalAllowedClassesOptional(t *testing.T) {
|
||||
p, err := parseProposal(`{"next_action":"a","success_condition":"b","timebox_minutes":20}`)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if len(p.AllowedWindowClasses) != 0 {
|
||||
t.Fatalf("absent field should yield empty slice, got %#v", p.AllowedWindowClasses)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user