From c8ae6b3b4fc0a5e4a7471f8496df5e0ae14f3cb2 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 24 Apr 2026 18:10:45 -0400 Subject: [PATCH] =?UTF-8?q?fix(#2636):=20surface=20gsd-sdk=20query=20failu?= =?UTF-8?q?res=20and=20add=20workflow=E2=86=94handler=20parity=20check=20(?= =?UTF-8?q?#2656)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(#2636): surface gsd-sdk query failures and add workflow↔handler parity check Root cause: workflows invoked `gsd-sdk query agent-skills ` with a trailing `2>/dev/null`, swallowing stderr and exit code. When the installed `@gsd-build/sdk` npm was stale (pre-query), the call resolved to an empty string and `agent_skills.` config was never injected into spawn prompts — silently. The handler exists on main (sdk/src/query/skills.ts), so this is a publish-drift + silent-fallback bug, not a missing handler. Fix: - Remove bare `2>/dev/null` from every `gsd-sdk query agent-skills …` invocation in workflows so SDK failures surface to stderr. - Apply the same rule to other no-fallback calls (audit-open, write-profile, generate-* profile handlers, frontmatter.get in commands). Best-effort cleanup calls (config-set workflow._auto_chain_active false) keep exit-code forgiveness via `|| true` but no longer suppress stderr. Parity tests: - New: tests/bug-2636-gsd-sdk-query-silent-swallow.test.cjs — fails if any `gsd-sdk query agent-skills … 2>/dev/null` is reintroduced. - Existing: tests/gsd-sdk-query-registry-integration.test.cjs already asserts every workflow noun resolves to a registered handler; confirmed passing post-change. Note: npm republish of @gsd-build/sdk is a separate release concern and is not included in this PR. * fix(#2636): address review — restore broken markdown fences and shell syntax The previous commit's mass removal of '2>/dev/null' suffixes also collapsed adjacent closing code fences and 'fi' tokens onto the command line, producing malformed markdown blocks and 'truefi' / 'true fi' shell syntax errors in the workflows. Repaired sites: - commands/gsd/quick.md, thread.md (frontmatter.get fences) - workflows/complete-milestone.md (audit-open fence) - workflows/profile-user.md (write-profile + generate-* fences) - workflows/verify-work.md (audit-open --json fence) - workflows/execute-phase.md (truefi -> true / fi) - workflows/plan-phase.md, discuss-phase-assumptions.md, discuss-phase/modes/chain.md (true fi -> true / fi) All 5450 tests pass. --- commands/gsd/quick.md | 2 +- commands/gsd/thread.md | 2 +- get-shit-done/workflows/audit-milestone.md | 2 +- get-shit-done/workflows/complete-milestone.md | 2 +- get-shit-done/workflows/diagnose-issues.md | 2 +- .../workflows/discuss-phase-assumptions.md | 4 +- get-shit-done/workflows/discuss-phase.md | 2 +- .../workflows/discuss-phase/modes/chain.md | 2 +- get-shit-done/workflows/docs-update.md | 2 +- get-shit-done/workflows/execute-phase.md | 6 +- .../steps/codebase-drift-gate.md | 2 +- get-shit-done/workflows/map-codebase.md | 2 +- get-shit-done/workflows/new-milestone.md | 6 +- get-shit-done/workflows/new-project.md | 6 +- get-shit-done/workflows/plan-phase.md | 8 +- get-shit-done/workflows/profile-user.md | 8 +- get-shit-done/workflows/quick.md | 8 +- get-shit-done/workflows/research-phase.md | 2 +- get-shit-done/workflows/secure-phase.md | 2 +- get-shit-done/workflows/ui-phase.md | 4 +- get-shit-done/workflows/ui-review.md | 2 +- get-shit-done/workflows/validate-phase.md | 2 +- get-shit-done/workflows/verify-work.md | 6 +- ...2636-gsd-sdk-query-silent-swallow.test.cjs | 73 +++++++++++++++++++ 24 files changed, 115 insertions(+), 42 deletions(-) create mode 100644 tests/bug-2636-gsd-sdk-query-silent-swallow.test.cjs diff --git a/commands/gsd/quick.md b/commands/gsd/quick.md index 1b9035da..11219cb3 100644 --- a/commands/gsd/quick.md +++ b/commands/gsd/quick.md @@ -71,7 +71,7 @@ For each directory found: - Check if PLAN.md exists - Check if SUMMARY.md exists; if so, read `status` from its frontmatter via: ```bash - gsd-sdk query frontmatter.get .planning/quick/{dir}/SUMMARY.md status 2>/dev/null + gsd-sdk query frontmatter.get .planning/quick/{dir}/SUMMARY.md status ``` - Determine directory creation date: `stat -f "%SB" -t "%Y-%m-%d"` (macOS) or `stat -c "%w"` (Linux); fall back to the date prefix in the directory name (format: `YYYYMMDD-` prefix) - Derive display status: diff --git a/commands/gsd/thread.md b/commands/gsd/thread.md index ec5e4743..b40f63b2 100644 --- a/commands/gsd/thread.md +++ b/commands/gsd/thread.md @@ -38,7 +38,7 @@ ls .planning/threads/*.md 2>/dev/null For each thread file found: - Read frontmatter `status` field via: ```bash - gsd-sdk query frontmatter.get .planning/threads/{file} status 2>/dev/null + gsd-sdk query frontmatter.get .planning/threads/{file} status ``` - If frontmatter `status` field is missing, fall back to reading markdown heading `## Status: OPEN` (or IN PROGRESS / RESOLVED) from the file body - Read frontmatter `updated` field for the last-updated date diff --git a/get-shit-done/workflows/audit-milestone.md b/get-shit-done/workflows/audit-milestone.md index 1f01e732..32af8666 100644 --- a/get-shit-done/workflows/audit-milestone.md +++ b/get-shit-done/workflows/audit-milestone.md @@ -18,7 +18,7 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo ```bash INIT=$(gsd-sdk query init.milestone-op) if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi -AGENT_SKILLS_CHECKER=$(gsd-sdk query agent-skills gsd-integration-checker 2>/dev/null) +AGENT_SKILLS_CHECKER=$(gsd-sdk query agent-skills gsd-integration-checker) ``` Extract from init JSON: `milestone_version`, `milestone_name`, `phase_count`, `completed_phases`, `commit_docs`. diff --git a/get-shit-done/workflows/complete-milestone.md b/get-shit-done/workflows/complete-milestone.md index e7923f09..5ddca9f4 100644 --- a/get-shit-done/workflows/complete-milestone.md +++ b/get-shit-done/workflows/complete-milestone.md @@ -41,7 +41,7 @@ When a milestone completes: Before proceeding with milestone close, run the comprehensive open artifact audit. ```bash -gsd-sdk query audit-open 2>/dev/null +gsd-sdk query audit-open ``` If the output contains open items (any section with count > 0): diff --git a/get-shit-done/workflows/diagnose-issues.md b/get-shit-done/workflows/diagnose-issues.md index 367c7a2e..0027d865 100644 --- a/get-shit-done/workflows/diagnose-issues.md +++ b/get-shit-done/workflows/diagnose-issues.md @@ -87,7 +87,7 @@ This runs in parallel - all gaps investigated simultaneously. **Load agent skills:** ```bash -AGENT_SKILLS_DEBUGGER=$(gsd-sdk query agent-skills gsd-debugger 2>/dev/null) +AGENT_SKILLS_DEBUGGER=$(gsd-sdk query agent-skills gsd-debugger) EXPECTED_BASE=$(git rev-parse HEAD) ``` diff --git a/get-shit-done/workflows/discuss-phase-assumptions.md b/get-shit-done/workflows/discuss-phase-assumptions.md index 9d84124c..20505b4c 100644 --- a/get-shit-done/workflows/discuss-phase-assumptions.md +++ b/get-shit-done/workflows/discuss-phase-assumptions.md @@ -66,7 +66,7 @@ Phase number from argument (required). ```bash INIT=$(gsd-sdk query init.phase-op "${PHASE}") if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi -AGENT_SKILLS_ANALYZER=$(gsd-sdk query agent-skills gsd-assumptions-analyzer 2>/dev/null) +AGENT_SKILLS_ANALYZER=$(gsd-sdk query agent-skills gsd-assumptions-analyzer) ``` Parse JSON for: `commit_docs`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, @@ -619,7 +619,7 @@ Check for auto-advance trigger: 2. Sync chain flag: ```bash if [[ ! "$ARGUMENTS" =~ --auto ]]; then - gsd-sdk query config-set workflow._auto_chain_active false 2>/dev/null + gsd-sdk query config-set workflow._auto_chain_active false || true fi ``` 3. Read consolidated auto-mode (`active` = chain flag OR user preference): diff --git a/get-shit-done/workflows/discuss-phase.md b/get-shit-done/workflows/discuss-phase.md index fc7e7bba..b74b38f5 100644 --- a/get-shit-done/workflows/discuss-phase.md +++ b/get-shit-done/workflows/discuss-phase.md @@ -111,7 +111,7 @@ Phase number from argument (required). ```bash INIT=$(gsd-sdk query init.phase-op "${PHASE}") if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi -AGENT_SKILLS_ADVISOR=$(gsd-sdk query agent-skills gsd-advisor-researcher 2>/dev/null) +AGENT_SKILLS_ADVISOR=$(gsd-sdk query agent-skills gsd-advisor-researcher) ``` Parse JSON for: `commit_docs`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_plans`, `has_verification`, `plan_count`, `roadmap_exists`, `planning_exists`, `response_language`. diff --git a/get-shit-done/workflows/discuss-phase/modes/chain.md b/get-shit-done/workflows/discuss-phase/modes/chain.md index 0806b174..c30c06be 100644 --- a/get-shit-done/workflows/discuss-phase/modes/chain.md +++ b/get-shit-done/workflows/discuss-phase/modes/chain.md @@ -26,7 +26,7 @@ (the user's persistent settings preference): ```bash if [[ ! "$ARGUMENTS" =~ --auto ]] && [[ ! "$ARGUMENTS" =~ --chain ]]; then - gsd-sdk query config-set workflow._auto_chain_active false 2>/dev/null + gsd-sdk query config-set workflow._auto_chain_active false || true fi ``` diff --git a/get-shit-done/workflows/docs-update.md b/get-shit-done/workflows/docs-update.md index 15fd9adb..8690dbf1 100644 --- a/get-shit-done/workflows/docs-update.md +++ b/get-shit-done/workflows/docs-update.md @@ -16,7 +16,7 @@ Load docs-update context: ```bash INIT=$(gsd-sdk query docs-init) if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi -AGENT_SKILLS=$(gsd-sdk query agent-skills gsd-doc-writer 2>/dev/null) +AGENT_SKILLS=$(gsd-sdk query agent-skills gsd-doc-writer) ``` Extract from init JSON: diff --git a/get-shit-done/workflows/execute-phase.md b/get-shit-done/workflows/execute-phase.md index 19b26525..c54de394 100644 --- a/get-shit-done/workflows/execute-phase.md +++ b/get-shit-done/workflows/execute-phase.md @@ -69,7 +69,7 @@ Load all context in one call: ```bash INIT=$(gsd-sdk query init.execute-phase "${PHASE_ARG}") if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi -AGENT_SKILLS=$(gsd-sdk query agent-skills gsd-executor 2>/dev/null) +AGENT_SKILLS=$(gsd-sdk query agent-skills gsd-executor) ``` Parse JSON for: `executor_model`, `verifier_model`, `commit_docs`, `parallelization`, `branching_strategy`, `branch_name`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `plans`, `incomplete_plans`, `plan_count`, `incomplete_count`, `state_exists`, `roadmap_exists`, `phase_req_ids`, `response_language`. @@ -130,7 +130,7 @@ inline path for each plan. ```bash # REQUIRED: prevents stale auto-chain from previous --auto runs if [[ ! "$ARGUMENTS" =~ --auto ]]; then - gsd-sdk query config-set workflow._auto_chain_active false 2>/dev/null + gsd-sdk query config-set workflow._auto_chain_active false || true fi ``` @@ -1339,7 +1339,7 @@ spawn template, and the two `workflow.drift_*` config keys. Verify phase achieved its GOAL, not just completed tasks. ```bash -VERIFIER_SKILLS=$(gsd-sdk query agent-skills gsd-verifier 2>/dev/null) +VERIFIER_SKILLS=$(gsd-sdk query agent-skills gsd-verifier) ``` ``` diff --git a/get-shit-done/workflows/execute-phase/steps/codebase-drift-gate.md b/get-shit-done/workflows/execute-phase/steps/codebase-drift-gate.md index 5f442109..53df17fc 100644 --- a/get-shit-done/workflows/execute-phase/steps/codebase-drift-gate.md +++ b/get-shit-done/workflows/execute-phase/steps/codebase-drift-gate.md @@ -45,7 +45,7 @@ First load the mapper agent's skill bundle (the executor's `AGENT_SKILLS` from step `init_context` is for `gsd-executor`, not the mapper): ```bash -AGENT_SKILLS_MAPPER=$(gsd-sdk query agent-skills gsd-codebase-mapper 2>/dev/null || true) +AGENT_SKILLS_MAPPER=$(gsd-sdk query agent-skills gsd-codebase-mapper) ``` Then spawn `gsd-codebase-mapper` agents with the `--paths` hint: diff --git a/get-shit-done/workflows/map-codebase.md b/get-shit-done/workflows/map-codebase.md index 4cad2799..96230452 100644 --- a/get-shit-done/workflows/map-codebase.md +++ b/get-shit-done/workflows/map-codebase.md @@ -71,7 +71,7 @@ Load codebase mapping context: ```bash INIT=$(gsd-sdk query init.map-codebase) if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi -AGENT_SKILLS_MAPPER=$(gsd-sdk query agent-skills gsd-codebase-mapper 2>/dev/null) +AGENT_SKILLS_MAPPER=$(gsd-sdk query agent-skills gsd-codebase-mapper) ``` Extract from init JSON: `mapper_model`, `commit_docs`, `codebase_dir`, `existing_maps`, `has_maps`, `codebase_dir_exists`, `subagent_timeout`, `date`. diff --git a/get-shit-done/workflows/new-milestone.md b/get-shit-done/workflows/new-milestone.md index a0ba5182..5542ba98 100644 --- a/get-shit-done/workflows/new-milestone.md +++ b/get-shit-done/workflows/new-milestone.md @@ -220,9 +220,9 @@ gsd-sdk query commit "docs: start milestone v[X.Y] [Name]" .planning/PROJECT.md ```bash INIT=$(gsd-sdk query init.new-milestone) if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi -AGENT_SKILLS_RESEARCHER=$(gsd-sdk query agent-skills gsd-project-researcher 2>/dev/null) -AGENT_SKILLS_SYNTHESIZER=$(gsd-sdk query agent-skills gsd-research-synthesizer 2>/dev/null) -AGENT_SKILLS_ROADMAPPER=$(gsd-sdk query agent-skills gsd-roadmapper 2>/dev/null) +AGENT_SKILLS_RESEARCHER=$(gsd-sdk query agent-skills gsd-project-researcher) +AGENT_SKILLS_SYNTHESIZER=$(gsd-sdk query agent-skills gsd-research-synthesizer) +AGENT_SKILLS_ROADMAPPER=$(gsd-sdk query agent-skills gsd-roadmapper) ``` Extract from init JSON: `researcher_model`, `synthesizer_model`, `roadmapper_model`, `commit_docs`, `research_enabled`, `current_milestone`, `project_exists`, `roadmap_exists`, `latest_completed_milestone`, `phase_dir_count`, `phase_archive_path`, `agents_installed`, `missing_agents`. diff --git a/get-shit-done/workflows/new-project.md b/get-shit-done/workflows/new-project.md index 5b387a48..fef467c6 100644 --- a/get-shit-done/workflows/new-project.md +++ b/get-shit-done/workflows/new-project.md @@ -59,9 +59,9 @@ The document should describe what you want to build. ```bash INIT=$(gsd-sdk query init.new-project) if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi -AGENT_SKILLS_RESEARCHER=$(gsd-sdk query agent-skills gsd-project-researcher 2>/dev/null) -AGENT_SKILLS_SYNTHESIZER=$(gsd-sdk query agent-skills gsd-research-synthesizer 2>/dev/null) -AGENT_SKILLS_ROADMAPPER=$(gsd-sdk query agent-skills gsd-roadmapper 2>/dev/null) +AGENT_SKILLS_RESEARCHER=$(gsd-sdk query agent-skills gsd-project-researcher) +AGENT_SKILLS_SYNTHESIZER=$(gsd-sdk query agent-skills gsd-research-synthesizer) +AGENT_SKILLS_ROADMAPPER=$(gsd-sdk query agent-skills gsd-roadmapper) ``` Parse JSON for: `researcher_model`, `synthesizer_model`, `roadmapper_model`, `commit_docs`, `project_exists`, `has_codebase_map`, `planning_exists`, `has_existing_code`, `has_package_file`, `is_brownfield`, `needs_codebase_map`, `has_git`, `project_path`, `agents_installed`, `missing_agents`. diff --git a/get-shit-done/workflows/plan-phase.md b/get-shit-done/workflows/plan-phase.md index 9378b266..a44e3450 100644 --- a/get-shit-done/workflows/plan-phase.md +++ b/get-shit-done/workflows/plan-phase.md @@ -33,9 +33,9 @@ Load all context in one call (paths only to minimize orchestrator context): ```bash INIT=$(gsd-sdk query init.plan-phase "$PHASE") if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi -AGENT_SKILLS_RESEARCHER=$(gsd-sdk query agent-skills gsd-phase-researcher 2>/dev/null) -AGENT_SKILLS_PLANNER=$(gsd-sdk query agent-skills gsd-planner 2>/dev/null) -AGENT_SKILLS_CHECKER=$(gsd-sdk query agent-skills gsd-plan-checker 2>/dev/null) +AGENT_SKILLS_RESEARCHER=$(gsd-sdk query agent-skills gsd-phase-researcher) +AGENT_SKILLS_PLANNER=$(gsd-sdk query agent-skills gsd-planner) +AGENT_SKILLS_CHECKER=$(gsd-sdk query agent-skills gsd-plan-checker) CONTEXT_WINDOW=$(gsd-sdk query config-get context_window 2>/dev/null || echo "200000") TDD_MODE=$(gsd-sdk query config-get workflow.tdd_mode 2>/dev/null || echo "false") ``` @@ -1470,7 +1470,7 @@ Check for auto-advance trigger using values already loaded in step 1: 3. **Sync chain flag with intent** — if user invoked manually (no `--auto` and no `--chain`), clear the ephemeral chain flag from any previous interrupted `--auto` chain. This does NOT touch `workflow.auto_advance` (the user's persistent settings preference): ```bash if [[ ! "$ARGUMENTS" =~ --auto ]] && [[ ! "$ARGUMENTS" =~ --chain ]]; then - gsd-sdk query config-set workflow._auto_chain_active false 2>/dev/null + gsd-sdk query config-set workflow._auto_chain_active false || true fi ``` diff --git a/get-shit-done/workflows/profile-user.md b/get-shit-done/workflows/profile-user.md index 31277f65..a27dcf59 100644 --- a/get-shit-done/workflows/profile-user.md +++ b/get-shit-done/workflows/profile-user.md @@ -271,7 +271,7 @@ Write updated analysis JSON back to `$ANALYSIS_PATH`. Display: "◆ Writing profile..." ```bash -gsd-sdk query write-profile --input "$ANALYSIS_PATH" --json 2>/dev/null +gsd-sdk query write-profile --input "$ANALYSIS_PATH" --json ``` Display: "✓ Profile written to $HOME/.claude/get-shit-done/USER-PROFILE.md" @@ -350,7 +350,7 @@ Generate selected artifacts sequentially (file I/O is fast, no benefit from para **For /gsd-dev-preferences (if selected):** ```bash -gsd-sdk query generate-dev-preferences --analysis "$ANALYSIS_PATH" --json 2>/dev/null +gsd-sdk query generate-dev-preferences --analysis "$ANALYSIS_PATH" --json ``` Display: "✓ Generated /gsd-dev-preferences at $HOME/.claude/commands/gsd/dev-preferences.md" @@ -358,7 +358,7 @@ Display: "✓ Generated /gsd-dev-preferences at $HOME/.claude/commands/gsd/dev-p **For CLAUDE.md profile section (if selected):** ```bash -gsd-sdk query generate-claude-profile --analysis "$ANALYSIS_PATH" --json 2>/dev/null +gsd-sdk query generate-claude-profile --analysis "$ANALYSIS_PATH" --json ``` Display: "✓ Added profile section to CLAUDE.md" @@ -366,7 +366,7 @@ Display: "✓ Added profile section to CLAUDE.md" **For Global CLAUDE.md (if selected):** ```bash -gsd-sdk query generate-claude-profile --analysis "$ANALYSIS_PATH" --global --json 2>/dev/null +gsd-sdk query generate-claude-profile --analysis "$ANALYSIS_PATH" --global --json ``` Display: "✓ Added profile section to $HOME/.claude/CLAUDE.md" diff --git a/get-shit-done/workflows/quick.md b/get-shit-done/workflows/quick.md index 1df8961c..c503ceac 100644 --- a/get-shit-done/workflows/quick.md +++ b/get-shit-done/workflows/quick.md @@ -140,10 +140,10 @@ fi ```bash INIT=$(gsd-sdk query init.quick "$DESCRIPTION") if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi -AGENT_SKILLS_PLANNER=$(gsd-sdk query agent-skills gsd-planner 2>/dev/null) -AGENT_SKILLS_EXECUTOR=$(gsd-sdk query agent-skills gsd-executor 2>/dev/null) -AGENT_SKILLS_CHECKER=$(gsd-sdk query agent-skills gsd-plan-checker 2>/dev/null) -AGENT_SKILLS_VERIFIER=$(gsd-sdk query agent-skills gsd-verifier 2>/dev/null) +AGENT_SKILLS_PLANNER=$(gsd-sdk query agent-skills gsd-planner) +AGENT_SKILLS_EXECUTOR=$(gsd-sdk query agent-skills gsd-executor) +AGENT_SKILLS_CHECKER=$(gsd-sdk query agent-skills gsd-plan-checker) +AGENT_SKILLS_VERIFIER=$(gsd-sdk query agent-skills gsd-verifier) ``` Parse JSON for: `planner_model`, `executor_model`, `checker_model`, `verifier_model`, `commit_docs`, `branch_name`, `quick_id`, `slug`, `date`, `timestamp`, `quick_dir`, `task_dir`, `roadmap_exists`, `planning_exists`. diff --git a/get-shit-done/workflows/research-phase.md b/get-shit-done/workflows/research-phase.md index 2c254a16..c53dd15e 100644 --- a/get-shit-done/workflows/research-phase.md +++ b/get-shit-done/workflows/research-phase.md @@ -42,7 +42,7 @@ If exists: Offer update/view/skip options. INIT=$(gsd-sdk query init.phase-op "${PHASE}") if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi # Extract: phase_dir, padded_phase, phase_number, state_path, requirements_path, context_path -AGENT_SKILLS_RESEARCHER=$(gsd-sdk query agent-skills gsd-phase-researcher 2>/dev/null) +AGENT_SKILLS_RESEARCHER=$(gsd-sdk query agent-skills gsd-phase-researcher) ``` ## Step 4: Spawn Researcher diff --git a/get-shit-done/workflows/secure-phase.md b/get-shit-done/workflows/secure-phase.md index 54bdb14f..f75f839f 100644 --- a/get-shit-done/workflows/secure-phase.md +++ b/get-shit-done/workflows/secure-phase.md @@ -18,7 +18,7 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo ```bash INIT=$(gsd-sdk query init.phase-op "${PHASE_ARG}") if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi -AGENT_SKILLS_AUDITOR=$(gsd-sdk query agent-skills gsd-security-auditor 2>/dev/null) +AGENT_SKILLS_AUDITOR=$(gsd-sdk query agent-skills gsd-security-auditor) ``` Parse: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`. diff --git a/get-shit-done/workflows/ui-phase.md b/get-shit-done/workflows/ui-phase.md index e5aae8ce..3fcf6225 100644 --- a/get-shit-done/workflows/ui-phase.md +++ b/get-shit-done/workflows/ui-phase.md @@ -21,8 +21,8 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo ```bash INIT=$(gsd-sdk query init.plan-phase "$PHASE") if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi -AGENT_SKILLS_UI=$(gsd-sdk query agent-skills gsd-ui-researcher 2>/dev/null) -AGENT_SKILLS_UI_CHECKER=$(gsd-sdk query agent-skills gsd-ui-checker 2>/dev/null) +AGENT_SKILLS_UI=$(gsd-sdk query agent-skills gsd-ui-researcher) +AGENT_SKILLS_UI_CHECKER=$(gsd-sdk query agent-skills gsd-ui-checker) ``` Parse JSON for: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_context`, `has_research`, `commit_docs`. diff --git a/get-shit-done/workflows/ui-review.md b/get-shit-done/workflows/ui-review.md index 2385df56..815fedd9 100644 --- a/get-shit-done/workflows/ui-review.md +++ b/get-shit-done/workflows/ui-review.md @@ -18,7 +18,7 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo ```bash INIT=$(gsd-sdk query init.phase-op "${PHASE_ARG}") if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi -AGENT_SKILLS_UI_REVIEWER=$(gsd-sdk query agent-skills gsd-ui-auditor 2>/dev/null) +AGENT_SKILLS_UI_REVIEWER=$(gsd-sdk query agent-skills gsd-ui-auditor) ``` Parse: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `commit_docs`. diff --git a/get-shit-done/workflows/validate-phase.md b/get-shit-done/workflows/validate-phase.md index 8618c574..d515eab5 100644 --- a/get-shit-done/workflows/validate-phase.md +++ b/get-shit-done/workflows/validate-phase.md @@ -18,7 +18,7 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo ```bash INIT=$(gsd-sdk query init.phase-op "${PHASE_ARG}") if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi -AGENT_SKILLS_AUDITOR=$(gsd-sdk query agent-skills gsd-nyquist-auditor 2>/dev/null) +AGENT_SKILLS_AUDITOR=$(gsd-sdk query agent-skills gsd-nyquist-auditor) ``` Parse: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`. diff --git a/get-shit-done/workflows/verify-work.md b/get-shit-done/workflows/verify-work.md index 5b43399d..ff30a699 100644 --- a/get-shit-done/workflows/verify-work.md +++ b/get-shit-done/workflows/verify-work.md @@ -32,8 +32,8 @@ If $ARGUMENTS contains a phase number, load context: ```bash INIT=$(gsd-sdk query init.verify-work "${PHASE_ARG}") if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi -AGENT_SKILLS_PLANNER=$(gsd-sdk query agent-skills gsd-planner 2>/dev/null) -AGENT_SKILLS_CHECKER=$(gsd-sdk query agent-skills gsd-plan-checker 2>/dev/null) +AGENT_SKILLS_PLANNER=$(gsd-sdk query agent-skills gsd-planner) +AGENT_SKILLS_CHECKER=$(gsd-sdk query agent-skills gsd-plan-checker) ``` Parse JSON for: `planner_model`, `checker_model`, `commit_docs`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `has_verification`, `uat_path`. @@ -464,7 +464,7 @@ Run phase artifact scan to surface any open items before marking phase verified: `audit-open` is CJS-only until registered on `gsd-sdk query`: ```bash -gsd-sdk query audit-open --json 2>/dev/null +gsd-sdk query audit-open --json ``` Parse the JSON output. For the CURRENT PHASE ONLY, surface: diff --git a/tests/bug-2636-gsd-sdk-query-silent-swallow.test.cjs b/tests/bug-2636-gsd-sdk-query-silent-swallow.test.cjs new file mode 100644 index 00000000..00402680 --- /dev/null +++ b/tests/bug-2636-gsd-sdk-query-silent-swallow.test.cjs @@ -0,0 +1,73 @@ +/** + * Regression guard for #2636 — `gsd-sdk query agent-skills ` calls in + * workflows must NOT silently swallow failures via a bare `2>/dev/null`. + * + * Root cause of #2636: when the installed npm `@gsd-build/sdk` was stale and + * the `agent-skills` handler was missing, every workflow line of the form + * AGENT_SKILLS_X=$(gsd-sdk query agent-skills 2>/dev/null) + * resolved to empty string, and the `agent_skills.` config was never + * injected into spawn prompts. No error ever surfaced. + * + * Fix: remove `2>/dev/null` from `agent-skills` calls so any SDK failure + * (stale binary, unregistered handler, runtime error) prints to the + * workflow's stderr and is visible to the user. + * + * Test scope: ONLY `gsd-sdk query agent-skills …` (the exact noun implicated + * in #2636). Other `gsd-sdk query config-get …` patterns commonly use + * `2>/dev/null || echo "default"` which IS exit-code aware (the `||` branch + * only runs on non-zero exit) and is a documented fallback pattern. + * + * Scans: get-shit-done/workflows/**\/*.md and commands/**\/*.md + */ + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const path = require('path'); + +const REPO_ROOT = path.join(__dirname, '..'); +const SCAN_ROOTS = [ + path.join(REPO_ROOT, 'get-shit-done', 'workflows'), + path.join(REPO_ROOT, 'commands'), + path.join(REPO_ROOT, 'agents'), +]; + +function walk(dir, out) { + let entries; + try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; } + for (const entry of entries) { + if (entry.name === 'node_modules' || entry.name === '.git') continue; + const full = path.join(dir, entry.name); + if (entry.isDirectory()) walk(full, out); + else if (entry.isFile() && entry.name.endsWith('.md')) out.push(full); + } +} + +describe('bug #2636 — agent-skills query must not silently swallow failures', () => { + test('no `gsd-sdk query agent-skills ... 2>/dev/null` in workflows', () => { + const files = []; + for (const root of SCAN_ROOTS) walk(root, files); + assert.ok(files.length > 0, 'expected to scan some workflow/command files'); + + // Match `gsd-sdk query agent-skills ` followed (on the same line) + // by `2>/dev/null` — the silent-swallow anti-pattern. + const ANTI = /gsd-sdk\s+query\s+agent-skills\b[^\n]*2>\/dev\/null/; + + const offenders = []; + for (const file of files) { + const lines = fs.readFileSync(file, 'utf8').split('\n'); + for (let i = 0; i < lines.length; i++) { + if (ANTI.test(lines[i])) { + offenders.push(path.relative(REPO_ROOT, file) + ':' + (i + 1) + ': ' + lines[i].trim()); + } + } + } + + assert.strictEqual( + offenders.length, 0, + 'Found `gsd-sdk query agent-skills ... 2>/dev/null` (silent swallow — ' + + 'root cause of #2636). Remove `2>/dev/null` so SDK failures surface.\n\n' + + offenders.join('\n'), + ); + }); +});