add CLAUDE.md and .claude/docs for agent context

This commit is contained in:
Daisuke Maki 2026-03-08 09:09:36 +09:00
parent b1734bbc34
commit 1fbf855884
7 changed files with 561 additions and 0 deletions

50
.claude/docs/cli.md Normal file
View file

@ -0,0 +1,50 @@
<!-- Agent-consumed file. Keep terse, unambiguous, machine-parseable. -->
# 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

View file

@ -0,0 +1,57 @@
<!-- Agent-consumed file. Keep terse, unambiguous, machine-parseable. -->
# 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

102
.claude/docs/internals.md Normal file
View file

@ -0,0 +1,102 @@
<!-- Agent-consumed file. Keep terse, unambiguous, machine-parseable. -->
# 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

189
.claude/docs/packages.md Normal file
View file

@ -0,0 +1,189 @@
<!-- Agent-consumed file. Keep terse, unambiguous, machine-parseable. -->
# 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)

58
.claude/docs/testing.md Normal file
View file

@ -0,0 +1,58 @@
<!-- Agent-consumed file. Keep terse, unambiguous, machine-parseable. -->
# 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`

1
AGENTS.md Symbolic link
View file

@ -0,0 +1 @@
CLAUDE.md

104
CLAUDE.md Normal file
View file

@ -0,0 +1,104 @@
<!-- Agent-consumed file. Keep terse, unambiguous, machine-parseable. -->
# 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_<os>_<arch>/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 |