diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index 2afc410..ade67bc 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -10,7 +10,7 @@ "source": "local", "path": "./" }, - "description": "Multi-repo Git workspaces for any AI coding agent — one agent across many repos." + "description": "One agent across many repos — real git worktrees, no index" } ] } diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 25f3ad9..c22b6f7 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -7,7 +7,7 @@ { "name": "claude-orbit", "source": "./", - "description": "Multi-repo Git workspaces for any AI coding agent — one agent across many repos." + "description": "One agent across many repos — real git worktrees, no index" } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index bd468ee..973be63 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "claude-orbit", - "version": "0.1.0", - "description": "Multi-repo Git workspaces for any AI coding agent — one agent across many repos.", + "version": "0.2.0", + "description": "One agent across many repos — real git worktrees, no index", "author": { "name": "orbcli contributors" }, diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 5ffa713..d90a6c3 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "codex-orbit", - "version": "0.1.0", - "description": "Multi-repo Git workspaces for any AI coding agent — one agent across many repos.", + "version": "0.2.0", + "description": "One agent across many repos — real git worktrees, no index", "author": { "name": "orbcli contributors" }, @@ -19,7 +19,7 @@ "hooks": "./hooks/codex/hooks.json", "interface": { "displayName": "Orbit", - "shortDescription": "Multi-repo Git workspaces for AI coding agents", + "shortDescription": "One agent across many repos — real git worktrees, no index", "longDescription": "Orbit is a Git-native multi-repo workspace manager that lets AI coding agents work across many repositories in a single session. It provides workspace management, repo discovery, memo cards for guiding repo pull decisions, and jot-based knowledge capture for incremental discovery.", "developerName": "orbcli contributors", "category": "Developer Tools", diff --git a/.opencode-plugin/package.json b/.opencode-plugin/package.json index 6bf2034..aa1ac77 100644 --- a/.opencode-plugin/package.json +++ b/.opencode-plugin/package.json @@ -1,7 +1,7 @@ { "name": "opencode-orbit", - "version": "0.1.0", - "description": "Multi-repo Git workspaces for OpenCode — one agent across many repos", + "version": "0.2.0", + "description": "One agent across many repos — real git worktrees, no index", "type": "module", "main": "index.ts", "files": [ diff --git a/.qoder-plugin/marketplace.json b/.qoder-plugin/marketplace.json index a9a83fb..21e5ac0 100644 --- a/.qoder-plugin/marketplace.json +++ b/.qoder-plugin/marketplace.json @@ -7,7 +7,7 @@ { "name": "qoder-orbit", "source": "./", - "description": "Multi-repo Git workspaces for any AI coding agent — one agent across many repos." + "description": "One agent across many repos — real git worktrees, no index" } ] } diff --git a/.qoder-plugin/plugin.json b/.qoder-plugin/plugin.json index dd00db4..39210db 100644 --- a/.qoder-plugin/plugin.json +++ b/.qoder-plugin/plugin.json @@ -1,8 +1,8 @@ { "name": "qoder-orbit", "displayName": "Orbit", - "version": "0.1.0", - "description": "Multi-repo Git workspaces for any AI coding agent — one agent across many repos.", + "version": "0.2.0", + "description": "One agent across many repos — real git worktrees, no index", "author": { "name": "orbcli contributors" }, diff --git a/CHANGELOG.md b/CHANGELOG.md index c66b1ae..b506f03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,40 +4,34 @@ All notable changes to this project will be documented in this file. Entries follow the CNCF convention — Urgent Upgrade Notes first, then Changes by Kind — and this project adheres to [Semantic Versioning](https://semver.org/). -## [Unreleased] +## Unreleased -Hardens the destructive surface: `prune` and `sync --force`/`--branch` become machine-enforced root-level operations. Also ships the context-system redesign and retires stop hooks. +## 0.2.0 - 2026-08-23 + +Locks down the destructive surface, redesigns the context system, and makes every install refresh to latest — a dogfooding-driven hardening cycle across 26 PRs (#16–#42). ### Urgent Upgrade Notes -- **BREAKING:** `ORBIT_BRANCH_PREFIX` no longer read — use `orbit config branch.prefix` before creating/pruning branches. -- **BREAKING:** `prune --force` now overrides the data guards too, not just branch protection. -- **BREAKING:** `prune` and `sync --force`/`--branch` run from the project root only — enforced on the **process tree**, not just the cwd: the whole invocation is refused when any ancestor process stands inside a workspace, whichever workspace is targeted. "Inside a workspace" is structural (any non-reserved directory directly under the root, `.repos` and dotdirs excluded), so a plain junk directory at the root is refused too. No flag releases it (`--force`, `--dry-run` included); run from a shell started outside every workspace. -- **BREAKING:** `prune` left the skill's action surface — agents report the need, humans run it. -- **BREAKING:** `prune --verify` removed. PR evidence is now used automatically whenever the workspace recorded `pr.url` entries and `gh` is available — no flag, and no `gh` call at all when nothing was recorded. The old flag gated a per-workspace "all PRs merged" check that force-deleted every branch in the workspace; verdicts are now per-branch, and a merged PR only clears the branch it covers — [`docs/spec-lifecycle.md`](docs/spec-lifecycle.md) → Prune Safety Guards → Branch Verdicts. -- Prune's messages changed — stderr diagnostics and the stdout report shape alike (worktree counts, residue groups, closing block) — contract in [`docs/spec-warnings.md`](docs/spec-warnings.md) → Refusals and skips. -- **BREAKING:** pool fetch config is now orbit-maintained state. Pools converge to the full wildcard map `+refs/heads/*:refs/remotes/origin/*` plus `fetch.prune=true` — written at clone, re-asserted at every `sync`/`info`/session-start/`prune` touchpoint, **removing any other `remote.origin.fetch` mapping** (per-branch entries from older orbit versions, hand edits, emptied configs all converge; each convergence is reported on stderr as it happens). To keep a custom refspec layout, set `orbit config git.fetchAllBranches once` (write the baseline at birth, never correct it) or `never` (fully self-managed); `git.fetchPrune` takes the same three modes. Visible consequences: `@{u}` / `git status` upstream lines work for every branch with upstream config — scoped or raw, no registration step — and a push materializes the tracking ref on the spot; tracking refs self-clean as branches are deleted upstream (the cleaning runs at the fetching touchpoints — since narrowed to `sync` / `prune`); and a bare `git fetch` or `git pull` in any worktree now pulls every branch's objects (a one-time step onto full-clone footing on huge repos — orbit's own commands still fetch named branches only, so agent/headless paths never trigger it). -- **BREAKING:** the `removed stale fetch refspec` / `added fetch refspec` / `would remove` / `would add` output lines are gone, and prune's `pool maintenance:` section no longer carries refspec content — the per-branch registration/reconciliation machinery was deleted outright. In their place, config convergence reports fixed per-key steering lines (`orbit: : fetch config converged: …` / `orbit: : push routing converged: …`) — contract in [`docs/spec-warnings.md`](docs/spec-warnings.md) → Config convergence lines. -- **BREAKING:** `push.default=upstream` joins the maintained set — re-asserted at the same touchpoints (scoped local names differ from remote names, so git's default `simple` would refuse a bare `git push`); escape with `orbit config git.pushUpstreamByDefault once` or `never`. And `push.autoSetupRemote` is gone: its only beneficiary was raw-mode bare push, and raw mode's contract is plain git — a fresh raw branch now gets git's native "no upstream" error naming `git push -u`, while the documented explicit `git push origin ` needs no config at all. Scoped mode is unaffected (its upstream is wired by `switch` up front), and the git ≥ 2.37 soft gate drops with the key. -- **BREAKING:** `install.sh` plain install now always refreshes — the marketplace snapshot is updated and the plugin reinstalled from it on every run ("install is latest"), instead of a no-op once present. The same now holds for the orbit runtime itself (previously skipped whenever `~/.local/bin/orbit` existed) and for the OpenCode plugin files. -- **BREAKING:** `install.sh --force` is now a full reset — it removes the plugin **and** the marketplace, then re-adds and reinstalls from the current source (previously: refresh/update in place). Use it to repair a broken plugin state or to switch the marketplace source. The reset first probes source reachability (`git ls-remote` through the retry chain): unreachable ⇒ teardown skipped and the existing install preserved, so an offline `--force` degrades to a plain refresh instead of destroying the last working snapshot. -- **BREAKING:** OpenCode plugin/skill removal is now directory-level: `--force` wipes `~/.config/opencode/skills/orbit/` entirely (so files dropped from older payloads can't linger), and `--uninstall --opencode` removes that directory instead of just `SKILL.md` — anything you placed inside it is deleted. -- The prune recovery narrative is stated precisely everywhere (spec-lifecycle Recoverability, USAGE, spec-warnings): recovery is the report's `(was )` plus object survival until gc (`gc.pruneExpire`, two weeks by default) — a deleted branch's own reflog is deleted with it, so the 90-day reflog window never applied post-prune. `core.logAllRefUpdates` / `gc.*` are declared premise-only in the dependency closure — user policy orbit deliberately does not manage. +- **BREAKING:** `prune` and `sync --force`/`--branch` run from the project root only, enforced via process ancestry (no flag, including `--force`, bypasses it; guards contract in [`docs/spec-lifecycle.md`](docs/spec-lifecycle.md) → Prune Safety Guards). `prune` also left the agent skill's action surface — agents report the need, humans run it. ([#26](https://github.com/orbcli/orbit/pull/26), [#27](https://github.com/orbcli/orbit/pull/27)) +- **BREAKING:** `prune --force` now overrides the data guards too, not just branch protection (uncommitted changes, unmerged jots, damaged worktrees) — it announces what it discards before doing it. ([#27](https://github.com/orbcli/orbit/pull/27)) +- **BREAKING:** `prune --verify` removed — merged-PR evidence is now applied automatically from the workspace's recorded `pr.url` entries whenever `gh` is available (no external call at all when nothing was recorded); drop the flag from scripts. ([#27](https://github.com/orbcli/orbit/pull/27)) +- **BREAKING:** `ORBIT_BRANCH_PREFIX` is no longer read — use `orbit config branch.prefix` (validated, immovable while branches carry it). ([#26](https://github.com/orbcli/orbit/pull/26)) +- **BREAKING:** `install.sh` now refreshes on every run — "install is latest": the marketplace snapshot is updated, and the plugin and the orbit runtime itself are reinstalled from it. `--force` is a full plugin+marketplace reset that first probes source reachability — an offline reset degrades to a plain refresh instead of destroying the last working install. ([#39](https://github.com/orbcli/orbit/pull/39), [#40](https://github.com/orbcli/orbit/pull/40)) +- **BREAKING:** OpenCode plugin/skill removal is directory-level (`--force`/`--uninstall` wipe `~/.config/opencode/skills/orbit/` entirely) — anything you placed inside is deleted. ([#39](https://github.com/orbcli/orbit/pull/39)) +- **BREAKING:** pool fetch config is now Orbit-maintained: a wildcard refspec plus `fetch.prune`, converged at the fetch touchpoints (orbit's own fetching commands — `sync` / `prune`); opt out per key via `orbit config git.fetchAllBranches` / `git.fetchPrune`. Side effect: a bare `git fetch`/`git pull` in any worktree now fetches every branch. ([#29](https://github.com/orbcli/orbit/pull/29)) +- **BREAKING:** `push.default=upstream` joins the maintained config set, so scoped branches push with a bare `git push`; `sync --branch` additionally moves `origin/HEAD`. ([#29](https://github.com/orbcli/orbit/pull/29)) +- **BREAKING:** output contracts changed. The `removed stale fetch refspec` / `added fetch refspec` / `would remove fetch refspec` / `would add fetch refspec` lines are gone with the per-branch registration machinery, and prune's `pool maintenance:` section no longer carries refspec content — config convergence reports fixed per-key steering lines instead (`orbit: : fetch config converged: …` / `push routing converged: …`). Prune's stderr diagnostics and stdout report shape (worktree counts, residue groups, closing block) changed too. Update anything that greps orbit's output — contracts in [`docs/spec-warnings.md`](docs/spec-warnings.md) and [`docs/spec-lifecycle.md`](docs/spec-lifecycle.md). +- `push.autoSetupRemote` is no longer written — a raw-mode bare `git push` now gets git's native "no upstream" hint instead of an auto-created upstream ([`docs/spec-worktree.md`](docs/spec-worktree.md) → Push Routing). ### Changes by Kind #### Security -- `orbit prune`: root-only, target-independent initiation guard via process ancestry, uncommitted-changes skip, bypass-free refusals — guards in [`docs/spec-lifecycle.md`](docs/spec-lifecycle.md) → Prune Safety Guards. -- `prune` now reclaims **residue**: ghost-workspace scoped branches (grouped by the reclaimed workspace, same merged/unmerged protection; `orbit prune --force` force-deletes a ghost's branches) and untraceable raw branches (reported with status + shell-quoted native delete commands, never auto-deleted). Kept branches end in a closing suggestion block. Git's own `Deleted branch …` chatter no longer leaks into the report — the deletion lines carry the commit instead (`(was )`), the recovery handle: good while the objects survive gc — [`docs/spec-lifecycle.md`](docs/spec-lifecycle.md) → Residue Cleanup and Maintenance Self-Heal. -- Prune's guard set is now stated over **arbitrary prior state**, not just interrupted runs: every file in a workspace — including the `.orbit` marker and the gitdir pointers the guards read — is agent-writable, so each reachable state must be self-healing, self-evident and repairable, or explicitly out of scope. New in that sweep: a **damaged worktree** (registered by the pool, gitdir pointer deleted or corrupted) is detected and the workspace skipped, instead of reading as ordinary content and being destroyed with the directory unannounced — `--force` still clears it, after saying that what it removes cannot be read and cannot be recovered; a failed `rm -rf` keeps the workspace and never prints `pruned:`; stale worktree registrations and orphan `branch.*` config are repaired automatically; `--force` announces that discarding un-persisted work cannot be undone. What prune deliberately does **not** protect is listed too (ignored files, non-repo content, stashes, detached HEAD, concurrency, direct writes into `.repos/`). -- Refusals replay the intended command (`cd && orbit `, your argv verbatim) only when the ancestry walk ran and came back clean; a blind walk (no `/proc`, no `lsof`, no usable `ps`) states the fact alone — a guard that cannot see must not hand out a ready-to-run destructive command. Workspace detection needs no `.orbit` marker either: metadata is disposable, and a guard a lost file can disable is no guard. -- Prune also skips top-level non-pool git repos (a `.git` directory is an independent clone, whatever its name) and unmerged jots (`--force` overrides, `--dry-run` reports). -- `sync --force`/`--branch` root-only; `--branch` writes no fetch config (the wildcard map covers any new default branch) and moves `origin/HEAD` — [`docs/spec-commands.md`](docs/spec-commands.md) → sync. -- Repo names validated as pool basenames in `sync`/`add`/`info`/`memo`/`clone --name` (path-traversal fix). Charset aligned with GitHub (`[A-Za-z0-9._-]`); leading `.`/`-` rejected — contract in [`docs/spec-commands.md`](docs/spec-commands.md) → Repo Name Contract. -- Branch prefix moved from env var to `orbit config branch.prefix`; validated, immovable while branches carry it. -- `orbit config` refuses `repos.*` writes. -- Auto-approve hooks strip quotes/backslashes per token — `'--force'`/`\-\-force` no longer bypass; `sync --branch` prompts too. (Extends [#25](https://github.com/orbcli/orbit/pull/25).) +- `prune` was rebuilt around all-or-nothing validation, a fixed pipeline with a `.prune-trash` recovery window, and per-branch verdicts. ([#27](https://github.com/orbcli/orbit/pull/27)) +- `prune` also reclaims residue — ghost-workspace branches and untraceable raw branches, reported with recovery handles, never auto-deleted. ([#27](https://github.com/orbcli/orbit/pull/27)) +- `prune` self-heals arbitrary prior damage: damaged worktrees detected and skipped, stale registrations repaired, failed deletions never reported as success. ([#27](https://github.com/orbcli/orbit/pull/27)) +- Repo names validated as pool basenames in `sync`/`add`/`info`/`memo`/`clone --name` (path-traversal fix; charset aligned with GitHub). +- Auto-approve hooks strip quotes/backslashes per token — `'--force'`/`\--force` no longer bypass the check; `sync --branch` prompts too. (Extends [#25](https://github.com/orbcli/orbit/pull/25).) #### Feature @@ -45,40 +39,48 @@ Hardens the destructive surface: `prune` and `sync --force`/`--branch` become ma - Session hooks are thin wrappers; new `session-resume.sh` injects the cruise block. ([#17](https://github.com/orbcli/orbit/pull/17), [#19](https://github.com/orbcli/orbit/pull/19)) - Scoped branch mode is now the default; raw→scoped conversion via `orbit switch -c `. ([#18](https://github.com/orbcli/orbit/pull/18)) - Human-facing output rework: header-first, repo-grouped prune reports. ([#20](https://github.com/orbcli/orbit/pull/20)) -- `orbit done` per-repo one-line warnings (jots / thin memo / over-budget card). ([#17](https://github.com/orbcli/orbit/pull/17)) -- `jot.bufferSize` config replaces the hardcoded aggregation threshold. ([#17](https://github.com/orbcli/orbit/pull/17), [#20](https://github.com/orbcli/orbit/pull/20)) -- One-shot explore/curate stderr on `add` (thin memo) and `memo` (over-budget). ([#17](https://github.com/orbcli/orbit/pull/17)) -- `docs/spec-branching.md` is restructured and renamed to **`docs/spec-worktree.md`** — the worktree model doc: the root decision (one pool, many worktrees, argued from the principles), the design priorities, actors/invocation layers, config ownership, and the touchpoint fetch discipline (moved out of spec-lifecycle, where they never belonged) join the branching strategy, push routing, and tracking display. It also gains **Git Dependency Closure**: orbit's complete git dependency surface enumerated as a closed set in three categories — invocations, feature/behavior premises, config keys — with the rule that the set only shrinks (any new dependency requires an explicit set extension in the same PR) and that a config key orbit depends on is managed state (converged or lifecycle-managed — or explicitly declared premise-only). +- `orbit done` per-repo one-line warnings (jots / thin memo / over-budget card); `jot.bufferSize` config replaces the hardcoded threshold; one-shot explore/curate stderr on `add`/`memo`. ([#17](https://github.com/orbcli/orbit/pull/17), [#20](https://github.com/orbcli/orbit/pull/20)) +- `docs/spec-branching.md` restructured into **`docs/spec-worktree.md`**, gaining the Git Dependency Closure — Orbit's git dependency surface as a closed, shrink-only set. ([#29](https://github.com/orbcli/orbit/pull/29)) +- `install.sh` gains a network-resilience layer: every network operation retries on transient failures and never fails silently; the plugin source resolves through a chain (`ORBIT_SOURCES` / `ORBIT_SOURCE`) that rotates per attempt. ([#31](https://github.com/orbcli/orbit/pull/31)) #### Bug or Regression -- Bare `git fetch` can no longer be broken by orbit's own state: a tracked branch deleted upstream no longer produces `couldn't find remote ref` anywhere — orbit's touchpoints fetch named branches only (git's fatal is swallowed) and converge the dead ref with one native `git remote prune origin`. The earlier per-branch refspec registry that caused the class is gone entirely. ([#21](https://github.com/orbcli/orbit/pull/21)) +- Bare `git fetch` can no longer break on a branch deleted upstream: touchpoints fetch named branches only and converge dead refs via native `git remote prune origin`; the per-branch refspec registry is gone. ([#21](https://github.com/orbcli/orbit/pull/21)) - `orbit switch ` always fetches first — no stale checkouts. ([#22](https://github.com/orbcli/orbit/pull/22)) -- Workspace-name validation actually rejects ref-illegal names (space, `~ ^ : ? * [ \`) — the bracket expression had a stray `]` that let every such name through (e.g. `orbit new --name 'dev*'`). -- 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). +- Read paths are purely local again (zero network): `orbit info` and the session-start block no longer fetch — #29's touchpoint fetch made every `info` and every session start pay N serial remote round-trips. Staleness (`remoteAhead`) now reads last-fetched refs; the fetching touchpoints are `sync`/`prune`. ([#35](https://github.com/orbcli/orbit/pull/35)) +- Prune's orphan-config sweep no longer reaps an empty repo's default-branch config, and guards branches checked out in live worktrees (the pool's own checkout exempt). ([#33](https://github.com/orbcli/orbit/pull/33), [#36](https://github.com/orbcli/orbit/pull/36)) +- Workspace-name validation actually rejects ref-illegal names (a stray `]` in the bracket expression let them all through); brief parser and status steering hardened. ([#23](https://github.com/orbcli/orbit/pull/23)) +- Bare `orbit goal` docs converged to reality: it is a write path (editor on TTY, stdin otherwise); the read is `orbit context goal`. ([#34](https://github.com/orbcli/orbit/pull/34)) - 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. - Session guard warns when process ancestry is unreadable, instead of failing silently open. -- `orbit info` and the `orbit context --startup` reignite block no longer fetch — read paths are purely local again (zero network): #29's touchpoint fetch made every `info` and every session start with worktrees pay N serial remote round-trips (the default branch plus each tracked branch, one fetch each), multiplying with pool residue. Ruling: without an async daemon, auto-fetch on a main path taxes a synchronous caller for advisory freshness, and low friction outranks it — auto-fetch may return only off the main path. Layer-1 staleness (`remoteAhead`) now reads last-fetched refs, refreshed by the remaining fetching touchpoints (`orbit sync` / `orbit prune`) or the user's own fetch/pull; fetch-config maintenance (a local write) stays. -- Bare `orbit prune` no longer reaps an empty repo's default-branch config: pool maintenance's orphan-config sweep treats the pool HEAD's target branch as always alive (possibly unborn) — its `branch..*` section is first-push routing, not residue. The protection tracks HEAD and self-releases once the branch gains a ref or the pool switches defaults; non-empty repos are unchanged (the ref check already keeps such sections). ([#36](https://github.com/orbcli/orbit/pull/36)) -- Session-injection hooks anchor their working directory to the host-injected project dir before workspace detection: hook CWD is not a cross-host contract, so a host running hooks from outside the project silently disabled `` injection for the entire session ("not in a workspace" is a designed silent no-op, so nothing ever surfaced). The shared `session-start.sh` / `session-resume.sh` now `cd` to `CLAUDE_PROJECT_DIR` (Claude Code's documented contract, also injected by Qoder) — guarded so empty/unset/invalid values and env-less hosts (codex sets hook CWD correctly by contract) pass through unchanged; host-native variants (qoder's `QODER_PROJECT_DIR`) are mapped onto it by host wrappers — and the OpenCode plugin anchors its shell to the SDK's `PluginInput.directory` instead of inheriting the opencode process cwd. ([#37](https://github.com/orbcli/orbit/pull/37)) -- Qoder IDE sessions receive the injected workspace context again: the IDE parses hook stdout strictly as JSON and silently drops bare text (the CLI has a plain-text fallback), so the qoder SessionStart hooks now run through `hooks/qoder/session-*.sh` wrappers that re-encode the shared scripts' output as `hookSpecificOutput.additionalContext` JSON (with the required `hookEventName`). The shared scripts' anchor reads only `CLAUDE_PROJECT_DIR`; host-native variants stay in host wrappers — the qoder wrapper maps `QODER_PROJECT_DIR` onto it. -- Plugin installs now actually refresh content: every agent CLI exits 0 on `marketplace add` of an already-registered marketplace **without refreshing its snapshot**, so install.sh's `add && return; update` fallback never ran — `--force` reinstalls were green yet installed stale content (one CLI's snapshot lagged six PRs). The marketplace step is now add-then-unconditionally-update (codex gates `upgrade` to git sources; path-backed marketplaces read live), and plugin (re)install re-copies from the refreshed snapshot. +- Plugin install works on SSH-less machines — `try.sh` defaults to HTTPS. ([#24](https://github.com/orbcli/orbit/pull/24)) +- Plugin installs previously kept shipping stale content: every agent CLI exits 0 when re-adding an existing marketplace without refreshing its snapshot, and the old path stopped at the add. Install now always updates after the add, and the plugin reinstall copies from the refreshed snapshot. ([#39](https://github.com/orbcli/orbit/pull/39)) +- Session-injection hooks anchor to the host-injected project dir before workspace detection — a host running hooks from outside the project silently disabled `` injection for the entire session. ([#37](https://github.com/orbcli/orbit/pull/37)) +- Qoder IDE sessions receive the injected workspace context again: the IDE parses hook stdout strictly as JSON, so qoder SessionStart hooks now re-encode the shared scripts' output via `hooks/qoder/` wrappers. ([#40](https://github.com/orbcli/orbit/pull/40)) +- `try.sh` demo no longer dies at its first commit on machines without a global git identity (fresh VMs, containers): the demo pool clones carry a repo-local `Pilot ` identity. ([#42](https://github.com/orbcli/orbit/pull/42)) +- OpenCode auto-approve matches `--force` token-for-token. ([#25](https://github.com/orbcli/orbit/pull/25)) +- Auto-approve tier contract restated by where the judgment lives: framework-verified subcommands stay bundled; `done`/`new` are framework-neutral; `prune`/`clone`/`config` and `sync --force`/`--branch` keep prompting. No hook behavior change — docs now match the hooks. ([#34](https://github.com/orbcli/orbit/pull/34)) +- Cruise-block hint gating fixed: the resume hint now keys on an enumeration-recall test instead of content-in-context. ([#32](https://github.com/orbcli/orbit/pull/32)) +- 65 silently vacuous bats assertions fixed (stock macOS bash 3.2 `set -e` bypasses failing `[[ ]]`) — converted to grep-based helpers, with a lint guard keeping the suite honest. ([#38](https://github.com/orbcli/orbit/pull/38)) + +#### Documentation + +- USAGE gains a "Config-management tools" section: snapshot-restore tools (provider switchers, dotfile syncers) silently roll back plugin registration; recovery = re-run the installer, prevention = keep Orbit's registration entries in the tool's stored config, with per-host snippets. ([#41](https://github.com/orbcli/orbit/pull/41)) +- Memo card scope pinned: repo facts only (roles + entry points), no deep structure. ([#28](https://github.com/orbcli/orbit/pull/28)) +- README and USAGE now state that auto-approve needs `jq` on PATH (not preinstalled on macOS/Linux) — without it the hook stays inert and commands fall back to the native permission prompt; the dependency goes away in a future release (hooks will parse payloads natively). +- Marketplace and package descriptions refreshed. #### Removal -- `install.sh --replace-marketplace` — removed before its first release (it entered after v0.1.0 and never shipped): a colliding `marketplace add` already re-points on most agent CLIs, and the one refused direction (codex git→path) is covered by the new `--force` reset. ([#16](https://github.com/orbcli/orbit/pull/16)) +- `install.sh --replace-marketplace` — never shipped (it entered after v0.1.0 and was removed before its first release): a colliding `marketplace add` re-points on most CLIs, and the refused direction is covered by `--force`. ([#16](https://github.com/orbcli/orbit/pull/16)) - `[seed]` jot sentinel and the gap model — memo state computed inline. ([#17](https://github.com/orbcli/orbit/pull/17)) - Stop hooks and `[nudge]`/`[overlong]` markers — covered by stderr + cruise block + done gate. ([#17](https://github.com/orbcli/orbit/pull/17)) -- Fetch-refspec reconciliation (register/remove directions, gating, default-branch exemption) — the wildcard map + `fetch.prune` are maintained instead, and tracked refs converge through native `git remote prune origin`; `orbit switch -c` no longer registers anything. -- `push.autoSetupRemote` clone/switch writes and the `orbit doctor` git ≥ 2.37 check — see the breaking note above; `orbit switch` no longer writes any push config (touchpoint convergence owns it). +- Fetch-refspec reconciliation machinery (register/remove directions, gating, exemptions) — replaced by the maintained wildcard map + `fetch.prune` and native `git remote prune origin`. +- `push.autoSetupRemote` writes and the `orbit doctor` git ≥ 2.37 check — `orbit switch` no longer writes any push config (touchpoint convergence owns it). -## [0.1.0] - 2026-07-06 +## 0.1.0 - 2026-07-06 ### Added diff --git a/README.md b/README.md index 538c89d..be6f1f6 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,7 @@ Resuming a workspace with repos already present skips priming — the same `--st ## Auto-approving safe commands -Plugin users: nothing to configure — all four plugins (Claude, Codex, OpenCode, Qoder) auto-approve safe orbit subcommands. Skill-only users can allowlist by hand. See [`skills/CONSTRAINTS.md`](skills/CONSTRAINTS.md#permission-and-auto-execution-policy) for command tiers and the allowlist snippet. +Plugin users: nothing to configure — all four plugins (Claude Code, Codex, OpenCode, Qoder) auto-approve safe orbit subcommands. The hook needs `jq` on PATH; without it the hook stays inert and you keep the native prompts. The dependency goes away in a future release. Skill-only users can allowlist by hand. See [`skills/CONSTRAINTS.md`](skills/CONSTRAINTS.md#permission-and-auto-execution-policy) for command tiers and the allowlist snippet. ## Command Reference diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d5ad93f..e1ab485 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,37 @@ +# 🚀 Orbit v0.2.0 — MaxQ + +*Released 2026-08-23* + +**Multi-repo Git workspaces for any coding agent — real worktrees, no index, no server.** + +MaxQ is the ascent phase where aerodynamic stress peaks and the airframe either holds or doesn't — seven weeks of daily dogfooding put this release under exactly that, and the structure held: 26 PRs of hardening, contract pinning, and repair. + +## Highlights + +- **The destructive surface is now machine-enforced.** `prune` and `sync --force`/`--branch` run from the project root only, verified by process ancestry — not by convention. Prune was rebuilt around all-or-nothing validation, per-branch verdicts, and a `.prune-trash` recovery window. +- **The context system was redesigned.** Session start injects a purpose-built startup block (goal, repo memos, staleness); resume/compact get a cheap cruise block. Hooks anchor to the host-injected project dir, so injection works across Claude Code, Codex, OpenCode, and Qoder — IDE included. +- **Install means latest.** Every install run now refreshes the marketplace snapshot and reinstalls the plugin from it; `--force` is a full reset (with a reachability probe so an offline reset can't destroy your last working install). The runtime itself refreshes the same way. + +## ⚠️ Breaking changes (upgrading from 0.1.0) + +- `prune` and `sync --force`/`--branch` run from the project root only — the CLI checks the calling shell's process ancestry, so `cd`-ing out first does not bypass it, and no flag does either. `prune` also left the agent skill's action surface: agents report the need, humans run it. +- `prune --force` now overrides the data guards too, not just branch protection — it announces what it discards first. +- `prune --verify` removed (merged-PR evidence now applies automatically from recorded PR URLs when `gh` is available); `ORBIT_BRANCH_PREFIX` is no longer read (use `orbit config branch.prefix`). +- `install.sh` refreshes on every run; `--force` is now a full plugin+marketplace reset (it probes source reachability first). +- Pool fetch and push config is Orbit-maintained (wildcard refspec + `fetch.prune` + `push.default=upstream`; opt out per key via `orbit config`) — a bare `git fetch`/`git pull` in any worktree now fetches every branch. +- Output contracts changed: the old fetch-refspec reconciliation lines are gone and prune's report shape changed — update anything that greps orbit's output. +- OpenCode plugin/skill removal is directory-level — anything inside `~/.config/opencode/skills/orbit/` is deleted. + +Also in: network-resilient installs (source chain + retries), SSH-less plugin install, demo runs on zero-config machines, guidance for config-management tools that silently roll back plugin registration ([USAGE](USAGE.md#config-management-tools) — recovery and prevention, per host), and a set of removals (stop hooks, the refspec registry, `--replace-marketplace`, and more — the full list is in the changelog); the mission drops spent weight to stay on course. Covered by 396 bats tests. + +Full details in the [changelog](CHANGELOG.md). + +--- + # 🚀 Orbit v0.1.0 — Liftoff +*Released 2026-07-06 · superseded by v0.2.0 (see above)* + **Source code as agent knowledge — multi-repo Git workspaces where agents read, code, and ship.** Orbit manages multi-repo Git workspaces for AI coding agents. Instead of embeddings, vector stores, or stitching together scattered docs, agents work directly against full source worktrees — grepping API signatures, tracing call chains, then editing, committing, and pushing, all from one directory. Every task gets its own isolated workspace, so multiple agents can run in parallel without stepping on each other. diff --git a/ROADMAP.md b/ROADMAP.md index 7697677..e65dd10 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6,49 +6,56 @@ - Command system aligned with design specs: 18 commands covering the complete workflow (clone / new / add / switch / jot / sync / done / prune / status / goal / repos / info / memo / config / context / doctor / completion / version) - Metadata infrastructure (`.repos/.orbit` global index + per-repo `.md` + workspace `.orbit` + brief extraction + staleness detection) - CLI experience: `--json` output + bash/zsh shell completion -- Claude Code skill + Qoder skill + Unified installer +- Skills/plugins bundled for four agents (Claude Code / Codex / OpenCode / Qoder) + unified installer - Core documentation (README / USAGE / ROADMAP / PRINCIPLES / spec series) - `orbit add --ref `: checkout to specified ref when creating worktree, ensuring the source code version the agent verifies matches the project's actual dependencies - `orbit memo --scaffold`: generate scaffold template to stdout (directory structure + README first paragraph + primary language detection), agent uses this as reference to write formal memo -- `orbit context`: output complete context of current workspace (goal + repo brief/memo + background info), agent gets all needed context with one command after entering workspace -- `orbit repos --json` output adds `memoBehind` field: agent can judge memo freshness at Level 0 - `orbit sync [repo...] [--force] [--branch ]`: sync pool repo to upstream latest (fast-forward / force reset / switch tracking branch) - `orbit info` two-layer staleness detection (remoteAhead / memoBehind), reading last-fetched refs - `orbit doctor`: environment health check (git ≥2.20 / bash ≥3.2 / jq+gh optional dependencies / `.repos/` structural integrity diagnostics) - `orbit jot`: lightweight discovery queue (push/pop) for recording knowledge during work, aggregated into memo at natural breakpoints — reduces per-discovery cost from ~500 tokens to ~20 tokens -- Deterministic session-start context injection: the plugin ships a `SessionStart` hook (startup / resume / compact) that runs `orbit context` to keep the agent aware it is inside a workspace — proven on **Claude Code** and **Qoder**, zero user effort (prompts to install the runtime when `orbit` is missing). +- Deterministic session-context injection: plugin-shipped hooks (SessionStart / resume / compact) keep the agent workspace-aware, working across all four bundled agents — zero user effort (prompts to install the runtime when `orbit` is missing) +- Destructive-surface hardening: machine-enforced root-only `prune` / `sync --force`, per-branch verdicts, `.prune-trash` recovery +- Context system: startup/cruise injection blocks; hook CWD anchoring across all four agents (incl. Qoder IDE JSON wrappers) +- Install semantics: every install refreshes to latest; `--force` = full plugin+marketplace reset with reachability probe +- Network-resilient installs (source chain + retries); SSH-less install; zero-config demo (`try.sh`) +- Plugin distribution: Claude Code / Codex / OpenCode / Qoder marketplaces + npm (`opencode-orbit`) + +## Short-term + +### Runtime & UX + +- [ ] Performance: hot paths (session start, `status`, `context`, `prune`) get measurably faster +- [ ] Drop the `jq` dependency — auto-approve hooks parse payloads natively +- [ ] Color output (TTY-aware): auto-colorize when `[ -t 1 ]`, plain text when piped + +### Integrity (pool & workspace) + +- [ ] Pool repo lifecycle: `retire` / `rename` for pool repos (clone gives birth; nothing takes them away) +- [ ] Pool shared-config self-heal via touchpoint convergence (native git has no permission model — prevention isn't possible, so converge-and-repair it is) +- [ ] Workspace integrity recovery: best-effort restoration of required elements ## Mid-term -### Knowledge Sharing +### Agent Ecosystem -- [ ] `export`: export repos + workspace combination as a reproducible rebuild script, enabling team members to one-click replicate the knowledge base (strategic: portability foundation for repos as team knowledge assets) -- [ ] `import`: rebuild repos + workspace environment from export script +- [ ] [pi](https://github.com/earendil-works/pi) support: bundled integration (skill + session-context injection) for the pi coding agent ### Feature Enhancements -- [ ] Color output (TTY-aware): auto-colorize when `[ -t 1 ]` detects TTY, plain text when piped/redirected - [ ] `orbit new --auto-name` agent auto-naming (opt-in) -- [ ] Cross-worktree conflict detection: detect files modified in the same repo across different workspaces, warning of potential merge conflicts -- [ ] PR URL → worktree: `orbit add --pr ` auto-fetch PR head branch to create worktree (depends on gh CLI) +- [ ] PR URL → worktree: `orbit add --pr ` fetches the PR head via origin's `pull/N/head` ref (GitHub first; no extra remote, no gh dependency) ## Long-term -- [ ] `graph`: cross-repo dependency graph +### Repo Graph -## Backlog +- [ ] `graph`: cross-repo dependency/reference graph over the pool — code refs and memo links alike; the long-run shape is an Obsidian-style graph view built on the link structure -The following items come from community benchmarking analysis. Not prioritized now but not excluded for the future. Entry criteria: short-term feasibility + validated real demand. +### Memo Management -- [ ] `trust` security model: repo-defined executable hooks require explicit approval before execution, preventing malicious repo injection (distinct from the plugin `SessionStart` hook; prerequisite: after a repo-hooks mechanism exists) -- [ ] Workspace fork: `orbit fork ` derive from existing workspace (prerequisite: after lifecycle stabilization) -- [ ] `orbit watch`: daemon monitoring PR merged / branch deleted, triggering notifications or auto prune -- [ ] Port range environment isolation: each workspace assigned an independent port range to avoid multi dev server conflicts (opt-in) -- [ ] MCP server: expose orbit capabilities as MCP endpoint (skill already provides agent integration path, MCP is supplementary not prerequisite) -- [ ] `exec ""`: batch execute on all repos within workspace (not essential for agent scenarios, nice-to-have for human scenarios) -- [ ] `orbit add` config file copy: copy gitignored config files from repos to worktree (only serves "running code" scenarios, not core to agent knowledge path) -- [ ] `.code-workspace` auto-generation: pure human IDE DX, not core to agent knowledge path -- [ ] Optional TUI mode: pure human interactive experience (prerequisite: after core command system stabilization) +- [ ] Memo write-pollution guard: a weaker agent's card write must not silently overwrite a stronger one (single-level `.bak` rotation before writeback) +- [ ] Memo staleness by content anchors (best-effort hints only: free-form card input means no hard verdicts) ## Known Issues and Compatibility @@ -58,7 +65,7 @@ The following items come from community benchmarking analysis. Not prioritized n | 2 | JetBrains IDE worktree support | IntelliJ / GoLand | Unconfirmed | Whether VCS model correctly recognizes worktree | | 3 | Language server cross-repo references | gopls / tsserver | Unconfirmed | Multi-repo cross-references under workspace | | 4 | Qoder / VS Code worktree recognition | Qoder + VS Code | Confirmed (working) | Branch, status, diff all work correctly | -| 5 | Session-start context injection on other frameworks | Agent frameworks beyond Claude Code / Qoder | Fallback only | No native `SessionStart`-equivalent hook means deterministic injection isn't available; the agent relies on the `orbit start` trigger phrase, which depends on the launch phrase actually being used | +| 5 | Session-start context injection on other frameworks | Agent frameworks beyond the four bundled ones (Claude Code / Codex / OpenCode / Qoder) | Fallback only | No native `SessionStart`-equivalent hook means deterministic injection isn't available; the agent relies on the `orbit start` trigger phrase, which depends on the launch phrase actually being used | ## Notes diff --git a/USAGE.md b/USAGE.md index c13b804..2b23967 100644 --- a/USAGE.md +++ b/USAGE.md @@ -605,7 +605,7 @@ orbit completion bash > /path/to/bash-completion/completions/orbit 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:** 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. +**Plugin users — nothing to do:** all four plugins ship an auto-approve hook that approves exactly the framework-verified subcommands and fails safe. The hook needs `jq` on PATH; without it the hook stays inert and you keep the native prompts. The dependency goes away in a future release. **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 b5515e8..966db6d 100644 --- a/docs/spec-commands.md +++ b/docs/spec-commands.md @@ -322,7 +322,7 @@ Environment health check; does not require being inside an orbit project. ## orbit version -Prints the orbit runtime version (e.g. `0.1.0`) to stdout and exits 0. Does not require being inside an orbit project. `--version` and `-v` are aliases. This is the runtime version, distinct from the plugin manifest version — the runtime is installable on its own (`install.sh` / curl bootstrap), so it carries its own version. +Prints the orbit runtime version (e.g. `0.2.0`) to stdout and exits 0. Does not require being inside an orbit project. `--version` and `-v` are aliases. This is the runtime version, distinct from the plugin manifest version — the runtime is installable on its own (`install.sh` / curl bootstrap), so it carries its own version. ## Edge Case Handling diff --git a/orbit.sh b/orbit.sh index 1343de5..5186fea 100755 --- a/orbit.sh +++ b/orbit.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -ORBIT_VERSION="0.1.0" +ORBIT_VERSION="0.2.0" ORBIT_ROOT="${ORBIT_ROOT:-}" ORBIT_DEFAULT_BRANCH_PREFIX="ws" ORBIT_CMD="${0##*/}"