diff --git a/get-shit-done/bin/gsd-tools.cjs b/get-shit-done/bin/gsd-tools.cjs
index c9002259f..4ebff20ac 100755
--- a/get-shit-done/bin/gsd-tools.cjs
+++ b/get-shit-done/bin/gsd-tools.cjs
@@ -852,9 +852,9 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
// Escape special regex chars in phase number, handle decimal
const escapedPhase = phaseNum.replace(/\./g, '\\.');
- // Match "## Phase X:" or "### Phase X:" with optional name
+ // Match "## Phase X:", "### Phase X:", or "#### Phase X:" with optional name
const phasePattern = new RegExp(
- `#{2,3}\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`,
+ `#{2,4}\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`,
'i'
);
const headerMatch = content.match(phasePattern);
@@ -888,7 +888,7 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
// Find the end of this section (next ## or ### phase header, or end of file)
const restOfContent = content.slice(headerIndex);
- const nextHeaderMatch = restOfContent.match(/\n#{2,3}\s+Phase\s+\d/i);
+ const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+Phase\s+\d/i);
const sectionEnd = nextHeaderMatch
? headerIndex + nextHeaderMatch.index
: content.length;
@@ -2475,7 +2475,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
const phasesDir = path.join(cwd, '.planning', 'phases');
// Extract all phase headings: ## Phase N: Name or ### Phase N: Name
- const phasePattern = /#{2,3}\s*Phase\s+(\d+(?:\.\d+)?)\s*:\s*([^\n]+)/gi;
+ const phasePattern = /#{2,4}\s*Phase\s+(\d+(?:\.\d+)?)\s*:\s*([^\n]+)/gi;
const phases = [];
let match;
@@ -2486,7 +2486,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
// Extract goal from the section
const sectionStart = match.index;
const restOfContent = content.slice(sectionStart);
- const nextHeader = restOfContent.match(/\n#{2,3}\s+Phase\s+\d/i);
+ const nextHeader = restOfContent.match(/\n#{2,4}\s+Phase\s+\d/i);
const sectionEnd = nextHeader ? sectionStart + nextHeader.index : content.length;
const section = content.slice(sectionStart, sectionEnd);
@@ -2606,7 +2606,7 @@ function cmdPhaseAdd(cwd, description, raw) {
const slug = generateSlugInternal(description);
// Find highest integer phase number
- const phasePattern = /#{2,3}\s*Phase\s+(\d+)(?:\.\d+)?:/gi;
+ const phasePattern = /#{2,4}\s*Phase\s+(\d+)(?:\.\d+)?:/gi;
let maxPhase = 0;
let m;
while ((m = phasePattern.exec(content)) !== null) {
@@ -2666,7 +2666,7 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw) {
const normalizedAfter = normalizePhaseName(afterPhase);
const unpadded = normalizedAfter.replace(/^0+/, '');
const afterPhaseEscaped = unpadded.replace(/\./g, '\\.');
- const targetPattern = new RegExp(`#{2,3}\\s*Phase\\s+0*${afterPhaseEscaped}:`, 'i');
+ const targetPattern = new RegExp(`#{2,4}\\s*Phase\\s+0*${afterPhaseEscaped}:`, 'i');
if (!targetPattern.test(content)) {
error(`Phase ${afterPhase} not found in ROADMAP.md`);
}
@@ -2698,7 +2698,7 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw) {
const phaseEntry = `\n### Phase ${decimalPhase}: ${description} (INSERTED)\n\n**Goal:** [Urgent work - to be planned]\n**Depends on:** Phase ${afterPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /gsd:plan-phase ${decimalPhase} to break down)\n`;
// Insert after the target phase section
- const headerPattern = new RegExp(`(#{2,3}\\s*Phase\\s+0*${afterPhaseEscaped}:[^\\n]*\\n)`, 'i');
+ const headerPattern = new RegExp(`(#{2,4}\\s*Phase\\s+0*${afterPhaseEscaped}:[^\\n]*\\n)`, 'i');
const headerMatch = content.match(headerPattern);
if (!headerMatch) {
error(`Could not find Phase ${afterPhase} header`);
@@ -2706,7 +2706,7 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw) {
const headerIdx = content.indexOf(headerMatch[0]);
const afterHeader = content.slice(headerIdx + headerMatch[0].length);
- const nextPhaseMatch = afterHeader.match(/\n#{2,3}\s+Phase\s+\d/i);
+ const nextPhaseMatch = afterHeader.match(/\n#{2,4}\s+Phase\s+\d/i);
let insertIdx;
if (nextPhaseMatch) {
@@ -2889,7 +2889,7 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
// Remove the target phase section
const targetEscaped = targetPhase.replace(/\./g, '\\.');
const sectionPattern = new RegExp(
- `\\n?#{2,3}\\s*Phase\\s+${targetEscaped}\\s*:[\\s\\S]*?(?=\\n#{2,3}\\s+Phase\\s+\\d|$)`,
+ `\\n?#{2,4}\\s*Phase\\s+${targetEscaped}\\s*:[\\s\\S]*?(?=\\n#{2,4}\\s+Phase\\s+\\d|$)`,
'i'
);
roadmapContent = roadmapContent.replace(sectionPattern, '');
@@ -2917,7 +2917,7 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
// Phase headings: ## Phase 18: or ### Phase 18: → ## Phase 17: or ### Phase 17:
roadmapContent = roadmapContent.replace(
- new RegExp(`(#{2,3}\\s*Phase\\s+)${oldStr}(\\s*:)`, 'gi'),
+ new RegExp(`(#{2,4}\\s*Phase\\s+)${oldStr}(\\s*:)`, 'gi'),
`$1${newStr}$2`
);
@@ -3029,7 +3029,7 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
// Update plan count in phase detail section
const planCountPattern = new RegExp(
- `(#{2,3}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,
+ `(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,
'i'
);
const planCountText = isComplete
@@ -3104,7 +3104,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
// Update plan count in phase section
const planCountPattern = new RegExp(
- `(#{2,3}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,
+ `(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,
'i'
);
roadmapContent = roadmapContent.replace(
@@ -3368,7 +3368,7 @@ function cmdValidateConsistency(cwd, raw) {
// Extract phases from ROADMAP
const roadmapPhases = new Set();
- const phasePattern = /#{2,3}\s*Phase\s+(\d+(?:\.\d+)?)\s*:/gi;
+ const phasePattern = /#{2,4}\s*Phase\s+(\d+(?:\.\d+)?)\s*:/gi;
let m;
while ((m = phasePattern.exec(roadmapContent)) !== null) {
roadmapPhases.add(m[1]);
@@ -3712,14 +3712,14 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
try {
const content = fs.readFileSync(roadmapPath, 'utf-8');
const escapedPhase = phaseNum.toString().replace(/\./g, '\\.');
- const phasePattern = new RegExp(`#{2,3}\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`, 'i');
+ const phasePattern = new RegExp(`#{2,4}\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`, 'i');
const headerMatch = content.match(phasePattern);
if (!headerMatch) return null;
const phaseName = headerMatch[1].trim();
const headerIndex = headerMatch.index;
const restOfContent = content.slice(headerIndex);
- const nextHeaderMatch = restOfContent.match(/\n#{2,3}\s+Phase\s+\d/i);
+ const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+Phase\s+\d/i);
const sectionEnd = nextHeaderMatch ? headerIndex + nextHeaderMatch.index : content.length;
const section = content.slice(headerIndex, sectionEnd).trim();
diff --git a/get-shit-done/workflows/new-project.md b/get-shit-done/workflows/new-project.md
index 868fb7358..656b29be7 100644
--- a/get-shit-done/workflows/new-project.md
+++ b/get-shit-done/workflows/new-project.md
@@ -217,6 +217,26 @@ node ~/.claude/get-shit-done/bin/gsd-tools.cjs commit "docs: initialize project"
## 5. Workflow Preferences
+**Check for global defaults** at `~/.gsd/defaults.json`. If the file exists, offer to use saved defaults:
+
+```
+AskUserQuestion([
+ {
+ question: "Use your saved default settings? (from ~/.gsd/defaults.json)",
+ header: "Defaults",
+ multiSelect: false,
+ options: [
+ { label: "Yes (Recommended)", description: "Use saved defaults, skip settings questions" },
+ { label: "No", description: "Configure settings manually" }
+ ]
+ }
+])
+```
+
+If "Yes": read `~/.gsd/defaults.json`, use those values for config.json, and skip directly to **Commit config.json** below.
+
+If "No" or `~/.gsd/defaults.json` doesn't exist: proceed with the questions below.
+
**Round 1 — Core workflow settings (4 questions):**
```
diff --git a/get-shit-done/workflows/settings.md b/get-shit-done/workflows/settings.md
index 179bbb6a6..9fe38cb8c 100644
--- a/get-shit-done/workflows/settings.md
+++ b/get-shit-done/workflows/settings.md
@@ -1,5 +1,5 @@
-Interactive configuration of GSD workflow agents (research, plan_check, verifier) and model profile selection via multi-question prompt. Updates .planning/config.json with user preferences.
+Interactive configuration of GSD workflow agents (research, plan_check, verifier) and model profile selection via multi-question prompt. Updates .planning/config.json with user preferences. Optionally saves settings as global defaults (~/.gsd/defaults.json) for future projects.
@@ -119,6 +119,48 @@ Merge new settings into existing config.json:
Write updated config to `.planning/config.json`.
+
+Ask whether to save these settings as global defaults for future projects:
+
+```
+AskUserQuestion([
+ {
+ question: "Save these as default settings for all new projects?",
+ header: "Defaults",
+ multiSelect: false,
+ options: [
+ { label: "Yes", description: "New projects start with these settings (saved to ~/.gsd/defaults.json)" },
+ { label: "No", description: "Only apply to this project" }
+ ]
+ }
+])
+```
+
+If "Yes": write the same config object (minus project-specific fields like `brave_search`) to `~/.gsd/defaults.json`:
+
+```bash
+mkdir -p ~/.gsd
+```
+
+Write `~/.gsd/defaults.json` with:
+```json
+{
+ "mode": ,
+ "depth": ,
+ "model_profile": ,
+ "commit_docs": ,
+ "parallelization": ,
+ "branching_strategy": ,
+ "workflow": {
+ "research": ,
+ "plan_check": ,
+ "verifier": ,
+ "auto_advance":
+ }
+}
+```
+
+
Display:
@@ -135,6 +177,7 @@ Display:
| Execution Verifier | {On/Off} |
| Auto-Advance | {On/Off} |
| Git Branching | {None/Per Phase/Per Milestone} |
+| Saved as Defaults | {Yes/No} |
These settings apply to future /gsd:plan-phase and /gsd:execute-phase runs.
@@ -152,5 +195,6 @@ Quick commands:
- [ ] Current config read
- [ ] User presented with 6 settings (profile + 4 workflow toggles + git branching)
- [ ] Config updated with model_profile, workflow, and git sections
+- [ ] User offered to save as global defaults (~/.gsd/defaults.json)
- [ ] Changes confirmed to user