This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
fude.nvim is a Neovim plugin for GitHub PR code review. It shows base branch diffs in a side pane, lets users create/view/reply to review comments with virtual text indicators, browse changed files via Telescope or quickfix, and view PR overviews. Requires Neovim >= 0.10 and GitHub CLI (gh).
- Formatter: StyLua —
stylua lua/ plugin/- Config:
.stylua.toml(tabs, 120 col width, double quotes)
- Config:
- Linter: Luacheck —
luacheck lua/ plugin/- Config:
.luacheckrc(globalvim, 120 char lines)
- Config:
- Help tags:
nvim --headless -c "helptags doc/" -c q - Tests: Plenary busted —
make testorbash run_tests.sh- Test files:
tests/fude/*_spec.lua - Bootstrap:
tests/minimal_init.lua - Shared helpers:
tests/helpers.lua(mock management, test buffer creation, async wait)
- Test files:
- All checks:
make all(lint + format-check + test)
All plugin code lives under lua/fude/. The plugin entry point is plugin/fude.lua which registers user commands.
init.lua— Plugin lifecycle (start/stop/toggle/reload). On start: detects PR viagh, fetches changed files, comments, PR commits (for scope selection), and authenticated user (for edit/delete ownership), saves original HEAD SHA, sets upBufEnter/WinClosed/GitSignsUpdateautocmds, integrates with gitsigns (per-buffer base selection), applies diffopt settings, sets buffer-local keymaps (]c/[c) for comment navigation, and starts auto-reload timer if configured.reloadre-fetches comments, changed files, viewed states, and PR commits in parallel with a completion barrier. On stop: stops auto-reload timer, restores original HEAD if in commit scope, tears everything down, removes buffer-local keymaps, and restores original state. Exports helpers:is_new_file(rel_path, changed_files)checks if a file is newly added in the PR;apply_gitsigns_base_for_buffer()sets per-buffer gitsigns base (new files usebase_ref, existing files usemerge_base_shato exclude merge commit noise, skipped whengitsigns_resetis true);compute_merge_base(base_ref)computes and caches the merge-base SHA;update_gitsigns_base(base_ref)sets global gitsigns base (used for commit scope);toggle_gitsigns()toggles gitsigns between PR base and HEAD;restore_gitsigns_base()restores gitsigns to PR base display.config.lua— Holdsdefaults, mergedopts, and mutablestate(active flag, PR metadata, window/buffer handles, comments, pending_comments, pending_review_id, pr_node_id, viewed_files, github_user, namespace ID).reset_state()preserves the namespace ID.gh.lua— Async wrapper aroundghCLI usingvim.system(). All GitHub API calls go throughrun()/run_json()with callback-based async pattern. Usesrepos/{owner}/{repo}path templates for REST API (resolved byghautomatically) andgh api graphqlfor GraphQL API (used by viewed file management). Supports stdin for JSON payloads (used bycreate_review()).get_pr_infodetects detached HEAD synchronously viagit symbolic-refand usesget_pr_by_commit(viacommits/{sha}/pullsAPI) directly, avoidinggh pr viewwhich may hang without a branch.parse_pr_from_commit_apiconverts the commits API response to the standard PR info format.util.lua— Shared utility functions.is_null(v)checks for bothnilandvim.NIL(JSON null compatibility across Neovim 0.11/0.12).diff.lua— Local git operations (sync). Gets repo root, converts paths to repo-relative, retrieves base branch file content viagit show, generates file diffs, and computes merge-base for gitsigns (avoids merge commit noise). Falls back toorigin/<ref>when local ref isn't available.preview.lua— Manages the side-by-side diff preview window. Creates a scratch buffer with base branch content, opens it in a vsplit, and enablesdiffthison both windows. Usesnoautocmdto prevent BufEnter cascades. Theopeningflag guards against re-entrant calls.comments.lua— Facade module re-exportingcomments/data.lua,comments/sync.lua, andcomments/pickers.lua. Contains comment navigation (next_comment/prev_comment), creation (create_comment/suggest_change), viewing (view_comments), reply (reply_to_comment), editing (edit_comment), and deletion (delete_comment). Also provides ownership helpers:is_own_comment,is_pending_comment,find_pending_key.require("fude.comments")is the public interface.comments/data.lua— Pure data functions with no state or side effects:line_from_diff_hunk,build_comment_map,find_next_comment_line,find_prev_comment_line,find_comment_by_id,get_comment_thread,parse_draft_key,build_pending_comments_from_review,build_review_comment_object,merge_pending_into_comments,pending_comments_to_array,get_comment_line_range,get_reply_target_id,get_comments_at,get_comment_lines,build_comment_entries,build_comment_browser_entries.comments/sync.lua— GitHub API sync/submit operations:load_comments,sync_pending_review,submit_as_review,reply_to_comment,edit_comment,delete_comment.load_commentsis the main entry point: detects pending review viaGET /pulls/{pr}/reviews, then fetches both submitted comments (GET /pulls/{pr}/comments) and pending review comments (GET /reviews/{id}/comments) in a single flow, convertingpositiontolinevialine_from_diff_hunkand building bothcomment_mapandpending_comments. Internalfetch_commentsis used by other functions for refreshing after mutations. Uses lazyrequire("fude.ui")to avoid circular dependencies.comments/pickers.lua— Entry point forlist_comments. Delegates toui/comment_browser.open().
ui.lua— Facade module re-exportingui/format.luaandui/extmarks.lua. Contains floating window UI: comment input editor, comment viewer, PR overview window, reply window, edit window, and review event selector.require("fude.ui")is the public interface.ui/format.lua— Pure format/calculation functions with no state or vim API side effects:calculate_float_dimensions,format_comments_for_display,normalize_check,format_check_status,deduplicate_checks,sort_checks,build_checks_summary,format_review_status,build_reviewers_list,build_reviewers_summary,calculate_overview_layout,calculate_comments_height,calculate_reply_window_dimensions,format_reply_comments_for_display,build_overview_left_lines,build_overview_right_lines,calculate_comment_browser_layout,format_comment_browser_list,format_comment_browser_thread,parse_markdown_line,build_highlighted_chunks,apply_markdown_highlight_to_line.ui/comment_browser.lua— 3-pane floating comment browser forFudeReviewListComments. Left pane: comment list (review + PR-level, time-descending). Right upper: thread display. Right lower: reply/edit/new comment input. Supports reply, edit, delete, new PR comment, jump to file, and refresh. Does not depend on Telescope.ui/sidepanel.lua— Toggleable sidebar showing Review Scope and Changed Files. Pure functions:format_scope_section,format_files_section,build_sidepanel_content,resolve_entry_at_cursor. Side-effect functions:open,close,toggle,refresh. Usesnvim_open_winwithsplitfor sidebar creation. Uses dedicatedfude_sidepanelnamespace for highlights (avoidsrefresh_extmarksclearing them on BufEnter). Auto-refreshes on scope change and reload. Keymaps:<CR>select/open,<Tab>toggle reviewed/viewed,Rreload,qclose.ui/extmarks.lua— Extmark management:flash_line,highlight_comment_lines,clear_comment_line_highlight,refresh_extmarks,clear_extmarks,clear_all_extmarks. Uses lazyrequire("fude.comments")to avoid circular dependencies.
files.lua— Changed files display via Telescope picker, snacks.picker, or quickfix list. All pickers show diff preview and viewed state toggle via<Tab>. Shows GitHub viewed status for each file. Exportsapply_viewed_toggle(path, on_done)as a picker-agnostic state mutator that invokes gh GraphQL mark/unmark, updatesstate.viewed_files, and callson_donewith the updated display fields; the Telescope adaptertoggle_viewed_in_telescopeand the snacks adaptertoggle_viewed_in_snacks(picker, item)both delegate to it.show()routes toshow_telescope/show_snacks/show_quickfixbased onconfig.opts.file_list_mode; snacks falls back to quickfix when snacks.nvim is missing.scope.lua— Review scope selection and navigation. Provides a Telescope picker (orvim.ui.selectfallback) for choosing between full PR scope and individual commit scope, with commit index display ([1/10]) and current scope marker (▶). Supports next/prev scope navigation (next_scope/prev_scope), marking commits as reviewed via<Tab>in the Telescope picker (tracked locally instate.reviewed_commits), and statusline integration (statusline()). On commit scope: checks out the commit, fetches commit-specific changed files, updates gitsigns base tosha^(global), and refreshes the diff preview. On full PR scope: restores the original HEAD, re-fetches PR-wide changed files, and computes merge-base (per-buffer gitsigns base is applied viaGitSignsUpdateautocmd in init.lua). Exportsapply_reviewed_toggle(sha)as a picker-agnostic state mutator that togglesstate.reviewed_commits[sha]and returns the updated display fields{ is_reviewed, reviewed_icon, reviewed_hl }; both the Telescope adaptertoggle_reviewed_in_telescopeand the snacks adaptertoggle_reviewed_in_snacks(picker, item)delegate to it.select_scope()routes toshow_telescope/show_snacks/show_vim_selectbased onconfig.opts.file_list_mode; snacks falls back tovim.ui.selectwhen snacks.nvim is missing.overview.lua— PR overview display: fetches extended PR info and issue-level comments, renders in a centered float with keymaps for commenting and refreshing.pr.lua— PR creation and editing.M.create()creates draft PRs from templates: searches forPULL_REQUEST_TEMPLATEfiles in standard GitHub locations, shows Telescope picker when multiple templates exist, and opens a two-pane float (title + body) for composing the PR. Submits viagh pr create --draft.M.edit()edits existing PR title/body: usesstate.pr_numberwhen review mode is active, otherwise detects viagh pr view. Both functions are independent of review mode (state.active).
- Async flow: GitHub API calls use
vim.system()callbacks withvim.schedule()for safe UI updates. - State management: All mutable state lives in
config.state. Modules read/write this shared table directly. - Namespace: The main Neovim namespace
"fude"(created inconfig.setup()) is used for comment extmarks. Dedicated namespaces are used where isolation is needed:"fude_sidepanel"(sidepanel highlights, avoidsrefresh_extmarksclearing),"fude_inline_hint"(cursor-following hints),"fude_refs"(clickable references),"fude_scope_preview"(Telescope scope preview). - Window management: Preview uses
noautocmdcommands to avoid triggering the plugin's ownBufEnterhandler during window operations. - Pure function extraction: Each module exports testable pure functions separately from side-effect code. Naming convention:
build_*,find_*,parse_*,format_*,should_*,make_*,calculate_*. These functions take all inputs as parameters and return data without readingconfig.stateor calling vim API. - Picker-agnostic state mutators (
apply_*): Functions that mutateconfig.stateand interact with external APIs (e.g.apply_viewed_toggle,apply_reviewed_toggle) but are independent of any specific picker implementation. They take inputs as parameters and either return the updated display fields (sync) or invoke a callback with them (async). Picker-specific adapters (toggle_*_in_<picker>) delegate to these mutators and handle only selection retrieval and picker refresh.
config.state の各フィールドを書き込む(W)モジュールと読み取る(R)モジュールの一覧。変更時は W/R 両方のモジュールへの影響を確認すること。
| Field | W (Write) | R (Read) |
|---|---|---|
active |
init | comments, comments/sync, ui/extmarks, files, scope, preview, overview, ui/sidepanel |
pr_number |
init | comments, comments/sync, ui, files, scope, overview, pr |
base_ref |
init | init, preview, scope |
head_ref |
init | scope |
merge_base_sha |
init, scope | init, scope |
pr_url |
init | ui |
changed_files |
init, init(reload), scope | init, files, scope, ui/sidepanel |
comments |
comments/sync | comments, comments/sync, files, ui/extmarks |
comment_map |
comments/sync | comments, comments/sync, ui/extmarks |
pending_comments |
comments, comments/sync | comments, comments/sync, files, ui/extmarks |
pending_review_id |
comments/sync | comments, comments/sync, comments/pickers, ui/extmarks |
pr_node_id |
init | init, files |
viewed_files |
init, init(reload), files | files, scope, ui/sidepanel |
preview_win |
preview | init, preview |
preview_buf |
preview | preview |
source_win |
preview | preview, scope |
scope |
scope | scope, preview, init |
scope_commit_sha |
scope | scope, preview, init |
scope_commit_index |
scope | scope |
pr_commits |
init, init(reload) | scope, ui/sidepanel |
original_head_sha |
init, scope | init, scope |
original_head_ref |
init | init, scope |
reviewed_commits |
scope | scope, ui/sidepanel |
ns_id |
config | ui/extmarks, comments |
reply_window |
ui | ui |
comment_browser |
ui/comment_browser | ui/comment_browser |
github_user |
init | comments, ui/comment_browser |
current_comment_style |
config | ui/extmarks, comments |
outdated_map |
comments/sync | comments/sync |
reload_timer |
init | init |
reloading |
init | init |
gitsigns_reset |
init, scope | init |
sidepanel |
ui/sidepanel | ui/sidepanel |
高リスクフィールド(多数のモジュールから参照):
active— 6モジュールが参照。変更時は全モジュールのガード条件を確認pr_number— 5モジュールが参照。PR切替時の整合性に注意changed_files— scope変更時に上書きされる。files表示およびgitsignsのper-buffer base判定との同期に注意
- Before committing: Always run
make alland confirm lint, format-check, and tests all pass. Do NOT commit if any check fails. - Tests required for new code: When adding or modifying a function that contains testable logic (pure functions, data access, parsing, etc.), add or update corresponding tests in
tests/fude/. Skip tests only for thin wrappers around vim API or external commands. For functions that interact with vim API (buffers, windows, extmarks) or async callbacks, write integration tests usingtests/helpers.lua:helpers.mock_gh(responses)— Mockgh.run/gh.run_jsonwith pattern-keyed responses ("args[1]:args[2]")helpers.mock_diff(path_map)— Mockdiff.to_repo_relativefor test buffer nameshelpers.wait_for(fn, ms)— Poll withvim.wait()for async callback completionhelpers.cleanup()— Restore all mocks, delete test buffers, reset state (call inafter_each)
- Test coverage check: After writing code, review whether the changed/added functions have test coverage. If not, write tests before committing.
- Formatting: Run
stylua lua/ plugin/ tests/after editing any Lua file to ensure consistent formatting. - Documentation: When adding or changing features, commands, keymaps, or configuration options, update the corresponding documentation (
README.md,doc/fude.txt,CLAUDE.mdArchitecture section) before committing.
- PRタイトル・本文は日本語で記述する(コード例・変数名・ファイルパスは英語のまま)
- コミットメッセージは英語の Conventional Commits 形式 (
feat:,fix:,refactor:,test:,docs:) - レビュー返信は日本語で記述する