Files
reliquary/docs/workpad/research/2026-09-29-17-orbit/phase-1-rust-capabilities.md
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

34 KiB
Raw Permalink Blame History

phase, stream, date, orbit-version, test-beds, status
phase stream date orbit-version test-beds status
1 rust-capabilities 2026-09-29 0.133.0 (~/.local/bin/orbit, embedded skill "orbit-cli" v0.18.2) spacecraft (Bevy game, 25-crate workspace, 903 .rs files), dirigent (Tokio/Dioxus server+web, 35-crate workspace, 989 .rs files) done

Orbit 0.133 on Rust: capabilities probe

Legend: [V] = verified here with command output. [I] = inferred (reasoned from evidence, not tested directly).

Scratch scripts used for the measurements are in ~/.cache/orbit-probe/ (calls.py = CALLS recall against grep, cfgcheck.py = cfg-gated item presence, ext.py = impl-for against EXTENDS, defdiff.sql, impl-heuristic.sql). Phase 2 can reuse them.

TL;DR

  • Orbit understands Rust well where code is visible. Name resolution is type-aware, and it follows use paths across workspace crates. Recall for "who calls X" was 85–100 % for free functions, Type::new-style associated functions and methods on concretely typed receivers. It even resolved calls through match bindings on enum payloads. [V]
  • It does not see everything. cfg is evaluated with every Cargo feature off (default features included), test off and debug_assertions off. Feature-gated modules drop out entirely. In dirigent the whole server feature is invisible: dirigent_core/src/runtime/mod.rs (180 KB) and config.rs (105 KB) have 0 definitions, and 176 of 989 Rust files have none. #[cfg(test)] mod tests never appears, so unit tests are invisible. Only tests/ integration tests are indexed. [V]
  • Trait graph is partial. impl Trait for Type gives EXTENDS Type→Trait only when the impl block is in the same file as the type (60/62 hits same-file, 0/10 cross-file). Supertraits work. External traits (Plugin, Display, Serialize, …) give nothing, and the result is silently empty (216 impl Plugin for in spacecraft). [V]
  • Weak spots for CALLS: calls through dyn Trait (0/4 and 1/12 callers found), calls inside macro arguments (assert!(f(..))), Bevy systems passed to add_systems (they are not calls), and Dioxus components used in rsx! (4 of 260 have any incoming edge). [V]
  • Review use is feasible. Two commits (main plus a detached git worktree) can be indexed at once and diffed at definition level with one --all SQL query. Orbit has no command to delete an index entry, so the worktree's rows stay in the shared DB. [V]
  • Hygiene:
    • Index with -t 4. With the default thread count the per-file watchdog skipped 4–7 large files per run, a different set each time.
    • Check processing.skipped_files in the index JSON.
    • Leave orbit sql at its default scope. MCP run_sql is always unscoped. [V]

Probe context and state notes

  • Pre-existing entries. These belong to the user, who had indexed the repos before this probe. They are not leftovers from the interrupted attempt, and I removed none of them. All had status indexed; there were no indexing/error rows. At probe start (17:12 local), each indexed commit compared with the repo's HEAD as follows [V]:

    repo_path indexed commit (last_indexed_at, UTC) HEAD at probe start stale?
    ~/Projects/spacecraft 3ff5f27 (14:37:01) 8d9ec30a (main) yes
    ~/Projects/dirigent dd8dfbb2 (14:36:49), branch feature/w1c-live-acp 5de6c726 (same branch) yes
    ~/Projects/sandcage 2e70b44 (14:37:22) 2e70b44 (main, dirty worktree: 6 modified docs) no; uncommitted edits are not visible to orbit [I]

    Staleness detection on the stale spacecraft entry (before any re-index) [V]:

    • orbit repo-map → Error: current commit 8d9ec30a… is not indexed … run: orbit index . (hard fail).
    • orbit sql "select count(*) from gl_definition" → note on stderr: "commit 8d9ec30a … is not indexed; querying every indexed commit (as with --all)". It returned 34881, the rows of all three repos.
    • orbit grep new → header grep "new" @ 8d9ec30a and results, with no warning.
    • orbit context <file> → returned a file map with no warning.

    I then re-indexed spacecraft and dirigent at their HEADs. Re-indexing a path replaces that path's entry. The entries are now spacecraft@8d9ec30a and dirigent@8c64bfd2 (dirigent got a new commit from another session during the probe). The "writes into the repo" check below is therefore a re-index check on existing entries, plus a first-index check on a fresh worktree (§10).

  • Concurrency. Other sessions were active during the probe. spacecraft was re-indexed by someone else at 17:12 local, between two of my queries. dirigent received a commit (8c64bfd2) and a file edit. The strategy-mvp worktree in spacecraft moved. The DuckDB file ~/.gitlab/orbit/graph.duckdb is shared by every agent on the host, and one row per repo_path is overwritten on re-index. [V]

  • orbit index writes nothing into the repo. Evidence:

    • spacecraft (re-index): git status --porcelain --ignored was identical before and after.
    • Scratch worktree (first index of a fresh checkout): identical, and no new files appeared.
    • dirigent: the only diffs came from the concurrent session (a test file edit, later committed).
    • All output goes to ~/.gitlab/orbit/graph.duckdb, which grew from 46.9 MB to 70 MB. [V]

1. CLI surface: what is new since 0.124

orbit --help (0.133.0) lists these subcommands: version index grep context sql schema list mcp repo-map skills setup uninstall query status ontology dsl tools graph-status config. [V]

Area 0.124 guide 0.133 (verified from --help and use)
index orbit index . -t/--threads (0 = auto), -s/--stats (per-file and per-language timings, skipped files), -v, --db PATH. PATH may be a directory that holds several repos. Prints JSON stats, including processing.skipped_files.
grep several queries, ranked One query; 'a|b|c' for OR. --repo; repeatable --path with globs; --kind; omit the query with --path to get a file outline. Each result is labelled exact-name / name/path / body-only ×N and shows the first body line that matched.
context --tests flag --tests removed (error: unexpected argument). Test/fixture/generated connections always print in their own section. Accepts Definition:<id>, File:<id>, exact FQNs, paths, path:start-end, directories. --repo.
sql default scope, --all Adds --repo PATH (scope to another checkout) and - to read stdin. New: if the current commit is not indexed, it falls back to --all and prints a note on stderr.
schema whole schema orbit schema gl_definition gl_edge (filter by table), --raw (JSON).
list table -F table|json|ndjson|csv.
repo-map overview/tree/api/class/extends/imports Same subcommands. --repo, --ext. class also covers traits and modules. overview adds "Key abstractions" (most EXTENDS descendants), "Most-imported defined symbols" and "Most-called callables". Refuses to run on an unindexed commit.
new – skills [get orbit [path]]: embedded vendor skill orbit-cli v0.18.2, with references local/cli.md, local/sql.md, local/repo_map.md. setup/uninstall: write instructions, hooks, skill and MCP config into claude/codex/duo/opencode/pi, with --dry-run and --project. config (~/.gitlab/orbit/settings.json, only telemetry.enabled).
new, remote-only – query, status, ontology, dsl, tools, graph-status talk to the remote GitLab Orbit API. Not probed.
Schema – _orbit_manifest now has project_id and parent_repo_path (links a worktree to its main repo). gl_file.reason (skip_excluded_extension, skip_oversize, skip_not_utf8, skip_binary). Byte and char offsets on definitions and imports. gl_edge has no commit_sha.
Env – ORBIT_DATA_DIR appears in the binary's strings [I: untested]. --db exists on every local command.

Changelog: not found locally. There is no changelog subcommand or file, and nothing under ~/.local/share or ~/.cache. The embedded skill says its source is gitlab-org/orbit/knowledge-graph. [V: absence locally]

2. MCP

orbit mcp serve is a stateless stdio server. I checked it with an initialize + tools/list + tools/call pipe (timeout 20). The process exited, and pgrep showed no orbit process left. [V]

  • Server orbit-cli 0.133.0, protocol 2024-11-05. Still only three tools:
    • get_graph_schema(db?)
    • index(path, threads?, stats?, db?)
    • run_sql(sql: string[], db?): now takes an array of statements and returns one result set per statement. "Orbit rejects large result sets."
  • run_sql has no repo/commit scoping. select count(*) from gl_definition gave 52114 rows across 4 commits over MCP, against 17110 with CLI default scope in spacecraft. Every MCP query therefore behaves like --all: filter by commit_sha, or join _orbit_manifest. [V]
  • grep, context and repo-map are still CLI-only. The guide's advice holds: use the CLI through bash.

3. Rust schema

definition_type for .rs files [V]:

type spacecraft dirigent notes
Field 6356 5359 struct fields and enum-variant fields (Variant::0, Variant::reason)
Function 2961 3430 free fns, including #[test] fns in tests/
Struct 2288 954
Method 1272 1960 fns with self, and trait method declarations (fqn Trait::m)
Variant 1270 1931 enum variants
Constant 794 367
Module 642 682 inline mod x {} and mod x;
AssociatedFunction 517 529 fns without self inside impl (new, default, …)
Enum 285 456
TypeAlias 29 62
Trait 18 30
Macro 10 7 macro_rules! only
Static 6 35
  • There is no Impl node. Methods from every impl block (in any file) hang off the type through fqn crate::path::Type::method. [V]
  • A trait impl's methods cannot be told apart from inherent methods: the fqn carries no trait. [V]
  • fqn root is the cargo target name. This covers lib crates, [[bin]] name and integration tests (file stem, e.g. archetypes::…). In spacecraft the root package lib warpspace and the bin warpspace (from warpspace_app) share the root warpspace. Group by file_path, not by fqn prefix. [V]
  • start_line includes doc comments and attributes. Example: Gamemode shows at mod.rs:177, but pub trait is at line 182. [V]

Edges (relationship_kind, source_kind → target_kind) [V]:

edge spacecraft dirigent
DEFINES File→Definition 17110 17152
DEFINES Definition→Definition (nesting) 9253 8718
IMPORTS File→ImportedSymbol 12912 11074
CALLS Definition→Definition 5570 10028
CALLS Definition→ImportedSymbol (unresolved/external) 877 2377
CALLS File→… (top-level) 69 74
CONTAINS Directory→File/Directory 4285 3220
EXTENDS Definition→Definition 43 (31 Rust) 34
  • CALLS targets include Struct (1157 in spacecraft) and Variant (406). These are construction sites: struct literals, tuple-struct and variant constructors. Type mentions in signatures, Query<&T> or generics produce no edge. [V]
  • Proc-macro invocations resolve. hex_color!(…) has 202 grep sites and 200 raw CALLS edges to the #[proc_macro] fn hex_color. macro_rules! invocations resolve at statement level (4/4, 7/7, 3/3). seam! invoked inside vec![…] has 0/63. [V]

4. Traits and impls

  • In-repo trait, implementor in the same file: EXTENDS Struct→Trait. 30/30 in spacecraft; 30/32 in dirigent (two misses in dirigent_server/src/adapters/process.rs, cause unknown). [V]
  • Impl block in a different file from the type: no EXTENDS (spacecraft 0/5, dirigent 0/5). Example: JsonlBackend implements 6 archivist traits in 6 files. Only ArchiveBackend is linked, because that impl is in backend.rs with the struct. The methods are still attached to JsonlBackend (repo-map class JsonlBackend lists them with their files). [V]
  • Extension traits on external types (impl RegisterGameUpdate for App, impl IntoPlayerId for u32): no edge, because the implementing type is not a node. [V]
  • Blanket impls (impl<T> AbilityClone for T): no edge. [V]
  • Supertraits work. trait Ability: Debug + Send + Sync + AbilityClone gives EXTENDS Ability→AbilityClone, so repo-map extends AbilityClone shows Ability at depth 1. External supertraits (Resource, Debug) are dropped. [V]
  • External traits: orbit repo-map extends Plugin prints only the header, with no rows and no warning. spacecraft has 216 impl Plugin for. dirigent has 348 impls of external traits. [V]
  • Enums implementing in-repo traits: neither repo had a case, so this is untested. [I: likely the same same-file rule]
  • Trait-object users are missing from EXTENDS by construction. repo-map extends <Trait> is a lower bound. Cross-check with git grep -n "impl .*<Trait> for", or use the method-name overlap heuristic (cookbook C7). [V]

5. CALLS coverage

Method used: for each identifier, compare the set of enclosing definitions of git grep -w hits (comments, use lines and the definition line excluded) with the set of CALLS sources pointing at the target. Script: ~/.cache/orbit-probe/calls.py.

repo identifier kind grep callers orbit callers recall notes
spacecraft generate_ship free fn, cross-crate 31 30 97 %
spacecraft themed_button free fn 25 25 100 %
spacecraft micro_exec_tick free fn 17 17 100 %
spacecraft border_radius_small method (theme.x()) 40 34 85 % misses inside nested with_children(|p| …) closures and on Res<UiTheme> receivers
spacecraft register_session_resource extension-trait method on App 19 19 100 %
spacecraft validate fn plus same-named method 46 35 76 % misses: assert!(validate(..)), matches!(validate(..)), field.field_type.validate(v)
spacecraft MonitorShared::new assoc fn 1 1 100 %
spacecraft Gamemode::{check_victory, spawn_player_units, room_for_joiner, initial_camera_transform} via Box<dyn Gamemode> 4 0 0 %
spacecraft update_flex_centering Bevy system 2 0 0 % passed to add_systems, not called
dirigent throttled free fn 79 73 92 %
dirigent use_workspace Dioxus hook 41 40 98 %
dirigent show_toast free fn 29 29 100 %
dirigent SchemaValidator::new assoc fn 47 46 98 %
dirigent handle_request method 29 28 97 %
dirigent put_session via Arc<dyn ArchiveBackend> field 12 1 8 % reg.backend.put_session(..).await
dirigent count_messages dyn / field chain 7 1 14 %
dirigent #[component] fns in rsx! Dioxus 260 comps 4 with any edge ~0 %

All rows above are [V].

  • Trait dispatch resolves when the concrete type is known. ReplayMover::update_movement does match self { ReplayMover::Pid(m) => m.update_movement(..) }, and edges go to each concrete impl method (PIDMovement::update_movement, …). They do not go to the trait declaration. [V]
  • Calls to the trait method declaration are rare, so "who calls Trait::m" undercounts generic and dyn callers. [V]
  • Cross-crate CALLS resolve. Cross-crate edges: spacecraft 807 of 4038 crate→crate edges, dirigent 613 of 8810. [V]
  • Default orbit sql scope deduplicates edges to one per (source, target). --all shows one edge per call site: border_radius_small has 34 edges scoped against 45 with --all, and hex_color 4 against 200. repo-map overview "callers" counts raw call sites. [V]
  • Share of callables with no incoming CALLS:
    • spacecraft: Function 1840/2961, Method 1019/1272, AssociatedFunction 405/517.
    • dirigent: Function 1848/3430, Method 1145/1960, AssociatedFunction 222/529.
    • Most of these are Bevy systems, trait impl methods, handlers and mains. "No callers" is not "dead code". [V]

6. Imports (gl_imported_symbol)

  • import_type values for Rust [V]:
    • Use: use a::b::C;
    • UseGroup: use a::{B, c::D}; nested groups are flattened to full paths.
    • GlobUse: use bevy::prelude::*; import_path = module, identifier_name empty.
    • AliasedUse: as.
    • PubUseGroup, ReExport, ReExportGlob, ReExportAliased: the pub use forms.
    • ModDeclaration: mod x; also lands here.
    • ExternCrate.
  • Paths are raw and not normalised. crate::theme::widgets::themed_button, super::theme::widgets::themed_button, super::widgets::themed_button and edition-2018 relative game_states::is_client_world all appear as written. Cross-crate imports carry the crate name (warpspace_engine::game_states::InSession). To match a symbol, use identifier_name, or import_path LIKE '%::name'. [V]
  • Imports are not resolved to definitions. context labels them external/unresolved, even in-repo ones. There is no ImportedSymbol→Definition edge. Re-export chains (pub use) are not followed. [V]
  • repo-map imports PATTERN returns aggregated counts per (symbol, import_path), not the file list. Use SQL for the files (cookbook C4). [V]
  • Imports are kept in files whose definitions were dropped (cfg-excluded modules, #[cfg(test)] files, vendor/). Example: runtime/mod.rs has 37 import rows and 0 definitions. An import matrix therefore includes feature-gated and test code, while definitions and CALLS exclude it. [V]
  • Crate-to-crate import matrix works (dirigent: dirigent_web→dirigent_api 217, dirigent_core→dirigent_protocol 201). [V]

7. Workspaces, crates, tests

  • All workspace members are indexed. spacecraft: 25 crates plus the root package. dirigent: 35 crates. The file set is git-tracked files only (903 of 2424 .rs files on disk in spacecraft; target/ and untracked files are ignored). [V]
  • Code outside the workspace crate trees gets no definitions, only imports and macro_rules. spacecraft vendor/ (88 tracked .rs files of vendored bevy_replicon/lightyear) has 5 definitions, all macros. archive/*.rs has 0. [V: counts; I: exact rule, probably module-tree reachability]
  • Tests [V]:
    • #[cfg(test)] mod tests { … } (144 inline in spacecraft, 389 in dirigent) and files pulled in by #[cfg(test)] mod tests; (e.g. all of warpspace_testing/src/tests/*.rs) have 0 definitions. fqn LIKE '%::tests::%' returns 0.
    • crates/*/tests/*.rs integration tests are indexed. Their fns show up in context under "Test, fixture, or generated connections". Example: generate_ship has 30 test callers from archetypes::…, determinism::….
    • Integration test files with #![cfg(feature = "…")] have 0 definitions.
    • context --tests no longer exists.
  • cfg evaluation. Features are all off, including default = [...]. debug_assertions off, test off, target_os = linux/unix on.
    • Evidence:
      • cfg(not(feature="server")) items are kept, cfg(feature="server") items dropped.
      • Of the four platform_id variants, only the cfg(all(unix, not(macos))) one is kept.
      • cfg(debug_assertions) consts and fns are dropped.
      • spacecraft update_steam_status (feature="steam", a default feature) is dropped.
    • #[cfg] on methods inside an impl block is not evaluated. Example: set_client_latency under a non-default feature is kept. [V]
    • Scale: files with zero definitions are spacecraft 212/903 (2.9 of 11.6 MB) and dirigent 176/989 (3.0 of 13.6 MB). Test files are included in those counts. The largest non-test blind spots in dirigent are dirigent_core/src/runtime/mod.rs, config.rs, connectors/gateway/mod.rs and connectors/opencode.rs, dirigent_api/src/acp_controller.rs, and dirigent_web/src/server.rs. Most of dirigent's backend sits behind server. [V]
    • No flag, config key or env var to enable features was found in --help, orbit config list or strings. [I]

8. repo-map quality and timings

step spacecraft dirigent
orbit index . -t 4 4.4–4.7 s, 0 skipped 5.3 s, 0 skipped
orbit index . (auto, 20 cores) 7.7–8.4 s, 4–7 files skipped (timeout_sentinel, "per-file watchdog killed analysis") 5.7 s, 0 skipped
orbit index . -t 1 7.2–8.0 s, 0 skipped –
repo-map overview 0.67 s, 132 lines 0.62 s, 141 lines
repo-map tree <dir> – 0.54 s

All timings are [V]. The skipped files were 700–2000-line Bevy UI/lab files. A watchdog skip under default threads silently drops those files' definitions. The first index I saw (by another agent) had 16456 definitions, against 17110 with no skips.

  • overview: useful.
    • Per-crate files/types/callables table.
    • "Key abstractions": LabScene 15, MovementBehaviour 6, Gamemode 3, EditTarget 3. It misses ArchiveBackend because of the cross-file impl gap.
    • "Most-imported defined symbols" (PlayerId 83, InSession 74, UiTheme 57) is a good anchor list for review context.
    • "Most-called callables" counts call sites and mixes in Python scripts.
    • The Languages row "unknown 2853" is noise from md/toml/assets.
  • tree <path>: good. Example: Enum ArchiveCapability @L9 / TypeAlias CapabilitySet @L20 / Trait ArchiveBackend @L44 ….
  • class <Name>: good for structs and traits.
    • It lists fields and methods across impl files, with locations.
    • Signatures are one-line extracts. Multi-line signatures truncate to fn apply_command( or async fn append_messages(, and some show only the name (delete_session_if_unchanged, trash_session).
  • extends <Trait>: correct depth tree, subject to the same-file and in-repo limits (§4).
  • imports <pattern>: counts per import path (§6).
  • api <path>: same data as tree plus signatures, with the same truncation. [I: not sampled separately; the signature extractor is shared with class]
  • grep: much better than 0.124. Match classes make exact-name hits obvious (put_session → trait declaration plus JsonlBackend::put_session, then body-only hits with the matching line). grep --path file.rs with no query prints an outline. It still ranks, caps with "… N more", and does not search excluded code (cfg/test). [V]

9. The guide's §7 gotchas for Rust

Guide gotcha Rust / 0.133 status
7.1 worktree duplication Carries over for --all. Worse for MCP run_sql, which is always unscoped [V]. Also new: --all edges are per call site, while default scope deduplicates them [V].
7.2 dynamic calls invisible Much smaller in Rust. Concrete receivers resolve at 85–100 %. It still applies to dyn Trait / Arc<dyn …> field receivers (0–14 %), macro arguments, Bevy systems registered by fn reference, and Dioxus rsx! components [V].
7.3 imports only via statements Carries over. Paths are also raw (crate::/super::/relative), not resolved, and re-exports are not followed [V].
7.4 third-party bases not nodes Carries over for external traits (silently empty) [V]. New: cross-file impls give no EXTENDS even for in-repo traits [V].
7.5 grep ranked, not exact Still true for counting. Match classes make exact-name lookups reliable. Excluded code (cfg-off, #[cfg(test)]) is never searched [V].
7.6 no type/decorator semantics Mostly carries over. repo-map class shows (truncated) signatures. Type mentions (fn params, Query<&T>, generics) are not edges. #[derive] and attributes are not modelled [V].
7.7 stale index, silent Changed. repo-map refuses on an unindexed commit. sql falls back to all commits with a stderr note, which is easy to miss in scripts and mixes repos [V]. grep printed @ <new sha> against an unindexed commit without a warning [V; what it searched is I]. Re-indexing the same path replaces the old commit's entry [V].
7.8 ticket numbers Not orbit-specific. Skipped.

New Rust-specific gotchas

  1. cfg blindness. All features off, test off, debug_assertions off. Feature-gated modules, whole crates' backends, and all #[cfg(test)] unit tests have no definitions and no CALLS. A review of feature-gated code must use git grep/read the files. Check gl_file rows with zero definitions before trusting a "no callers" or "not found" result (cookbook C6). [V]
  2. Watchdog skips under default threads. Index with -t 4. Assert processing.skipped_files == 0 in the JSON. [V]
  3. EXTENDS only for same-file impls, in-repo traits and in-repo types. "Implementors of X" is a lower bound. [V]
  4. dyn Trait dispatch has no callers. For trait methods, search call sites with orbit grep '<method>' (body-only hits) or git grep '\.method('. [V]
  5. Macros. Calls inside macro arguments are missed (assert!, matches!, vec![seam!(..)], rsx!). Statement-level macro_rules! and proc-macro invocations do produce CALLS. [V]
  6. ECS / framework wiring is not calls. Bevy add_systems(Update, f), observers and Dioxus components give no incoming edges. [V]
  7. CALLS→Struct means construction, not usage. "Who uses component Health": 4 of 52 enclosing definitions found. Use repo-map imports or SQL on imports, or grep. [V]
  8. fqn root is the cargo target name, and collides (lib vs bin warpspace). Integration tests are rooted at the file stem. Group by file_path. [V]
  9. start_line includes doc comments and attributes. This helps when mapping diff hunks to definitions, but a hunk touching only a doc comment counts as touching the item. [V]
  10. Default scope deduplicates edges; --all does not. Do not compare counts across the two. [V]
  11. SQL papercuts:
    • target is a reserved word in DuckDB (AS target fails); use tgt.
    • An empty result prints nothing (no header), exit 0.
    • zsh loops: don't name a variable path. That is a shell issue, not orbit. [V]
  12. Shared DB and concurrent agents. One graph.duckdb per user. Other sessions re-index the same repos, and repo_path rows are replaced. --db <file> isolates cleanly. orbit index . -t 4 --db ~/.cache/orbit-probe/scratch.duckdb built a 22.8 MB DB with 17110 definitions. list, sql and repo-map extends with the same --db worked, rm of the file was the whole cleanup, and the shared DB was untouched (still 4 entries). [V] ORBIT_DATA_DIR is untested. [I]

10. Multi-commit / worktree comparison

Steps [V]:

  1. git -C ~/Projects/spacecraft worktree add --detach ~/.cache/orbit-probe/wt-spacecraft bfbbcd96. This commit is 4 commits behind main, with 6 .rs files changed (+363).
  2. orbit index . -t 4 inside the worktree: 3.8 s, 17074 definitions. git status --porcelain --ignored was unchanged.
  3. _orbit_manifest gained a row with repo_path=/home/g4b/.cache/orbit-probe/wt-spacecraft, parent_repo_path=/home/g4b/Projects/spacecraft, its own project_id, and branch='HEAD detached at bfbbcd96'.
  4. Definition IDs are unique per commit: 0 shared IDs between the two commits. Join across commits on (fqn, definition_type, file_path).
  5. orbit sql --repo <worktree> … and orbit repo-map --repo <worktree> … scope to that commit.
  6. orbit sql --all -f defdiff.sql (cookbook C8) listed exactly the expected changes:
    • added: warpspace_ui::replay_browser::* (plugin, structs, fns), MenuAction::OpenReplay, MenuState::ReplayBrowser, warpspace::start_replay_session, auto_start_replay, Args::replay
    • resized: handle_menu_actions 70→82, main 248→264, spawn_main_menu_root 181→188
    • Python script adds and removes in the same range
  7. Re-creating the worktree at another commit and re-indexing the same path replaced that path's entry.
  8. Cleanup: git worktree remove + git worktree prune. git worktree list is back to main + .worktrees/strategy-mvp.

What is left over:

  • Orbit keeps the index entry. orbit list still shows /home/g4b/.cache/orbit-probe/wt-spacecraft @ bfbbcd96, with 17074 definitions in the DB.
  • There is no CLI command to remove an entry: no prune/forget/remove in --help. orbit index <deleted path> fails with "failed to find git top-level".
  • Removing the entry would need direct DuckDB surgery or a separate --db. I did neither, because the DB is shared.
  • Scratch files in ~/.cache/orbit-probe/ (about 250 KB of scripts and outputs) remain on purpose for phase 2. No scratch DB.

Implication for the skill: branch-vs-main review works well at definition level. Only length changes are detected, so pair it with git diff -U0 hunks for body edits. Worktree entries accumulate in the shared DB, so a dedicated --db per review is the clean pattern. --db isolation and deleting the file afterwards were verified (gotcha 12). Indexing two commits into one scratch DB was not tried, but it should behave like the shared DB [I].

Tested cookbook

All of these ran on the repos above. Default scope unless marked.

# Index (always check skipped_files)
orbit index . -t 4 | grep -E '"definitions"|"skipped_files"'
orbit list

# Orientation
orbit repo-map                                  # overview
orbit repo-map tree crates/dirigent_archivist/src/backend
orbit repo-map class JsonlBackend               # fields + methods across impl files
orbit repo-map extends MovementBehaviour        # same-file in-repo implementors (lower bound)
orbit repo-map imports themed_button            # counts per import path
orbit grep put_session --kind Method --path crates/dirigent_archivist
orbit grep --path crates/dirigent_archivist/src/backend/health.rs   # outline
orbit context Definition:<id>                   # source + callers/callees + test callers

C1. Definition types (Rust only)

SELECT d.definition_type, count(*) n
FROM gl_definition d JOIN gl_file f ON f.path = d.file_path
WHERE f.language = 'rust' GROUP BY 1 ORDER BY 2 DESC;

C2. Callers of one definition (distinct callers under default scope; add --all plus a commit filter to count call sites)

SELECT s.fqn AS caller, s.file_path, s.start_line
FROM gl_edge e
JOIN gl_definition s ON s.id = e.source_id
JOIN gl_definition t ON t.id = e.target_id
WHERE e.relationship_kind = 'CALLS'
  AND t.fqn = 'dirigent_web::components::toast::show_toast'
ORDER BY s.file_path;

C3. Cross-crate call matrix

SELECT split_part(s.file_path,'/',2) src_crate, split_part(t.file_path,'/',2) dst_crate, count(*) n
FROM gl_edge e JOIN gl_definition s ON s.id=e.source_id JOIN gl_definition t ON t.id=e.target_id
WHERE e.relationship_kind='CALLS' AND s.file_path LIKE 'crates/%' AND t.file_path LIKE 'crates/%'
  AND split_part(s.file_path,'/',2) <> split_part(t.file_path,'/',2)
GROUP BY ALL ORDER BY n DESC;

C4. Files importing a symbol; crate import matrix (includes cfg-off and test files)

SELECT file_path, import_type, import_path FROM gl_imported_symbol
WHERE identifier_name = 'themed_button' ORDER BY 1;

SELECT split_part(i.file_path,'/',2) from_crate, split_part(i.import_path,'::',1) to_crate, count(*) n
FROM gl_imported_symbol i
WHERE i.file_path LIKE 'crates/%' AND i.import_path LIKE 'dirigent_%'
  AND split_part(i.file_path,'/',2) <> split_part(i.import_path,'::',1)
GROUP BY ALL ORDER BY n DESC;

C5. Trait implementors via EXTENDS (lower bound)

SELECT s.fqn implementor, s.definition_type, t.name AS trait_name, s.file_path
FROM gl_edge e JOIN gl_definition s ON s.id=e.source_id JOIN gl_definition t ON t.id=e.target_id
WHERE e.relationship_kind='EXTENDS' AND t.definition_type='Trait' ORDER BY trait_name;

C6. Blind-spot check: Rust files with no definitions (cfg-off, test-only, vendor)

SELECT f.path, f.size_bytes FROM gl_file f
WHERE f.language='rust'
  AND NOT EXISTS (SELECT 1 FROM gl_definition d WHERE d.file_path=f.path)
ORDER BY f.size_bytes DESC LIMIT 20;

C7. Candidate implementors by method-name overlap (covers cross-file impls, but noisy: it also matched the Archivist coordinator with 7/21)

WITH tm AS (
  SELECT m.name FROM gl_definition t
  JOIN gl_definition m ON m.fqn LIKE t.fqn || '::%' AND m.definition_type='Method'
  WHERE t.definition_type='Trait' AND t.name='ArchiveBackend')
SELECT regexp_replace(d.fqn,'::[^:]+$','') implementor, count(DISTINCT d.name) matched,
       (SELECT count(*) FROM tm) trait_methods
FROM gl_definition d WHERE d.definition_type='Method' AND d.name IN (SELECT name FROM tm)
GROUP BY 1 HAVING count(DISTINCT d.name) >= 3 ORDER BY matched DESC;

C8. Definition diff between two indexed commits (orbit sql --all -f defdiff.sql; SHAs from orbit list)

WITH a AS (SELECT fqn, definition_type, file_path, end_line-start_line+1 len
           FROM gl_definition WHERE commit_sha='<old_sha>'),
     b AS (SELECT fqn, definition_type, file_path, end_line-start_line+1 len
           FROM gl_definition WHERE commit_sha='<new_sha>')
SELECT CASE WHEN a.fqn IS NULL THEN 'added' WHEN b.fqn IS NULL THEN 'removed' ELSE 'resized' END change,
       coalesce(b.fqn,a.fqn) fqn, coalesce(b.definition_type,a.definition_type) kind,
       coalesce(b.file_path,a.file_path) file, a.len old_len, b.len new_len
FROM a FULL OUTER JOIN b USING (fqn, definition_type, file_path)
WHERE a.fqn IS NULL OR b.fqn IS NULL OR a.len <> b.len ORDER BY file, fqn;

C9. Callables with no incoming CALLS (triage only; Bevy systems, trait impls and handlers dominate)

SELECT d.definition_type, count(*) total,
       count(*) FILTER (WHERE NOT EXISTS (SELECT 1 FROM gl_edge e
         WHERE e.relationship_kind='CALLS' AND e.target_id=d.id)) no_incoming
FROM gl_definition d
WHERE d.file_path LIKE '%.rs' AND d.definition_type IN ('Function','Method','AssociatedFunction')
GROUP BY 1;

Open points for phase 2

  • Blast radius across two commits: join CALLS on fqn between the worktree and main entries. Measure how much the cfg and dyn gaps hurt on a real diff.
  • Run a full per-review lifecycle in one scratch --db: index main and the branch worktree, diff, delete the file. Single-commit --db isolation is already verified.
  • Decide how the skill should state cfg blindness. Options: always run C6 first, or grep feature-gated files touched by the diff.

Refinement 2026-09-29 (from phase 2)

Phase 2 (phase-2-review-workflows.md) found that orbit indexes the working tree, including uncommitted and untracked files, but labels the index with HEAD's commit. Any claim above that an index reflects a commit exactly should be read with this in mind.