diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index ce991803b..7836f3edc 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -42,7 +42,8 @@ GSD stores project settings in `.planning/config.json`. Created during `/gsd:new "git": { "branching_strategy": "none", "phase_branch_template": "gsd/phase-{phase}-{slug}", - "milestone_branch_template": "gsd/{milestone}-{slug}" + "milestone_branch_template": "gsd/{milestone}-{slug}", + "quick_branch_template": null }, "gates": { "confirm_project": true, @@ -144,6 +145,7 @@ To keep planning artifacts out of git: | `git.branching_strategy` | enum | `none` | `none`, `phase`, or `milestone` | | `git.phase_branch_template` | string | `gsd/phase-{phase}-{slug}` | Branch name template for phase strategy | | `git.milestone_branch_template` | string | `gsd/{milestone}-{slug}` | Branch name template for milestone strategy | +| `git.quick_branch_template` | string or null | `null` | Optional branch name template for `/gsd:quick` tasks | ### Strategy Comparison @@ -160,6 +162,15 @@ To keep planning artifacts out of git: | `{phase}` | `phase_branch_template` | `03` (zero-padded) | | `{slug}` | Both templates | `user-authentication` (lowercase, hyphenated) | | `{milestone}` | `milestone_branch_template` | `v1.0` | +| `{num}` / `{quick}` | `quick_branch_template` | `260317-abc` (quick task ID) | + +Example quick-task branching: + +```json +"git": { + "quick_branch_template": "gsd/quick-{num}-{slug}" +} +``` ### Merge Options at Milestone Completion diff --git a/docs/USER-GUIDE.md b/docs/USER-GUIDE.md index 948fa9899..ba7b1abf8 100644 --- a/docs/USER-GUIDE.md +++ b/docs/USER-GUIDE.md @@ -356,12 +356,13 @@ GSD stores project settings in `.planning/config.json`. Configure during `/gsd:n "ui_phase": true, "ui_safety_gate": true }, - "git": { - "branching_strategy": "none", - "phase_branch_template": "gsd/phase-{phase}-{slug}", - "milestone_branch_template": "gsd/{milestone}-{slug}" - } -} + "git": { + "branching_strategy": "none", + "phase_branch_template": "gsd/phase-{phase}-{slug}", + "milestone_branch_template": "gsd/{milestone}-{slug}", + "quick_branch_template": null + } +} ``` ### Core Settings @@ -398,9 +399,10 @@ Disable these to speed up phases in familiar domains or when conserving tokens. | Setting | Options | Default | What it Controls | |---------|---------|---------|------------------| -| `git.branching_strategy` | `none`, `phase`, `milestone` | `none` | When and how branches are created | -| `git.phase_branch_template` | Template string | `gsd/phase-{phase}-{slug}` | Branch name for phase strategy | -| `git.milestone_branch_template` | Template string | `gsd/{milestone}-{slug}` | Branch name for milestone strategy | +| `git.branching_strategy` | `none`, `phase`, `milestone` | `none` | When and how branches are created | +| `git.phase_branch_template` | Template string | `gsd/phase-{phase}-{slug}` | Branch name for phase strategy | +| `git.milestone_branch_template` | Template string | `gsd/{milestone}-{slug}` | Branch name for milestone strategy | +| `git.quick_branch_template` | Template string or `null` | `null` | Optional branch name for `/gsd:quick` tasks | **Branching strategies explained:** @@ -410,7 +412,15 @@ Disable these to speed up phases in familiar domains or when conserving tokens. | `phase` | At each `execute-phase` | One phase per branch | Code review per phase, granular rollback | | `milestone` | At first `execute-phase` | All phases share one branch | Release branches, PR per version | -**Template variables:** `{phase}` = zero-padded number (e.g., "03"), `{slug}` = lowercase hyphenated name, `{milestone}` = version (e.g., "v1.0"). +**Template variables:** `{phase}` = zero-padded number (e.g., "03"), `{slug}` = lowercase hyphenated name, `{milestone}` = version (e.g., "v1.0"), `{num}` / `{quick}` = quick task ID (e.g., "260317-abc"). + +Example quick-task branching: + +```json +"git": { + "quick_branch_template": "gsd/quick-{num}-{slug}" +} +``` ### Model Profiles (Per-Agent Breakdown) diff --git a/get-shit-done/bin/lib/config.cjs b/get-shit-done/bin/lib/config.cjs index ef1466f61..8b04c256d 100644 --- a/get-shit-done/bin/lib/config.cjs +++ b/get-shit-done/bin/lib/config.cjs @@ -17,7 +17,7 @@ const VALID_CONFIG_KEYS = new Set([ 'workflow.research', 'workflow.plan_check', 'workflow.verifier', 'workflow.nyquist_validation', 'workflow.ui_phase', 'workflow.ui_safety_gate', 'workflow._auto_chain_active', - 'git.branching_strategy', 'git.phase_branch_template', 'git.milestone_branch_template', + 'git.branching_strategy', 'git.phase_branch_template', 'git.milestone_branch_template', 'git.quick_branch_template', 'planning.commit_docs', 'planning.search_gitignored', ]); @@ -91,6 +91,7 @@ function ensureConfigFile(cwd) { branching_strategy: 'none', phase_branch_template: 'gsd/phase-{phase}-{slug}', milestone_branch_template: 'gsd/{milestone}-{slug}', + quick_branch_template: null, workflow: { research: true, plan_check: true, diff --git a/get-shit-done/bin/lib/core.cjs b/get-shit-done/bin/lib/core.cjs index f61571191..6d4eaa115 100644 --- a/get-shit-done/bin/lib/core.cjs +++ b/get-shit-done/bin/lib/core.cjs @@ -58,6 +58,7 @@ function loadConfig(cwd) { branching_strategy: 'none', phase_branch_template: 'gsd/phase-{phase}-{slug}', milestone_branch_template: 'gsd/{milestone}-{slug}', + quick_branch_template: null, research: true, plan_checker: true, verifier: true, @@ -103,6 +104,7 @@ function loadConfig(cwd) { branching_strategy: get('branching_strategy', { section: 'git', field: 'branching_strategy' }) ?? defaults.branching_strategy, phase_branch_template: get('phase_branch_template', { section: 'git', field: 'phase_branch_template' }) ?? defaults.phase_branch_template, milestone_branch_template: get('milestone_branch_template', { section: 'git', field: 'milestone_branch_template' }) ?? defaults.milestone_branch_template, + quick_branch_template: get('quick_branch_template', { section: 'git', field: 'quick_branch_template' }) ?? defaults.quick_branch_template, research: get('research', { section: 'workflow', field: 'research' }) ?? defaults.research, plan_checker: get('plan_checker', { section: 'workflow', field: 'plan_check' }) ?? defaults.plan_checker, verifier: get('verifier', { section: 'workflow', field: 'verifier' }) ?? defaults.verifier, diff --git a/get-shit-done/bin/lib/init.cjs b/get-shit-done/bin/lib/init.cjs index 069a4b8a4..3f21237a3 100644 --- a/get-shit-done/bin/lib/init.cjs +++ b/get-shit-done/bin/lib/init.cjs @@ -309,6 +309,13 @@ function cmdInitQuick(cwd, description, raw) { const timeBlocks = Math.floor(secondsSinceMidnight / 2); const timeEncoded = timeBlocks.toString(36).padStart(3, '0'); const quickId = dateStr + '-' + timeEncoded; + const branchSlug = slug || 'quick'; + const quickBranchName = config.quick_branch_template + ? config.quick_branch_template + .replace('{num}', quickId) + .replace('{quick}', quickId) + .replace('{slug}', branchSlug) + : null; const result = { // Models @@ -319,6 +326,7 @@ function cmdInitQuick(cwd, description, raw) { // Config commit_docs: config.commit_docs, + branch_name: quickBranchName, // Quick task info quick_id: quickId, diff --git a/get-shit-done/bin/lib/verify.cjs b/get-shit-done/bin/lib/verify.cjs index 449e81f54..57adb1878 100644 --- a/get-shit-done/bin/lib/verify.cjs +++ b/get-shit-done/bin/lib/verify.cjs @@ -753,6 +753,7 @@ function cmdValidateHealth(cwd, options, raw) { branching_strategy: 'none', phase_branch_template: 'gsd/phase-{phase}-{slug}', milestone_branch_template: 'gsd/{milestone}-{slug}', + quick_branch_template: null, workflow: { research: true, plan_check: true, diff --git a/get-shit-done/references/planning-config.md b/get-shit-done/references/planning-config.md index 3bb884377..f8276c761 100644 --- a/get-shit-done/references/planning-config.md +++ b/get-shit-done/references/planning-config.md @@ -11,7 +11,8 @@ Configuration options for `.planning/` directory behavior. "git": { "branching_strategy": "none", "phase_branch_template": "gsd/phase-{phase}-{slug}", - "milestone_branch_template": "gsd/{milestone}-{slug}" + "milestone_branch_template": "gsd/{milestone}-{slug}", + "quick_branch_template": null } ``` @@ -22,6 +23,7 @@ Configuration options for `.planning/` directory behavior. | `git.branching_strategy` | `"none"` | Git branching approach: `"none"`, `"phase"`, or `"milestone"` | | `git.phase_branch_template` | `"gsd/phase-{phase}-{slug}"` | Branch template for phase strategy | | `git.milestone_branch_template` | `"gsd/{milestone}-{slug}"` | Branch template for milestone strategy | +| `git.quick_branch_template` | `null` | Optional branch template for quick-task runs | diff --git a/get-shit-done/workflows/quick.md b/get-shit-done/workflows/quick.md index 4f21ebad0..7138dd031 100644 --- a/get-shit-done/workflows/quick.md +++ b/get-shit-done/workflows/quick.md @@ -111,7 +111,7 @@ INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init quick "$DESCRIP if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi ``` -Parse JSON for: `planner_model`, `executor_model`, `checker_model`, `verifier_model`, `commit_docs`, `quick_id`, `slug`, `date`, `timestamp`, `quick_dir`, `task_dir`, `roadmap_exists`, `planning_exists`. +Parse JSON for: `planner_model`, `executor_model`, `checker_model`, `verifier_model`, `commit_docs`, `branch_name`, `quick_id`, `slug`, `date`, `timestamp`, `quick_dir`, `task_dir`, `roadmap_exists`, `planning_exists`. **If `roadmap_exists` is false:** Error — Quick mode requires an active project with ROADMAP.md. Run `/gsd:new-project` first. @@ -119,6 +119,20 @@ Quick tasks can run mid-phase - validation only checks ROADMAP.md exists, not ph --- +**Step 2.5: Handle quick-task branching** + +**If `branch_name` is empty/null:** Skip and continue on the current branch. + +**If `branch_name` is set:** Check out the quick-task branch before any planning commits: + +```bash +git checkout -b "$branch_name" 2>/dev/null || git checkout "$branch_name" +``` + +All quick-task commits for this run stay on that branch. User handles merge/rebase afterward. + +--- + **Step 3: Create task directory** ```bash diff --git a/get-shit-done/workflows/settings.md b/get-shit-done/workflows/settings.md index 1c7ada565..5f70afba6 100644 --- a/get-shit-done/workflows/settings.md +++ b/get-shit-done/workflows/settings.md @@ -166,7 +166,8 @@ Merge new settings into existing config.json: "ui_safety_gate": true/false }, "git": { - "branching_strategy": "none" | "phase" | "milestone" + "branching_strategy": "none" | "phase" | "milestone", + "quick_branch_template": }, "hooks": { "context_warnings": true/false, @@ -211,6 +212,7 @@ Write `~/.gsd/defaults.json` with: "commit_docs": , "parallelization": , "branching_strategy": , + "quick_branch_template": , "workflow": { "research": , "plan_check": , diff --git a/tests/init.test.cjs b/tests/init.test.cjs index 5e67a7018..d36762860 100644 --- a/tests/init.test.cjs +++ b/tests/init.test.cjs @@ -679,6 +679,7 @@ describe('cmdInitQuick', () => { assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); + assert.strictEqual(output.branch_name, null); assert.strictEqual(output.slug, 'fix-login-bug'); assert.strictEqual(output.description, 'Fix login bug'); @@ -736,6 +737,44 @@ describe('cmdInitQuick', () => { const output = JSON.parse(result.output); assert.ok(output.slug.length <= 40, `Slug should be <= 40 chars, got ${output.slug.length}: "${output.slug}"`); }); + + test('returns quick branch name when quick_branch_template is configured', () => { + fs.writeFileSync( + path.join(tmpDir, '.planning', 'config.json'), + JSON.stringify({ + git: { + quick_branch_template: 'gsd/quick-{num}-{slug}', + }, + }, null, 2) + ); + + const result = runGsdTools('init quick "Fix login bug"', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.ok(output.branch_name, 'branch_name should be set'); + assert.ok(output.branch_name.startsWith('gsd/quick-')); + assert.ok(output.branch_name.endsWith('-fix-login-bug')); + assert.ok(output.branch_name.includes(output.quick_id), 'branch_name should include quick_id'); + }); + + test('uses fallback slug in quick branch name when description is omitted', () => { + fs.writeFileSync( + path.join(tmpDir, '.planning', 'config.json'), + JSON.stringify({ + git: { + quick_branch_template: 'gsd/quick-{quick}-{slug}', + }, + }, null, 2) + ); + + const result = runGsdTools('init quick', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.ok(output.branch_name, 'branch_name should be set'); + assert.ok(output.branch_name.endsWith('-quick'), `Expected fallback slug in branch name, got "${output.branch_name}"`); + }); }); // ───────────────────────────────────────────────────────────────────────────── diff --git a/tests/quick-branching.test.cjs b/tests/quick-branching.test.cjs new file mode 100644 index 000000000..5259ae199 --- /dev/null +++ b/tests/quick-branching.test.cjs @@ -0,0 +1,39 @@ +/** + * Quick task branching tests + * + * Validates that /gsd:quick exposes branch_name from init and that the + * workflow checks out a dedicated quick-task branch when configured. + */ + +const { test, describe } = require('node:test'); +const assert = require('node:assert'); +const fs = require('fs'); +const path = require('path'); + +describe('quick workflow: branching support', () => { + const workflowPath = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'quick.md'); + let content; + + test('workflow file exists', () => { + assert.ok(fs.existsSync(workflowPath), 'workflows/quick.md should exist'); + }); + + test('init parse list includes branch_name', () => { + content = fs.readFileSync(workflowPath, 'utf-8'); + assert.ok(content.includes('branch_name'), 'quick workflow should parse branch_name from init JSON'); + }); + + test('workflow includes quick-task branching step', () => { + content = fs.readFileSync(workflowPath, 'utf-8'); + assert.ok(content.includes('Step 2.5: Handle quick-task branching')); + assert.ok(content.includes('git checkout -b "$branch_name" 2>/dev/null || git checkout "$branch_name"')); + }); + + test('branching step runs before task directory creation', () => { + content = fs.readFileSync(workflowPath, 'utf-8'); + const branchingIndex = content.indexOf('Step 2.5: Handle quick-task branching'); + const createDirIndex = content.indexOf('Step 3: Create task directory'); + assert.ok(branchingIndex !== -1 && createDirIndex !== -1, 'workflow should contain both branching and directory steps'); + assert.ok(branchingIndex < createDirIndex, 'branching should happen before quick task directories and commits'); + }); +});