fix(2137): skip worktree isolation when .gitmodules detected (#2144)

* feat(sdk): add typed query foundation and gsd-sdk query (Phase 1)

Add sdk/src/query registry and handlers with tests, GSDQueryError, CLI query wiring, and supporting type/tool-scoping hooks. Update CHANGELOG. Vitest 4 constructor mock fixes in milestone-runner tests.

Made-with: Cursor

* fix(2137): skip worktree isolation when .gitmodules detected

When a project contains git submodules, worktree isolation cannot
correctly handle submodule commits — three separate gaps exist in
worktree setup, executor commit protocol, and merge-back. Rather
than patch each gap individually, detect .gitmodules at phase start
and fall back to sequential execution, which handles submodules
transparently (Option B).

Affected workflows: execute-phase.md, quick.md

---------

Co-authored-by: David Sienkowski <dave@sienkowski.com>
This commit is contained in:
Tom Boucher
2026-04-12 08:33:04 -04:00
committed by GitHub
parent 58632e0718
commit 002bcf2a8a
63 changed files with 17872 additions and 23 deletions

View File

@@ -54,7 +54,7 @@ export function parseCliArgs(argv: string[]): ParsedCliArgs {
version: { type: 'boolean', short: 'v', default: false },
},
allowPositionals: true,
strict: true,
strict: false,
});
const command = positionals[0] as string | undefined;
@@ -86,12 +86,14 @@ Usage: gsd-sdk <command> [args] [options]
Commands:
run <prompt> Run a full milestone from a text prompt
auto Run the full autonomous lifecycle (discover → execute → advance)
auto Run the full autonomous lifecycle (discover -> execute -> advance)
init [input] Bootstrap a new project from a PRD or description
input can be:
@path/to/prd.md Read input from a file
"description" Use text directly
(empty) Read from stdin
query <command> Execute a registered native query command (registry: sdk/src/query/index.ts)
Use --pick <field> to extract a specific field
Options:
--init <input> Bootstrap from a PRD before running (auto only)
@@ -207,8 +209,58 @@ export async function main(argv: string[] = process.argv.slice(2)): Promise<void
return;
}
// ─── Query command ──────────────────────────────────────────────────────
if (args.command === 'query') {
const { createRegistry } = await import('./query/index.js');
const { extractField } = await import('./query/registry.js');
const { GSDError, exitCodeFor } = await import('./errors.js');
const queryArgs = argv.slice(1); // everything after 'query'
const queryCommand = queryArgs[0];
if (!queryCommand) {
console.error('Error: "gsd-sdk query" requires a command');
process.exitCode = 10;
return;
}
// Extract --pick before dispatch
const pickIdx = queryArgs.indexOf('--pick');
let pickField: string | undefined;
if (pickIdx !== -1) {
if (pickIdx + 1 >= queryArgs.length) {
console.error('Error: --pick requires a field name');
process.exitCode = 10;
return;
}
pickField = queryArgs[pickIdx + 1];
queryArgs.splice(pickIdx, 2);
}
try {
const registry = createRegistry();
const result = await registry.dispatch(queryCommand, queryArgs.slice(1), args.projectDir);
let output: unknown = result.data;
if (pickField) {
output = extractField(output, pickField);
}
console.log(JSON.stringify(output, null, 2));
} catch (err) {
if (err instanceof GSDError) {
console.error(`Error: ${err.message}`);
process.exitCode = exitCodeFor(err.classification);
} else {
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
process.exitCode = 1;
}
}
return;
}
if (args.command !== 'run' && args.command !== 'init' && args.command !== 'auto') {
console.error('Error: Expected "gsd-sdk run <prompt>", "gsd-sdk auto", or "gsd-sdk init [input]"');
console.error('Error: Expected "gsd-sdk run <prompt>", "gsd-sdk auto", "gsd-sdk init [input]", or "gsd-sdk query <command>"');
console.error(USAGE);
process.exitCode = 1;
return;