Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Hardens the destructive surface: `prune` and `sync --force`/`--branch` become ma
- **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; 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:** 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: <repo>: fetch config converged: …` / `orbit: <repo>: 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 <branch>` 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.
- The prune recovery narrative is stated precisely everywhere (spec-lifecycle Recoverability, USAGE, spec-warnings): recovery is the report's `(was <sha>)` 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.
Expand Down Expand Up @@ -62,6 +62,7 @@ Hardens the destructive surface: `prune` and `sync --force`/`--branch` become ma
- `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.

#### Removal

Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
- `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 <branch>]`: sync pool repo to upstream latest (fast-forward / force reset / switch tracking branch)
- `orbit info` auto-fetch + two-layer staleness detection (remoteAhead / memoBehind)
- `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).
Expand Down
2 changes: 1 addition & 1 deletion USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ View detailed memo of a specific repo:
orbit info backend
```

`orbit info` automatically fetches upstream and detects two layers of staleness:
`orbit info` is purely local (never fetches) and detects two layers of staleness against last-fetched refs — refreshed by `orbit sync` / `orbit prune`, or your own fetch/pull:
- **Pool behind upstream**: stderr shows `N new commits on origin/main`, run `orbit sync` to synchronize
- **Memo behind pool HEAD**: stderr shows `memo is N commits behind HEAD`, consider updating memo

Expand Down
14 changes: 7 additions & 7 deletions docs/spec-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,23 +223,23 @@ Jot entries are real discoveries only — orbit writes no system placeholders in

- **`--startup` (session-start block)**: used by the `SessionStart` hook and by the skill's startup detection; one call doubles as workspace detection (fails fast outside a workspace). Routes internally on worktree presence: empty → **prime**, populated → **reignite**. `--prime` / `--reignite` select the block explicitly (human/debug use; the skill exposes only `--startup` and the bare form).
- **prime** (cold start, empty workspace): outputs `path` / `goal` (if set) / `state` (with a DONE banner when the workspace is `done`), then the **pool roster** — the "add menu": `available in pool (orbit add <repo> ...):` followed by ` <name> <one-line brief>` per pool repo. Briefs resolve via the shared fallback (index → memo → README; [spec-metadata](spec-metadata.md) "Fallback Rules"), so a freshly cloned repo shows its README brief rather than `-`. The steering that `orbit repos` sends to stderr is inlined as trailing stdout sections — pool repos without a memo are listed under `no memo (write the card via orbit memo <repo>; …):`, and repos whose memo exists but whose index entry drifted under `index out of sync (repair via orbit memo <repo> --refresh):` — because hook injection carries only stdout. Level-0 briefs only (no memo dumps) — full memo on demand via `orbit info <repo>` (progressive loading). Empty pool prints `pool is empty — clone a repo into the pool first: orbit clone <url>`.
- **reignite** (worktrees present): outputs the same durables, then per worktree repo: `--- <name> (branch: <branch>) ---` + two-layer staleness (memoBehind + remoteAhead — fetches like `orbit info`, advisory only; sync stays on-demand) + conditional `status:` line (jot count and level / commits behind upstream / `memo thin` / `memo over budget` — only repos with something pending) + small jot queues inlined (up to `jot.bufferSize` entries; larger queues collapse to a count + pop hint) + the full memo card. No roster, no source.
- **reignite** (worktrees present): outputs the same durables, then per worktree repo: `--- <name> (branch: <branch>) ---` + two-layer staleness (memoBehind + remoteAhead — purely local, read from last-fetched refs; advisory only, sync stays on-demand) + conditional `status:` line (jot count and level / commits behind upstream / `memo thin` / `memo over budget` — only repos with something pending) + small jot queues inlined (up to `jot.bufferSize` entries; larger queues collapse to a count + pop hint) + the full memo card. No roster, no source.
- **Bare `orbit context` (cruise block)**: the in-session counterpart of the startup block, for compact/resume recovery — cheap durables (`path` / `goal` if set / `state`, with DONE banner) plus **conditional per-repo status** (only repos with pending jots, commits behind upstream, an untracked raw-mode branch, or a non-ok memo state), one line each: `repo <name>: 3 jots (building) | 2 behind upstream | memo thin`. Never fetches (uses local refs; behind is measured against the worktree branch's `@{upstream}` — a raw-mode branch without upstream shows `no upstream (fetch origin <branch> to track)` instead of a count, so the agent knows to materialize the ref). Does **not** dump memos — pull a repo's memo on demand with `orbit info <repo>`.
- **Single key query**: `orbit context <key>` outputs a single value and exits. Supported keys: `workspace` (name), `path` (absolute path), `goal`, `state` (active/done).
- Must be executed within a workspace (inferred from CWD); errors at project root or outside a project — hooks treat failure as a silent no-op.
- A key cannot be combined with `--startup`/`--prime`/`--reignite`; the three mode flags are mutually exclusive.
- `--json`: structured output for debug/scripting (format in the "JSON Output Format" section below). The bare form's JSON mirrors the cruise block (no memos); `--startup --json` follows the same worktree routing — empty workspace emits the prime JSON shape, populated workspace emits the reignite JSON shape.

## orbit info Auto-fetch
## orbit info staleness

`orbit info <repo>` maintains the repo's fetch config and fetches on execution (the touchpoint discipline of [`spec-worktree.md`](./spec-worktree.md#touchpoint-fetch-discipline): the default branch plus every tracked remote branch, one explicit refspec per fetch, never a bare fetch), enabling two-layer staleness detection:
`orbit info <repo>` is a screening command and stays purely local — zero network. It maintains the repo's fetch config (a local write; [`spec-worktree.md`](./spec-worktree.md#config-ownership)) but never fetches, and detects two-layer staleness against last-fetched refs:

- **Layer 1 (pool ← upstream)**: After fetch, compares pool repo's local branch with `origin/<branch>`; if behind, outputs to stderr: `orbit: <repo>: N new commits on origin/<branch>`
- **Layer 1 (pool ← upstream)**: Compares pool repo's local branch with the last-fetched `origin/<branch>`; if behind, outputs to stderr: `orbit: <repo>: N new commits on origin/<branch>`
- **Layer 2 (memo ← pool HEAD)**: Existing `orbit_staleness_check`, compares HEAD at memo write time with pool repo's current HEAD

Both layer warnings output to stderr, not affecting stdout. A config convergence (see [`spec-worktree.md`](./spec-worktree.md#config-ownership)) prints its `orbit: <repo>: … converged: …` line to stderr at the moment it happens. Fetch failures are silently tolerated (network unavailability does not block viewing); a remote-deleted tracked branch's ref is converged by the closing `git remote prune origin`, and only the default branch's failure can surface — as an explicit WARNING when the remote answers.
Both layer warnings output to stderr, not affecting stdout. A config convergence (see [`spec-worktree.md`](./spec-worktree.md#config-ownership)) prints its `orbit: <repo>: … converged: …` line to stderr at the moment it happens.

The same maintain-then-named-fetch pair runs per repo in the `orbit context --startup` reignite block (the session-start hook path). Tracking refs also materialize without any touchpoint: under the wildcard map a push updates `origin/<branch>` on the spot. `orbit repos` and bare `orbit context` (cruise block) do not fetch (stay purely local and fast), only show Layer 2.
Layer 1 is only as fresh as the last fetch. The fetching touchpoints are `orbit sync` and `orbit prune` (the touchpoint discipline of [`spec-worktree.md`](./spec-worktree.md#touchpoint-fetch-discipline)); the user's own `git fetch` / `git pull` refreshes the same refs. The `orbit context --startup` reignite block (the session-start hook path) maintains config per repo but never fetches either — session start is the session's main path and stays zero-network. Tracking refs also materialize without any touchpoint: under the wildcard map a push updates `origin/<branch>` on the spot. `orbit repos` and bare `orbit context` (cruise block) do not fetch either (stay purely local and fast), only show Layer 2.

## orbit sync

Expand Down Expand Up @@ -497,6 +497,6 @@ Field descriptions:
```

- `worktrees`: array, one entry per worktree repo (unfiltered, unlike the bare form)
- `memoBehind` / `remoteAhead`: numbers, two-layer staleness distances (reignite fetches, like `orbit info`)
- `memoBehind` / `remoteAhead`: numbers, two-layer staleness distances (purely local — reignite reads last-fetched refs, like `orbit info`)
- `jotEntries`: array of string, unpopped jot entries (full list; the markdown block inlines only up to `jot.bufferSize`)
- `memo`: string, full memo content (`""` when no memo)
5 changes: 3 additions & 2 deletions docs/spec-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@
- **startup** (`orbit context --startup`): cold start (empty workspace) →
durables (`path` / `goal` / `state`, DONE banner when done) + the pool
roster; populated workspace → durables + each repo's memo card + two-layer
staleness (memoBehind + remoteAhead — fetches like `orbit info`, advisory
only; sync stays on-demand) + conditional per-repo status + small jot
staleness (memoBehind + remoteAhead — purely local, read from
last-fetched refs, advisory only; the hook is the session's main path
and never fetches; sync stays on-demand) + conditional per-repo status + small jot
queues inlined (up to `jot.bufferSize`).
- **cruise** (bare `orbit context`): cheap durables + conditional per-repo
status only (pending jots with level / commits behind upstream /
Expand Down
9 changes: 4 additions & 5 deletions docs/spec-knowledge.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Agents fetch repo information on demand, drilling down level by level, autonomou

```
Level 0: orbit repos <- global index: name + url + brief + memoBehind (purely local)
Level 1: orbit info <repo> <- per-repo markdown full text + fetch + two-layer staleness detection
Level 1: orbit info <repo> <- per-repo markdown full text + two-layer staleness detection (purely local)
Level 2: orbit add <- worktree enters workspace
Level 3: agent reads/writes code <- memo describes pool repo's stable branch (main branch) state
```
Expand Down Expand Up @@ -44,10 +44,9 @@ fi

### Layer 1: Pool Behind Upstream (`remoteAhead`)

When `orbit info` executes, it automatically fetches the repo's tracking branch, then compares the local branch with `origin/<branch>`:
`orbit info` never fetches — a screening command stays purely local. It compares the local branch with the **last-fetched** `origin/<branch>`; the refs are refreshed by the fetching touchpoints (`orbit sync` / `orbit prune`) or the user's own `git fetch` / `git pull`:

```bash
git -C .repos/backend fetch origin main 2>/dev/null
local_head=$(git -C .repos/backend rev-parse refs/heads/main)
remote_head=$(git -C .repos/backend rev-parse refs/remotes/origin/main)
if [ "$local_head" != "$remote_head" ]; then
Expand All @@ -59,10 +58,10 @@ fi
### Design Points

- Both layers output hints to **stderr**, avoiding stdout pollution
- `orbit repos` only checks Layer 2 (purely local, fast); `orbit info` checks both layers (triggers fetch)
- `orbit repos` only checks Layer 2 (purely local, fast); `orbit info` checks both layers (purely local — Layer 1 reads last-fetched refs)
- No hard thresholds are set; only distance numbers are reported, leaving the agent/human to decide whether to update
- Layer 2 precondition: `head` field exists **and** per-repo `.md` file exists (no .md triggers the fallback path)
- Layer 1 silently skips on fetch failure (network unavailability does not block viewing)
- Layer 1 freshness is bounded by the last fetching touchpoint (network unavailability never blocks viewing)
- `orbit clone` writes basic index fields (url + head) but does not generate per-repo `.md`

## Sync and Memo Cascading Relationship
Expand Down
Loading
Loading