feat(quick): add quick-task branch support

This commit is contained in:
Colin
2026-03-17 12:04:02 -04:00
parent 4915d28f98
commit 3a0c81133b
11 changed files with 144 additions and 15 deletions

View File

@@ -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,
@@ -142,6 +143,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
@@ -158,6 +160,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

View File

@@ -349,12 +349,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
@@ -391,9 +392,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:**
@@ -403,7 +405,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)

View File

@@ -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,

View File

@@ -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,
@@ -100,6 +101,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,

View File

@@ -267,6 +267,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
@@ -277,6 +284,7 @@ function cmdInitQuick(cwd, description, raw) {
// Config
commit_docs: config.commit_docs,
branch_name: quickBranchName,
// Quick task info
quick_id: quickId,

View File

@@ -746,6 +746,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,

View File

@@ -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 |
</config_schema>
<commit_docs_behavior>

View File

@@ -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

View File

@@ -157,7 +157,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": <string|null>
},
"hooks": {
"context_warnings": true/false
@@ -200,6 +201,7 @@ Write `~/.gsd/defaults.json` with:
"commit_docs": <current>,
"parallelization": <current>,
"branching_strategy": <current>,
"quick_branch_template": <current>,
"workflow": {
"research": <current>,
"plan_check": <current>,

View File

@@ -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}"`);
});
});
// ─────────────────────────────────────────────────────────────────────────────

View File

@@ -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');
});
});