Skip to content

Commit a940ea2

Browse files
halfaipgclaude
andcommitted
feat: add "Start building" escape option to planning questions
Every planning question now shows a final option to skip remaining questions and start building immediately. If the user has already answered some questions, generates a plan from what's collected. If no answers yet, goes straight to the agent with the raw prompt. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3ed2ed3 commit a940ea2

3 files changed

Lines changed: 73 additions & 0 deletions

File tree

chat.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,42 @@ func (m *chatModel) handlePlanAnswer(input string) tea.Cmd {
564564
return nil
565565
}
566566

567+
// "Start building" — skip remaining questions, go to plan generation
568+
if answer == AnswerStartBuilding {
569+
m.segments = append(m.segments, segment{
570+
kind: "user",
571+
text: " " + styleUserLabel.Render(" → ") + "Start building\n",
572+
})
573+
m.planState.CurrentQ = nil
574+
m.planState.Done = true
575+
m.rebuildViewport()
576+
577+
// If we have any Q&A, generate a plan; otherwise go straight to agent
578+
if len(m.planState.QAHistory) > 0 {
579+
m.notify.Push(Notification{Type: NotifyProgress, Text: "Generating plan..."})
580+
glue := m.glue
581+
ps := m.planState
582+
return func() tea.Msg {
583+
plan := glue.GeneratePlan(ps.OriginalPrompt, ps.QAHistory)
584+
return planGeneratedMsg{plan: plan}
585+
}
586+
}
587+
588+
// No Q&A at all — skip plan, go straight to agent
589+
m.segments = append(m.segments, segment{
590+
kind: "text",
591+
text: styleMuted.Render(" Skipping plan. Starting agent...\n\n"),
592+
})
593+
m.input.Placeholder = "describe what you want to build..."
594+
original := m.planState.OriginalPrompt
595+
m.planState = nil
596+
m.startAgent(original)
597+
return tea.Batch(
598+
m.waitForEvent(),
599+
tea.Tick(10*time.Second, func(t time.Time) tea.Msg { return narrateTickMsg{} }),
600+
)
601+
}
602+
567603
// Show the answer
568604
m.segments = append(m.segments, segment{
569605
kind: "user",

plan.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ func FormatQuestion(q *PlanQuestion, questionNum int) string {
261261
sb.WriteString(label + "\n")
262262
}
263263

264+
sb.WriteString(fmt.Sprintf("\n [%d] Start building — skip remaining questions\n", len(q.Options)+1))
265+
264266
hint := "\n Type a number to select"
265267
if q.Type == "multiselect" {
266268
hint = "\n Type numbers separated by commas (e.g. 1,3)"
@@ -271,13 +273,24 @@ func FormatQuestion(q *PlanQuestion, questionNum int) string {
271273
return sb.String()
272274
}
273275

276+
// AnswerStartBuilding is returned by ParseAnswer when the user picks "Start building".
277+
const AnswerStartBuilding = "__START_BUILDING__"
278+
274279
// ParseAnswer interprets user input as option selection(s) or free text.
280+
// Returns AnswerStartBuilding if the user chose the "Start building" escape option.
275281
func ParseAnswer(input string, q *PlanQuestion) string {
276282
input = strings.TrimSpace(input)
277283
if input == "" {
278284
return input
279285
}
280286

287+
// Check for the "Start building" escape option (last number)
288+
skipIdx := len(q.Options) + 1
289+
var parsed int
290+
if _, err := fmt.Sscanf(input, "%d", &parsed); err == nil && parsed == skipIdx {
291+
return AnswerStartBuilding
292+
}
293+
281294
// Try to parse as number(s)
282295
parts := strings.Split(input, ",")
283296
var selectedLabels []string

plan_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,30 @@ func TestFormatQuestion(t *testing.T) {
104104
if !strings.Contains(formatted, "Stateless") {
105105
t.Error("should contain option description")
106106
}
107+
if !strings.Contains(formatted, "[3] Start building") {
108+
t.Error("should contain Start building escape option")
109+
}
110+
}
111+
112+
func TestParseAnswerStartBuilding(t *testing.T) {
113+
q := &PlanQuestion{
114+
Options: []PlanOption{
115+
{ID: "opt1", Label: "Option A"},
116+
{ID: "opt2", Label: "Option B"},
117+
},
118+
}
119+
120+
// Picking the last number (len+1) should return the sentinel
121+
answer := ParseAnswer("3", q)
122+
if answer != AnswerStartBuilding {
123+
t.Errorf("expected AnswerStartBuilding, got %q", answer)
124+
}
125+
126+
// Regular options still work
127+
answer = ParseAnswer("1", q)
128+
if answer != "Option A" {
129+
t.Errorf("expected 'Option A', got %q", answer)
130+
}
107131
}
108132

109133
func TestFormatQuestionMultiselect(t *testing.T) {

0 commit comments

Comments
 (0)