Skip to content

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.

Ripple analysis runs at four call sites. The trigger field on ripple.fired distinguishes them:

TriggerWherePurpose
pre_editBefore write / edit / multiedit apply changesWarns the agent about likely follow-ups before it commits to an edit shape
post_editAfter edit, multiedit, and lsp_symbol_edit succeedLists concrete files/symbols whose references the edit just shifted
toolDirect lsp_ripple_impact agent-tool callLets the agent ask “what depends on this file?” without making a change
analyze_ripple_impactSlice 2 — analyze_ripple_impact tool, also the embedded path used by edit/multiedit to produce suggested editsReturns ranked rename-style suggestions, not just an impact list
  • 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.

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).

Aliases: /ripple-status, /rip. Read-only. Renders:

  • State (ready (LSP clients: N) or ripple 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.action from a process-scoped 16-entry ring buffer

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.

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.

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.

OutcomeWhenLevelWhat operators see
skipped_no_lspNo LSP client configured for the source file’s languageinfoSingle ripple.action event; ripple did not run
skipped_no_symbolsLSP returned no document symbolsinfoSingle ripple.action; nothing to walk
timed_outAnalyzer exceeded the default timeout (1s pre-edit, 2s post-edit)warnripple.action action="timed_out" elapsed_ms=...
erroredLSP findReferences or CODEOWNERS lookup failedwarnripple.action action="errored" continuation event
phase_errorA non-fatal phase failed mid-analysis (codeowners load, per-symbol find-references)warnripple.action action="phase_error" phase=... [symbol=...] error=... — analysis continues with partial results
cappedHit symbols_per_file, affected_files, or rename_map_entries=50 capwarnAdvisory 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.

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).

FieldTypeWhen set
invocation_iduint64Always — pairs fired with its closing action
session_idstringAlways
source_filestringAlways
triggerstringOne of pre_edit, post_edit, tool, analyze_ripple_impact, or "" (legacy Analyze callers)
modestringSlice 2 only: advisory or required
rename_map_sizeintSlice 2 only
timeout_msintSlice 1 only
FieldTypeWhen set
invocation_iduint64Always — mirrors the paired ripple.fired
session_idstringAlways
source_filestringAlways
triggerstringMirrors the paired ripple.fired
actionstringOne of: analyzed, skipped_no_lsp, skipped_no_symbols, timed_out, errored, succeeded, no_suggestions, unavailable_advisory, unavailable_required, capped, phase_error
affected_countintSlice 1 terminal actions
symbol_countintSlice 1 terminal actions
suggestions_countintSlice 2 terminal actions
modestringSlice 2
capstringaction="capped" only — one of symbols_per_file, affected_files, rename_map_entries
input_sizeintaction="capped" only — pre-truncation size
cap_sizeintaction="capped" only — applied cap
phasestringaction="phase_error" only — e.g. codeowners_load, find_references
symbolstringaction="phase_error" only — symbol name when the phase is per-symbol
errorstringaction="phase_error" only — wrapped error message
elapsed_msint64Slice 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.