diff --git a/get-shit-done/bin/gsd-tools.cjs b/get-shit-done/bin/gsd-tools.cjs index a7cd70377..4ee194ec1 100755 --- a/get-shit-done/bin/gsd-tools.cjs +++ b/get-shit-done/bin/gsd-tools.cjs @@ -181,10 +181,6 @@ async function main() { cwd = worktreeRoot; } - // Multi-repo guard: if CWD is inside a sub-repo, walk up to the project root - // so .planning/ is read/written at the correct level. - cwd = findProjectRoot(cwd); - const rawIndex = args.indexOf('--raw'); const raw = rawIndex !== -1; if (rawIndex !== -1) args.splice(rawIndex, 1); @@ -195,6 +191,17 @@ async function main() { error('Usage: gsd-tools [args] [--raw] [--cwd ]\nCommands: state, resolve-model, find-phase, commit, verify-summary, verify, frontmatter, template, generate-slug, current-timestamp, list-todos, verify-path-exists, config-ensure-section, init'); } + // Multi-repo guard: resolve project root for commands that read/write .planning/. + // Skip for pure-utility commands that don't touch .planning/ to avoid unnecessary + // filesystem traversal on every invocation. + const SKIP_ROOT_RESOLUTION = new Set([ + 'generate-slug', 'current-timestamp', 'verify-path-exists', + 'verify-summary', 'template', 'frontmatter', + ]); + if (!SKIP_ROOT_RESOLUTION.has(command)) { + cwd = findProjectRoot(cwd); + } + switch (command) { case 'state': { const subcommand = args[1]; diff --git a/get-shit-done/bin/lib/commands.cjs b/get-shit-done/bin/lib/commands.cjs index 27461c393..f0d95a3a0 100644 --- a/get-shit-done/bin/lib/commands.cjs +++ b/get-shit-done/bin/lib/commands.cjs @@ -298,6 +298,10 @@ function cmdCommitToSubrepo(cwd, message, files, raw) { } } + if (unmatched.length > 0) { + process.stderr.write(`Warning: ${unmatched.length} file(s) did not match any sub-repo prefix: ${unmatched.join(', ')}\n`); + } + const repos = {}; for (const [repo, repoFiles] of Object.entries(grouped)) { const repoCwd = path.join(cwd, repo); diff --git a/get-shit-done/references/git-integration.md b/get-shit-done/references/git-integration.md index 2b6a9b733..ef530533d 100644 --- a/get-shit-done/references/git-integration.md +++ b/get-shit-done/references/git-integration.md @@ -274,7 +274,7 @@ Set `commit_docs: false` so planning docs stay local and are not committed to an ### How It Works -1. **Auto-detection:** During `/gsd:new-project`, directories with their own `.git` folder are detected and offered for selection as sub-repos. +1. **Auto-detection:** During `/gsd:new-project`, directories with their own `.git` folder are detected and offered for selection as sub-repos. On subsequent runs, `loadConfig` auto-syncs the `sub_repos` list with the filesystem — adding newly created repos and removing deleted ones. This means `config.json` may be rewritten automatically when repos change on disk. 2. **File grouping:** Code files are grouped by their sub-repo prefix (e.g., `backend/src/api/users.ts` belongs to the `backend/` repo). 3. **Independent commits:** Each sub-repo receives its own atomic commit via `gsd-tools.cjs commit-to-subrepo`. File paths are made relative to the sub-repo root before staging. 4. **Planning stays local:** The `.planning/` directory is not committed; it acts as cross-repo coordination.