From 383007dca400c31d3f5db1ac1338fd0da48897c1 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Sun, 5 Apr 2026 14:04:08 -0700 Subject: [PATCH] feat(workflows): add conditional thinking partner at decision points (#1816) Integrate lightweight thinking partner analysis at two workflow decision points, controlled by features.thinking_partner config (default: false): 1. discuss-phase: when developer answers reveal competing priorities (detected via keyword/structural signals), offers brief tradeoff analysis before locking decisions 2. plan-phase: when plan-checker flags architectural tradeoffs, analyzes options and recommends an approach aligned with phase goals before entering the revision loop The thinking partner is opt-in, skippable (No, I have decided), and brief (3-5 bullets). A third integration point for /gsd-explore will be added when #1729 lands. Closes #1726 Co-authored-by: Claude Opus 4.6 --- get-shit-done/bin/lib/config.cjs | 1 + get-shit-done/bin/lib/core.cjs | 2 +- get-shit-done/references/thinking-partner.md | 96 +++++++++ get-shit-done/workflows/discuss-phase.md | 14 ++ get-shit-done/workflows/plan-phase.md | 19 ++ tests/thinking-partner.test.cjs | 205 +++++++++++++++++++ 6 files changed, 336 insertions(+), 1 deletion(-) create mode 100644 get-shit-done/references/thinking-partner.md create mode 100644 tests/thinking-partner.test.cjs diff --git a/get-shit-done/bin/lib/config.cjs b/get-shit-done/bin/lib/config.cjs index 38d245fd5..24757d0ae 100644 --- a/get-shit-done/bin/lib/config.cjs +++ b/get-shit-done/bin/lib/config.cjs @@ -27,6 +27,7 @@ const VALID_CONFIG_KEYS = new Set([ 'planning.commit_docs', 'planning.search_gitignored', 'workflow.subagent_timeout', 'hooks.context_warnings', + 'features.thinking_partner', 'project_code', 'phase_naming', 'manager.flags.discuss', 'manager.flags.plan', 'manager.flags.execute', 'response_language', diff --git a/get-shit-done/bin/lib/core.cjs b/get-shit-done/bin/lib/core.cjs index 30bf1ddcd..0974c1226 100644 --- a/get-shit-done/bin/lib/core.cjs +++ b/get-shit-done/bin/lib/core.cjs @@ -300,7 +300,7 @@ function loadConfig(cwd) { // Extract top-level key names from dot-notation paths (e.g., 'workflow.research' → 'workflow') ...[...VALID_CONFIG_KEYS].map(k => k.split('.')[0]), // Section containers that hold nested sub-keys - 'git', 'workflow', 'planning', 'hooks', + 'git', 'workflow', 'planning', 'hooks', 'features', // Internal keys loadConfig reads but config-set doesn't expose 'model_overrides', 'agent_skills', 'context_window', 'resolve_model_ids', // Deprecated keys (still accepted for migration, not in config-set) diff --git a/get-shit-done/references/thinking-partner.md b/get-shit-done/references/thinking-partner.md new file mode 100644 index 000000000..f39732fe8 --- /dev/null +++ b/get-shit-done/references/thinking-partner.md @@ -0,0 +1,96 @@ +# Thinking Partner Integration + +Conditional extended thinking at workflow decision points. Activates when `features.thinking_partner: true` in `.planning/config.json` (default: false). + +--- + +## Tradeoff Detection Signals + +The thinking partner activates when developer responses contain specific signals indicating competing priorities: + +**Keyword signals:** +- "or" / "versus" / "vs" connecting two approaches +- "tradeoff" / "trade-off" / "tradeoffs" +- "on one hand" / "on the other hand" +- "pros and cons" +- "not sure between" / "torn between" + +**Structural signals:** +- Developer lists 2+ competing options +- Developer asks "which is better" or "what would you recommend" +- Developer reverses a previous decision ("actually, maybe we should...") + +**When NOT to activate:** +- Developer has already made a clear choice +- The "or" is rhetorical or trivial (e.g., "tabs or spaces" — use project convention) +- Simple yes/no questions +- Developer explicitly asks to move on + +--- + +## Integration Points + +### 1. Discuss Phase — Tradeoff Deep-Dive + +**When:** During `discuss_areas` step, after a developer answer reveals competing priorities. + +**What:** Pause the normal question flow and offer a brief structured analysis: +``` +I notice competing priorities here — {X} optimizes for {A} while {Y} optimizes for {B}. + +Want me to think through the tradeoffs before we decide? +[Yes, analyze tradeoffs] / [No, I've decided] +``` + +If yes, provide a brief (3-5 bullet) analysis covering: +- What each approach optimizes for +- What each approach sacrifices +- Which aligns better with the project's stated goals (from PROJECT.md) +- A recommendation with reasoning + +Then return to the normal discussion flow. + +### 2. Plan Phase — Architectural Decision Analysis + +**When:** During step 11 (Handle Checker Return), when the plan-checker flags issues containing architectural tradeoff keywords. + +**What:** Before sending to the revision loop, analyze the architectural decision: +``` +The plan-checker flagged an architectural tradeoff: {issue description} + +Brief analysis: +- Option A: {approach} — {pros/cons} +- Option B: {approach} — {pros/cons} +- Recommendation: {choice} because {reasoning aligned with phase goals} + +Apply this recommendation to the revision? [Yes] / [No, let me decide] +``` + +### 3. Explore — Approach Comparison (requires #1729) + +**When:** During Socratic conversation, when multiple viable approaches emerge. +**Note:** This integration point will be added when /gsd-explore (#1729) lands. + +--- + +## Configuration + +```json +{ + "features": { + "thinking_partner": true + } +} +``` + +Default: `false`. The thinking partner is opt-in because it adds latency to interactive workflows. + +--- + +## Design Principles + +1. **Lightweight** — inline analysis, not a separate interactive session +2. **Opt-in** — must be explicitly enabled, never activates by default +3. **Skippable** — always offer "No, I've decided" to bypass +4. **Brief** — 3-5 bullets max, not a full research report +5. **Aligned** — recommendations reference PROJECT.md goals when available diff --git a/get-shit-done/workflows/discuss-phase.md b/get-shit-done/workflows/discuss-phase.md index 292fdd07a..7d0bb784b 100644 --- a/get-shit-done/workflows/discuss-phase.md +++ b/get-shit-done/workflows/discuss-phase.md @@ -607,6 +607,20 @@ Table-first discussion flow — present research-backed comparison tables, then - If user picks from table options → record as locked decision for that area - If user picks "Other" → receive their input, reflect it back for confirmation, record + **Thinking partner (conditional):** + If `features.thinking_partner` is enabled in config, check the user's answer for tradeoff signals + (see `references/thinking-partner.md` for signal list). If tradeoff detected: + + ``` + I notice competing priorities here — {option_A} optimizes for {goal_A} while {option_B} optimizes for {goal_B}. + + Want me to think through the tradeoffs before we lock this in? + [Yes, analyze] / [No, decision made] + ``` + + If yes: provide 3-5 bullet analysis (what each optimizes/sacrifices, alignment with PROJECT.md goals, recommendation). Then return to normal flow. + If no or thinking_partner disabled: continue to next area. + 4. **After recording pick, Claude decides whether follow-up questions are needed:** - If the pick has ambiguity that would affect downstream planning → ask 1-2 targeted follow-up questions using AskUserQuestion - If the pick is clear and self-contained → move to next area diff --git a/get-shit-done/workflows/plan-phase.md b/get-shit-done/workflows/plan-phase.md index ff65971b9..ca7d222fb 100644 --- a/get-shit-done/workflows/plan-phase.md +++ b/get-shit-done/workflows/plan-phase.md @@ -772,6 +772,25 @@ Task( - **`## VERIFICATION PASSED`:** Display confirmation, proceed to step 13. - **`## ISSUES FOUND`:** Display issues, check iteration count, proceed to step 12. +**Thinking partner for architectural tradeoffs (conditional):** +If `features.thinking_partner` is enabled, scan the checker's issues for architectural tradeoff keywords +("architecture", "approach", "strategy", "pattern", "vs", "alternative"). If found: + +``` +The plan-checker flagged an architectural decision point: +{issue description} + +Brief analysis: +- Option A: {approach_from_plan} — {pros/cons} +- Option B: {alternative_approach} — {pros/cons} +- Recommendation: {choice} aligned with {phase_goal} + +Apply this to the revision? [Yes] / [No, I'll decide] +``` + +If yes: include the recommendation in the revision prompt. If no: proceed to revision loop as normal. +If thinking_partner disabled: skip this block entirely. + ## 12. Revision Loop (Max 3 Iterations) Track `iteration_count` (starts at 1 after initial plan + check). diff --git a/tests/thinking-partner.test.cjs b/tests/thinking-partner.test.cjs new file mode 100644 index 000000000..817316f1e --- /dev/null +++ b/tests/thinking-partner.test.cjs @@ -0,0 +1,205 @@ +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const path = require('path'); + +const GSD_ROOT = path.join(__dirname, '..', 'get-shit-done'); + +describe('Thinking Partner Integration (#1726)', () => { + // Reference doc tests + describe('Reference document', () => { + const refPath = path.join(GSD_ROOT, 'references', 'thinking-partner.md'); + + test('thinking-partner.md exists', () => { + assert.ok(fs.existsSync(refPath), 'references/thinking-partner.md should exist'); + }); + + test('documents all 3 integration points', () => { + const content = fs.readFileSync(refPath, 'utf-8'); + assert.ok(content.includes('### 1. Discuss Phase'), 'should document Discuss Phase integration'); + assert.ok(content.includes('### 2. Plan Phase'), 'should document Plan Phase integration'); + assert.ok(content.includes('### 3. Explore'), 'should document Explore integration'); + }); + + test('documents keyword tradeoff signals', () => { + const content = fs.readFileSync(refPath, 'utf-8'); + assert.ok(content.includes('"or"'), 'should list "or" as keyword signal'); + assert.ok(content.includes('"versus"'), 'should list "versus" as keyword signal'); + assert.ok(content.includes('"tradeoff"'), 'should list "tradeoff" as keyword signal'); + assert.ok(content.includes('"pros and cons"'), 'should list "pros and cons" as keyword signal'); + assert.ok(content.includes('"torn between"'), 'should list "torn between" as keyword signal'); + }); + + test('documents structural tradeoff signals', () => { + const content = fs.readFileSync(refPath, 'utf-8'); + assert.ok(content.includes('2+ competing options'), 'should list competing options signal'); + assert.ok(content.includes('which is better'), 'should list "which is better" signal'); + assert.ok(content.includes('reverses a previous decision'), 'should list decision reversal signal'); + }); + + test('documents when NOT to activate', () => { + const content = fs.readFileSync(refPath, 'utf-8'); + assert.ok(content.includes('When NOT to activate'), 'should document non-activation cases'); + assert.ok(content.includes('already made a clear choice'), 'should mention clear choices'); + }); + + test('feature is opt-in with default false', () => { + const content = fs.readFileSync(refPath, 'utf-8'); + assert.ok(content.includes('Default: `false`'), 'should document default as false'); + assert.ok(content.includes('opt-in'), 'should describe feature as opt-in'); + }); + + test('documents design principles', () => { + const content = fs.readFileSync(refPath, 'utf-8'); + assert.ok(content.includes('Lightweight'), 'should list Lightweight principle'); + assert.ok(content.includes('Opt-in'), 'should list Opt-in principle'); + assert.ok(content.includes('Skippable'), 'should list Skippable principle'); + assert.ok(content.includes('Brief'), 'should list Brief principle'); + assert.ok(content.includes('Aligned'), 'should list Aligned principle'); + }); + + test('explore integration deferred to #1729', () => { + const content = fs.readFileSync(refPath, 'utf-8'); + assert.ok(content.includes('#1729'), 'should reference issue #1729 for explore integration'); + }); + }); + + // Config tests + describe('Config integration', () => { + test('features.thinking_partner is in VALID_CONFIG_KEYS', () => { + const configSrc = fs.readFileSync( + path.join(GSD_ROOT, 'bin', 'lib', 'config.cjs'), + 'utf-8' + ); + assert.ok( + configSrc.includes("'features.thinking_partner'"), + 'VALID_CONFIG_KEYS should contain features.thinking_partner' + ); + }); + + test('features is in KNOWN_TOP_LEVEL section containers', () => { + const coreSrc = fs.readFileSync( + path.join(GSD_ROOT, 'bin', 'lib', 'core.cjs'), + 'utf-8' + ); + // The KNOWN_TOP_LEVEL set should include 'features' in section containers + assert.ok( + coreSrc.includes("'features'"), + 'KNOWN_TOP_LEVEL should contain features as a section container' + ); + }); + }); + + // Workflow integration tests + describe('Discuss-phase integration', () => { + test('discuss-phase.md contains thinking partner conditional block', () => { + const content = fs.readFileSync( + path.join(GSD_ROOT, 'workflows', 'discuss-phase.md'), + 'utf-8' + ); + assert.ok( + content.includes('Thinking partner (conditional)'), + 'discuss-phase.md should contain thinking partner conditional block' + ); + }); + + test('discuss-phase references features.thinking_partner config', () => { + const content = fs.readFileSync( + path.join(GSD_ROOT, 'workflows', 'discuss-phase.md'), + 'utf-8' + ); + assert.ok( + content.includes('features.thinking_partner'), + 'discuss-phase.md should reference the config key' + ); + }); + + test('discuss-phase references thinking-partner.md for signal list', () => { + const content = fs.readFileSync( + path.join(GSD_ROOT, 'workflows', 'discuss-phase.md'), + 'utf-8' + ); + assert.ok( + content.includes('references/thinking-partner.md'), + 'discuss-phase.md should reference the signal list doc' + ); + }); + + test('discuss-phase offers skip option', () => { + const content = fs.readFileSync( + path.join(GSD_ROOT, 'workflows', 'discuss-phase.md'), + 'utf-8' + ); + assert.ok( + content.includes('No, decision made'), + 'discuss-phase.md should offer a skip/decline option' + ); + }); + }); + + describe('Plan-phase integration', () => { + test('plan-phase.md contains thinking partner conditional block', () => { + const content = fs.readFileSync( + path.join(GSD_ROOT, 'workflows', 'plan-phase.md'), + 'utf-8' + ); + assert.ok( + content.includes('Thinking partner for architectural tradeoffs (conditional)'), + 'plan-phase.md should contain thinking partner conditional block' + ); + }); + + test('plan-phase references features.thinking_partner config', () => { + const content = fs.readFileSync( + path.join(GSD_ROOT, 'workflows', 'plan-phase.md'), + 'utf-8' + ); + assert.ok( + content.includes('features.thinking_partner'), + 'plan-phase.md should reference the config key' + ); + }); + + test('plan-phase scans for architectural tradeoff keywords', () => { + const content = fs.readFileSync( + path.join(GSD_ROOT, 'workflows', 'plan-phase.md'), + 'utf-8' + ); + assert.ok( + content.includes('"architecture"'), + 'plan-phase.md should list architecture as a keyword' + ); + assert.ok( + content.includes('"approach"'), + 'plan-phase.md should list approach as a keyword' + ); + assert.ok( + content.includes('"alternative"'), + 'plan-phase.md should list alternative as a keyword' + ); + }); + + test('plan-phase offers skip option', () => { + const content = fs.readFileSync( + path.join(GSD_ROOT, 'workflows', 'plan-phase.md'), + 'utf-8' + ); + assert.ok( + content.includes("No, I'll decide"), + 'plan-phase.md should offer a skip/decline option' + ); + }); + + test('plan-phase block is between step 11 and step 12', () => { + const content = fs.readFileSync( + path.join(GSD_ROOT, 'workflows', 'plan-phase.md'), + 'utf-8' + ); + const step11Idx = content.indexOf('## 11. Handle Checker Return'); + const thinkingIdx = content.indexOf('Thinking partner for architectural tradeoffs'); + const step12Idx = content.indexOf('## 12. Revision Loop'); + assert.ok(step11Idx < thinkingIdx, 'thinking partner block should come after step 11'); + assert.ok(thinkingIdx < step12Idx, 'thinking partner block should come before step 12'); + }); + }); +});