CLI Principles
CLI Principles Section 6a Draft

6a. Minimal, targeted writes

Source 06a-minimal-writes.md

Back to the spec index

Goal: each write changes as little as possible, so a wrong command does little harm and is easy to review.

  • One target per command is strongly encouraged. It is not strict. A write names its target by exact ID. No wildcards, no “all”, no implicit “current” target. Bulk work is best done as many small commands, each confirmed on its own.
    • A CLI that does take several targets says so in describe (a repeatable -id). The changes output lists every target, and -change-limit caps how many one command may touch. It is atomic over the targets where it can be, and reports exactly what was applied where it cannot (see Atomic).
  • Narrow verbs over general ones. Prefer tracker task set-status -id T-12 -status done to tracker task update -file whole-task.yaml. A write takes only the fields it changes.
  • Patch, not replace. update merges the named fields and leaves the rest alone. Whole-object replace is a separate, clearly named verb (replace).
  • Unset is explicit. Clearing a field needs its own flag (-unset <field>). An empty or missing value never clears anything.
  • No upsert by default. create fails if the thing exists. update fails if it does not. Combine them only in a named upsert verb.
  • Preconditions. A write can state what it expects to be true (-if-version). An etag or revision is carried as the version token, so there is one parameter and one spelling (3a). Preconditions are optional. If the data changed since the agent last read it, the write fails with a conflict code and changes nothing. get output includes the version so it can be passed back.
  • Idempotent. Running the same write twice gives the same result and reports unchanged the second time. No duplicate side effects. This is a principle for every command, see section 1.
  • Atomic. A write either applies fully or not at all. Multi-step writes inside one command roll back, or report exactly what was applied.
  • Show the exact diff. changes in the confirm and dry-run output (section 6) lists each field as before -> after. If nothing would change, say so and exit 0 without writing.
  • Report what changed. The result lists only the fields actually changed, plus the new version.
  • Guard the dangerous ones. delete and replace need -confirm like every mutation (it is always a bare parameter). The changes output shows exactly what will be removed or replaced. Prefer soft delete (mark deleted, keep recoverable) and print how to undo.
  • Limits. Caps per command (items touched, bytes written). Going over the cap needs an explicit -change-limit N. The default is a number an adopter fixes.
  • Backups. For file-backed data, a write keeps the previous version (or an undo record) before it replaces anything. Backups are kept for a default time (for example 7 days, an adopter fixes the value): a write prunes backups older than that. They live in the CLI’s data folder, ~/.local/share/<cli>/backup/ (section 9).
  • Remote systems. Same rules: one resource, one field set, version check against the remote revision, no cascading changes the caller did not name.