fix(#2636): surface gsd-sdk query failures and add workflow↔handler parity check (#2656)

* fix(#2636): surface gsd-sdk query failures and add workflow↔handler parity check

Root cause: workflows invoked `gsd-sdk query agent-skills <slug>` 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.<slug>` 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.
This commit is contained in:
Tom Boucher
2026-04-24 18:10:45 -04:00
committed by GitHub
parent 7ed05c8811
commit c8ae6b3b4f
24 changed files with 115 additions and 42 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
```
</step>
@@ -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)
```
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,73 @@
/**
* Regression guard for #2636 — `gsd-sdk query agent-skills <slug>` 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 <slug> 2>/dev/null)
* resolved to empty string, and the `agent_skills.<slug>` 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 <slug>` 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'),
);
});
});