diff --git a/config/claude/CLAUDE.md b/config/claude/CLAUDE.md index cae28fa..fc40017 100644 --- a/config/claude/CLAUDE.md +++ b/config/claude/CLAUDE.md @@ -14,6 +14,10 @@ If given express permission to use git, NEVER push to a main/master branch. If given express permission to use git, NEVER commit ai-related files (e.g. CLAUDE.md). +If given express permission to use git, ALWAYS work on a feature branch. If on +`main` or `master`, create and switch to a branch before making changes. Branch +naming: `type/short-description` (e.g. `fix/diagnostic-range`). + If given express permission to use git, ALWAYS use this commit message format: type(scope): imperative summary @@ -21,7 +25,10 @@ If given express permission to use git, ALWAYS use this commit message format: - Valid types: `feat` `fix` `docs` `refactor` `perf` `test` `ci` `build` `revert` - Scope is optional, lowercase. Subject: lowercase after colon, no trailing period, max 72 chars. - Body required for non-trivial commits, using `Problem:` / `Solution:` format. + Keep each section to 2-3 sentences. - One logical change per commit. Refactors, formatting, and features must be separate commits. +- Use backticks around code identifiers, function names, and file paths in + commit messages and PR descriptions (e.g. `setup()`, `lua/oil/view.lua`). If given express permission to use git, ALWAYS check for a PR template at `.github/pull_request_template.md` and follow it. If none exists, use diff --git a/config/claude/hooks/guard.sh b/config/claude/hooks/guard.sh new file mode 100755 index 0000000..7d0d641 --- /dev/null +++ b/config/claude/hooks/guard.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +INPUT=$(cat) +CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty') + +if printf '%s' "$CMD" | grep -qE '\bgh\b.*\s(-R|--repo)\b'; then + echo "Blocked: do not target other repos with -R/--repo. Run gh commands against the current repo only." >&2 + exit 2 +fi + +if printf '%s' "$CMD" | grep -qE '\bgh\s+issue\s+create\b'; then + echo "Blocked: gh issue create must be run manually or explicitly approved." >&2 + exit 2 +fi + +if printf '%s' "$CMD" | grep -qE '\bgit\s+push\b'; then + BRANCH=$(git branch --show-current 2>/dev/null || true) + if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then + echo "Blocked: never push directly to $BRANCH. Use a feature branch." >&2 + exit 2 + fi +fi + +exit 0 diff --git a/config/claude/rules/git.md b/config/claude/rules/git.md index bebf047..a287a0c 100644 --- a/config/claude/rules/git.md +++ b/config/claude/rules/git.md @@ -19,36 +19,43 @@ Solution: describe what this commit does ### Body Required for any non-trivial change. Use `Problem:` / `Solution:` sections. -Wrap at 72 characters. Separate from header with a blank line. +2-3 sentences per section, max. Wrap at 72 characters. Separate from header +with a blank line. + +Use backticks around code identifiers, function names, and file paths +(e.g. `setup()`, `lua/oil/view.lua`, `FIELD_NAME`). ### Examples Good: ``` -fix(lsp): correct off-by-one in diagnostic range +fix(lsp): correct off-by-one in `diagnostic_range` Problem: diagnostics highlighted one character past the actual error, -causing confusion when multiple diagnostics appeared on adjacent tokens. +causing confusion on adjacent tokens. -Solution: subtract 1 from the end column returned by the language server -before converting to 0-indexed nvim columns. +Solution: subtract 1 from the end column returned by the language +server before converting to 0-indexed nvim columns. ``` ``` -refactor: extract repeated buffer lookup into helper +refactor: extract repeated buffer lookup into `get_buf_entry` ``` Bad: ``` -Fixed stuff # not imperative, vague -feat: Add Feature. # uppercase after colon, trailing period +Fixed stuff +feat: Add Feature. fix(lsp): correct off-by-one in diagnostic range and also refactor the -entire highlight module and add new tests # multiple concerns +entire highlight module and add new tests ``` -## Branch Naming +## Branch Rules + +Always work on a feature branch. Never commit or push directly to `main` or +`master`. If on the default branch, create and switch to a topic branch first. ``` type/short-description @@ -65,15 +72,15 @@ If no template exists, fall back to: ``` ## Problem - +<1-2 sentences> ## Solution - +<1-2 sentences> ``` -Either way, write in plain prose. No bullet-point walls, no AI-style markdown -headings beyond what the template calls for. Keep it concise and human. +Write concise prose. No bullet-point walls, no verbose AI-style markdown. +Use backticks for code references. ## Decomposition Rules diff --git a/config/claude/skills/commit/SKILL.md b/config/claude/skills/gc/SKILL.md similarity index 88% rename from config/claude/skills/commit/SKILL.md rename to config/claude/skills/gc/SKILL.md index 13e7a07..81266fa 100644 --- a/config/claude/skills/commit/SKILL.md +++ b/config/claude/skills/gc/SKILL.md @@ -1,4 +1,4 @@ -# /commit +# /gc Create a conventional commit from staged or unstaged changes. @@ -26,7 +26,10 @@ Create a conventional commit from staged or unstaged changes. - Scope is optional, lowercase. - Non-trivial changes require a body with `Problem:` / `Solution:` sections, wrapped at 72 chars, separated from header by a blank line. + - Keep the body tight: 2-3 sentences per section, max. - Trivial one-liners: header alone is fine. + - Use backticks around code identifiers, function names, and file paths + (e.g. `setup()`, `lua/pending/store.lua`). - Match the style of the recent commits from step 1. 4. Present the full message and ask for approval. diff --git a/config/claude/skills/guide/SKILL.md b/config/claude/skills/guide/SKILL.md new file mode 100644 index 0000000..0bee03f --- /dev/null +++ b/config/claude/skills/guide/SKILL.md @@ -0,0 +1,51 @@ +# /guide + +Interactive step-by-step guide for nvim plugin development tasks. + +## Instructions + +1. Ask the user what they want to accomplish. Gather enough context to build a + step-by-step plan (the plugin name, the goal, any constraints). + +2. Determine the plugin name from the current repo (basename of the working + directory) and today's date. Run exactly one Bash command: + + ``` + basename "$(git rev-parse --show-toplevel)" && date +%Y-%m-%d + ``` + +3. Build a numbered step-by-step plan as a markdown file. Run exactly one Bash + command: + + ``` + mkdir -p /tmp/- && cat > /tmp/-/guide.md <<'EOF' + # + + Plugin: + Date: + Branch: + + ## Steps + + 1. + 2. + ... + + ## Current + + Step 1: + EOF + ``` + +4. Present step 1 to the user. Execute it. Show the result. Ask for + confirmation before moving to the next step. + +5. After the user confirms, update the `## Current` section in the guide to + reflect the next step, then execute it. Repeat until all steps are done. + +6. When all steps are complete, update the guide with a `## Done` section and + print the path to the guide file. + +Keep each step small and self-contained. Prefer one logical change per step. +If a step reveals unexpected complexity, break it into sub-steps and update +the guide before proceeding. diff --git a/config/claude/skills/pr/SKILL.md b/config/claude/skills/pr/SKILL.md index ba01932..444a4e1 100644 --- a/config/claude/skills/pr/SKILL.md +++ b/config/claude/skills/pr/SKILL.md @@ -21,28 +21,35 @@ Create a pull request from the current branch. ``` ## Problem - + <1-2 sentences> ## Solution - + <1-2 sentences> ``` - - Write in plain prose. No bullet walls, no AI markdown soup. + - Write concise prose. No bullet walls, no verbose explanations. + - Use backticks around code identifiers, function names, and file paths. 3. Present the title and body. Ask for approval. -4. After approval, run exactly one Bash command (push + create chained): +4. After approval, if `scripts/ci.sh` exists, run it: ``` - git push -u origin && gh pr create --title "" --body "$(cat <<'EOF' + bash scripts/ci.sh + ``` + If it fails, show the output and stop. Do NOT create the PR. + +5. Run exactly one Bash command: + ``` + gh pr create --title "<title>" --body "$(cat <<'EOF' <body here> EOF )" ``` Print the PR URL from the output. -Total: 2 Bash calls (gather + push/create). Do not run any other commands. Do -not read files, explore code, or run additional git commands beyond what is -listed above. +Total: 2 Bash calls (gather + create), or 3 if CI ran. Do not run any other +commands. Do not read files, explore code, or run additional git commands beyond +what is listed above. Never force-push, even with lease. Never target main/master as the head branch. diff --git a/config/claude/skills/qpr/SKILL.md b/config/claude/skills/qpr/SKILL.md new file mode 100644 index 0000000..2a31a33 --- /dev/null +++ b/config/claude/skills/qpr/SKILL.md @@ -0,0 +1,48 @@ +# /qpr + +Create a pull request immediately, no approval step. + +## Instructions + +1. Run exactly this one Bash command: + + ``` + echo "---BRANCH---" && git branch --show-current && echo "---LOG---" && git log --oneline main..HEAD && echo "---STAT---" && git diff main...HEAD --stat && echo "---TEMPLATE---" && cat .github/pull_request_template.md 2>/dev/null || true + ``` + + If the branch is `main` or `master`, tell the user and stop. + +2. If `scripts/ci.sh` exists, run it: + ``` + bash scripts/ci.sh + ``` + If it fails, show the output and stop. + +3. Draft the PR (do NOT present for approval — create it immediately): + - **Title**: `type(scope): imperative summary`, max 72 chars. + - **Body**: if a PR template was found, fill it in. Otherwise: + + ``` + ## Problem + + <1-2 sentences> + + ## Solution + + <1-2 sentences> + ``` + + - Use backticks around code identifiers, function names, and file paths. + + Run exactly one Bash command: + ``` + gh pr create --title "<title>" --body "$(cat <<'EOF' + <body here> + EOF + )" + ``` + Print the PR URL from the output. + +Total: 2-3 Bash calls. Do not run any other commands. + +Never force-push. Never target main/master as the head branch. diff --git a/config/nvim/lua/plugins/dev.lua b/config/nvim/lua/plugins/dev.lua index 22f88b0..89ace4e 100644 --- a/config/nvim/lua/plugins/dev.lua +++ b/config/nvim/lua/plugins/dev.lua @@ -61,7 +61,7 @@ local git_status = new_git_status() return { { 'barrettruth/midnight.nvim', - enabled = false, + enabled = true, after = function() vim.cmd.colorscheme('midnight') end, @@ -300,6 +300,6 @@ return { latex = { open = { 'sioyek', '--new-instance' } }, }) end, - keys = { { '<leader>p', '<cmd>Preview toggle<cr>' } }, + keys = { { '<leader>p', '<cmd>Preview watch<cr>' } }, }, } diff --git a/config/nvim/lua/plugins/git.lua b/config/nvim/lua/plugins/git.lua index 824bf7b..ed8ecc7 100644 --- a/config/nvim/lua/plugins/git.lua +++ b/config/nvim/lua/plugins/git.lua @@ -225,12 +225,14 @@ end) return { { 'barrettruth/diffs.nvim', + enabled = true, before = function() vim.g.diffs = { + debug = '/tmp/diffs.log', fugitive = true, neogit = false, extra_filetypes = { 'diff' }, - hide_prefix = true, + hide_prefix = false, highlights = { vim = { enabled = true, diff --git a/home/modules/shell.nix b/home/modules/shell.nix index c6f7635..a6e613c 100644 --- a/home/modules/shell.nix +++ b/home/modules/shell.nix @@ -354,6 +354,19 @@ in "api.github.com" ]; tools.web_fetch = true; + hooks = { + PreToolUse = [ + { + matcher = "Bash"; + hooks = [ + { + type = "command"; + command = "${config.xdg.configHome}/claude/hooks/guard.sh"; + } + ]; + } + ]; + }; }; }; @@ -369,6 +382,10 @@ in source = config.lib.file.mkOutOfStoreSymlink "${repoDir}/config/claude/skills"; }; + xdg.configFile."claude/hooks" = lib.mkIf claude { + source = config.lib.file.mkOutOfStoreSymlink "${repoDir}/config/claude/hooks"; + }; + xdg.configFile."tmux/themes/midnight.conf".source = config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/.config/nix/config/tmux/themes/midnight.conf"; xdg.configFile."tmux/themes/daylight.conf".source = diff --git a/scripts/mux b/scripts/mux index 983ea05..0083f4b 100755 --- a/scripts/mux +++ b/scripts/mux @@ -145,7 +145,7 @@ ai) ;; code) require nvim - spawn_or_focus code 'nvim -c "lua require([[config.tmux]]).run([[nvim]])"' + spawn_or_focus code 'nvim .' ;; git) require nvim git @@ -153,13 +153,9 @@ git) if ! git -C "$pane_path" rev-parse --is-inside-work-tree >/dev/null 2>&1; then tmux display-message "Not a git repository" else - spawn_or_focus git 'nvim -c "lua require([[config.tmux]]).run([[git]])"' + spawn_or_focus git 'nvim -c "Git|only"' fi ;; -run) - require nvim - spawn_or_focus run 'nvim -c "lua require([[config.tmux]]).run([[run]])"' - ;; shell) spawn_or_focus shell ;; @@ -178,7 +174,7 @@ cmd) action=$(printf '%s' "$result" | sed -n '2p') [ $rc -eq 130 ] && exit if [ -n "$query" ] && [ "$query" != "$action" ]; then - tmux $query + tmux "$query" elif [ -n "$action" ]; then case "$action" in switch-client) pick_session ;;