From 9325ca00173a03f282e74b6a86c4083fa88977d5 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sat, 16 May 2026 02:15:32 -0500 Subject: 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. --- todo.org | 714 +++++++++++++++++++++++++++++++-------------------------------- 1 file changed, 356 insertions(+), 358 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,379 +2560,118 @@ 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] +** TODO [#C] Extend F2 "preview" convention across modes :feature: -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. +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: -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). +- Hugo blog (hugo-config.el) — preview the post in browser +- HTML / web-mode — open in browser +- Any other mode with a natural "preview this" action -Out of scope: the F9 =ai-vterm= Claude-Code launcher (=modules/ai-vterm.el=) — separate module, working well. +Keep the binding mode-local so F2 stays available as a global candidate where no preview makes sense. -Closing event log: +** TODO [#C] Build localrepo and document limitations :feature: -- 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. +Repeatable installs and safe rollbacks. -*** 2026-05-16 Sat @ 01:17:58 -0500 Rewrote update_text_file.el and wired it into cj/gptel-local-tool-features +.localrepo only contains packages from package.el archives. +Treesitter grammars are downloaded separately by treesit-auto on first use. +For true offline reproducibility, need to cache treesitter grammars separately. -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. +** TODO [#C] Investigate TRAMP/dirvish showing question marks for file dates :bug: -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. +Remote directories in dirvish show "?" instead of actual modification dates. +Tried several approaches without success - needs deeper investigation. -=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. +**Attempted fixes (all reverted):** +1. Connection-local dired-listing-switches with -alh (didn't help) +2. Disabling tramp-direct-async-process (reported to cause this, but disabling didn't fix it) +3. Hook to set different listing switches for remote vs local (didn't help) -*** 2026-05-16 Sat @ 01:31:03 -0500 Split the magit wiring into per-feature with-eval-after-load blocks +**Possible causes to investigate:** +- dirvish may be using its own attribute fetching that bypasses dired-listing-switches +- May need dirvish-specific configuration for remote file attributes +- Could be an Emacs 29/30 + TRAMP + dirvish interaction issue +- May require changes to how dirvish renders the file-size attribute on remote -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. +**Files involved:** +- modules/tramp-config.el +- modules/dirvish-config.el -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. +** TODO [#C] Finish terminal GPG pinentry configuration :feature: -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. +Continue work on terminal-mode GPG passphrase prompts (loopback mode). +Branch: terminal-pinentry -*** 2026-05-16 Sat @ 01:33:20 -0500 Added ERT coverage for ai-conversations.el +Changes in progress (modules/auth-config.el): +- Use epa-pinentry-mode 'loopback in terminal +- Use external pinentry (pinentry-dmenu) in GUI +- Requires env-terminal-p from host-environment module -=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. +** TODO [#D] Polish reveal.js presentation setup :feature: -*** 2026-05-16 Sat @ 01:39:11 -0500 Added ERT coverage for the gptel-tools .el files +Three small reveal.js improvements; collected into one task because each on its own is too small to track separately. -Five new test files cover the five remaining gptel tools beyond -=update_text_file= (which was tested with its rewrite): +1. *Image insertion helper.* Function to insert images with proper org-reveal attributes (sizing, background images, etc.) without having to remember the syntax. +2. *Default font sizing for slide elements.* Configure reveal.js font sizes for headings, body text, code blocks, etc. — better defaults via =org-reveal-head-preamble= CSS or a custom theme. +3. *Custom dupre reveal.js theme.* CSS theme using the colors from =themes/dupre-palette.el=. Install into =reveal.js/css/theme/= for use with =#+REVEAL_THEME: dupre=. -- =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). +** TODO [#D] Evaluate and integrate Buttercup for behavior-driven integration tests :tests: -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=. +Complex workflow testing capability. -52 new tests, all green. +** TODO [#D] Track ELPA upstream byte-compile warnings (esxml, poetry) :chore: -*** 2026-05-16 Sat @ 02:01:48 -0500 Wrote the gptel-tools shortlist design doc +Two ELPA packages emit byte-compile warnings on =make compile= that aren't fixable in this repo: -[[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: +1. =elpa/esxml-20250421.1632/esxml.el= — =Warning: Unknown type: attrs= and =Unknown type: stringp= (a defcustom =:type= spec). +2. =elpa/poetry-20240329.1103/poetry.el= — =Warning: Case 'X will match 'quote'= for four cases (=post-command=, =projectile=, =project=, =switch-buffer=). Quoted symbols inside =pcase= clauses — should be unquoted upstream. -- *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). +No action in this repo. Revisit when packages update. File upstream issues if warnings linger past a few months. -Follow-up work surfaced in the doc: +Discovered 2026-04-26 in =*Messages*= during compile. -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. +** TODO [#D] Add status dashboard for dwim-shell-command processes :feature: -Three open questions called out for review at the bottom of the -doc. +Create a command to show all running dwim-shell-command processes with their status. +Currently, there's no unified view of multiple running extractions/conversions. -*** 2026-05-16 Sat @ 01:54:34 -0500 Added directive-picker wrappers around gptel-rewrite +**Current behavior:** +- Each command shows spinner in minibuffer while running +- Process buffers created: `*Extract audio*`, etc. +- On completion: buffer renamed to `*Extract audio done*` or `*Extract audio error*` +- No way to see all running processes at once -New module =modules/ai-rewrite.el= with two commands: +**Recommended approach:** +Custom status buffer that reads `dwim-shell-command--commands`. +Can add mode-line indicator later as enhancement. +* Emacs Resolved +** DONE [#B] Fix likely =elpa-mirror-location= path bug :bug:quick: +CLOSED: [2026-05-03 Sun] -- =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). +=early-init.el= builds =elpa-mirror-location= with: -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. +#+begin_src emacs-lisp +(concat user-home-dir ".elpa-mirrors/") +#+end_src -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. +That likely expands to =~/..= incorrectly, e.g. =/home/cjennings.elpa-mirrors/= +instead of =/home/cjennings/.elpa-mirrors/=. Use =expand-file-name= instead. -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. +Acceptance criteria: +- Local mirror paths resolve under the home directory as intended. +- Add a small testable helper if this logic moves out of =early-init.el=. -*** 2026-05-16 Sat @ 01:59:44 -0500 Built the saved-conversations browser +Done 2026-05-03: +- Replaced =concat= path construction with =expand-file-name= for + =elpa-mirror-location=, =localrepo-location=, and local mirror archive paths. +- Added =tests/test-early-init-paths.el= to load =early-init.el= with package + side effects stubbed and assert local archive paths. -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). +** DONE [#B] Fix =vc-follow-symlinks= setting in =system-defaults.el= :bug:quick: +CLOSED: [2026-05-03 Sun] -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: " -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: - -- Hugo blog (hugo-config.el) — preview the post in browser -- HTML / web-mode — open in browser -- Any other mode with a natural "preview this" action - -Keep the binding mode-local so F2 stays available as a global candidate where no preview makes sense. - -** TODO [#C] Build localrepo and document limitations :feature: - -Repeatable installs and safe rollbacks. - -.localrepo only contains packages from package.el archives. -Treesitter grammars are downloaded separately by treesit-auto on first use. -For true offline reproducibility, need to cache treesitter grammars separately. - -** TODO [#C] Investigate TRAMP/dirvish showing question marks for file dates :bug: - -Remote directories in dirvish show "?" instead of actual modification dates. -Tried several approaches without success - needs deeper investigation. - -**Attempted fixes (all reverted):** -1. Connection-local dired-listing-switches with -alh (didn't help) -2. Disabling tramp-direct-async-process (reported to cause this, but disabling didn't fix it) -3. Hook to set different listing switches for remote vs local (didn't help) - -**Possible causes to investigate:** -- dirvish may be using its own attribute fetching that bypasses dired-listing-switches -- May need dirvish-specific configuration for remote file attributes -- Could be an Emacs 29/30 + TRAMP + dirvish interaction issue -- May require changes to how dirvish renders the file-size attribute on remote - -**Files involved:** -- modules/tramp-config.el -- modules/dirvish-config.el - -** TODO [#C] Finish terminal GPG pinentry configuration :feature: - -Continue work on terminal-mode GPG passphrase prompts (loopback mode). -Branch: terminal-pinentry - -Changes in progress (modules/auth-config.el): -- Use epa-pinentry-mode 'loopback in terminal -- Use external pinentry (pinentry-dmenu) in GUI -- Requires env-terminal-p from host-environment module - -** TODO [#D] Polish reveal.js presentation setup :feature: - -Three small reveal.js improvements; collected into one task because each on its own is too small to track separately. - -1. *Image insertion helper.* Function to insert images with proper org-reveal attributes (sizing, background images, etc.) without having to remember the syntax. -2. *Default font sizing for slide elements.* Configure reveal.js font sizes for headings, body text, code blocks, etc. — better defaults via =org-reveal-head-preamble= CSS or a custom theme. -3. *Custom dupre reveal.js theme.* CSS theme using the colors from =themes/dupre-palette.el=. Install into =reveal.js/css/theme/= for use with =#+REVEAL_THEME: dupre=. - -** TODO [#D] Evaluate and integrate Buttercup for behavior-driven integration tests :tests: - -Complex workflow testing capability. - -** TODO [#D] Track ELPA upstream byte-compile warnings (esxml, poetry) :chore: - -Two ELPA packages emit byte-compile warnings on =make compile= that aren't fixable in this repo: - -1. =elpa/esxml-20250421.1632/esxml.el= — =Warning: Unknown type: attrs= and =Unknown type: stringp= (a defcustom =:type= spec). -2. =elpa/poetry-20240329.1103/poetry.el= — =Warning: Case 'X will match 'quote'= for four cases (=post-command=, =projectile=, =project=, =switch-buffer=). Quoted symbols inside =pcase= clauses — should be unquoted upstream. - -No action in this repo. Revisit when packages update. File upstream issues if warnings linger past a few months. - -Discovered 2026-04-26 in =*Messages*= during compile. - -** TODO [#D] Add status dashboard for dwim-shell-command processes :feature: - -Create a command to show all running dwim-shell-command processes with their status. -Currently, there's no unified view of multiple running extractions/conversions. - -**Current behavior:** -- Each command shows spinner in minibuffer while running -- Process buffers created: `*Extract audio*`, etc. -- On completion: buffer renamed to `*Extract audio done*` or `*Extract audio error*` -- No way to see all running processes at once - -**Recommended approach:** -Custom status buffer that reads `dwim-shell-command--commands`. -Can add mode-line indicator later as enhancement. -* Emacs Resolved -** DONE [#B] Fix likely =elpa-mirror-location= path bug :bug:quick: -CLOSED: [2026-05-03 Sun] - -=early-init.el= builds =elpa-mirror-location= with: - -#+begin_src emacs-lisp -(concat user-home-dir ".elpa-mirrors/") -#+end_src - -That likely expands to =~/..= incorrectly, e.g. =/home/cjennings.elpa-mirrors/= -instead of =/home/cjennings/.elpa-mirrors/=. Use =expand-file-name= instead. - -Acceptance criteria: -- Local mirror paths resolve under the home directory as intended. -- Add a small testable helper if this logic moves out of =early-init.el=. - -Done 2026-05-03: -- Replaced =concat= path construction with =expand-file-name= for - =elpa-mirror-location=, =localrepo-location=, and local mirror archive paths. -- Added =tests/test-early-init-paths.el= to load =early-init.el= with package - side effects stubbed and assert local archive paths. - -** DONE [#B] Fix =vc-follow-symlinks= setting in =system-defaults.el= :bug:quick: -CLOSED: [2026-05-03 Sun] - -=modules/system-defaults.el= has: +=modules/system-defaults.el= has: #+begin_src emacs-lisp (setq-default vc-follow-symlinks) @@ -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: " +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. -- cgit v1.2.3