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.
34 KiB
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
usepaths 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 throughmatchbindings on enum payloads. [V] - It does not see everything. cfg is evaluated with every Cargo feature off (default features included),
testoff anddebug_assertionsoff. Feature-gated modules drop out entirely. In dirigent the wholeserverfeature is invisible:dirigent_core/src/runtime/mod.rs(180 KB) andconfig.rs(105 KB) have 0 definitions, and 176 of 989 Rust files have none.#[cfg(test)] mod testsnever appears, so unit tests are invisible. Onlytests/integration tests are indexed. [V] - Trait graph is partial.
impl Trait for TypegivesEXTENDS Type→Traitonly 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 (216impl Plugin forin 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 toadd_systems(they are not calls), and Dioxus components used inrsx!(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--allSQL 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_filesin the index JSON. - Leave
orbit sqlat its default scope. MCPrun_sqlis always unscoped. [V]
- Index with
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 noindexing/errorrows. 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), branchfeature/w1c-live-acp5de6c726(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→ headergrep "new" @ 8d9ec30aand 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@8d9ec30aanddirigent@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. Thestrategy-mvpworktree in spacecraft moved. The DuckDB file~/.gitlab/orbit/graph.duckdbis shared by every agent on the host, and one row perrepo_pathis overwritten on re-index. [V] -
orbit indexwrites nothing into the repo. Evidence:- spacecraft (re-index):
git status --porcelain --ignoredwas 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]
- spacecraft (re-index):
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, protocol2024-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_sqlhas no repo/commit scoping.select count(*) from gl_definitiongave 52114 rows across 4 commits over MCP, against 17110 with CLI default scope in spacecraft. Every MCP query therefore behaves like--all: filter bycommit_sha, or join_orbit_manifest. [V]grep,contextandrepo-mapare 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
Implnode. Methods from every impl block (in any file) hang off the type through fqncrate::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]] nameand integration tests (file stem, e.g.archetypes::…). In spacecraft the root package libwarpspaceand the binwarpspace(fromwarpspace_app) share the rootwarpspace. Group byfile_path, not by fqn prefix. [V] start_lineincludes doc comments and attributes. Example:Gamemodeshows atmod.rs:177, butpub traitis 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) andVariant(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 insidevec![…]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 indirigent_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:
JsonlBackendimplements 6 archivist traits in 6 files. OnlyArchiveBackendis linked, because that impl is inbackend.rswith the struct. The methods are still attached toJsonlBackend(repo-map class JsonlBackendlists 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 + AbilityClonegivesEXTENDS Ability→AbilityClone, sorepo-map extends AbilityCloneshowsAbilityat depth 1. External supertraits (Resource,Debug) are dropped. [V] - External traits:
orbit repo-map extends Pluginprints only the header, with no rows and no warning. spacecraft has 216impl 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 withgit 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_movementdoesmatch 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 anddyncallers. [V] - Cross-crate CALLS resolve. Cross-crate edges: spacecraft 807 of 4038 crate→crate edges, dirigent 613 of 8810. [V]
- Default
orbit sqlscope deduplicates edges to one per (source, target).--allshows one edge per call site:border_radius_smallhas 34 edges scoped against 45 with--all, andhex_color4 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_typevalues 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_nameempty.AliasedUse:as.PubUseGroup,ReExport,ReExportGlob,ReExportAliased: thepub useforms.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_buttonand edition-2018 relativegame_states::is_client_worldall appear as written. Cross-crate imports carry the crate name (warpspace_engine::game_states::InSession). To match a symbol, useidentifier_name, orimport_path LIKE '%::name'. [V] - Imports are not resolved to definitions.
contextlabels themexternal/unresolved, even in-repo ones. There is no ImportedSymbol→Definition edge. Re-export chains (pub use) are not followed. [V] repo-map imports PATTERNreturns 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.rshas 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_api217,dirigent_core→dirigent_protocol201). [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
.rsfiles 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. spacecraftvendor/(88 tracked.rsfiles of vendored bevy_replicon/lightyear) has 5 definitions, all macros.archive/*.rshas 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 ofwarpspace_testing/src/tests/*.rs) have 0 definitions.fqn LIKE '%::tests::%'returns 0.crates/*/tests/*.rsintegration tests are indexed. Their fns show up incontextunder "Test, fixture, or generated connections". Example:generate_shiphas 30 test callers fromarchetypes::…,determinism::….- Integration test files with
#![cfg(feature = "…")]have 0 definitions. context --testsno longer exists.
- cfg evaluation. Features are all off, including
default = [...].debug_assertionsoff,testoff,target_os = linux/unixon.- Evidence:
cfg(not(feature="server"))items are kept,cfg(feature="server")items dropped.- Of the four
platform_idvariants, only thecfg(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_latencyunder 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.rsandconnectors/opencode.rs,dirigent_api/src/acp_controller.rs, anddirigent_web/src/server.rs. Most of dirigent's backend sits behindserver. [V] - No flag, config key or env var to enable features was found in
--help,orbit config listorstrings. [I]
- Evidence:
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
ArchiveBackendbecause of the cross-file impl gap. - "Most-imported defined symbols" (
PlayerId83,InSession74,UiTheme57) 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(orasync 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 astreeplus signatures, with the same truncation. [I: not sampled separately; the signature extractor is shared withclass]grep: much better than 0.124. Match classes make exact-name hits obvious (put_session→ trait declaration plusJsonlBackend::put_session, then body-only hits with the matching line).grep --path file.rswith 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
- cfg blindness. All features off,
testoff,debug_assertionsoff. 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 usegit grep/read the files. Checkgl_filerows with zero definitions before trusting a "no callers" or "not found" result (cookbook C6). [V] - Watchdog skips under default threads. Index with
-t 4. Assertprocessing.skipped_files == 0in the JSON. [V] - EXTENDS only for same-file impls, in-repo traits and in-repo types. "Implementors of X" is a lower bound. [V]
dyn Traitdispatch has no callers. For trait methods, search call sites withorbit grep '<method>'(body-only hits) orgit grep '\.method('. [V]- Macros. Calls inside macro arguments are missed (
assert!,matches!,vec![seam!(..)],rsx!). Statement-levelmacro_rules!and proc-macro invocations do produce CALLS. [V] - ECS / framework wiring is not calls. Bevy
add_systems(Update, f), observers and Dioxus components give no incoming edges. [V] - CALLS→Struct means construction, not usage. "Who uses component
Health": 4 of 52 enclosing definitions found. Userepo-map importsor SQL on imports, or grep. [V] - fqn root is the cargo target name, and collides (lib vs bin
warpspace). Integration tests are rooted at the file stem. Group byfile_path. [V] start_lineincludes 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]- Default scope deduplicates edges;
--alldoes not. Do not compare counts across the two. [V] - SQL papercuts:
targetis a reserved word in DuckDB (AS targetfails); usetgt.- 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]
- Shared DB and concurrent agents. One
graph.duckdbper user. Other sessions re-index the same repos, andrepo_pathrows are replaced.--db <file>isolates cleanly.orbit index . -t 4 --db ~/.cache/orbit-probe/scratch.duckdbbuilt a 22.8 MB DB with 17110 definitions.list,sqlandrepo-map extendswith the same--dbworked,rmof the file was the whole cleanup, and the shared DB was untouched (still 4 entries). [V]ORBIT_DATA_DIRis untested. [I]
10. Multi-commit / worktree comparison
Steps [V]:
git -C ~/Projects/spacecraft worktree add --detach ~/.cache/orbit-probe/wt-spacecraft bfbbcd96. This commit is 4 commits behindmain, with 6.rsfiles changed (+363).orbit index . -t 4inside the worktree: 3.8 s, 17074 definitions.git status --porcelain --ignoredwas unchanged._orbit_manifestgained a row withrepo_path=/home/g4b/.cache/orbit-probe/wt-spacecraft,parent_repo_path=/home/g4b/Projects/spacecraft, its ownproject_id, andbranch='HEAD detached at bfbbcd96'.- Definition IDs are unique per commit: 0 shared IDs between the two commits. Join across commits on
(fqn, definition_type, file_path). orbit sql --repo <worktree> …andorbit repo-map --repo <worktree> …scope to that commit.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_actions70→82,main248→264,spawn_main_menu_root181→188 - Python script adds and removes in the same range
- added:
- Re-creating the worktree at another commit and re-indexing the same path replaced that path's entry.
- Cleanup:
git worktree remove+git worktree prune.git worktree listis back tomain+.worktrees/strategy-mvp.
What is left over:
- Orbit keeps the index entry.
orbit liststill 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
dyngaps 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--dbisolation 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.