fix(cli): intercept --help on every subcommand (#1201)#1202
Merged
bradygaster merged 4 commits intoJun 3, 2026
Conversation
Previously `squad <command> --help` and `squad <command> -h` were silently dropped by the CLI router and the command would execute for real — sometimes with destructive side effects (`squad init --help` scaffolded .squad/, .github/, .gitignore into the cwd; `squad triage --help` and `squad watch --help` started a long polling loop; `squad doctor --help` ran the doctor; etc.). Only `loop --help` and `state-mcp --help` were intercepting the flag. Fix: - Add `packages/squad-cli/src/cli/core/command-help.ts` with a per-command help registry (`printCommandHelp`) and a generic fallback (`printGenericCommandHelp`) for commands without dedicated text. - In `cli-entry.ts`, intercept `--help`/`-h` for every subcommand in one place near the top of the router, after the existing top-level `--help` and `--version` handlers. Skip the intercept only when no command is present or when the command itself is `help`/`version` (already handled above). - Remove the now-redundant inline `--help` blocks from the `state-mcp` and `loop` routes; the early intercept handles them and dispatches into the same `command-help.ts` registry, so help text stays centralized. Tests: - `test/cli/command-help.test.ts` — unit tests for `printCommandHelp` and `printGenericCommandHelp`, plus end-to-end spawns that prove `squad init --help`, `init -h`, `triage --help`, `watch --help`, `doctor --help`, `status --help` print help and do not trigger any of the side effects (no .squad/.github scaffolding, no polling loop, no doctor banner, no active-squad print). - `test/acceptance/features/subcommand-help.feature` — Gherkin scenarios wired into the existing acceptance runner. Closes bradygaster#1201 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a long-standing CLI routing bug where squad <subcommand> --help / -h would silently run the subcommand (sometimes with destructive side effects). It centralizes per-command help text in a registry and adds an early router intercept so help is printed and the command exits before any real execution.
Changes:
- Added a centralized per-command help registry (
command-help.ts) with a generic fallback and a test helper (commandsWithHelp()). - Updated
cli-entry.tsto intercept--help/-hafter any subcommand and route help output through the registry. - Added unit + acceptance coverage to prevent regressions for subcommand help interception.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
packages/squad-cli/src/cli/core/command-help.ts |
Adds per-command help registry, generic fallback, and command list helper for tests. |
packages/squad-cli/src/cli-entry.ts |
Adds early subcommand help intercept; removes redundant inline --help handling from specific routes. |
test/cli/command-help.test.ts |
Adds unit tests for the help registry plus optional CLI-level E2E checks when dist/ is present. |
test/acceptance/features/subcommand-help.feature |
Adds acceptance scenarios verifying subcommand --help/-h produces help output and exits cleanly. |
test/acceptance/acceptance.test.ts |
Registers the new acceptance feature. |
.changeset/fix-subcommand-help.md |
Publishes a patch changeset for the CLI behavior fix. |
This was referenced Jun 3, 2026
…radygaster#1202 review) The help registry is keyed by canonical command name, but cli-entry.ts routes 'streams' and 'workstreams' to subsquads. Without normalization, 'squad streams --help' / 'squad workstreams --help' fell through to the generic fallback. Add a COMMAND_ALIASES map + normalizeCommandAlias() helper, apply it inside printCommandHelp, and cover both aliases in test/cli/command-help.test.ts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…verage (PR bradygaster#1202 review) The 'falls back to a generic help message for unknown commands' case actually ran 'discover --help' (a registered command) and only asserted command-specific output, leaving the generic-fallback CLI path untested. Rename it to describe what it does and add a new case that spawns the CLI with a guaranteed-unknown command name and asserts the generic-fallback wording. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…gaster#1202 review) The Gherkin scenario for 'init --help' only checked stdout / exit code, so a regression that writes .squad/, .github/, or .gitignore while printing help would still pass. Add a new 'the temp directory has no <X> entry' step definition and assert all three paths are absent after the run. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Fixes
squad <command> --help(and-h) silently running the command instead of showing help text. The flag is now intercepted in one place for every subcommand and routed to a centralized per-command help registry.Why
Closes #1201.
Before this PR,
--helpand-hwere intercepted only when the first argument (squad --help/squad -h/squad help). After a subcommand, the flag was silently dropped and the command would execute for real — sometimes with destructive side effects:squad init --help.squad/,.github/,.gitignore,.gitattributes,.copilot/into the cwdsquad triage --help/watch --helpsquad doctor --helpsquad status --helpsquad roles --help,upgrade --help,hire --help, …all the restOnly
squad loop --helpandsquad state-mcp --helpwere checking for the flag.This actively trained users out of using
--helpon this CLI, andinit --helpwriting files in the cwd was outright destructive. Related (closed but unfixed): #757.How
One place, one registry.
New
packages/squad-cli/src/cli/core/command-help.tsexports:printCommandHelp(cmd, version): boolean— lookscmdup in a record of per-command help printers, prints the matching help block, returnstrue. Returnsfalseif no dedicated block exists.printGenericCommandHelp(cmd): void— friendly fallback that names the command and points atsquad help.commandsWithHelp(): readonly string[]— test helper that lists registered commands (used by a regression-guard test).In
cli-entry.ts, immediately after the existing top-level--helpblock and before the per-command routes, intercept any--help/-hthat appears after a subcommand:s if (cmd && cmd !== 'help' && cmd !== '--help' && cmd !== '-h' && cmd !== 'version' && cmd !== '--version' && cmd !== '-v' && (args.includes('--help') || args.includes('-h'))) { if (!printCommandHelp(cmd, VERSION)) { printGenericCommandHelp(cmd); } return; }The previously-existing inline
--helpblocks inside thestate-mcpandlooproutes are now redundant (the early intercept fires first) and have been removed. Their help text moved into the new registry, so help stays centralized.Help text covers every routed command. Every command in the existing
cmd === '...'switch has a dedicated entry:init, upgrade, migrate, status, roles, cost, triage, watch, loop, hire, copilot, plugin, export, import, scrub-emails, start, nap, memory, state-mcp, doctor, consult, extract, subsquads, link, build, aspire, schedule, personal, preset, cast, rc, remote-control, copilot-bridge, init-remote, rc-tunnel, discover, delegate, upstream, economy, externalize, internalize, config. A unit test (commandsWithHelp()equality) guards against regression when a new command is added.Behavior is preserved for cases that already worked.
squad --help,squad -h,squad help,squad(no args),squad --version,squad -v,squad versionall still route exactly as before.Verification
Manual repro after the fix (cwd is empty after each call — no scaffolding):
`
$ node packages/squad-cli/dist/cli-entry.js init --help
squad init v0.9.7-preview — Initialize Squad in the current project
Usage: squad init [options]
squad init --mode remote
...
$ node packages/squad-cli/dist/cli-entry.js doctor --help
squad doctor v0.9.7-preview — Validate squad setup
Usage: squad doctor
...
`
Automated:
npm run build— passesnpm run lint— passes (tsc --noEmitclean on both packages)npm run lint:eslint— 0 errors (only the existing pre-existing warnings; the new file is tagged/* eslint-disable no-console */since it's a help printer, matching the style of the top-level help incli-entry.ts)npx vitest run test/cli/command-help.test.ts— 11/11 passnpx vitest run test/acceptance/acceptance.test.ts— all 33 scenarios pass, including the 4 new ones fromsubcommand-help.featurenpx vitest run test/cli/loop.test.ts test/cli/state-mcp.test.ts test/cli/init.test.ts test/cli/doctor.test.ts test/acceptance/acceptance.test.ts test/cli/command-help.test.ts— 100% pass.changeset/fix-subcommand-help.mdadded)PR Readiness Checklist
Branch & Commit
dev(notmain)devBuild & Test
npm run buildpassesnpm testpasses (all directly-affected suites; fullnpm testrun shows a small number of pre-existing flakes on Windows tied to~/.squadpresence andcli-packaging-smokenpm packcollisions under parallel workers — none touched by this PR)npm run lintpassesnpm run lint:eslintpassesChangeset
.changeset/fix-subcommand-help.md(@bradygaster/squad-cli: patch)Docs
Exports
squad-cli.Breaking Changes
None. Public CLI surface (existing commands and their semantics) is unchanged. Only behavior change is that
squad <cmd> --helpnow exits cleanly with help text instead of executing the command.Waivers
None.