diff --git a/openspec/changes/archive/2026-08-26-add-cli-help-flag/.openspec.yaml b/openspec/changes/archive/2026-08-26-add-cli-help-flag/.openspec.yaml new file mode 100644 index 0000000..9bb4379 --- /dev/null +++ b/openspec/changes/archive/2026-08-26-add-cli-help-flag/.openspec.yaml @@ -0,0 +1,3 @@ +schema: spec-driven +created: 2026-08-26 +skip_specs: true diff --git a/openspec/changes/archive/2026-08-26-add-cli-help-flag/proposal.md b/openspec/changes/archive/2026-08-26-add-cli-help-flag/proposal.md new file mode 100644 index 0000000..c7e0e97 --- /dev/null +++ b/openspec/changes/archive/2026-08-26-add-cli-help-flag/proposal.md @@ -0,0 +1,34 @@ +## Why + +After the first real `npm publish` attempt of `@openspec-ui/cli` +(`2026-08-26-prepare-cli-npm-publish`), the user tried +`openspec-ui-cli --help` to see the available options and got only +`openspec-ui-cli: unknown command '--help' (supported: validate)` — no +usage text at all. The options (`--cwd`, `--format`) were documented +only in `packages/cli/README.md` on the npm registry page, not +discoverable from the CLI itself. Any real external consumer running +this CLI for the first time hits the same gap. + +## What Changes + +- Add a `USAGE` string and `--help`/`-h` handling to + `packages/cli/src/main.ts`: printed to stdout, exits `0`. +- Also print `USAGE` to stderr alongside the existing "unknown command" + and argument-parsing error messages, so a mistake (not just an + explicit `--help`) also surfaces the available options immediately. +- Update `packages/cli/README.md` to mention `--help`/`-h`. + +## Capabilities + +### Modified Capabilities + +(none in the specified-behavior sense — `validate`'s own behavior and +exit-code contract are unchanged; this only adds a `--help` path and +richer error output. `.openspec.yaml` sets `skip_specs: true`.) + +## Impact + +- `packages/cli/src/main.ts` +- `packages/cli/src/main.test.ts` +- `packages/cli/README.md` +- `.changeset/*.md` (new changeset file) diff --git a/openspec/changes/archive/2026-08-26-add-cli-help-flag/tasks.md b/openspec/changes/archive/2026-08-26-add-cli-help-flag/tasks.md new file mode 100644 index 0000000..8632da3 --- /dev/null +++ b/openspec/changes/archive/2026-08-26-add-cli-help-flag/tasks.md @@ -0,0 +1,24 @@ +## 1. Add --help + +- [x] 1.1 Add a `USAGE` string (command syntax, `--cwd`/`--format` + options, exit-code contract) and `--help`/`-h` handling to + `runMain()` in `packages/cli/src/main.ts`, checked before argument + parsing; prints to stdout and exits `0`. +- [x] 1.2 Print `USAGE` to stderr alongside the existing unknown-command + and argument-parsing error messages. +- [x] 1.3 Update `packages/cli/README.md` to mention `--help`/`-h`. + +## 2. Verification + +- [x] 2.1 Add tests to `packages/cli/src/main.test.ts`: `--help` and + `-h` print usage and exit `0`; an unknown command still exits `2` and + now also prints usage as a second stderr line (existing assertions on + the first stderr line are unaffected). +- [x] 2.2 `npm run typecheck`, `npm run lint`, and `npm run test` + (workspace) pass for `@openspec-ui/cli`. +- [x] 2.3 Rebuild (`npm run build --workspace @openspec-ui/cli`) and + smoke-test `node packages/cli/dist/cli.js --help` directly. +- [x] 2.4 Propose a changeset (`npx changeset`) for `@openspec-ui/cli` + (patch: new flag, no breaking change) instead of hand-editing + `version`/`CHANGELOG.md`; apply it via `npx changeset version`. +- [x] 2.5 Run `openspec change validate --strict add-cli-help-flag`. diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 176f863..8a57c39 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,15 @@ # @openspec-ui/cli +## 0.1.2 + +### Patch Changes + +- Add `--help`/`-h` to `openspec-ui-cli`, printing usage (command syntax, + options, exit codes) and exiting `0`. Usage is now also printed + alongside argument-parsing and unknown-command errors, so a mistake + surfaces the available options immediately instead of only an error + message. + ## 0.1.1 ### Patch Changes diff --git a/packages/cli/README.md b/packages/cli/README.md index 19e83e5..67b511f 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -17,6 +17,11 @@ npx @openspec-ui/cli validate --cwd . --format text to the current directory). - `--format json|text`: `json` (the default) prints an aggregated report suitable for further processing; `text` prints a human-readable table. +- `--help` / `-h`: print this usage summary and exit `0`. + +```sh +npx @openspec-ui/cli --help +``` ## Exit codes diff --git a/packages/cli/package.json b/packages/cli/package.json index 26b1975..54e1d9b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openspec-ui/cli", - "version": "0.1.1", + "version": "0.1.2", "description": "Non-interactive OpenSpec change validation for CI merge gates (see docs/adr/0007-ci-cli-third-delivery-target.md).", "license": "MIT", "repository": { diff --git a/packages/cli/src/main.test.ts b/packages/cli/src/main.test.ts index 2f42c42..da51eb8 100644 --- a/packages/cli/src/main.test.ts +++ b/packages/cli/src/main.test.ts @@ -97,4 +97,34 @@ describe("runMain", () => { expect(code).toBe(2); expect(io.errLines[0]).toContain("--format must be"); }); + + it("prints usage and exits 0 for --help", async () => { + const io = collectingIo(); + + const code = await runMain(["--help"], { validateAll: vi.fn(), ...io }); + + expect(code).toBe(0); + expect(io.outLines[0]).toContain("Usage:"); + expect(io.outLines[0]).toContain("--cwd"); + expect(io.outLines[0]).toContain("--format"); + }); + + it("prints usage and exits 0 for -h", async () => { + const io = collectingIo(); + + const code = await runMain(["-h"], { validateAll: vi.fn(), ...io }); + + expect(code).toBe(0); + expect(io.outLines[0]).toContain("Usage:"); + }); + + it("prints usage alongside an unknown-command error", async () => { + const io = collectingIo(); + + const code = await runMain(["bogus"], { validateAll: vi.fn(), ...io }); + + expect(code).toBe(2); + expect(io.errLines[0]).toContain("unknown command"); + expect(io.errLines[1]).toContain("Usage:"); + }); }); diff --git a/packages/cli/src/main.ts b/packages/cli/src/main.ts index 08fe663..0fe79ab 100644 --- a/packages/cli/src/main.ts +++ b/packages/cli/src/main.ts @@ -5,6 +5,22 @@ import { runValidateAll, type ValidateAllResult } from "./openspec-validate.js"; +const USAGE = `openspec-ui-cli — non-interactive OpenSpec change validation for CI merge gates. + +Usage: + openspec-ui-cli validate [--cwd ] [--format json|text] + +Options: + --cwd Repository root containing openspec/changes/ (default: current directory) + --format json|text Output format (default: json) + --help, -h Show this help and exit + +Exit codes: + 0 every active change passed strict validation + 1 at least one active change failed strict validation + 2 the CLI itself could not complete the check (bad arguments, the + openspec CLI missing, a filesystem error)`; + export interface MainOptions { cwd?: string; format?: "json" | "text"; @@ -58,13 +74,20 @@ export async function runMain(argv: string[], deps: MainDeps = {}): Promise