feat(quick): add quick-task branch support
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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}"`);
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
39
tests/quick-branching.test.cjs
Normal file
39
tests/quick-branching.test.cjs
Normal 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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user