From 59b0854464ef29511a0d09f1e76fd1140e675833 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Mon, 11 May 2026 07:18:20 -0500 Subject: refactor(ai-vterm): rename Claude-specific names to a generic "agent" I may add other terminal agents to this launcher (aider, an open-source LLM TUI), so the buffer prefix, the user knob, and the internal helpers shouldn't say "Claude". The module name (ai-vterm) and the `cj/ai-vterm-*` customs were already generic. This finishes the job: - buffer prefix `claude []` -> `agent []` (the `defconst` and the matching display-buffer-alist regex move together) - `cj/ai-vterm-claude-command` -> `cj/ai-vterm-agent-command` (the default still runs the `claude` CLI, with a docstring note on swapping it) - `cj/--ai-vterm-claude-buffers` / `-displayed-claude-window` / `-reuse-existing-claude` -> `-agent-*`, and their test files renamed to match - prose in the module commentary and docstrings, plus the matching test docstrings and buffer-name literals `vterm-config.el` hardcodes the same buffer prefix in `cj/--vterm-toggle-buffer-p` (F12 excludes agent buffers from its candidate set), so that literal moved too. Collapsing it into the shared `cj/--ai-vterm-name-prefix` is a cleanup for another day. After a reload, a project's buffer opens as `agent [foo]` instead of `claude [foo]`. Old buffers keep their names until killed. I also corrected two stale `eshell-vterm-config.el` references in ai-vterm.el docstrings (that module was split into `vterm-config.el`). Two things keep saying "Claude": the `cj/ai-vterm-agent-command` default value (the actual CLI), and the "Claude Code" example in `vterm-config.el`'s cursor-restore docstring (a concrete TUI example, not branding). 90 tests pass. `make validate-modules` clean. --- modules/ai-vterm.el | 135 +++++++++-------- modules/vterm-config.el | 20 +-- tests/test-ai-vterm--agent-buffers.el | 59 +++++++ tests/test-ai-vterm--buffer-name.el | 14 +- tests/test-ai-vterm--candidates.el | 2 +- tests/test-ai-vterm--claude-buffers.el | 59 ------- tests/test-ai-vterm--dispatch.el | 42 ++--- tests/test-ai-vterm--display-rule.el | 14 +- tests/test-ai-vterm--display-saved.el | 194 ++++++++++++------------ tests/test-ai-vterm--displayed-agent-window.el | 60 ++++++++ tests/test-ai-vterm--displayed-claude-window.el | 60 -------- tests/test-ai-vterm--launch-command.el | 36 ++--- tests/test-ai-vterm--live-tmux-sessions.el | 2 +- tests/test-ai-vterm--pick-buffer-candidates.el | 40 ++--- tests/test-ai-vterm--pick-project.el | 4 +- tests/test-ai-vterm--reuse-existing-agent.el | 99 ++++++++++++ tests/test-ai-vterm--reuse-existing-claude.el | 99 ------------ tests/test-ai-vterm--show-or-create.el | 24 +-- tests/test-ai-vterm--sort-candidates.el | 2 +- tests/test-ai-vterm--tmux-session-name.el | 2 +- tests/test-vterm-tmux-history.el | 10 +- tests/test-vterm-toggle--buffer-filter.el | 36 ++--- tests/testutil-vterm-buffers.el | 8 +- 23 files changed, 512 insertions(+), 509 deletions(-) create mode 100644 tests/test-ai-vterm--agent-buffers.el delete mode 100644 tests/test-ai-vterm--claude-buffers.el create mode 100644 tests/test-ai-vterm--displayed-agent-window.el delete mode 100644 tests/test-ai-vterm--displayed-claude-window.el create mode 100644 tests/test-ai-vterm--reuse-existing-agent.el delete mode 100644 tests/test-ai-vterm--reuse-existing-claude.el diff --git a/modules/ai-vterm.el b/modules/ai-vterm.el index 7b84ba14..9e2f9774 100644 --- a/modules/ai-vterm.el +++ b/modules/ai-vterm.el @@ -1,18 +1,18 @@ -;;; ai-vterm.el --- In-Emacs Claude launcher with vertical-split vterm -*- lexical-binding: t; -*- +;;; ai-vterm.el --- In-Emacs AI-agent launcher with vertical-split vterm -*- lexical-binding: t; -*- ;; Author: Craig Jennings ;;; Commentary: -;; Picks a Claude-template project (a dir under ~/.emacs.d, ~/code/*, or +;; Picks an AI-agent project (a dir under ~/.emacs.d, ~/code/*, or ;; ~/projects/* containing .ai/protocols.org), opens or reuses a vterm -;; buffer named "claude []", sends Claude Code's startup +;; buffer named "agent []", sends the agent's startup ;; instruction to it, and routes the buffer to a right-side window via ;; display-buffer-alist. Multiple projects produce multiple coexisting ;; buffers that share the same right-side slot; switching among them is a ;; buffer-switch, not a kill-and-recreate. ;; -;; Each project's Claude runs inside a tmux session named +;; Each project's agent runs inside a tmux session named ;; "" (default prefix "aiv-"). ;; The prefix lets `tmux ls' be filtered to AI-vterm's own sessions, so ;; after an Emacs crash the project picker can match surviving sessions @@ -23,21 +23,21 @@ ;; ;; Three F-key entry points: ;; -;; - F9 `cj/ai-vterm' -- DWIM dispatch. If a claude buffer is +;; - F9 `cj/ai-vterm' -- DWIM dispatch. If an agent buffer is ;; currently displayed in this frame, F9 quits its window -;; (toggle off). Otherwise, if exactly one claude buffer is +;; (toggle off). Otherwise, if exactly one agent buffer is ;; alive, F9 re-displays it; if zero or two-plus are alive, F9 ;; falls through to the project picker. ;; - C-F9 `cj/ai-vterm-pick-project' -- always show the project -;; picker, even when a claude buffer is currently displayed. +;; picker, even when an agent buffer is currently displayed. ;; Used when the user wants to start a new project session ;; instead of toggling the current one. -;; - M-F9 `cj/ai-vterm-pick-buffer' -- pick from the alive claude -;; buffers (no project candidates, no creation). When a claude +;; - M-F9 `cj/ai-vterm-pick-buffer' -- pick from the alive agent +;; buffers (no project candidates, no creation). When an agent ;; buffer is currently displayed, the picked buffer replaces it ;; in that window so orientation and size are preserved. ;; -;; Existing windmove (Shift-arrows) handles code <-> Claude focus +;; Existing windmove (Shift-arrows) handles code <-> agent focus ;; toggling. Buffer-move (C-M-arrows) handles side-swap. Neither ;; needs anything new from this module. @@ -53,12 +53,15 @@ (declare-function vterm-send-return "vterm" ()) (defgroup ai-vterm nil - "In-Emacs Claude launcher with vertical-split vterm." + "In-Emacs AI-agent launcher with vertical-split vterm." :group 'tools) -(defcustom cj/ai-vterm-claude-command +(defcustom cj/ai-vterm-agent-command "claude \"Read .ai/protocols.org and follow all instructions.\"" - "Shell command sent to a fresh AI-vterm to start Claude Code." + "Shell command sent to a fresh AI-vterm to start the agent. + +The default invokes the Claude Code CLI; set it to whatever terminal +agent you run (aider, an open-source LLM TUI, etc.)." :type 'string :group 'ai-vterm) @@ -66,14 +69,14 @@ "When non-nil, the generic vterm tmux-launch hook skips its auto-tmux step. ai-vterm dynamically binds this around `(vterm)' so the hook in -eshell-vterm-config.el doesn't send a bare \"tmux\\n\" before the named +vterm-config.el doesn't send a bare \"tmux\\n\" before the named session launch command runs. The hook reads the variable via `bound-and-true-p' so loading order between the two modules doesn't matter.") (defcustom cj/ai-vterm-project-roots (list (expand-file-name "~/.emacs.d")) - "Directories that are themselves Claude-template projects. + "Directories that are themselves AI-agent projects. Each entry is included as a candidate when it exists and contains .ai/protocols.org. Use this for single-project roots like ~/.emacs.d." :type '(repeat directory) @@ -82,7 +85,7 @@ Each entry is included as a candidate when it exists and contains (defcustom cj/ai-vterm-container-roots (list (expand-file-name "~/code") (expand-file-name "~/projects")) - "Directories whose immediate children are scanned for Claude projects. + "Directories whose immediate children are scanned for agent projects. Each entry's child directories are included as candidates when they contain .ai/protocols.org. Use this for container dirs like ~/code." :type '(repeat directory) @@ -112,19 +115,19 @@ running program." :type 'string :group 'ai-vterm) -(defconst cj/--ai-vterm-name-prefix "claude [" +(defconst cj/--ai-vterm-name-prefix "agent [" "Buffer-name prefix shared by all AI-vterm buffers. Single source of truth for both buffer construction in `cj/--ai-vterm-buffer-name' and detection in `cj/--ai-vterm-buffer-p'. The display-buffer-alist rule keys on the -escaped form \"\\\\`claude \\\\[\" -- they must stay in sync.") +escaped form \"\\\\`agent \\\\[\" -- they must stay in sync.") (defun cj/--ai-vterm-buffer-name (dir) "Return the AI-vterm buffer name for project directory DIR. -The name pattern is \"claude []\". The display-buffer-alist -rule keys on the literal prefix \"claude [\", so changing the format +The name pattern is \"agent []\". The display-buffer-alist +rule keys on the literal prefix \"agent [\", so changing the format breaks routing to the right-side window." (format "%s%s]" cj/--ai-vterm-name-prefix @@ -134,13 +137,13 @@ breaks routing to the right-side window." "Return non-nil when BUFFER is an AI-vterm buffer. A buffer qualifies when its name starts with the literal prefix in -`cj/--ai-vterm-name-prefix' (\"claude [\"). The check is anchored at -the start so names like \"foo claude [bar]\" do not match." +`cj/--ai-vterm-name-prefix' (\"agent [\"). The check is anchored at +the start so names like \"foo agent [bar]\" do not match." (and (bufferp buffer) (buffer-live-p buffer) (string-prefix-p cj/--ai-vterm-name-prefix (buffer-name buffer)))) -(defun cj/--ai-vterm-claude-buffers () +(defun cj/--ai-vterm-agent-buffers () "Return the live AI-vterm buffers in `buffer-list' order. Order matches `buffer-list' on the selected frame, which is most- @@ -148,11 +151,11 @@ recently-selected first. Non-AI-vterm buffers are filtered out via `cj/--ai-vterm-buffer-p'." (seq-filter #'cj/--ai-vterm-buffer-p (buffer-list))) -(defun cj/--ai-vterm-displayed-claude-window (&optional frame) +(defun cj/--ai-vterm-displayed-agent-window (&optional frame) "Return a window in FRAME currently displaying an AI-vterm buffer, or nil. FRAME defaults to the selected frame. When more than one window in -the frame shows a claude buffer, the first one in `window-list' order +the frame shows an agent buffer, the first one in `window-list' order is returned. The minibuffer is excluded from the search." (seq-find (lambda (w) (cj/--ai-vterm-buffer-p (window-buffer w))) @@ -211,7 +214,7 @@ comes from `cj/--ai-vterm-tmux-session-name'; the first window is named window auto-names after its command and the two read distinctly. The shell command run on first creation is - ; exec bash + ; exec bash so the tmux window survives the AI command exiting -- the session stays alive with a bare bash prompt for recovery, and reattach works the same way." (let ((session (cj/--ai-vterm-tmux-session-name dir)) @@ -220,14 +223,14 @@ alive with a bare bash prompt for recovery, and reattach works the same way." (shell-quote-argument session) (shell-quote-argument cj/ai-vterm-tmux-window-name) (shell-quote-argument start-dir) - (concat cj/ai-vterm-claude-command "; exec bash")))) + (concat cj/ai-vterm-agent-command "; exec bash")))) (defun cj/--ai-vterm-has-marker-p (dir) "Return non-nil when DIR contains .ai/protocols.org." (file-exists-p (expand-file-name ".ai/protocols.org" dir))) (defun cj/--ai-vterm-candidates () - "Return the list of Claude-template project paths. + "Return the list of AI-agent project paths. Each entry of `cj/ai-vterm-project-roots' contributes itself when it exists and contains .ai/protocols.org. Each entry of @@ -276,7 +279,7 @@ list." Used by `cj/--ai-vterm-display-saved' as the size fallback when `cj/--ai-vterm-last-size' is nil (i.e. the user hasn't yet toggled -off a claude window in this session). Applies to both width and +off an agent window in this session). Applies to both width and height axes -- the same fallback fraction is used for either default direction." :type 'number @@ -285,7 +288,7 @@ direction." (defvar cj/--ai-vterm-last-direction nil "Last user-chosen direction for the AI-vterm display. -Symbol: right, below, left, or above. nil means no claude window +Symbol: right, below, left, or above. nil means no agent window has been toggled off yet this session, so the default direction applies. Captured at toggle-off by `cj/--ai-vterm-capture-state' and consumed by `cj/--ai-vterm-display-saved'.") @@ -301,9 +304,9 @@ fraction). Body size, not total size, because total-width includes the right-edge divider when the window has a right sibling but excludes it when the window is at the frame edge. Capturing total-width -from a rightmost claude (no divider) and replaying into a middle +from a rightmost agent (no divider) and replaying into a middle position (with divider) leaves the body 1 column short -- visible -as 1 col of the sibling buffer peeking through where claude should +as 1 col of the sibling buffer peeking through where agent should have ended. Body-width is divider-independent and matches what the user actually sees. @@ -328,22 +331,22 @@ default. Does nothing when WINDOW is not live." 'cj/--ai-vterm-last-direction 'cj/--ai-vterm-last-size)) -(defun cj/--ai-vterm-reuse-existing-claude (buffer _alist) +(defun cj/--ai-vterm-reuse-existing-agent (buffer _alist) "Display-buffer action: reuse any window in this frame already showing -a claude buffer. +an agent buffer. -Looks up `cj/--ai-vterm-displayed-claude-window' on the selected -frame. When a claude window exists, replaces its buffer with BUFFER +Looks up `cj/--ai-vterm-displayed-agent-window' on the selected +frame. When an agent window exists, replaces its buffer with BUFFER and returns the window. When none exists, returns nil so the next action in the chain runs. This is more specific than `display-buffer-use-some-window', which would happily steal any non-selected window (e.g. a code window -above the claude split) when the user is focused in claude and -swaps projects via C-F9. The selective lookup here keeps non-claude +above the agent split) when the user is focused in agent and +swaps projects via C-F9. The selective lookup here keeps non-agent windows undisturbed and preserves the user's split geometry across project changes." - (let ((win (cj/--ai-vterm-displayed-claude-window))) + (let ((win (cj/--ai-vterm-displayed-agent-window))) (when win (set-window-buffer win buffer) win))) @@ -361,13 +364,13 @@ vars, falling back to `right' and `cj/ai-vterm-window-width'." (defun cj/--ai-vterm-display-rule-list () "Return the `display-buffer-alist' entry list installed by this module. -The single rule routes any buffer whose name starts with \"claude [\" +The single rule routes any buffer whose name starts with \"agent [\" through three actions in order: 1. `display-buffer-reuse-window' -- if the same buffer is already visible in any window, focus that one. -2. `cj/--ai-vterm-reuse-existing-claude' -- otherwise, if any - window in this frame already shows a claude-prefixed buffer, +2. `cj/--ai-vterm-reuse-existing-agent' -- otherwise, if any + window in this frame already shows an agent-prefixed buffer, swap its buffer for the new one (preserves geometry across project changes via C-F9). 3. `cj/--ai-vterm-display-saved' -- otherwise, split per the saved @@ -381,11 +384,11 @@ resulting window an ordinary window so all standard window commands work. `display-buffer-use-some-window' is also avoided -- it would happily -steal any non-selected window (e.g. a code window above a claude -split) when the user is focused in claude and switches projects." - '(("\\`claude \\[" +steal any non-selected window (e.g. a code window above an agent +split) when the user is focused in agent and switches projects." + '(("\\`agent \\[" (display-buffer-reuse-window - cj/--ai-vterm-reuse-existing-claude + cj/--ai-vterm-reuse-existing-agent cj/--ai-vterm-display-saved) (inhibit-same-window . t)))) @@ -402,7 +405,7 @@ project's tmux launch command (see `cj/--ai-vterm-launch-command') so the same project basename reattaches across Emacs restarts. The dynamic binding of `cj/--ai-vterm-suppress-tmux' around `(vterm)' -suppresses the generic tmux-launch hook in eshell-vterm-config.el so +suppresses the generic tmux-launch hook in vterm-config.el so it doesn't fire a bare \"tmux\\n\" before the project-named launch command runs. @@ -435,7 +438,7 @@ Returns the buffer." (defun cj/--ai-vterm-format-candidate (path &optional sessions) "Return the display name for PATH in the AI-vterm project picker. -Appends \" [running]\" when the project's claude buffer exists with +Appends \" [running]\" when the project's agent buffer exists with a live process; otherwise \" [detached]\" when PATH's tmux session name is in SESSIONS (a session that survived an Emacs crash, no buffer yet); otherwise just the abbreviated path. Path is @@ -467,7 +470,7 @@ the metadata keeps the order ALIST was built in." (complete-with-action action alist string predicate)))) (defun cj/--ai-vterm-pick-project () - "Prompt for a Claude-template project; return its absolute path. + "Prompt for an AI-agent project; return its absolute path. Candidates come from `cj/--ai-vterm-candidates', ordered by `cj/--ai-vterm-sort-candidates' so projects with a live tmux session @@ -478,7 +481,7 @@ alive) or \" [detached]\" (the tmux session survived, no buffer). Signals `user-error' when no candidates exist." (let ((candidates (cj/--ai-vterm-candidates))) (unless candidates - (user-error "No Claude-template projects found under %s" + (user-error "No AI-agent projects found under %s" (mapconcat #'identity (append cj/ai-vterm-project-roots cj/ai-vterm-container-roots) @@ -500,23 +503,23 @@ Signals `user-error' when no candidates exist." "Compute the F9 (`cj/ai-vterm') action without performing it. Returns one of: -- (toggle-off . WINDOW) -- claude is displayed in WINDOW; quit it. -- (redisplay-recent . BUFFER) -- 1+ alive claude buffers; show MRU. -- (pick-project) -- zero alive claude buffers; prompt. +- (toggle-off . WINDOW) -- agent is displayed in WINDOW; quit it. +- (redisplay-recent . BUFFER) -- 1+ alive agent buffers; show MRU. +- (pick-project) -- zero alive agent buffers; prompt. -When 2+ claude buffers are alive, F9 redisplays the most-recently- +When 2+ agent buffers are alive, F9 redisplays the most-recently- selected one rather than opening the project picker. C-F9 is the explicit \"start a different project\" surface; M-F9 is the explicit -\"switch among existing claudes\" surface. F9 keeps a single, simple -job: toggle whichever claude was last in use. +\"switch among existing agents\" surface. F9 keeps a single, simple +job: toggle whichever agent was last in use. A pure-decision helper so the dispatch logic is exercisable in tests without firing real `display-buffer' or `quit-window' calls." - (let ((win (cj/--ai-vterm-displayed-claude-window))) + (let ((win (cj/--ai-vterm-displayed-agent-window))) (cond (win (cons 'toggle-off win)) (t - (let ((buffers (cj/--ai-vterm-claude-buffers))) + (let ((buffers (cj/--ai-vterm-agent-buffers))) (cond (buffers (cons 'redisplay-recent (car buffers))) (t '(pick-project)))))))) @@ -543,17 +546,17 @@ Each cell is (DISPLAY-NAME . BUFFER)." (list (cons (format "%s [shown]" (buffer-name shown)) shown)))))) (defun cj/ai-vterm-pick-project (&optional arg) - "Pick a Claude-template project and open or reuse its vterm. + "Pick an AI-agent project and open or reuse its vterm. The project is picked from a filtered completing-read list of dirs that contain .ai/protocols.org. The vterm buffer is named -\"claude []\" and is routed to a right-side window via +\"agent []\" and is routed to a right-side window via `display-buffer-alist'. Multiple projects coexist as separate buffers; reinvoking on the same project reuses its existing vterm. With prefix ARG, display the buffer without selecting its window. -Bound to C-F9 -- always shows the project picker, even when a claude +Bound to C-F9 -- always shows the project picker, even when an agent buffer is currently displayed." (interactive "P") (let* ((dir (cj/--ai-vterm-pick-project)) @@ -581,10 +584,10 @@ Signals `user-error' when no AI-vterm buffers exist. Bound to M-F9." (interactive) - (let ((buffers (cj/--ai-vterm-claude-buffers))) + (let ((buffers (cj/--ai-vterm-agent-buffers))) (unless buffers - (user-error "No Claude buffers")) - (let* ((shown-win (cj/--ai-vterm-displayed-claude-window)) + (user-error "No agent buffers")) + (let* ((shown-win (cj/--ai-vterm-displayed-agent-window)) (shown-buf (and shown-win (window-buffer shown-win))) (alist (cj/--ai-vterm-pick-buffer-candidates buffers shown-buf)) (chosen (completing-read "AI vterm buffer: " alist nil t)) @@ -624,11 +627,11 @@ AI-vterm buffers without touching the project list." ;; semantics are unconditional. `quit-window' only deletes the ;; window when its `quit-restore' parameter records that it was ;; created for the buffer. Buffer-move (C-M-arrows) leaves the - ;; claude buffer in a window without that history, so + ;; agent buffer in a window without that history, so ;; `quit-window' would just bury -- the window stays with some ;; other buffer in it, and the next toggle-on then creates a ;; fresh side window for a count of N+1. Skip the deletion - ;; only when claude is the lone window in the frame (delete + ;; only when agent is the lone window in the frame (delete ;; would leave none); bury in that case. (if (one-window-p) (bury-buffer (window-buffer win)) diff --git a/modules/vterm-config.el b/modules/vterm-config.el index e601ab40..191d74e8 100644 --- a/modules/vterm-config.el +++ b/modules/vterm-config.el @@ -197,7 +197,7 @@ ai-vterm.el is loaded." ;; vterm-toggle is kept installed so `M-x vterm-toggle' still works, ;; but F12 below is bound to a custom toggle (`cj/vterm-toggle') that -;; excludes claude-prefixed buffers from its candidate set. +;; excludes agent-prefixed buffers from its candidate set. (use-package vterm-toggle :defer .5 :config @@ -207,11 +207,11 @@ ai-vterm.el is loaded." ;; ;; Replacement for `vterm-toggle' on F12. Two reasons to roll our own: ;; -;; 1. claude exclusion. vterm-toggle picks the most-recently-selected +;; 1. agent exclusion. vterm-toggle picks the most-recently-selected ;; vterm buffer as the toggle target. When the user just used F9 -;; on a claude vterm, the most-recent vterm IS claude, so F12 ends -;; up toggling claude -- which has its own F9 / C-F9 / M-F9 surface -;; in `ai-vterm.el' and shouldn't be affected by F12. The claude +;; on an agent vterm, the most-recent vterm IS agent, so F12 ends +;; up toggling agent -- which has its own F9 / C-F9 / M-F9 surface +;; in `ai-vterm.el' and shouldn't be affected by F12. The agent ;; exclusion lives in the candidate filter (`cj/--vterm-toggle-buffer-p'). ;; ;; 2. user-modified geometry. vterm-toggle's display rule had a @@ -245,7 +245,7 @@ nil means fall back to `cj/vterm-toggle-window-height' as a fraction.") Qualifies when BUFFER is alive, has `vterm-mode' (or its name starts with the vterm-toggle prefix), AND its name does NOT start with the -claude prefix used by ai-vterm.el. The claude exclusion keeps F12 +agent prefix used by ai-vterm.el. The agent exclusion keeps F12 from grabbing buffers that ai-vterm.el's F9 dispatch owns." (and (bufferp buffer) (buffer-live-p buffer) @@ -254,7 +254,7 @@ from grabbing buffers that ai-vterm.el's F9 dispatch owns." (string-prefix-p (or (bound-and-true-p vterm-buffer-name) "*vterm*") (buffer-name buffer))) - (not (string-prefix-p "claude [" (buffer-name buffer))))))) + (not (string-prefix-p "agent [" (buffer-name buffer))))))) (defun cj/--vterm-toggle-buffers () "Return live F12-managed vterm buffers in `buffer-list' (MRU) order." @@ -292,7 +292,7 @@ vars, falling back to `below' and `cj/vterm-toggle-window-height'." Routes any vterm buffer that satisfies `cj/--vterm-toggle-buffer-p' through two actions: reuse-window (for visible vterm windows) then -the saved-geometry display action. Excludes claude buffers via the +the saved-geometry display action. Excludes agent buffers via the predicate -- those are handled by ai-vterm.el's display rule." '(((lambda (buffer-or-name _) (cj/--vterm-toggle-buffer-p (get-buffer buffer-or-name))) @@ -322,7 +322,7 @@ Returns one of: (declare-function vterm "vterm" (&optional buffer-name)) (defun cj/vterm-toggle () - "Toggle a normal (non-claude) vterm buffer. + "Toggle a normal (non-agent) vterm buffer. - If an F12-managed vterm is currently displayed in this frame, capture its geometry and delete its window (toggle off). Falls @@ -333,7 +333,7 @@ Returns one of: - Otherwise, create a new vterm via `(vterm)' which routes through the same display action. -Excludes claude-prefixed vterm buffers; those have their own F9 / +Excludes agent-prefixed vterm buffers; those have their own F9 / C-F9 / M-F9 dispatch via `cj/ai-vterm'." (interactive) (pcase (cj/--vterm-toggle-dispatch) diff --git a/tests/test-ai-vterm--agent-buffers.el b/tests/test-ai-vterm--agent-buffers.el new file mode 100644 index 00000000..57d01730 --- /dev/null +++ b/tests/test-ai-vterm--agent-buffers.el @@ -0,0 +1,59 @@ +;;; test-ai-vterm--agent-buffers.el --- Tests for cj/--ai-vterm-agent-buffers -*- lexical-binding: t; -*- + +;;; Commentary: +;; The helper returns the list of buffers whose names start with the +;; literal prefix "agent [". Order is the same order `buffer-list' +;; gives them (most-recently-selected first). Non-agent buffers and +;; buffers whose names merely contain the prefix as a substring are +;; excluded. + +;;; Code: + +(require 'ert) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) +(require 'ai-vterm) +(require 'testutil-vterm-buffers) + +(ert-deftest test-ai-vterm--agent-buffers-empty-when-none-exist () + "Boundary: no agent-prefixed buffers anywhere -> empty list." + (cj/test--kill-agent-buffers) + (unwind-protect + (should (null (cj/--ai-vterm-agent-buffers))) + (cj/test--kill-agent-buffers))) + +(ert-deftest test-ai-vterm--agent-buffers-returns-only-agent-buffers () + "Normal: filters to only agent-prefixed buffers, leaves others alone." + (cj/test--kill-agent-buffers) + (let ((c1 (get-buffer-create "agent [a]")) + (c2 (get-buffer-create "agent [b]")) + (other (get-buffer-create "regular-buffer"))) + (unwind-protect + (let ((result (cj/--ai-vterm-agent-buffers))) + (should (memq c1 result)) + (should (memq c2 result)) + (should-not (memq other result)) + (should (= (length result) 2))) + (kill-buffer c1) + (kill-buffer c2) + (kill-buffer other)))) + +(ert-deftest test-ai-vterm--agent-buffers-anchors-prefix-not-substring () + "Boundary: 'foo agent [bar]' is not an agent buffer -- prefix anchored." + (cj/test--kill-agent-buffers) + (let ((not-agent (get-buffer-create "foo agent [bar]"))) + (unwind-protect + (should-not (memq not-agent (cj/--ai-vterm-agent-buffers))) + (kill-buffer not-agent)))) + +(ert-deftest test-ai-vterm--agent-buffers-bare-agent-not-included () + "Boundary: 'agent' alone (no bracket) doesn't match the 'agent [' prefix." + (cj/test--kill-agent-buffers) + (let ((bare (get-buffer-create "agent"))) + (unwind-protect + (should-not (memq bare (cj/--ai-vterm-agent-buffers))) + (kill-buffer bare)))) + +(provide 'test-ai-vterm--agent-buffers) +;;; test-ai-vterm--agent-buffers.el ends here diff --git a/tests/test-ai-vterm--buffer-name.el b/tests/test-ai-vterm--buffer-name.el index 95c673ba..2ebe91ee 100644 --- a/tests/test-ai-vterm--buffer-name.el +++ b/tests/test-ai-vterm--buffer-name.el @@ -2,7 +2,7 @@ ;;; Commentary: ;; Tests for the buffer-name transform. Given an absolute project -;; directory, the helper returns "claude []". The naming pattern +;; directory, the helper returns "agent []". The naming pattern ;; is what the display-buffer-alist rule keys on, so a regression here ;; silently breaks routing to the right side-window. @@ -14,29 +14,29 @@ (require 'ai-vterm) (ert-deftest test-ai-vterm--buffer-name-normal-project () - "Normal: a typical project path yields claude []." + "Normal: a typical project path yields agent []." (should (equal (cj/--ai-vterm-buffer-name "/home/cjennings/projects/foo") - "claude [foo]"))) + "agent [foo]"))) (ert-deftest test-ai-vterm--buffer-name-trailing-slash () "Boundary: trailing slash collapses before basename extraction." (should (equal (cj/--ai-vterm-buffer-name "/home/cjennings/projects/foo/") - "claude [foo]"))) + "agent [foo]"))) (ert-deftest test-ai-vterm--buffer-name-dot-prefix-dir () "Boundary: dot-prefix dirs (.emacs.d) preserve the dot in the basename." (should (equal (cj/--ai-vterm-buffer-name "/home/cjennings/.emacs.d") - "claude [.emacs.d]"))) + "agent [.emacs.d]"))) (ert-deftest test-ai-vterm--buffer-name-space-in-basename () "Boundary: a space in the basename round-trips into the buffer name." (should (equal (cj/--ai-vterm-buffer-name "/tmp/my work") - "claude [my work]"))) + "agent [my work]"))) (ert-deftest test-ai-vterm--buffer-name-deeply-nested () "Normal: only the last path component is used." (should (equal (cj/--ai-vterm-buffer-name "/a/b/c/d/e/leaf") - "claude [leaf]"))) + "agent [leaf]"))) (provide 'test-ai-vterm--buffer-name) ;;; test-ai-vterm--buffer-name.el ends here diff --git a/tests/test-ai-vterm--candidates.el b/tests/test-ai-vterm--candidates.el index b45888cc..be9041ce 100644 --- a/tests/test-ai-vterm--candidates.el +++ b/tests/test-ai-vterm--candidates.el @@ -19,7 +19,7 @@ (require 'ai-vterm) (defun test-ai-vterm--make-marker (dir) - "Create DIR/.ai/protocols.org so DIR registers as a Claude project." + "Create DIR/.ai/protocols.org so DIR registers as an AI-agent project." (let ((ai-dir (expand-file-name ".ai" dir))) (make-directory ai-dir t) (write-region "" nil (expand-file-name "protocols.org" ai-dir)))) diff --git a/tests/test-ai-vterm--claude-buffers.el b/tests/test-ai-vterm--claude-buffers.el deleted file mode 100644 index f975b64e..00000000 --- a/tests/test-ai-vterm--claude-buffers.el +++ /dev/null @@ -1,59 +0,0 @@ -;;; test-ai-vterm--claude-buffers.el --- Tests for cj/--ai-vterm-claude-buffers -*- lexical-binding: t; -*- - -;;; Commentary: -;; The helper returns the list of buffers whose names start with the -;; literal prefix "claude [". Order is the same order `buffer-list' -;; gives them (most-recently-selected first). Non-claude buffers and -;; buffers whose names merely contain the prefix as a substring are -;; excluded. - -;;; Code: - -(require 'ert) - -(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) -(add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) -(require 'ai-vterm) -(require 'testutil-vterm-buffers) - -(ert-deftest test-ai-vterm--claude-buffers-empty-when-none-exist () - "Boundary: no claude-prefixed buffers anywhere -> empty list." - (cj/test--kill-claude-buffers) - (unwind-protect - (should (null (cj/--ai-vterm-claude-buffers))) - (cj/test--kill-claude-buffers))) - -(ert-deftest test-ai-vterm--claude-buffers-returns-only-claude-buffers () - "Normal: filters to only claude-prefixed buffers, leaves others alone." - (cj/test--kill-claude-buffers) - (let ((c1 (get-buffer-create "claude [a]")) - (c2 (get-buffer-create "claude [b]")) - (other (get-buffer-create "regular-buffer"))) - (unwind-protect - (let ((result (cj/--ai-vterm-claude-buffers))) - (should (memq c1 result)) - (should (memq c2 result)) - (should-not (memq other result)) - (should (= (length result) 2))) - (kill-buffer c1) - (kill-buffer c2) - (kill-buffer other)))) - -(ert-deftest test-ai-vterm--claude-buffers-anchors-prefix-not-substring () - "Boundary: 'foo claude [bar]' is not a claude buffer -- prefix anchored." - (cj/test--kill-claude-buffers) - (let ((not-claude (get-buffer-create "foo claude [bar]"))) - (unwind-protect - (should-not (memq not-claude (cj/--ai-vterm-claude-buffers))) - (kill-buffer not-claude)))) - -(ert-deftest test-ai-vterm--claude-buffers-bare-claude-not-included () - "Boundary: 'claude' alone (no bracket) doesn't match the 'claude [' prefix." - (cj/test--kill-claude-buffers) - (let ((bare (get-buffer-create "claude"))) - (unwind-protect - (should-not (memq bare (cj/--ai-vterm-claude-buffers))) - (kill-buffer bare)))) - -(provide 'test-ai-vterm--claude-buffers) -;;; test-ai-vterm--claude-buffers.el ends here diff --git a/tests/test-ai-vterm--dispatch.el b/tests/test-ai-vterm--dispatch.el index 8871af9a..94b02123 100644 --- a/tests/test-ai-vterm--dispatch.el +++ b/tests/test-ai-vterm--dispatch.el @@ -3,8 +3,8 @@ ;;; Commentary: ;; The dispatch helper is a pure decision function used by F9. ;; Returns one of (toggle-off . WIN), (redisplay-recent . BUF), -;; or (pick-project) based on whether a claude buffer is currently -;; displayed and whether any alive claude buffers exist. Tests mock +;; or (pick-project) based on whether an agent buffer is currently +;; displayed and whether any alive agent buffers exist. Tests mock ;; the two underlying helpers so the dispatch logic can be exercised ;; without touching real windows. @@ -19,38 +19,38 @@ (require 'testutil-vterm-buffers) (ert-deftest test-ai-vterm--dispatch-window-displayed-returns-toggle-off () - "Normal: displayed claude window -> (toggle-off . WIN)." + "Normal: displayed agent window -> (toggle-off . WIN)." (let ((sentinel-win 'fake-window)) - (cl-letf (((symbol-function 'cj/--ai-vterm-displayed-claude-window) + (cl-letf (((symbol-function 'cj/--ai-vterm-displayed-agent-window) (lambda (&optional _frame) sentinel-win))) (should (equal (cj/--ai-vterm-dispatch) (cons 'toggle-off sentinel-win)))))) (ert-deftest test-ai-vterm--dispatch-no-window-single-buffer-returns-redisplay-recent () - "Normal: no displayed claude, one alive buffer -> redisplay-recent + buffer." - (cj/test--kill-claude-buffers) - (let ((b1 (get-buffer-create "claude [single]"))) + "Normal: no displayed agent, one alive buffer -> redisplay-recent + buffer." + (cj/test--kill-agent-buffers) + (let ((b1 (get-buffer-create "agent [single]"))) (unwind-protect - (cl-letf (((symbol-function 'cj/--ai-vterm-displayed-claude-window) + (cl-letf (((symbol-function 'cj/--ai-vterm-displayed-agent-window) (lambda (&optional _frame) nil)) - ((symbol-function 'cj/--ai-vterm-claude-buffers) + ((symbol-function 'cj/--ai-vterm-agent-buffers) (lambda () (list b1)))) (should (equal (cj/--ai-vterm-dispatch) (cons 'redisplay-recent b1)))) (kill-buffer b1)))) (ert-deftest test-ai-vterm--dispatch-no-window-multiple-buffers-returns-redisplay-recent () - "Normal: no displayed claude, 2+ alive buffers -> redisplay-recent + MRU. -F9 redisplays the most-recently-selected claude (head of buffer-list + "Normal: no displayed agent, 2+ alive buffers -> redisplay-recent + MRU. +F9 redisplays the most-recently-selected agent (head of buffer-list order) rather than opening the project picker, so the user toggles -THE claude they were last using. Other claudes are reachable via M-F9." - (cj/test--kill-claude-buffers) - (let ((b1 (get-buffer-create "claude [a]")) - (b2 (get-buffer-create "claude [b]"))) +THE agent they were last using. Other agents are reachable via M-F9." + (cj/test--kill-agent-buffers) + (let ((b1 (get-buffer-create "agent [a]")) + (b2 (get-buffer-create "agent [b]"))) (unwind-protect - (cl-letf (((symbol-function 'cj/--ai-vterm-displayed-claude-window) + (cl-letf (((symbol-function 'cj/--ai-vterm-displayed-agent-window) (lambda (&optional _frame) nil)) - ((symbol-function 'cj/--ai-vterm-claude-buffers) + ((symbol-function 'cj/--ai-vterm-agent-buffers) (lambda () (list b1 b2)))) (should (equal (cj/--ai-vterm-dispatch) (cons 'redisplay-recent b1)))) @@ -58,11 +58,11 @@ THE claude they were last using. Other claudes are reachable via M-F9." (kill-buffer b2)))) (ert-deftest test-ai-vterm--dispatch-no-window-zero-buffers-returns-pick-project () - "Boundary: no displayed claude, zero alive buffers -> pick-project." - (cj/test--kill-claude-buffers) - (cl-letf (((symbol-function 'cj/--ai-vterm-displayed-claude-window) + "Boundary: no displayed agent, zero alive buffers -> pick-project." + (cj/test--kill-agent-buffers) + (cl-letf (((symbol-function 'cj/--ai-vterm-displayed-agent-window) (lambda (&optional _frame) nil)) - ((symbol-function 'cj/--ai-vterm-claude-buffers) + ((symbol-function 'cj/--ai-vterm-agent-buffers) (lambda () nil))) (should (equal (cj/--ai-vterm-dispatch) '(pick-project))))) diff --git a/tests/test-ai-vterm--display-rule.el b/tests/test-ai-vterm--display-rule.el index af481eb3..15d270e2 100644 --- a/tests/test-ai-vterm--display-rule.el +++ b/tests/test-ai-vterm--display-rule.el @@ -2,7 +2,7 @@ ;;; Commentary: ;; The module installs a `display-buffer-alist' entry routing buffers -;; whose names match "\\`claude \\[" to a right-side window. These +;; whose names match "\\`agent \\[" to a right-side window. These ;; tests verify the rule reaches the right side and ignores buffers ;; that don't match the prefix. @@ -26,13 +26,13 @@ (let ((display-buffer-alist (cj/--ai-vterm-display-rule-list))) ,@body))) -(ert-deftest test-ai-vterm--display-rule-routes-claude-buffer-to-right () - "Normal: a buffer named \"claude [foo]\" lands in a window to the right. +(ert-deftest test-ai-vterm--display-rule-routes-agent-buffer-to-right () + "Normal: a buffer named \"agent [foo]\" lands in a window to the right. The rule uses `display-buffer-in-direction' with `(direction . right)', which splits the current window so the new window's left edge sits at a positive column. The buffer winds up in that new window." - (let ((name "claude [display-rule-test]")) + (let ((name "agent [display-rule-test]")) (test-ai-vterm--cleanup name) (unwind-protect (test-ai-vterm--with-clean-frame @@ -43,7 +43,7 @@ a positive column. The buffer winds up in that new window." (test-ai-vterm--cleanup name)))) (ert-deftest test-ai-vterm--display-rule-skips-non-matching-buffer () - "Boundary: a buffer not named \"claude [...]\" does not match the rule. + "Boundary: a buffer not named \"agent [...]\" does not match the rule. The rule's regex doesn't fire, so `display-buffer' falls back to the default action -- reuse the current window -- and no rightward split @@ -59,8 +59,8 @@ occurs." (test-ai-vterm--cleanup name)))) (ert-deftest test-ai-vterm--display-rule-prefix-not-substring () - "Boundary: \"foo claude [bar]\" does not match -- the rule anchors at start." - (let ((name "foo claude [substring-test]")) + "Boundary: \"foo agent [bar]\" does not match -- the rule anchors at start." + (let ((name "foo agent [substring-test]")) (test-ai-vterm--cleanup name) (unwind-protect (test-ai-vterm--with-clean-frame diff --git a/tests/test-ai-vterm--display-saved.el b/tests/test-ai-vterm--display-saved.el index 2b2bad09..a17df0aa 100644 --- a/tests/test-ai-vterm--display-saved.el +++ b/tests/test-ai-vterm--display-saved.el @@ -23,7 +23,7 @@ (ert-deftest test-ai-vterm--display-saved-uses-defaults-when-state-nil () "Normal: nil state -> direction=rightmost, size=cj/ai-vterm-window-width. The cardinal `right' default maps to the frame-edge variant -`rightmost' so claude lands at the frame's right edge regardless of +`rightmost' so agent lands at the frame's right edge regardless of which window is selected." (let (received-buf received-alist (cj/--ai-vterm-last-direction nil) @@ -100,13 +100,13 @@ which window is selected." "Regression: capture+delete+display in a 3-window layout preserves body-width. Reproduces Craig's `peeking ~1 col' report from 2026-05-09: when -the new claude lands at a different position than the captured one +the new agent lands at a different position than the captured one (rightmost vs middle), `window-total-width' differs by 1 because of the right divider. `window-body-width' is divider-independent and is what the user actually sees, so the assertion locks down the body match." - (cj/test--kill-claude-buffers) - (let ((claude-name "claude [3win-roundtrip]") + (cj/test--kill-agent-buffers) + (let ((agent-name "agent [3win-roundtrip]") (left-name "*test-3win-left*") (right-name "*test-3win-right*")) (unwind-protect @@ -114,25 +114,25 @@ the body match." (delete-other-windows) (let ((left-buf (get-buffer-create left-name)) (right-buf (get-buffer-create right-name)) - (claude-buf (get-buffer-create claude-name))) - ;; Build: left | claude | right. Selected window starts as + (agent-buf (get-buffer-create agent-name))) + ;; Build: left | agent | right. Selected window starts as ;; the only window. Split right twice to get three windows. (set-window-buffer (selected-window) left-buf) (let* ((right-win (split-window (selected-window) nil 'right)) (_ (set-window-buffer right-win right-buf)) - (claude-win (split-window (selected-window) nil 'right))) - (set-window-buffer claude-win claude-buf) - ;; Capture claude's state. - (cj/--ai-vterm-capture-state claude-win) + (agent-win (split-window (selected-window) nil 'right))) + (set-window-buffer agent-win agent-buf) + ;; Capture agent's state. + (cj/--ai-vterm-capture-state agent-win) (let ((captured-size cj/--ai-vterm-last-size) (captured-direction cj/--ai-vterm-last-direction)) - ;; Simulate quit-window on claude. - (delete-window claude-win) + ;; Simulate quit-window on agent. + (delete-window agent-win) ;; Now route a fresh display through the actual rule. (let* ((display-buffer-alist (cj/--ai-vterm-display-rule-list)) - (new-win (display-buffer claude-buf))) + (new-win (display-buffer agent-buf))) (should (windowp new-win)) - (should (eq (window-buffer new-win) claude-buf)) + (should (eq (window-buffer new-win) agent-buf)) ;; The captured size should be replayed exactly. (should (= (window-body-width new-win) captured-size)) @@ -140,12 +140,12 @@ the body match." (should (eq captured-direction 'right))))))) (when (get-buffer left-name) (kill-buffer left-name)) (when (get-buffer right-name) (kill-buffer right-name)) - (cj/test--kill-claude-buffers)))) + (cj/test--kill-agent-buffers)))) -(ert-deftest test-ai-vterm--display-saved-3window-claude-rightmost-roundtrip () - "Round-trip when claude is the rightmost window (no right divider)." - (cj/test--kill-claude-buffers) - (let ((claude-name "claude [rightmost]") +(ert-deftest test-ai-vterm--display-saved-3window-agent-rightmost-roundtrip () + "Round-trip when agent is the rightmost window (no right divider)." + (cj/test--kill-agent-buffers) + (let ((agent-name "agent [rightmost]") (left-name "*test-rm-left*") (mid-name "*test-rm-mid*")) (unwind-protect @@ -153,28 +153,28 @@ the body match." (delete-other-windows) (let ((left-buf (get-buffer-create left-name)) (mid-buf (get-buffer-create mid-name)) - (claude-buf (get-buffer-create claude-name))) - ;; Build: left | mid | claude (claude rightmost) + (agent-buf (get-buffer-create agent-name))) + ;; Build: left | mid | agent (agent rightmost) (set-window-buffer (selected-window) left-buf) (let* ((mid-win (split-window (selected-window) nil 'right)) - (claude-win (split-window mid-win nil 'right))) + (agent-win (split-window mid-win nil 'right))) (set-window-buffer mid-win mid-buf) - (set-window-buffer claude-win claude-buf) - (cj/--ai-vterm-capture-state claude-win) + (set-window-buffer agent-win agent-buf) + (cj/--ai-vterm-capture-state agent-win) (let ((captured-size cj/--ai-vterm-last-size)) - (delete-window claude-win) + (delete-window agent-win) (let* ((display-buffer-alist (cj/--ai-vterm-display-rule-list)) - (new-win (display-buffer claude-buf))) + (new-win (display-buffer agent-buf))) (should (windowp new-win)) (should (= (window-body-width new-win) captured-size))))))) (when (get-buffer left-name) (kill-buffer left-name)) (when (get-buffer mid-name) (kill-buffer mid-name)) - (cj/test--kill-claude-buffers)))) + (cj/test--kill-agent-buffers)))) (ert-deftest test-ai-vterm--display-saved-3window-after-mouse-resize () "Round-trip after a deliberate mid-window resize (mimics mouse-drag)." - (cj/test--kill-claude-buffers) - (let ((claude-name "claude [mouse-resize]") + (cj/test--kill-agent-buffers) + (let ((agent-name "agent [mouse-resize]") (left-name "*test-mr-left*") (right-name "*test-mr-right*")) (unwind-protect @@ -182,32 +182,32 @@ the body match." (delete-other-windows) (let ((left-buf (get-buffer-create left-name)) (right-buf (get-buffer-create right-name)) - (claude-buf (get-buffer-create claude-name))) + (agent-buf (get-buffer-create agent-name))) (set-window-buffer (selected-window) left-buf) (let* ((right-win (split-window (selected-window) nil 'right)) - (claude-win (split-window (selected-window) nil 'right))) + (agent-win (split-window (selected-window) nil 'right))) (set-window-buffer right-win right-buf) - (set-window-buffer claude-win claude-buf) - ;; Resize claude smaller to mimic the user dragging the - ;; divider. Shrink claude by 5 cols, give to left. + (set-window-buffer agent-win agent-buf) + ;; Resize agent smaller to mimic the user dragging the + ;; divider. Shrink agent by 5 cols, give to left. (let ((delta -5)) - (when (window--resizable-p claude-win delta t) - (window-resize claude-win delta t))) - (cj/--ai-vterm-capture-state claude-win) + (when (window--resizable-p agent-win delta t) + (window-resize agent-win delta t))) + (cj/--ai-vterm-capture-state agent-win) (let ((captured-size cj/--ai-vterm-last-size)) - (delete-window claude-win) + (delete-window agent-win) (let* ((display-buffer-alist (cj/--ai-vterm-display-rule-list)) - (new-win (display-buffer claude-buf))) + (new-win (display-buffer agent-buf))) (should (windowp new-win)) (should (= (window-body-width new-win) captured-size))))))) (when (get-buffer left-name) (kill-buffer left-name)) (when (get-buffer right-name) (kill-buffer right-name)) - (cj/test--kill-claude-buffers)))) + (cj/test--kill-agent-buffers)))) (ert-deftest test-ai-vterm--display-saved-roundtrip-via-cj/ai-vterm-toggle () "End-to-end: toggle-off via dispatch then redisplay -- preserves size." - (cj/test--kill-claude-buffers) - (let ((claude-name "claude [toggle-roundtrip]") + (cj/test--kill-agent-buffers) + (let ((agent-name "agent [toggle-roundtrip]") (left-name "*test-tr-left*") (right-name "*test-tr-right*")) (unwind-protect @@ -215,33 +215,33 @@ the body match." (delete-other-windows) (let ((left-buf (get-buffer-create left-name)) (right-buf (get-buffer-create right-name)) - (claude-buf (get-buffer-create claude-name))) + (agent-buf (get-buffer-create agent-name))) (set-window-buffer (selected-window) left-buf) (let* ((right-win (split-window (selected-window) nil 'right)) - (claude-win (split-window (selected-window) nil 'right))) + (agent-win (split-window (selected-window) nil 'right))) (set-window-buffer right-win right-buf) - (set-window-buffer claude-win claude-buf) + (set-window-buffer agent-win agent-buf) (let ((display-buffer-alist (cj/--ai-vterm-display-rule-list))) - ;; Focus claude (mimics `M-x cj/ai-vterm' from inside claude). - (select-window claude-win) - (let ((before-size (window-body-width claude-win))) + ;; Focus agent (mimics `M-x cj/ai-vterm' from inside agent). + (select-window agent-win) + (let ((before-size (window-body-width agent-win))) ;; Toggle off via the actual command -- captures + quit-window. (cj/ai-vterm) - (should-not (cj/--ai-vterm-displayed-claude-window)) + (should-not (cj/--ai-vterm-displayed-agent-window)) ;; Toggle on -- single-buffer DWIM redisplay path. (cj/ai-vterm) - (let* ((new-win (cj/--ai-vterm-displayed-claude-window)) + (let* ((new-win (cj/--ai-vterm-displayed-agent-window)) (new-size (window-body-width new-win))) (should (windowp new-win)) (should (= new-size before-size)))))))) (when (get-buffer left-name) (kill-buffer left-name)) (when (get-buffer right-name) (kill-buffer right-name)) - (cj/test--kill-claude-buffers)))) + (cj/test--kill-agent-buffers)))) (ert-deftest test-ai-vterm--display-saved-two-toggle-cycles-stable () "Two consecutive toggle-off+toggle-on cycles -- no compounding error." - (cj/test--kill-claude-buffers) - (let ((claude-name "claude [two-cycle]") + (cj/test--kill-agent-buffers) + (let ((agent-name "agent [two-cycle]") (left-name "*test-2c-left*") (right-name "*test-2c-right*")) (unwind-protect @@ -249,86 +249,86 @@ the body match." (delete-other-windows) (let ((left-buf (get-buffer-create left-name)) (right-buf (get-buffer-create right-name)) - (claude-buf (get-buffer-create claude-name))) + (agent-buf (get-buffer-create agent-name))) (set-window-buffer (selected-window) left-buf) (let* ((right-win (split-window (selected-window) nil 'right)) - (claude-win (split-window (selected-window) nil 'right))) + (agent-win (split-window (selected-window) nil 'right))) (set-window-buffer right-win right-buf) - (set-window-buffer claude-win claude-buf) + (set-window-buffer agent-win agent-buf) (let ((display-buffer-alist (cj/--ai-vterm-display-rule-list)) - (initial-size (window-body-width claude-win))) - (select-window claude-win) + (initial-size (window-body-width agent-win))) + (select-window agent-win) ;; Cycle 1 (cj/ai-vterm) ; off (cj/ai-vterm) ; on (let ((cycle1-size (window-body-width - (cj/--ai-vterm-displayed-claude-window)))) + (cj/--ai-vterm-displayed-agent-window)))) (should (= cycle1-size initial-size)) - (select-window (cj/--ai-vterm-displayed-claude-window)) + (select-window (cj/--ai-vterm-displayed-agent-window)) ;; Cycle 2 (cj/ai-vterm) ; off (cj/ai-vterm) ; on (let ((cycle2-size (window-body-width - (cj/--ai-vterm-displayed-claude-window)))) + (cj/--ai-vterm-displayed-agent-window)))) (should (= cycle2-size initial-size)))))))) (when (get-buffer left-name) (kill-buffer left-name)) (when (get-buffer right-name) (kill-buffer right-name)) - (cj/test--kill-claude-buffers)))) + (cj/test--kill-agent-buffers)))) (ert-deftest test-ai-vterm--display-saved-craig-c-x-3-roundtrip () "Reproduces Craig's repro from 2026-05-09: launch -> F9 -> dashboard splits via C-x 3 -> toggle off -> toggle on. -Expected: new claude lands at the same total-width it had before." - (cj/test--kill-claude-buffers) - (let ((claude-name "claude [c-x-3-repro]") +Expected: new agent lands at the same total-width it had before." + (cj/test--kill-agent-buffers) + (let ((agent-name "agent [c-x-3-repro]") (dash-name "*test-cx3-dashboard*")) (unwind-protect (save-window-excursion (delete-other-windows) (let ((dash-buf (get-buffer-create dash-name)) - (claude-buf (get-buffer-create claude-name))) + (agent-buf (get-buffer-create agent-name))) (set-window-buffer (selected-window) dash-buf) (let ((display-buffer-alist (cj/--ai-vterm-display-rule-list))) - ;; Step 1: F9 displays claude. Layout: dashboard | claude. - (let ((claude-win-1 (display-buffer claude-buf))) - (should (windowp claude-win-1))) + ;; Step 1: F9 displays agent. Layout: dashboard | agent. + (let ((agent-win-1 (display-buffer agent-buf))) + (should (windowp agent-win-1))) ;; Step 2: focus dashboard, C-x 3 (split-window-right). (let ((dash-win (get-buffer-window dash-buf))) (select-window dash-win) (split-window-right)) - ;; Layout now: dashboard1 | dashboard2 | claude - ;; Capture claude's pre-toggle body width for later assertion. - (let* ((claude-win-2 (cj/--ai-vterm-displayed-claude-window)) - (size-before (window-body-width claude-win-2))) - ;; Step 3: F9 toggles claude off (selected is dashboard). + ;; Layout now: dashboard1 | dashboard2 | agent + ;; Capture agent's pre-toggle body width for later assertion. + (let* ((agent-win-2 (cj/--ai-vterm-displayed-agent-window)) + (size-before (window-body-width agent-win-2))) + ;; Step 3: F9 toggles agent off (selected is dashboard). (cj/ai-vterm) - (should-not (cj/--ai-vterm-displayed-claude-window)) - ;; Step 4: F9 toggles claude on -- redisplay-single path. + (should-not (cj/--ai-vterm-displayed-agent-window)) + ;; Step 4: F9 toggles agent on -- redisplay-single path. (cj/ai-vterm) - (let* ((claude-win-3 (cj/--ai-vterm-displayed-claude-window)) - (size-after (window-body-width claude-win-3))) - (should (windowp claude-win-3)) + (let* ((agent-win-3 (cj/--ai-vterm-displayed-agent-window)) + (size-after (window-body-width agent-win-3))) + (should (windowp agent-win-3)) (should (= size-after size-before))))))) (when (get-buffer dash-name) (kill-buffer dash-name)) - (cj/test--kill-claude-buffers)))) + (cj/test--kill-agent-buffers)))) (ert-deftest test-ai-vterm--toggle-after-buffer-move-no-extra-window () - "Regression: toggle-off must remove claude's window even when buffer-move + "Regression: toggle-off must remove agent's window even when buffer-move has cleared its `quit-restore' parameter. Reproduces Craig's repro from 2026-05-09: 3 windows, user uses -buffer-move (C-M-arrows) to relocate claude. buffer-move swaps +buffer-move (C-M-arrows) to relocate agent. buffer-move swaps buffers between windows and leaves the receiving window with no -record that it was created for the claude buffer. `quit-window' +record that it was created for the agent buffer. `quit-window' respects that history and only buries -- the window stays with some other buffer in it. The next toggle-on then doesn't recognize -that window as a claude home and creates a fresh one alongside, +that window as an agent home and creates a fresh one alongside, landing the user at N+1 windows instead of N. Assertion: after toggle-off+toggle-on, the window count is back to its pre-cycle value, regardless of `quit-restore' state." - (cj/test--kill-claude-buffers) - (let ((claude-name "claude [buffer-move-toggle]") + (cj/test--kill-agent-buffers) + (let ((agent-name "agent [buffer-move-toggle]") (left-name "*test-bm-left*") (right-name "*test-bm-right*")) (unwind-protect @@ -336,31 +336,31 @@ its pre-cycle value, regardless of `quit-restore' state." (delete-other-windows) (let ((left-buf (get-buffer-create left-name)) (right-buf (get-buffer-create right-name)) - (claude-buf (get-buffer-create claude-name))) + (agent-buf (get-buffer-create agent-name))) (set-window-buffer (selected-window) left-buf) (let* ((right-win (split-window (selected-window) nil 'right)) - (claude-win (split-window (selected-window) nil 'right))) + (agent-win (split-window (selected-window) nil 'right))) (set-window-buffer right-win right-buf) - (set-window-buffer claude-win claude-buf) - ;; Mimic buffer-move's effect: claude lives in this + (set-window-buffer agent-win agent-buf) + ;; Mimic buffer-move's effect: agent lives in this ;; window but quit-restore says nothing about it. - (set-window-parameter claude-win 'quit-restore nil) + (set-window-parameter agent-win 'quit-restore nil) (let ((display-buffer-alist (cj/--ai-vterm-display-rule-list)) (window-count-before (count-windows))) - (select-window claude-win) + (select-window agent-win) (cj/ai-vterm) ; off (cj/ai-vterm) ; on (should (= (count-windows) window-count-before)) - ;; Claude must be displayed exactly once. - (let ((claude-windows + ;; Agent must be displayed exactly once. + (let ((agent-windows (seq-filter (lambda (w) - (eq (window-buffer w) claude-buf)) + (eq (window-buffer w) agent-buf)) (window-list)))) - (should (= (length claude-windows) 1))))))) + (should (= (length agent-windows) 1))))))) (when (get-buffer left-name) (kill-buffer left-name)) (when (get-buffer right-name) (kill-buffer right-name)) - (cj/test--kill-claude-buffers)))) + (cj/test--kill-agent-buffers)))) (provide 'test-ai-vterm--display-saved) ;;; test-ai-vterm--display-saved.el ends here diff --git a/tests/test-ai-vterm--displayed-agent-window.el b/tests/test-ai-vterm--displayed-agent-window.el new file mode 100644 index 00000000..f36ca9f5 --- /dev/null +++ b/tests/test-ai-vterm--displayed-agent-window.el @@ -0,0 +1,60 @@ +;;; test-ai-vterm--displayed-agent-window.el --- Tests for the displayed-window helper -*- lexical-binding: t; -*- + +;;; Commentary: +;; The helper returns a window in the selected frame whose buffer +;; satisfies `cj/--ai-vterm-buffer-p', or nil when no such window +;; exists. Used by F9 dispatch and M-F9 in-place replacement. + +;;; Code: + +(require 'ert) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) +(require 'ai-vterm) +(require 'testutil-vterm-buffers) + +(ert-deftest test-ai-vterm--displayed-agent-window-no-buffers-returns-nil () + "Boundary: no agent buffers anywhere -> nil." + (cj/test--kill-agent-buffers) + (save-window-excursion + (delete-other-windows) + (should-not (cj/--ai-vterm-displayed-agent-window)))) + +(ert-deftest test-ai-vterm--displayed-agent-window-not-displayed-returns-nil () + "Boundary: agent buffer exists but not in any window -> nil." + (cj/test--kill-agent-buffers) + (let ((b1 (get-buffer-create "agent [hidden]"))) + (unwind-protect + (save-window-excursion + (delete-other-windows) + (should-not (cj/--ai-vterm-displayed-agent-window))) + (kill-buffer b1)))) + +(ert-deftest test-ai-vterm--displayed-agent-window-returns-window-when-displayed () + "Normal: agent buffer in a window -> returns that window." + (cj/test--kill-agent-buffers) + (let ((b1 (get-buffer-create "agent [shown]"))) + (unwind-protect + (save-window-excursion + (delete-other-windows) + (let ((win (split-window-right))) + (set-window-buffer win b1) + (let ((result (cj/--ai-vterm-displayed-agent-window))) + (should (windowp result)) + (should (eq (window-buffer result) b1))))) + (kill-buffer b1)))) + +(ert-deftest test-ai-vterm--displayed-agent-window-ignores-non-agent-windows () + "Boundary: only a non-agent buffer is displayed -> nil." + (cj/test--kill-agent-buffers) + (let ((other (get-buffer-create "regular-displayed-buffer"))) + (unwind-protect + (save-window-excursion + (delete-other-windows) + (set-window-buffer (selected-window) other) + (should-not (cj/--ai-vterm-displayed-agent-window))) + (kill-buffer other)))) + +(provide 'test-ai-vterm--displayed-agent-window) +;;; test-ai-vterm--displayed-agent-window.el ends here diff --git a/tests/test-ai-vterm--displayed-claude-window.el b/tests/test-ai-vterm--displayed-claude-window.el deleted file mode 100644 index 9f84f87b..00000000 --- a/tests/test-ai-vterm--displayed-claude-window.el +++ /dev/null @@ -1,60 +0,0 @@ -;;; test-ai-vterm--displayed-claude-window.el --- Tests for the displayed-window helper -*- lexical-binding: t; -*- - -;;; Commentary: -;; The helper returns a window in the selected frame whose buffer -;; satisfies `cj/--ai-vterm-buffer-p', or nil when no such window -;; exists. Used by F9 dispatch and M-F9 in-place replacement. - -;;; Code: - -(require 'ert) - -(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) -(add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) -(require 'ai-vterm) -(require 'testutil-vterm-buffers) - -(ert-deftest test-ai-vterm--displayed-claude-window-no-buffers-returns-nil () - "Boundary: no claude buffers anywhere -> nil." - (cj/test--kill-claude-buffers) - (save-window-excursion - (delete-other-windows) - (should-not (cj/--ai-vterm-displayed-claude-window)))) - -(ert-deftest test-ai-vterm--displayed-claude-window-not-displayed-returns-nil () - "Boundary: claude buffer exists but not in any window -> nil." - (cj/test--kill-claude-buffers) - (let ((b1 (get-buffer-create "claude [hidden]"))) - (unwind-protect - (save-window-excursion - (delete-other-windows) - (should-not (cj/--ai-vterm-displayed-claude-window))) - (kill-buffer b1)))) - -(ert-deftest test-ai-vterm--displayed-claude-window-returns-window-when-displayed () - "Normal: claude buffer in a window -> returns that window." - (cj/test--kill-claude-buffers) - (let ((b1 (get-buffer-create "claude [shown]"))) - (unwind-protect - (save-window-excursion - (delete-other-windows) - (let ((win (split-window-right))) - (set-window-buffer win b1) - (let ((result (cj/--ai-vterm-displayed-claude-window))) - (should (windowp result)) - (should (eq (window-buffer result) b1))))) - (kill-buffer b1)))) - -(ert-deftest test-ai-vterm--displayed-claude-window-ignores-non-claude-windows () - "Boundary: only a non-claude buffer is displayed -> nil." - (cj/test--kill-claude-buffers) - (let ((other (get-buffer-create "regular-displayed-buffer"))) - (unwind-protect - (save-window-excursion - (delete-other-windows) - (set-window-buffer (selected-window) other) - (should-not (cj/--ai-vterm-displayed-claude-window))) - (kill-buffer other)))) - -(provide 'test-ai-vterm--displayed-claude-window) -;;; test-ai-vterm--displayed-claude-window.el ends here diff --git a/tests/test-ai-vterm--launch-command.el b/tests/test-ai-vterm--launch-command.el index 17f02f02..7e455a8b 100644 --- a/tests/test-ai-vterm--launch-command.el +++ b/tests/test-ai-vterm--launch-command.el @@ -2,12 +2,12 @@ ;;; Commentary: ;; The launch command is what gets typed into a fresh vterm shell to bring -;; up Claude inside a per-project tmux session. The session is named +;; up the agent inside a per-project tmux session. The session is named ;; `cj/ai-vterm-tmux-session-prefix' + the project basename, so a second -;; F9 on the same project reattaches to the running Claude rather than +;; F9 on the same project reattaches to the running agent rather than ;; spawning a new one, and `tmux ls' output can be filtered to AI-vterm's ;; own sessions. The trailing `exec bash' keeps the tmux window alive if -;; Claude exits, leaving the session intact for recovery. +;; the agent exits, leaving the session intact for recovery. ;;; Code: @@ -18,22 +18,22 @@ (ert-deftest test-ai-vterm--launch-command-uses-new-session-attach () "Normal: starts with `tmux new-session -A' so existing sessions reattach." - (let ((cj/ai-vterm-claude-command "claude")) + (let ((cj/ai-vterm-agent-command "agent")) (should (string-prefix-p "tmux new-session -A " (cj/--ai-vterm-launch-command "/code/foo"))))) (ert-deftest test-ai-vterm--launch-command-includes-prefixed-session-name () "Normal: the session name is the prefixed form from the name helper." - (let ((cj/ai-vterm-claude-command "claude") + (let ((cj/ai-vterm-agent-command "agent") (cj/ai-vterm-tmux-session-prefix "aiv-")) (should (string-match-p " -s aiv-foo " (cj/--ai-vterm-launch-command "/code/foo"))))) (ert-deftest test-ai-vterm--launch-command-names-window () - "Normal: `-n ' so the claude window is named distinctly." - (let ((cj/ai-vterm-claude-command "claude") + "Normal: `-n ' so the agent window is named distinctly." + (let ((cj/ai-vterm-agent-command "agent") (cj/ai-vterm-tmux-window-name "ai")) (should (string-match-p " -n ai " @@ -41,36 +41,36 @@ (ert-deftest test-ai-vterm--launch-command-honors-custom-window-name () "Boundary: a non-default window name is what `-n' gets." - (let ((cj/ai-vterm-claude-command "claude") - (cj/ai-vterm-tmux-window-name "claude")) + (let ((cj/ai-vterm-agent-command "agent") + (cj/ai-vterm-tmux-window-name "agent")) (should (string-match-p - " -n claude " + " -n agent " (cj/--ai-vterm-launch-command "/code/foo"))))) (ert-deftest test-ai-vterm--launch-command-includes-start-directory () "Normal: `-c ' so the new session's first window starts in DIR." - (let ((cj/ai-vterm-claude-command "claude")) + (let ((cj/ai-vterm-agent-command "agent")) (should (string-match-p " -c /code/foo " (cj/--ai-vterm-launch-command "/code/foo"))))) -(ert-deftest test-ai-vterm--launch-command-includes-claude-command () - "Normal: the configured claude command is in the launched shell command." - (let ((cj/ai-vterm-claude-command "claude --some-flag")) +(ert-deftest test-ai-vterm--launch-command-includes-agent-command () + "Normal: the configured agent command is in the launched shell command." + (let ((cj/ai-vterm-agent-command "agent --some-flag")) (should (string-match-p - "claude --some-flag" + "agent --some-flag" (cj/--ai-vterm-launch-command "/code/foo"))))) (ert-deftest test-ai-vterm--launch-command-tails-with-exec-bash () - "Boundary: `exec bash' tails so the tmux window survives Claude exiting." - (let ((cj/ai-vterm-claude-command "claude")) + "Boundary: `exec bash' tails so the tmux window survives the agent exiting." + (let ((cj/ai-vterm-agent-command "agent")) (should (string-match-p "exec bash" (cj/--ai-vterm-launch-command "/code/foo"))))) (ert-deftest test-ai-vterm--launch-command-handles-spaces-in-basename () "Boundary: a basename with whitespace becomes hyphenated before quoting." - (let ((cj/ai-vterm-claude-command "claude") + (let ((cj/ai-vterm-agent-command "agent") (cj/ai-vterm-tmux-session-prefix "aiv-")) (should (string-match-p " -s aiv-my-work " diff --git a/tests/test-ai-vterm--live-tmux-sessions.el b/tests/test-ai-vterm--live-tmux-sessions.el index 38a0488d..e00b0018 100644 --- a/tests/test-ai-vterm--live-tmux-sessions.el +++ b/tests/test-ai-vterm--live-tmux-sessions.el @@ -2,7 +2,7 @@ ;;; Commentary: ;; Lists the live tmux sessions that carry the AI-vterm prefix so the -;; project picker can surface projects whose Claude session survived an +;; project picker can surface projects whose agent session survived an ;; Emacs crash. tmux being absent or no server running is a normal ;; "nothing to match" outcome, not an error -- the lister returns nil. diff --git a/tests/test-ai-vterm--pick-buffer-candidates.el b/tests/test-ai-vterm--pick-buffer-candidates.el index ddfd7529..c32039de 100644 --- a/tests/test-ai-vterm--pick-buffer-candidates.el +++ b/tests/test-ai-vterm--pick-buffer-candidates.el @@ -22,57 +22,57 @@ (ert-deftest test-ai-vterm--pick-buffer-candidates-empty-buffers () "Boundary: empty buffer list -> empty alist regardless of shown." - (cj/test--kill-claude-buffers) + (cj/test--kill-agent-buffers) (should (null (cj/--ai-vterm-pick-buffer-candidates nil nil))) (should (null (cj/--ai-vterm-pick-buffer-candidates nil 'sentinel)))) (ert-deftest test-ai-vterm--pick-buffer-candidates-shown-nil () "Normal: shown is nil -> straight alist in input order, no marker." - (cj/test--kill-claude-buffers) - (let ((b1 (get-buffer-create "claude [a]")) - (b2 (get-buffer-create "claude [b]"))) + (cj/test--kill-agent-buffers) + (let ((b1 (get-buffer-create "agent [a]")) + (b2 (get-buffer-create "agent [b]"))) (unwind-protect (let ((result (cj/--ai-vterm-pick-buffer-candidates (list b1 b2) nil))) - (should (equal result `(("claude [a]" . ,b1) - ("claude [b]" . ,b2))))) + (should (equal result `(("agent [a]" . ,b1) + ("agent [b]" . ,b2))))) (kill-buffer b1) (kill-buffer b2)))) (ert-deftest test-ai-vterm--pick-buffer-candidates-shown-promotes-non-shown () "Normal: shown buffer sorts last with [shown] suffix; others first." - (cj/test--kill-claude-buffers) - (let ((b1 (get-buffer-create "claude [a]")) - (b2 (get-buffer-create "claude [b]")) - (b3 (get-buffer-create "claude [c]"))) + (cj/test--kill-agent-buffers) + (let ((b1 (get-buffer-create "agent [a]")) + (b2 (get-buffer-create "agent [b]")) + (b3 (get-buffer-create "agent [c]"))) (unwind-protect (let ((result (cj/--ai-vterm-pick-buffer-candidates (list b1 b2 b3) b1))) (should (equal result - `(("claude [b]" . ,b2) - ("claude [c]" . ,b3) - ("claude [a] [shown]" . ,b1))))) + `(("agent [b]" . ,b2) + ("agent [c]" . ,b3) + ("agent [a] [shown]" . ,b1))))) (kill-buffer b1) (kill-buffer b2) (kill-buffer b3)))) (ert-deftest test-ai-vterm--pick-buffer-candidates-shown-only-buffer () "Boundary: shown is the only entry -> single cell with [shown] marker." - (cj/test--kill-claude-buffers) - (let ((b1 (get-buffer-create "claude [a]"))) + (cj/test--kill-agent-buffers) + (let ((b1 (get-buffer-create "agent [a]"))) (unwind-protect (let ((result (cj/--ai-vterm-pick-buffer-candidates (list b1) b1))) - (should (equal result `(("claude [a] [shown]" . ,b1))))) + (should (equal result `(("agent [a] [shown]" . ,b1))))) (kill-buffer b1)))) (ert-deftest test-ai-vterm--pick-buffer-candidates-shown-not-in-buffers () "Boundary: stale shown buffer not in list -> all cells are non-shown." - (cj/test--kill-claude-buffers) - (let ((b1 (get-buffer-create "claude [a]")) - (b-stale (get-buffer-create "claude [stale]"))) + (cj/test--kill-agent-buffers) + (let ((b1 (get-buffer-create "agent [a]")) + (b-stale (get-buffer-create "agent [stale]"))) (unwind-protect (let ((result (cj/--ai-vterm-pick-buffer-candidates (list b1) b-stale))) - (should (equal result `(("claude [a]" . ,b1))))) + (should (equal result `(("agent [a]" . ,b1))))) (kill-buffer b1) (kill-buffer b-stale)))) diff --git a/tests/test-ai-vterm--pick-project.el b/tests/test-ai-vterm--pick-project.el index a90fe822..f332589a 100644 --- a/tests/test-ai-vterm--pick-project.el +++ b/tests/test-ai-vterm--pick-project.el @@ -69,7 +69,7 @@ Works whether COLLECTION is an alist or a completion-table function." '("/c/baz [detached]" "/c/bar" "/c/foo")))))) (ert-deftest test-ai-vterm--format-candidate-flags-running-project () - "Normal: a path whose claude buffer has a live process gets a [running] suffix." + "Normal: a path whose agent buffer has a live process gets a [running] suffix." (let* ((path (expand-file-name "~/code/already-running")) (buffer-name (cj/--ai-vterm-buffer-name path)) (buf (get-buffer-create buffer-name))) @@ -107,7 +107,7 @@ Works whether COLLECTION is an alist or a completion-table function." (ert-deftest test-ai-vterm--format-candidate-omits-flag-when-not-running () "Boundary: a path with no buffer or no live process -> plain abbreviated path." (let ((path (expand-file-name "~/code/not-running"))) - ;; Make sure no claude buffer exists for this path. + ;; Make sure no agent buffer exists for this path. (let ((bn (cj/--ai-vterm-buffer-name path))) (when (get-buffer bn) (kill-buffer bn))) (should (equal (cj/--ai-vterm-format-candidate path) diff --git a/tests/test-ai-vterm--reuse-existing-agent.el b/tests/test-ai-vterm--reuse-existing-agent.el new file mode 100644 index 00000000..e6848014 --- /dev/null +++ b/tests/test-ai-vterm--reuse-existing-agent.el @@ -0,0 +1,99 @@ +;;; test-ai-vterm--reuse-existing-agent.el --- Tests for reuse-existing-agent action -*- lexical-binding: t; -*- + +;;; Commentary: +;; The action looks for any window in the selected frame whose buffer +;; satisfies `cj/--ai-vterm-buffer-p'. When found, swaps that +;; window's buffer for the one being displayed and returns the +;; window. When not found, returns nil so the next action in the +;; chain runs. +;; +;; This is the action that keeps C-F9 (project-switch) from stealing +;; a non-agent window when the user is focused inside agent. + +;;; Code: + +(require 'ert) + +(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) +(add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) +(require 'ai-vterm) +(require 'testutil-vterm-buffers) + +(ert-deftest test-ai-vterm--reuse-existing-agent-swaps-buffer-when-window-exists () + "Normal: an agent window exists -> swap its buffer, return the window." + (cj/test--kill-agent-buffers) + (save-window-excursion + (delete-other-windows) + (let ((existing (get-buffer-create "agent [existing]")) + (new-buf (get-buffer-create "agent [new]")) + (split (split-window (selected-window) nil 'right))) + (unwind-protect + (progn + (set-window-buffer split existing) + (let ((result (cj/--ai-vterm-reuse-existing-agent new-buf nil))) + (should (eq result split)) + (should (eq (window-buffer split) new-buf)))) + (kill-buffer existing) + (kill-buffer new-buf))))) + +(ert-deftest test-ai-vterm--reuse-existing-agent-returns-nil-when-no-agent-window () + "Boundary: no agent window in frame -> nil (chain continues to next action)." + (cj/test--kill-agent-buffers) + (save-window-excursion + (delete-other-windows) + (let ((new-buf (get-buffer-create "agent [no-existing]"))) + (unwind-protect + (should (null (cj/--ai-vterm-reuse-existing-agent new-buf nil))) + (kill-buffer new-buf))))) + +(ert-deftest test-ai-vterm--reuse-existing-agent-leaves-non-agent-windows-alone () + "Boundary: only non-agent windows in frame -> nil; other windows untouched." + (cj/test--kill-agent-buffers) + (save-window-excursion + (delete-other-windows) + (let ((code-buf (get-buffer-create "*test-code-buffer*")) + (new-agent (get-buffer-create "agent [new-here]")) + (other-win (split-window (selected-window) nil 'right))) + (unwind-protect + (progn + (set-window-buffer (selected-window) code-buf) + (set-window-buffer other-win code-buf) + (let ((result (cj/--ai-vterm-reuse-existing-agent + new-agent nil))) + (should (null result)) + (should (eq (window-buffer (selected-window)) code-buf)) + (should (eq (window-buffer other-win) code-buf)))) + (kill-buffer code-buf) + (kill-buffer new-agent))))) + +(ert-deftest test-ai-vterm--reuse-existing-agent-preserves-non-agent-window-when-swapping () + "Normal: swap agent window only; the other window keeps its buffer. + +This is the C-F9-from-agent regression: with agent at the bottom +and code on top, switching projects must replace the bottom window's +buffer, not the top window's." + (cj/test--kill-agent-buffers) + (save-window-excursion + (delete-other-windows) + (let* ((code-buf (get-buffer-create "*test-code-top*")) + (agent-a (get-buffer-create "agent [a]")) + (agent-b (get-buffer-create "agent [b]")) + (top-win (selected-window)) + (bottom-win (split-window top-win nil 'below))) + (unwind-protect + (progn + (set-window-buffer top-win code-buf) + (set-window-buffer bottom-win agent-a) + ;; Focus the agent window -- this is the regression scenario. + (select-window bottom-win) + (let ((result (cj/--ai-vterm-reuse-existing-agent + agent-b nil))) + (should (eq result bottom-win)) + (should (eq (window-buffer bottom-win) agent-b)) + (should (eq (window-buffer top-win) code-buf)))) + (kill-buffer code-buf) + (kill-buffer agent-a) + (kill-buffer agent-b))))) + +(provide 'test-ai-vterm--reuse-existing-agent) +;;; test-ai-vterm--reuse-existing-agent.el ends here diff --git a/tests/test-ai-vterm--reuse-existing-claude.el b/tests/test-ai-vterm--reuse-existing-claude.el deleted file mode 100644 index 195e50a2..00000000 --- a/tests/test-ai-vterm--reuse-existing-claude.el +++ /dev/null @@ -1,99 +0,0 @@ -;;; test-ai-vterm--reuse-existing-claude.el --- Tests for reuse-existing-claude action -*- lexical-binding: t; -*- - -;;; Commentary: -;; The action looks for any window in the selected frame whose buffer -;; satisfies `cj/--ai-vterm-buffer-p'. When found, swaps that -;; window's buffer for the one being displayed and returns the -;; window. When not found, returns nil so the next action in the -;; chain runs. -;; -;; This is the action that keeps C-F9 (project-switch) from stealing -;; a non-claude window when the user is focused inside claude. - -;;; Code: - -(require 'ert) - -(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory)) -(add-to-list 'load-path (expand-file-name "tests" user-emacs-directory)) -(require 'ai-vterm) -(require 'testutil-vterm-buffers) - -(ert-deftest test-ai-vterm--reuse-existing-claude-swaps-buffer-when-window-exists () - "Normal: a claude window exists -> swap its buffer, return the window." - (cj/test--kill-claude-buffers) - (save-window-excursion - (delete-other-windows) - (let ((existing (get-buffer-create "claude [existing]")) - (new-buf (get-buffer-create "claude [new]")) - (split (split-window (selected-window) nil 'right))) - (unwind-protect - (progn - (set-window-buffer split existing) - (let ((result (cj/--ai-vterm-reuse-existing-claude new-buf nil))) - (should (eq result split)) - (should (eq (window-buffer split) new-buf)))) - (kill-buffer existing) - (kill-buffer new-buf))))) - -(ert-deftest test-ai-vterm--reuse-existing-claude-returns-nil-when-no-claude-window () - "Boundary: no claude window in frame -> nil (chain continues to next action)." - (cj/test--kill-claude-buffers) - (save-window-excursion - (delete-other-windows) - (let ((new-buf (get-buffer-create "claude [no-existing]"))) - (unwind-protect - (should (null (cj/--ai-vterm-reuse-existing-claude new-buf nil))) - (kill-buffer new-buf))))) - -(ert-deftest test-ai-vterm--reuse-existing-claude-leaves-non-claude-windows-alone () - "Boundary: only non-claude windows in frame -> nil; other windows untouched." - (cj/test--kill-claude-buffers) - (save-window-excursion - (delete-other-windows) - (let ((code-buf (get-buffer-create "*test-code-buffer*")) - (new-claude (get-buffer-create "claude [new-here]")) - (other-win (split-window (selected-window) nil 'right))) - (unwind-protect - (progn - (set-window-buffer (selected-window) code-buf) - (set-window-buffer other-win code-buf) - (let ((result (cj/--ai-vterm-reuse-existing-claude - new-claude nil))) - (should (null result)) - (should (eq (window-buffer (selected-window)) code-buf)) - (should (eq (window-buffer other-win) code-buf)))) - (kill-buffer code-buf) - (kill-buffer new-claude))))) - -(ert-deftest test-ai-vterm--reuse-existing-claude-preserves-non-claude-window-when-swapping () - "Normal: swap claude window only; the other window keeps its buffer. - -This is the C-F9-from-claude regression: with claude at the bottom -and code on top, switching projects must replace the bottom window's -buffer, not the top window's." - (cj/test--kill-claude-buffers) - (save-window-excursion - (delete-other-windows) - (let* ((code-buf (get-buffer-create "*test-code-top*")) - (claude-a (get-buffer-create "claude [a]")) - (claude-b (get-buffer-create "claude [b]")) - (top-win (selected-window)) - (bottom-win (split-window top-win nil 'below))) - (unwind-protect - (progn - (set-window-buffer top-win code-buf) - (set-window-buffer bottom-win claude-a) - ;; Focus the claude window -- this is the regression scenario. - (select-window bottom-win) - (let ((result (cj/--ai-vterm-reuse-existing-claude - claude-b nil))) - (should (eq result bottom-win)) - (should (eq (window-buffer bottom-win) claude-b)) - (should (eq (window-buffer top-win) code-buf)))) - (kill-buffer code-buf) - (kill-buffer claude-a) - (kill-buffer claude-b))))) - -(provide 'test-ai-vterm--reuse-existing-claude) -;;; test-ai-vterm--reuse-existing-claude.el ends here diff --git a/tests/test-ai-vterm--show-or-create.el b/tests/test-ai-vterm--show-or-create.el index 3fee4883..0a3dbde5 100644 --- a/tests/test-ai-vterm--show-or-create.el +++ b/tests/test-ai-vterm--show-or-create.el @@ -3,7 +3,7 @@ ;;; Commentary: ;; Tests the show-or-create branching: ;; -;; - buffer absent -> vterm called, claude command sent +;; - buffer absent -> vterm called, agent command sent ;; - buffer present, live -> vterm not called, buffer displayed ;; - buffer present, dead -> old buffer killed, vterm recreates ;; @@ -58,7 +58,7 @@ VARS is a plist of capture variable names: :calls, :strings, :returns, (ert-deftest test-ai-vterm--show-or-create-creates-when-buffer-missing () "Normal: no existing buffer -> vterm called once, launch cmd sent." - (let ((name "claude [normal-create-test]")) + (let ((name "agent [normal-create-test]")) (test-ai-vterm--cleanup name) (unwind-protect (test-ai-vterm--with-mock-vterm (:calls calls :strings strings @@ -73,7 +73,7 @@ VARS is a plist of capture variable names: :calls, :strings, :returns, (ert-deftest test-ai-vterm--show-or-create-displays-existing-when-process-live () "Normal: buffer exists with live process -> vterm not called." - (let ((name "claude [reuse-test]")) + (let ((name "agent [reuse-test]")) (test-ai-vterm--cleanup name) (unwind-protect (let ((buf (get-buffer-create name))) @@ -89,7 +89,7 @@ VARS is a plist of capture variable names: :calls, :strings, :returns, (ert-deftest test-ai-vterm--show-or-create-recreates-when-process-dead () "Boundary: buffer exists with dead process -> killed and recreated." - (let ((name "claude [dead-test]")) + (let ((name "agent [dead-test]")) (test-ai-vterm--cleanup name) (unwind-protect (let ((stale (get-buffer-create name))) @@ -111,17 +111,17 @@ VARS is a plist of capture variable names: :calls, :strings, :returns, Real `vterm' replaces the selected window's buffer as a side-effect of construction. On a fresh-boot frame (one window showing the dashboard), that side-effect previously left the original window pointing at the new -claude buffer; the dashboard was buried, the alist-routed split then -created a second window also showing claude. The wrapper must restore +agent buffer; the dashboard was buried, the alist-routed split then +created a second window also showing agent. The wrapper must restore the original window state before `display-buffer' fires so dashboard -stays put and the alist places claude into a fresh right-side split. +stays put and the alist places agent into a fresh right-side split. This test stubs `vterm' to mimic the pop-to-buffer-same-window side-effect and asserts the originally-selected window still shows its original buffer after `cj/--ai-vterm-show-or-create' returns." - (let ((claude-name "claude [preserve-window-test]") + (let ((agent-name "agent [preserve-window-test]") (orig-name "*test-original-buffer*")) - (test-ai-vterm--cleanup claude-name) + (test-ai-vterm--cleanup agent-name) (when (get-buffer orig-name) (kill-buffer orig-name)) (unwind-protect (save-window-excursion @@ -139,14 +139,14 @@ after `cj/--ai-vterm-show-or-create' returns." (lambda (_s &optional _) nil)) ((symbol-function 'vterm-send-return) (lambda () nil))) - (cj/--ai-vterm-show-or-create "/tmp/preserve" claude-name) + (cj/--ai-vterm-show-or-create "/tmp/preserve" agent-name) (should (eq (window-buffer orig-win) orig-buf))))) - (test-ai-vterm--cleanup claude-name) + (test-ai-vterm--cleanup agent-name) (when (get-buffer orig-name) (kill-buffer orig-name))))) (ert-deftest test-ai-vterm--show-or-create-returns-buffer () "Normal: return value is the vterm buffer." - (let ((name "claude [return-test]")) + (let ((name "agent [return-test]")) (test-ai-vterm--cleanup name) (unwind-protect (test-ai-vterm--with-mock-vterm (:calls _c :strings _s diff --git a/tests/test-ai-vterm--sort-candidates.el b/tests/test-ai-vterm--sort-candidates.el index 0b602083..5e3d760a 100644 --- a/tests/test-ai-vterm--sort-candidates.el +++ b/tests/test-ai-vterm--sort-candidates.el @@ -2,7 +2,7 @@ ;;; Commentary: ;; The project picker lists candidates with a live tmux session first -;; (so a Claude that survived an Emacs crash is easy to get back to), +;; (so an agent that survived an Emacs crash is easy to get back to), ;; then everything else. Within each group the order is alphabetical ;; by abbreviated path. diff --git a/tests/test-ai-vterm--tmux-session-name.el b/tests/test-ai-vterm--tmux-session-name.el index 44c20a8b..8d9220eb 100644 --- a/tests/test-ai-vterm--tmux-session-name.el +++ b/tests/test-ai-vterm--tmux-session-name.el @@ -2,7 +2,7 @@ ;;; Commentary: ;; The tmux session name is `cj/ai-vterm-tmux-session-prefix' followed by -;; the project's basename, so reopening Claude on the same project (e.g. +;; the project's basename, so reopening the agent on the same project (e.g. ;; after an Emacs crash) reattaches to the same tmux session rather than ;; spawning a new one -- and the prefix lets `tmux ls' output be filtered ;; down to AI-vterm's own sessions. Whitespace in the basename becomes diff --git a/tests/test-vterm-tmux-history.el b/tests/test-vterm-tmux-history.el index db82176f..901d96c9 100644 --- a/tests/test-vterm-tmux-history.el +++ b/tests/test-vterm-tmux-history.el @@ -164,13 +164,13 @@ modern keyboards and was redundant." "Normal: an AI-vterm-named buffer still resolves by process TTY. The copy path belongs to `vterm-mode', not to `*vterm*'-named buffers. -A buffer named like `claude [repo]' (ai-vterm.el's naming) is a +A buffer named like `agent [repo]' (ai-vterm.el's naming) is a `vterm-mode' buffer and must inherit tmux history copy. The pane lookup keys off the live process TTY, never the buffer name -- so the AI-vterm name neither helps nor blocks resolution." - (let ((claude (cj/test--make-fake-vterm-buffer "claude [emacs.d]"))) + (let ((agent (cj/test--make-fake-vterm-buffer "agent [emacs.d]"))) (unwind-protect - (with-current-buffer claude + (with-current-buffer agent (cl-letf (((symbol-function 'get-buffer-process) (lambda (_buffer) 'fake-process)) ((symbol-function 'process-tty-name) @@ -179,8 +179,8 @@ AI-vterm name neither helps nor blocks resolution." '((("list-clients" "-F" "#{client_tty}\t#{pane_id}") 0 "/dev/pts/1\t%1\n/dev/pts/8\t%8\n")) (should (equal (cj/vterm--current-tmux-pane-id) "%8"))))) - (when (buffer-live-p claude) - (kill-buffer claude))))) + (when (buffer-live-p agent) + (kill-buffer agent))))) (provide 'test-vterm-tmux-history) ;;; test-vterm-tmux-history.el ends here diff --git a/tests/test-vterm-toggle--buffer-filter.el b/tests/test-vterm-toggle--buffer-filter.el index 82afe756..d6fd2c8c 100644 --- a/tests/test-vterm-toggle--buffer-filter.el +++ b/tests/test-vterm-toggle--buffer-filter.el @@ -4,8 +4,8 @@ ;; Three closely-related helpers determine which vterm buffers F12 ;; manages: the predicate `cj/--vterm-toggle-buffer-p', the MRU list ;; `cj/--vterm-toggle-buffers', and the per-frame window finder -;; `cj/--vterm-toggle-displayed-window'. All three exclude claude- -;; prefixed buffers so claude has its own F9 surface. +;; `cj/--vterm-toggle-displayed-window'. All three exclude agent- +;; prefixed buffers so agent has its own F9 surface. ;;; Code: @@ -17,22 +17,22 @@ (require 'testutil-vterm-buffers) (defun test-vterm-toggle--cleanup () - "Kill leftover claude- and *test-vterm- prefixed buffers." - (cj/test--kill-claude-buffers) + "Kill leftover agent- and *test-vterm- prefixed buffers." + (cj/test--kill-agent-buffers) (cj/test--kill-test-vterm-buffers)) (ert-deftest test-vterm-toggle--buffer-p-accepts-vterm-mode () - "Normal: a vterm-mode buffer with non-claude name qualifies." + "Normal: a vterm-mode buffer with non-agent name qualifies." (test-vterm-toggle--cleanup) (let ((buf (cj/test--make-fake-vterm-buffer "*test-vterm-1*"))) (unwind-protect (should (cj/--vterm-toggle-buffer-p buf)) (kill-buffer buf)))) -(ert-deftest test-vterm-toggle--buffer-p-rejects-claude () - "Boundary: claude-prefixed vterm buffers are excluded from F12's set." +(ert-deftest test-vterm-toggle--buffer-p-rejects-agent () + "Boundary: agent-prefixed vterm buffers are excluded from F12's set." (test-vterm-toggle--cleanup) - (let ((buf (cj/test--make-fake-vterm-buffer "claude [project-a]"))) + (let ((buf (cj/test--make-fake-vterm-buffer "agent [project-a]"))) (unwind-protect (should-not (cj/--vterm-toggle-buffer-p buf)) (kill-buffer buf)))) @@ -52,17 +52,17 @@ (kill-buffer buf) (should-not (cj/--vterm-toggle-buffer-p buf)))) -(ert-deftest test-vterm-toggle--buffers-filters-claude () - "Normal: returns vterm buffers but excludes claude-prefixed ones." +(ert-deftest test-vterm-toggle--buffers-filters-agent () + "Normal: returns vterm buffers but excludes agent-prefixed ones." (test-vterm-toggle--cleanup) (let ((normal (cj/test--make-fake-vterm-buffer "*test-vterm-normal*")) - (claude (cj/test--make-fake-vterm-buffer "claude [for-test]"))) + (agent (cj/test--make-fake-vterm-buffer "agent [for-test]"))) (unwind-protect (let ((result (cj/--vterm-toggle-buffers))) (should (memq normal result)) - (should-not (memq claude result))) + (should-not (memq agent result))) (kill-buffer normal) - (kill-buffer claude)))) + (kill-buffer agent)))) (ert-deftest test-vterm-toggle--displayed-window-finds-vterm () "Normal: vterm in a window -> returns that window." @@ -78,17 +78,17 @@ (should (eq (window-buffer result) vt))))) (kill-buffer vt)))) -(ert-deftest test-vterm-toggle--displayed-window-skips-claude () - "Boundary: only a claude vterm is displayed -> nil (claude not F12-managed)." +(ert-deftest test-vterm-toggle--displayed-window-skips-agent () + "Boundary: only an agent vterm is displayed -> nil (agent not F12-managed)." (test-vterm-toggle--cleanup) - (let ((claude (cj/test--make-fake-vterm-buffer "claude [skip-test]"))) + (let ((agent (cj/test--make-fake-vterm-buffer "agent [skip-test]"))) (unwind-protect (save-window-excursion (delete-other-windows) (let ((win (split-window-right))) - (set-window-buffer win claude) + (set-window-buffer win agent) (should-not (cj/--vterm-toggle-displayed-window)))) - (kill-buffer claude)))) + (kill-buffer agent)))) (provide 'test-vterm-toggle--buffer-filter) ;;; test-vterm-toggle--buffer-filter.el ends here diff --git a/tests/testutil-vterm-buffers.el b/tests/testutil-vterm-buffers.el index 864dd8f3..01a65d90 100644 --- a/tests/testutil-vterm-buffers.el +++ b/tests/testutil-vterm-buffers.el @@ -1,4 +1,4 @@ -;;; testutil-vterm-buffers.el --- Shared helpers for vterm/claude buffer tests -*- lexical-binding: t; -*- +;;; testutil-vterm-buffers.el --- Shared helpers for vterm/agent buffer tests -*- lexical-binding: t; -*- ;;; Commentary: ;; Cleanup helpers and a fake-vterm constructor used across the @@ -15,9 +15,9 @@ (when (string-prefix-p prefix (buffer-name b)) (kill-buffer b)))) -(defun cj/test--kill-claude-buffers () - "Kill all live buffers whose name matches the AI-vterm prefix \"claude [\"." - (cj/test--kill-buffers-matching-prefix "claude [")) +(defun cj/test--kill-agent-buffers () + "Kill all live buffers whose name matches the AI-vterm prefix \"agent [\"." + (cj/test--kill-buffers-matching-prefix "agent [")) (defun cj/test--kill-test-vterm-buffers () "Kill all live buffers whose name starts with \"*test-vterm\"." -- cgit v1.2.3