From d0aae7b63c5b2e239acf59b0be649b8b8bea8901 Mon Sep 17 00:00:00 2001 From: Srinivas Koduri Date: Wed, 18 Mar 2026 20:06:49 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20address=20PR=20review=20=E2=80=94=20nest?= =?UTF-8?q?ed=20path=20resolution,=20sub=5Frepos=20in=20init,=20depth-1=20?= =?UTF-8?q?detection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - findProjectRoot: use isInsideGitRepo() to walk up and find .git in ancestor dirs, fixing nested paths like backend/src/modules/ - Add sub_repos to cmdInitExecutePhase output so execute-plan.md and gsd-executor.md can route commits correctly - Align new-project.md sub-repo detection to maxdepth 1 matching detectSubRepos() behavior - Add 3 nested path tests for .git heuristic, sub_repos, and multiRepo Co-Authored-By: Claude Opus 4.6 (1M context) --- get-shit-done/bin/lib/core.cjs | 19 ++++++++--- get-shit-done/bin/lib/init.cjs | 1 + get-shit-done/workflows/new-project.md | 4 +-- tests/core.test.cjs | 47 ++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 6 deletions(-) diff --git a/get-shit-done/bin/lib/core.cjs b/get-shit-done/bin/lib/core.cjs index 3101f02e5..edcf95e3c 100644 --- a/get-shit-done/bin/lib/core.cjs +++ b/get-shit-done/bin/lib/core.cjs @@ -57,7 +57,18 @@ function findProjectRoot(startDir) { const resolved = path.resolve(startDir); const root = path.parse(resolved).root; const homedir = require('os').homedir(); - const startHasGit = fs.existsSync(path.join(resolved, '.git')); + + // Check if startDir or any of its ancestors (up to but not including a + // candidate project root) contains a .git directory. This handles both + // `backend/` (direct sub-repo) and `backend/src/modules/` (nested inside). + function isInsideGitRepo(candidateParent) { + let d = resolved; + while (d !== candidateParent && d !== root) { + if (fs.existsSync(path.join(d, '.git'))) return true; + d = path.dirname(d); + } + return false; + } let dir = resolved; while (dir !== root) { @@ -82,15 +93,15 @@ function findProjectRoot(startDir) { } // Check legacy multiRepo flag - if (config.multiRepo === true && startHasGit) { + if (config.multiRepo === true && isInsideGitRepo(parent)) { return parent; } } catch { // config.json missing or malformed — fall back to .git heuristic } - // Heuristic: parent has .planning/ and startDir has its own .git - if (startHasGit) { + // Heuristic: parent has .planning/ and we're inside a git repo + if (isInsideGitRepo(parent)) { return parent; } } diff --git a/get-shit-done/bin/lib/init.cjs b/get-shit-done/bin/lib/init.cjs index b19d3d1d8..6083dd908 100644 --- a/get-shit-done/bin/lib/init.cjs +++ b/get-shit-done/bin/lib/init.cjs @@ -57,6 +57,7 @@ function cmdInitExecutePhase(cwd, phase, raw) { // Config flags commit_docs: config.commit_docs, + sub_repos: config.sub_repos, parallelization: config.parallelization, context_window: config.context_window, branching_strategy: config.branching_strategy, diff --git a/get-shit-done/workflows/new-project.md b/get-shit-done/workflows/new-project.md index b5cb42482..9f1bd89d2 100644 --- a/get-shit-done/workflows/new-project.md +++ b/get-shit-done/workflows/new-project.md @@ -535,12 +535,12 @@ node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit "chore: add project Check for directories with their own `.git` folders (separate repos within the workspace): ```bash -find . -maxdepth 2 -type d -name ".git" -not -path "./.git" +find . -maxdepth 1 -type d -not -name ".*" -not -name "node_modules" -exec test -d "{}/.git" \; -print ``` **If sub-repos found:** -Strip the `/.git` suffix and `./` prefix to get directory names (e.g., `./backend/.git` → `backend`). +Strip the `./` prefix to get directory names (e.g., `./backend` → `backend`). Use AskUserQuestion: - header: "Multi-Repo Workspace" diff --git a/tests/core.test.cjs b/tests/core.test.cjs index eca98f1f4..77fb46956 100644 --- a/tests/core.test.cjs +++ b/tests/core.test.cjs @@ -1167,6 +1167,53 @@ describe('findProjectRoot', () => { assert.strictEqual(findProjectRoot(backendDir), projectRoot); }); + test('walks up from nested path inside sub-repo via .git heuristic', () => { + fs.mkdirSync(path.join(projectRoot, '.planning'), { recursive: true }); + + // Sub-repo with .git at its root + const backendDir = path.join(projectRoot, 'backend'); + fs.mkdirSync(path.join(backendDir, '.git'), { recursive: true }); + + // Nested path deep inside the sub-repo + const nestedDir = path.join(backendDir, 'src', 'modules', 'auth'); + fs.mkdirSync(nestedDir, { recursive: true }); + + // isInsideGitRepo walks up and finds backend/.git + assert.strictEqual(findProjectRoot(nestedDir), projectRoot); + }); + + test('walks up from nested path inside sub-repo via sub_repos config', () => { + fs.mkdirSync(path.join(projectRoot, '.planning'), { recursive: true }); + fs.writeFileSync( + path.join(projectRoot, '.planning', 'config.json'), + JSON.stringify({ sub_repos: ['backend'] }) + ); + + // Nested path deep inside the sub-repo + const nestedDir = path.join(projectRoot, 'backend', 'src', 'modules'); + fs.mkdirSync(nestedDir, { recursive: true }); + + // With sub_repos config, it checks topSegment of relative path + assert.strictEqual(findProjectRoot(nestedDir), projectRoot); + }); + + test('walks up from nested path via legacy multiRepo flag', () => { + fs.mkdirSync(path.join(projectRoot, '.planning'), { recursive: true }); + fs.writeFileSync( + path.join(projectRoot, '.planning', 'config.json'), + JSON.stringify({ multiRepo: true }) + ); + + const backendDir = path.join(projectRoot, 'backend'); + fs.mkdirSync(path.join(backendDir, '.git'), { recursive: true }); + + // Nested inside sub-repo — isInsideGitRepo walks up and finds backend/.git + const nestedDir = path.join(backendDir, 'src'); + fs.mkdirSync(nestedDir, { recursive: true }); + + assert.strictEqual(findProjectRoot(nestedDir), projectRoot); + }); + test('does not walk up for dirs without .git when no sub_repos config', () => { fs.mkdirSync(path.join(projectRoot, '.planning'), { recursive: true });