irongit

My nvim configuration

feat(claude): add AI-generated commit message command

Add `<leader>cg` mapping that pipes the staged diff to `claude -p`,
strips markdown fences, and commits with the returned message.
huncholanehuncholaneauthored
parent 67d01e3commit 664edbf0e13ee23c71d823859b305ab0290a9b08Browse files

2 files changed, +71 -0

+68-0claude-nvim/lua/claude/commit.lua
@@ -0,0 +1,68 @@
1+local M = {}
2+
3+local skip_permissions = false
4+
5+function M.setup(opts)
6+ if opts and opts.skip_permissions then skip_permissions = true end
7+end
8+
9+local function strip_fences(text)
10+ local lines = vim.split(text or "", "\n", { plain = true })
11+ while #lines > 0 and lines[#lines] == "" do table.remove(lines) end
12+ if #lines >= 2 and lines[1]:match("^```") then
13+ table.remove(lines, 1)
14+ if lines[#lines] and lines[#lines]:match("^```%s*$") then
15+ table.remove(lines)
16+ end
17+ end
18+ return table.concat(lines, "\n")
19+end
20+
21+function M.commit()
22+ vim.system({ "git", "diff", "--staged" }, { text = true }, vim.schedule_wrap(function(diff_out)
23+ if diff_out.code ~= 0 then
24+ vim.notify("git diff failed: " .. (diff_out.stderr or ""), vim.log.levels.ERROR)
25+ return
26+ end
27+ local diff = diff_out.stdout or ""
28+ if diff:match("^%s*$") then
29+ vim.notify("claude: no staged changes", vim.log.levels.WARN)
30+ return
31+ end
32+ vim.notify("claude: writing commit message…")
33+ local prompt = "Write a Conventional Commits style commit message for the following staged git diff. "
34+ .. "Subject line under 72 chars, imperative mood. Optional body explains why if non-obvious. "
35+ .. "Output ONLY the commit message. No explanation, no markdown fences.\n\n"
36+ .. diff
37+ local cmd = { "claude", "-p", prompt }
38+ if skip_permissions then table.insert(cmd, "--dangerously-skip-permissions") end
39+ vim.system(cmd, { text = true }, vim.schedule_wrap(function(out)
40+ if out.code ~= 0 then
41+ vim.notify("claude failed: " .. (out.stderr or ""), vim.log.levels.ERROR)
42+ return
43+ end
44+ local msg = strip_fences(out.stdout or ""):gsub("^%s+", ""):gsub("%s+$", "")
45+ if msg == "" then
46+ vim.notify("claude: empty commit message", vim.log.levels.ERROR)
47+ return
48+ end
49+ vim.system(
50+ { "git", "commit", "-F", "-" },
51+ { text = true, stdin = msg },
52+ vim.schedule_wrap(function(commit_out)
53+ if commit_out.code ~= 0 then
54+ vim.notify(
55+ "git commit failed: " .. (commit_out.stderr ~= "" and commit_out.stderr or commit_out.stdout or ""),
56+ vim.log.levels.ERROR
57+ )
58+ return
59+ end
60+ local subject = msg:match("^[^\n]*") or msg
61+ vim.notify("commit: " .. subject)
62+ end)
63+ )
64+ end))
65+ end))
66+end
67+
68+return M
+3-0claude-nvim/lua/claude/init.lua
@@ -6,6 +6,7 @@ local highlights = require("claude.highlights")
66 local pickers = require("claude.pickers")
77 local edit = require("claude.edit")
88 local timetravel = require("claude.timetravel")
9+local commit = require("claude.commit")
910
1011 ---@class ClaudeConfig
1112 local defaults = {
@@ -29,6 +30,7 @@ function M.setup(opts)
2930 runner.setup(opts)
3031 highlights.setup(opts)
3132 edit.setup(opts)
33+ commit.setup(opts)
3234
3335 vim.keymap.set("n", "<leader>c", "", { desc = "Claude" })
3436
@@ -36,6 +38,7 @@ function M.setup(opts)
3638 vim.keymap.set("n", "<leader>ci", edit.prompt_insert, { desc = "Claude: Insert at cursor with prompt" })
3739 vim.keymap.set("n", "<leader>cu", timetravel.undo, { desc = "Claude: Undo last claude change" })
3840 vim.keymap.set("n", "<leader>cU", timetravel.redo, { desc = "Claude: Redo claude change" })
41+ vim.keymap.set("n", "<leader>cg", commit.commit, { desc = "Claude: Commit staged with generated message" })
3942 vim.keymap.set("n", "<leader>ct", ui.toggle, { desc = "Claude: Toggle panel" })
4043 vim.keymap.set("n", "<leader>cn", ui.start_new, { desc = "Claude: New session" })
4144 vim.keymap.set("n", "<leader>cr", ui.resume_last, { desc = "Claude: Resume last session" })