spec: project defaults — zero-arg deploy, resolved -app, saved state
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 file changed,
+117
−0
docs/superpowers/specs/2026-08-13-project-defaults-design.md+117 −0
diff --git a/docs/superpowers/specs/2026-08-13-project-defaults-design.md b/docs/superpowers/specs/2026-08-13-project-defaults-design.md| new file mode 100644 |
| index 000000000..69c7cf26d |
| --- /dev/null |
| +++ b/docs/superpowers/specs/2026-08-13-project-defaults-design.md |
| @@ -0,0 +1,117 @@ |
| +# Project defaults: zero-arg deploy, resolved -app, saved project state |
| + |
| +**Date:** 2026-08-13 · **Status:** approved (Paul, in-session; all three |
| +open calls answered: prompt-once-save-suggest artifacts, offer app create, |
| +app resolution on every app-scoped member verb) |
| + |
| +The capstone of the proactive-CLI direction (#157, #160–#162): a command |
| +run with no arguments resolves what it can, asks about what is genuinely |
| +ambiguous, and offers to remember the answers as project state in |
| +`./.carlos/config` — so `carlos deploy`, alone, in a fresh checkout, is |
| +the entire onboarding: login offered, account resolved, app chosen or |
| +created, artifact named once, and every later `carlos deploy` is |
| +zero-question. Scripts and non-TTY callers keep exact current behavior; |
| +explicit flags always win and never touch a prompt. |
| + |
| +## Config schema (all additive; rewrites already preserve unknown keys) |
| + |
| +`cliConfig` gains: |
| + |
| + app — the project's app name |
| + kind — "binary" | "static" |
| + artifact — project-relative path deploy ships (dir for static, |
| + file for binary) |
| + target — GOOS-GOARCH, binary kind only |
| + |
| +The pairing rule extends to the triple: `app` (and the deploy block) are |
| +honored only when the SAME file's `console` matches the session's console |
| +— the file means "this checkout deploys as this app on this console(/ |
| +account)", and `-console` to another stack must drag none of it along. |
| +The `account` key keeps its existing rule unchanged. |
| + |
| +## Omitted `-app` (API mode, every app-scoped member verb) |
| + |
| +deploy, ship, promote, rollback, channels, releases (+ retention), |
| +restart, logs, env, secrets, instances (signed-in verbs), store, domains, |
| +ledger, version target — one seam (`resolvedApp`, sibling of |
| +`resolvedAccount`), in order: |
| + |
| +1. **`-app` flag** — verbatim, exactly as today. |
| +2. **Config `app`** (nearest walk, console-paired as above) — muted |
| + `app: <name> (the default from <file>)` line. |
| +3. **The resolved account's apps**: `GET /api/cli/apps` already returns |
| + each membership's `apps` array — the SAME single fetch defaultAccount |
| + memoizes, now kept whole on the session (accounts + apps; one request |
| + per invocation total, however many of account/app resolve). |
| + - exactly one app → use it, muted `app: <name>`; |
| + - several + TTY → numbered chooser (the #159 chooser generalized), |
| + then ONE save offer covering the resolution (below); |
| + - several + non-TTY → refusal listing the apps (discovery, like |
| + `severalAccountsErr`); |
| + - zero → **deploy offers to create** (next section); every other verb |
| + errors naming the fix: `account <name> has no apps — create one |
| + with: carlos apps create -app <name>`. |
| +4. Old console (endpoint 404) → today's "usage: … -app required" error; |
| + nothing new to fall back on. |
| + |
| +## Zero-arg `carlos deploy` |
| + |
| +With no `-app` and no file arguments, deploy becomes the wizard; each |
| +step is skipped when config or flags already answer it: |
| + |
| +1. Account, app: the seams above. **Zero apps in the account** → offer: |
| + `no apps in <account> yet — create "<dir>" (this directory's name)? |
| + [y/N]` where `<dir>` is the cwd base name, client-validated by |
| + `validAppName` first (an invalid name prompts for one instead). y runs |
| + the existing `createApp` door (rate limiter and server validation |
| + unchanged). |
| +2. Artifact: config `artifact`/`kind`/`target`, else prompt with a |
| + detected SUGGESTION — never a silent guess: if exactly one of |
| + `public/`, `dist/`, `_site/` exists it pre-fills the prompt |
| + (`artifact to deploy [./public]:`); an empty answer takes the |
| + suggestion; no suggestion means no pre-fill. The answer must exist on |
| + disk: a directory means `kind: static`, an executable file means |
| + `kind: binary` (target prompted with `linux-arm64` pre-filled). |
| + Shipping stays byte-identical to `ship` with the same values — |
| + detection only ever fills a PROMPT, the human always confirms. |
| +3. **One save offer at the end of first-run resolution**, before the |
| + ship: `save these as this project's deploy defaults (.carlos/config)? |
| + [y/N]` — writes console + account + app + kind + artifact (+ target), |
| + one write, `saveConfigAccount`'s rules (skipped from `$HOME`, |
| + warn-and-continue on failure). Values that came FROM config are not |
| + re-offered. |
| +4. Version, channel, host, watch: deploy's existing logic untouched |
| + (`gitShortHead`, channel from instances or the one channel, `-host` |
| + for instance-less apps). One addition: where channel resolution |
| + already errors as ambiguous, a TTY gets a chooser over the app's |
| + channels instead of the error (no save — channels are per-run state). |
| + The instance-less `-host` requirement keeps its current error; hosts |
| + move per-deploy and a stale saved host would ship a lie. |
| + |
| +Non-TTY zero-arg deploy with no config: one error naming both remedies |
| +(`-app <a> <artifact> …, or run carlos deploy once at a terminal to save |
| +project defaults`). |
| + |
| +## Testing |
| + |
| +- resolvedApp table: flag bypass, config honored/console-mismatch |
| + ignored, one-app auto (+muted line), several TTY chooser + non-TTY |
| + refusal naming apps, zero-apps error naming apps create, single fetch |
| + shared with account resolution (hit counter stays 1). |
| +- Wizard: artifact suggestion detection (each candidate dir, ambiguity = |
| + no pre-fill, missing path re-prompts), kind/target inference, create |
| + offer (y creates via a stub console, n stops with the named error, |
| + invalid dir name prompts instead), the one combined save write, config |
| + round-trip making the second run zero-question (assert NO prompts by |
| + giving the chooser an empty reader). |
| +- Non-TTY: exact errors; explicit flags never prompt (hit counter + |
| + empty chooser reader). |
| +- `carlosTestEnv` rules for anything shelling the binary. |
| + |
| +## Out of scope |
| + |
| +- Building artifacts (rastrillo/CI's job — the CLI ships bytes it is |
| + handed, it does not make them). |
| +- Saving channel/host (per-run state; a stale saved value ships lies). |
| +- `ship` zero-file form (deploy is the porcelain; ship stays explicit). |
| +- Registry-mode/direct-mode verbs (unchanged throughout). |