From 6460c228edd5a0302ef57c1fed99b7c7f006379c Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 3 Apr 2026 14:45:34 -0400 Subject: [PATCH] Delete docs/superpowers directory --- ...26-03-18-materialize-new-project-config.md | 700 ------------------ ...6-03-20-multi-project-workspaces-design.md | 185 ----- 2 files changed, 885 deletions(-) delete mode 100644 docs/superpowers/plans/2026-03-18-materialize-new-project-config.md delete mode 100644 docs/superpowers/specs/2026-03-20-multi-project-workspaces-design.md diff --git a/docs/superpowers/plans/2026-03-18-materialize-new-project-config.md b/docs/superpowers/plans/2026-03-18-materialize-new-project-config.md deleted file mode 100644 index 44aaf28f6..000000000 --- a/docs/superpowers/plans/2026-03-18-materialize-new-project-config.md +++ /dev/null @@ -1,700 +0,0 @@ -# Materialize new-project config on initialization - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** When `/gsd:new-project` creates `.planning/config.json`, the file contains all effective defaults — not just the 6 user-chosen keys — so developers can see every setting without reading source code. - -**Architecture:** Add a single JS function `buildNewProjectConfig(cwd, userChoices)` in `config.cjs` as the one source of truth for a new project's full config. Expose it as a CLI command `config-new-project`. Update the `new-project.md` workflow to call this command instead of writing a partial JSON inline. - -**Tech Stack:** Node.js/CommonJS, existing gsd-tools CLI, `node:test` for tests. - ---- - -## Background: what exists today - -`new-project.md` Step 5 writes this partial config (the AI fills the template): - -```json -{ - "mode": "...", "granularity": "...", "parallelization": "...", - "commit_docs": "...", "model_profile": "...", - "workflow": { "research", "plan_check", "verifier", "nyquist_validation" } -} -``` - -Missing keys silently resolved by `loadConfig()` at runtime: - -- `search_gitignored: false` -- `brave_search: false` (or env-detected `true`) -- `git.branching_strategy: "none"` -- `git.phase_branch_template: "gsd/phase-{phase}-{slug}"` -- `git.milestone_branch_template: "gsd/{milestone}-{slug}"` - -Full config that should exist from the start: - -```json -{ - "mode": "yolo|interactive", - "granularity": "coarse|standard|fine", - "model_profile": "balanced", - "commit_docs": true, - "parallelization": true, - "search_gitignored": false, - "brave_search": false, - "git": { - "branching_strategy": "none", - "phase_branch_template": "gsd/phase-{phase}-{slug}", - "milestone_branch_template": "gsd/{milestone}-{slug}" - }, - "workflow": { - "research": true, - "plan_check": true, - "verifier": true, - "nyquist_validation": true - } -} -``` - ---- - -## File map - -| File | Action | Purpose | -|------|--------|---------| -| `get-shit-done/bin/lib/config.cjs` | Modify | Add `buildNewProjectConfig()` + `cmdConfigNewProject()` | -| `get-shit-done/bin/gsd-tools.cjs` | Modify | Register `config-new-project` case + update usage string | -| `get-shit-done/workflows/new-project.md` | Modify | Steps 2a + 5: replace inline JSON write with CLI call | -| `tests/config.test.cjs` | Modify | Add `config-new-project` test suite | - ---- - -## Task 1: Add `buildNewProjectConfig` and `cmdConfigNewProject` to config.cjs - -**Files:** - -- Modify: `get-shit-done/bin/lib/config.cjs` - -- [ ] **Step 1.1: Write the failing tests first** - -Add to `tests/config.test.cjs` (after the `config-get` suite, before `module.exports`): - -```js -// ─── config-new-project ────────────────────────────────────────────────────── - -describe('config-new-project command', () => { - let tmpDir; - - beforeEach(() => { - tmpDir = createTempProject(); - }); - - afterEach(() => { - cleanup(tmpDir); - }); - - test('creates full config with all expected top-level and nested keys', () => { - const choices = JSON.stringify({ - mode: 'interactive', - granularity: 'standard', - parallelization: true, - commit_docs: true, - model_profile: 'balanced', - workflow: { research: true, plan_check: true, verifier: true, nyquist_validation: true }, - }); - const result = runGsdTools(['config-new-project', choices], tmpDir); - assert.ok(result.success, `Command failed: ${result.error}`); - - const config = readConfig(tmpDir); - - // User choices present - assert.strictEqual(config.mode, 'interactive'); - assert.strictEqual(config.granularity, 'standard'); - assert.strictEqual(config.parallelization, true); - assert.strictEqual(config.commit_docs, true); - assert.strictEqual(config.model_profile, 'balanced'); - - // Defaults materialized - assert.strictEqual(typeof config.search_gitignored, 'boolean'); - assert.strictEqual(typeof config.brave_search, 'boolean'); - - // git section present with all three keys - assert.ok(config.git && typeof config.git === 'object', 'git section should exist'); - assert.strictEqual(config.git.branching_strategy, 'none'); - assert.strictEqual(config.git.phase_branch_template, 'gsd/phase-{phase}-{slug}'); - assert.strictEqual(config.git.milestone_branch_template, 'gsd/{milestone}-{slug}'); - - // workflow section present with all four keys - assert.ok(config.workflow && typeof config.workflow === 'object', 'workflow section should exist'); - assert.strictEqual(config.workflow.research, true); - assert.strictEqual(config.workflow.plan_check, true); - assert.strictEqual(config.workflow.verifier, true); - assert.strictEqual(config.workflow.nyquist_validation, true); - }); - - test('user choices override defaults', () => { - const choices = JSON.stringify({ - mode: 'yolo', - granularity: 'coarse', - parallelization: false, - commit_docs: false, - model_profile: 'quality', - workflow: { research: false, plan_check: false, verifier: true, nyquist_validation: false }, - }); - const result = runGsdTools(['config-new-project', choices], tmpDir); - assert.ok(result.success, `Command failed: ${result.error}`); - - const config = readConfig(tmpDir); - assert.strictEqual(config.mode, 'yolo'); - assert.strictEqual(config.granularity, 'coarse'); - assert.strictEqual(config.parallelization, false); - assert.strictEqual(config.commit_docs, false); - assert.strictEqual(config.model_profile, 'quality'); - assert.strictEqual(config.workflow.research, false); - assert.strictEqual(config.workflow.plan_check, false); - assert.strictEqual(config.workflow.verifier, true); - assert.strictEqual(config.workflow.nyquist_validation, false); - // Defaults still present for non-chosen keys - assert.strictEqual(config.git.branching_strategy, 'none'); - assert.strictEqual(typeof config.search_gitignored, 'boolean'); - }); - - test('works with empty choices — all defaults materialized', () => { - const result = runGsdTools(['config-new-project', '{}'], tmpDir); - assert.ok(result.success, `Command failed: ${result.error}`); - - const config = readConfig(tmpDir); - assert.strictEqual(config.model_profile, 'balanced'); - assert.strictEqual(config.commit_docs, true); - assert.strictEqual(config.parallelization, true); - assert.strictEqual(config.search_gitignored, false); - assert.ok(config.git && typeof config.git === 'object'); - assert.strictEqual(config.git.branching_strategy, 'none'); - assert.ok(config.workflow && typeof config.workflow === 'object'); - assert.strictEqual(config.workflow.nyquist_validation, true); - }); - - test('is idempotent — returns already_exists if config exists', () => { - // First call: create - const choices = JSON.stringify({ mode: 'yolo', granularity: 'fine' }); - const first = runGsdTools(['config-new-project', choices], tmpDir); - assert.ok(first.success, `First call failed: ${first.error}`); - const firstOut = JSON.parse(first.output); - assert.strictEqual(firstOut.created, true); - - // Second call: idempotent - const second = runGsdTools(['config-new-project', choices], tmpDir); - assert.ok(second.success, `Second call failed: ${second.error}`); - const secondOut = JSON.parse(second.output); - assert.strictEqual(secondOut.created, false); - assert.strictEqual(secondOut.reason, 'already_exists'); - - // Config unchanged - const config = readConfig(tmpDir); - assert.strictEqual(config.mode, 'yolo'); - assert.strictEqual(config.granularity, 'fine'); - }); - - test('auto_advance in workflow choices is preserved', () => { - const choices = JSON.stringify({ - mode: 'yolo', - granularity: 'standard', - workflow: { research: true, plan_check: true, verifier: true, nyquist_validation: true, auto_advance: true }, - }); - const result = runGsdTools(['config-new-project', choices], tmpDir); - assert.ok(result.success, `Command failed: ${result.error}`); - - const config = readConfig(tmpDir); - assert.strictEqual(config.workflow.auto_advance, true); - }); - - test('rejects invalid JSON choices', () => { - const result = runGsdTools(['config-new-project', '{not-json}'], tmpDir); - assert.strictEqual(result.success, false); - assert.ok(result.error.includes('Invalid JSON'), `Expected "Invalid JSON" in: ${result.error}`); - }); - - test('output JSON has created:true on success', () => { - const choices = JSON.stringify({ mode: 'interactive', granularity: 'standard' }); - const result = runGsdTools(['config-new-project', choices], tmpDir); - assert.ok(result.success, `Command failed: ${result.error}`); - const out = JSON.parse(result.output); - assert.strictEqual(out.created, true); - assert.strictEqual(out.path, '.planning/config.json'); - }); -}); -``` - -- [ ] **Step 1.2: Run failing tests to confirm they fail** - -```bash -cd /Users/diego/Dev/get-shit-done -node --test tests/config.test.cjs 2>&1 | grep -E "config-new-project|FAIL|Error" -``` - -Expected: All `config-new-project` tests fail with "config-new-project is not a valid command" or similar. - -- [ ] **Step 1.3: Implement `buildNewProjectConfig` and `cmdConfigNewProject` in config.cjs** - -In `get-shit-done/bin/lib/config.cjs`, add the following after the `validateKnownConfigKeyPath` function (around line 35) and before `ensureConfigFile`: - -```js -/** - * Build a fully-materialized config for a new project. - * - * Merges (in order of increasing priority): - * 1. Hardcoded defaults - * 2. User-level defaults from ~/.gsd/defaults.json (if present) - * 3. userChoices (the settings the user explicitly selected during new-project) - * - * Returns a plain object — does NOT write any files. - */ -function buildNewProjectConfig(cwd, userChoices) { - const choices = userChoices || {}; - const homedir = require('os').homedir(); - - // Detect Brave Search API key availability - const braveKeyFile = path.join(homedir, '.gsd', 'brave_api_key'); - const hasBraveSearch = !!(process.env.BRAVE_API_KEY || fs.existsSync(braveKeyFile)); - - // Load user-level defaults from ~/.gsd/defaults.json if available - const globalDefaultsPath = path.join(homedir, '.gsd', 'defaults.json'); - let userDefaults = {}; - try { - if (fs.existsSync(globalDefaultsPath)) { - userDefaults = JSON.parse(fs.readFileSync(globalDefaultsPath, 'utf-8')); - // Migrate deprecated "depth" key to "granularity" - if ('depth' in userDefaults && !('granularity' in userDefaults)) { - const depthToGranularity = { quick: 'coarse', standard: 'standard', comprehensive: 'fine' }; - userDefaults.granularity = depthToGranularity[userDefaults.depth] || userDefaults.depth; - delete userDefaults.depth; - try { - fs.writeFileSync(globalDefaultsPath, JSON.stringify(userDefaults, null, 2), 'utf-8'); - } catch {} - } - } - } catch { - // Ignore malformed global defaults - } - - const hardcoded = { - model_profile: 'balanced', - commit_docs: true, - parallelization: true, - search_gitignored: false, - brave_search: hasBraveSearch, - git: { - branching_strategy: 'none', - phase_branch_template: 'gsd/phase-{phase}-{slug}', - milestone_branch_template: 'gsd/{milestone}-{slug}', - }, - workflow: { - research: true, - plan_check: true, - verifier: true, - nyquist_validation: true, - }, - }; - - // Three-level merge: hardcoded <- userDefaults <- choices - return { - ...hardcoded, - ...userDefaults, - ...choices, - git: { - ...hardcoded.git, - ...(userDefaults.git || {}), - ...(choices.git || {}), - }, - workflow: { - ...hardcoded.workflow, - ...(userDefaults.workflow || {}), - ...(choices.workflow || {}), - }, - }; -} - -/** - * Command: create a fully-materialized .planning/config.json for a new project. - * - * Accepts user-chosen settings as a JSON string (the keys the user explicitly - * configured during /gsd:new-project). All remaining keys are filled from - * hardcoded defaults and optional ~/.gsd/defaults.json. - * - * Idempotent: if config.json already exists, returns { created: false }. - */ -function cmdConfigNewProject(cwd, choicesJson, raw) { - const configPath = path.join(cwd, '.planning', 'config.json'); - const planningDir = path.join(cwd, '.planning'); - - // Idempotent: don't overwrite existing config - if (fs.existsSync(configPath)) { - output({ created: false, reason: 'already_exists' }, raw, 'exists'); - return; - } - - // Parse user choices - let userChoices = {}; - if (choicesJson && choicesJson.trim() !== '') { - try { - userChoices = JSON.parse(choicesJson); - } catch (err) { - error('Invalid JSON for config-new-project: ' + err.message); - } - } - - // Ensure .planning directory exists - try { - if (!fs.existsSync(planningDir)) { - fs.mkdirSync(planningDir, { recursive: true }); - } - } catch (err) { - error('Failed to create .planning directory: ' + err.message); - } - - const config = buildNewProjectConfig(cwd, userChoices); - - try { - fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8'); - output({ created: true, path: '.planning/config.json' }, raw, 'created'); - } catch (err) { - error('Failed to write config.json: ' + err.message); - } -} -``` - -Also add `cmdConfigNewProject` to the `module.exports` at the bottom of `config.cjs`. - -- [ ] **Step 1.4: Run tests to verify they pass** - -```bash -cd /Users/diego/Dev/get-shit-done -node --test tests/config.test.cjs 2>&1 | tail -20 -``` - -Expected: All `config-new-project` tests pass. Existing tests still pass. - -- [ ] **Step 1.5: Commit** - -```bash -cd /Users/diego/Dev/get-shit-done -git add get-shit-done/bin/lib/config.cjs tests/config.test.cjs -git commit -m "feat: add config-new-project command for full config materialization" -``` - ---- - -## Task 2: Register `config-new-project` in gsd-tools.cjs - -**Files:** - -- Modify: `get-shit-done/bin/gsd-tools.cjs` - -- [ ] **Step 2.1: Add the case to the switch in gsd-tools.cjs** - -After the `config-get` case (around line 401), add: - -```js - case 'config-new-project': { - config.cmdConfigNewProject(cwd, args[1], raw); - break; - } -``` - -Also update the usage string on line 178 to include `config-new-project`: - -Current: `...config-ensure-section, init` -New: `...config-ensure-section, config-new-project, init` - -- [ ] **Step 2.2: Smoke-test the CLI registration** - -```bash -cd /Users/diego/Dev/get-shit-done -node get-shit-done/bin/gsd-tools.cjs config-new-project '{"mode":"interactive","granularity":"standard"}' --cwd /tmp/gsd-smoke-$(date +%s) -``` - -Expected: outputs `{"created":true,"path":".planning/config.json"}` (or similar). - -Clean up: `rm -rf /tmp/gsd-smoke-*` - -- [ ] **Step 2.3: Run full test suite** - -```bash -cd /Users/diego/Dev/get-shit-done -node --test tests/config.test.cjs 2>&1 | tail -10 -``` - -Expected: All pass. - -- [ ] **Step 2.4: Commit** - -```bash -cd /Users/diego/Dev/get-shit-done -git add get-shit-done/bin/gsd-tools.cjs -git commit -m "feat: register config-new-project in gsd-tools CLI router" -``` - ---- - -## Task 3: Update new-project.md workflow to use config-new-project - -**Files:** - -- Modify: `get-shit-done/workflows/new-project.md` - -This is the core change. Two places need updating: - -- **Step 2a** (auto mode config creation, around line 168–195) -- **Step 5** (interactive mode config creation, around line 470–498) - -- [ ] **Step 3.1: Update Step 2a (auto mode)** - -Find the block in Step 2a that creates config.json: - -```markdown -Create `.planning/config.json` with mode set to "yolo": - -```json -{ - "mode": "yolo", - "granularity": "[selected]", - ... -} -``` - -``` - -Replace the inline JSON write instruction with: - -```markdown -Create `.planning/config.json` using the CLI (fills in all defaults automatically): - -```bash -mkdir -p .planning -node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-new-project "$(cat <<'CHOICES' -{ - "mode": "yolo", - "granularity": "[selected: coarse|standard|fine]", - "parallelization": [true|false], - "commit_docs": [true|false], - "model_profile": "[selected: quality|balanced|budget|inherit]", - "workflow": { - "research": [true|false], - "plan_check": [true|false], - "verifier": [true|false], - "nyquist_validation": [true|false], - "auto_advance": true - } -} -CHOICES -)" -``` - -The command merges your selections with all runtime defaults (`search_gitignored`, `brave_search`, `git` section), producing a fully-materialized config. - -``` - -- [ ] **Step 3.2: Update Step 5 (interactive mode)** - -Find the block in Step 5 that creates config.json: - -```markdown -Create `.planning/config.json` with all settings: - -```json -{ - "mode": "yolo|interactive", - ... -} -``` - -``` - -Replace with: - -```markdown -Create `.planning/config.json` using the CLI (fills in all defaults automatically): - -```bash -mkdir -p .planning -node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-new-project "$(cat <<'CHOICES' -{ - "mode": "[selected: yolo|interactive]", - "granularity": "[selected: coarse|standard|fine]", - "parallelization": [true|false], - "commit_docs": [true|false], - "model_profile": "[selected: quality|balanced|budget|inherit]", - "workflow": { - "research": [true|false], - "plan_check": [true|false], - "verifier": [true|false], - "nyquist_validation": [true|false] - } -} -CHOICES -)" -``` - -The command merges your selections with all runtime defaults (`search_gitignored`, `brave_search`, `git` section), producing a fully-materialized config. - -``` - -- [ ] **Step 3.3: Verify the workflow file reads correctly** - -```bash -cd /Users/diego/Dev/get-shit-done -grep -n "config-new-project\|config\.json\|CHOICES" get-shit-done/workflows/new-project.md -``` - -Expected: 2 occurrences of `config-new-project` (one per step), no more inline JSON templates for config creation. - -- [ ] **Step 3.4: Commit** - -```bash -cd /Users/diego/Dev/get-shit-done -git add get-shit-done/workflows/new-project.md -git commit -m "feat: use config-new-project in new-project workflow for full config materialization" -``` - ---- - -## Task 4: Validation - -- [ ] **Step 4.1: Run the full test suite** - -```bash -cd /Users/diego/Dev/get-shit-done -node --test tests/ 2>&1 | tail -30 -``` - -Expected: All tests pass (no regressions). - -- [ ] **Step 4.2: Manual end-to-end validation** - -Simulate what `new-project.md` does for a new project: - -```bash -# Create a fresh project dir -TMP=$(mktemp -d) -cd "$TMP" - -# Step 1 simulation: what init new-project returns -node /Users/diego/Dev/get-shit-done/get-shit-done/bin/gsd-tools.cjs init new-project --cwd "$TMP" - -# Step 5 simulation: create full config -node /Users/diego/Dev/get-shit-done/get-shit-done/bin/gsd-tools.cjs config-new-project '{ - "mode": "interactive", - "granularity": "standard", - "parallelization": true, - "commit_docs": true, - "model_profile": "balanced", - "workflow": { - "research": true, - "plan_check": true, - "verifier": true, - "nyquist_validation": true - } -}' --cwd "$TMP" - -# Verify the file has all 12 expected keys -echo "=== Generated config.json ===" -cat "$TMP/.planning/config.json" - -# Clean up -rm -rf "$TMP" -``` - -Expected output: a config.json with `mode`, `granularity`, `model_profile`, `commit_docs`, `parallelization`, `search_gitignored`, `brave_search`, `git` (3 sub-keys), `workflow` (4 sub-keys) — 12 top-level keys total (or 10 if counting `git` and `workflow` as single keys). - -- [ ] **Step 4.3: Verify idempotency** - -```bash -TMP=$(mktemp -d) -CHOICES='{"mode":"yolo","granularity":"coarse"}' - -node /Users/diego/Dev/get-shit-done/get-shit-done/bin/gsd-tools.cjs config-new-project "$CHOICES" --cwd "$TMP" -FIRST=$(cat "$TMP/.planning/config.json") - -# Second call should be no-op -node /Users/diego/Dev/get-shit-done/get-shit-done/bin/gsd-tools.cjs config-new-project "$CHOICES" --cwd "$TMP" -SECOND=$(cat "$TMP/.planning/config.json") - -[ "$FIRST" = "$SECOND" ] && echo "IDEMPOTENT: OK" || echo "IDEMPOTENT: FAIL" -rm -rf "$TMP" -``` - -Expected: `IDEMPOTENT: OK` - -- [ ] **Step 4.4: Verify loadConfig still reads the new format correctly** - -```bash -TMP=$(mktemp -d) -node /Users/diego/Dev/get-shit-done/get-shit-done/bin/gsd-tools.cjs config-new-project '{ - "mode":"yolo","granularity":"standard","parallelization":true,"commit_docs":true, - "model_profile":"balanced", - "workflow":{"research":true,"plan_check":false,"verifier":true,"nyquist_validation":true} -}' --cwd "$TMP" - -# loadConfig should correctly read plan_check (nested as workflow.plan_check) -node /Users/diego/Dev/get-shit-done/get-shit-done/bin/gsd-tools.cjs config-get workflow.plan_check --cwd "$TMP" -# Expected: false - -node /Users/diego/Dev/get-shit-done/get-shit-done/bin/gsd-tools.cjs config-get git.branching_strategy --cwd "$TMP" -# Expected: "none" - -rm -rf "$TMP" -``` - -- [ ] **Step 4.5: Final full test suite + commit** - -```bash -cd /Users/diego/Dev/get-shit-done -node --test tests/ 2>&1 | grep -E "pass|fail|error" | tail -5 -``` - -Expected: All pass, 0 failures. - ---- - -## Appendix: PR description for upstream - -``` -feat: materialize all config defaults at new-project initialization - -**Problem:** -`/gsd:new-project` creates `.planning/config.json` with only the 6 keys -the user explicitly chose during onboarding. Five additional keys -(`search_gitignored`, `brave_search`, `git.branching_strategy`, -`git.phase_branch_template`, `git.milestone_branch_template`) are resolved -silently by `loadConfig()` at runtime but never written to disk. - -This creates two problems: -1. **Discoverability**: users can't see or understand `git.branching_strategy` - without reading source code — it doesn't appear in their config. -2. **Implicit expansion**: the first time `/gsd:settings` or `config-set` - writes to the config, those keys still aren't added. The config only - reflects a fraction of the effective configuration. - -**Solution:** -Add `config-new-project` CLI command to `gsd-tools.cjs`. The command: -- Accepts user-chosen values as JSON -- Merges them with all runtime defaults (including env-detected `brave_search`) -- Writes the fully-materialized config in one shot - -Update `new-project.md` workflow (Steps 2a and 5) to call this command -instead of writing a hardcoded partial JSON template. Defaults now live in -exactly one place: `buildNewProjectConfig()` in `config.cjs`. - -**Why this is conservative:** -- No changes to `loadConfig()`, `ensureConfigFile()`, or any read path -- No new config keys introduced -- No semantic changes — same values the system was already resolving silently -- Fully backward-compatible: `loadConfig()` continues to handle both the old - partial format (existing projects) and the new full format -- Idempotent: calling `config-new-project` twice is safe -- No new user-facing flags - -**Why this improves discoverability:** -A developer opening `.planning/config.json` for the first time can now see -`git.branching_strategy: "none"` and immediately understand that branching -is available and configurable, without reading the GSD source. -``` diff --git a/docs/superpowers/specs/2026-03-20-multi-project-workspaces-design.md b/docs/superpowers/specs/2026-03-20-multi-project-workspaces-design.md deleted file mode 100644 index 1953dd5c2..000000000 --- a/docs/superpowers/specs/2026-03-20-multi-project-workspaces-design.md +++ /dev/null @@ -1,185 +0,0 @@ -# Multi-Project Workspaces (`/gsd:new-workspace`) - -**Issue:** #1241 -**Date:** 2026-03-20 -**Status:** Approved - -## Problem - -GSD is tied to one `.planning/` directory per working directory. Users with multiple independent projects (monorepo-style setups with 20+ child repos) or users needing feature branch isolation in the same repo cannot run parallel GSD sessions without manual cloning and state management. - -## Solution - -Three new commands that create, list, and remove **physical workspace directories** — each containing repo copies (git worktrees or clones) and an independent `.planning/` directory. - -This covers two use cases: -- **Multi-repo orchestration (A):** Workspace spanning multiple repos from a parent directory -- **Feature branch isolation (B):** Workspace containing a worktree of the current repo (special case of A where `--repos .`) - -## Commands - -### `/gsd:new-workspace` - -Creates a workspace directory with repo copies and its own `.planning/`. - -``` -/gsd:new-workspace --name feature-b --repos hr-ui,ZeymoAPI --path ~/workspaces/feature-b -/gsd:new-workspace --name feature-b --repos . --strategy worktree # same-repo isolation -``` - -**Arguments:** - -| Flag | Required | Default | Description | -|------|----------|---------|-------------| -| `--name` | Yes | — | Workspace name | -| `--repos` | No | Interactive selection | Comma-separated repo paths or names | -| `--path` | No | `~/gsd-workspaces/` | Target directory | -| `--strategy` | No | `worktree` | `worktree` (lightweight, shared .git) or `clone` (fully independent) | -| `--branch` | No | `workspace/` | Branch to checkout | -| `--auto` | No | false | Skip interactive questions, use defaults | - -### `/gsd:list-workspaces` - -Scans `~/gsd-workspaces/*/WORKSPACE.md` for workspace manifests. Displays table with name, path, repo count, GSD status (has PROJECT.md, current phase). - -### `/gsd:remove-workspace` - -Removes a workspace directory after confirmation. For worktree strategy, runs `git worktree remove` for each member repo first. Refuses if any repo has uncommitted changes. - -## Directory Structure - -``` -~/gsd-workspaces/feature-b/ # workspace root -├── WORKSPACE.md # manifest -├── .planning/ # independent GSD planning directory -│ ├── PROJECT.md # (if user ran /gsd:new-project) -│ ├── STATE.md -│ └── config.json -├── hr-ui/ # git worktree of source repo -│ └── (repo contents on workspace/feature-b branch) -└── ZeymoAPI/ # git worktree of source repo - └── (repo contents on workspace/feature-b branch) -``` - -Key properties: -- `.planning/` is at the workspace root, not inside any individual repo -- Each repo is a peer directory under the workspace root -- `WORKSPACE.md` is the only GSD-specific file at the root (besides `.planning/`) -- For `--strategy clone`, same structure but repos are full clones - -## WORKSPACE.md Format - -```markdown -# Workspace: feature-b - -Created: 2026-03-20 -Strategy: worktree - -## Member Repos - -| Repo | Source | Branch | Strategy | -|------|--------|--------|----------| -| hr-ui | /root/source/repos/hr-ui | workspace/feature-b | worktree | -| ZeymoAPI | /root/source/repos/ZeymoAPI | workspace/feature-b | worktree | - -## Notes - -[User can add context about what this workspace is for] -``` - -## Workflow - -### `/gsd:new-workspace` Workflow Steps - -1. **Setup** — Call `init new-workspace`, parse JSON context -2. **Gather inputs** — If `--name`/`--repos`/`--path` not provided, ask interactively. For repos, show child `.git` directories in cwd as options -3. **Validate** — Target path doesn't exist (or is empty). Source repos exist and are git repos -4. **Create workspace directory** — `mkdir -p ` -5. **Copy repos** — For each repo: - - Worktree: `git worktree add / -b workspace/` - - Clone: `git clone /` -6. **Write WORKSPACE.md** — Manifest with source paths, strategy, branch -7. **Initialize .planning/** — `mkdir -p /.planning` -8. **Offer /gsd:new-project** — Ask if user wants to run project initialization in the new workspace -9. **Commit** — If commit_docs enabled, atomic commit of WORKSPACE.md -10. **Done** — Print workspace path and next steps - -### Init Function (`cmdInitNewWorkspace`) - -Detects: -- Child git repos in cwd (for interactive repo selection) -- Whether target path already exists -- Whether source repos have uncommitted changes -- Whether `git worktree` is available -- Default workspace base dir (`~/gsd-workspaces/`) - -Returns JSON with flags for workflow gating. - -## Error Handling - -### Validation Errors (Block Creation) - -- **Target path exists and is non-empty** — Error with suggestion to pick a different name/path -- **Source repo path doesn't exist or isn't a git repo** — Error listing which repos failed -- **`git worktree add` fails** (e.g., branch exists) — Fall back to `workspace/-` branch, or error if that also fails - -### Graceful Handling - -- **Source repo has uncommitted changes** — Warn but allow (worktrees checkout the branch fresh, don't copy working directory state) -- **Partial failure in multi-repo workspace** — Create workspace with repos that succeeded, report failures, write partial WORKSPACE.md -- **`--repos .` (current repo, case B)** — Detect repo name from directory name or git remote, use as subdirectory name - -### Remove-Workspace Safety - -- **Uncommitted changes in workspace repos** — Refuse removal, print which repos have changes -- **Worktree removal fails** (e.g., source repo deleted) — Warn and continue with directory cleanup -- **Confirmation** — Require explicit confirmation with workspace name typed out - -### List-Workspaces Edge Cases - -- **`~/gsd-workspaces/` doesn't exist** — "No workspaces found" -- **WORKSPACE.md exists but repos inside are gone** — Show workspace, mark repos as missing - -## Testing - -### Unit Tests (`tests/workspace.test.cjs`) - -1. `cmdInitNewWorkspace` returns correct JSON — detects child git repos, validates target path, detects git worktree availability -2. WORKSPACE.md generation — correct format with repo table, strategy, date -3. Repo discovery — identifies `.git` directories in cwd children, skips non-git directories and files -4. Validation — rejects existing non-empty target paths, rejects non-git source paths - -### Integration Tests (same file) - -5. Worktree creation — creates workspace, verifies repo directories are valid git worktrees -6. Clone creation — creates workspace, verifies repos are independent clones -7. List workspaces — creates two workspaces, verifies list output includes both -8. Remove workspace — creates workspace with worktrees, removes it, verifies cleanup -9. Partial failure — one valid repo + one invalid path, workspace created with valid repo only - -All tests use temp directories and clean up after themselves. Follow existing `node:test` + `node:assert` patterns. - -## Implementation Files - -| Component | Path | -|-----------|------| -| Command: new-workspace | `commands/gsd/new-workspace.md` | -| Command: list-workspaces | `commands/gsd/list-workspaces.md` | -| Command: remove-workspace | `commands/gsd/remove-workspace.md` | -| Workflow: new-workspace | `get-shit-done/workflows/new-workspace.md` | -| Workflow: list-workspaces | `get-shit-done/workflows/list-workspaces.md` | -| Workflow: remove-workspace | `get-shit-done/workflows/remove-workspace.md` | -| Init function | `get-shit-done/bin/lib/init.cjs` (add `cmdInitNewWorkspace`, `cmdInitListWorkspaces`, `cmdInitRemoveWorkspace`) | -| Routing | `get-shit-done/bin/gsd-tools.cjs` (add cases to init switch) | -| Tests | `tests/workspace.test.cjs` | - -## Design Decisions - -| Decision | Rationale | -|----------|-----------| -| Physical directories over logical registry | Filesystem is source of truth — matches GSD's existing cwd-based detection pattern | -| Worktree as default strategy | Lightweight (shared .git objects), fast to create, easy to clean up | -| `.planning/` at workspace root | Gives full isolation from individual repo planning. Each workspace is an independent GSD project | -| No central registry | Avoids state drift. `list-workspaces` scans the filesystem directly | -| Case B as special case of A | `--repos .` reuses the same machinery, no special feature-branch code needed | -| Default path `~/gsd-workspaces/` | Predictable location for `list-workspaces` to scan, keeps workspaces out of source repos |