fix: address PR review — nested path resolution, sub_repos in init, depth-1 detection
- 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) <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user