fix: address maintainer review — gate findProjectRoot, warn on unmatched files

1. Gate findProjectRoot to commands that access .planning/ — skip for
   pure-utility commands (generate-slug, current-timestamp, template,
   frontmatter, verify-path-exists, verify-summary) to avoid unnecessary
   filesystem traversal on every invocation.

2. Warn to stderr when commit-to-subrepo encounters files that don't
   match any configured sub-repo prefix.

3. Document that loadConfig auto-syncs sub_repos with the filesystem,
   so config.json may be rewritten when repos are added or removed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Srinivas Koduri
2026-03-19 10:05:50 -07:00
parent fd0d546484
commit 32c6d880bf
3 changed files with 16 additions and 5 deletions

View File

@@ -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 <command> [args] [--raw] [--cwd <path>]\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];

View File

@@ -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);

View File

@@ -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.