feat: Phase 2 caller migration — gsd-sdk query in workflows, agents, commands (#2179)

* feat: Phase 2 caller migration — gsd-sdk query in workflows (#2122)

Cherry-picked orchestration rewrites from feat/sdk-foundation (#2008, 4018fee) onto current main, resolving conflicts to keep upstream worktree guards and post-merge test gate. SDK stub registry omitted (out of Phase 2 scope per #2122).

Refs: #2122 #2008
Made-with: Cursor

* docs: add gsd-sdk query migration blurb

Made-with: Cursor

* docs(workflows): extend Phase 2 gsd-sdk query caller migration

- Swap node gsd-tools.cjs for gsd-sdk query in review, plan-phase, execute-plan,
  ship, extract_learnings, ai-integration-phase, eval-review, next, thread
- Document graphify CJS-only in gsd-planner; dual-path in CLI-TOOLS and ARCHITECTURE
- Update tests: workstreams gsd-sdk path, thread frontmatter.get, workspace init.*,
  CRLF-safe autonomous frontmatter parse
- CHANGELOG: Phase 2 caller migration scope

Made-with: Cursor

* docs(phase2): USER-GUIDE + remaining gsd-sdk query call sites

- USER-GUIDE: dual-path CLI section; state validate/sync use full CJS path
- Commands: debug (config-get+tdd), quick (security note), intel Task prompt
- Agent: gsd-debug-session-manager resolve-model via jq
- Workflows: milestone-summary, forensics, next, complete-milestone/verify-work
  (audit-open CJS notes), discuss-phase, progress, verify-phase, add/insert/remove
  phase, transition, manager, quick workflow; remove-phase commit without --files
- Test: quick-session-management accepts frontmatter.get
- CHANGELOG: Phase 2 follow-up bullet

Made-with: Cursor

* docs(phase2): align gsd-sdk query examples in commands and agents

- init.* query names; frontmatter.get uses positional field name
- state.* handlers use positional args; commit uses positional paths
- CJS-only notes for from-gsd2 and graphify; learnings.query wording
- CHANGELOG: Phase 2 orchestration doc pass

Made-with: Cursor

* docs(phase2): normalize gsd-sdk query commit to positional file paths

- Strip --files from commit examples in workflows, references, commands
- Keep commit-to-subrepo ... --files (separate handler)
- git-planning-commit.md: document positional args
- Tests: new-project commit line, state.record-session, gates CRLF, roadmap.analyze
- CHANGELOG [Unreleased]

Made-with: Cursor

* feat(sdk): gsd-sdk query parity with gsd-tools and PR 2179 registry fixes

- Route query via longest-prefix match and dotted single-token expansion; fall back
  to runGsdToolsQuery (same argv as node gsd-tools.cjs) for full CLI coverage.
- Parse gsd-sdk query permissively so gsd-tools flags (--json, --verify, etc.) are
  not rejected by strict parseArgs.
- resolveGsdToolsPath: honor GSD_TOOLS_PATH; prefer bundled get-shit-done copy
  over project .claude installs; export runGsdToolsQuery from the SDK.
- Fix gsd-tools audit-open (core.output; pass object for --json JSON).
- Register summary-extract as alias of summary.extract; fix audit-fix workflow to
  call audit-uat instead of invalid init.audit-uat (PR review).

Updates QUERY-HANDLERS.md and CHANGELOG [Unreleased].

Made-with: Cursor

* fix(sdk): Phase 2 scope — Trek-e review (#2179, #2122)

- Remove gsd-sdk query passthrough to gsd-tools.cjs; drop GSD_TOOLS_PATH
- Consolidate argv routing in resolveQueryArgv(); update USAGE and QUERY-HANDLERS
- Surface @file: read failures in GSDTools.parseOutput
- execute-plan: defer Task Commit Protocol to gsd-executor
- stale-colon-refs: skip .planning/ and root CLAUDE.md (gitignored overlays)
- CHANGELOG [Unreleased]: maintainer review and routing notes

Made-with: Cursor
This commit is contained in:
Rezolv
2026-04-15 22:46:31 -04:00
committed by GitHub
parent 762b8ed25b
commit d3a79917fa
120 changed files with 845 additions and 586 deletions

View File

@@ -34,20 +34,82 @@ export interface ParsedCliArgs {
ws: string | undefined;
help: boolean;
version: boolean;
/**
* When `command === 'query'`, tokens after `query` with only known SDK flags removed.
* Extra flags are kept so handlers that share gsd-tools-style argv (e.g. `--pick`) still receive them.
*/
queryArgv?: string[];
}
/**
* Strip `--pick <field>` from argv before parseArgs so the global parser stays strict.
* Query dispatch removes --pick separately in main(); this only affects CLI parsing.
* Parse `gsd-sdk query …` without rejecting unknown flags (query argv is forwarded to the registry).
*/
function argvForCliParse(argv: string[]): string[] {
if (argv[0] !== 'query') return argv;
const copy = [...argv];
const pickIdx = copy.indexOf('--pick');
if (pickIdx !== -1 && pickIdx + 1 < copy.length) {
copy.splice(pickIdx, 2);
function parseCliArgsQueryPermissive(argv: string[]): ParsedCliArgs {
let projectDir = process.cwd();
let ws: string | undefined;
let wsPort: number | undefined;
let model: string | undefined;
let maxBudget: number | undefined;
let help = false;
let version = false;
const queryArgv: string[] = [];
let i = 1;
while (i < argv.length) {
const a = argv[i];
if (a === '--project-dir' && argv[i + 1]) {
projectDir = argv[i + 1];
i += 2;
continue;
}
if (a === '--ws' && argv[i + 1]) {
ws = argv[i + 1];
i += 2;
continue;
}
if (a === '--ws-port' && argv[i + 1]) {
wsPort = Number(argv[i + 1]);
i += 2;
continue;
}
if (a === '--model' && argv[i + 1]) {
model = argv[i + 1];
i += 2;
continue;
}
if (a === '--max-budget' && argv[i + 1]) {
maxBudget = Number(argv[i + 1]);
i += 2;
continue;
}
if (a === '-h' || a === '--help') {
help = true;
i += 1;
continue;
}
if (a === '-v' || a === '--version') {
version = true;
i += 1;
continue;
}
queryArgv.push(a);
i += 1;
}
return copy;
return {
command: 'query',
prompt: undefined,
initInput: undefined,
init: undefined,
projectDir,
wsPort,
model,
maxBudget,
ws,
help,
version,
queryArgv,
};
}
/**
@@ -55,8 +117,12 @@ function argvForCliParse(argv: string[]): string[] {
* Exported for testing — the main() function uses this internally.
*/
export function parseCliArgs(argv: string[]): ParsedCliArgs {
if (argv[0] === 'query') {
return parseCliArgsQueryPermissive(argv);
}
const { values, positionals } = parseArgs({
args: argvForCliParse(argv),
args: argv,
options: {
'project-dir': { type: 'string', default: process.cwd() },
'ws-port': { type: 'string' },
@@ -106,8 +172,8 @@ Commands:
@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
query <argv...> Registered query handlers only (longest-prefix argv match; see QUERY-HANDLERS.md)
Use --pick <field> to extract a specific field from JSON output
Options:
--init <input> Bootstrap from a PRD before running (auto only)
@@ -226,13 +292,13 @@ export async function main(argv: string[] = process.argv.slice(2)): Promise<void
// ─── 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 { extractField, resolveQueryArgv } = await import('./query/registry.js');
const { GSDToolsError } = await import('./gsd-tools.js');
const { GSDError, exitCodeFor, ErrorClassification } = await import('./errors.js');
const queryArgs = argv.slice(1); // everything after 'query'
const queryCommand = queryArgs[0];
const queryArgs = args.queryArgv ?? [];
if (!queryCommand) {
if (queryArgs.length === 0 || !queryArgs[0]) {
console.error('Error: "gsd-sdk query" requires a command');
process.exitCode = 10;
return;
@@ -253,7 +319,17 @@ export async function main(argv: string[] = process.argv.slice(2)): Promise<void
try {
const registry = createRegistry();
const result = await registry.dispatch(queryCommand, queryArgs.slice(1), args.projectDir);
const tokens = [...queryArgs];
const matched = resolveQueryArgv(tokens, registry);
if (!matched) {
throw new GSDError(
`Unknown command: "${tokens.join(' ')}". Use a registered \`gsd-sdk query\` subcommand (see sdk/src/query/QUERY-HANDLERS.md) or invoke \`node …/gsd-tools.cjs\` for CJS-only operations.`,
ErrorClassification.Validation,
);
}
const result = await registry.dispatch(matched.cmd, matched.args, args.projectDir);
let output: unknown = result.data;
if (pickField) {
@@ -265,6 +341,9 @@ export async function main(argv: string[] = process.argv.slice(2)): Promise<void
if (err instanceof GSDError) {
console.error(`Error: ${err.message}`);
process.exitCode = exitCodeFor(err.classification);
} else if (err instanceof GSDToolsError) {
console.error(`Error: ${err.message}`);
process.exitCode = err.exitCode ?? 1;
} else {
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
process.exitCode = 1;