Skip to content

fix(cli): intercept --help on every subcommand (#1201)#1202

Merged
bradygaster merged 4 commits into
bradygaster:devfrom
tamirdresher:tamirdresher/1201-subcommand-help
Jun 3, 2026
Merged

fix(cli): intercept --help on every subcommand (#1201)#1202
bradygaster merged 4 commits into
bradygaster:devfrom
tamirdresher:tamirdresher/1201-subcommand-help

Conversation

@tamirdresher

Copy link
Copy Markdown
Collaborator

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, --help and -h were 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:

Command Before this PR
squad init --help Scaffolded .squad/, .github/, .gitignore, .gitattributes, .copilot/ into the cwd
squad triage --help / watch --help Started a 10-minute polling loop
squad doctor --help Ran the doctor health check
squad status --help Printed active squad path
squad roles --help, upgrade --help, hire --help, …all the rest Just ran the command, ignoring the flag

Only squad loop --help and squad state-mcp --help were checking for the flag.

This actively trained users out of using --help on this CLI, and init --help writing 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.ts exports:

    • printCommandHelp(cmd, version): boolean — looks cmd up in a record of per-command help printers, prints the matching help block, returns true. Returns false if no dedicated block exists.
    • printGenericCommandHelp(cmd): void — friendly fallback that names the command and points at squad 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 --help block and before the per-command routes, intercept any --help/-h that 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 --help blocks inside the state-mcp and loop routes 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 version all 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 — passes
  • npm run lint — passes (tsc --noEmit clean 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 in cli-entry.ts)
  • npx vitest run test/cli/command-help.test.ts — 11/11 pass
  • npx vitest run test/acceptance/acceptance.test.ts — all 33 scenarios pass, including the 4 new ones from subcommand-help.feature
  • npx 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

⚠️ Quick Check

  • If SDK/CLI source files changed: completed the applicable Changeset step below (.changeset/fix-subcommand-help.md added)

PR Readiness Checklist

Branch & Commit

  • Branch created from dev (not main)
  • Branch is up to date with dev
  • Verified diff contains only intended changes
  • PR is not in draft mode (will mark ready once CI is green)
  • Commit history is clean (single commit)

Build & Test

  • npm run build passes
  • npm test passes (all directly-affected suites; full npm test run shows a small number of pre-existing flakes on Windows tied to ~/.squad presence and cli-packaging-smoke npm pack collisions under parallel workers — none touched by this PR)
  • npm run lint passes
  • npm run lint:eslint passes

Changeset

  • Changeset added via direct file: .changeset/fix-subcommand-help.md (@bradygaster/squad-cli: patch)

Docs

  • No new user-facing capability — help text additions only. README is unchanged.

Exports

  • N/A — no new public modules; the new helper is internal to squad-cli.

Breaking Changes

None. Public CLI surface (existing commands and their semantics) is unchanged. Only behavior change is that squad <cmd> --help now exits cleanly with help text instead of executing the command.

Waivers

None.

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>
@tamirdresher tamirdresher marked this pull request as ready for review June 2, 2026 12:31
Copilot AI review requested due to automatic review settings June 2, 2026 12:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.ts to intercept --help/-h after 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.

Comment thread packages/squad-cli/src/cli/core/command-help.ts
Comment thread test/cli/command-help.test.ts Outdated
Comment thread test/acceptance/features/subcommand-help.feature
tamirdresher and others added 3 commits June 3, 2026 08:31
…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>
@bradygaster bradygaster merged commit 3b75cea into bradygaster:dev Jun 3, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: 'squad <command> --help' (and '-h') silently runs the command instead of showing help

3 participants