aboutsummaryrefslogtreecommitdiff
path: root/todo.org
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-16 02:15:32 -0500
committerCraig Jennings <c@cjennings.net>2026-05-16 02:15:32 -0500
commit9325ca00173a03f282e74b6a86c4083fa88977d5 (patch)
treecc17e9a2a9d34c9efea29c9eb16221647de5ab6c /todo.org
parentadca2736e2e3ef1eef1aaf62542aa2189a560463 (diff)
downloaddotemacs-9325ca00173a03f282e74b6a86c4083fa88977d5.tar.gz
dotemacs-9325ca00173a03f282e74b6a86c4083fa88977d5.zip
chore(todo): archive completed work to Resolved
Move the closed Gptel Work PROJECT and the flycheck modeline task from Open Work into Resolved. Both shipped this round.
Diffstat (limited to 'todo.org')
-rw-r--r--todo.org560
1 files changed, 279 insertions, 281 deletions
diff --git a/todo.org b/todo.org
index e69c45c3..5bee821c 100644
--- a/todo.org
+++ b/todo.org
@@ -2548,26 +2548,6 @@ Test the new REST API client integration in a running Emacs session.
- C-; R s should replace :skyfi-key = PLACEHOLDER with real key
- Key should NOT be written to disk (verify file still shows PLACEHOLDER)
-** DONE [#C] Implement flycheck modeline customization :feature:
-CLOSED: [2026-05-16 Sat]
-
-Spec: [[file:docs/design/flycheck-modeline-customization.org][docs/design/flycheck-modeline-customization.org]] (Option 4 / hybrid).
-
-=modules/flycheck-config.el= got two new =:custom= lines:
-=flycheck-mode-line-prefix= → "🐛", =flycheck-mode-success-indicator= →
-" ✓". =flycheck-mode-line-color= stays default-t so counts pick up
-=error= / =warning= faces automatically.
-
-=modules/modeline-config.el= got one new =(:eval ...)= form in
-=mode-line-format=, placed between the recording indicator and
-=cj/modeline-vc-branch=. Two guards: =(mode-line-window-selected-p)=
-gates to the active window; =(bound-and-true-p flycheck-mode)= prevents
-the call from firing in buffers where flycheck hasn't loaded.
-
-=tests/test-modeline-config-flycheck-segment.el= -- 3 smoke tests
-asserting the segment is present and both guards are in place. The
-existing modeline tests stay green.
-
** TODO [#C] Migrate from Company to Corfu (with prescient integration) :feature:
Spec: [[file:docs/design/company-to-corfu-migration.org][docs/design/company-to-corfu-migration.org]]
@@ -2580,267 +2560,6 @@ used to handle. Per-module fixups for ledger, AUCTeX, eshell, mu4e
compose, and the three =prog-*= modules. See the design doc for the
full translation table, migration steps, tests, and risks.
-** DONE [#B] Gptel Work :refactor:cleanup:feature:
-CLOSED: [2026-05-16 Sat]
-
-Keep gptel as a focused side-tool for one-off conversations, impromptu help, and the rewrite-region code helper. Workflow stays distinct from the dedicated Claude-Code agents launched via F9, so per-project agent sessions don't get cluttered with general-purpose chat.
-
-In scope:
-- The =cj/ai-keymap= (=C-; a=) commands in =modules/ai-config.el=.
-- The save/load/delete + autosave flow in =modules/ai-conversations.el=.
-- The local-tools surface in =gptel-tools/= and the loader =cj/gptel-load-local-tools=.
-- gptel-magit's three triggers (M-g in git-commit, =g= in magit-commit transient, =x= in magit-diff transient).
-
-Out of scope: the F9 =ai-vterm= Claude-Code launcher (=modules/ai-vterm.el=) — separate module, working well.
-
-Closing event log:
-
-- Rewrote =gptel-tools/update_text_file.el= in pure Elisp + wired into =cj/gptel-local-tool-features=; 48 ERT tests.
-- Split gptel-magit wiring into per-feature =with-eval-after-load= blocks (=git-commit=, =magit-commit=, =magit-diff=); rewrote the lazy-loading test to inspect =after-load-alist= directly.
-- Added 36 ERT tests for =ai-conversations.el= (helpers, autosave hook, interactive save/delete).
-- Added 52 ERT tests for the other five gptel-tools files; small refactor on =read_buffer.el= and =write_text_file.el= to extract testable helpers.
-- =cj/gptel-autosave-toggle= + =[AS]= mode-line indicator, bound to =C-; a A=.
-- =cj/gptel-quick-ask= one-shot Q&A buffer with =q= / =escape= / =c= bindings (new module =ai-quick-ask.el=), bound to =C-; a q=.
-- Directive-picker wrappers around =gptel-rewrite= (=ai-rewrite.el=); =C-; a r= picks directive + rewrites, =C-; a R= redoes with a different directive.
-- Dired-style saved-conversations browser (=ai-conversations-browser.el=) with RET/l/d/r/g/q bindings, bound to =C-; a b=.
-- Shortlist design doc at =docs/design/gptel-tools-shortlist.org= for additional gptel tools (7 ADOPT, 2 DEFER, 1 SKIP); live community-tool survey remains as follow-up work for Craig.
-
-*** 2026-05-16 Sat @ 01:17:58 -0500 Rewrote update_text_file.el and wired it into cj/gptel-local-tool-features
-
-I rewrote =gptel-tools/update_text_file.el= in pure Elisp. The previous
-version shelled out to sed for everything, had a stray quote terminator
-at EOF, produced literal backslash-n where actual newlines were
-expected, and prompted via =y-or-n-p= redundantly with gptel's own
-=:confirm t= flag.
-
-The five operations (=replace=, =append=, =prepend=, =insert-at-line=,
-=delete-lines=) split into pure string transforms that test without
-touching the disk. The file-level wrapper validates the path, enforces
-the 10MB size limit, takes a timestamped backup, and writes atomically.
-No backup is created when the operation is a no-op.
-
-=tests/test-update-text-file.el= covers Normal / Boundary / Error per
-operation plus the wrapper -- 48 tests green. Added =update_text_file=
-to =cj/gptel-local-tool-features= so gptel exposes it on next restart.
-
-*** 2026-05-16 Sat @ 01:31:03 -0500 Split the magit wiring into per-feature with-eval-after-load blocks
-
-Root cause: =magit.el= calls =(provide 'magit)= BEFORE its
-=(cl-eval-when (load eval) ...)= block requires =magit-commit= and
-=magit-stash=. A single =with-eval-after-load 'magit= fires while
-those transient prefixes are still undefined, and
-=transient-append-suffix= silently no-ops on missing prefixes
-(documented behavior unless =transient-error-on-insert-failure= is
-set). Two of three triggers failed silently because of this; only
-M-g worked, because =git-commit= IS required before the provide.
-
-Fix: replace the single =with-eval-after-load 'magit= with three
-per-feature blocks (=git-commit=, =magit-commit=, =magit-diff=). Each
-hooks the exact dependency the wiring needs.
-
-The existing lazy-loading test was rewritten to check
-=after-load-alist= registration directly rather than driving the
-hooks via =provide= -- in Emacs 30 batch mode, =provide= does not
-fire registered =eval-after-load= callbacks; only an actual =load=
-does. Inspecting the registration is stronger evidence anyway: the
-guard against the regression is "no entry for =magit=, entries for
-=git-commit=, =magit-commit=, =magit-diff=," which is exactly what
-the test asserts.
-
-*** 2026-05-16 Sat @ 01:33:20 -0500 Added ERT coverage for ai-conversations.el
-
-=tests/test-ai-conversations.el= covers every helper in the module
-plus the interactive entry points. 36 tests across Normal / Boundary /
-Error categories: slug normalization, timestamp decoding, file
-enumeration (existing topics, latest-for-topic, candidate ordering for
-both =newest-first= and =oldest-first=), the save-buffer/strip-headers
-round-trip, the autosave-after-send + autosave-after-response hooks,
-the install-once guard for the post-response hook, and the
-save/delete interactive entry points exercised via =cl-letf= stubs.
-Per-test temp directories; no writes outside them.
-
-*** 2026-05-16 Sat @ 01:39:11 -0500 Added ERT coverage for the gptel-tools .el files
-
-Five new test files cover the five remaining gptel tools beyond
-=update_text_file= (which was tested with its rewrite):
-
-- =tests/test-gptel-tools-read-buffer.el= -- 5 tests for the new
- =cj/read-buffer--get-content= helper extracted from the
- =gptel-make-tool= lambda.
-- =tests/test-gptel-tools-write-text-file.el= -- 10 tests for the
- helpers extracted from =write_text_file.el= (validate-path,
- backup-name, ensure-parent, run with normal/overwrite/error
- paths).
-- =tests/test-gptel-tools-read-text-file.el= -- 12 tests for the
- pre-existing helpers: =cj/validate-file-path=,
- =cj/get-file-metadata=, =cj/check-file-size-limits=,
- =cj/detect-binary-file=, =cj/handle-special-file-types=.
-- =tests/test-gptel-tools-list-directory-files.el= -- 15 tests for
- the =list-directory-files--*= helpers (mode-to-permissions for
- files/dirs/executables, get-file-info, extension filter, formatter,
- recursive vs flat listing, error path).
-- =tests/test-gptel-tools-move-to-trash.el= -- 10 tests for the
- =gptel--move-to-trash-*= helpers (unique-name generation with and
- without extension, path validation gating HOME and /tmp, critical
- directory rejection, perform on files and directories).
-
-Two small refactors landed first to make the tooling testable:
-=read_buffer.el= and =write_text_file.el= had their main bodies
-inlined into the =gptel-make-tool= lambdas; I extracted them into
-=cj/read-buffer--get-content= and =cj/write-text-file--run= (plus
-=--validate-path=, =--backup-name=, =--ensure-parent=) following the
-Internal/Wrapper split documented in =elisp-testing.md=.
-
-52 new tests, all green.
-
-*** 2026-05-16 Sat @ 02:01:48 -0500 Wrote the gptel-tools shortlist design doc
-
-[[file:../docs/design/gptel-tools-shortlist.org][docs/design/gptel-tools-shortlist.org]] covers each of the candidates
-called out in the task body plus a few obvious adjacents. Decisions:
-
-- *ADOPT* (7): =search_in_files=, =git_status= / =git_log= /
- =git_diff= (three tools), =web_fetch=, =search_emacs_help=,
- =find_file_by_name=, =take_screenshot=. Each gets a sketch in the
- doc (args, validation, implementation outline).
-- *DEFER* (2): =run_shell_command= (huge surface, click-fatigue
- risk; ADOPT-bucket tools cover most legit use cases), =org_capture=
- (needs UX design for template pre-fill and round-trip).
-- *SKIP* (1): =eval_elisp= (code execution from a model is too
- dangerous even with confirm-each-call).
-
-Follow-up work surfaced in the doc:
-
-1. *Live community survey* -- walk the gptel README's tool examples,
- MELPA =gptel-tool-*=, GitHub =gptel-make-tool= search,
- karthink's gptel repo. I couldn't do live web research from
- this session; that pass remains for Craig to do or to delegate.
-2. *Per-tool implementation sub-tasks* -- each ADOPT entry deserves
- its own [#B] under =Gptel Work= when Craig reviews this shortlist.
-3. *Sandboxing convention* -- decide whether =web_fetch= needs an
- allowlist of outbound URLs, and the same call for
- =run_shell_command= if it's promoted from DEFER.
-
-Three open questions called out for review at the bottom of the
-doc.
-
-*** 2026-05-16 Sat @ 01:54:34 -0500 Added directive-picker wrappers around gptel-rewrite
-
-New module =modules/ai-rewrite.el= with two commands:
-
-- =cj/gptel-rewrite-with-directive= (=C-; a r=, replacing the bare
- =gptel-rewrite= binding): completing-read on a directive name from
- =cj/gptel-rewrite-directives=, then rewrite the active region.
-- =cj/gptel-rewrite-redo-with-different-directive= (=C-; a R=): replay
- the prior region with a different directive (markers are saved
- buffer-local so the region survives accept/reject of the first
- rewrite).
-
-Open-question answer: the directive is injected via a one-shot
-=let=-binding on =gptel-rewrite-directives-hook= (an abnormal hook
-that gptel-rewrite already supports for per-call system messages),
-not by mutating =gptel-directives= globally. No advice on
-=gptel-rewrite= and no state to clean up after the call returns.
-
-Directives ship inline as a =defcustom= alist with the six names
-called out in the task body (=terse=, =fix-grammar=,
-=refactor-readability=, =add-docstring=, =explain-as-comment=,
-=shorten=) so customization is straightforward without a separate
-file layer.
-
-9 tests in =tests/test-ai-rewrite.el= cover the defcustom shape,
-the wrapper (normal path, no-region error, unknown-directive
-error, last-state recording), and the redo (replays prior region,
-errors when no previous, excludes the current directive from the
-re-pick prompt). =gptel-rewrite= stubbed for tests so no rewrite
-UI fires.
-
-*** 2026-05-16 Sat @ 01:59:44 -0500 Built the saved-conversations browser
-
-New module =modules/ai-conversations-browser.el= +
-=cj/gptel-browse-conversations= entry point bound to =C-; a b=
-(which-key labelled "browse conversations"). Opens a dired-style
-=*GPTel-Conversations*= buffer in =cj/gptel-browser-mode= (a
-=special-mode= derivative).
-
-Each row shows date, time, topic slug, and a preview of the most
-recent message (configurable length via
-=cj/gptel-browser-preview-length=, default 60 chars). Rows sort
-newest first.
-
-Bindings in the browser:
-- =RET= / =l=: load the conversation (delegates to
- =cj/gptel-load-conversation= with the file pre-selected via a
- =cl-letf= stub on =completing-read= so the user isn't prompted
- twice), then bury the browser window.
-- =d=: delete the file under point after =y-or-n-p= confirmation,
- re-render.
-- =r=: rename the file under point; preserves the timestamp,
- slugifies the new topic, refuses unchanged input and existing
- targets.
-- =g=: refresh.
-- =n= / =p=: next / previous row.
-- =q=: quit-window.
-
-21 tests in =tests/test-ai-conversations-browser.el= cover the
-helpers (topic parsing, header stripping, preview shaping for
-truncate / short / empty cases, row-for-file with both
-conversation and non-conversation filenames, rows enumeration,
-render output for empty and populated cases, newest-first sort,
-rename-target preservation of timestamp + slug, rename-target
-error on missing timestamp) and the file-touching actions (delete
-with y, cancel with n, rename, rename-on-empty-line error).
-
-*** 2026-05-16 Sat @ 01:46:55 -0500 Added cj/gptel-quick-ask one-shot command
-
-New module =modules/ai-quick-ask.el=. Bound to =C-; a q= via
-=cj/ai-keymap= (which-key labelled "quick ask").
-
-=cj/gptel-quick-ask=: read a prompt in the minibuffer, create the
-=*GPTel-Quick*= buffer in =cj/gptel-quick-mode= (a special-mode
-derivative with =q= / =escape= / =c= bindings), insert "Q: <prompt>"
-and the response marker, then call =gptel-request= with =:stream t=
-streaming into the buffer.
-
-=cj/gptel-quick-dismiss= (=q= / =escape=): delete the window and
-kill the buffer. Idempotent when the buffer is absent.
-
-=cj/gptel-quick-continue= (=c=): extract the prompt and response,
-seed them into =*AI-Assistant*= under proper org headings (matching
-=cj/gptel--fresh-org-prefix= shape), display the side window,
-dismiss the quick buffer.
-
-13 tests in =tests/test-ai-quick-ask.el=:
-- Pure helpers: initial-text shape, extract-response (normal /
- multi-line / no-marker / empty), seed-text shape (with and without
- response).
-- =ask=: creates the buffer in the right mode with the prompt
- recorded, calls =gptel-request=, errors on empty prompt.
-- =dismiss=: kills the buffer, no-op when absent.
-- =continue=: seeds =*AI-Assistant*= with both prompt and response,
- dismisses the quick buffer, errors when called outside a quick
- buffer.
-
-=gptel-request= stubbed in tests so no network call happens.
-
-*** 2026-05-16 Sat @ 01:41:51 -0500 Added cj/gptel-autosave-toggle + [AS] mode-line indicator
-
-=cj/gptel-autosave-toggle= flips =cj/gptel-autosave-enabled= in the
-current GPTel buffer. Bound to =C-; a A= via =cj/ai-keymap=
-(which-key labelled "toggle autosave"). When autosave is OFF and no
-filepath is configured, the command prompts to save the conversation
-first so a save target exists. When autosave is ON, the command
-turns it off.
-
-=cj/gptel-autosave-mode-line-format= surfaces " [AS]" in the
-mode-line when autosave is on, blank when off. Installed via a
-=gptel-mode-hook= so every GPTel buffer picks it up. The install
-helper is idempotent.
-
-6 new tests in =tests/test-ai-conversations.el= cover the enable /
-disable paths, the no-filepath prompt path, the
-not-a-gptel-buffer error path, the mode-line format evaluation, and
-the install idempotence.
-
** TODO [#C] Extend F2 "preview" convention across modes :feature:
F2 is the universal preview key. Currently bound in markdown-mode (markdown-preview) and org-mode (org-reveal, moved from F5). Extend to other modes where a "preview" action is natural:
@@ -5547,3 +5266,282 @@ Commit the file
CLOSED: [2026-05-15 Fri]
When running gptel magit during a commit message on this machine, I get the following error consistently:
transient-setup: Cannot open load file: No such file or directory, gptel-magit
+** DONE [#C] Implement flycheck modeline customization :feature:
+CLOSED: [2026-05-16 Sat]
+
+Spec: [[file:docs/design/flycheck-modeline-customization.org][docs/design/flycheck-modeline-customization.org]] (Option 4 / hybrid).
+
+=modules/flycheck-config.el= got two new =:custom= lines:
+=flycheck-mode-line-prefix= → "🐛", =flycheck-mode-success-indicator= →
+" ✓". =flycheck-mode-line-color= stays default-t so counts pick up
+=error= / =warning= faces automatically.
+
+=modules/modeline-config.el= got one new =(:eval ...)= form in
+=mode-line-format=, placed between the recording indicator and
+=cj/modeline-vc-branch=. Two guards: =(mode-line-window-selected-p)=
+gates to the active window; =(bound-and-true-p flycheck-mode)= prevents
+the call from firing in buffers where flycheck hasn't loaded.
+
+=tests/test-modeline-config-flycheck-segment.el= -- 3 smoke tests
+asserting the segment is present and both guards are in place. The
+existing modeline tests stay green.
+** DONE [#B] Gptel Work :refactor:cleanup:feature:
+CLOSED: [2026-05-16 Sat]
+
+Keep gptel as a focused side-tool for one-off conversations, impromptu help, and the rewrite-region code helper. Workflow stays distinct from the dedicated Claude-Code agents launched via F9, so per-project agent sessions don't get cluttered with general-purpose chat.
+
+In scope:
+- The =cj/ai-keymap= (=C-; a=) commands in =modules/ai-config.el=.
+- The save/load/delete + autosave flow in =modules/ai-conversations.el=.
+- The local-tools surface in =gptel-tools/= and the loader =cj/gptel-load-local-tools=.
+- gptel-magit's three triggers (M-g in git-commit, =g= in magit-commit transient, =x= in magit-diff transient).
+
+Out of scope: the F9 =ai-vterm= Claude-Code launcher (=modules/ai-vterm.el=) — separate module, working well.
+
+Closing event log:
+
+- Rewrote =gptel-tools/update_text_file.el= in pure Elisp + wired into =cj/gptel-local-tool-features=; 48 ERT tests.
+- Split gptel-magit wiring into per-feature =with-eval-after-load= blocks (=git-commit=, =magit-commit=, =magit-diff=); rewrote the lazy-loading test to inspect =after-load-alist= directly.
+- Added 36 ERT tests for =ai-conversations.el= (helpers, autosave hook, interactive save/delete).
+- Added 52 ERT tests for the other five gptel-tools files; small refactor on =read_buffer.el= and =write_text_file.el= to extract testable helpers.
+- =cj/gptel-autosave-toggle= + =[AS]= mode-line indicator, bound to =C-; a A=.
+- =cj/gptel-quick-ask= one-shot Q&A buffer with =q= / =escape= / =c= bindings (new module =ai-quick-ask.el=), bound to =C-; a q=.
+- Directive-picker wrappers around =gptel-rewrite= (=ai-rewrite.el=); =C-; a r= picks directive + rewrites, =C-; a R= redoes with a different directive.
+- Dired-style saved-conversations browser (=ai-conversations-browser.el=) with RET/l/d/r/g/q bindings, bound to =C-; a b=.
+- Shortlist design doc at =docs/design/gptel-tools-shortlist.org= for additional gptel tools (7 ADOPT, 2 DEFER, 1 SKIP); live community-tool survey remains as follow-up work for Craig.
+
+*** 2026-05-16 Sat @ 01:17:58 -0500 Rewrote update_text_file.el and wired it into cj/gptel-local-tool-features
+
+I rewrote =gptel-tools/update_text_file.el= in pure Elisp. The previous
+version shelled out to sed for everything, had a stray quote terminator
+at EOF, produced literal backslash-n where actual newlines were
+expected, and prompted via =y-or-n-p= redundantly with gptel's own
+=:confirm t= flag.
+
+The five operations (=replace=, =append=, =prepend=, =insert-at-line=,
+=delete-lines=) split into pure string transforms that test without
+touching the disk. The file-level wrapper validates the path, enforces
+the 10MB size limit, takes a timestamped backup, and writes atomically.
+No backup is created when the operation is a no-op.
+
+=tests/test-update-text-file.el= covers Normal / Boundary / Error per
+operation plus the wrapper -- 48 tests green. Added =update_text_file=
+to =cj/gptel-local-tool-features= so gptel exposes it on next restart.
+
+*** 2026-05-16 Sat @ 01:31:03 -0500 Split the magit wiring into per-feature with-eval-after-load blocks
+
+Root cause: =magit.el= calls =(provide 'magit)= BEFORE its
+=(cl-eval-when (load eval) ...)= block requires =magit-commit= and
+=magit-stash=. A single =with-eval-after-load 'magit= fires while
+those transient prefixes are still undefined, and
+=transient-append-suffix= silently no-ops on missing prefixes
+(documented behavior unless =transient-error-on-insert-failure= is
+set). Two of three triggers failed silently because of this; only
+M-g worked, because =git-commit= IS required before the provide.
+
+Fix: replace the single =with-eval-after-load 'magit= with three
+per-feature blocks (=git-commit=, =magit-commit=, =magit-diff=). Each
+hooks the exact dependency the wiring needs.
+
+The existing lazy-loading test was rewritten to check
+=after-load-alist= registration directly rather than driving the
+hooks via =provide= -- in Emacs 30 batch mode, =provide= does not
+fire registered =eval-after-load= callbacks; only an actual =load=
+does. Inspecting the registration is stronger evidence anyway: the
+guard against the regression is "no entry for =magit=, entries for
+=git-commit=, =magit-commit=, =magit-diff=," which is exactly what
+the test asserts.
+
+*** 2026-05-16 Sat @ 01:33:20 -0500 Added ERT coverage for ai-conversations.el
+
+=tests/test-ai-conversations.el= covers every helper in the module
+plus the interactive entry points. 36 tests across Normal / Boundary /
+Error categories: slug normalization, timestamp decoding, file
+enumeration (existing topics, latest-for-topic, candidate ordering for
+both =newest-first= and =oldest-first=), the save-buffer/strip-headers
+round-trip, the autosave-after-send + autosave-after-response hooks,
+the install-once guard for the post-response hook, and the
+save/delete interactive entry points exercised via =cl-letf= stubs.
+Per-test temp directories; no writes outside them.
+
+*** 2026-05-16 Sat @ 01:39:11 -0500 Added ERT coverage for the gptel-tools .el files
+
+Five new test files cover the five remaining gptel tools beyond
+=update_text_file= (which was tested with its rewrite):
+
+- =tests/test-gptel-tools-read-buffer.el= -- 5 tests for the new
+ =cj/read-buffer--get-content= helper extracted from the
+ =gptel-make-tool= lambda.
+- =tests/test-gptel-tools-write-text-file.el= -- 10 tests for the
+ helpers extracted from =write_text_file.el= (validate-path,
+ backup-name, ensure-parent, run with normal/overwrite/error
+ paths).
+- =tests/test-gptel-tools-read-text-file.el= -- 12 tests for the
+ pre-existing helpers: =cj/validate-file-path=,
+ =cj/get-file-metadata=, =cj/check-file-size-limits=,
+ =cj/detect-binary-file=, =cj/handle-special-file-types=.
+- =tests/test-gptel-tools-list-directory-files.el= -- 15 tests for
+ the =list-directory-files--*= helpers (mode-to-permissions for
+ files/dirs/executables, get-file-info, extension filter, formatter,
+ recursive vs flat listing, error path).
+- =tests/test-gptel-tools-move-to-trash.el= -- 10 tests for the
+ =gptel--move-to-trash-*= helpers (unique-name generation with and
+ without extension, path validation gating HOME and /tmp, critical
+ directory rejection, perform on files and directories).
+
+Two small refactors landed first to make the tooling testable:
+=read_buffer.el= and =write_text_file.el= had their main bodies
+inlined into the =gptel-make-tool= lambdas; I extracted them into
+=cj/read-buffer--get-content= and =cj/write-text-file--run= (plus
+=--validate-path=, =--backup-name=, =--ensure-parent=) following the
+Internal/Wrapper split documented in =elisp-testing.md=.
+
+52 new tests, all green.
+
+*** 2026-05-16 Sat @ 02:01:48 -0500 Wrote the gptel-tools shortlist design doc
+
+[[file:../docs/design/gptel-tools-shortlist.org][docs/design/gptel-tools-shortlist.org]] covers each of the candidates
+called out in the task body plus a few obvious adjacents. Decisions:
+
+- *ADOPT* (7): =search_in_files=, =git_status= / =git_log= /
+ =git_diff= (three tools), =web_fetch=, =search_emacs_help=,
+ =find_file_by_name=, =take_screenshot=. Each gets a sketch in the
+ doc (args, validation, implementation outline).
+- *DEFER* (2): =run_shell_command= (huge surface, click-fatigue
+ risk; ADOPT-bucket tools cover most legit use cases), =org_capture=
+ (needs UX design for template pre-fill and round-trip).
+- *SKIP* (1): =eval_elisp= (code execution from a model is too
+ dangerous even with confirm-each-call).
+
+Follow-up work surfaced in the doc:
+
+1. *Live community survey* -- walk the gptel README's tool examples,
+ MELPA =gptel-tool-*=, GitHub =gptel-make-tool= search,
+ karthink's gptel repo. I couldn't do live web research from
+ this session; that pass remains for Craig to do or to delegate.
+2. *Per-tool implementation sub-tasks* -- each ADOPT entry deserves
+ its own [#B] under =Gptel Work= when Craig reviews this shortlist.
+3. *Sandboxing convention* -- decide whether =web_fetch= needs an
+ allowlist of outbound URLs, and the same call for
+ =run_shell_command= if it's promoted from DEFER.
+
+Three open questions called out for review at the bottom of the
+doc.
+
+*** 2026-05-16 Sat @ 01:54:34 -0500 Added directive-picker wrappers around gptel-rewrite
+
+New module =modules/ai-rewrite.el= with two commands:
+
+- =cj/gptel-rewrite-with-directive= (=C-; a r=, replacing the bare
+ =gptel-rewrite= binding): completing-read on a directive name from
+ =cj/gptel-rewrite-directives=, then rewrite the active region.
+- =cj/gptel-rewrite-redo-with-different-directive= (=C-; a R=): replay
+ the prior region with a different directive (markers are saved
+ buffer-local so the region survives accept/reject of the first
+ rewrite).
+
+Open-question answer: the directive is injected via a one-shot
+=let=-binding on =gptel-rewrite-directives-hook= (an abnormal hook
+that gptel-rewrite already supports for per-call system messages),
+not by mutating =gptel-directives= globally. No advice on
+=gptel-rewrite= and no state to clean up after the call returns.
+
+Directives ship inline as a =defcustom= alist with the six names
+called out in the task body (=terse=, =fix-grammar=,
+=refactor-readability=, =add-docstring=, =explain-as-comment=,
+=shorten=) so customization is straightforward without a separate
+file layer.
+
+9 tests in =tests/test-ai-rewrite.el= cover the defcustom shape,
+the wrapper (normal path, no-region error, unknown-directive
+error, last-state recording), and the redo (replays prior region,
+errors when no previous, excludes the current directive from the
+re-pick prompt). =gptel-rewrite= stubbed for tests so no rewrite
+UI fires.
+
+*** 2026-05-16 Sat @ 01:59:44 -0500 Built the saved-conversations browser
+
+New module =modules/ai-conversations-browser.el= +
+=cj/gptel-browse-conversations= entry point bound to =C-; a b=
+(which-key labelled "browse conversations"). Opens a dired-style
+=*GPTel-Conversations*= buffer in =cj/gptel-browser-mode= (a
+=special-mode= derivative).
+
+Each row shows date, time, topic slug, and a preview of the most
+recent message (configurable length via
+=cj/gptel-browser-preview-length=, default 60 chars). Rows sort
+newest first.
+
+Bindings in the browser:
+- =RET= / =l=: load the conversation (delegates to
+ =cj/gptel-load-conversation= with the file pre-selected via a
+ =cl-letf= stub on =completing-read= so the user isn't prompted
+ twice), then bury the browser window.
+- =d=: delete the file under point after =y-or-n-p= confirmation,
+ re-render.
+- =r=: rename the file under point; preserves the timestamp,
+ slugifies the new topic, refuses unchanged input and existing
+ targets.
+- =g=: refresh.
+- =n= / =p=: next / previous row.
+- =q=: quit-window.
+
+21 tests in =tests/test-ai-conversations-browser.el= cover the
+helpers (topic parsing, header stripping, preview shaping for
+truncate / short / empty cases, row-for-file with both
+conversation and non-conversation filenames, rows enumeration,
+render output for empty and populated cases, newest-first sort,
+rename-target preservation of timestamp + slug, rename-target
+error on missing timestamp) and the file-touching actions (delete
+with y, cancel with n, rename, rename-on-empty-line error).
+
+*** 2026-05-16 Sat @ 01:46:55 -0500 Added cj/gptel-quick-ask one-shot command
+
+New module =modules/ai-quick-ask.el=. Bound to =C-; a q= via
+=cj/ai-keymap= (which-key labelled "quick ask").
+
+=cj/gptel-quick-ask=: read a prompt in the minibuffer, create the
+=*GPTel-Quick*= buffer in =cj/gptel-quick-mode= (a special-mode
+derivative with =q= / =escape= / =c= bindings), insert "Q: <prompt>"
+and the response marker, then call =gptel-request= with =:stream t=
+streaming into the buffer.
+
+=cj/gptel-quick-dismiss= (=q= / =escape=): delete the window and
+kill the buffer. Idempotent when the buffer is absent.
+
+=cj/gptel-quick-continue= (=c=): extract the prompt and response,
+seed them into =*AI-Assistant*= under proper org headings (matching
+=cj/gptel--fresh-org-prefix= shape), display the side window,
+dismiss the quick buffer.
+
+13 tests in =tests/test-ai-quick-ask.el=:
+- Pure helpers: initial-text shape, extract-response (normal /
+ multi-line / no-marker / empty), seed-text shape (with and without
+ response).
+- =ask=: creates the buffer in the right mode with the prompt
+ recorded, calls =gptel-request=, errors on empty prompt.
+- =dismiss=: kills the buffer, no-op when absent.
+- =continue=: seeds =*AI-Assistant*= with both prompt and response,
+ dismisses the quick buffer, errors when called outside a quick
+ buffer.
+
+=gptel-request= stubbed in tests so no network call happens.
+
+*** 2026-05-16 Sat @ 01:41:51 -0500 Added cj/gptel-autosave-toggle + [AS] mode-line indicator
+
+=cj/gptel-autosave-toggle= flips =cj/gptel-autosave-enabled= in the
+current GPTel buffer. Bound to =C-; a A= via =cj/ai-keymap=
+(which-key labelled "toggle autosave"). When autosave is OFF and no
+filepath is configured, the command prompts to save the conversation
+first so a save target exists. When autosave is ON, the command
+turns it off.
+
+=cj/gptel-autosave-mode-line-format= surfaces " [AS]" in the
+mode-line when autosave is on, blank when off. Installed via a
+=gptel-mode-hook= so every GPTel buffer picks it up. The install
+helper is idempotent.
+
+6 new tests in =tests/test-ai-conversations.el= cover the enable /
+disable paths, the no-filepath prompt path, the
+not-a-gptel-buffer error path, the mode-line format evaluation, and
+the install idempotence.