From 37bb14eaf8dcd15e524f2a8805a9ad3b915860df Mon Sep 17 00:00:00 2001 From: Lex Christopherson Date: Sun, 15 Feb 2026 11:14:58 -0600 Subject: [PATCH] feat(config): add user-level default settings via ~/.gsd/defaults.json (closes #492) New projects seed config from ~/.gsd/defaults.json when available, skipping the 8 setup questions. /gsd:settings offers to save current settings as global defaults after configuration. Co-Authored-By: Claude Opus 4.6 --- get-shit-done/bin/gsd-tools.cjs | 32 +++++++++--------- get-shit-done/workflows/new-project.md | 20 +++++++++++ get-shit-done/workflows/settings.md | 46 +++++++++++++++++++++++++- 3 files changed, 81 insertions(+), 17 deletions(-) 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