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 — and, when Slice 2 is enabled, as concrete suggested 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 | Slice 2 — analyze_ripple_impact tool, also the embedded path used by edit/multiedit to produce suggested edits | Returns ranked rename-style suggestions, not just an impact list |
Slice 1 vs Slice 2
Section titled “Slice 1 vs Slice 2”- Slice 1 — analysis. Ripple walks LSP
findReferences, applies CODEOWNERS scoping, and returns affected files and symbol counts. Always-on whenever an LSP client is configured. - Slice 2 — suggested edits. Builds on Slice 1 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, the embedded flow surfaces a warning instead of silently dropping suggestions.
Configuration
Section titled “Configuration”The full reference lives in the [options.suggested_edits] section. Minimal example:
[options.suggested_edits]mode = "advisory" # or "required"Caps 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
- 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)”Same caps, timeout, and mode as /ripple. The “Last analysis” line is intentionally TUI-only — the recent-events ring is process-scoped and lives in the running TUI, not in CLI state.
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.
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 | Slice 2 only: advisory or required |
rename_map_size | int | Slice 2 only |
timeout_ms | int | Slice 1 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 | Slice 1 terminal actions |
symbol_count | int | Slice 1 terminal actions |
suggestions_count | int | Slice 2 terminal actions |
mode | string | Slice 2 |
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 | Slice 1 terminal actions |
Levels: terminal Slice-1 successes (analyzed, skipped_*) and Slice-2 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