Skip to content

Give bare checkout, push, and pull git-like defaults

The intended onboarding flow is init → checkout <name> → remote add → bind → pull, but v1 required the explicit forms (checkout create <name>, push --all, pull --all) and made the bare invocations errors. Bare forest checkout <name> now creates, registers, and activates the checkout when the name is not registered (and still just switches when it is). Bare forest push / forest pull now target every bound stage, printing a per-stage warning (Warning: stage 'x' is not bound; skipping. Run: forest bind x <PATH>) for unbound stages instead of aborting — a fresh clone with one bound stage can pull immediately. --all keeps its strict semantics: any unbound stage aborts the whole transfer, so scripts that must move everything still fail loudly.

When no active remote is set and exactly one remote is configured, remote resolution falls back to that remote instead of erroring — remote add origin followed by bare push/pull just works, and forest remote use is only required once a second remote exists. The fallback never writes local.yaml; it is inferred per invocation, so adding a second remote restores the explicit remote use requirement.

Read commands and follow-ups (2026-07-01)

Live verification of the fresh-clone flow (bind one stage of several, start working) surfaced three gaps; the same contract now extends to them:

  • Bare forest status and forest diff cover bound stages with the same per-stage warning, exiting 1 only when no stage is bound (or none is configured). A skipped stage contributes no rows at all — its sync-state entries do not resurface as remote-only. Explicit --stage x on an unbound stage keeps the hard error. forest ls is remote-driven and never needed a binding, so it is unchanged.
  • forest checkout remove --yes skips the confirmation prompt, giving the auto-create trade-off a scriptable undo (removal stays metadata-only, ADR 0001). Without --yes, the non-interactive-terminal guard is unchanged.
  • forest add STAGE PATH --sync-by [subdirectory|directory|file] sets the unit-discovery mode at creation instead of requiring a hand-edit of forest.yaml; the default (subdirectory) is not serialized. When a bare push finds no units but a bound stage directory is non-empty, it prints a one-line hint naming the stage's sync_by mode.

Consequences

  • forest checkout <name> on an unregistered name runs the create flow, including the legacy-root (forest checkout adopt) guard and checkout-name validation; typo'd names create a checkout, which forest checkout remove (or remove --yes in scripts) undoes.
  • Bare push/pull with zero configured stages keep the existing "no stages configured" error; with stages but zero bound they exit 1 after the warnings.
  • Bare pull discovers units from the remote (like pull --all); bare push discovers them locally (like push --all).
  • checkout create, --all, --stage, --id, and explicit unit ids behave exactly as before.
  • All remote-using commands (push/pull/status/diff/ls) inherit the single-remote fallback via _resolve_remote; with two or more remotes and no active one they error as before.
  • Contract locked in tests/test_bare_command_defaults.py; supersedes VAL-PULL-028 / VAL-PUSH-035 usage-error expectations.