Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion home-manager/programs/neovim/lua/ai.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,70 @@
-- Integrates an AI workspace with chat, prompts, and actions inside Neovim.
-- AI integration for Neovim
-- Includes sidekick.nvim and opencode.nvim (opencode AI assistant integration)

-- ====================================================================================
-- SIDEKICK (AI workspace with chat, prompts, and actions)
-- From: https://github.com/folke/sidekick.nvim
-- ====================================================================================
require("sidekick").setup({})

-- ====================================================================================
-- SNACKS (Required for opencode.nvim)
-- From: https://github.com/folke/snacks.nvim
-- ====================================================================================
require("snacks").setup({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium

Missing error handling for snacks.nvim setup. If the plugin isn't installed, this will cause Neovim to fail on startup. Consider wrapping in pcall() for graceful degradation:

local ok, snacks = pcall(require, "snacks")
if ok then
  snacks.setup({
    input = {},
    picker = {},
    terminal = {},
  })
else
  vim.notify("snacks.nvim not available - opencode.nvim will not function", vim.log.levels.WARN)
end

This pattern is already used elsewhere in the codebase (e.g., utils.lua line 64 for nvim-web-devicons).

Agent: 🏛 Architecture • Fix in Cursor • Fix in Claude

Prompt for Agent
Task: Address review feedback left on GitHub.
Repository: shunkakinoki/dotfiles#575
File: home-manager/programs/neovim/lua/ai.lua#L14
Action: Open this file location in your editor, inspect the highlighted code, and resolve the issue described below.

Feedback:
Missing error handling for snacks.nvim setup. If the plugin isn't installed, this will cause Neovim to fail on startup. Consider wrapping in pcall() for graceful degradation:

```lua
local ok, snacks = pcall(require, "snacks")
if ok then
  snacks.setup({
    input = {},
    picker = {},
    terminal = {},
  })
else
  vim.notify("snacks.nvim not available - opencode.nvim will not function", vim.log.levels.WARN)
end

This pattern is already used elsewhere in the codebase (e.g., utils.lua line 64 for nvim-web-devicons).


</details>

input = {},
picker = {},
terminal = {},
})

-- ====================================================================================
-- OPENCODE.NVIM (opencode AI assistant integration)
-- From: https://github.com/NickvanDyke/opencode.nvim
-- ====================================================================================

-- Configuration options
---@type opencode.Opts
vim.g.opencode_opts = {
-- Use default configuration
}

-- Required for opts.events.reload
vim.o.autoread = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium

Setting vim.o.autoread = true globally affects all buffers in Neovim, not just opencode-related buffers. This means any file modified externally will be automatically reloaded without warning.

While necessary for opencode's reload functionality, this could cause unexpected behavior if users are working with files that are also being modified by external processes. Consider documenting this side effect more prominently, perhaps with a comment explaining the global impact.

Agent: 🏛 Architecture • Fix in Cursor • Fix in Claude

Prompt for Agent
Task: Address review feedback left on GitHub.
Repository: shunkakinoki/dotfiles#575
File: home-manager/programs/neovim/lua/ai.lua#L32
Action: Open this file location in your editor, inspect the highlighted code, and resolve the issue described below.

Feedback:
Setting `vim.o.autoread = true` globally affects all buffers in Neovim, not just opencode-related buffers. This means any file modified externally will be automatically reloaded without warning.

While necessary for opencode's reload functionality, this could cause unexpected behavior if users are working with files that are also being modified by external processes. Consider documenting this side effect more prominently, perhaps with a comment explaining the global impact.


Comment on lines +31 to +33

Copilot AI Jan 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting autoread globally affects all buffers in Neovim, not just opencode buffers. This can cause unexpected behavior where files are automatically reloaded without user confirmation when they change on disk. Consider whether this global setting is necessary, or if opencode.nvim should handle buffer reloading through its own mechanisms without requiring a global option change.

Suggested change
-- Required for opts.events.reload
vim.o.autoread = true

Copilot uses AI. Check for mistakes.
-- Keymaps for opencode
-- <C-a> - Ask opencode about current context
vim.keymap.set({ "n", "x" }, "<C-a>", function()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium

The keymaps for opencode should also be wrapped in error handling. If opencode.nvim fails to load, these keymaps will cause runtime errors when triggered. Consider:

local opencode_ok, opencode = pcall(require, "opencode")
if opencode_ok then
  vim.keymap.set({ "n", "x" }, "<C-a>", function()
    opencode.ask("@this: ", { submit = true })
  end, { desc = "Ask opencode" })
  -- ... other keymaps
end

This ensures graceful degradation if the plugin is unavailable.

Agent: 🏛 Architecture • Fix in Cursor • Fix in Claude

Prompt for Agent
Task: Address review feedback left on GitHub.
Repository: shunkakinoki/dotfiles#575
File: home-manager/programs/neovim/lua/ai.lua#L36
Action: Open this file location in your editor, inspect the highlighted code, and resolve the issue described below.

Feedback:
The keymaps for opencode should also be wrapped in error handling. If opencode.nvim fails to load, these keymaps will cause runtime errors when triggered. Consider:

```lua
local opencode_ok, opencode = pcall(require, "opencode")
if opencode_ok then
  vim.keymap.set({ "n", "x" }, "<C-a>", function()
    opencode.ask("@this: ", { submit = true })
  end, { desc = "Ask opencode" })
  -- ... other keymaps
end

This ensures graceful degradation if the plugin is unavailable.


</details>

require("opencode").ask("@this: ", { submit = true })
end, { desc = "Ask opencode" })

-- <C-x> - Execute opencode action from selection menu
vim.keymap.set({ "n", "x" }, "<C-x>", function()
require("opencode").select()
end, { desc = "Execute opencode action…" })

-- <C-.> - Toggle opencode terminal
vim.keymap.set({ "n", "t" }, "<C-.>", function()
require("opencode").toggle()
end, { desc = "Toggle opencode" })
Comment on lines +46 to +48

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This introduces a keymap <C-.> that conflicts with an existing keymap for sidekick defined in keymaps.lua:257. The sidekick mapping is for modes n, t, i, x, while this new mapping is for n, t. Since ai.lua is loaded after keymaps.lua, this mapping will override the sidekick one in normal and terminal modes, but not in insert or visual mode. This leads to inconsistent behavior where <C-.> does different things depending on the mode. This conflict should be resolved by choosing a different keymap for one of the actions or by removing the conflicting one if it's no longer needed.


-- Operator mode: add range to opencode prompt
vim.keymap.set({ "n", "x" }, "go", function()
return require("opencode").operator("@this ")
end, { expr = true, desc = "Add range to opencode" })

vim.keymap.set("n", "goo", function()
return require("opencode").operator("@this ") .. "_"
end, { expr = true, desc = "Add line to opencode" })
Comment on lines +51 to +57

Copilot AI Jan 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The go keymap conflicts with the existing buffer-local go keymap defined in autocmds.lua (line 217) for fugitive buffers. This creates a keybinding collision where the opencode keymap will be shadowed by the fugitive buffer-local mapping. Consider using a different key combination like gO or <leader>go to avoid conflicts.

Suggested change
vim.keymap.set({ "n", "x" }, "go", function()
return require("opencode").operator("@this ")
end, { expr = true, desc = "Add range to opencode" })
vim.keymap.set("n", "goo", function()
return require("opencode").operator("@this ") .. "_"
end, { expr = true, desc = "Add line to opencode" })
vim.keymap.set({ "n", "x" }, "gO", function()
return require("opencode").operator("@this ")
end, { expr = true, desc = "Add range to opencode (gO)" })
vim.keymap.set("n", "gOO", function()
return require("opencode").operator("@this ") .. "_"
end, { expr = true, desc = "Add line to opencode (gOO)" })

Copilot uses AI. Check for mistakes.

-- Scroll keymaps for opencode session
vim.keymap.set("n", "<S-C-u>", function()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Ctrl+Shift-U/D mappings likely collapse to Ctrl-U/D and override default half-page scroll keys globally, breaking normal navigation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/programs/neovim/lua/ai.lua, line 60:

<comment>Ctrl+Shift-U/D mappings likely collapse to Ctrl-U/D and override default half-page scroll keys globally, breaking normal navigation.</comment>

<file context>
@@ -1,3 +1,70 @@
+end, { expr = true, desc = "Add line to opencode" })
+
+-- Scroll keymaps for opencode session
+vim.keymap.set("n", "<S-C-u>", function()
+	require("opencode").command("session.half.page.up")
+end, { desc = "opencode half page up" })
</file context>

require("opencode").command("session.half.page.up")
end, { desc = "opencode half page up" })

vim.keymap.set("n", "<S-C-d>", function()
Comment on lines +60 to +64

Copilot AI Jan 15, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The <S-C-u> and <S-C-d> keymaps may not work reliably in all terminal emulators. Shift+Ctrl combinations with letter keys are often not distinguishable from their non-shifted counterparts in terminal Neovim. Consider using alternative keymaps that work consistently across terminals, such as <leader>u and <leader>d or using the standard <C-u> and <C-d> with a prefix key.

Suggested change
vim.keymap.set("n", "<S-C-u>", function()
require("opencode").command("session.half.page.up")
end, { desc = "opencode half page up" })
vim.keymap.set("n", "<S-C-d>", function()
vim.keymap.set("n", "<leader>u", function()
require("opencode").command("session.half.page.up")
end, { desc = "opencode half page up" })
vim.keymap.set("n", "<leader>d", function()

Copilot uses AI. Check for mistakes.
require("opencode").command("session.half.page.down")
end, { desc = "opencode half page down" })

-- Remap increment/decrement since we use <C-a> and <C-x> for opencode
vim.keymap.set("n", "+", "<C-a>", { desc = "Increment", noremap = true })
vim.keymap.set("n", "-", "<C-x>", { desc = "Decrement", noremap = true })
Comment on lines +34 to +70

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The keymaps for opencode.nvim are defined here in ai.lua. However, there is a central keymaps.lua file where other keymaps, including those for sidekick, are defined. To maintain consistency and have a single source of truth for keybindings, it would be better to move these opencode.nvim keymaps to keymaps.lua, under a new OPENCODE section. This would also make the <C-.> conflict with sidekick more apparent and easier to manage.

Comment on lines +69 to +70

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These keymaps override the default behavior of + and - in normal mode, which are standard Vim motions for moving to the next and previous line respectively. While this is documented in the pull request description, it's a significant departure from default Vim behavior that could be disruptive and surprising. Have you considered using different keys for increment/decrement to avoid overriding these built-in motions? For example, <leader>+ and <leader>- could be alternatives that don't clash with default functionality.

2 changes: 2 additions & 0 deletions home-manager/programs/neovim/lua/plugins.lua
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ vim.pack.add({
{ src = "https://github.com/yioneko/nvim-vtsls" },

-- AI
{ src = "https://github.com/folke/snacks.nvim" }, -- Required for opencode.nvim

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: New plugins added without corresponding lock entries—snacks.nvim and opencode.nvim are unpinned, undermining reproducibility.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/programs/neovim/lua/plugins.lua, line 87:

<comment>New plugins added without corresponding lock entries—snacks.nvim and opencode.nvim are unpinned, undermining reproducibility.</comment>

<file context>
@@ -84,7 +84,9 @@ vim.pack.add({
 	{ src = "https://github.com/yioneko/nvim-vtsls" },
 
 	-- AI
+	{ src = "https://github.com/folke/snacks.nvim" }, -- Required for opencode.nvim
 	{ src = "https://github.com/folke/sidekick.nvim" },
+	{ src = "https://github.com/NickvanDyke/opencode.nvim" },
</file context>

{ src = "https://github.com/folke/sidekick.nvim" },
{ src = "https://github.com/NickvanDyke/opencode.nvim" },

-- TERMINAL
{ src = "https://github.com/akinsho/toggleterm.nvim" },
Expand Down
133 changes: 132 additions & 1 deletion home-manager/programs/neovim/tests/ai_spec.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-- Tests for lua/ai.lua
-- Tests AI/sidekick integration (plugin not loaded in minimal test env)
-- Tests AI integrations: sidekick and opencode.nvim

describe("ai", function()
describe("sidekick API", function()
Expand All @@ -10,6 +10,67 @@ describe("ai", function()
end)
end)

describe("snacks API", function()
it("should have expected snacks components structure", function()
-- snacks.nvim provides input, picker, and terminal
local expected_components = { "input", "picker", "terminal" }
for _, component in ipairs(expected_components) do
assert.is_string(component)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: AI integration test is tautological: only asserts hardcoded strings are strings, so it cannot fail even if snacks/opencode integrations are broken.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/programs/neovim/tests/ai_spec.lua, line 18:

<comment>AI integration test is tautological: only asserts hardcoded strings are strings, so it cannot fail even if snacks/opencode integrations are broken.</comment>

<file context>
@@ -10,6 +10,67 @@ describe("ai", function()
+			-- snacks.nvim provides input, picker, and terminal
+			local expected_components = { "input", "picker", "terminal" }
+			for _, component in ipairs(expected_components) do
+				assert.is_string(component)
+			end
+		end)
</file context>

end
end)
Comment on lines +14 to +20

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The test should have expected snacks components structure is tautological. It defines a local table and then asserts properties of that same table. It doesn't test any interaction with the snacks.nvim plugin or its configuration. This provides no value and can be misleading about test coverage. Please consider removing this test or changing it to assert something meaningful about the integration, if possible within your testing framework.

end)

describe("opencode.nvim API", function()
it("should support opencode_opts global variable", function()
-- opencode.nvim uses vim.g.opencode_opts for configuration
vim.g.opencode_opts = {}
assert.is_table(vim.g.opencode_opts)
vim.g.opencode_opts = nil
end)

it("should support autoread option for reload events", function()
-- Required for opencode reload functionality
vim.o.autoread = true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Test sets vim.o.autoread globally without restoring the prior value, causing state leakage between tests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/programs/neovim/tests/ai_spec.lua, line 33:

<comment>Test sets vim.o.autoread globally without restoring the prior value, causing state leakage between tests.</comment>

<file context>
@@ -10,6 +10,67 @@ describe("ai", function()
+
+		it("should support autoread option for reload events", function()
+			-- Required for opencode reload functionality
+			vim.o.autoread = true
+			assert.equals(true, vim.o.autoread)
+		end)
</file context>

assert.equals(true, vim.o.autoread)
end)

it("should support context placeholders pattern", function()
-- opencode.nvim uses context placeholders like @this, @buffer, etc.
local placeholders = {
"@this",
"@buffer",
"@buffers",
"@visible",
"@diagnostics",
"@quickfix",
"@diff",
"@marks",
}
for _, placeholder in ipairs(placeholders) do
assert.is_string(placeholder)
assert.is_true(placeholder:sub(1, 1) == "@")
end
end)
Comment on lines +37 to +53

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test for context placeholders is tautological. It defines a local table placeholders and asserts its contents. It doesn't test anything about opencode.nvim itself. This test provides a false sense of coverage as it will pass regardless of the plugin's actual implementation. If the goal is to document the placeholders, a comment in the configuration file would be more appropriate. As a test, this provides little value and should be refactored or removed.


it("should support prompt library pattern", function()
-- opencode.nvim includes built-in prompts
local prompts = {
"diagnostics",
"diff",
"document",
"explain",
"fix",
"implement",
"optimize",
"review",
"test",
}
for _, prompt in ipairs(prompts) do
assert.is_string(prompt)
end
end)
Comment on lines +55 to +71

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to other new tests in this file, this test for the prompt library is tautological. It verifies a hardcoded list of strings. This doesn't ensure that opencode.nvim actually provides these prompts or that they are correctly configured. This test is misleading about the actual test coverage and should be removed or refactored to be a meaningful test of the plugin's integration.

end)

describe("AI keymaps expected", function()
it("should support leader-based AI keymaps pattern", function()
-- Verify keymap API works for AI-style mappings
Expand All @@ -18,5 +79,75 @@ describe("ai", function()
assert.is_true(keymap ~= "")
vim.keymap.del("n", "<leader>ai_test")
end)

it("should support opencode ask keymap pattern", function()
-- Test <C-a> style keymap for ask
vim.keymap.set({ "n", "x" }, "<C-a>", function() end, { desc = "Ask opencode" })
local keymap_n = vim.fn.maparg("<C-a>", "n")
local keymap_x = vim.fn.maparg("<C-a>", "x")
assert.is_true(keymap_n ~= "")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Keymap existence check is unreliable: maparg() returns empty string for Lua callback mappings, so these assertions can fail even when the mapping exists.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At home-manager/programs/neovim/tests/ai_spec.lua, line 88:

<comment>Keymap existence check is unreliable: maparg() returns empty string for Lua callback mappings, so these assertions can fail even when the mapping exists.</comment>

<file context>
@@ -18,5 +79,75 @@ describe("ai", function()
+			vim.keymap.set({ "n", "x" }, "<C-a>", function() end, { desc = "Ask opencode" })
+			local keymap_n = vim.fn.maparg("<C-a>", "n")
+			local keymap_x = vim.fn.maparg("<C-a>", "x")
+			assert.is_true(keymap_n ~= "")
+			assert.is_true(keymap_x ~= "")
+			vim.keymap.del("n", "<C-a>")
</file context>

assert.is_true(keymap_x ~= "")
vim.keymap.del("n", "<C-a>")
vim.keymap.del("x", "<C-a>")
end)

it("should support opencode select keymap pattern", function()
-- Test <C-x> style keymap for select
vim.keymap.set({ "n", "x" }, "<C-x>", function() end, { desc = "Execute opencode action" })
local keymap_n = vim.fn.maparg("<C-x>", "n")
local keymap_x = vim.fn.maparg("<C-x>", "x")
assert.is_true(keymap_n ~= "")
assert.is_true(keymap_x ~= "")
vim.keymap.del("n", "<C-x>")
vim.keymap.del("x", "<C-x>")
end)

it("should support opencode toggle keymap pattern", function()
-- Test <C-.> style keymap for toggle
vim.keymap.set({ "n", "t" }, "<C-.>", function() end, { desc = "Toggle opencode" })
local keymap_n = vim.fn.maparg("<C-.>", "n")
local keymap_t = vim.fn.maparg("<C-.>", "t")
assert.is_true(keymap_n ~= "")
assert.is_true(keymap_t ~= "")
vim.keymap.del("n", "<C-.>")
vim.keymap.del("t", "<C-.>")
end)

it("should support operator mode keymaps", function()
-- Test operator mode keymaps (go, goo)
vim.keymap.set({ "n", "x" }, "go", function()
return ""
end, { expr = true, desc = "Add range to opencode" })
local keymap = vim.fn.maparg("go", "n")
assert.is_true(keymap ~= "")
vim.keymap.del("n", "go")
vim.keymap.del("x", "go")
end)

it("should support remapped increment/decrement", function()
-- When <C-a> and <C-x> are used for opencode, + and - replace them
vim.keymap.set("n", "+", "<C-a>", { desc = "Increment", noremap = true })
vim.keymap.set("n", "-", "<C-x>", { desc = "Decrement", noremap = true })
local keymap_plus = vim.fn.maparg("+", "n")
local keymap_minus = vim.fn.maparg("-", "n")
assert.is_true(keymap_plus ~= "")
assert.is_true(keymap_minus ~= "")
vim.keymap.del("n", "+")
vim.keymap.del("n", "-")
end)
end)

describe("opencode command pattern", function()
it("should support session scroll commands", function()
-- opencode supports command() for session control
local scroll_commands = {
"session.half.page.up",
"session.half.page.down",
}
for _, cmd in ipairs(scroll_commands) do
assert.is_string(cmd)
assert.is_true(cmd:find("^session%.") ~= nil)
end
end)
end)
end)
Loading