Files
reliquary/docs/workpad/research/2026-09-29-17-orbit/phase-2-review-workflows.md
T
g4borg 0dee6c415b 📝 Add orbit research tree, goal, and session journal
Research tree on using the orbit code-graph CLI for review in Rust
projects: the Python/Django field guide recorded as phase 0, orbit 0.133
capabilities on spacecraft and dirigent, fit with Spire's review stage,
and tested review recipes (changed definitions, blast radius, blindness
preflight). Includes the orbit-skill goal and the Igor session journal.
2026-09-29 19:52:59 +02:00

38 KiB
Raw Blame History

phase, stream, date, orbit-version, depends-on, test-beds, status
phase stream date orbit-version depends-on test-beds status
2 review-workflows 2026-09-29 0.133.0 phase-1-rust-capabilities.md, phase-1-review-fit.md, phase-0-field-guide-python-django.md spacecraft (2 ranges), dirigent (5 ranges), all historical, indexed from detached worktrees into per-review isolated DBs findings

Phase 2: Review workflows with orbit + grep (Rust)

Legend: [V] = verified here with command output and a ground-truth comparison. [I] = inferred, not tested directly.

Scripts, SQL and rules files are in ~/.cache/orbit-probe/recipes/ (full list in §11). Raw outputs for every range are in ~/.cache/orbit-probe/out/. Nothing was compiled. The shared DB ~/.gitlab/orbit/graph.duckdb was not written: its sha256 was the same before and after the run.

TL;DR

  • Orbit gives a reviewer a structural map of a Rust diff that the diff text does not. It lists which definitions were added, removed, changed in signature or touched in body; where their callers and importers are; which trait implementors must follow a trait change; and whether new crate edges appeared. A full pass indexes base and tip into a throwaway DB and runs six scripts. It takes about 30 s and prints about 16 KB, compared with a 56–411 KB raw diff. [V]

  • Within the code orbit can see, the changed-definitions map is exact: precision 100 % and recall 100 % across 7 ranges. But orbit cannot see much of Rust:

    • Of all item declarations the diffs added or removed, 27–100 % were visible depending on the range. Every miss was in #[cfg(test)] code, a cfg(feature) region, or a file gated at a parent mod.
    • The preflight measured 6 % to 71 % of changed lines as invisible to orbit. dirigent's server feature hides whole connector modules.
    • Run the blindness preflight first. It decides how much to trust everything after it. [V]
  • Blast radius: one level down, orbit misses half; one level up, it misses little. Recall at definition level was 49 % (129 of 265 grep-confirmed dependents). At file level, callers plus importers reached 93 % (138/148). The gaps:

    • cfg(test) code: 36
    • enum variant literals and multi-line patterns: 32
    • Bevy systems passed as fn refs: 21
    • gated files: 17
    • Type::default()-style calls: 6
    • rsx! bodies: 4
    • dyn receivers: 2

    Report orbit's callers as a lower bound. Close each gap with the grep named in its class. [V]

  • The review findings that no plain git diff shows:

    • Doc drift: a new crate edge warpspace_replay → warpspace_sync_core contradicts the dependency graph in AGENTS.md.
    • Re-export shims: 8 of 8 removed duplicate types were correctly traced to pub use shims that keep the old paths alive for 1–13 importers.
    • Hidden trait implementor: a cfg-gated MockBackend of a changed trait. Orbit cannot see it; the grep fallback in the trait recipe does.
    • Stop signal: a branch where 71 % of changed lines are feature-gated. There the preflight says to stop relying on orbit. [V]
  • New facts about orbit:

    • Orbit indexes the working tree, including uncommitted and untracked files, but labels the rows with HEAD's sha. [V]
    • repo-map api accepts directory prefixes only. A file path silently returns nothing. [V]
    • No import rows are recorded for use inside #[cfg(test)] mod blocks (0 of 621) or inside fn bodies (mostly none). Inline paths such as other_crate::x::Y never become import rows. [V]
  • Feedback log: one JSONL line per orbit query that informed a decision. Written by a tested helper, fb.sh, and stored by default outside git at ~/.local/share/orbit-skill/feedback.jsonl (§9).


1. Test beds and ranges

id repo range (base..tip) what it is .rs files +/− lines
sc-replay spacecraft 21cdd1fd..bfbbcd96 (45c50f07^..bfbbcd96) replay feature FL-1..3 + refactor, 13 commits 72 +2245/−361
sc-unify spacecraft 00325961..8df804e1 (a8711df5^..8df804e1) "unify duplicate types / delete dead code" series 39 +364/−1361
dg-tokens dirigent 9e9374be..d3f99fb7 (merge PR #28) session token stats 17 +1043/−32
dg-acp dirigent 0c40e871..5de6c726 (merge-base(main)..branch) W1c live ACP branch (typed updates, generic SessionUpdate<Id>) 58 +4331/−2020
dg-archivist dirigent 313c0f11..22034c02 (merge) reading pane + archivist redesign 32 +5033/−170
dg-trash dirigent 300f4319^..300f4319 adds ArchiveBackend::trash_session 17 +1153/−120
dg-move dirigent cd4e8f70^..cd4e8f70 adds delete_session_if_unchanged, removes log_stamp from the trait 8 +324/−142

Every index used -t 4: skipped_files=0 and errored_files=0 on all 16 indexes. [V]

2. Per-review setup (shared by all recipes)

R=~/.cache/orbit-probe/recipes
$R/review-setup.sh ~/Projects/dirigent 300f4319^ 300f4319 dg-trash > out/dg-trash.env
# -> DB=~/.cache/orbit-probe/dg-trash.duckdb  BASE_WT=…/worktrees/dg-trash-base  TIP_WT=…/worktrees/dg-trash-tip  BASE_SHA TIP_SHA REPO
...recipes...
$R/review-teardown.sh ~/Projects/dirigent dg-trash   # git worktree remove + prune + rm DB
  • Both commits go into one isolated DB. orbit sql --repo <worktree> --db <db> scopes each query to one commit, so recipes query base and tip separately and join them in Python on (fqn, kind). Definition IDs differ between commits. [V]
  • Time: about 6 s per index with -t 4, plus worktree checkout. review-pass.sh (all six recipes) ran in 30 s wall time on dg-tokens. [V]
  • Orbit indexes the working tree, not the commit. I appended a fn to a tracked file and created an untracked .rs file in a scratch worktree, then indexed it into a throwaway DB. Both appeared: orbit_probe_dirty_marker and zz_probe_untracked.rs. The manifest still said commit_sha=cd4e8f70 (HEAD). [V] This has two consequences:
    • (a) An index labelled with sha X proves nothing if the tree was dirty. The preflight checks git status --porcelain.
    • (b) An uncommitted diff, for example Athanor output before commit, can be reviewed by indexing the live checkout into an isolated --db as "tip". [I: tested only in a scratch worktree, not on a live checkout] Phase 1's statement that "uncommitted edits are not visible" is corrected.
  • Do not reuse the shared DB for review. Its per-repo row is replaced by whichever agent indexed last, and is often stale (phase 1).

3. Recipe 1: Changed definitions

Answers: which definitions the diff added, removed, moved, changed in signature, or touched only in body, with a one-line signature delta and field/variant membership changes. Also which changed lines no definition covers; that tally feeds recipe 5.

Command: python3 $R/defdiff.py out/<id>.env --json out/<id>.defdiff.json

How it works:

  1. git diff -U0 -M base tip -- '*.rs' gives changed line numbers on both sides.
  2. Each line maps to its innermost orbit definition. Fields and variants roll up to their parent type; Module is used only if nothing smaller covers the line.
  3. Definitions are matched across commits on (fqn, kind), same file first, following renames.
  4. The signature is re-read from the source: the declaration up to { or ;, skipping docs and attributes. It is compared as normalised text.
  5. Children of an added or removed type collapse into (+N members).
  6. Uncovered lines are classified as cfg-test, cfg-feature, cfg-other, file-has-no-defs, import, impl-header/brace or blank/comment.

A single-hunk manual variant is in sql/hunk-to-defs.sql.

Sample output (sc-replay, excerpt):

crates/warpspace_debug/src/bin/ui_main.rs
  S Function spawn_embedded_server_on_menu L234-273
      sig: …on<Res<EmbeddedServer>>, [--]{+replay_path: Option<Res<ReplayPath>>, +})
  + Struct ReplayPath L47-49
  ~ Struct Args L13-45 29->33 lines members +replay
crates/warpspace_domains/src/orders/core.rs
  ~ Enum orders::core::OrderOrigin L155-173 15->19 lines members +External.peer_debug +External.player -External.0
src/sync/game_record.rs
  S Constant sync::game_record::FORMAT_VERSION L413-418
      sig: …st FORMAT_VERSION: u32 = [-5-]{+6+};

The OrderOrigin::External change from a tuple variant to a struct variant is visible at a glance. It forced edits in 35 test files, none of which orbit can see (recipe 5).

Measured against ground truth (gt_items.py). Ground truth is a regex extraction of item declarations (fn|struct|enum|trait|const|static|type|mod|macro_rules!) from both file versions; the table compares added and removed items per file:

range added: text / orbit / recall removed: text / orbit / recall precision every miss is in…
sc-replay 87 / 78 / 90 % 3 / 1 / 33 % 100 % cfg-test (5), file with no defs (6: warpspace_testing/src/tests/*)
sc-unify 5 / 5 / 100 % 79 / 71 / 90 % 100 % cfg-test (8)
dg-tokens 40 / 15 / 38 % 0 100 % cfg-test (23), cfg-feature (2)
dg-acp 152 / 41 / 27 % 32 / 2 / 6 % 100 % cfg-test (109), file gated by server (32)
dg-archivist 189 / 104 / 55 % 0 100 % gated file (44), cfg-test (40), cfg-feature (1)
dg-trash 52 / 37 / 71 % 1 / 0 100 % cfg-test (13), cfg-feature (3)
dg-move 14 / 5 / 36 % 5 / 4 / 80 % 100 % cfg-test (6), gated file (3), cfg-feature (1)
  • Precision is 100 % everywhere, and recall on orbit-visible code is 100 %. Every missed item fell into a cfg or gated category. [V]
  • Signature detection was checked against fn declaration lines that change on both sides of the diff:
    • Every one was either reported as S, or sat in gated/test code (dg-acp: 9 in server-gated or test code).
    • Two single-line fns (new, world) whose declaration line changed only in the body were correctly reported as body-only (~). [V]
  • Moves (>, same fqn in another file) did not occur in any range. The logic is untested. [I] Moves across crates change the fqn and show as -/+. The script annotates a + with "same name removed in …" when the name and kind match.

Blind spots:

  • Everything under cfg-off: all features off, test off, debug_assertions off.
  • Files gated at a parent mod.
  • Changes to impl headers or where-clauses. There is no Impl node, so the line is unattributed.
  • Changes to #[derive] or attributes count as touching the item, because start_line includes attributes.
  • Signature text over 12 lines is cut.

Output size: 2.5–12.7 KB, which is 3–8 % of the raw diff (sc-replay 12.7 KB against 214 KB; dg-acp 12.3 KB against 411 KB). About 1–3.5k tokens. Runtime about 1.5 s. [V]

4. Recipe 2: Blast radius

Answers: for each changed definition (S, -, >, and ~ with member changes), who depends on it:

  • CALLS callers. For a type, this includes callers of its methods, associated fns and variant constructors.
  • Importing files.
  • EXTENDS implementors, for traits.

Dependents are split into in-diff and OUTSIDE diff. The outside ones are what a reviewer has to check. For removed definitions it also reports:

  • whether a same-name definition survives elsewhere, and
  • whether the tip still imports the old path. If so, is the path kept alive by a re-export, or does it dangle?

Command: python3 $R/blast.py out/<id>.env out/<id>.defdiff.json [--measure]. Standalone SQL: sql/dependents.sql, sql/importers.sql.

Sample output (dg-acp, then sc-unify):

~ Enum conversation::MessagePart [crates/dirigent_protocol/src/conversation.rs]  callers=16 outside=12  importers=37 outside=17
    <- crates/dirigent_protocol/src/merge.rs:447 apply_to_message
    <- crates/dirigent_archivist/src/record_upgrade.rs:511 unreadable_placeholder
    … 6 more outside callers
- Struct info::UnitInfo [crates/warpspace_combat/src/info.rs]  callers=0 outside=0  importers=9 outside=9
    = same-name Struct survives at tip in: crates/warpspace_core/src/info_vocab.rs
    = old path '…info::UnitInfo' kept alive by re-export at crates/warpspace_combat/src/lib.rs:17 (8 importers use it)

Measured (--measure). Ground truth was git grep at the same commit, with these rules:

  • Method names are matched as .name/::name, associated fns as Type::name.
  • A site counts only if its file can see the name: it imports the name, glob-imports the owner module, is the defining file, or uses a qualified owner::name.
  • Import lines, type mentions, impl headers, pattern matches and const reads are excluded. These produce no edge by design, and are not "callers".
  • Names defined more than once at that commit were skipped as ambiguous (66 targets, mostly sc-unify's duplicates).
range dependents (grep ∪ orbit) orbit found def-level recall file-level (callers ∪ importers)
sc-replay 41 15 37 % 27/31 = 87 %
sc-unify 13 5 38 % 10/11 = 91 %
dg-tokens 5 2 40 % 2/2
dg-acp 179 91 51 % 80/84 = 95 %
dg-archivist 19 8 42 % 13/14
dg-trash 3 3 100 % 3/3
dg-move 5 5 100 % 3/3
total 265 129 49 % 138/148 = 93 %

Why orbit missed a dependent, with the grep that closes each gap (136 misses, all [V]):

gap class n example fallback
inside #[cfg(test)] 36 game_record.rs:2437 RecordCounters::default() git grep -nw Name limited to test modules; read the test module
enum variant literal / multi-line match pattern 32 SessionUpdateVariant::AgentMessageChunk(chunk) git grep -nE 'Enum::\w+'. For an added variant, find every match on the enum: exhaustiveness is invisible to orbit
fn passed by reference (Bevy system, callback) 21 app.add_systems(FixedFirst, conductor_gate_system); git grep -nw fn_name. For a Bevy system a changed signature is harmless, so ask "is it still registered?"
file gated at parent mod / tests dir 17 connectors/opencode.rs (server feature) read the file; git grep
Type::default(), derive/trait associated calls 6 RecordCounters::default() git grep -n 'Type::'
other (struct literal Type { … }, return type in signature) 12 let runner = HeadlessRunner { git grep -nw Type
inside a multi-line rsx! body 4 let title = format_token_title( in a rsx!{} git grep -nw
cfg-other / cfg-feature 6 matches!(&err, ArchivistError::LogUnreadable …) read the region
method on an unresolved receiver (dyn/field/closure) 2 .translate_notification(…) git grep -n '\.method('
  • Orbit-only dependents (19 in dg-acp, 7 in sc-replay) are real. For a changed type, they are callers of its methods, which a grep for the type name cannot find. Examples: HeadlessRunner <- fast_forward_to, ArchivistError <- backfill_connector_sessions. For types, orbit and grep complement each other. [V]
  • The re-export check was 8/8 correct on sc-unify. All 8 removed types whose old path the tip still imports are kept alive by pub use. I checked 5 by hand, for example input/runtime.rs:14 pub use warpspace_core::input_vocab::{ActionState, Actions, InputRuntime};. A naive "name still imported" check would have flagged all 8 as dangling. [V]

Output size: brief mode (no --measure) is 0.9–5 KB for feature ranges and 14 KB for sc-unify's 48 removal targets. Runtime about 5 s. [V]

5. Recipe 3: Trait-impl coverage

Answers: for each trait the diff touches (the trait itself, or a method whose parent is a trait):

  • its methods at tip, and which were added, removed or changed in signature;
  • every implementor, from three sources:
    • E: EXTENDS; only same-file impls.
    • G: git grep 'impl … Trait … for Type'. Text, so it also sees gated and test impls.
    • H: method-name overlap of at least 60 %.
  • per implementor, how many trait methods it defines and which the diff touched;
  • for implementors orbit cannot see, a text check that the new method exists in the impl file.

Command: python3 $R/traitcov.py out/<id>.env out/<id>.defdiff.json [--trait Name] [--all-traits]. Standalone SQL: sql/implementors.sql (E ∪ H).

Sample output (dg-trash):

Trait dirigent_archivist::backend::traits::ArchiveBackend [crates/dirigent_archivist/src/backend/traits.rs:33] methods=21 +['trash_session']
   [EGH] JsonlBackend  defines 20/21 touched:trash_session
   [G  ] MockBackend  type not indexed (external/gated)  impl-in: crates/dirigent_archivist/src/backend/mock.rs  cfg-test  new method(s) ['trash_session'] found by text in impl file (orbit can't see it)

MockBackend lives in a file gated by #![cfg(any(test, feature = "test-utils"))]. git show 300f4319:…/mock.rs confirms async fn trash_session at line 369. 20/21 means as_search, a default method, is not overridden. That is expected, not a finding. [V]

Measured. Over every in-repo trait at tip, grep impls were taken as ground truth (--all-traits):

repo @ tip grep impls found by E or H only grep: gated only grep: type not indexed only grep: cross-file/other H false positives E found, grep regex missed
dirigent @ 5de6c726 (30 traits) 67 41 (61 %) 20 5 1 8 (facades such as Archivist, ServerRuntime) 0
spacecraft @ bfbbcd96 (18 traits) 42 33 (79 %) 1 4 4 1 1 (multi-line impl header)
  • Diff-scoped runs caught the one new trait in sc-replay (ReplayHarnessAssertions). Its only impl sits in another file ([G]), so EXTENDS has no edge; the text check found it.
  • sc-unify removed the trait AbilityLike. The recipe prints the grep that checks for leftover impls. [V]

Blind spots:

  • External traits (Plugin, Serialize, …) are not nodes, so there is no trait to anchor on.
  • Blanket impls.
  • impl on external types (impl Ext for App).
  • The H heuristic cannot tell a delegating facade from an implementor. Confirm H-only rows by reading.
  • The G regex misses multi-line impl headers.

Output size: under 1 KB per touched trait. [V]

6. Recipe 4: Module and crate boundary guards

Answers:

  • new crate→crate import edges (tip minus base, whole repo);
  • new import rows in changed files, checked against a per-repo rules file (allow-lists and deny patterns);
  • new intra-crate module edges (crate::a → crate::b);
  • two grep fallbacks, because imports alone miss edges:
    • (a) inline qualified paths (other_crate::x::Y with no use);
    • (b) Cargo.toml dependency changes, checked against the allow-list.

--audit checks every tip import against the rules. Use it to validate the rules themselves.

Command: python3 $R/boundary.py out/<id>.env out/<id>.defdiff.json $R/rules/<repo>.rules [--audit] [--measure]. Standalone SQL: sql/crate-edges-delta.sql (run with --all).

Boundaries inferred from the repos:

  • spacecraft (rules/spacecraft.rules):
    • allow-lists per crate, from AGENTS.md "Dependency Graph";
    • warpspace_core must not import domains/combat/hero/client (crates/warpspace_core/CLAUDE.md: "Dependency floor");
    • warpspace_combat must not import hero/client (crates/warpspace_combat/CLAUDE.md).
  • dirigent (rules/dirigent.rules):
    • dirigent_acp_api must not depend on dirigent_core or dirigent_archivist (acp_api and core CLAUDE.md);
    • dirigent_logs depends on no dirigent crate;
    • dirigent_diff has no Dioxus;
    • dirigent_web must not import OpenCodeClient (docs/architecture/rules.md);
    • dirigent_appservice has no workspace path deps.

Sample output (sc-replay):

new crate edges: 1
   + warpspace_replay -> warpspace_sync_core  (4 imports, e.g. crates/warpspace_replay/src/speed.rs)
crate edges seen only as inline paths in changed files (no import row):
   ~ warpspace_replay -> warpspace_protocol  e.g. crates/warpspace_replay/src/harness.rs:583  [NEW: not in base imports or base inline paths]
Cargo.toml workspace-dep changes: ['+warpspace_replay in crates/warpspace_debug/Cargo.toml', '+warpspace_sync_core in crates/warpspace_replay/Cargo.toml', '+warpspace_protocol in …']
   !! warpspace_replay gains dependency warpspace_sync_core, not in documented allow-list
new intra-crate module edges: 3
   + warpspace_client: interaction -> spectator

Findings:

  • Real finding, verified: the replay feature added warpspace_sync_core and warpspace_protocol to warpspace_replay's Cargo.toml. AGENTS.md still lists replay's deps as (warpspace, domains, combat, client, engine). That is documentation drift a reviewer should flag. [V]
  • warpspace_protocol is used only through inline paths such as warpspace_protocol::ids::UnitNetId. The import-based edge check missed it; the inline-path scan caught it. [V]

Measured:

  • Rule audits at tip:

    • spacecraft: 0 violations with the rules as documented, and 4 once replay's documented allow-list is added. Those 4 are exactly the drift above.
    • dirigent: 7 violations, all false: dirigent_appservice importing its own lib by crate name. I fixed this by skipping dst == src, but did not re-run after the fix. [I: post-fix count expected 0]
  • New-import detection, file level: compared with files whose diff adds a use line, recall per range was 100 % (sc-replay), 85 % (sc-unify), 75 % (dg-tokens), 91 % (dg-acp), 90 % (dg-trash) and 100 % (dg-archivist). [V] The misses were:

    • use inside fn bodies: dg-tokens archivist.rs (a use inside a cfg(feature) fn), dg-move coordinator/tests.rs;
    • use lines that were only reformatted (sc-unify).
  • Which use statements become import rows (dirigent @ cd4e8f70, all 5,547 use lines):

    where the use line sits recorded
    visible code, module level 2870/3080 (93 %; most misses are probably multi-line groups whose row line differs [I])
    visible code, nested (fn body or inline mod) 337/357
    inside #[cfg(test)] regions 0/621
    files gated at a parent mod, top level 843/897
    files gated at a parent mod, nested 0/350
    cfg(feature) / other regions 192/289

    This refines phase 1's "imports are kept in gated files": that holds at top level of file-gated modules, but not inside #[cfg(test)] mod blocks. [V]

Blind spots:

  • Inline qualified paths. The text scan covers them only in changed files.
  • super::/relative paths are attributed to the file's own crate.
  • Re-exports are not followed, so a dependency through a re-exporting crate shows as an edge to that crate.
  • The compiler already enforces the Cargo graph. The guard is valuable for documented rules beyond Cargo (doc drift, intra-crate layering, "web must not use OpenCodeClient") and for review awareness ("first import from X to Y").

Output size: 1–2 KB. [V]

7. Recipe 5: Blindness preflight

Answers: before any orbit answer is trusted, what can orbit not see for this diff?

  1. Index freshness: manifest sha equals worktree HEAD equals the requested sha, status=indexed, the tree is clean, and skipped_files/errored_files are 0. A dirty tree matters because orbit indexes the working tree (§2).
  2. Changed files with no non-macro definitions, with size and a reason. The script walks up the mod chain to the gating #[cfg]. macro_rules! are indexed even in unreachable files, so they are excluded from the count.
  3. Share of changed lines in cfg-test, cfg-feature, other cfg regions, or gated files.
  4. Cargo features of the touched crates. Orbit evaluates all features off, default features included.
  5. Added lines with constructs CALLS misses: add_systems/observers, rsx!, dyn, and macro arguments.

Command: python3 $R/preflight.py out/<id>.env out/<id>.defdiff.json. Standalone SQL: sql/blind-files.sql.

Sample output (dg-acp, excerpt):

[OK] index tip: manifest=5de6c726 head=5de6c726 dirty=0 skipped=0 errored=0
[PARTIAL] 15/58 changed .rs files have NO definitions in orbit (read/grep them):
      crates/dirigent_core/src/connectors/acp/connector.rs (238 KB) — gated at crates/dirigent_core/src/lib.rs:75 by cfg(feature = "server")
      crates/dirigent_core/src/runtime/mod.rs (176 KB) — gated at crates/dirigent_core/src/lib.rs:58 by cfg(feature = "server")
      crates/dirigent_protocol/src/adapters/opencode.rs (57 KB) — gated at crates/dirigent_protocol/src/adapters/mod.rs:5 by cfg(feature = "adapters")
[PARTIAL] 4539/6351 changed lines (71%) are in code orbit cannot see; unattributed: cfg-test=3176, file-has-no-defs=1363, import=25, …
[INFO] added lines with CALLS-blind constructs: assert!/matches!/vec! args (missed calls): 302

Measured [V]:

range changed .rs files with no defs changed lines orbit cannot see
sc-unify 2/39 111/1725 = 6 %
dg-archivist 3/32 1192/5203 = 23 %
dg-trash 4/17 290/1273 = 23 %
sc-replay 35/72 (all warpspace_testing/src/tests/*) 733/2606 = 28 %
dg-tokens 3/17 373/1075 = 35 %
dg-move 3/8 191/466 = 41 %
dg-acp 15/58 4539/6351 = 71 %
  • Reason lookup: the reason given for each blind file matched the actual gate in every case I checked. Examples:
    • connector.rs → lib.rs:75 #[cfg(feature = "server")] pub mod connectors;
    • dirigent_web/src/server.rs → main.rs:27
  • The cfg-region scanner is text-based (brace matching). It handled struct-field-level #[cfg], multi-line fn signatures and #![cfg] file attributes after two fixes, and gives the same categories as the definition-based check. [V]

Output size: 1.3–2.8 KB. [V]

8. Recipe 6: Compact context for the reviewer

Answers: the shape of the touched area as one line per type or callable, marked with change ops, within a byte budget. Changed items always show. Unchanged items show only for small files; otherwise they collapse to a count and a pointer (orbit grep --path <file>). Non-test files come first.

Command: python3 $R/context.py out/<id>.env out/<id>.defdiff.json [--budget 8000]

## crates/dirigent_archivist/src/coordinator/meta.rs  (4 changed / 18 items)
~   pub async fn cleanup_empty_sessions(&self, archive: Option<String>) -> Result<(usize, usize)>  L456
+   pub async fn storage_usage(&self) -> Vec<ArchiveStorageUsage>  L583
  … 14 unchanged items (orbit grep --path crates/dirigent_archivist/src/coordinator/meta.rs)
## crates/dirigent_archivist/src/backend/traits.rs  (2 changed / 54 items)
+   async fn trash_session(&self, scroll_id: Uuid, trash_dir: &TrashDir) -> Result<TrashOutcome>;  L117

Measured and compared [V]:

  • context.py produced 5.3 KB (dg-trash), 8.3 KB (dg-tokens), 9.9 KB (sc-replay) and 10.5 KB (dg-acp) at budget 8000.

  • orbit repo-map api over the directories touched by dg-trash is 206 KB even after squeezing table padding. The raw output is about 3× larger.

  • repo-map api <file.rs> returns (no matching definitions under <file>/). It appends / and accepts directory prefixes only; use orbit grep --path <file> for a single-file outline. [V]

  • Signature quality in repo-map api (754 entries in dirigent_archivist/src, 367 in dirigent_protocol/src):

    • full signature: 71 % and 92 %;
    • truncated at (: 18 % and 3 %;
    • name only: 9 % and 4 %;
    • garbage: 2 % and 0 %. These are let … lines shown as signatures, e.g. Function let base = format!("{stamp}-{suffix}"); [L438].

    context.py re-reads declarations from source and is not affected.

  • orbit repo-map class <Trait> is a good one-off for a touched trait: 21 methods with signatures. Two multi-line signatures showed only the name (trash_session, replace_session_messages).

Output size: 5–10.5 KB at the default budget (about 1.3–2.6k tokens). [V]


9. End-to-end "orbit review pass"

This is written for Spire's search agent or an ad-hoc Opus reviewer. One command runs steps 1–7: review-pass.sh <repo> <base> <tip> <name> [rules]. It was tested at 30 s and 16 KB for dg-tokens.

  1. Pick the range. For a branch, use base = git merge-base main <branch> and tip = <branch>. For an uncommitted Athanor diff, use tip = the working tree: index the checkout into an isolated DB, noting the dirty flag. For a merged PR, use <merge>^1..<merge>.
  2. Set up: review-setup.sh. Stop condition: skipped_files > 0 or errored_files > 0 → re-index. If it persists, list the skipped files as blind.
  3. Preflight (recipe 5). Decide the trust level:
    • blind share under about 25 % → orbit-led review with grep on the listed gaps;
    • 25–60 % → orbit for the visible part only; read the blind files directly;
    • over 60 % (dg-acp) → stop using orbit for this review except for recipes 1 and 4. Say so in the report.
  4. Changed definitions (recipe 1). Compare against the plan: is every planned item present, and is anything present that was not planned? Read every S row, every removed row, and every enum or struct with member changes.
  5. Blast radius (recipe 2) for S/-/member-changed rows. For each OUTSIDE-diff dependent, ask whether it needed a change it didn't get. Then run the grep for each gap class the preflight flagged:
    • added enum variant → grep every match on it;
    • changed Bevy system signature → check it is still registered;
    • removed item → confirm the old path is re-exported or gone.
  6. Trait coverage (recipe 3) for touched traits. Every implementor marked !! or [G] gets a read.
  7. Boundary guard (recipe 4) with the repo's rules file. Also compare any new crate edge or Cargo.toml dependency against the documented graph.
  8. Context (recipe 6) goes into the reviewer brief together with the plan.
  9. Report:
    • Fact = something orbit states and grep or a file read confirmed. Examples: "X signature changed", "implementor Y does not define new method m (checked in file:line)".
    • Hypothesis = any orbit count or absence ("no other callers", "no implementors", "no boundary crossing") that has not been grep-closed. Label it "lower bound (orbit)" and name the gap classes that apply.
    • Blind = the preflight's blind files and share, stated once at the top of the review.
    • Log one feedback record per orbit result that changed a decision, and one per result that proved wrong (§10).
  10. Teardown: review-teardown.sh <repo> <name>. Check with git worktree list and pgrep -a orbit.

Stop conditions (any one stops the pass):

  • the preflight blind share is above 60 %;
  • the index is not fresh or not clean;
  • the plan's key items are all in blind files;
  • the question is behavioural (runtime ordering, Bevy schedule, async). Orbit shows structure, not runtime.

10. Feedback record format (for the prototype skill)

Format: append-only JSONL, one line per orbit query whose result was used, or should have been used, in a decision. It must be cheap to write: one shell call, and the helper fills in the context.

{"ts":"2026-09-29T18:42:48+02:00","repo":"dirigent","commit":"d3f99fb7","dirty":false,"orbit":"0.133.0",
 "agent":"search","recipe":"blast","purpose":"callers of format_token_title","cmd":"blast.py dg-tokens",
 "rows":0,"verdict":"grep-needed","gap":"rsx","note":"both call sites inside rsx! block; git grep found them"}
field required meaning
ts, repo, commit, dirty, orbit auto filled in by the helper. repo is resolved through git-common-dir, so worktrees log under the main repo name
agent env ORBIT_AGENT search, reviewer, researcher, …
recipe yes defdiff, blast, traitcov, boundary, preflight, context, adhoc-sql, grep, repo-map, context-cmd
purpose yes the question in a few words
cmd yes the orbit command or script call
rows yes result size (rows, or items reported)
verdict yes useful, wrong, incomplete, grep-needed, noise, unused
gap optional cfg-test, cfg-feature, gated-file, dyn, fn-ref, macro, rsx, cross-file-impl, external-trait, inline-path, pattern-match, stale-index, sig-truncated, other
note optional one sentence. Say what grep or reading found that orbit didn't, or vice versa

Helper: tested; it appended exactly the record above.

ORBIT_AGENT=search ~/.cache/orbit-probe/recipes/fb.sh blast grep-needed "callers of format_token_title" "blast.py dg-tokens" 0 "both call sites inside rsx! block; git grep found them" rsx

Where it lives (options):

  1. Default: ~/.local/share/orbit-skill/feedback.jsonl. Central, outside every repo, and survives cache cleaning. One file keeps mining across spacecraft and dirigent to a single jq call. ORBIT_FEEDBACK overrides it.
  2. Per repo: <repo>/local/orbit-feedback.jsonl. local/ is git-ignored in both spacecraft (.gitignore:22 /local/**) and dirigent (local/.gitignore: *). It is visible to the user in the project. Worktrees have no local/, so the helper would have to resolve to the main checkout.
  3. Tracked: raw logs are not recommended. The mined summaries belong in reliquary/docs/workpad/research/…/ (for example a later "phase-4 field feedback" report).

Mining [I: not built]. Example: jq -s 'group_by(.recipe, .verdict) | map({recipe: .[0].recipe, verdict: .[0].verdict, n: length})' feedback.jsonl, and group_by(.gap) for the gap histogram.

11. Questions only real usage can answer (the feedback log should capture these)

  1. Does the pass change review outcomes? How often does an OUTSIDE-diff dependent (recipe 2) or an [G]/!! implementor (recipe 3) lead to a real finding, compared with noise? (verdict=useful vs noise per recipe)
  2. Blast radius on compiled code: since the tip compiles, how often does "caller outside the diff" matter at all? When it does, is it cfg-gated code the compiler never built? Real usage can record builds the probe was not allowed to run.
  3. Which gap classes dominate in day-to-day diffs, as opposed to these 7 historical ranges? In particular: Bevy fn refs in spacecraft, rsx! in dirigent_web, server gating in dirigent_core. (gap histogram)
  4. Where to set the preflight thresholds: 25 % and 60 % are guesses. Record the blind share and whether the review still found orbit useful.
  5. Uncommitted review: does indexing the live checkout (dirty working tree) into an isolated DB work reliably while other sessions edit the same checkout? Are there races with half-written files? (dirty=true records)
  6. Is the boundary rules file worth maintaining? How often does it catch drift, compared with how often it needs editing? (recipe=boundary)
  7. Token budget: is about 16 KB of pass output the right size for an Opus reviewer? Which sections do reviewers actually quote? (verdict=unused on sections)
  8. Agent fit: can Spire's Sonnet search agent run the pass and relay only the facts and hypotheses, or does the reviewer need the raw output? (agent field)
  9. Moves and renames: > is untested. Do real refactors produce same-fqn moves, or mostly cross-crate -/+ pairs?
  10. Feature-on indexing: if orbit ever gains a features flag, how much of the blind share goes away? Until then, is a text-only fallback good enough for server-gated dirigent code?
  11. Staleness in the wild: how often do reviewers hit a stale or dirty index in practice? (gap=stale-index)

12. Corrections and additions to phase 1

  • Orbit indexes the working tree (uncommitted edits and untracked non-ignored files), and the manifest records HEAD's sha. Phase 1's "uncommitted edits are not visible [I]" is wrong. [V]
  • Imports inside #[cfg(test)] mod blocks are not recorded (0/621). Neither are nested use statements in gated files (0/350). Phase 1's "imports kept in gated files" holds only for top-level use in files gated at the parent mod. [V]
  • Inline qualified paths are not import rows. Crate-edge matrices from gl_imported_symbol undercount. [V]
  • repo-map api needs a directory prefix. A file path returns empty silently. About 2 % of its "signatures" are let statements. [V]
  • No CALLS edges target Constant/Static. Constant reads are not edges. CALLS targets seen at tip: Function, Struct, Variant, Method, Field, AssociatedFunction, Macro, plus Python kinds. [V]

13. Scripts and files (~/.cache/orbit-probe/recipes/)

file recipe purpose
review-setup.sh setup detached worktrees for base and tip, index both with -t 4 --db <name>.duckdb, assert skipped_files==0, print the env file
review-teardown.sh cleanup git worktree remove for both, prune, delete the DB
review-pass.sh end-to-end setup plus recipes 1–6, prints in reading order
ob.py, regions.py lib orbit sql wrapper (fails on "not indexed" fallback); cfg-region scanner
defdiff.py 1 changed definitions, signature deltas, unattributed-line tally, JSON for the other recipes
gt_items.py 1 (eval) regex ground truth for added and removed items
blast.py 2 dependents in/outside the diff, re-export check for removed items, --measure grep ground truth
traitcov.py 3 E/G/H implementors, new-method coverage, text fallback
boundary.py, rules/{spacecraft,dirigent}.rules 4 crate and module edge delta, rules, inline-path and Cargo.toml fallbacks
preflight.py 5 blindness preflight
context.py 6 budgeted API brief
fb.sh feedback JSONL append helper
sql/hunk-to-defs.sql, dependents.sql, importers.sql, implementors.sql, crate-edges-delta.sql, blind-files.sql 1, 2, 2, 3, 4, 5 standalone SQL, each tested against a real DB

Phase-1 scripts (calls.py, ext.py, defdiff.sql, …) remain in ~/.cache/orbit-probe/.

14. Hygiene report

  • Worktrees: 16 detached worktrees were created under ~/.cache/orbit-probe/worktrees/. All were removed with git worktree remove plus git worktree prune. At the end, git worktree list shows:
    • spacecraft: main and the user's .worktrees/strategy-mvp;
    • dirigent: the main checkout only.
  • Isolated DBs: eight per-review DBs (*.duckdb) and one throwaway dirty-tree DB were deleted.
  • Shared DB: ~/.gitlab/orbit/graph.duckdb is unchanged: same size (72,626,176 bytes), same mtime (17:23:09) and same sha256 c9784dbe… before and after. The wt-spacecraft entry from phase 1 is still there. I did not touch it.
  • Scratch worktree edits: the dirty-tree test edited only my own scratch worktree. It was reverted (git checkout -- <file> inside that worktree, and removal of the untracked file) before the worktree was removed. No repository refs or files were modified.
  • Processes: no compilation was run. pgrep -a orbit found no orbit process at the end.
  • Scratch size: ~/.cache/orbit-probe/ is 1.2 MB (scripts plus text outputs in out/). Nothing was written to /tmp.