Files
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

416 lines
34 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 1
stream: rust-capabilities
date: 2026-09-29
orbit-version: 0.133.0 (~/.local/bin/orbit, embedded skill "orbit-cli" v0.18.2)
test-beds: spacecraft (Bevy game, 25-crate workspace, 903 .rs files), dirigent (Tokio/Dioxus server+web, 35-crate workspace, 989 .rs files)
status: 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 `main`s. **"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.
```bash
# 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)**
```sql
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)
```sql
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**
```sql
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)
```sql
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)
```sql
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)
```sql
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)
```sql
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`)
```sql
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)
```sql
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.