diff --git a/.opencode-plugin/plugin.test.ts b/.opencode-plugin/plugin.test.ts
index 37a17db..d17f03a 100644
--- a/.opencode-plugin/plugin.test.ts
+++ b/.opencode-plugin/plugin.test.ts
@@ -89,15 +89,26 @@ test("wrapContext: tier-specific hint inside the tags, content untouched", () =>
assert.equal(wrapContext(body, false), `\n${CRUISE_HINT}\n${body}\n`)
})
-test("allowsOrbitCommand: safe tiers auto-approved", () => {
+test("allowsOrbitCommand: framework-verified tiers auto-approved", () => {
assert.equal(allowsOrbitCommand("orbit status"), true)
assert.equal(allowsOrbitCommand("orbit memo backend"), true)
assert.equal(allowsOrbitCommand("orbit.sh jot backend \"discovery\""), true)
assert.equal(allowsOrbitCommand("/usr/local/bin/orbit context --startup"), true)
})
-test("allowsOrbitCommand: destructive / externally-visible tiers still prompt", () => {
- for (const sub of ["done", "prune", "clone", "config", "new"]) {
+test("allowsOrbitCommand: framework-neutral lifecycle subcommands are not bundled", () => {
+ // done/new are non-destructive and reversible, but orbit cannot judge
+ // *when* running them is right — workflow timing is the user's call, so
+ // the framework takes no position: not bundled into the allow set, not
+ // marked must-confirm. Users who want them prompt-less allowlist them in
+ // their own agent settings.
+ assert.equal(allowsOrbitCommand("orbit done"), false)
+ assert.equal(allowsOrbitCommand("orbit done --pr https://example.com/pr/1"), false)
+ assert.equal(allowsOrbitCommand("orbit new \"fix api\""), false)
+})
+
+test("allowsOrbitCommand: always-prompt tiers still prompt", () => {
+ for (const sub of ["prune", "clone", "config"]) {
assert.equal(allowsOrbitCommand(`orbit ${sub}`), false)
}
})
diff --git a/.opencode-plugin/plugin.ts b/.opencode-plugin/plugin.ts
index 66ac3c3..e0d29d9 100644
--- a/.opencode-plugin/plugin.ts
+++ b/.opencode-plugin/plugin.ts
@@ -83,8 +83,9 @@ const invokesOrbitCli = (cmd: string): boolean =>
return false
})
-// Subcommands in tiers 1–2 (read-only + idempotent workspace-write) from
-// skills/CONSTRAINTS.md. Excluded: done, prune, clone, config, new.
+// Subcommands in the framework-verified auto-approve tiers from
+// skills/CONSTRAINTS.md. Excluded: prune, clone, config (always prompt);
+// done, new (framework-neutral — the user's own allowlist decides).
const SAFE_SUBCOMMANDS = new Set([
"repos", "info", "status", "context", "goal",
"jot", "memo", "add", "switch", "sync",
@@ -93,9 +94,9 @@ const SAFE_SUBCOMMANDS = new Set([
// Auto-approve decision for a single bash command line, mirroring
// hooks/auto-approve.sh (parity contract: docs/spec-hooks.md). Only a bare,
-// un-chained orbit invocation whose subcommand is in the safe tiers is
-// allowed. All matching is token-exact — never substring: `--forceful` or an
-// `--force=x`-style spelling must not trip the destructive guard.
+// un-chained orbit invocation whose subcommand is in the framework-verified
+// tiers is allowed. All matching is token-exact — never substring:
+// `--forceful` or an `--force=x`-style spelling must not trip the destructive guard.
const allowsOrbitCommand = (cmd: string): boolean => {
// Refuse anything with shell chaining/redirection/substitution.
if (/[;&|`$()><\n]/.test(cmd)) return false
@@ -204,9 +205,10 @@ const orbitPlugin = (async ({ client, $ }) => {
// ── PreToolUse/Bash equivalent ──────────────────────────────────────
// Auto-approves single, un-chained orbit invocations whose subcommand is
- // in the two safe tiers. Destructive/externally-visible subcommands
- // (done, prune, clone, config, new) and sync --force still prompt. The
- // decision itself lives in allowsOrbitCommand (pure, test-covered).
+ // in the framework-verified tiers. prune/clone/config always prompt;
+ // done/new are framework-neutral (not bundled — the user's own allowlist
+ // decides); sync --force/--branch prompt. The decision itself lives in
+ // allowsOrbitCommand (pure, test-covered).
"permission.ask": async (input, output) => {
try {
if (input.type !== "bash") return
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1172092..6515c39 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -56,6 +56,8 @@ Hardens the destructive surface: `prune` and `sync --force`/`--branch` become ma
- Brief parser and status steering hardened. ([#23](https://github.com/orbcli/orbit/pull/23))
- Plugin install works on SSH-less machines — `try.sh` defaults to HTTPS. ([#24](https://github.com/orbcli/orbit/pull/24))
- OpenCode auto-approve matches `--force` token-exactly. ([#25](https://github.com/orbcli/orbit/pull/25))
+- Auto-approve tier contract restated by where the judgment lives: framework-verified subcommands (read-only / destructive read / idempotent workspace-write) stay bundled; `done`/`new` are **framework-neutral** — workflow timing is the user's call, so they are neither bundled nor marked must-confirm (users who want them prompt-less allowlist them in their own agent settings; snippets in `skills/CONSTRAINTS.md`); `prune`/`clone`/`config` and `sync --force`/`--branch` keep prompting. No hook behavior change — the tier table, USAGE §17, spec-hooks and SKILL now match what the hooks already did, replacing the stale "done/new are destructive, human-initiated" classification.
+- Bare `orbit goal` doc promises converged to reality: it is a write path (editor on a TTY, stdin set otherwise) and never had a read path — the read is `orbit context goal`. USAGE, SKILL (workflow + examples) and CONSTRAINTS no longer promise the bare read. The execution-location matrix in spec-commands also gained the missing `orbit config` row (runs anywhere in the project).
- Jot queue stores entries in `[jot ""]` subsections — names plain git-config keys can't hold (`my_repo`, `2048`) now jot and pop correctly.
- `orbit clone` rejects a URL whose basename violates the pool-name contract (e.g. `.github`), pointing at `--name`.
- Workspace/repo inference compares physical paths — commands work through symlinked cwds.
diff --git a/USAGE.md b/USAGE.md
index 4834fe6..49bb4dc 100644
--- a/USAGE.md
+++ b/USAGE.md
@@ -270,9 +270,10 @@ orbit status task-01 # Specify when at project root
View/set workspace goal:
```bash
-orbit goal # Read
+orbit context goal # Read
orbit goal "new goal" # Set/update
echo "new goal" | orbit goal # Set from stdin (pipe-friendly)
+orbit goal # Modify interactively (editor on a TTY) — never a read
orbit goal --clear # Delete goal
```
@@ -561,9 +562,9 @@ orbit completion bash > /path/to/bash-completion/completions/orbit
## 17. Auto-approving safe commands
-An orbit session runs read-only and idempotent subcommands (`context` / `repos` / `info` / `status`, plus workspace-writes like `add` / `memo` / `jot`) constantly, so per-command confirmation prompts add up. Those safe tiers can run without a prompt; destructive or externally-visible commands (`done` `prune` `clone` `config` `new`) always keep prompting.
+An orbit session runs read-only and idempotent subcommands (`context` / `repos` / `info` / `status`, plus workspace-writes like `add` / `memo` / `jot`) constantly, so per-command confirmation prompts add up. Those framework-verified tiers can run without a prompt; destructive or externally-visible commands (`prune` `clone` `config`) always keep prompting. Workflow-timing commands (`done` `new`) are deliberately outside the framework's list — orbit takes no position on when they should run; allowlist them in your own agent settings if you want them prompt-less.
-**Plugin users — nothing to do:** both plugins ship a `PreToolUse` hook that auto-approves exactly the safe subcommands and fails safe. **Skill-only / other agents:** add a static allowlist to your agent settings.
+**Plugin users — nothing to do:** all four plugins ship an auto-approve hook that approves exactly the framework-verified subcommands and fails safe. **Skill-only / other agents:** add a static allowlist to your agent settings.
The exact command tiers, the ready-to-paste allowlist snippet, and the rationale for each tier all live in [`skills/CONSTRAINTS.md`](skills/CONSTRAINTS.md#permission-and-auto-execution-policy).
diff --git a/docs/spec-commands.md b/docs/spec-commands.md
index 500395a..90a4aae 100644
--- a/docs/spec-commands.md
+++ b/docs/spec-commands.md
@@ -125,6 +125,7 @@ There is no standalone `orbit init` command. Commands that require `.repos/` (`c
| `orbit doctor` | ✓ | ✓ | ✓ |
| `orbit version` | ✓ | ✓ | ✓ |
| `orbit completion` | ✓ | ✓ | ✓ |
+| `orbit config` | ✓ | ✓ | ✓ |
## Workspace and Repo Inference
diff --git a/docs/spec-hooks.md b/docs/spec-hooks.md
index a069723..94d1eb3 100644
--- a/docs/spec-hooks.md
+++ b/docs/spec-hooks.md
@@ -128,13 +128,15 @@ fallback (the agent runs bare `orbit context` itself).
`hooks/auto-approve.sh` (wired as `PreToolUse`/`Bash` for Claude/Qoder,
`PermissionRequest`/`Bash` for Codex via the exit-code wrapper) auto-approves
only a **single, un-chained** `orbit` invocation whose subcommand is in the
-two safe tiers — the tier contract itself lives in
+auto-approve tiers — the tier contract itself lives in
[skills/CONSTRAINTS.md](../skills/CONSTRAINTS.md#permission-and-auto-execution-policy):
- Refuses anything with shell chaining/redirection/substitution (`;` `&` `|`
`` ` `` `$(` `>` `<`, newline) — the normal confirmation prompt happens.
-- Refuses non-orbit binaries and tier-3 subcommands (`done` `prune` `clone`
- `config` `new`, and `sync --force` / `sync --branch`).
+- Refuses non-orbit binaries, the always-prompt tiers (`prune` `clone`
+ `config`, and `sync --force` / `sync --branch`), and the framework-neutral
+ workflow-timing commands (`done` `new` — not bundled; the user's own
+ allowlist decides).
- Matching is **token-exact** (whitespace split), never substring: `sync
--forceful` must not trip the destructive guard. Each token is normalized
(quotes and backslashes stripped) before comparison, because `'--force'`,
diff --git a/hooks/auto-approve.sh b/hooks/auto-approve.sh
index 3ecce73..de3e155 100755
--- a/hooks/auto-approve.sh
+++ b/hooks/auto-approve.sh
@@ -2,9 +2,10 @@
# Orbit PreToolUse hook — auto-approve safe orbit commands.
#
# Reduces confirmation prompts for the agent's high-frequency orbit calls.
-# Only auto-approves read-only + idempotent-workspace-write subcommands; the
-# destructive / externally-visible ones (done, prune, clone, config, new) still
-# fall through to the normal confirmation flow.
+# Only auto-approves framework-verified subcommands (read-only + idempotent
+# workspace-write). done/new are framework-neutral (workflow timing — the
+# user's own allowlist decides); prune/clone/config always prompt. Anything
+# non-matching falls through to the normal confirmation flow.
#
# Contract: on a match, print a PreToolUse "allow" decision on stdout and exit 0.
# On anything else, print nothing and exit 0 (normal confirmation preserved).
@@ -45,8 +46,10 @@ rest=${trimmed#"$first"}
rest=${rest#"${rest%%[![:space:]]*}"}
subcmd=${rest%%[[:space:]]*}
-# Auto-approve tier: read-only + idempotent workspace writes.
-# Excluded (still prompt): done, prune, clone, config, new.
+# Auto-approve tier: framework-verified only (read-only + idempotent
+# workspace writes). Excluded: prune, clone, config (always prompt —
+# destructive / shared-infrastructure); done, new (framework-neutral —
+# workflow timing, the user's own allowlist decides).
case "$subcmd" in
repos|info|status|context|goal|jot|memo|add|switch|sync|version|doctor|completion) ;;
*) exit 0 ;;
diff --git a/skills/CONSTRAINTS.md b/skills/CONSTRAINTS.md
index 787e30f..fb7d916 100644
--- a/skills/CONSTRAINTS.md
+++ b/skills/CONSTRAINTS.md
@@ -31,28 +31,34 @@ There is one `SKILL.md` (`skills/orbit/SKILL.md`), shared by Claude, Qoder, and
Agents gate every shell command behind a user confirmation prompt. For an orbit session that runs `context` / `repos` / `info` / `status` dozens of times, that turns into confirmation fatigue. This section defines which orbit subcommands are safe to run without a prompt, why, and how to enable that per agent.
-### Command Tiers (by side effect)
+### Command Tiers (by where the judgment lives)
-The tiers below are the contract. Anything not in the first two tiers must keep prompting.
+The tiers below are the contract. **Yes** rows are bundled into the auto-approve hooks; **always-prompt** rows are a framework position; the **neutral** row is a non-position — prompting is the default, and users mirror their own workflow habit via their own static allowlist.
-| Tier | Subcommands | Side effect | Auto-approve? |
-|------|-------------|-------------|---------------|
-| **Read-only** | `repos` `info` `status` `context` `goal` (read) `version` `doctor` `completion` | None, or reads workspace/pool metadata. No repo, no remote, no filesystem mutation outside `.orbit` cache | **Yes** |
-| **Destructive read** | `jot --pop` | Reads *and* deletes the queue in one step — no undo, no archive. Auto-approved because it is the mandatory first half of pop→merge, but the skill must pair it with the memo write in the same turn | **Yes** |
-| **Idempotent workspace-write** | `add` `switch` `sync` (bare / with repo name) `memo` `goal` (write) `jot` (write) | Mutates the local workspace/worktree or the `.orbit` cache. Re-runnable, reversible, never touches a remote | **Yes** |
-| **Destructive / externally-visible** | `done` `prune` `clone` `config` `new` `sync --force` `sync --branch` | Marks lifecycle state, deletes worktrees/branches, writes to `.repos/`, changes project config, or resets/reshapes pool-wide state | **No — always prompt** |
+| Tier | Subcommands | Rationale | Auto-approve? |
+|------|-------------|-----------|---------------|
+| **Framework-verified — read-only** | `repos` `info` `status` `context` `version` `doctor` `completion` | orbit can verify these are safe: no side effect, or reads of workspace/pool metadata. No repo, no remote, no filesystem mutation outside `.orbit` cache | **Yes** |
+| **Framework-verified — destructive read** | `jot --pop` | Reads *and* deletes the queue in one step — no undo, no archive. Safe on two pillars: the queue is not user data (jot+memo is agent-maintained by design), and pop timing is procedural, not user rhythm — the skill mandates pairing pop with the same-turn memo write | **Yes** |
+| **Framework-verified — idempotent workspace-write** | `add` `switch` `sync` (bare / with repo name) `memo` `goal` `jot` (write) | Mutates the local workspace/worktree or the `.orbit` cache. Re-runnable, reversible, never touches a remote | **Yes** |
+| **Framework-neutral — workflow timing** | `done` `new` | Non-destructive and reversible (`done` flips lifecycle state — setting a goal reactivates the workspace; `new` only creates a guarded, `prune`-reclaimable directory). But orbit cannot judge *when* running them is right — that timing is the user's workflow. The framework takes no position: not bundled into the hooks, not marked must-confirm; users who want them prompt-less allowlist them in their own agent settings (snippet below). The behavior rules live at the skill layer (Done Trigger Rules, workspace-creation conventions) | **No — neutral (user's own allowlist may)** |
+| **Project-level / shared-infrastructure change** | `config` `clone` | Changes project-wide behavior; creates a shared-pool entry (network fetch + `.repos/` write) — structural change to state every workspace shares | **No — always prompt** |
+| **Irreversible delete/reset — runtime-gated** | `prune` `sync --force` `sync --branch` | Deletes worktrees/branches, resets or re-points the shared pool. Already machine-refused from inside any workspace at the runtime layer (process-ancestry + cwd guards); the prompt and the skill's "report the need" stance mirror that same policy at their own layers | **No — always prompt** |
-**Why the first tiers are safe to auto-run:** they cannot lose the user's work or leak outside the machine. Reads have no effect; the idempotent writes only build up the workspace the agent is already working in (worktrees, memos, jots) and are trivially reversible with git. **Why the last tier still prompts:** `prune` deletes worktrees and branches, `done` flips lifecycle state, `clone` writes into the shared pool, `config` changes project-wide behavior, and `sync --force`/`--branch` reset or re-point the pool every workspace shares — each is either hard to reverse or visible beyond the current workspace, so the user should stay in the loop.
+**Why the framework-verified tiers are safe to auto-run:** they cannot lose the user's work or leak outside the machine. Pure reads have no effect; `jot --pop` deletes only the agent-maintained queue, not user data; the idempotent writes only build up the workspace the agent is already working in (worktrees, memos, jots, goal) and are trivially reversible.
+
+**Why workflow-timing commands are neutral:** gating `done`/`new` buys no safety (nothing is destroyed), but orbit cannot judge *when* they should run — is the work complete, should a new workspace exist? That judgment is the user's workflow rhythm, and bundling them into the hooks would be the framework taking a position on it. So the framework ships neither an allow nor a must-confirm: the default permission flow prompts, and users mirror their own habit with their own static allowlist. The skill layer carries the behavior rules.
+
+**Why the last two tiers still prompt:** `config`/`clone` structurally change state every workspace shares; `prune`/`sync --force`/`--branch` are irreversible — and for those the runtime is the primary enforcement (root-only, machine-checked), with the hook prompt and skill guidance deliberately consistent with it. The dividing line is **where the judgment lives and whether the effect is reversible** — not "touches pool state": `sync` (ff-only) and `memo` (writes `.repos/..md`) both write pool-side state yet stay auto-approved, because neither destroys anything. **Actor is not a dimension:** human and agent invocations are treated identically at every tier.
**Flag-level exceptions are part of the contract:** a subcommand in a safe tier does not make all of its flags safe. `sync` is auto-approved bare or with a repo name; `sync --force` (pool `reset --hard`) and `sync --branch` (re-points the pool's checked-out branch and `origin/HEAD`) must prompt — and both run **only from the project root**, since they destroy or re-point state the calling workspace does not own. Hook implementations compare *normalized* tokens (quotes and backslashes stripped), because `'--force'`, `--force''` and `\-\-force` all reach the CLI as the same `--force`.
-`new` is excluded on purpose: new workspaces are created at project root, outside the agent's scope (see Anti-Pattern #3), so it should be human-initiated regardless of permissions.
+`new` creates at the project root from any CWD — the calling agent stays scoped to its current workspace and cannot enter the one it just created. From a non-project CWD it implicitly bootstraps a fresh project (`.repos/`) there — non-destructive and reversible; users who allowlist `new` should know they are also allowlisting that bootstrap.
### Two ways to enable it
-**1. Bundled auto-approve hook (zero config, recommended).** All three plugins (Claude, Codex, Qoder) ship the shared `hooks/auto-approve.sh`, wired as a `PreToolUse` / `Bash` (Claude/Qoder) or `PermissionRequest` / `Bash` (Codex) matcher; OpenCode uses the `permission.ask` hook in plugin.ts. The matching semantics (single un-chained invocation, safe tiers only, JSON allow vs exit-code translation, fail-safe to the normal prompt) are specified in [docs/spec-hooks.md](../docs/spec-hooks.md#auto-approve-semantics). Nothing to configure; installing the plugin is enough.
+**1. Bundled auto-approve hook (zero config, recommended).** All three plugins (Claude, Codex, Qoder) ship the shared `hooks/auto-approve.sh`, wired as a `PreToolUse` / `Bash` (Claude/Qoder) or `PermissionRequest` / `Bash` (Codex) matcher; OpenCode uses the `permission.ask` hook in plugin.ts. The matching semantics (single un-chained invocation, framework-verified tiers only, JSON allow vs exit-code translation, fail-safe to the normal prompt) are specified in [docs/spec-hooks.md](../docs/spec-hooks.md#auto-approve-semantics). Nothing to configure; installing the plugin is enough.
-**2. Static allowlist in agent settings (opt-in, for users who prefer explicit config or run skill-only without the plugin hook).** Plugins cannot declare a permission allowlist — only the user's own settings can — so this path is manual. Mirror the two safe tiers:
+**2. Static allowlist in agent settings (opt-in, for users who prefer explicit config or run skill-only without the plugin hook).** Plugins cannot declare a permission allowlist — only the user's own settings can — so this path is manual. Mirror the auto-approve tiers:
*Claude Code* — `.claude/settings.json` (project) or `~/.claude/settings.json` (global):
@@ -118,6 +124,8 @@ The tiers below are the contract. Anything not in the first two tiers must keep
}
```
+**Workflow-timing commands (optional, your call).** `done` and `new` are deliberately absent from every snippet above — the framework takes no position on when they should run. If your workflow wants them prompt-less, allowlist them yourself — Claude: `"Bash(orbit done:*)"`, `"Bash(orbit new:*)"`; opencode: `"orbit done": "allow"`, `"orbit done *": "allow"`, `"orbit new": "allow"`, `"orbit new *": "allow"`. Note that allowlisting `new` also allowlists its implicit project bootstrap in a non-project CWD (see the tier notes above).
+
### Maintainer contract
If you change the tier of any subcommand, or add/remove a subcommand, update **all** in the same change so they never drift:
@@ -214,8 +222,8 @@ The skill must not blur the two classes by telling the agent to "act on all `orb
Every skill must guide the agent to discover before acting:
-1. `orbit goal` — understand the workspace objective
-2. `orbit repos` — screen: view available repos (name + url + brief), identify potentially relevant candidates
+1. `orbit context goal` — understand the workspace objective (skip it when a held block already carries the goal; bare `orbit goal` never reads: a TTY opens an editor, non-TTY sets from stdin)
+2. `orbit repos` — screen: view available repos (name + url + brief), identify potentially relevant candidates. Skip when the startup block's pool roster already answers it (cold start); re-run to refresh when the pool may have changed or a needed field (URL) is beyond the brief
3. `orbit info ` — assess: read the memo card for candidate repos (roles: when/why to add; entry points: where to start), also detects upstream freshness and memo staleness
- **README fallback = no memo.** When `orbit info` falls back to the README, no memo exists. The README is the repo's unprocessed façade, not decision context — it must not be treated as "enough" to skip `orbit add` or the step 7 exploration
- Mid-work self-check: bare `orbit context` shows goal + per-repo status (jots / behind / memo state), not memos — it does not replace steps 1–3
@@ -224,7 +232,7 @@ Every skill must guide the agent to discover before acting:
5. **Cold-start sync** — if step 3 showed remoteAhead > 0, run `orbit sync ` now (before add). Agent hasn't started relying on the code yet, so sync cost is lowest. This ensures `orbit add` creates the worktree from the latest pool HEAD
6. `orbit add ` — bring into workspace only repos confirmed in step 4 as needing full source. Worktree starts from pool's current HEAD (latest after sync). `-s` suppresses the memo echo only when context is already held (from step 3 `orbit info`, the startup block, or a prior session). **Hard rule:** if step 3 showed **no memo** (README fallback), `-s` is forbidden — no memo means zero inherited context, so add without `-s` and explore in step 7
- **No/low-memo nudge at add:** when the added repo's memo is missing or thin, `orbit add` prints a one-shot stderr naming the scope to explore — explore and write the card before done. The skill must guide the agent to act on it in step 7 (the same state resurfaces via per-repo status in bare `orbit context` and at `orbit done`)
-7. **Memo check** — first, pop any residual jot entries from a prior session: `orbit jot --pop`. Then, based on staleness info from step 3 (recalculated after sync):
+7. **Memo check** — if the startup block (hook-injected or self-run `orbit context --startup`) reported pending jots for this repo, pop them first (`orbit jot --pop`) and merge them into the same write. The startup moment is the discriminator: the session has not worked yet, so those entries are a prior session's by construction — their capturing context is gone, and this memo write is their only survival path. Same-session jots aggregate at wrap-up or the overflow warning (capture/aggregate split, [docs/spec-knowledge.md](../docs/spec-knowledge.md)). Then, based on staleness info from step 3 (recalculated after sync):
- "memo is N commits behind HEAD" → memo is stale. Read existing memo as a base, check whether recent changes involve structural changes, only incrementally append or correct — don't rewrite. Merge any popped jot entries into the same write. If no structural changes and no jot entries, run `orbit memo --refresh` to reset the staleness counter (prevents re-evaluation in future sessions)
- No memo or thin card (doesn't answer both card questions) → first check what you already know from prior code work this session (grep/edit/commit/trace all count as exploring). If that context is sufficient, go straight to write. Only if context is still insufficient do you trigger explore, and only within the scope orbit names for you (the add-time stderr carries it). Use `orbit memo --scaffold` for the template, then write. Include any popped jot entries
- **This step builds understanding *now*** (read code / draft the memo skeleton) — it cannot be deferred to wrap-up. Step 10 only aggregates incremental discoveries on top of it; it is not where first-time exploration happens
@@ -252,7 +260,7 @@ Human: orbit new "fix API" --exec "claude"
↓
orbit: implicit init (if needed) → mkdir task-01 → write .orbit → exec claude in task-01/
↓
-Agent launches, orbit goal → learns the workspace objective
+Agent launches, orbit context goal → learns the workspace objective
↓
Agent: orbit repos → view available repos in pool → determine which are needed
↓
@@ -299,7 +307,7 @@ Brand new project, `.repos/` just initialized with no repos. Agent uses the skil
| `orbit sync [repo...] [--force] [--branch ]` | Sync pool repo to upstream latest | Needs to operate on repos inside .repos/ (ff/reset/switch branch) — `--force` / `--branch` are **root-level only** |
| `orbit done [--pr]` | Mark task complete | Workspace-level semantic, not a git concept |
| `orbit status` | View workspace status | Aggregates multi-repo branch/ahead/behind |
-| `orbit goal` | Read/set workspace objective | Reads/writes workspace/.orbit goal field |
+| `orbit goal` | Set/clear workspace objective (read via `orbit context goal`) | Writes workspace/.orbit goal field |
| `orbit context [] [--startup|--prime|--reignite] [--json]` | Model-facing context blocks: bare = cruise block (durables + conditional per-repo status: jots / behind / memo state); `--startup` = session-start block (cold start → pool roster; populated → memos + staleness + per-repo status); key = single value (workspace/path/goal/state); `--prime`/`--reignite` are human/debug routing targets | Aggregates workspace durables + per-repo status, needs to read .repos/ |
| `orbit prune` | Reclaim completed workspaces | Cross-workspace cleanup of worktrees + branches — **root-level role, not the skill's** (see below) |
| `orbit config [ []]` | Read/set project configuration | Needs to read/write .repos/.orbit |
@@ -435,7 +443,7 @@ Skill does not need to explain internal mechanics (prefix stripping, push.defaul
1. **Directly accessing .repos/** — breaks Layer 2 boundary (including the config channel: `git config` from a worktree reaches the pool's shared config — see principle 4)
2. **Assuming the agent understands git config internals** — use behavioral descriptions instead of config details
-3. **Agent running orbit new inside a workspace** — new workspaces are created at project root, but the agent's scope is the current workspace and it cannot switch to the new workspace. `orbit new` should be initiated by humans
+3. **Running `orbit new` expecting to land in the new workspace** — `new` creates at the project root from any CWD and does not move the caller: a workspace is entered by launching a session in it, not by the session that created it
4. **Forcing scoped mode** — raw mode is a perfectly valid choice
5. **Scanning the entire codebase to write descriptions without having worked in the repo** — memo should arise naturally from actual work, not as a standalone summarization task
6. **Writing memo for repos you haven't touched** — memo writeback is on-demand; only manage repos you added and worked in
diff --git a/skills/orbit/SKILL.md b/skills/orbit/SKILL.md
index c9cea42..c804d79 100644
--- a/skills/orbit/SKILL.md
+++ b/skills/orbit/SKILL.md
@@ -12,7 +12,7 @@ Run `orbit context --startup` to detect whether you are inside a workspace — o
- **Succeeds (exit 0):** you are in a workspace. Output is the session-start block — read it and apply orbit conventions for the entire session.
- **Fails:** you are not in a workspace. Don't apply orbit conventions.
-Bare `orbit context` is the **cruise** block (cheap durables + conditional per-repo status: jots / behind / memo state) — the in-session counterpart of the startup block, used to self-recover after a compaction or when working memory may have been lost. It does not dump memos: pull a repo's memo on demand with `orbit info `. Single-key queries (`orbit context workspace`, `orbit context goal`, `orbit context state`) serve quick lookups.
+Bare `orbit context` is the **cruise** block (cheap durables + conditional per-repo status) — the in-session counterpart of the startup block. It does not dump memos: pull one on demand with `orbit info `. Single-key queries (`orbit context workspace|goal|state`) serve quick lookups.
Detection is workspace-level only. Project root path is never exposed (prevents operating on `.repos/` infrastructure). Repo-level detection uses git natively.
@@ -20,14 +20,12 @@ Detection is workspace-level only. Project root path is never exposed (prevents
Orbit has no runtime to *enforce* procedure — **its `orbit:`-prefixed stderr lines are the steering channel, split into two classes you must distinguish**:
-- **Actionable** — lines that name a next workflow (`pop + merge`, `explore + write`, `curate once`, `card budget ... curate`, `fetch`/`switch -c`). These are **procedure requirements, not suggestions**. The current task feeling higher-priority does not license skipping: every actionable item must be closed **before `orbit done` marks the workspace complete** — the close-out procedure is what keeps the next session's context intact. Most are immediate (add-time explore nudge, done-gate debt, over-budget memo — treat them as "before the next major step"); jot overflow is the one deferrable case — you may keep coding and aggregate at wrap-up (step 10) instead of dropping everything now. Skipping an actionable item is exactly the failure mode this channel exists to prevent.
+- **Actionable** — lines that name a next workflow (`pop + merge`, `explore + write`, `curate once`, `card budget ... curate`, `fetch`/`switch -c`). These are **procedure requirements, not suggestions**. The current task feeling higher-priority does not license skipping: every actionable item must be closed **before `orbit done` marks the workspace complete** — the close-out procedure is what keeps the next session's context intact. Most are immediate (add-time explore nudge, done-gate debt, over-budget memo — treat them as "before the next major step"); jot overflow is the one deferrable case — you may keep coding and aggregate at wrap-up (step 10) instead of dropping everything now.
- **Informational** — lines that just report state (`N jots (building)`, `no memo, showing README`, `no memo; showing first N of M README lines`). Read and note; no named workflow to execute.
-stdout is machine-readable data; the steering lives on stderr. The actionable class is non-negotiable; the informational class is awareness only.
-
## Startup detection
-Workspace context can reach you two ways: a **SessionStart hook** injects it automatically (wrapped in `` tags), or you run `orbit context --startup` yourself when the user asks to start working. The hook injects in two tiers with decreasing token budgets: **startup** (`orbit context --startup` — a fresh workspace with no repos gets durables + the **pool roster**: repos available to `orbit add`, one-line brief each, so you can orient and choose; a workspace that already holds repos gets each repo's memo card + staleness + conditional per-repo status) and **resume/compact** (bare `orbit context` — cheap durables + conditional per-repo status only). The `` tag is how you distinguish hook-injected context from your own command output. Treat any injected block identically — the moment you observe workspace context, check the state **before your first reply of the session**, even if the user's opening message is unrelated to orbit.
+Workspace context can reach you two ways: a **SessionStart hook** injects it automatically (wrapped in `` tags), or you run `orbit context --startup` yourself when the user asks to start working. The hook injects in two tiers with decreasing token budgets: **startup** (`orbit context --startup` — fresh workspace: durables + the **pool roster** (repos available to `orbit add`, one-line brief each); populated workspace: each repo's memo card + staleness + conditional per-repo status) and **resume/compact** (bare `orbit context` — cheap durables + conditional per-repo status only). The tag distinguishes hook-injected context from your own command output. Treat any injected block identically — the moment you observe it, check the state **before your first reply**, even if the opening request is unrelated to orbit.
**An injected block IS your completed preflight — do not re-fetch it.** When an `` block is present, the hook has *already run* the preflight for you. Read what's there and go straight to the workflow decision (done-check, then goal) — do NOT run `orbit context --startup`, `orbit context`, or `orbit repos` to "load" context you already hold. You fetch context yourself only in the no-injection path below.
@@ -38,8 +36,8 @@ The startup roster already carries every pool repo's name + one-line brief, so a
`state: done` is the workspace's **lifecycle state** — it means the workspace was already marked complete via `orbit done`. It is NOT a loading/progress indicator; do not misread it as "context finished loading".
When no `` block was injected and the user asks to start working, run `orbit context --startup` — success means you are in a workspace (read the block), failure means you are not:
-1. Read the startup block. It is lean by design: a cold start lists the pool by name + brief and does **not** dump full memos — pull a repo's memo on demand with `orbit info ` once you engage it.
-2. **If the workspace state is `done`, remind the user first** — before doing anything else (this applies to your very first reply of the session, whether the state came from the injected hook block or from your own `orbit context --startup`), tell them the workspace is already marked done and ask how they want to proceed (reopen work, have it reclaimed from the project root, or start elsewhere). Do not silently continue the workflow on a done workspace.
+1. Read the startup block: a cold start lists the pool by name + brief without dumping memos — pull a repo's memo on demand with `orbit info ` once you engage it.
+2. **If the workspace state is `done`, remind the user first** — before anything else (applies to your very first reply, however the state reached you), tell them the workspace is already marked done and ask how they want to proceed (reopen work, have it reclaimed from the project root, or start elsewhere). Do not silently continue the workflow on a done workspace.
3. If the goal is non-empty, proceed with the workflow based on the goal. If the block listed pending jots or `memo thin` repos, fold them into memo per Workflow steps 7 and 10 as you get started.
4. If the goal is empty, ask the user what they want to accomplish.
@@ -67,25 +65,22 @@ If the `orbit` command is not found, tell the user to install the runtime with `
These steps describe the work itself, independent of who performs it. Run them yourself, or delegate the exploration path (screen → assess → add → work + jot) to workers — see "Delegating to sub-agents" below. Lifecycle and aggregation (`memo`, `done`) always stay with you.
-1. **Read goal first.** Run `orbit goal` to understand what this workspace is for.
-2. **Screen.** Run `orbit repos` to see available repos (name + url + brief). Identify candidates relevant to your goal.
+1. **Read goal first.** It's already in any block you hold (startup or cruise) — don't re-fetch. Run `orbit context goal` when you hold no block or the goal may have changed.
+2. **Screen.** Cold-start startup blocks already carry the pool roster (name + brief) — screen from it. Otherwise (populated workspace, the pool may have changed, you need the URL) run `orbit repos`. Identify candidates relevant to your goal.
3. **Assess.** Run `orbit info ` for each candidate — the memo card: the repo's roles (when/why to add) and entry points (where to start). Also detects upstream freshness and memo staleness.
- **README fallback = no memo.** When `orbit info` falls back to showing the README, it means **no memo exists**. The README is the repo's unprocessed façade, not decision context — never treat it as "enough" to justify skipping `orbit add` or the step 7 exploration.
- **Mid-work self-check:** bare `orbit context` shows goal + per-repo status (jots / behind / memo state), not memos — it does not replace steps 1–3.
4. **Decide.** Based on info: memo card gives enough context (the repo's roles and entry points answer your need) → don't add. Need to grep source, trace call chains, or modify code → `orbit add`. Repo not in pool → `orbit clone` then add. Only need docs → web search. A README fallback (step 3) is **not** "enough context" — it never justifies "don't add".
- **Task type doesn't exempt you from exploring.** Release, ops, and pure-research tasks explore first too — the default mental model is not "editing code". If you judge full source truly isn't needed, state that reason explicitly here rather than skipping exploration by default.
-5. **Cold-start sync.** If step 3 showed remoteAhead > 0, run `orbit sync ` now — before add. Agent hasn't started relying on the code yet, so sync cost is lowest. This ensures `orbit add` creates the worktree from the latest pool HEAD. **Scope:** `sync` fast-forwards the *pool* repo (`.repos/`) only — it does **not** move a worktree you've already checked out. If a worktree tracks the branch you synced, it's now behind the pool; bring it up to date with native git if you want. Don't re-run `orbit sync` expecting the worktree to advance.
+5. **Cold-start sync.** If step 3 showed remoteAhead > 0, run `orbit sync ` now — before add, while nothing depends on the old code yet — so the worktree starts from the latest pool HEAD. `sync` moves the pool repo only, never an existing worktree (catch one up with native git if wanted; scope note also in the command map).
6. **Add repos.** Run `orbit add ` only for repos that need full source (from inside a workspace directory). Worktree starts from pool's current HEAD (latest after sync). Pass `-s` when you already hold enough context to justify the add — from `orbit info` in step 3, the memo surfaced in the startup block, or a prior session: `orbit add -s`. Plain `orbit add` (no `-s`) echoes the memo as a safety net — reach for it only when adding without that context; seeing the memo dump means you added blind and should confirm you actually need the full source. **Hard rule:** if step 3's `orbit info` showed **no memo** (README fallback), `-s` is forbidden — no memo means zero inherited context, so add without `-s` and explore in step 7.
- **No/low-memo nudge at add.** When you add a repo whose memo is missing or thin, `orbit add` prints a one-shot stderr naming the scope to explore — explore and write the card before done. It is an *instruction to you*: act on it in step 7. The same state resurfaces via per-repo status in bare `orbit context` and at `orbit done`.
-7. **Memo check.** First, pop any residual jot entries from a prior session: `orbit jot --pop`. Then, based on staleness info from step 3 (recalculated after sync):
+7. **Memo check.** If your startup block (hook-injected or your own `orbit context --startup`) reported pending jots for this repo, pop them first (`orbit jot --pop`) and fold them into a memo write this turn — the write below if it happens, a fold-only write otherwise. Then, based on staleness info from step 3 (recalculated after sync):
- **"memo is N commits behind HEAD"** → memo is stale. Read the existing memo first, then skim recent changes. If structure changed (new entry points, renamed modules, changed deps), incrementally update — add or correct, don't rewrite. Merge any popped jot entries into the same write. If no structural changes and no jot entries, run `orbit memo --refresh` to reset the staleness counter (prevents re-evaluation in future sessions).
- **No memo** or **thin card** (doesn't answer both card questions) → write one now. **First check what you already know from prior code work in this session** — grep, edit, commit, and trace all count as exploring. If that accumulated context is enough to name roles and entry points, go straight to write; **do not trigger a formal explore step**. Only if context is still insufficient do you trigger explore, and only within the scope orbit names for you (in the add-time stderr — don't survey the whole tree). Use `orbit memo --scaffold` for the template, then write the roles + MVP/VIP entry points. Include any popped jot entries.
- **This step builds understanding *now* — it cannot be deferred to wrap-up.** Reading the code and drafting the memo skeleton happen here, before any target action. Step 10 only aggregates incremental discoveries on top of the understanding you build here; it is not where exploration first happens.
- **Discovery gate — applies to every added repo.** Do not begin *any* target action — edit, branch, push, release, or tag — until steps 3–7 are complete for that repo. Jumping from `add` straight to a target action is the exact failure this gate prevents.
-8. **Branch.** Before making changes, create a feature branch in each repo you'll modify:
- - **Scoped mode** (default, most cases): `orbit switch -c ` — wires upstream tracking automatically, avoids the "already used by worktree" trap, isolates branch names across workspaces, and is cleaned up by `orbit prune`.
- - **Raw mode** (advanced, not recommended for most work): `git checkout -b ` — use only when you explicitly want pure git with no orbit branch management.
- - **Raw-mode branch already created?** Convert it to scoped mode with `orbit switch -c ` — it creates `ws//` from your current HEAD (preserving all local commits and staged changes), wires upstream tracking, and you can then delete the raw branch with `git branch -d `.
+8. **Branch.** Before making changes, create a feature branch in each repo you'll modify — `orbit switch -c ` (scoped mode, the default; see **Branch modes** below for the raw alternative and conversion).
9. **Work.** Use standard git commands inside worktrees. **jot feeds the card, so jot only what the card needs and doesn't have yet** — a role the card doesn't list, or an MVP/VIP entry point the card misses or gets wrong. Run `orbit jot "one-liner"` from within the repo directory — lightweight, no need to read or merge memo. If jot warns the buffer is filling (`building` at half of the buffer orbit reports; `overflow` past it), consider aggregating now (see step 10). **Jot on others' behalf (fallback):** if a finding about a repo you added or worked in comes from a source you didn't brief on orbit (a custom agent, or non-agent external research), run `orbit jot "discovery"` yourself when you receive it. The primary path is to brief that agent so it jots on its own (see Delegating).
- **What to jot**: only information the card is missing and needs — a **role** (why a workspace would pull this repo in) or an **MVP/VIP entry point** (the file/dir to start from, and why), about the repo's main branch, that isn't already captured. Reviewing a diff or refactoring counts as reading code: a role or entry point you learn while traversing the base (main-branch) structure is jottable even when the change itself is not. **Not**: deep code structure (module internals, conventions, pitfalls, call graphs) — not the card's business; not feature-branch changes; not debug notes; not anything the card already says.
- **Jot triggers (event-driven — don't wait for wrap-up).** Jot the moment you realize (a) this repo also serves a role the card doesn't list, or (b) the real entry point for a task differs from — or is absent in — what the card names. If the discovery isn't a role or an entry point the card needs, it isn't a jot.
@@ -95,7 +90,7 @@ These steps describe the work itself, independent of who performs it. Run them y
- **Reflect first**: before popping, review what you learned this session about repos you added or worked in. If any structural insight never made it into a jot, jot it now, then continue. Keep scope to repos you added or worked in; do not sweep repos you only read via `orbit info`.
- **Jot aggregation**: for each repo with jot entries, run `orbit jot --pop` to consume entries, then `orbit info ` to read current card, merge entries in — staying within the card budget orbit reports (curate, don't append) and following merge-first rules — write back via `cat <<'EOF' | orbit memo `. Before `orbit done`, run bare `orbit context` and confirm no repo you developed is left with `memo thin` and no capture — `orbit done` warns per repo as the final backstop.
- **Writeback is terminal.** Merging into memo is the *last* action for that repo — capture everything *before* the pop→merge, including insights that surface while you draft your report to the user. A jot made after writeback is stranded: this session's aggregation is already closed, so it sits orphaned in the queue until a future session. If a genuine discovery surfaces post-writeback, re-run pop→merge to fold it in — don't leave it queued.
- - **Memo quality gate**: for each repo you actually touched this session (grep, edit, commit, and trace all count as exploring — not just a dedicated explore pass), run `orbit info ` and judge whether the card answers both questions — its roles, and the MVP/VIP entry points — by substance, not line count (an accurate thin card is fine; orbit's card budget is an append-drift guard, not a quota to fill). If it doesn't and you understand the repo well enough, upgrade it now (cold-start write per "Upgrade thin cards"); if you didn't explore deeply enough to write accurately, do NOT pad — report instead and let the user decide. Don't `orbit done` leaving a repo you actively worked in with a known-thin card unacknowledged.
+ - **Memo quality gate**: for each repo you actually worked this session (the step-7 bar: grep/edit/commit/trace all count), run `orbit info ` and judge whether the card answers both questions — its roles, and the MVP/VIP entry points — by substance, not line count (an accurate thin card is fine; orbit's card budget is an append-drift guard, not a quota to fill). If it doesn't and you understand the repo well enough, upgrade it now (cold-start write per "Upgrade thin cards"); if you didn't explore deeply enough to write accurately, do NOT pad — report instead and let the user decide. Don't `orbit done` leaving a repo you actively worked in with a known-thin card unacknowledged.
- **PR impact assessment**: memo describes the pool repo's stable (main) branch, not your feature branch — do NOT update memo based on feature branch state. If your PR introduces changes the card would need to reflect (a new entry point, or a new role for the repo), include a post-merge memo refresh suggestion: "After merge: `orbit sync && orbit info ` — update the card if roles or entry points changed."
11. **Mark done.** When the workspace has a goal and you've completed the work (PR created, code committed, tests passing), run the wrap-up sequence from step 10, then `orbit done --pr `. When the workspace has no goal, only run `orbit done` when the user explicitly asks.
- **The done gate is actionable, not advisory.** `orbit done` does not block — but if its stderr reports any per-repo debt (residual jots / thin memo / over-budget card), **do not leave it there**: go back and execute the named workflow (pop + merge / explore + write / curate once) for each repo, then `orbit done` again. The CLI fires the warning once and does not loop — it's on you to close it.
@@ -114,11 +109,11 @@ Orbit exists to support cross-repo work, so a sub-agent should follow a thread a
**Capture vs aggregate.** Workers `jot` during work (append-only, concurrency-safe); you fold jots into `memo` at wrap-up (step 10). This is how knowledge discovered inside a worker's context survives after that context is gone.
-**Brief custom agents too — don't default to proxying.** No sub-agent loads this skill; jot reaches any of them only through the briefing you paste in. So when you delegate repo-touching work to a domain/custom agent (research, design, ops), add the jot lines from the template above to its prompt as well — then it jots its own structural findings about repos it added or worked in, same as a built-in worker. Proxy is only the fallback: for findings from an agent you didn't brief, or from non-agent external research, jot them yourself (step 9) and sweep for misses in the wrap-up reflection (step 10).
+**Brief custom agents too — don't default to proxying.** Jot reaches a custom agent only through the briefing you paste in — add the jot lines from the template below to any domain agent's prompt (research, design, ops), and it jots its own findings about repos it added or worked in, same as a built-in worker. Proxy is only the fallback: for findings from an agent you didn't brief, or from non-agent external research, jot them yourself (step 9) and sweep for misses in the wrap-up reflection (step 10).
**Concurrency is your job.** Serial delegation (one worker at a time) has no race — a lone worker can do almost anything you can. When you fan out **parallel** workers, partition by repo so their mutations stay disjoint; converge writes (`memo`, `done`) serially yourself afterward.
-**Brief every worker.** Workers can't see this skill, so paste a filled-in briefing into each orbit sub-agent prompt:
+**Brief every worker** — paste a filled-in briefing into each orbit sub-agent prompt:
```
You are working in an Orbit workspace. The `orbit` CLI is on your PATH.
@@ -162,7 +157,7 @@ orbit done [--pr ...] [--json]
# Status (from workspace or root)
orbit status [workspace] [--json]
-orbit goal ["text" / --clear]
+orbit goal ["text" / --clear] # write-only — read via orbit context goal
orbit context [] [--startup|--prime|--reignite] [--json] # bare = cruise block (durables + per-repo status); --startup = session-start block; key: workspace, path, goal, state
# Configuration & diagnostics
@@ -173,7 +168,7 @@ orbit doctor
## Branch modes
- **Scoped (default).** `orbit switch -c ` creates `ws//` tracking `origin/` — upstream wired up front, no cross-workspace collision, cleaned up by `orbit prune`. Bare `git push` works (`push.default=upstream` routes the prefixed local name to the clean remote name). `orbit switch ` without `-c` switches to an existing remote branch.
-- **Raw (advanced).** Choose only when you explicitly want pure git — for everything else scoped wins: upstream wired up front, no checkout trap, names isolated, prune-cleaned. Raw is `git checkout -b `: no prefix, no upstream wiring, no prune cleanup. A bare `git push` needs an upstream — wire once with `git push -u origin `, or stay explicit with `git push origin ` (no config needed).
+- **Raw (advanced).** Choose only when you explicitly want pure git — for everything else scoped wins: upstream wired up front, no checkout trap, names isolated, prune-cleaned. Raw is `git checkout -b `: no prefix, no upstream wiring, no prune cleanup. A bare `git push` needs an upstream — wire once with `git push -u origin `, or stay explicit with `git push origin ` (no config needed). Convert to scoped anytime with `orbit switch -c ` — lossless (keeps local commits and staged changes), then `git branch -d ` the raw one.
- **The "already used by worktree" trap.** The pool holds each repo's base branch, so git refuses to check out a branch another worktree holds — don't fight it, run `orbit switch `. To branch off the latest baseline: `orbit switch master` → `git pull --ff-only` → `git checkout -b feature/x`.
**Tracking display.** The pool's fetch config carries the full wildcard map: once a branch has upstream config, `git status` / `@{upstream}` resolve and a push materializes the tracking ref on the spot — no registration, no touchpoint wait.
@@ -226,7 +221,7 @@ Rules:
1. **Never access `.repos/` directly.** All repos operations go through orbit commands.
2. **Never hand-edit orbit-managed git config.** `git config` inside a worktree writes the pool repo's *shared* config — a worktree is not a config boundary. The keys orbit manages — `remote.origin.fetch`, `fetch.prune`, `push.default`, and scoped branches' `branch.*` sections — are off-limits: edits to converged keys are reverted at the next touchpoint, and edits to `branch.*` silently corrupt tracking and prune's bookkeeping.
-3. **Don't run `orbit new` if already in a workspace.** It creates at project root level.
+3. **`orbit new` creates at the project root, from any CWD — it doesn't move you there.** A workspace is entered by launching a session in it, not by the session that created it.
4. **Reclaiming workspaces is not yours.** `orbit prune` deletes worktrees, branches and workspace directories across the whole project — it belongs to whoever operates the project root, not to a session working inside a workspace. If cleanup comes up, report the need and stop there; don't run it, and don't relocate to make it runnable. The same rule covers `sync --force` / `sync --branch`: they destroy or re-point the shared pool, which your workspace does not own.
5. **Default scope is the current workspace** inferred from CWD. Don't target other workspaces unless explicitly asked.
6. **Understand before your first target action on an added repo.** A *knowledge* gate, not an approval gate: before your first edit / branch / push / tag / release on a repo, complete Workflow steps 3–7 (info → memo → explore). `add` only creates a worktree — it is not understanding and never clears the gate. Normal work clears it at edit time; the trap is jumping straight from `add` to a high-impact action ("just tag a release", publish) on a repo you never read. (Gates on *understanding*, not permission — orbit takes no stance on *whether* you push or commit.)
@@ -234,11 +229,11 @@ Rules:
## Safe to run freely
These orbit subcommands are read-only or idempotent workspace-writes — run them without asking:
-- **Read-only:** `repos` `info` `status` `context` `goal` (read) `version` `doctor` `completion`
+- **Read-only:** `repos` `info` `status` `context` `version` `doctor` `completion`
- **Destructive read:** `jot --pop` — it *consumes* the queue (read + delete, no undo). Safe to run without asking, but only as the first half of pop→merge: never pop until you're ready to write the memo in the same turn.
-- **Idempotent workspace-write:** `add` `switch` `sync` (bare or with a repo name) `memo` `jot` `goal` (write)
+- **Idempotent workspace-write:** `add` `switch` `sync` (bare or with a repo name) `memo` `jot` `goal`
-`done` `clone` `config` `new` are destructive or reach outside the workspace — confirm before running these. `sync --force` and `sync --branch` are not yours either: both destroy or re-point shared pool state and run only from the project root, so report the need rather than trying them (the bare `sync` being safe does not extend to these flags). `prune` is not on your list at all — see Safety rules.
+`done` and `new` are workflow-timing commands — non-destructive and reversible; when to run them is governed by the workflow (step 11; Safety rule 3), and whether they prompt is the user's own permission setup — orbit takes no position at the permission layer. `clone` and `config` change project-level / shared state — confirm before running these. `sync --force` and `sync --branch` are not yours either: both destroy or re-point shared pool state and run only from the project root, so report the need rather than trying them (the bare `sync` being safe does not extend to these flags). `prune` is not on your list at all — see Safety rules.
## Communication
@@ -258,10 +253,11 @@ These orbit subcommands are read-only or idempotent workspace-writes — run the
**Start working in an existing workspace:**
```bash
-orbit goal # understand the task
-orbit repos # see what's available
+# startup block (hook-injected or your own `orbit context --startup`) already holds goal + pool roster
+orbit info backend # assess: memo card (roles + entry points)
orbit add backend # bring repo into workspace
cd backend/
+orbit switch -c fix-api # branch before changes (scoped mode)
# ... work with git normally ...
orbit done --pr https://github.com/org/backend/pull/42
```
diff --git a/tests/24_auto_approve.bats b/tests/24_auto_approve.bats
index ab64d86..66c17c1 100644
--- a/tests/24_auto_approve.bats
+++ b/tests/24_auto_approve.bats
@@ -22,7 +22,7 @@ hook_decide() {
printf '{"tool_name":"Bash","tool_input":{"command":"%s"}}' "$1" | bash "$HOOK"
}
-@test "auto-approve: safe subcommands are allowed" {
+@test "auto-approve: framework-verified subcommands are allowed" {
run hook_decide "orbit status"
[ "$status" -eq 0 ]
assert_contains "$output" '"permissionDecision":"allow"'
@@ -31,13 +31,24 @@ hook_decide() {
assert_contains "$output" '"permissionDecision":"allow"'
}
-@test "auto-approve: destructive tier prompts" {
- for sub in done prune clone config new; do
+@test "auto-approve: always-prompt tiers prompt" {
+ for sub in prune clone config; do
run hook_decide "orbit $sub"
[ -z "$output" ]
done
}
+@test "auto-approve: framework-neutral lifecycle subcommands are not bundled" {
+ # done/new are non-destructive and reversible, but orbit cannot judge
+ # *when* running them is right — workflow timing is the user's call, so
+ # the framework takes no position: not bundled, not must-confirm. Users
+ # who want them prompt-less allowlist them in their own agent settings.
+ run hook_decide "orbit done --pr https://example.com/pr/1"
+ [ -z "$output" ]
+ run hook_decide "orbit new \"fix api\""
+ [ -z "$output" ]
+}
+
@test "auto-approve: sync --force and --branch prompt" {
run hook_decide "orbit sync --force"
[ -z "$output" ]