Skip to content

feat(cli): command-descriptor registry + stash manifest --json#572

Merged
coderdan merged 2 commits into
agent-friendly-clifrom
cli-command-registry-manifest
Jul 8, 2026
Merged

feat(cli): command-descriptor registry + stash manifest --json#572
coderdan merged 2 commits into
agent-friendly-clifrom
cli-command-registry-manifest

Conversation

@coderdan

@coderdan coderdan commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Implements phase 1 of the CLI help/manifest proposal (docs/plans/cli-help-and-manifest.md), tracked by CIP-3431.

Stacked on #569 (agent-friendly-cli). Base this PR's review on the diff against that branch; it should be reviewed/merged after #569.

Why

The docs V2 CLI reference is generated from the stash CLI, but today it has to scrape --help — a hand-written string that drifts from the real command set (e.g. published --help still listed db install after it moved to eql install). This adds a structured, versioned source of truth.

What

  • src/cli/registry.tsFlag / CommandDescriptor / CommandGroup types plus the full command surface (groups, summaries, flags; long + examples for key commands), populated from the existing HELP and per-command flag parsing.
  • src/cli/manifest.tsbuildManifest(version){ name, version, groups[] }, the exact contract the docs generator (cipherstash/docs#45) targets. version comes from the CLI's package.json, so a generated page always names the version it describes.
  • stash manifest --json emits the structured surface; stash manifest (no flag) prints a grouped human-readable list. Pure metadata — no native binary required, so it runs anywhere.

Scope / non-goals

Additive and non-breaking: the top-level HELP string is untouched in this PR. Rendering the top-level and per-command --help from the same registry (and wiring run) is the documented phase 2–4 follow-on — deliberately separate to keep this diff reviewable and to avoid disturbing the help text existing e2e tests pin.

Tests

  • Unit: buildManifest shape, version stamping, hidden-command exclusion, clean JSON round-trip, the auth login worked example, shared --database-url flag.
  • E2E: stash manifest --json (versioned/grouped, contains known commands), stash manifest (plain list), --help lists manifest.
  • Full suite green: 367 unit + all auth/help e2e; typecheck adds no new errors; lint clean.

@coderdan coderdan requested a review from a team as a code owner July 8, 2026 03:38
@changeset-bot

changeset-bot Bot commented Jul 8, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d6c7a48

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
stash Minor
@cipherstash/e2e Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6d0f61ab-8342-4a7b-95d1-364d68b75d89

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cli-command-registry-manifest

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…-3431)

Phase 1 of docs/plans/cli-help-and-manifest.md: a single source of truth for
command metadata so help/docs can't drift from the real command set.

- src/cli/registry.ts: Flag/CommandDescriptor/CommandGroup types + the full
  command surface (groups, summaries, flags; long/examples for key commands),
  populated from the existing HELP surface and per-command flag parsing.
- src/cli/manifest.ts: buildManifest(version) → { name, version, groups[] },
  the exact contract the docs generator (cipherstash/docs#45) targets. version
  comes from the CLI's package.json.
- `stash manifest --json` emits the structured surface; `stash manifest` prints
  a grouped human-readable list. Pure metadata — no native binary required.

Additive and non-breaking; the top-level HELP string is untouched. Rendering
help from the registry (phases 2–4) is the documented follow-on.

Tests: unit coverage for buildManifest shape/version/hidden-exclusion; e2e for
`manifest --json`, `manifest`, and `--help` listing the command.
@coderdan coderdan force-pushed the cli-command-registry-manifest branch from 202dab5 to 715e489 Compare July 8, 2026 04:27

@coderdan coderdan left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Test-coverage review — stash manifest / command-descriptor registry (PR #572)

Solid coverage overall: buildManifest version/name stamping, non-hidden command counts, the auth login worked example, the shared --database-url flag, JSON round-tripping, and both e2e output modes are all exercised. Two gaps worth closing, both in packages/cli/src/cli/manifest.ts:

  1. The hidden-command exclusion is only tested vacuously. No command in registry.ts sets hidden: true, so the excludes hidden commands test loops over an empty array and the .filter((cmd) => !cmd.hidden) branch's actual drop path is never executed. This is the classic "the negative case has no data to negate" anti-pattern — the guard could be inverted (filter(c => c.hidden)) and every test would still pass. Because buildManifest reads the module-level registry, closing this needs a small testability seam.
  2. The examples optional-field passthrough branch is untested. toManifestCommand has three separate if (...!== undefined) copy branches for long, examples, and flags; long and flags are asserted present (via auth login) and the all-undefined case is asserted absent (via wizard), but no test confirms a command's examples array actually survives into the manifest.

Inline sketches below. No out-of-scope security concerns noted.

Additional coverage gaps not posted inline

  • The text-mode output of manifestCommand (commands/manifest/index.ts) is exercised by the e2e only for auth login / --json substrings; the grouped title: headers and the trailing Run stash manifest --json hint line are not asserted. Low value — mentioning for completeness.

Comment thread packages/cli/src/cli/manifest.ts
Comment thread packages/cli/src/cli/manifest.ts Outdated
Registry-vs-reality fixes (the drift this PR exists to prevent):
- encrypt status/plan: drop --table/--column — both dispatch to zero-arg
  commands (statusCommand()/planCommand()) that ignore them
- eql install: add the real --name/--out flags (forwarded to the drizzle path)
- db migrate: mark summary "(not yet implemented)" and drop the --database-url
  flag it never reads (dispatch only prints a not-implemented warning)

Cleanup / correctness:
- manifest: toManifestCommand now defensively copies examples/flags so the
  manifest can't alias the registry's shared flag singletons (mutation safety)
- registry: extract TABLE/COLUMN/DRY_RUN/EXCLUDE_OPERATOR_FAMILY/SUPABASE_COMPAT
  shared flag consts (were copy-pasted 3-5x), matching the DATABASE_URL_FLAG
  pattern
- manifest: drop the single-use `ManifestFlag = Flag` alias
- soften the "single source of truth" claim (registry docstring + changeset):
  it's additive; HELP stays authoritative for --help until phase 2

Tests:
- buildManifest gains a `groups` injection seam so the hidden-command filter is
  driven by a stub (was vacuously green against the real registry)
- add positive assertions for examples passthrough and the defensive-copy fix
@coderdan

coderdan commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Addressed a code review + the test-gap review in d6c7a482.

Registry-vs-reality drift (the failure mode this PR exists to prevent — the manifest was hand-transcribed from HELP and had picked up its inaccuracies):

  • encrypt status / encrypt plan no longer advertise --table/--column — both dispatch to zero-arg statusCommand()/planCommand() that ignore them.
  • eql install now surfaces its real --name/--out flags (forwarded into the drizzle migration path).
  • db migrate summary now reads "(not yet implemented)" and drops the --database-url flag it never reads (dispatch only prints a not-implemented warning).

Correctness / cleanup:

  • toManifestCommand defensively copies examples/flags, so the manifest can't alias the registry's shared flag singletons (a consumer mutating a manifest entry can no longer corrupt the registry or sibling commands).
  • Extracted TABLE/COLUMN/DRY_RUN/EXCLUDE_OPERATOR_FAMILY/SUPABASE_COMPAT shared flag consts (were copy-pasted 3–5×), matching the existing DATABASE_URL_FLAG pattern.
  • Dropped the single-use ManifestFlag = Flag alias.
  • Softened the "single source of truth" wording (registry docstring + changeset) — phase 1 is additive; HELP stays authoritative for --help and must be kept in sync by hand until the phase-2 follow-on renders it from the registry.

Test gaps (both threads):

  • buildManifest gained a groups injection seam; the hidden-command filter is now driven by a stub (was vacuously green against the real registry).
  • Added positive assertions for the examples passthrough and the new defensive-copy behavior.

One finding I did not change: the stash <version> banner in manifest is hard-coded and asserted by the e2e. AGENTS.md:77 exempts command-name-like literals ("keep them inline"), and stash is the binary name + runtime version data — no assertion-stable copy to extract — so leaving it inline is the rule-compliant choice.

Verification: 382 unit + manifest e2e green; typecheck adds no new errors; lint clean.

@coderdan coderdan changed the title feat(cli): command-descriptor registry + stash manifest --json (CIP-3431) feat(cli): command-descriptor registry + stash manifest --json Jul 8, 2026
@coderdan coderdan merged commit 7b0bf68 into agent-friendly-cli Jul 8, 2026
6 checks passed
@coderdan coderdan deleted the cli-command-registry-manifest branch July 8, 2026 05:02
coderdan added a commit that referenced this pull request Jul 8, 2026
Registry-vs-reality fixes (the drift this PR exists to prevent):
- encrypt status/plan: drop --table/--column — both dispatch to zero-arg
  commands (statusCommand()/planCommand()) that ignore them
- eql install: add the real --name/--out flags (forwarded to the drizzle path)
- db migrate: mark summary "(not yet implemented)" and drop the --database-url
  flag it never reads (dispatch only prints a not-implemented warning)

Cleanup / correctness:
- manifest: toManifestCommand now defensively copies examples/flags so the
  manifest can't alias the registry's shared flag singletons (mutation safety)
- registry: extract TABLE/COLUMN/DRY_RUN/EXCLUDE_OPERATOR_FAMILY/SUPABASE_COMPAT
  shared flag consts (were copy-pasted 3-5x), matching the DATABASE_URL_FLAG
  pattern
- manifest: drop the single-use `ManifestFlag = Flag` alias
- soften the "single source of truth" claim (registry docstring + changeset):
  it's additive; HELP stays authoritative for --help until phase 2

Tests:
- buildManifest gains a `groups` injection seam so the hidden-command filter is
  driven by a stub (was vacuously green against the real registry)
- add positive assertions for examples passthrough and the defensive-copy fix
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.

1 participant