fix: findProjectRoot returns startDir when it already has .planning/
When cwd is a git repo inside a GSD workspace, findProjectRoot() walked up and returned the workspace parent (which also has .planning/) instead of the cwd itself. This caused all init commands to resolve project_root to the workspace root, making phase/roadmap lookups fail with "Phase not found" errors. The fix adds an early return: if startDir already contains a .planning/ directory, it is the project root — no need to walk up to a parent. Fixes #1362 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,13 @@ function findProjectRoot(startDir) {
|
||||
const root = path.parse(resolved).root;
|
||||
const homedir = require('os').homedir();
|
||||
|
||||
// If startDir already contains .planning/, it IS the project root.
|
||||
// Do not walk up to a parent workspace that also has .planning/ (#1362).
|
||||
const ownPlanning = path.join(resolved, '.planning');
|
||||
if (fs.existsSync(ownPlanning) && fs.statSync(ownPlanning).isDirectory()) {
|
||||
return startDir;
|
||||
}
|
||||
|
||||
// Check if startDir or any of its ancestors (up to AND including the
|
||||
// candidate project root) contains a .git directory. This handles both
|
||||
// `backend/` (direct sub-repo) and `backend/src/modules/` (nested inside),
|
||||
|
||||
@@ -1509,6 +1509,33 @@ describe('findProjectRoot', () => {
|
||||
|
||||
assert.strictEqual(findProjectRoot(projectRoot), projectRoot);
|
||||
});
|
||||
|
||||
test('does not walk past child with own .planning/ to workspace parent (#1362)', () => {
|
||||
// Workspace layout: parent has .planning/, child git repo also has .planning/
|
||||
// findProjectRoot should return the child (startDir), not the parent
|
||||
fs.mkdirSync(path.join(projectRoot, '.planning'), { recursive: true });
|
||||
|
||||
const childRepo = path.join(projectRoot, 'authenticator');
|
||||
fs.mkdirSync(path.join(childRepo, '.planning'), { recursive: true });
|
||||
fs.mkdirSync(path.join(childRepo, '.git'), { recursive: true });
|
||||
|
||||
assert.strictEqual(findProjectRoot(childRepo), childRepo);
|
||||
});
|
||||
|
||||
test('does not walk past nested dir whose git root has .planning/ (#1362)', () => {
|
||||
// Workspace layout: parent has .planning/, child git repo also has .planning/
|
||||
// cwd is deep inside child — should resolve to child root, not workspace root
|
||||
fs.mkdirSync(path.join(projectRoot, '.planning'), { recursive: true });
|
||||
|
||||
const childRepo = path.join(projectRoot, 'authenticator');
|
||||
fs.mkdirSync(path.join(childRepo, '.planning'), { recursive: true });
|
||||
fs.mkdirSync(path.join(childRepo, '.git'), { recursive: true });
|
||||
|
||||
const deepDir = path.join(childRepo, 'src', 'lib');
|
||||
fs.mkdirSync(deepDir, { recursive: true });
|
||||
|
||||
assert.strictEqual(findProjectRoot(deepDir), childRepo);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── reapStaleTempFiles ─────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user