From a74e6b1e94ffa261c87b0ff27a735ab4975790d7 Mon Sep 17 00:00:00 2001 From: Ghadi Saab Date: Sun, 22 Mar 2026 08:56:22 +0000 Subject: [PATCH] fix: add text_mode support to plan-phase workflow `workflow.text_mode: true` (or `--text` flag) now applies to plan-phase, not just discuss-phase. Fixes #1313. Changes: - `init plan-phase` now exposes `text_mode` from config in its JSON output - plan-phase workflow parses `--text` flag and resolves TEXT_MODE from init JSON or flag, whichever is set - All four AskUserQuestion call sites (no-context gate, research prompt, UI design contract gate, requirements coverage gap) now conditionally present as plain-text numbered lists when TEXT_MODE is active - `--text` added to plan-phase command argument-hint and flags docs - Tests added for init output and workflow references Co-Authored-By: Claude Sonnet 4.6 --- commands/gsd/plan-phase.md | 3 +- get-shit-done/bin/lib/init.cjs | 1 + get-shit-done/workflows/plan-phase.md | 47 ++++++++++++++++++++++++--- tests/discuss-mode.test.cjs | 25 ++++++++++++++ tests/init.test.cjs | 19 +++++++++++ 5 files changed, 89 insertions(+), 6 deletions(-) diff --git a/commands/gsd/plan-phase.md b/commands/gsd/plan-phase.md index 1f26ebbb7..9af3ce0e7 100644 --- a/commands/gsd/plan-phase.md +++ b/commands/gsd/plan-phase.md @@ -1,7 +1,7 @@ --- name: gsd:plan-phase description: Create detailed phase plan (PLAN.md) with verification loop -argument-hint: "[phase] [--auto] [--research] [--skip-research] [--gaps] [--skip-verify] [--prd ] [--reviews]" +argument-hint: "[phase] [--auto] [--research] [--skip-research] [--gaps] [--skip-verify] [--prd ] [--reviews] [--text]" agent: gsd-planner allowed-tools: - Read @@ -36,6 +36,7 @@ Phase number: $ARGUMENTS (optional — auto-detects next unplanned phase if omit - `--skip-verify` — Skip verification loop - `--prd ` — Use a PRD/acceptance criteria file instead of discuss-phase. Parses requirements into CONTEXT.md automatically. Skips discuss-phase entirely. - `--reviews` — Replan incorporating cross-AI review feedback from REVIEWS.md (produced by `/gsd:review`) +- `--text` — Use plain-text numbered lists instead of TUI menus (required for `/rc` remote sessions) Normalize phase input in step 2 before any directory lookups. diff --git a/get-shit-done/bin/lib/init.cjs b/get-shit-done/bin/lib/init.cjs index 4c94a032a..0ed669e91 100644 --- a/get-shit-done/bin/lib/init.cjs +++ b/get-shit-done/bin/lib/init.cjs @@ -173,6 +173,7 @@ function cmdInitPlanPhase(cwd, phase, raw) { plan_checker_enabled: config.plan_checker, nyquist_validation_enabled: config.nyquist_validation, commit_docs: config.commit_docs, + text_mode: config.text_mode, // Phase info phase_found: !!phaseInfo, diff --git a/get-shit-done/workflows/plan-phase.md b/get-shit-done/workflows/plan-phase.md index 27ec1e5b8..f0ab019a6 100644 --- a/get-shit-done/workflows/plan-phase.md +++ b/get-shit-done/workflows/plan-phase.md @@ -26,7 +26,7 @@ INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init plan-phase "$PH if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi ``` -Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_enabled`, `plan_checker_enabled`, `nyquist_validation_enabled`, `commit_docs`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_reviews`, `has_plans`, `plan_count`, `planning_exists`, `roadmap_exists`, `phase_req_ids`. +Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_enabled`, `plan_checker_enabled`, `nyquist_validation_enabled`, `commit_docs`, `text_mode`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_reviews`, `has_plans`, `plan_count`, `planning_exists`, `roadmap_exists`, `phase_req_ids`. **File paths (for blocks):** `state_path`, `roadmap_path`, `requirements_path`, `context_path`, `research_path`, `verification_path`, `uat_path`, `reviews_path`. These are null if files don't exist. @@ -34,7 +34,9 @@ Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_ ## 2. Parse and Normalize Arguments -Extract from $ARGUMENTS: phase number (integer or decimal like `2.1`), flags (`--research`, `--skip-research`, `--gaps`, `--skip-verify`, `--prd `, `--reviews`). +Extract from $ARGUMENTS: phase number (integer or decimal like `2.1`), flags (`--research`, `--skip-research`, `--gaps`, `--skip-verify`, `--prd `, `--reviews`, `--text`). + +Set `TEXT_MODE=true` if `--text` is present in $ARGUMENTS OR `text_mode` from init JSON is `true`. When `TEXT_MODE` is active, replace every `AskUserQuestion` call with 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 TUI menus don't work through the Claude App. Extract `--prd ` from $ARGUMENTS. If present, set PRD_FILE to the filepath. @@ -192,7 +194,20 @@ Read discuss mode for context gate label: DISCUSS_MODE=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-get workflow.discuss_mode 2>/dev/null || echo "discuss") ``` -Use AskUserQuestion: +If `TEXT_MODE` is true, present as a plain-text numbered list: +``` +No CONTEXT.md found for Phase {X}. Plans will use research and requirements only — your design preferences won't be included. + +1. Continue without context — Plan using research + requirements only +[If DISCUSS_MODE is "assumptions":] +2. Gather context (assumptions mode) — Analyze codebase and surface assumptions before planning +[If DISCUSS_MODE is "discuss" or unset:] +2. Run discuss-phase first — Capture design decisions before planning + +Enter number: +``` + +Otherwise use AskUserQuestion: - header: "No context" - question: "No CONTEXT.md found for Phase {X}. Plans will use research and requirements only — your design preferences won't be included. Continue or capture context first?" - options: @@ -225,6 +240,17 @@ If "Run discuss-phase first": **If no explicit flag (`--research` or `--skip-research`) and not `--auto`:** Ask the user whether to research, with a contextual recommendation based on the phase: +If `TEXT_MODE` is true, present as a plain-text numbered list: +``` +Research before planning Phase {X}: {phase_name}? + +1. Research first (Recommended) — Investigate domain, patterns, and dependencies before planning. Best for new features, unfamiliar integrations, or architectural changes. +2. Skip research — Plan directly from context and requirements. Best for bug fixes, simple refactors, or well-understood tasks. + +Enter number: +``` + +Otherwise use AskUserQuestion: ``` AskUserQuestion([ { @@ -359,7 +385,18 @@ UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1) **If UI-SPEC.md missing AND `UI_GATE_CFG` is `true`:** -Use AskUserQuestion: +If `TEXT_MODE` is true, present as a plain-text numbered list: +``` +Phase {N} has frontend indicators but no UI-SPEC.md. Generate a design contract before planning? + +1. Generate UI-SPEC first — Run /gsd:ui-phase {N} then re-run /gsd:plan-phase {N} +2. Continue without UI-SPEC +3. Not a frontend phase + +Enter number: +``` + +Otherwise use AskUserQuestion: - header: "UI Design Contract" - question: "Phase {N} has frontend indicators but no UI-SPEC.md. Generate a design contract before planning?" - options: @@ -667,7 +704,7 @@ Options: 3. Proceed anyway — accept coverage gaps ``` -Use AskUserQuestion to present the options. +If `TEXT_MODE` is true, present as a plain-text numbered list (options already shown in the block above). Otherwise use AskUserQuestion to present the options. ## 14. Present Final Status diff --git a/tests/discuss-mode.test.cjs b/tests/discuss-mode.test.cjs index f5b125e17..1f0b56599 100644 --- a/tests/discuss-mode.test.cjs +++ b/tests/discuss-mode.test.cjs @@ -117,6 +117,31 @@ describe('workflow.discuss_mode config', () => { assert.ok(workflow.includes('--text'), 'should handle --text flag'); }); + test('plan-phase workflow references text_mode', () => { + const planPhase = fs.readFileSync( + path.join(__dirname, '..', 'get-shit-done', 'workflows', 'plan-phase.md'), 'utf8' + ); + assert.ok(planPhase.includes('text_mode'), 'plan-phase workflow should reference text_mode'); + assert.ok(planPhase.includes('TEXT_MODE'), 'plan-phase workflow should use TEXT_MODE variable'); + assert.ok(planPhase.includes('--text'), 'plan-phase workflow should handle --text flag'); + }); + + test('plan-phase command argument-hint includes --text', () => { + const command = fs.readFileSync( + path.join(__dirname, '..', 'commands', 'gsd', 'plan-phase.md'), 'utf8' + ); + assert.ok(command.includes('--text'), 'argument-hint should include --text flag'); + }); + + test('plan-phase init exposes text_mode in workflow flags', () => { + const initSrc = fs.readFileSync( + path.join(__dirname, '..', 'get-shit-done', 'bin', 'lib', 'init.cjs'), 'utf8' + ); + // The cmdInitPlanPhase result object must include text_mode + const planPhaseBlock = initSrc.slice(initSrc.indexOf('function cmdInitPlanPhase')); + assert.ok(planPhaseBlock.includes('text_mode: config.text_mode'), 'init plan-phase must expose text_mode'); + }); + test('progress workflow references discuss_mode', () => { const progress = fs.readFileSync( path.join(__dirname, '..', 'get-shit-done', 'workflows', 'progress.md'), 'utf8' diff --git a/tests/init.test.cjs b/tests/init.test.cjs index 929f18473..d6769ea82 100644 --- a/tests/init.test.cjs +++ b/tests/init.test.cjs @@ -54,6 +54,25 @@ describe('init commands', () => { assert.strictEqual(output.uat_path, '.planning/phases/03-api/03-UAT.md'); }); + test('init plan-phase exposes text_mode from config (defaults false)', () => { + const result = runGsdTools('init plan-phase 03', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + const output = JSON.parse(result.output); + assert.strictEqual(output.text_mode, false, 'text_mode should default to false'); + }); + + test('init plan-phase exposes text_mode true when set in config', () => { + const configPath = path.join(tmpDir, '.planning', 'config.json'); + const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); + config.workflow = { ...(config.workflow || {}), text_mode: true }; + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + + const result = runGsdTools('init plan-phase 03', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + const output = JSON.parse(result.output); + assert.strictEqual(output.text_mode, true, 'text_mode should reflect config value'); + }); + test('init progress returns file paths', () => { const result = runGsdTools('init progress', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`);