Ripple Edits
Ripple edits answer the question every refactor begs: “What else needs to change because I changed this?” After each edit, BMO walks the LSP reference graph from the touched symbols, ranks the affected sites, and surfaces them as context for the agent. When suggested edits are enabled, BMO can also return concrete follow-up edits.
When it fires
Section titled “When it fires”Ripple analysis runs at four call sites. The trigger field on ripple.fired distinguishes them:
| Trigger | Where | Purpose |
|---|---|---|
pre_edit | Before write / edit / multiedit apply changes | Warns the agent about likely follow-ups before it commits to an edit shape |
post_edit | After edit, multiedit, and lsp_symbol_edit succeed | Lists concrete files/symbols whose references the edit just shifted |
tool | Direct lsp_ripple_impact agent-tool call | Lets the agent ask “what depends on this file?” without making a change |
analyze_ripple_impact | Suggested-edits analyzer, also the embedded path used by edit/multiedit to produce suggestions | Returns ranked rename-style suggestions, not just an impact list |
Impact analysis vs suggested edits
Section titled “Impact analysis vs suggested edits”- Impact analysis. Ripple walks LSP
findReferences, applies CODEOWNERS scoping, and returns affected files and symbol counts. It is always on whenever an LSP client is configured. - Suggested edits. BMO can build on impact analysis by computing a rename map and a single before/after diff, gated by
[options.suggested_edits].mode:advisory(default) — surface suggestions; the agent decides.required— when ripple is unavailable, or when cached reference locations are missing or expired, the edit still succeeds but the response metadata and downstream change contract mark the follow-up requirement unmet.
Configuration
Section titled “Configuration”The full reference lives in the [options.suggested_edits] section. Minimal example:
[options.suggested_edits]mode = "advisory" # or "required"Caps, the suggested-edits cache TTL, and the analyzer timeout are compile-time constants today. View their effective values with bmo config show-ripple or /ripple (see below).
Operator surfaces
Section titled “Operator surfaces”/ripple (TUI)
Section titled “/ripple (TUI)”Aliases: /ripple-status, /rip. Read-only. Renders:
- State (
ready (LSP clients: N)orripple unavailable — LSP not configured or ready) - Effective caps (
symbols_per_file,affected_files,rename_map_entries) - Default analyzer timeout and cached-reference TTL
- Resolved
[options.suggested_edits]mode - The most recent paired
ripple.fired/ripple.actionfrom a process-scoped 16-entry ring buffer
bmo config show-ripple (CLI)
Section titled “bmo config show-ripple (CLI)”The default text view renders the same snapshot contract as /ripple, but in detached CLI mode it truthfully reports that recent ripple events are not available from another process. Use bmo config show-ripple --format=json for the canonical config-only snapshot payload. Live recent-event tails remain in-process only.
Sidebar indicator
Section titled “Sidebar indicator”The TUI sidebar shows a one-line Ripple: status — ready, capped N, warn, or LSP unavailable — derived from the same ring buffer. Missing LSP is reported as dependency-unavailable state; BMO reserves disabled wording for operator/config gates.
Agent parity
Section titled “Agent parity”Both lsp_ripple_impact and analyze_ripple_impact are exposed to agents. The /ripple command is registered as AgentParityReadOnly with those two tool hints, so agents discovering the operator surface map see the corresponding tools.
Failure modes
Section titled “Failure modes”| Outcome | When | Level | What operators see |
|---|---|---|---|
skipped_no_lsp | No LSP client configured for the source file’s language | info | Single ripple.action event; ripple did not run |
skipped_no_symbols | LSP returned no document symbols | info | Single ripple.action; nothing to walk |
timed_out | Analyzer exceeded the default timeout (1s pre-edit, 2s post-edit) | warn | ripple.action action="timed_out" elapsed_ms=... |
errored | LSP findReferences or CODEOWNERS lookup failed | warn | ripple.action action="errored" continuation event |
phase_error | A non-fatal phase failed mid-analysis (codeowners load, per-symbol find-references) | warn | ripple.action action="phase_error" phase=... [symbol=...] error=... — analysis continues with partial results |
capped | Hit symbols_per_file, affected_files, or rename_map_entries=50 cap | warn | Advisory ripple.action action="capped" cap=..., paired with a terminal action that still completes |
capped is an advisory event — the run continues with truncated input. The Last-analysis line in /ripple always shows the terminal action, not the capped advisory. When required mode cannot turn ripple output into concrete follow-up suggestions because cached references are unavailable, the tool metadata and change contract preserve that requirement gap instead of collapsing it into a silent empty-success case.
Event schema
Section titled “Event schema”Ripple emits exactly two structured slog events per analysis. Every ripple.fired is paired with exactly one terminal ripple.action (with zero or more capped advisories interleaved).
ripple.fired
Section titled “ripple.fired”| Field | Type | When set |
|---|---|---|
invocation_id | uint64 | Always — pairs fired with its closing action |
session_id | string | Always |
source_file | string | Always |
trigger | string | One of pre_edit, post_edit, tool, analyze_ripple_impact, or "" (legacy Analyze callers) |
mode | string | Suggested edits only: advisory or required |
rename_map_size | int | Suggested edits only |
timeout_ms | int | Impact analysis only |
ripple.action
Section titled “ripple.action”| Field | Type | When set |
|---|---|---|
invocation_id | uint64 | Always — mirrors the paired ripple.fired |
session_id | string | Always |
source_file | string | Always |
trigger | string | Mirrors the paired ripple.fired |
action | string | One of: analyzed, skipped_no_lsp, skipped_no_symbols, timed_out, errored, succeeded, no_suggestions, unavailable_advisory, unavailable_required, capped, phase_error |
affected_count | int | Impact-analysis terminal actions |
symbol_count | int | Impact-analysis terminal actions |
suggestions_count | int | Suggested-edits terminal actions |
mode | string | Suggested edits |
cap | string | action="capped" only — one of symbols_per_file, affected_files, rename_map_entries |
input_size | int | action="capped" only — pre-truncation size |
cap_size | int | action="capped" only — applied cap |
phase | string | action="phase_error" only — e.g. codeowners_load, find_references |
symbol | string | action="phase_error" only — symbol name when the phase is per-symbol |
error | string | action="phase_error" only — wrapped error message |
elapsed_ms | int64 | Impact-analysis terminal actions |
Levels: terminal impact-analysis successes (analyzed, skipped_*) and suggested-edits advisory outcomes log at info. timed_out, errored, unavailable_required, phase_error, and all capped advisories log at warn.
See also
Section titled “See also”- Maintainer topic (architecture, internal contracts):
docs/topics/tools/ripple.md Edit Quality— the broader quality story ripple feeds intoSmart Apply— sibling LSP-driven edit surface