3 Komitmen f67ac5942e ... 31a849526d

Pembuat SHA1 Pesan Tanggal
  Tom McKenzie 31a849526d Merge branch 'master' of https://github.com/grrowl/dotfiles 1 bulan lalu
  Tom McKenzie e3fee65e4a stuff 1 bulan lalu
  Tom McKenzie 0d2981a682 feat: track CLAUDE.md and symlink it into homedir 1 bulan lalu
8 mengubah file dengan 227 tambahan dan 9 penghapusan
  1. 1 1
      .gitconfig
  2. 35 1
      .rc.d/aliases.sh
  3. 69 0
      .rc.d/ssh-agent.sh
  4. 29 5
      .tmux.conf
  5. 69 0
      CLAUDE.md
  6. 11 0
      config/ghostty/config
  7. 12 1
      config/zed/keymap.json
  8. 1 1
      setup.sh

+ 1 - 1
.gitconfig

@@ -46,7 +46,7 @@
 	# use delta if installed, else fall back to less (don't break git without delta)
 	pager = command -v delta >/dev/null 2>&1 && delta || less -R
 [push]
-	default = upstream
+	default = simple
 [status]
 	submoduleSummary = true
 [merge "npm-merge-driver"]

+ 35 - 1
.rc.d/aliases.sh

@@ -92,7 +92,41 @@ alias pbjsons="pbpaste | jq -sRr '@json'"
 alias vscode-server="docker run -it --init -p 3000:3000 -v "$(pwd):/home/workspace:cached" gitpod/openvscode-server"
 
 alias cc='claude --dangerously-skip-permissions --system-prompt "."'
-alias ccw='claude --dangerously-skip-permissions --system-prompt "." --tmux -w'
+
+# cct — boot cc inside tmux. Already in tmux: a new window in the CURRENT
+#       session. Outside: attach/create the session for this dir (tt's naming)
+#       and boot cc there.
+cct() {
+  local cmd='claude --dangerously-skip-permissions --system-prompt "."'
+  # tmux runs the command via sh -c, so re-quote each arg to survive splitting
+  local a
+  for a in "$@"; do cmd="$cmd $(printf '%q' "$a")"; done
+  if [ -n "$TMUX" ]; then
+    tmux new-window -c "$PWD" "$cmd"
+    return
+  fi
+  local name
+  name="$(basename "$(dirname "$PWD")")_$(basename "$PWD")"
+  if tmux has-session -t "=$name" 2>/dev/null; then
+    tmux new-window -t "=$name" -c "$PWD" "$cmd"
+    tmux attach-session -t "=$name"
+  else
+    tmux new-session -s "$name" -c "$PWD" "$cmd"
+  fi
+}
+
+# ccw — cc in a fresh git worktree. Outside tmux, let claude build its own
+#       session (--tmux). Inside tmux, DON'T pass --tmux: it creates a separate
+#       session and switch-clients you into it, yanking you out of whatever you
+#       were working in. Give it a window in the current session instead.
+ccw() {
+  local cmd='claude --dangerously-skip-permissions --system-prompt "." -w'
+  if [ -n "$TMUX" ]; then
+    tmux new-window -c "$PWD" "$cmd${*:+ $*}"
+  else
+    claude --dangerously-skip-permissions --system-prompt "." --tmux -w "$@"
+  fi
+}
 
 # gw — create a git worktree under .claude/worktrees/<name>, copy in the
 #      .worktreeinclude (gitignored) files, cd into it, and print its path.

+ 69 - 0
.rc.d/ssh-agent.sh

@@ -0,0 +1,69 @@
+#!/bin/bash
+
+# SSH agent forwarding status.
+#
+# Sourced by both bash and zsh (see .bashrc / .zshrc), so keep this POSIX-ish.
+#
+# `ssh-agent-status` prints a one-line summary and can be run by hand any time.
+# It also runs automatically when you land on an interactive SSH login, so a
+# missing forward is obvious immediately rather than at the first git push.
+#
+# A missing forward is dim rather than red: it is usually deliberate, and the
+# line is there to tell you which mode you are in, not to report a fault.
+#
+# A missing forward is almost always a client-side omission: the machine you
+# connected FROM needs `ForwardAgent yes` for this host in its ~/.ssh/config.
+# Fix it there, not here.
+
+ssh-agent-status() {
+  local reset='\033[0m' dim='\033[2m' grey='\033[90m'
+  local green='\033[0;32m' yellow='\033[0;33m' red='\033[0;31m'
+  # Not `status`: that is a read-only builtin in zsh and assigning to it
+  # aborts the function on every zsh login.
+  local keys rc count
+
+  if [ -z "$SSH_AUTH_SOCK" ]; then
+    printf "${dim}${grey}- ssh agent not forwarded (SSH_AUTH_SOCK is unset)${reset}\n"
+    return 1
+  fi
+
+  # Keep the assignment and the status check separate: `local keys=$(...)`
+  # would return the status of `local`, not of ssh-add.
+  keys=$(ssh-add -l 2>/dev/null)
+  rc=$?
+
+  case $rc in
+    0)
+      # Counted in-shell rather than piping through wc and tr. This runs on
+      # every SSH login, and those are two processes spawned to count at most a
+      # handful of lines.
+      count=0
+      while IFS= read -r _; do count=$((count + 1)); done <<EOF
+$keys
+EOF
+      printf "${green}+ ssh agent forwarded${reset} ${dim}(%s keys)${reset}\n" "$count"
+      ;;
+    1)
+      printf "${yellow}! ssh agent forwarded but holds no keys${reset}\n"
+      return 1
+      ;;
+    *)
+      printf "${red}x ssh agent socket is dead${reset} ${dim}(%s)${reset}\n" "$SSH_AUTH_SOCK"
+      return 1
+      ;;
+  esac
+}
+
+# Report on interactive SSH logins only. .zshrc sources .rc.d for
+# non-interactive shells too, so without this guard the banner would be written
+# into the stream used by scp, rsync and git-over-ssh and corrupt them.
+#
+# Local shells never reach the function at all, so this costs a local terminal
+# nothing: $- and SSH_CONNECTION are both shell builtins.
+case $- in
+  *i*)
+    if [ -n "$SSH_CONNECTION" ] && [ -t 1 ]; then
+      ssh-agent-status || true
+    fi
+    ;;
+esac

+ 29 - 5
.tmux.conf

@@ -17,14 +17,19 @@ set -g status-left ' '
 # prefix-highlight (native #{client_prefix} — no plugin needed) + copy/sync indicators.
 # Indicators prepend to status-right so [+] [d] stay flush-right (mouse math intact).
 set -g status-right '#{?client_prefix,#[bg=colour3#,fg=colour235#,bold] ^B #[default] ,}#{?#{==:#{pane_mode},copy-mode},#[bg=colour2#,fg=colour235#,bold] COPY #[default] ,}#{?pane_synchronized,#[bg=colour1#,fg=colour15#,bold] SYNC #[default] ,} [+] [d] '
-# Tab label = the app's pane title (Claude Code sets a good one), trimmed to 24;
+# Tab label = the app's pane title (Claude Code sets a good one);
 # falls back to the running command for bare shells (where pane_title == hostname).
 # #{E:...} forces the stored format in @wtitle to expand. Replaces #W to dodge the
 # window-name pollution (apps leak junk like "2.1.156" into the window NAME).
-set -g @wtitle '#{?#{||:#{==:#{pane_title},},#{==:#{pane_title},#{host}}},#{pane_current_command},#{=/24/…:pane_title}}'
+# @wtitle = full (feeds Ghostty title), @wtab = truncated (feeds status-bar tabs).
+set -g @wtitle '#{?#{||:#{==:#{pane_title},},#{==:#{pane_title},#{host}}},#{pane_current_command},#{pane_title}}'
+set -g @wtab '#{?#{||:#{==:#{pane_title},},#{==:#{pane_title},#{host}}},#{pane_current_command},#{=/24/…:pane_title}}'
+# Forward the active pane's title to the outer terminal (Ghostty tab/window title)
+set -g set-titles on
+set -g set-titles-string '#{E:@wtitle}'
 # Tab: index:(NpZ) title — pane count + zoom flag prefixed in parens, only when split.
-setw -g window-status-format ' #I:#{?#{>:#{window_panes},1},(#{window_panes}p#{?window_zoomed_flag,Z,}) ,}#{E:@wtitle} '
-setw -g window-status-current-format ' #I:#{?#{>:#{window_panes},1},(#{window_panes}p#{?window_zoomed_flag,Z,}) ,}#{E:@wtitle} '
+setw -g window-status-format ' #I:#{?#{>:#{window_panes},1},(#{window_panes}p#{?window_zoomed_flag,Z,}) ,}#{E:@wtab} '
+setw -g window-status-current-format ' #I:#{?#{>:#{window_panes},1},(#{window_panes}p#{?window_zoomed_flag,Z,}) ,}#{E:@wtab} '
 setw -g window-status-current-style 'bg=colour4 fg=colour15 bold'
 set -g pane-border-status top
 
@@ -58,7 +63,26 @@ bind w display-menu -T "Windows" \
 
 bind-key -n M-w new-window
 
+# Navigation hierarchy (no prefix), shared with Zed where possible:
+#   Shift+arrow       -> tabs/windows (flat, left/right only)
+#   Ctrl+Shift+arrow  -> panes (the 2D grid). Same chord in Zed.
+# Modified arrows arrive as legacy CSI 1;6 sequences, so tmux parses C-S-Left
+# natively -- no user-keys translation needed. Never use Cmd here: Ghostty
+# owns it and never forwards it to tmux.
+# -r lets you hold/repeat within repeat-time.
+bind-key -n -r S-Left  previous-window
+bind-key -n -r S-Right next-window
+
+bind-key -n -r C-S-Left  select-pane -L
+bind-key -n -r C-S-Right select-pane -R
+bind-key -n -r C-S-Up    select-pane -U
+bind-key -n -r C-S-Down  select-pane -D
+
 # Fork Claude Code session into a new window
 bind R source-file ~/.tmux.conf \; display "Reloaded"
 
-bind f run-shell 'T="Fork ($(date "+%a %H:%M"))"; tmux new-window -a -n "$T" "claude --dangerously-skip-permissions --continue --fork-session"; tmux select-pane -T "$T"'
+bind f run-shell 'T="Fork ($(date "+%a %H:%M"))"; tmux new-window -a -n "$T" "claude --dangerously-skip-permissions --resume --fork-session"; tmux select-pane -T "$T"'
+
+# >>> tmux-claude-status >>>
+source-file /Users/tom/.claude/tmux-claude-status.conf
+# <<< tmux-claude-status <<<

+ 69 - 0
CLAUDE.md

@@ -0,0 +1,69 @@
+# CLAUDE.md — 12-rule template
+
+These rules apply to every task in this project unless explicitly overridden.
+Bias: caution over speed on non-trivial work. Use judgment on trivial tasks.
+
+## Rule 1 — Think Before Coding
+State assumptions explicitly. If uncertain, ask rather than guess.
+Present multiple interpretations when ambiguity exists.
+Push back when a simpler approach exists.
+Stop when confused. Name what's unclear.
+
+## Rule 2 — Simplicity First
+Minimum code that solves the problem. Nothing speculative.
+No features beyond what was asked. No abstractions for single-use code.
+Test: would a senior engineer say this is overcomplicated? If yes, simplify.
+
+## Rule 3 — Surgical Changes
+Touch only what you must. Clean up only your own mess.
+Don't "improve" adjacent code, comments, or formatting.
+Don't refactor what isn't broken. Match existing style.
+
+## Rule 4 — Goal-Driven Execution
+Define success criteria. Loop until verified.
+Don't follow steps. Define success and iterate.
+Strong success criteria let you loop independently.
+
+## Rule 5 — Use the model only for judgment calls
+Use me for: classification, drafting, summarization, extraction.
+Do NOT use me for: routing, retries, deterministic transforms.
+If code can answer, code answers.
+
+## Rule 6 — Spend in proportion to stakes
+Trivial task, trivial spend. Deep spends must earn their keep: say what they bought.
+Delegate bulk reading to subagents. Conclusions, not dumps.
+Flag large or unusual spend as it happens, with why.
+Stated limits are ceilings, not suggestions.
+
+Delegation: set the model on every subagent call. Unset inherits the host tier and bills grunt work at it.
+Opus for implementation, design, review, and anything with judgment in it.
+Sonnet for basic research — locating code, gathering facts, bulk reading, summarising.
+Pick the subagent by job: Explore to find code, Plan to design, claude for the rest.
+Never general-purpose — it inherits the host to do work a named agent does cheaper.
+
+## Rule 7 — Surface conflicts, don't average them
+If two patterns contradict, pick one (more recent / more tested).
+Explain why. Flag the other for cleanup.
+Don't blend conflicting patterns.
+
+## Rule 8 — Read before you write
+Before adding code, read exports, immediate callers, shared utilities.
+"Looks orthogonal" is dangerous. If unsure why code is structured a way, ask.
+
+## Rule 9 — Tests verify intent, not just behavior
+Tests must encode WHY behavior matters, not just WHAT it does.
+A test that can't fail when business logic changes is wrong.
+
+## Rule 10 — Checkpoint after every significant step
+Summarize what was done, what's verified, what's left.
+Don't continue from a state you can't describe back.
+If you lose track, stop and restate.
+
+## Rule 11 — Match the codebase's conventions, even if you disagree
+Conformance > taste inside the codebase.
+If you genuinely think a convention is harmful, surface it. Don't fork silently.
+
+## Rule 12 — Fail loud
+"Completed" is wrong if anything was skipped silently.
+"Tests pass" is wrong if any were skipped.
+Default to surfacing uncertainty, not hiding it.

+ 11 - 0
config/ghostty/config

@@ -30,4 +30,15 @@ quick-terminal-position = bottom
 quick-terminal-autohide = false
 quick-terminal-animation-duration = 0.1
 # copy-on-select = false
+
 keybind = shift+enter=text:\x1b\r
+
+# Navigation hierarchy: cmd+alt = tabs. Panes/splits are ctrl+shift+arrow,
+# deliberately left unbound here so they pass straight through to tmux
+# (which owns splits inside the terminal). Same chord works in Zed.
+keybind = cmd+alt+left=previous_tab
+keybind = cmd+alt+right=next_tab
+# Drop Ghostty's default goto_split:up/down -- orphaned leftovers, since
+# cmd+alt+left/right above already claimed this modifier for tabs.
+keybind = cmd+alt+up=unbind
+keybind = cmd+alt+down=unbind

+ 12 - 1
config/zed/keymap.json

@@ -12,7 +12,18 @@
       "cmd-p": "command_palette::Toggle",
       "cmd-shift-p": "project_symbols::Toggle",
       "alt-cmd-o": "workspace::Open",
-      "cmd-alt-n": ["task::Spawn", { "task_name": "agent terminal" }]
+      "cmd-alt-n": ["task::Spawn", { "task_name": "agent terminal" }],
+
+      // Navigation hierarchy, kept parallel with Ghostty + tmux:
+      //   cmd-alt-arrow    -> tabs (flat, left/right only)  [same as Ghostty]
+      //   ctrl-shift-arrow -> panes/splits (the 2D grid)    [same as tmux]
+      // Tabs can't share tmux's shift-arrow here because that's text selection.
+      "cmd-alt-left": "pane::ActivatePreviousItem",
+      "cmd-alt-right": "pane::ActivateNextItem",
+      "ctrl-shift-left": "workspace::ActivatePaneLeft",
+      "ctrl-shift-right": "workspace::ActivatePaneRight",
+      "ctrl-shift-up": "workspace::ActivatePaneUp",
+      "ctrl-shift-down": "workspace::ActivatePaneDown"
     }
   },
   {

+ 1 - 1
setup.sh

@@ -3,7 +3,7 @@
 # symlink dotfiles
 dir="$HOME/dotfiles"
 olddir="$HOME/dotfiles/bak"   # backup directory
-files=".zshrc .zshrc.d .rc.d .bashrc .vimrc .bash_logout .gitconfig .gitignore_global .gemrc .vim .tmux.conf"    # list of files/folders to symlink in homedir
+files=".zshrc .zshrc.d .rc.d .bashrc .vimrc .bash_logout .gitconfig .gitignore_global .gemrc .vim .tmux.conf CLAUDE.md"    # list of files/folders to symlink in homedir
 
 ## create dotfiles_old in homedir
 mkdir -p $olddir