diff --git a/.claude/docs/cli.md b/.claude/docs/cli.md new file mode 100644 index 0000000..62911b8 --- /dev/null +++ b/.claude/docs/cli.md @@ -0,0 +1,50 @@ + + +# CLI + +## Entry Point + +`cmd/peco/peco.go` — parses flags, creates `peco.New()`, calls `Run(ctx)` + +## Flags (CLIOptions) + +| Flag | Type | Description | +|------|------|-------------| +| `--help` | bool | Show help | +| `--version` | bool | Show version | +| `--query` | string | Initial query string | +| `--rcfile` | string | Config file path | +| `--buffer-size` | int | Max lines to read (0=unlimited) | +| `--null` | bool | Use NUL as line separator | +| `--initial-index` | int | Initial cursor position | +| `--initial-filter` | string | Initial filter name | +| `--prompt` | string | Prompt string | +| `--layout` | string | Layout type: top-down, bottom-up | +| `--select-1` | bool | Auto-select if single match | +| `--exit-zero` | bool | Exit 0 even on cancel | +| `--select-all` | bool | Select all lines initially | +| `--on-cancel` | string | Cancel behavior: success/error | +| `--selection-prefix` | string | Prefix for selected lines | +| `--exec` | string | Command to execute with selection | +| `--print-query` | bool | Print query as first output line | +| `--color` | ColorMode | Color mode: auto, none | +| `--height` | string | Terminal height spec | + +## Exit Codes + +- 0 — success (lines selected) +- 0 — cancel with `--on-cancel success` or `--exit-zero` +- 1 — cancel (default) +- Custom — from `--exec` command exit status + +## Input + +- Reads from stdin by default +- Positional arg → read from file +- Supports streaming (infinite) input + +## Output + +- Selected lines to stdout, one per line +- With `--print-query`: query string as first line +- With `--exec`: pipes selection to command diff --git a/.claude/docs/dependencies.md b/.claude/docs/dependencies.md new file mode 100644 index 0000000..9ee49ac --- /dev/null +++ b/.claude/docs/dependencies.md @@ -0,0 +1,57 @@ + + +# Internal Dependency Graph + +``` +cmd/peco → peco (root), internal/util +cmd/filterbench → filter + +peco (root) → config, filter, hub, line, pipeline, query, selection, sig + → internal/ansi, internal/keyseq, internal/util, internal/buffer + +config → internal/util + +filter → line, pipeline, internal/util + +selection → line + +line → internal/ansi + +pipeline → line + +internal/buffer → line +``` + +## Layer Grouping + +### Leaf (no internal deps) +- `hub` — message bus, no imports +- `query` — query text/caret, no imports +- `sig` — signal handling, no imports +- `internal/ansi` — ANSI parser, no imports +- `internal/keyseq` — key matching, no imports +- `internal/util` — platform utils, no imports + +### Core +- `line` → internal/ansi +- `pipeline` → line +- `internal/buffer` → line + +### Processing +- `config` → internal/util +- `filter` → line, pipeline, internal/util +- `selection` → line + +### Application +- `peco` (root) → all above +- `cmd/peco` → peco, internal/util +- `cmd/filterbench` → filter + +## External Dependencies +- `github.com/gdamore/tcell/v2` — terminal UI (screen.go) +- `github.com/goccy/go-yaml` — config parsing +- `github.com/google/btree` — ordered selection storage +- `github.com/jessevdk/go-flags` — CLI flag parsing +- `github.com/lestrrat-go/pdebug` — debug logging +- `github.com/mattn/go-runewidth` — Unicode width calculation +- `github.com/stretchr/testify` — test assertions diff --git a/.claude/docs/internals.md b/.claude/docs/internals.md new file mode 100644 index 0000000..22c7def --- /dev/null +++ b/.claude/docs/internals.md @@ -0,0 +1,102 @@ + + +# Internals + +## Concurrency Model + +Three main goroutines coordinated via context cancellation: + +1. **Input loop** (`input.go`) — reads terminal events, resolves key sequences via Keymap, dispatches actions +2. **View loop** (`view.go`) — renders screen in response to hub messages (draw, paging, status) +3. **Filter loop** (`filter.go`) — executes query against line buffer when query changes + +Communication → **Hub** (`hub/`), central message bus with typed generic channels. + +## Data Flow + +``` +stdin/file → Source → MemoryBuffer + ↓ +User keystroke → Input → Hub.SendQuery() → Filter loop + ↓ +Filter.Apply() → FilteredBuffer → Hub.SendDraw() → View loop + ↓ +View → Layout → Screen (tcell) → terminal +``` + +## Hub Message Types + +| Channel | Payload | Sender | Receiver | +|---------|---------|--------|----------| +| `QueryCh` | `string` | Input (action) | Filter loop | +| `DrawCh` | `*DrawOptions` | Filter, actions | View loop | +| `PagingCh` | `PagingRequest` | Input (action) | View loop | +| `StatusMsgCh` | `StatusMsg` | Various | View loop | + +Hub supports **batch mode** — multiple sends within `Batch()` callback are processed together. + +## Buffer Architecture + +- `MemoryBuffer` — stores all input lines, grows as Source reads +- `FilteredBuffer` — wraps any Buffer with index range (page slice) +- `Source` — implements `pipeline.Source`, reads input lines into MemoryBuffer +- `ContextBuffer` — adds surrounding context lines for zoom view +- `CurrentLineBuffer()` returns raw MemoryBuffer (no filter) or FilteredBuffer (after filter) + +## Filter Pipeline + +1. Query change arrives via Hub +2. Filter loop creates pipeline: `MemoryBufferSource → filter.Apply → MemoryBuffer` +3. Filter.Apply runs in parallel chunks (if `SupportsParallel()`) +4. Results collected into new MemoryBuffer → set as CurrentLineBuffer +5. Hub.SendDraw() triggers View redraw + +## Screen Abstraction + +- `Screen` interface wraps terminal operations +- `TcellScreen` — production impl using tcell/v2 +- `InlineScreen` — wraps TcellScreen for height-limited display +- `DummyScreen` — test mock with event injection + +## Layout System + +- `BasicLayout` composes: `UserPrompt` + `ListArea` + `StatusBar` +- Layout variants registered via `RegisterLayout(name, LayoutBuilder)` +- Built-in: `top-down` (default), `bottom-up`, `top-down-query-bottom` +- `AnchorSettings` controls vertical positioning (top/bottom anchor) + +## Key Sequence Resolution + +- `internal/keyseq.Keyseq` uses AhoCorasick matcher +- Supports multi-key sequences (e.g., C-x,C-c) +- Longest-match-wins semantics +- `InMiddleOfChain()` indicates partial match in progress + +## Selection Model + +- `selection.Set` uses `google/btree` for ordered storage by line ID +- Supports: single select, multi-select (toggle), range select, select-all +- Sticky selection — persists across query changes (configurable) + +## Action System + +- ~40 built-in actions in `action.go` +- Actions implement `Action` interface: `Execute(ctx, *Peco, Event)` +- `ActionFunc` — function adapter with `Register()` for key binding +- Combined actions — multiple actions bound to single key sequence +- `Keymap.LookupAction(Event) → Action` — resolve event to action + +## State Objects + +| State | Purpose | +|-------|---------| +| `Location` | Current page, line number, offset, per-page count | +| `SingleKeyJumpState` | Single-key jump mode toggle and prefix map | +| `ZoomState` | Zoom view buffer and line reference | +| `FrozenState` | Frozen source buffer for suspend/resume | +| `QueryExecState` | Query execution delay timer | + +## Object Pools + +- `line.GetMatched/ReleaseMatched` — pool for Matched line wrappers +- `internal/buffer.GetLineListBuf/ReleaseLineListBuf` — pool for line slices diff --git a/.claude/docs/packages.md b/.claude/docs/packages.md new file mode 100644 index 0000000..709cce2 --- /dev/null +++ b/.claude/docs/packages.md @@ -0,0 +1,189 @@ + + +# Package Map + +## peco (root) + +Interactive filtering tool core. Holds global state, goroutine loops, UI components. + +- **New() → *Peco** — create new instance +- **(*Peco).Setup() → error** — initialize from config/options +- **(*Peco).Run(ctx) → error** — main event loop +- **(*Peco).ApplyConfig(CLIOptions) → error** — apply CLI flags to config +- **(*Peco).SetupSource(ctx) → (*Source, error)** — initialize input source +- **(*Peco).PrintResults()** — output selected lines to stdout +- **(*Peco).CurrentLineBuffer() → Buffer** — active line buffer (raw or filtered) +- **(*Peco).ExecQuery(ctx, func()) → bool** — execute filter with debounce +- Key types: `Peco`, `Buffer`, `FilteredBuffer`, `MemoryBuffer`, `Source`, `Screen`, `Layout`, `Action`, `Keymap`, `Event`, `CLIOptions`, `Location`, `PageCrop` +- Key interfaces: `MessageHub`, `Screen`, `Layout`, `Action`, `ActionMap`, `Buffer`, `Keyseq`, `ConfigReader` +- Screen impls: `TcellScreen` (production), `InlineScreen` (height-limited), `DummyScreen` (tests) +- Layout impls: `BasicLayout` with builders: `DefaultLayout`, `BottomUpLayout`, `TopDownQueryBottomLayout` +- Files: `peco.go`, `action.go`, `buffer.go`, `caret.go`, `event.go`, `filter.go`, `input.go`, `keymap.go`, `layout.go`, `layout_any.go`, `layout_windows.go`, `options.go`, `page.go`, `screen.go`, `screen_inline.go`, `source.go`, `state.go`, `view.go`, `vertical_anchor_gen.go` +- Imports: config, filter, hub, line, pipeline, query, selection, sig, internal/ansi, internal/keyseq, internal/util + +## cmd/peco + +CLI entry point. + +- Parses flags via `go-flags`, creates `peco.New()`, calls `Run(ctx)` +- Files: `peco.go` +- Imports: peco (root), internal/util + +## cmd/filterbench + +Benchmark tool for filter performance. + +- Files: `main.go` +- Imports: filter + +## config/ + +Configuration loading and types. + +- **Config** — main config struct (Keymap, Action, Style, Layout, CustomFilter, SingleKeyJump, Height, etc.) +- **(*Config).Init() → error** — set defaults +- **(*Config).ReadFilename(string) → error** — load YAML config file +- **LocateRcfile(Locator) → (string, error)** — find config file path +- Key types: `Config`, `StyleSet`, `Style`, `Attribute`, `OnCancelBehavior`, `ColorMode`, `CustomFilterConfig`, `SingleKeyJumpConfig`, `HeightSpec` +- Color constants: `ColorDefault`, `ColorBlack`..`ColorWhite`, `AttrBold`, `AttrUnderline`, `AttrReverse`, `AttrTrueColor` +- Layout constants: `LayoutTypeTopDown`, `LayoutTypeBottomUp`, `LayoutTypeTopDownQueryBottom` +- Files: `config.go`, `style.go`, `height.go`, `layout.go` +- Imports: internal/util + +## filter/ + +Filter algorithm implementations. + +- **Filter** interface — `Apply(ctx, []line.Line, ChanOutput) → error`, `BufSize() → int`, `NewContext(ctx, string) → ctx`, `String() → string`, `SupportsParallel() → bool` +- **Collector** interface (optional) — `ApplyCollect(ctx, []line.Line) → ([]line.Line, error)` +- **Set** — filter collection with rotation: `Add(Filter)`, `Rotate()`, `Current() → Filter`, `SetCurrentByName(string) → error` +- Implementations: `NewIgnoreCase()`, `NewCaseSensitive()`, `NewSmartCase()`, `NewRegexp()`, `NewIRegexp()`, `NewFuzzy(longestSort bool)`, `NewExternalCmd(name, cmd string, args []string, threshold int, idgen IDGenerator, enableSep bool)` +- Files: `filter.go`, `base.go`, `regexp.go`, `fuzzy.go`, `external.go`, `set.go` +- Imports: line, pipeline, internal/util + +## hub/ + +Central message bus for goroutine communication. + +- **New(bufsize int) → *Hub** — create hub with channel buffer size +- **(*Hub).SendDraw(ctx, *DrawOptions)** — trigger screen redraw +- **(*Hub).SendQuery(ctx, string)** — send query change +- **(*Hub).SendPaging(ctx, PagingRequest)** — send paging command +- **(*Hub).SendStatusMsg(ctx, string, time.Duration)** — show status message +- **(*Hub).Batch(ctx, func(ctx))** — batch multiple sends atomically +- Channel accessors: `DrawCh()`, `PagingCh()`, `QueryCh()`, `StatusMsgCh()` +- Key types: `Hub`, `Payload[T]`, `DrawOptions`, `PagingRequest`, `PagingRequestType`, `StatusMsg` +- Paging types: `ToLineAbove`, `ToLineBelow`, `ToScrollPageDown`, `ToScrollPageUp`, `ToScrollLeft`, `ToScrollRight`, `ToScrollFirstItem`, `ToScrollLastItem`, `ToLineInPage` +- Files: `hub.go`, `draw.go`, `paging.go`, `paging_request_type_gen.go` +- Imports: (none internal) + +## line/ + +Line data types for display and selection. + +- **Line** interface — `ID() → uint64`, `Buffer() → string`, `DisplayString() → string`, `Output() → string`, `IsDirty() → bool`, `SetDirty(bool)`, implements `btree.Item` +- **NewRaw(id uint64, s string, enableSep bool, stripANSI bool) → *Raw** — create raw line +- **NewMatched(Line, [][]int) → *Matched** — wrap line with match indices +- **GetMatched(Line, [][]int) → *Matched** — pooled allocation +- **ReleaseMatched(*Matched)** — return to pool +- **IDGenerator** interface — `Next() → uint64` +- Files: `raw.go`, `matched.go` +- Imports: internal/ansi, btree + +## pipeline/ + +Generic source→acceptor→destination pipeline. + +- **New() → *Pipeline** — create pipeline +- **(*Pipeline).SetSource(Source)** — set data source +- **(*Pipeline).Add(Acceptor)** — add processing stage +- **(*Pipeline).SetDestination(Destination)** — set terminal stage +- **(*Pipeline).Run(ctx) → error** — execute pipeline +- Key interfaces: `Source` (`Start`, `Reset`), `Acceptor` (`Accept`), `Destination` (`Accept`, `Reset`, `Done`), `Suspender` (optional `Suspend`/`Resume`) +- **ChanOutput** (chan line.Line) — `Send(ctx, line.Line) → error`, `OutCh() → <-chan line.Line` +- **NewQueryContext(ctx, string) → ctx** / **QueryFromContext(ctx) → string** — pass query through context +- Files: `pipeline.go` +- Imports: line + +## query/ + +Query text and caret management. + +- **Text** — query string with save/restore: `Set(string)`, `Reset()`, `SaveQuery()`, `RestoreSavedQuery()`, `DeleteRange(int, int)`, `InsertAt(rune, int)`, `String()`, `Len()`, `RuneSlice()`, `RuneAt(int)` +- **Caret** — cursor position: `Pos() → int`, `SetPos(int)`, `Move(int)` +- Files: `query.go` +- Imports: (none internal) + +## selection/ + +Ordered selection storage using btree. + +- **New() → *Set** — create selection set +- **(*Set).Add(line.Line)** — add to selection +- **(*Set).Remove(line.Line)** — remove from selection +- **(*Set).Has(line.Line) → bool** — check membership +- **(*Set).Len() → int** — count selected +- **(*Set).Ascend(func(line.Line) bool)** — iterate in order +- **(*Set).Copy(dst *Set)** — copy all items +- **RangeStart** — range selection start marker: `Valid()`, `Value()`, `SetValue(int)`, `Reset()` +- Files: `selection.go` +- Imports: line, btree + +## sig/ + +OS signal handling. + +- **New(handler ReceivedHandler, sigs ...os.Signal) → *Handler** +- **(*Handler).Loop(ctx, func()) → error** — signal listening loop +- **ReceivedHandler** interface — `Handle(os.Signal)` +- Files: `sig.go` +- Imports: (none internal) + +## internal/ansi + +ANSI escape sequence parser. + +- **Parse(string) → ParseResult** — strip ANSI, extract color spans +- **ExtractSegment([]AttrSpan, start, end int) → []AttrSpan** — slice attr spans for substring +- Key types: `ParseResult` (`Stripped string`, `Attrs []AttrSpan`), `AttrSpan` (`Fg, Bg Attribute`, `Length int`) +- Files: `parser.go` +- Imports: (none internal) + +## internal/buffer + +Line list buffer pool. + +- **GetLineListBuf() → []line.Line** — get from pool +- **ReleaseLineListBuf([]line.Line)** — return to pool +- Files: `line.go` +- Imports: line + +## internal/keyseq + +Key sequence matching (multi-key bindings). + +- **New() → *Keyseq** — create matcher (uses AhoCorasick internally) +- **(*Keyseq).Add(KeyList, any)** — register key sequence → action +- **(*Keyseq).Compile() → error** — build matcher +- **(*Keyseq).AcceptKey(Key) → (any, error)** — feed key, get action if matched +- **ToKeyList(string) → (KeyList, error)** — parse "C-x,C-c" → KeyList +- **KeyEventToString(KeyType, rune, ModifierKey) → (string, error)** — event → name +- Key types: `Key`, `KeyList`, `KeyType`, `ModifierKey` +- Matcher impls: Trie, TernarySearch, AhoCorasick (AhoCorasick used by default) +- Files: `keyseq.go`, `keys.go`, `trie.go`, `ternary.go`, `ahocorasick.go` +- Imports: (none internal) + +## internal/util + +Platform utilities. + +- **IsTty(io.Reader) → bool** — check if reader is terminal +- **Homedir() → (string, error)** — user home directory +- **Shell(ctx, string) → *exec.Cmd** — create shell command +- **StripANSISequence(string) → string** — remove ANSI escapes (deprecated, use internal/ansi) +- **IsCollectResultsError(error) → bool** — check for collect-results sentinel +- **IsIgnorableError(error) → bool** — check for ignorable errors +- **GetExitStatus(error) → (int, bool)** — extract exit code +- Platform files: `tty_posix.go`, `tty_bsd.go`, `tty_windows.go`, `shell_unix.go`, `shell_windows.go`, `homedir_posix.go`, `homedir_darwin.go`, `homedir_windows.go` +- Files: `util.go` +- Imports: (none internal) diff --git a/.claude/docs/testing.md b/.claude/docs/testing.md new file mode 100644 index 0000000..7d89f13 --- /dev/null +++ b/.claude/docs/testing.md @@ -0,0 +1,58 @@ + + +# Testing + +## Commands + +```bash +make test # go test -v -race ./... +go test -v -run TestFoo ./... # single test, all packages +go test -v -run TestFoo ./filter/ # single test, specific package +go test -race -coverprofile=coverage.out ./... # coverage +``` + +## Test Package Convention + +- Tests use same package name (not `_test` suffix) — white-box testing +- Exception: some packages use `_test` suffix for external testing + +## Key Test Helpers + +- `newPeco() → *Peco` — creates test instance with DummyScreen, default config +- `NewDummyScreen() → *DummyScreen` — mock terminal; supports `SendEvent(Event)` for input injection +- DummyScreen has fixed size, no-op rendering, collects events + +## Test Patterns + +- Table-driven tests with `t.Run()` subtests +- Regression tests for GitHub issues in `issues_test.go` +- Filter tests in `filter/filter_test.go`, `filter/base_test.go` +- Hub tests in `hub/hub_test.go` +- Key sequence tests in `internal/keyseq/trie_test.go`, `ahocorasick_test.go`, `ternary_test.go` +- Pipeline tests in `pipeline/pipeline_test.go` +- Selection tests in `selection/selection_test.go` +- Query tests in `query/query_test.go` + +## Benchmark Tests + +- `filter/bench_test.go` — filter algorithm benchmarks +- `hub/bench_test.go` — hub message passing benchmarks +- `line/bench_test.go` — line allocation benchmarks +- `internal/ansi/bench_test.go` — ANSI parsing benchmarks +- `internal/util/bench_test.go` — utility benchmarks +- `cmd/filterbench/` — standalone filter benchmark CLI + +## No Test Data Directory + +- No `testdata/` or golden files +- Tests use inline data and programmatic setup + +## Build Tags + +- Platform-specific files: `_posix.go`, `_bsd.go`, `_windows.go`, `_darwin.go` +- No custom build tags for testing + +## Code Generation + +- `go generate ./...` — runs `stringer` for enum types +- Generated files: `vertical_anchor_gen.go`, `hub/paging_request_type_gen.go` diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 0000000..681311e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..414c59c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,104 @@ + + +# CLAUDE.md + +peco is an interactive filtering tool for the terminal, written in Go. + +## Pre-Read Rules + +Read the linked doc BEFORE working in that area. No exceptions. + +| Trigger | Doc | +|---------|-----| +| Working with any package API or adding imports | `.claude/docs/packages.md` | +| Modifying cross-package dependencies | `.claude/docs/dependencies.md` | +| Writing or running tests | `.claude/docs/testing.md` | +| Modifying CLI flags, entry point, or output | `.claude/docs/cli.md` | +| Modifying concurrency, hub, buffers, filters, layout, screen, actions | `.claude/docs/internals.md` | + +## Build & Test Commands + +```bash +make # Download deps and build (default target) +make build # Build binary to releases/peco__/peco +make test # Run all tests: go test -v ./... +make deps # Download Go module dependencies +make clean # Remove build artifacts +``` + +Run a single test: +```bash +go test -v -run TestFunctionName ./... +go test -v -run TestFunctionName ./filter/ # for a specific package +``` + +The entry point is `cmd/peco/peco.go`. + +## Architecture + +### Concurrency Model + +peco runs three main goroutines coordinated via context cancellation: + +- **Input loop** (`input.go`) — reads termbox key events, resolves key sequences via Keymap, dispatches actions +- **View loop** (`view.go`) — renders screen in response to draw/paging/status messages +- **Filter loop** (`filter.go`) — executes queries against the line buffer when query text changes + +These goroutines communicate through the **Hub** (`hub/`), a central message bus with typed channels: `QueryCh`, `DrawCh`, `PagingCh`, `StatusMsgCh`. + +### Data Flow + +1. **Source** (`source.go`) reads input lines (stdin or file), implements `pipeline.Source` +2. User keystrokes trigger actions that modify the query +3. Query changes are sent to the Filter loop via Hub +4. **Filter** applies the active filter algorithm to produce matched lines +5. Results flow through the **Pipeline** (`pipeline/`) as `Source → Acceptor → Destination` +6. **View** receives draw messages and delegates to **Layout** (`layout.go`) which composes `UserPrompt`, `ListArea`, and `StatusBar` +7. **Screen** (`screen.go`) wraps termbox-go for terminal cell rendering + +### Key Interfaces + +- **`Buffer`** — line storage (`LineAt`, `Size`); implemented by `MemoryBuffer`, `FilteredBuffer`, `Source` +- **`Filter`** (in `filter/`) — `Apply(ctx, []line.Line, ChanOutput)` for each filter algorithm (IgnoreCase, CaseSensitive, SmartCase, Regexp, IRegexp, Fuzzy, ExternalCmd) +- **`Line`** (`line/`) — represents a single line with `ID`, `Buffer`, `DisplayString`, `Output` +- **`Screen`** — terminal abstraction (`Init`, `SetCell`, `Flush`, `PollEvent`); `DummyScreen` used in tests +- **`Layout`** — screen composition (`DrawScreen`, `DrawPrompt`, `MovePage`) +- **`Action`** — user actions bound to keys (`action.go`); ~40 built-in actions, supports combined action sequences + +### Selection + +Uses `google/btree` for ordered selection storage. Supports multi-select, range mode, and sticky selection (persists across query changes). + +### Key Sequence Resolution + +`internal/keyseq/` implements Trie, TernarySearch, and AhoCorasick for matching multi-key sequences to actions (longest-match-wins). + +### Platform-Specific Code + +Files suffixed `_posix.go` / `_windows.go` in `screen.go` and `internal/util/` handle TTY detection, shell integration, and home directory resolution per platform. + +### Code Generation + +Uses `go:generate` with `stringer` for enum string representations. + +## Testing Patterns + +- `newPeco()` helper creates a test instance with `DummyScreen` (mock terminal) +- `NewDummyScreen()` supports event injection for simulating user input +- Table-driven tests with `t.Run()` subtests are the common pattern +- Regression tests for specific GitHub issues in `issues_test.go` + +## Cache Maintenance + +These docs cache repository state. Still read source before modifying code. + +1. When your changes affect a doc below, update it in the same commit. +2. If you notice any doc is wrong or stale — even on an unrelated task — fix it immediately. + +| Doc | Update trigger | +|-----|----------------| +| `packages.md` | Add/remove/rename exported functions, types, or packages | +| `dependencies.md` | Add/remove internal package imports | +| `testing.md` | Change test infrastructure, helpers, or test commands | +| `cli.md` | Add/remove CLI flags, change exit codes or output format | +| `internals.md` | Change concurrency model, hub channels, buffer types, layout system, action system |