Investigate
/xavier investigate <symptom> works backward from a bug. Where Research is topic-driven ("teach me about X") and Code Review is diff-driven ("check this change"), investigate is hypothesis-driven — you describe what's broken, and Xavier spawns one remora per causal axis to test a specific hypothesis in parallel. The Shark pattern fans the work out; the shark ranks the returning findings by evidence strength and hands you a ranked diagnosis, not a fix.
How it works
- Parse the input —
--fileand--testflags are pulled off; the remainder is the symptom description. - Check for prior investigations — existing notes for this repo are globbed from
investigations/. If matches are found, you're asked "related to one of these, or new investigation?" - Normalize the symptom — the free-text is structured into what's broken, where it manifests, when it started, and the entry point (if provided).
- Generate investigation axes — the 5 fixed axes always run; 1–2 dynamic axes are generated based on the symptom type.
- Spawn remoras in parallel — one remora per axis, launched in a single message so they run concurrently.
- Each remora reports back — a structured response with Findings, Hypothesis, and Evidence Strength (strong / moderate / weak), capped at 500 words.
- Rank and group — the shark ranks hypotheses across remoras, groups ones that point to the same root cause, and flags corroborating evidence (e.g., a recent commit from the git remora plus a matching code path from the tracing remora).
- Present the diagnosis inline — ranked hypotheses with evidence and suggested next steps for the top one.
- Save to the vault — the note is written to
~/.xavier/investigations/<repo>_<date>_<slug>.md. On a "new investigation" filename collision, anHHMMsuffix is appended so nothing is overwritten.
Investigation axes
Every investigation runs five fixed axes. Each axis is a remora with a distinct angle on the symptom.
| Axis | What it checks |
|---|---|
| Code path tracing | Follow execution from the symptom back to its inputs. Starts from --file or --test entry point when provided. |
| Recent changes | git log / git blame on the affected area — commits that could have introduced the regression. |
| Dependency boundaries | Integration points, external calls, API contracts near the symptom. |
| Test coverage | What tests exist for this area, which are failing, gaps that would have caught this. |
| Error pattern matching | Similar error handling, known workarounds, prior occurrences of the same symptom elsewhere. |
Dynamic axes (1–2) are generated per symptom. Examples: concurrency analysis for race conditions, state flow analysis for rendering bugs, schema drift for data issues, auth-chain analysis for permission errors. The dynamic axes let specialized bugs get specialized investigation without bloating every run.
Flags
--file <path>
Anchors the investigation to a specific file. The path is canonicalized and must resolve under the repo root — no escaping out. Useful when you can already see where the bug manifests and want remoras to start there rather than search the whole codebase.
--test <name>
Anchors the investigation to a named test. The test-coverage axis prioritizes it; the code-path axis uses the test as its entry point. Useful for flaky or failing tests where the test itself is the most concrete reproduction you have.
Prior-investigation flow
Before spawning remoras, Xavier checks investigations/ for prior notes scoped to the current repo. If matches exist, you pick one of two branches:
- Related. The prior investigation's findings are passed to every remora as extra context. The new note's
relatedfrontmatter links back to the prior one via a wikilink. Remoras know what was already looked at and focus on what's new or was missed. - New. Remoras start fresh with no prior context. On filename collision (same repo, same date, same slug), an
HHMMsuffix is appended so the earlier note isn't overwritten.
The match is by repo only — bug symptoms are too varied for automatic similarity matching, so you decide relevance.
Evidence strength
Every remora rates its hypothesis on a three-point scale. The shark uses these ratings to rank the final list.
| Strength | Meaning |
|---|---|
| Strong | Direct evidence linking symptom to cause |
| Moderate | Circumstantial evidence or partial match |
| Weak | Speculative — based on patterns, not direct observation |
Investigation note schema
Investigation notes use Zettelkasten frontmatter with type: investigation and two type-specific fields. See Vault Structure for the base schema shared across all note types.
---repo: my-apptype: investigationcreated: 2026-04-23updated: 2026-04-23tags: [investigation, flaky-test, router]related: ["[[investigations/my-app_2026-04-16_router-regression]]"]symptom: "Router test times out intermittently after upgrading @solidjs/router"verdict: "Race in lazy-route preload swallowed by silent timeout"---The symptom field is a normalized one-line summary of what's broken — the primary identifier for the note. The verdict field is a one-line summary of the top hypothesis. Together they let you scan a directory of investigation notes without opening any of them.
Example
You have a router test that flakes when the suite runs in parallel. You've seen it fail once in CI but can't reliably reproduce it locally.
/xavier investigate "router test flakes when running in parallel" --test routes.test.tsxXavier checks for prior investigations (none found), normalizes the symptom, spawns six remoras (five fixed plus a dynamic concurrency-analysis remora), and returns:
── Diagnosis: router test flakes when running in parallel ──
1. Lazy-route preload race — Strong Findings: `preloadRoute()` in src/router/preload.ts resolves a shared cache without a lock. Under parallel test runs, two tests hitting the same route entry see an unresolved promise that the test timeout swallows silently. Corroborating evidence: recent-changes remora found commit 8f2a11c ("move preload cache to module scope") landed three days before the first CI flake; code-path remora traced the exact function call.
2. Test isolation gap — Moderate Findings: routes.test.tsx shares a module-scoped router instance with auth.test.tsx via a common setup file. State from one test bleeds into the other when run in the same worker.
3. Vitest worker pool misconfig — Weak Findings: vitest.config.ts leaves `poolOptions.threads.singleThread` unset, defaulting to multi-threaded. No direct evidence this triggers the flake — pattern-matched from similar Vitest reports.
── Suggested Next Steps ──- Add a mutex around the preload cache or scope it per-request to confirm hypothesis 1. Revert commit 8f2a11c in a branch and re-run the test suite under parallel load as a second signal.- Split the shared router setup between routes.test.tsx and auth.test.tsx so each test owns its router instance.- Capture a failing run's timing with `--reporter=verbose` to confirm the swallowed timeout theory.
Saved to ~/.xavier/investigations/my-app_2026-04-23_router-flake.mdThe diagnosis stops here. Xavier doesn't attempt a fix — you stay in control of what changes. If you want to drive the fix through an autonomous loop, hand the note off to /xavier loop.
When to use it
Reach for investigate when you have a concrete symptom and need a ranked list of probable causes. Use Research when you want to learn about a topic or pattern, not debug one instance of it. Use Code Review when the question is "is this diff safe to ship" rather than "why is this broken". See CLI commands for the full signature and flag reference.
Last updated: 4/23/26, 10:15 AM
Edit this page on GitHub