Skip to content

feat!: one name per meaning in the CLI, typed and snake_case output - #15

Merged
romamo merged 6 commits into
mainfrom
cli-best-practices
Oct 3, 2026
Merged

romamo merged 6 commits into
mainfrom
cli-best-practices

Conversation

@romamo

@romamo romamo commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator

Items 1 to 5 of the CLI review. Each one is a breaking change best made before 1.0.

Names

  • Decision records get their own group: decisions list|show|approve. operations decisions and operations approve move there, and decisions show is new. operations keeps the catalog: list, show, propose
  • Renames: data migrate is data copy, no longer read as migrate. services inspect is services observe, matching observe
  • Old paths still answer with exit 13 REDIRECTED and the new command in error.redirect.command

Flags

  • migrate: --release and --env-file are now --component-release and --component-env-file. They take COMPONENT=VALUE pairs, while deploy's flags of the same name take one value
  • --observed: defaults to tmp/observed everywhere. Before, observe && audit failed because audit required the flag, and the operator used tmp/operator/observed

Output

  • snake_case data keys: field names in data are snake_case, like treaty's meta
    • Map keys that are data keep their spelling: resource kinds, environment variable names and ids
    • Embedded records, anything with an apiVersion, stay as stored
  • Typed output schemas: every data key now has a JSON type, from one table in commands.py, with a per-shape name:type override where a name holds something else. treaty checks every answer against it
  • Empty collections: an optional list or object is written as []/{} rather than null, as treaty's agent spec requires

Not in this PR

  • Item 6, settings instead of repeated flags (--schemas, --engine, gateway TLS): blocked on two treaty gaps:
    • The mcp serve tool provider and provided tools can't read the settings
    • treaty reads .cloudfall.toml from the current directory, not from the project directory
  • Item 7, one flag for the fleet: deferred to M16

Checks

  • ruff and mypy are clean, and pytest passes: 363 tests
  • New tests cover the default --observed, decisions show, the redirects, and the snake_case rule
  • treaty audit is unchanged: the same 5 known advice and warning findings

Downstream

  • atlas-fleet: its Taskfile calls services inspect, which now redirects to services observe
  • LedgrAI: its README calls data migrate in a note, which now redirects to data copy

🤖 Generated with Claude Code

romamo and others added 6 commits October 3, 2026 19:47
audit, services status and dashboard required it, while observe writes
tmp/observed by default, so `observe && audit` failed. The operator's drift
checks refresh the same directory instead of tmp/operator/observed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ponent-env-file

deploy --release and --env-file take one value; migrate's took
COMPONENT=VALUE pairs under the same names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
operations kept both the catalog and the decision records, one letter pair
away from operator's proposal receipts, and each had an approve. Decision
records move to `decisions list|show|approve`; `decisions show` is new.
`data migrate` (copy one database) is `data copy`, no longer read as
`migrate`, and `services inspect` is `services observe`, as `observe`
collects server evidence. The old paths answer REDIRECTED (exit 13) with
the new command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
meta used snake_case and data camelCase in the same envelope. Field names
in data are now snake_case at every depth (openProposals is open_proposals).
Map keys that are data keep their spelling: resource kinds, environment
variable names and ids. A record or resource embedded in data, anything
with an apiVersion, keeps the form it is stored in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every command's output schema listed its data keys with no types, so an
agent could not tell a list from a string without running the command.
Each key now declares its type in the operating contract (one table by
name, overridden per shape where a name holds something else), and treaty
checks every answer against it. As treaty's agent spec requires, an
optional list or object is written empty, not null: inventory show's
ansible is {} for a project.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@romamo
romamo merged commit 087c0f7 into main Oct 3, 2026
1 check passed
@romamo
romamo deleted the cli-best-practices branch October 3, 2026 17:13
@shipmill-romamo shipmill-romamo Bot added this to the 0.6.0 milestone Oct 8, 2026
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