From 13c3ef3580b8428cd1b9e2482e67e653626cedcc Mon Sep 17 00:00:00 2001 From: Francis Belanger Date: Fri, 9 Oct 2026 08:44:52 -0400 Subject: [PATCH 1/8] docs: add help file for Neovim tag documentation --- doc/opencode.txt | 505 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 505 insertions(+) create mode 100644 doc/opencode.txt diff --git a/doc/opencode.txt b/doc/opencode.txt new file mode 100644 index 00000000..85207f67 --- /dev/null +++ b/doc/opencode.txt @@ -0,0 +1,505 @@ +*opencode.txt* OpenCode conversations inside Neovim + +============================================================================== +CONTENTS *opencode.nvim* + + Start here ........................................... |opencode-start| + Everyday keys ........................................ |opencode-keys| + Configuration ........................................ |opencode-config| + Documentation ........................................ |opencode-docs| + +============================================================================== +START HERE *opencode-start* + +Install the OpenCode CLI and connect a provider in OpenCode first. Install this +plugin with your plugin manager, then call setup once: >lua + + require('opencode').setup({}) +< +With lazy.nvim, opts = {} calls setup for you. Optional Markdown renderer setup +and picker/completion integrations are covered in the repository's README. + +Open a project file, press oi, and type a small request. Leave insert +mode with , then press to submit. also submits in insert +mode when your terminal supports it. closes the panel in normal mode. + +The current file is context by default. Type # in input insert mode to inspect +context before sending. OpenCode permissions control tool access; review edits +and keep important work committed or backed up. + +The current plugin supports OpenCode V1 and V2. The v1 plugin branch preserves +the old implementation; it is not needed to connect to a V1 server. + +============================================================================== +EVERYDAY KEYS *opencode-keys* + +Global defaults: > + og Hide or restore panel + oi Focus input in insert mode + oo Focus output + ot Switch panel/editor focus + oy Add visual selection as context (visual mode) + oY Insert selected code inline (visual mode) + os Resume a session + oI Start a new session + oN Start a new session in a panel tab + op Choose provider/model + od Review session diff (V2) + o/ Quick chat (experimental) +< +In the panel: > + Switch input/output (normal mode) + Cancel running request + ]] / [[ Next/previous message (output, normal mode) + gf Jump to referenced file (output, normal mode) + @ / ~ Mention completion / file picker (input, insert mode) + # Inspect context (input, insert mode) + / Browse slash commands (input, insert mode) +< +Use :Opencode help for available actions and :Opencode with command-line +completion to explore subcommands. Keys above assume the default prefix. + +============================================================================== +CONFIGURATION *opencode-config* + +Override only what you need: >lua + + require('opencode').setup({ + keymap_prefix = 'a', + ui = { window_width = 0.35, icons = { preset = 'text' } }, + }) +< +Full default configuration (all plugin options and keymaps): >lua + + require('opencode').setup({ + preferred_picker = nil, + preferred_completion = nil, + default_global_keymaps = true, + default_mode = 'build', + default_system_prompt = nil, + keymap_prefix = 'o', + opencode_executable = 'opencode', + lock_session_to_directory = false, + server = { + url = nil, + port = nil, + timeout = 5, + retry_delay = 2000, + health_check_ttl_ms = 5000, + spawn_command = nil, + kill_command = nil, + auto_kill = true, + path_map = nil, + reverse_path_map = nil, + username = nil, + password = nil, + password_file = nil, + }, + -- stylua: ignore + keymap = { + session_diff = { + list = { + [''] = { 'activate', desc = 'Open file or toggle folder' }, + ['gf'] = { 'open_file', desc = 'Open working-tree file in a new tab' }, + [''] = { 'next_file', desc = 'Next file' }, + [''] = { 'prev_file', desc = 'Previous file' }, + ['r'] = { 'toggle_range', desc = 'Choose message range', nowait = true }, + ['p'] = { 'toggle_view', desc = 'Toggle diff layout' }, + ['q'] = { 'close', desc = 'Close diff', nowait = true }, + ['g?'] = { 'toggle_help', desc = 'Toggle keymap help' }, + }, + messages = { + [''] = { 'activate', desc = 'Review selected range' }, + ['r'] = { 'toggle_range', desc = 'Return to files' }, + ['f'] = { 'mark_from', desc = 'Mark range start' }, + ['t'] = { 'mark_to', desc = 'Mark range end' }, + ['K'] = { 'show_message_preview', desc = 'Preview message' }, + [''] = { 'toggle_range', desc = 'Return to files' }, + ['q'] = { 'toggle_range', desc = 'Return to files', nowait = true }, + ['g?'] = { 'toggle_help', desc = 'Toggle keymap help' }, + }, + preview = { + ['gf'] = { 'open_file', desc = 'Open working-tree file in a new tab' }, + [''] = { 'next_file', desc = 'Next file' }, + [''] = { 'prev_file', desc = 'Previous file' }, + ['q'] = { 'close', desc = 'Close diff', nowait = true }, + ['p'] = { 'toggle_view', desc = 'Toggle diff layout' }, + ['g?'] = { 'toggle_help', desc = 'Toggle keymap help' }, + ['c'] = { 'add_comment', mode = { 'n', 'x' }, desc = 'Add or edit review comment', nowait = true }, + ['dc'] = { 'delete_comment', desc = 'Delete review comment' }, + [']r'] = { 'next_comment', desc = 'Next review comment' }, + ['[r'] = { 'prev_comment', desc = 'Previous review comment' }, + }, + comment = { + [''] = { 'submit_comment', mode = 'n', desc = 'Save review comment' }, + [''] = { 'submit_comment', mode = { 'n', 'i' }, desc = 'Save review comment' }, + ['q'] = { 'cancel_comment', desc = 'Cancel review comment', nowait = true }, + [''] = { 'cancel_comment', desc = 'Cancel review comment' }, + }, + message_preview = { + ['q'] = { 'hide_message_preview', desc = 'Close message preview', nowait = true }, + [''] = { 'hide_message_preview', desc = 'Close message preview' }, + ['g?'] = { 'toggle_help', desc = 'Toggle keymap help' }, + }, + help = { + ['g?'] = { 'toggle_help' }, + ['q'] = { 'toggle_help', nowait = true }, + [''] = { 'toggle_help' }, + }, + }, + editor = { + ['og'] = { 'toggle', desc = 'Toggle Opencode window' }, + ['oi'] = { 'open_input', desc = 'Open input window' }, + ['oI'] = { 'open_input_new_session', desc = 'Open input (new session)' }, + ['oN'] = { 'open_session_tab', desc = 'Open new Opencode session tab' }, + ['o<'] = { 'prev_session_tab', desc = 'Previous Opencode session tab' }, + ['o>'] = { 'next_session_tab', desc = 'Next Opencode session tab' }, + ['o?'] = { 'select_session_tab', desc = 'Select Opencode session tab' }, + ['o1'] = { 'select_session_tab', { 1 }, desc = 'Select Opencode session tab 1' }, + ['o2'] = { 'select_session_tab', { 2 }, desc = 'Select Opencode session tab 2' }, + ['o3'] = { 'select_session_tab', { 3 }, desc = 'Select Opencode session tab 3' }, + ['o4'] = { 'select_session_tab', { 4 }, desc = 'Select Opencode session tab 4' }, + ['o5'] = { 'select_session_tab', { 5 }, desc = 'Select Opencode session tab 5' }, + ['o6'] = { 'select_session_tab', { 6 }, desc = 'Select Opencode session tab 6' }, + ['o7'] = { 'select_session_tab', { 7 }, desc = 'Select Opencode session tab 7' }, + ['o8'] = { 'select_session_tab', { 8 }, desc = 'Select Opencode session tab 8' }, + ['o9'] = { 'select_session_tab', { 9 }, desc = 'Select Opencode session tab 9' }, + ['oh'] = { 'select_history', desc = 'Select from history' }, + ['oo'] = { 'open_output', desc = 'Open output window' }, + ['ot'] = { 'toggle_focus', desc = 'Toggle focus' }, + ['oT'] = { 'timeline', desc = 'Session timeline' }, + ['oq'] = { 'close', desc = 'Close Opencode window' }, + ['oQ'] = { 'close_session_tab', desc = 'Close current Opencode session tab' }, + ['os'] = { 'select_session', desc = 'Select session' }, + ['oS'] = { 'navigate_session_tree', { 'child', 'picker' }, desc = 'Select child session' }, + ['oP'] = { 'navigate_session_tree', { 'parent' }, desc = 'Go to parent session' }, + ['oB'] = { 'navigate_session_tree', { 'sibling', 'picker' }, desc = 'Select sibling session' }, + ['oR'] = { 'rename_session', desc = 'Rename session' }, + ['op'] = { 'configure_provider', desc = 'Configure provider' }, + ['oV'] = { 'configure_variant', desc = 'Configure model variant' }, + ['oy'] = { 'add_visual_selection', mode = { 'v' }, desc = 'Add visual selection to context' }, + ['oY'] = { 'add_visual_selection_inline', mode = { 'v' }, desc = 'Insert visual selection inline into input' }, + ['oz'] = { 'toggle_zoom', desc = 'Toggle zoom' }, + ['ov'] = { 'paste_image', desc = 'Paste image from clipboard' }, + ['od'] = { 'diff_open', desc = 'Open diff view' }, + ['o]'] = { 'diff_next', desc = 'Next diff' }, + ['o['] = { 'diff_prev', desc = 'Previous diff' }, + ['oc'] = { 'diff_close', desc = 'Close diff view' }, + ['ora'] = { 'diff_revert_all_last_prompt', desc = 'Revert all (last prompt)' }, + ['ort'] = { 'diff_revert_this_last_prompt', desc = 'Revert this (last prompt)' }, + ['orA'] = { 'diff_revert_all', desc = 'Revert all changes' }, + ['orT'] = { 'diff_revert_this', desc = 'Revert this change' }, + ['orr'] = { 'diff_restore_snapshot_file', desc = 'Restore file snapshot' }, + ['orR'] = { 'diff_restore_snapshot_all', desc = 'Restore all snapshots' }, + ['ox'] = { 'swap_position', desc = 'Swap window position' }, + ['otr'] = { 'toggle_reasoning_output', desc = 'Toggle reasoning output' }, + ['ott'] = { 'toggle_tool_output', desc = 'Toggle tool output' }, + ['otm'] = { 'toggle_max_messages', desc = 'Toggle max messages' }, + ['o/'] = { 'quick_chat', mode = { 'n', 'x' }, desc = 'Quick chat with current context' }, + ['oDu'] = { 'copy_server_url', desc = 'Copy server url' }, + + }, + output_window = { + ['gg'] = { 'first_message', desc = 'Load history and go to the first message' }, + [''] = { 'close', desc = 'Close Opencode windows' }, + [''] = { 'cancel', desc = 'Cancel running request' }, + [']]'] = { 'next_message', desc = 'Go to next message' }, + ['[['] = { 'prev_message', desc = 'Go to previous message' }, + [']u'] = { 'next_user_message', desc = 'Go to next user message' }, + ['[u'] = { 'prev_user_message', desc = 'Go to previous user message' }, + [''] = { 'toggle_pane', mode = { 'n' }, desc = 'Toggle input/output panes' }, + ['i'] = { 'focus_input', desc = 'Focus input window' }, + ['gr'] = { 'references', desc = 'Browse code references' }, + ['gf'] = { 'jump_to_file', desc = 'Jump to file at cursor' }, + [''] = { 'jump_to_target_at_cursor', desc = 'Jump to target at cursor' }, + ['gd'] = { 'jump_to_target_at_cursor', desc = 'Jump to target at cursor' }, + [''] = { 'toggle_input', mode = { 'n' }, desc = 'Toggle input window' }, + [''] = { 'cycle_variant', mode = { 'n' }, desc = 'Cycle model variants' }, + ['oS'] = { 'navigate_session_tree', { 'child', 'picker' }, desc = 'Select child session' }, + ['oP'] = { 'navigate_session_tree', { 'parent' }, desc = 'Go to parent session' }, + ['oB'] = { 'navigate_session_tree', { 'sibling', 'picker' }, desc = 'Select sibling session' }, + ['oD'] = { 'debug_message', desc = 'Open raw message debug view' }, + ['oO'] = { 'debug_output', desc = 'Open raw output debug view' }, + ['oDs'] = { 'debug_session', desc = 'Open raw session debug view' }, + }, + tab_strip_window = { + [''] = { 'select_session_tab_target', { 'mouse' }, nowait = true, desc = 'Select tab under mouse' }, + ['<2-LeftMouse>'] = { 'select_session_tab_target', { 'mouse' }, nowait = true, desc = 'Select tab under mouse' }, + [''] = { 'select_session_tab_target', { 'cursor' }, nowait = true, desc = 'Select tab under cursor' }, + }, + input_window = { + [''] = { 'submit_input_prompt', mode = { 'n' }, desc = 'Submit prompt' }, + [''] = { 'submit_input_prompt', mode = { 'n', 'i' }, desc = 'Submit prompt' }, + [''] = { 'close', desc = 'Close Opencode windows', defer_to_completion = true }, + [''] = { 'cancel', desc = 'Cancel running request' , defer_to_completion = true }, + ['~'] = { 'mention_file', mode = 'i', desc = 'Mention file in context' }, + ['@'] = { 'mention', mode = 'i', desc = 'Open mention picker' }, + ['/'] = { 'slash_commands', mode = 'i', desc = 'Open slash commands picker' }, + ['#'] = { 'context_items', mode = 'i', desc = 'Open context items picker' }, + [''] = { 'paste_image', mode = 'i', desc = 'Paste image from clipboard' }, + [''] = { 'toggle_pane', mode = { 'n' }, desc = 'Toggle input/output panes', defer_to_completion = true }, + [''] = { 'prev_prompt_history', mode = { 'n', 'i' }, desc = 'Previous prompt history item', defer_to_completion = true }, + [''] = { 'next_prompt_history', mode = { 'n', 'i' }, desc = 'Next prompt history item' , defer_to_completion = true }, + [''] = { 'switch_mode', mode = { 'n', 'i' }, desc = 'Switch agent mode' }, + [''] = { 'cycle_variant', mode = { 'n', 'i' }, desc = 'Cycle model variants' }, + [''] = { 'toggle_input', mode = { 'n', 'i' }, desc = 'Toggle input window' }, + ['gr'] = { 'references', desc = 'Browse code references' }, + ['oS'] = { 'navigate_session_tree', { 'child', 'picker' }, desc = 'Select child session' }, + ['oP'] = { 'navigate_session_tree', { 'parent' }, desc = 'Go to parent session' }, + ['oB'] = { 'navigate_session_tree', { 'sibling', 'picker' }, desc = 'Select sibling session' }, + ['oD'] = { 'debug_message', desc = 'Open raw message debug view' }, + ['oO'] = { 'debug_output', desc = 'Open raw output debug view' }, + ['oDs'] = { 'debug_session', desc = 'Open raw session debug view' }, + }, + session_picker = { + rename_session = { '', desc = 'Rename selected session' }, + delete_session = { '', desc = 'Delete selected sessions' }, + new_session = { '', desc = 'Create a new session' }, + open_in_tab = { '', desc = 'Open selected session in a new panel tab' }, + fork_session = { '', desc = 'Fork selected session' }, + toggle_scope = { '', desc = 'Toggle between project/global scope' }, + }, + session_tab_picker = { + new_tab = { '', desc = 'Create a new panel tab' }, + close_tab = { '', desc = 'Close selected panel tab' }, + }, + timeline_picker = { + undo = { '', mode = { 'i', 'n' }, desc = 'Undo to selected message' }, + fork = { '', mode = { 'i', 'n' }, desc = 'Fork from selected message' }, + }, + history_picker = { + delete_entry = { '', mode = { 'i', 'n' }, desc = 'Delete selected history entries' }, + clear_all = { '', mode = { 'i', 'n' }, desc = 'Clear all history entries' }, + }, + model_picker = { + toggle_favorite = { '', mode = { 'i', 'n' }, desc = 'Toggle model favorite' }, + }, + mcp_picker = { + toggle_connection = { '', mode = { 'i', 'n' }, desc = 'Toggle MCP server connection' }, + }, + quick_chat = { + cancel = { '', mode = { 'i', 'n' }, desc = 'Cancel active quick chat requests' }, + }, + }, + ui = { + enable_treesitter_markdown = true, + position = 'right', + input_position = 'bottom', + window_width = 0.40, + zoom_width = 0.8, + float = { + width = 0.95, + height = 0.9, + row = nil, + col = nil, + border = 'rounded', + gap = 1, + zindex = 40, + opts = { + winblend = 0, + }, + }, + picker_width = 100, + display_model = true, + display_context_size = true, + display_cost = true, + hide_single_tab = true, + notify_on_background_prompt = true, + window_highlight = 'Normal:OpencodeBackground,FloatBorder:OpencodeBorder', + persist_state = true, + icons = { + preset = 'nerdfonts', + overrides = {}, + }, + loading_animation = { + frames = { '⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏' }, + }, + output = { + filetype = 'opencode_output', + time_format = nil, + compact_assistant_headers = false, + actions = { + open_in_new_tab = false, + }, + rendering = { + markdown_debounce_ms = 250, + on_data_rendered = nil, + markdown_on_idle = false, + -- If set to a number, markdown rendering will be deferred while + -- `state.user_message_count[session_id]` is greater than this value. + -- If `nil`, the existing behavior is used (defer while > 0). + markdown_on_idle_threshold = nil, + event_throttle_ms = 40, + event_collapsing = true, + }, + tools = { + show_output = true, + show_reasoning_output = true, + use_folds = true, + fold_exclude = { { server = 'sequential-thinking', tool = 'sequentialthinking' } }, + -- Reduced default threshold to make small tool outputs foldable by default. + -- Users can override this in their config if they prefer the previous value. + folding_threshold = 25, + }, + + max_messages = nil, + always_scroll_to_bottom = false, + }, + questions = { + use_vim_ui_select = false, -- If true, render questions with vim.ui.select instead of in the output buffer + inline_other_input = true, -- If true, show an inline floating input for "Other" instead of cmdline prompt + }, + input = { + min_height = 0.10, + max_height = 0.25, + text = { + wrap = false, + }, + -- Auto-hide input window when prompt is submitted or focus switches to output window + auto_hide = false, + -- Window-local options applied to the input window. + -- Any valid Neovim window option can be added here. + -- Users can override these and add any extra option, e.g.: + -- win_options = { signcolumn = 'no', cursorline = true, conceallevel = 2 } + win_options = { + signcolumn = 'yes', + cursorline = false, + number = false, + relativenumber = false, + }, + }, + picker = { + snacks_layout = nil, + }, + completion = { + file_sources = { + enabled = true, + preferred_cli_tool = 'server', + ignore_patterns = { + '^%.git/', + '^%.svn/', + '^%.hg/', + '^%.jj/', + 'node_modules/', + '%.pyc$', + '%.o$', + '%.obj$', + '%.exe$', + '%.dll$', + '%.so$', + '%.dylib$', + '%.class$', + '%.jar$', + '%.war$', + '%.ear$', + 'target/', + 'build/', + 'dist/', + 'out/', + 'deps/', + '%.tmp$', + '%.temp$', + '%.log$', + '%.cache$', + }, + max_files = 10, + max_display_length = 50, + }, + }, + }, + context = { + enabled = true, + cursor_data = { + enabled = false, + context_lines = 5, -- Number of lines before and after cursor to include in context + }, + diagnostics = { + enabled = true, + info = false, + warning = true, + error = true, + only_closest = true, -- Only diagnostics for cursor/selection; disable to include the whole buffer + }, + current_file = { + enabled = true, + show_full_path = true, + }, + files = { + enabled = true, + show_full_path = true, + }, + selection = { + enabled = true, + }, + review_comments = { + enabled = true, + }, + agents = { + enabled = true, + }, + buffer = { + enabled = false, -- Disable entire buffer context by default, only used in quick chat + }, + git_diff = { + enabled = false, + }, + }, + logging = { + enabled = false, + level = 'info', -- debug, info, warn, error + outfile = nil, + }, + debug = { + enabled = false, + capture_streamed_events = false, + show_ids = true, + highlight_changed_lines = false, + highlight_changed_lines_timeout_ms = 120, + quick_chat = { + keep_session = false, + set_active_session = false, + }, + }, + prompt_guard = nil, + child_readonly = true, + snapshot_path = nil, + hooks = { + on_file_edited = nil, + on_session_loaded = nil, + on_done_thinking = nil, + on_permission_requested = nil, + on_question_asked = nil, + on_topbar_render = nil, + on_footer_render = nil, + }, + quick_chat = { + default_model = nil, + default_agent = nil, + instructions = nil, -- Use instructions prompt by default + }, + }) +< +Lua API: require('opencode.api'). Action signatures: lua/opencode/types.lua. + +For local V2 installations, leave server settings unset. The plugin discovers +OpenCode's native background service. Exiting Neovim does not stop that service. + +============================================================================== +DOCUMENTATION *opencode-docs* + +Guides, reference, and recipes live alongside the code in docs/: + + https://github.com/sudo-tee/opencode.nvim/blob/main/docs/README.md + +Read docs from your installed commit for version-matched behavior. Useful files: + + docs/getting-started.md Installation and first prompt + docs/usage.md Sessions, tabs, permissions, quick chat + docs/context.md What gets sent with a prompt + docs/review.md V2 diffs and inline comments + docs/configuration.md Windows, keys, rendering, integrations + docs/reference.md Commands and Lua API + docs/servers.md Explicit servers and authentication + docs/troubleshooting.md Diagnosis and bug reports + + vim:tw=78:ts=8:noet:ft=help:norl: From bcfe6f34c818d294b1d454b032d97b60361029b4 Mon Sep 17 00:00:00 2001 From: Francis Belanger Date: Fri, 9 Oct 2026 14:54:16 -0400 Subject: [PATCH 2/8] docs: restructure documentation into guides and reference Replace the monolithic README with a short overview and a docs/ tree covering getting started, usage, context, review, configuration, reference, extensions, servers, and troubleshooting. Trim the vimdoc entry point to a summary that links into docs/, and add a CONTRIBUTING guide with the documentation conventions. Add :Opencode debug events [filename] and :Opencode debug log commands with matching api.debug_events/api.debug_log helpers, so captured events can be exported and the plugin log opened without reaching into Lua. Update the manual testing notes to use the new command. --- CONTRIBUTING.md | 112 ++ README.md | 1568 +---------------- doc/opencode.txt | 437 +---- docs/README.md | 34 +- docs/compatibility.md | 54 + docs/configuration.md | 671 +++++++ docs/context.md | 94 + docs/extensions.md | 125 ++ docs/getting-started.md | 110 ++ docs/recipes/README.md | 20 + docs/recipes/TEMPLATE.md | 50 +- docs/recipes/bidirectional-sync/README.md | 151 +- .../recipes/change-by-change-review/README.md | 163 +- docs/recipes/quiet-blink.md | 58 + docs/recipes/review-session-diff.md | 32 +- docs/recipes/three-state-layout/README.md | 70 +- docs/reference.md | 324 ++++ docs/review.md | 110 ++ docs/servers.md | 168 ++ docs/troubleshooting.md | 122 ++ docs/usage.md | 143 ++ lua/opencode/api.lua | 10 + lua/opencode/commands/handlers/workflow.lua | 29 +- lua/opencode/config.lua | 1 - lua/opencode/ui/debug_helper.lua | 16 + tests/manual/README.md | 2 +- tests/unit/api_spec.lua | 14 + tests/unit/commands_handlers_spec.lua | 21 + 28 files changed, 2503 insertions(+), 2206 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 docs/compatibility.md create mode 100644 docs/configuration.md create mode 100644 docs/context.md create mode 100644 docs/extensions.md create mode 100644 docs/getting-started.md create mode 100644 docs/recipes/README.md create mode 100644 docs/recipes/quiet-blink.md create mode 100644 docs/reference.md create mode 100644 docs/review.md create mode 100644 docs/servers.md create mode 100644 docs/troubleshooting.md create mode 100644 docs/usage.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..e5000107 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,112 @@ +# Contributing + +Small, focused changes are easiest to review. For a bug report, start with +[Troubleshooting](docs/troubleshooting.md#reporting-a-bug). For a feature or +architecture change, describe the workflow first so we can agree on its scope. + +## Development + +Run commands from the repository root. `make help` lists targets and examples. + +| Command | Purpose | +| --- | --- | +| `make check` | Formatting checks, Lua type checks, and all tests | +| `make format-check` | Check Lua formatting without changing files | +| `make typecheck` | Check Lua types | +| `make test` | Run all tests | +| `make test-minimal` / `make test-unit` | Run a smaller suite | +| `make test-replay` | Run V1/V2 snapshot replay tests | +| `make replay` | Launch the interactive replay tester | +| `make replay-regenerate` | Regenerate expected snapshots with confirmation | +| `make topology` | Scan dependency topology | +| `make topology-diff` | Compare dependency topology snapshots | + +Use `TEST` for a suite or file, `FILTER` for test-name filtering, and `ARGS` for +underlying tool options: + +```sh +make test TEST=tests/unit/formatter_spec.lua +make test TEST=unit FILTER="Timer" +make typecheck ARGS="-f github" +make replay ARGS="-c ReplayAll" +make replay-regenerate FILE=v2/formatters.json +make topology ARGS="--json" +make topology-diff ARGS="--from main --to HEAD --json" +``` + +For replay regeneration, `FILE` is relative to `tests/data`. See +[dependency topology docs](scripts/dependency-topology/README.md) for scanner +options and policy details. + +Read [`AGENTS.md`](AGENTS.md) and any instructions in the directory you change. +For Lua changes, format **changed files only** with `stylua`, run +`make format-check`, and get `make typecheck` to zero errors. Run relevant tests +and report missing tools or unrelated failures rather than silently fixing +unrelated files. `make format` formats the whole project; do not use it for a +small patch. + +## Documentation + +Documentation lives in this repository, not a wiki. Update it in the same PR as +the code it describes. + +### Where a change belongs + +- **README:** what the plugin does, installation, a quick start, and links. + No defaults table or full action list. +- **Guides in `docs/`:** explain a task in the order someone performs it. +- **Reference pages:** exact settings, command/API contracts, and caveats. +- **Recipes:** optional, self-contained workflows with prerequisites and undo + instructions. Start from the [template](docs/recipes/TEMPLATE.md). +- **`doc/opencode.txt`:** a short `:help` entry point that points to `docs/`. + +### Writing and examples + +Write for someone in the middle of a task. Say what to press and what happens. +Skip adjectives like “seamless” or “powerful”; an example says more. Label +experimental and V1-only behavior in the section that describes it. + +Describe what the user sees, not how the code works. Internal names, state +fields, and protocol details belong in code comments or the PR description. +Add a warning only when an action can lose work or leak data, and say it once. + +Check defaults and keys against [`config.lua`](lua/opencode/config.lua), API +names against [`api.lua`](lua/opencode/api.lua), and commands against their +[handlers](lua/opencode/commands/handlers). The full defaults table lives only in +[`docs/configuration.md`](docs/configuration.md); update it when `config.lua` +changes. Provider, agent, and server settings belong to OpenCode, so link to its +docs instead of repeating them. + +Lua examples should run as pasted. Say whether a block goes in a lazy.nvim spec +or in `setup()`. When showing a remap, disable the old key explicitly. + +### Screenshots and videos + +Capture the real plugin, not a mockup: + +- Use a small project with readable code. Keep one theme and font size across + a set of images, and crop empty space rather than context. +- Show the action and its result. Cut install waits and long model thinking; + aim for 15–30 seconds per clip. +- Remove secrets, personal paths, private prompts, and URLs that contain + credentials. `oDu` copies such a URL. +- Write alt text that describes what the image shows. Give a video a one-line + summary in the surrounding text. +- Upload recordings as GitHub attachments and put the URL on its own line + instead of using a `