From 0487151142c62d7d148d8f1cf015b892dc491deb Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Thu, 19 Mar 2026 09:19:01 -0400 Subject: [PATCH] fix(workflows): add text_mode config for Claude Code remote session compatibility (#1214) - Add workflow.text_mode config option (default: false) that replaces AskUserQuestion TUI menus with plain-text numbered lists - Document --text flag and config-set workflow.text_mode true as the fix for /rc remote sessions where the Claude App cannot forward TUI menu selections - Update discuss-phase.md with text mode parsing and answer_validation fallback documentation - Add text_mode to loadConfig defaults and VALID_CONFIG_KEYS - Add regression tests for config-set and loadConfig --- get-shit-done/bin/lib/config.cjs | 1 + get-shit-done/bin/lib/core.cjs | 2 ++ get-shit-done/workflows/discuss-phase.md | 19 +++++++++++++++++++ get-shit-done/workflows/settings.md | 3 +++ tests/config.test.cjs | 10 ++++++++++ tests/core.test.cjs | 1 + 6 files changed, 36 insertions(+) diff --git a/get-shit-done/bin/lib/config.cjs b/get-shit-done/bin/lib/config.cjs index ef1466f61..e40e0d84d 100644 --- a/get-shit-done/bin/lib/config.cjs +++ b/get-shit-done/bin/lib/config.cjs @@ -16,6 +16,7 @@ const VALID_CONFIG_KEYS = new Set([ 'search_gitignored', 'brave_search', 'workflow.research', 'workflow.plan_check', 'workflow.verifier', 'workflow.nyquist_validation', 'workflow.ui_phase', 'workflow.ui_safety_gate', + 'workflow.text_mode', 'workflow._auto_chain_active', 'git.branching_strategy', 'git.phase_branch_template', 'git.milestone_branch_template', 'planning.commit_docs', 'planning.search_gitignored', diff --git a/get-shit-done/bin/lib/core.cjs b/get-shit-done/bin/lib/core.cjs index 66918b018..453fe6e8a 100644 --- a/get-shit-done/bin/lib/core.cjs +++ b/get-shit-done/bin/lib/core.cjs @@ -64,6 +64,7 @@ function loadConfig(cwd) { nyquist_validation: true, parallelization: true, brave_search: false, + text_mode: false, // when true, use plain-text numbered lists instead of AskUserQuestion menus resolve_model_ids: false, // when true, resolve aliases (opus/sonnet/haiku) to full model IDs context_window: 200000, // default 200k; set to 1000000 for Opus/Sonnet 4.6 1M models }; @@ -108,6 +109,7 @@ function loadConfig(cwd) { nyquist_validation: get('nyquist_validation', { section: 'workflow', field: 'nyquist_validation' }) ?? defaults.nyquist_validation, parallelization, brave_search: get('brave_search') ?? defaults.brave_search, + text_mode: get('text_mode', { section: 'workflow', field: 'text_mode' }) ?? defaults.text_mode, resolve_model_ids: get('resolve_model_ids') ?? defaults.resolve_model_ids, context_window: get('context_window') ?? defaults.context_window, model_overrides: parsed.model_overrides || null, diff --git a/get-shit-done/workflows/discuss-phase.md b/get-shit-done/workflows/discuss-phase.md index 5182afce8..b0da511c3 100644 --- a/get-shit-done/workflows/discuss-phase.md +++ b/get-shit-done/workflows/discuss-phase.md @@ -110,6 +110,18 @@ Phase: "API documentation" 1. Retry the question once with the same parameters 2. If still empty, present the options as a plain-text numbered list and ask the user to type their choice number Never proceed with an empty answer. + +**Text mode (`workflow.text_mode: true` in config or `--text` flag):** +When text mode is active, **do not use AskUserQuestion at all**. Instead, present every +question as a plain-text numbered list and ask the user to type their choice number. +This is required for Claude Code remote sessions (`/rc` mode) where the Claude App +cannot forward TUI menu selections back to the host. + +Enable text mode: +- Per-session: pass `--text` flag to any command (e.g., `/gsd:discuss-phase --text`) +- Per-project: `gsd-tools config-set workflow.text_mode true` + +Text mode applies to ALL workflows in the session, not just discuss-phase. @@ -464,6 +476,13 @@ With that context: How should users authenticate? When disabled (default), skip the research and present questions directly as before. +**Text mode support:** Parse optional `--text` from `$ARGUMENTS`. +- Accept `--text` flag OR read `workflow.text_mode` from config (from init context) +- When active, replace ALL `AskUserQuestion` calls with plain-text numbered lists +- User types a number to select, or types free text for "Other" +- This is required for Claude Code remote sessions (`/rc` mode) where TUI menus + don't work through the Claude App + **Batch mode support:** Parse optional `--batch` from `$ARGUMENTS`. - Accept `--batch`, `--batch=N`, or `--batch N` diff --git a/get-shit-done/workflows/settings.md b/get-shit-done/workflows/settings.md index 1c7ada565..13cd8399a 100644 --- a/get-shit-done/workflows/settings.md +++ b/get-shit-done/workflows/settings.md @@ -172,6 +172,9 @@ Merge new settings into existing config.json: "context_warnings": true/false, "workflow_guard": true/false, "research_questions": true/false + }, + "workflow": { + "text_mode": true/false // Use plain-text questions instead of TUI menus (for /rc remote sessions) } } ``` diff --git a/tests/config.test.cjs b/tests/config.test.cjs index 789753153..33fb9d44a 100644 --- a/tests/config.test.cjs +++ b/tests/config.test.cjs @@ -287,6 +287,16 @@ describe('config-set command', () => { ); }); + test('sets workflow.text_mode for remote session support', () => { + writeConfig(tmpDir, {}); + + const result = runGsdTools('config-set workflow.text_mode true', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const config = readConfig(tmpDir); + assert.strictEqual(config.workflow.text_mode, true); + }); + test('errors when no key path provided', () => { const result = runGsdTools('config-set', tmpDir); assert.strictEqual(result.success, false); diff --git a/tests/core.test.cjs b/tests/core.test.cjs index 56d658a53..3a3861ea1 100644 --- a/tests/core.test.cjs +++ b/tests/core.test.cjs @@ -61,6 +61,7 @@ describe('loadConfig', () => { assert.strictEqual(config.brave_search, false); assert.strictEqual(config.parallelization, true); assert.strictEqual(config.nyquist_validation, true); + assert.strictEqual(config.text_mode, false); }); test('reads model_profile from config.json', () => {