| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
| |
EmacSQL 4.3.1 registers a finalizer per connection that calls
emacsql-close after GC. The sqlite-builtin and sqlite-module backends
clear their handle slot during an explicit close, so the finalizer
later runs emacsql-close on a closed connection and sqlite-close fires:
finalizer failed: (wrong-type-argument sqlitep nil)
Adds an :around method on emacsql-close for both backends that
short-circuits when the handle is already nil. Requires cl-generic
and eieio at the top of the file so the cl-defmethod forms expand.
|
| |
|
|
|
|
|
|
|
|
| |
Captures five durable findings worth carrying forward:
- :config blocks need a full Emacs launch smoke test. nerd-icons (:defer change) and flycheck (eval in :command) both passed unit tests but broke at launch.
- gptel-model must be a symbol. The modeline render calls symbolp and OpenAI's renderer is strict where Anthropic's tolerated strings.
- flycheck-define-checker rejects (eval ...) in the :command executable slot. Wrap the whole macro in eval+backquote to splice a computed path.
- Emacs 30 batch mode: provide doesn't fire eval-after-load callbacks. Only load does, so tests should assert against after-load-alist directly.
- Warn at module load when an external tool path is configured but missing (cj/executable-find-or-warn) instead of letting the first call fail mid-edit.
|
| |
|
|
|
|
|
|
|
|
|
| |
Adds an org-tidy use-package block hooked into org-mode and sets
org-tidy-properties-style to 'inline so each :PROPERTIES: drawer
collapses to a small marker in the heading line. The drawer stays
editable through TAB cycling or via M-x org-tidy-mode toggle.
Also sets org-cycle-hide-drawers to 'all in cj/org-general-settings
so drawers fold whenever their parent heading folds -- the native
companion to org-tidy's overlay-based hiding.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Undercover now instruments gptel-tools/*.el alongside modules/*.el,
so the new git_status / git_log / git_diff / web_fetch tools (and
their successors) report coverage instead of reading as zero.
The matching pre-coverage clean step deletes gptel-tools/*.elc so
stale byte-compiled artifacts don't shadow the .el sources. If
Emacs loads the .elc first, undercover's source instrumentation
never runs.
docs/design/coverage.org gains an Elisp-coverage-producer subsection
documenting the glob, the :merge-report dependence (SimpleCov merges
cross-process reports, LCOV does not), and the missing-artifact
failure mode.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Adds docs/design/gptel-network-tools.org capturing the brainstorm
output for the next gptel-tools batch -- net_diagnose, net_discover,
net_services, network_status, dns_lookup -- with argv shapes,
target-gating guardrails for nmap, and a ~47-test sketch.
Restructures the GPTel Tool Work parent in todo.org with seven themed
categories: Git, Org, messaging, file/buffer, filesystem, media /
reading, and dev workflow. Each carries a body framing the design
choice and stub child themes. Filesystem covers the pandoc /
imagemagick / ffmpeg / ripgrep / fd / file+exiftool / jq+yq surface
plus an eshell escape hatch. Per-theme spec lands in the task body
once written. Implementation tasks join as siblings once the spec
is approved.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
flycheck's `flycheck-define-checker' macro requires the `:command'
executable to be a string literal at macro-expansion time -- it does
`(stringp (car command))' and errors otherwise. The previous
`(eval (expand-file-name ...))' form (commit d84aa437, the
externalization fix) put a `(eval FORM)' wrapper in the executable
position, which flycheck rejected at load:
Error (use-package): recentf/:config: Command executable for
syntax checker languagetool must be a string:
(eval (expand-file-name "scripts/languagetool-flycheck"
user-emacs-directory))
`(eval FORM)' is only valid for SUBSEQUENT command-list elements
(arguments), not the executable.
Wrap the entire `flycheck-define-checker' invocation in `eval' +
backquote so the expanded path is spliced in as a string literal
before the macro sees it. The hardcoded `~/.emacs.d/...' path is
gone for the same reason the original externalization wanted it
gone: survives a non-standard `user-emacs-directory'.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
The default-backend swap to gpt-5.5 (commit 0f029ab5) set
`gptel-model' as the string "gpt-5.5". gptel's modeline-display
code calls `symbolp' on the model value and signals
`wrong-type-argument symbolp "gpt-5.5"' on every render, which
manifested as Emacs freezing in the AI-Assistant buffer ("Querying
ChatGPT..." → error in process sentinel → repeated redisplay errors).
Both default-setting sites now use `'gpt-5.5' (interned symbol).
The Anthropic backend tolerated string model names so the original
"claude-opus-4-7" string worked, which is why this hadn't surfaced
before.
|
| |
|
|
|
|
|
|
| |
Two places set the default backend + model on gptel initialization
-- `cj/ensure-gptel-backends' (the lazy-init fallback) and the
`use-package gptel :config' block (the eager-set after initialization).
Both now pick the ChatGPT backend with `gpt-5.5' instead of Claude
with `claude-opus-4-7'.
|
| |
|
|
|
|
|
|
|
|
|
| |
- gptel-git-tools-magit-backend.org -- spec for reimplementing the
three current git_* tools on top of magit, plus three new tools
(blame, show, branches).
- gptel-agentic-tool-ideas.org -- brainstorm seed for additional
agentic gptel tools.
- agentic-knowledgebase.org -- design sketch for using org-roam as
the agent's durable project memory with org-agenda as the
execution layer.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Fourth ADOPT entry from `docs/design/gptel-tools-shortlist.org'.
Lets gptel pull a URL into the conversation so the model can read
docs / current API shapes / etc. without me copy-pasting.
Shape:
- URL must be `http://' or `https://' (file://, ftp://, javascript:,
scheme-less, etc. are rejected at the validator).
- HTML responses go through `pandoc -f html -t plain' so the model
gets a reading shape that isn't full of markup; falls back to
`w3m -dump -T text/html' if pandoc isn't on PATH; signals
`user-error' if neither is. Pass `raw=t' to skip stripping.
- Output capped at 200KB by default, hard cap 1MB; `max_bytes'
argument lets the caller pick a lower cap. Truncation reported
inline.
- 4xx / 5xx response codes signal `error' with the code -- the
alternative is returning an error page body, which the model
would treat as content.
`:confirm t' on the tool because every call is a real outbound
network request. The tool's description warns that URLs go
wherever the user-agent points, including internal networks if
that's what the URL names.
`tests/test-gptel-tools-web-fetch.el' -- 20 tests across Normal /
Boundary / Error. URL validator covers http / https / non-string
/ empty / non-http schemes. `--effective-max-bytes' covers default
/ low-clamp / hard-cap / passthrough. Truncate helper covers
under-cap / at-cap / over-cap with the marker. HTML stripper runs
against real pandoc / w3m (both installed in dev env, neither
should mangle simple markup). Orchestrator stubs
`cj/gptel-web-fetch--retrieve' via `cl-letf' to cover normal /
raw / 4xx / 5xx / oversize / bad-scheme paths.
Wired into `cj/gptel-local-tool-features' so gptel exposes the
tool on next restart.
Note: `call-process-region' invocation flattened to a single
`with-temp-buffer' with DELETE=t -- the initial draft nested a
second temp buffer and routed output to the inner one, which got
killed before `buffer-string' on the outer ran. Test caught it.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Three read-only git context tools so gptel can see what's changed
without me pasting `git status` / `git log` / `git diff` output into
every chat turn. Builds the first batch from the ADOPT bucket in
`docs/design/gptel-tools-shortlist.org`.
Shape per tool:
- `gptel-tools/git_status.el` — `git status --short --branch` for a
directory inside a git working tree under HOME. Returns the
porcelain output, or a "Clean working tree" marker when only the
branch line is present.
- `gptel-tools/git_log.el` — `git log --oneline -nN` with an optional
`--since` filter. N defaults to 20, capped at 100; nil / non-
integer / out-of-range N falls back to the default.
- `gptel-tools/git_diff.el` — `git diff [REF1 [REF2]] [-- FILE]`.
Output capped at ~500KB so a runaway diff can't blow up context;
truncation is reported inline.
Validation is uniform: path must resolve under HOME, must be a
directory, must be inside a git working tree (verified via
`git rev-parse --is-inside-work-tree`). Color is disabled via
`-c color.ui=false` at the git level (`git status` doesn't accept
`--no-color` directly).
Tests run against real temp git repos created via `process-file`,
not mocked — there's nothing in gptel-tools/git_*.el that's
process-mockable in a meaningful way, and a real `git init` + a
couple of commits is cheaper than building a fake. 31 tests total:
7 for git_status, 11 for git_log, 13 for git_diff.
Wired into `cj/gptel-local-tool-features` so gptel exposes the
three tools on next restart.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Commit d618bb46's defer change broke startup with a `void-function
nerd-icons-faicon' error. `dashboard-config.el' calls
`nerd-icons-faicon' / `nerd-icons-mdicon' / `nerd-icons-devicon' at
load time to build `dashboard-navigator-buttons', so nerd-icons must
be loaded eagerly before dashboard-config requires. The "defer for
batch and headless" intent doesn't hold here -- dashboard loads
unconditionally at startup, so nerd-icons does too either way.
Kept the `with-eval-after-load 'nerd-icons' safety net for the
re-evaluation case (advice still attaches if this module is
re-required after nerd-icons already loaded).
Comment in the file records why deferral isn't workable here so a
future cleanup pass doesn't try the same change again.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- markdown-config.el: two related fixes on `markdown-preview'.
First, the URL was `https://localhost:8080/imp' but simple-httpd
serves plaintext on port 8080 -- the browser hit a TLS handshake
against a non-TLS listener and the preview never rendered. Changed
to `http://' and switched from `browse-url-generic' to plain
`browse-url' so the user's default protocol handler picks the
browser. Second, the function used to start the network listener
as a side effect of opening a preview; that's split into a
separate `cj/markdown-preview-server-start' command and
`markdown-preview' now signals a `user-error' (with the recovery
command in the message) when the server isn't running.
- slack-config.el: wrap the
`which-key-add-keymap-based-replacements' call in
`with-eval-after-load 'which-key'. Matches the pattern other
config modules use and means a slow / missing which-key load
won't block requiring slack-config.
- ai-vterm.el: pass the inner shell-command-string through
`shell-quote-argument' before wrapping in the tmux invocation.
The default value with embedded double quotes was safe under the
prior literal-single-quote wrap, but a user-customized
`cj/ai-vterm-agent-command' containing a single quote silently
broke the shell parse. Two existing tests updated to tolerate
the post-quote escape shape; new regression test asserts a
single-quote-bearing custom command survives.
- eshell-config.el: scope the `TERM=xterm-256color' override to
eshell-spawned processes only via an `eshell-mode' hook that
prepends to a buffer-local `process-environment'. The previous
global `setenv' at config-time changed `TERM' for every
subsequent `start-process' across the Emacs session, so any
subprocess (not just eshell pipelines) inherited
`xterm-256color' regardless of whether the receiver could
interpret the escapes.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- prog-lsp.el: rename `cj/lsp--remove-eldoc-provider' →
`cj/lsp--remove-eldoc-provider-global' and call it once from the
lsp-mode `:config' block instead of attaching it per-buffer via
`lsp-managed-mode-hook'. The previous per-buffer remove with the
buffer-local flag raced lsp-mode's own population of the local
hook; removing the provider from the global default before any LSP
buffer attaches makes the absence stick. Two existing tests
updated to the new contract (remove-from-default + idempotent
re-run).
- prog-webdev.el / prog-python.el: warn at load time when
`prettier' or `pyright' is missing on PATH via
`cj/executable-find-or-warn'. Both modules now `(require
'system-lib)' to expose the helper. Missing dependencies surface
up front instead of mid-edit at first format/LSP attach.
- keyboard-compat.el: document existing idempotence. The hook
install uses a named function so `add-hook' deduplicates, and the
hook body only calls `define-key' (latest binding wins, same
value) -- adding a comment so future readers don't re-question.
- dev-fkeys.el: add a `typescript' clause to
`cj/--f6-test-runner-cmd-for'. F6 now runs `npx --no-install
vitest <path>' when vitest is on PATH, otherwise `npx --no-install
jest <path>'. Updates the matching test from "returns nil" to
cover both code paths; the impl-level test now asserts the routed
command instead of expecting a user-error.
- flycheck-config.el: build the LanguageTool wrapper path with
`(expand-file-name "scripts/languagetool-flycheck"
user-emacs-directory)' instead of a hardcoded `~/.emacs.d/...'.
Survives a non-standard `user-emacs-directory'.
- latex-config.el: replace the hardcoded Zathura viewer with
`cj/--latex-select-pdf-viewer', which walks
`cj/--latex-pdf-viewer-candidates' (zathura → evince → okular →
SumatraPDF → xdg-open) and falls back to "PDF Tools" when nothing
is on PATH. Each entry maps an executable to the matching
TeX-view-program-list name so AUCTeX's defaults handle the
actual viewer invocation.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- org-roam-config.el: extract `cj/--org-roam-should-copy-completed-task-p'
and gate the `org-after-todo-state-change-hook' on it. Skips
fileless buffers (org-capture, indirect, temp Org) where
`buffer-file-name' is nil and the downstream copy used to crash.
Same gcal.org skip preserved. Five existing tests updated to
bind `buffer-file-name' inside `run-hooks' so the positive-case
hook still fires.
- org-webclipper.el: drop the redundant
`org-protocol-protocol-alist' registration inside
`cj/webclipper-ensure-initialized'. The
`with-eval-after-load 'org-protocol' block at the bottom of the
module is the single registration site now; comment in the
initializer explains why. Split the matching test into two:
one for template registration (the initializer's actual job) and
one for protocol registration (which now fires from the
after-load block when `org-protocol' provides).
- org-webclipper.el: validate `:url' and `:title' in
`cj/org-protocol-webclip'. `:url' must be a non-empty string;
`:title' must be a string when provided. Signals `user-error'
with the unexpected value instead of silently setting the
globals to nil and failing downstream in the capture handler.
- mu4e-org-contacts-integration.el: declare `contacts-file' (via
`eval-when-compile (defvar ...)') and `cj/get-all-contact-emails'
(via `declare-function') near the top of the file. Byte-compile
in isolation no longer warns about free variables / unknown
functions; the cross-module dependency is explicit at the top.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- popper-config.el: move `(popper-mode +1)` and `(popper-echo-mode
+1)` from the use-package `:init` block into `:config`.
`:disabled t' on use-package skips `:config' but still runs
`:init', so the previous shape enabled popper-mode on every load,
including batch / test runs, despite the disabled marker.
- modeline-config.el: make `cj/modeline-vc-fetch' fall back when
the internal `vc-git--symbolic-ref' is missing. `require' uses
`nil 'noerror', the call sits inside an `fboundp' guard, and
`ignore-errors' wraps the call itself so an Emacs version that
renames or removes the accessor leaves `branch' at
`vc-working-revision''s output instead of crashing the modeline.
- ui-config.el: guard the cursor-color `post-command-hook' behind
`(display-graphic-p)' both at install time and inside the
function body. Batch / TTY runs short-circuit cleanly with no
per-command overhead. A `server-after-make-frame-hook' catches
the daemon case where the first GUI frame is created after
ui-config loads and installs the hook lazily. Updates
test-ui-config--buffer-cursor-state and
test-ui-cursor-color-integration to stub `display-graphic-p' so
the work body still runs under batch.
- nerd-icons-config.el: drop `:demand t' (`:defer t' now), keeping
the `:config' advice install as the natural lazy-on-load path.
Add a `with-eval-after-load 'nerd-icons' block as a safety net for
the already-loaded case on re-eval; the block uses `advice-member-p'
so the advice never stacks.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- Guard `cj/duplicate-line-or-region' when COMMENT is non-nil but the
current mode has no `comment-start' (e.g. fundamental-mode).
Previously the function silently produced malformed output via
`comment-region'; now it signals a clear `user-error'.
- Factor the `find-file' advice install in external-open.el into
`cj/external-open-install-advice'. Same idempotent shape
(remove-then-add) but the intent is named.
- Add `cj/--validate-decoration-char' in custom-comments.el and
wire it into all six divider / border / box helpers. Rejects
multi-char strings, empty strings, and control characters like
newline/tab that would corrupt subsequent `M-q' flows. Updated
the five nil-decoration ERT tests from `:type 'wrong-type-argument'
(the old crash signal from `string-to-char' on nil) to
`:type 'user-error', since the validator produces a clear
message instead of a deep crash.
- Extract `cj/--require-spell-checker' in flyspell-and-abbrev.el.
Both `cj/flyspell-toggle' and `cj/flyspell-then-abbrev' now call
the shared helper; the checker list lives in
`cj/--spell-checker-executables', so adding nuspell or any other
checker is a one-line edit.
- Preserve trailing newlines in custom-ordering output. Both
`cj/--arrayify' and `cj/--unarrayify' now detect a trailing
newline on the input region and re-append it to the result,
matching the pattern custom-text-enclose.el already uses.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
system-defaults, chrono-tools
Six small fixes the 2026-05-15 module-by-module re-review surfaced:
- Consolidate `user-home-dir` -- canonical defconst stays in
early-init.el (package-archive bootstrap needs it before normal
modules load); user-constants.el switches to a `defvar` with the
identical `(getenv "HOME")` expression so the module still loads /
byte-compiles standalone, but at runtime early-init's defconst
wins.
- Drop the redundant `(autoload 'env-bsd-p ...)` line in
system-defaults.el. The `(eval-when-compile (require
'host-environment))` already exposes the symbol to the byte
compiler, and at runtime host-environment is loaded earlier in
init.el. Added a comment documenting the boundary.
- Convert `cj/debug-modules` and `cj/use-online-repos` from `defvar`
to `defcustom`, with `:type`, `:group 'cj`, and a top-level
`(defgroup cj ...)` so both show up in M-x customize.
- Name the package-archive priorities in early-init.el. Nine new
defconsts replace the magic numbers (200 / 125 / 120 / 115 / 100 /
25 / 20 / 15 / 5) with one constant each, plus a header comment
explaining the local-first ordering and the gnu > nongnu > melpa >
melpa-stable trust ranking within each tier.
- Delete the 19-line commented-out `use-package time` world-clock
block in chrono-tools.el. `time-zones` immediately above is the
active replacement; git history preserves the old config if anyone
needs it.
- Add coverage for `cj/tmr-select-sound-file`. Collapsed the
prefix-arg branch into a delegation to
`cj/tmr-reset-sound-to-default` (single reset source) and
extracted `cj/tmr--available-sound-files` as a pure helper that
tests directly. 9 ERT tests across Normal / Boundary / Error
cover the available-sounds helper, the reset path, the prefix-arg
delegation (no prompt), the normal selection path, and the
empty-dir / missing-dir / cancel boundaries.
|
| |
|
|
|
| |
Move the closed Gptel Work PROJECT and the flycheck modeline task
from Open Work into Resolved. Both shipped this round.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The custom modeline builds `mode-line-format` from explicit segments
and skips `minor-mode-alist`, so flycheck's lighter never appears.
That hid error and warning counts even in buffers where flycheck was
auto-enabling (every emacs-lisp and sh buffer).
The fix is Option 4 from the design doc: customize the flycheck
modeline variables, then add a single guarded `(:eval ...)` form to
`mode-line-format`. Five new lines total, two-file change.
`modules/flycheck-config.el` :custom block gets:
(flycheck-mode-line-prefix "🐛")
(flycheck-mode-success-indicator " ✓")
`flycheck-mode-line-color` stays default-t so error / warning counts
pick up their faces automatically.
`modules/modeline-config.el` `mode-line-format` gets an `(:eval ...)`
between the recording indicator and `cj/modeline-vc-branch`:
(:eval (when (and (mode-line-window-selected-p)
(bound-and-true-p flycheck-mode))
(flycheck-mode-line-status-text)))
The `mode-line-window-selected-p` guard mirrors `cj/modeline-vc-branch`
and `cj/modeline-misc-info` -- segments hide in inactive windows.
The `bound-and-true-p flycheck-mode` guard keeps the form silent in
buffers where flycheck hasn't loaded or isn't enabled, which is
safer than referencing `flycheck-mode` directly.
The `(:eval ...)` is inline rather than a named `defvar-local`, so no
addition to the risky-local-variable list is needed.
`tests/test-modeline-config-flycheck-segment.el` -- 3 smoke tests
asserting the segment is present and both guards are in place. All
existing tests stay green.
Manual verification (per the design doc) is the user's call -- the
emoji prefix and the colored count behavior need a running GUI Emacs
to observe.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The Gptel Work project asked for a survey of published gptel tools
with adopt / skip / defer decisions per candidate. I can't do a
live community-tool survey from this session, so the doc covers
the candidates the task body called out 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 posture, implementation outline.
- DEFER (2): `run_shell_command` (huge surface, click-fatigue
risk; the ADOPT-bucket tools cover most legit use cases),
`org_capture` (needs UX design for template pre-fill and the
round-trip).
- SKIP (1): `eval_elisp` (code execution from a model is too
dangerous even with confirm-each-call).
The doc also lists three follow-ups: the live community survey
that this session couldn't do, per-tool implementation sub-tasks
to be filed under the next iteration of Gptel Work, and a
sandboxing-convention decision for `web_fetch` (allowlist of
outbound URLs vs description-only warning).
Three open questions at the bottom of the doc for review:
build-all-at-once vs paired stages, `fd` as a hard dep vs `find`
fallback, Hyprland-only screenshot vs Wayland-generic via a
portal.
Closes the Gptel Work PROJECT for this iteration -- all 9 in-scope
sub-tasks landed this session.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
conversations
`cj/gptel-load-conversation` prompts via `completing-read`. A
dedicated browser shows what each conversation is about at a
glance and supports single-key load / delete / rename without
having to scroll a minibuffer list.
New module `modules/ai-conversations-browser.el` +
`cj/gptel-browse-conversations` entry point bound to `C-; a b`
("browse conversations"). Opens `*GPTel-Conversations*` in
`cj/gptel-browser-mode` (a `special-mode` derivative).
Each row shows date, time, topic slug, and a preview of the most
recent message (length configurable via
`cj/gptel-browser-preview-length`, default 60 chars). Rows sort
newest first.
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 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 cover the helpers (topic parsing, header stripping,
preview shaping for truncate / short / empty cases, row-for-file
with conversation + non-conversation filenames, rows enumeration,
render output for empty + 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).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`gptel-rewrite` is the killer feature for the keep-gptel decision,
and it now lives behind two commands instead of the bare call:
- `cj/gptel-rewrite-with-directive` (`C-; a r`, replacing the
former 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. The region
is preserved via markers stored buffer-local on the first call so
it survives accept/reject of the prior rewrite.
I picked the hook injection approach over an `:after`-advice +
state-capture pattern. `gptel-rewrite-directives-hook` is an
abnormal hook gptel-rewrite already consults for a per-call
system message. Wrapping the call in a one-shot `let`-binding on
that hook gives the directive exactly the lifetime of the rewrite
and leaves nothing to clean up. Mutating `gptel-directives`
globally would mean either restoring it afterward or living with
the change -- both worse than the hook.
Directives ship inline as a `defcustom` alist with the six names
called out in the task -- `terse`, `fix-grammar`,
`refactor-readability`, `add-docstring`, `explain-as-comment`,
`shorten`. Customization is a `customize-variable` or `setq`
away.
9 tests cover the defcustom shape (default names present, bodies
non-empty strings), the wrapper (normal path, no-region error,
unknown-directive error, last-state recording), and the redo
(replays the prior region, errors when no previous, excludes the
current directive from the re-pick prompt). `gptel-rewrite`
stubbed in tests so no rewrite UI fires.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
New module `modules/ai-quick-ask.el`. Bound to `C-; a q` via
`cj/ai-keymap` ("quick ask").
`cj/gptel-quick-ask` reads a prompt in the minibuffer, creates a
transient `*GPTel-Quick*` buffer in `cj/gptel-quick-mode` (a
special-mode derivative with `q` / `escape` / `c` bindings), inserts
"Q: <prompt>" plus a response marker, then calls `gptel-request`
with `:stream t` so the answer streams into the buffer. Doesn't
touch `*AI-Assistant*`, doesn't autosave.
Two follow-up commands work in 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 + response,
seed them into `*AI-Assistant*` under proper org headings (matching
the `cj/gptel--fresh-org-prefix` shape), display the side window,
then dismiss the quick buffer.
13 tests cover the pure helpers (initial-text shape, response
extraction across normal / multi-line / no-marker / empty inputs,
seed-text shape), the ask path (buffer created in right mode,
prompt recorded, gptel-request called, empty-prompt error), the
dismiss path (kills buffer / no-op when absent), and the continue
path (seeds `*AI-Assistant*`, dismisses quick buffer, errors
outside a quick buffer). `gptel-request` is stubbed in tests so
nothing hits the network.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
indicator
`cj/gptel-autosave-enabled` flipped to t inside the save/load entry
points with no way back off short of editing the variable or
clearing the buffer, and no visible indicator that it was on.
Two pieces:
- `cj/gptel-autosave-toggle` flips the buffer-local state in the
current GPTel buffer. Bound to `C-; a A` via `cj/ai-keymap`
(which-key: "toggle autosave"). When autosave is OFF and no
filepath is configured yet, the command prompts to save the
conversation first so a save target exists; otherwise it just
flips the bit.
- `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 cover enable/disable paths, the no-filepath prompt path,
the not-a-gptel-buffer error path, the mode-line format evaluation,
and the install idempotence.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The gptel-tools files had zero direct coverage outside of
`update_text_file`, which landed with its rewrite earlier this
session. This commit adds 52 tests across the five other tools.
For three of the tools the helpers were already top-level defuns
(`read_text_file`, `list_directory_files`, `move_to_trash`). The
other two had their main bodies inlined into the `gptel-make-tool`
lambda -- I extracted them so the work is testable without mocking
gptel itself:
read_buffer.el -> `cj/read-buffer--get-content`
write_text_file.el -> `cj/write-text-file--run` plus
`--validate-path`, `--backup-name`,
`--ensure-parent`
Test files, by tool:
- read_buffer.el (5 tests): normal, empty, buffer-object,
text-property-stripping, missing buffer.
- write_text_file.el (10 tests): validate-path, backup-name
shape, ensure-parent (creates missing / rejects unwritable), run
with normal / overwrite / existing-no-overwrite / empty content /
outside-home.
- read_text_file.el (12 tests): validate-file-path (normal +
three error shapes), metadata plist shape, size limits (no-op /
hard cap / warning bypass with no-confirm), binary detection
(text vs null-byte), special-type EPUB and generic-binary paths.
- list_directory_files.el (15 tests): mode-to-permissions (file /
dir / executable), get-file-info (file / directory), extension
filter (keep / drop / always-dir / nil-extension), format-file-
entry, list-directory flat / recursive / error, format-output
with and without files.
- move_to_trash.el (10 tests): unique-name (no conflict /
conflict with timestamp / no-extension), validate-path (HOME / /tmp
/ outside / critical-dir / missing), perform on file and
directory.
Each test file uses the same load-path / gptel-stub idiom
(`eval-and-compile` block, gptel stub when the real package isn't
available) so the byte-compile hook is happy.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
ai-conversations.el shipped without direct tests. This file covers
every helper and interactive entry point across Normal / Boundary /
Error.
Helpers: `cj/gptel--slugify-topic` (ASCII, empty input, all-special,
unicode stripped, idempotent, trim, digits); `cj/gptel--timestamp-
from-filename` (normal decode, year-edge boundaries, malformed
inputs returning nil); `cj/gptel--existing-topics` and `cj/gptel--
latest-file-for-topic` (multi-topic / multi-timestamp temp dirs,
empty dir, missing dir, prefix-overlap isolation); `cj/gptel--
conversation-candidates` (newest-first and oldest-first sort order,
display-string shape, error on missing dir); `cj/gptel--save-buffer-
to-file` (visibility headers prepended, round-trip through `cj/
gptel--strip-visibility-headers`).
Autosave: post-response hook saves only when gptel-mode + enabled +
filepath are all set; autosave-after-send swallows write errors via
`message` instead of signaling; the install-once guard prevents
double-registration.
Interactive entry points: save/delete exercised via `cl-letf` stubs
on `completing-read` and `y-or-n-p`.
Per-test temp directories; no writes outside them.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The wiring keyed on `with-eval-after-load 'magit` fires while two
of its three references are still undefined. `magit.el` calls
`(provide 'magit)` BEFORE its `cl-eval-when (load eval)` block
requires `magit-commit` and `magit-stash`. At that moment the
`magit-commit` transient prefix doesn't exist, and
`transient-append-suffix` silently no-ops on missing prefixes
(default `transient-error-on-insert-failure` is nil). The "g
Generate commit" and "x Explain" suffixes never landed. Only the
M-g binding worked, because `git-commit` IS required before
provide.
Three per-feature hooks replace the single `'magit` hook: one each
on `git-commit`, `magit-commit`, and `magit-diff`. Each hooks the
exact dependency the wiring needs, side-stepping the load-order
race entirely.
The companion test was rewritten to check `after-load-alist`
registration rather than drive the hooks through `provide`. Emacs
30 batch mode doesn't fire registered `eval-after-load` callbacks
on `provide` alone -- only an actual `load` does. Inspecting the
registration is the stronger guard anyway: the regression is "a
single `'magit` hook," and the right shape of that check is "no
entry under `magit`, entries under `git-commit`, `magit-commit`,
`magit-diff`."
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
I rewrote `update_text_file.el` in pure Elisp. The previous version
shelled out to sed for everything, had a stray quote terminator at EOF
(line 149) that broke loading, 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
a 10MB size limit, takes a timestamped backup, and writes atomically.
No backup is created when the operation is a no-op. Patterns are
literal substrings (not regex) so the model can't trip over
metacharacter quoting.
`tests/test-update-text-file.el` covers Normal / Boundary / Error per
operation plus the file-level wrapper. 48 tests green. Added
`update_text_file` to `cj/gptel-local-tool-features` so gptel exposes
the tool after restart.
|
| |
|
|
|
| |
Move the two DONE entries (gptel-magit install + gptel org-mode
prompt-buffer tab-width) from Emacs Open Work to Resolved.
|
| | |
|
| |
|
|
|
|
|
|
| |
gptel's `gptel--with-buffer-copy-internal` copies the source buffer's `major-mode` symbol but doesn't run mode hooks. An inherited-org-mode prompt buffer keeps `tab-width` at this config's global default of 4 instead of the 8 that `org-mode-hook` would set. When gptel later parses the prompt buffer with `org-element`, Org's `tab-width=8` guard raises "Tab width in Org files must be 8, not 4."
I was hitting this on every second `gptel-magit-generate-message` from COMMIT_EDITMSG. `vc-config.el` sets `git-commit-major-mode 'org-mode'`, and the diffs contained list-shaped content that `org-element--list-struct` parsed.
The advice forces `tab-width=8` in the prompt buffer when its inherited mode is org-mode. It's a local workaround for an upstream gap. An upstream patch to run `(delay-mode-hooks (funcall major-mode))` in the buffer-copy is the real fix. I'll send it next.
|
| | |
|
| | |
|
| |
|
|
|
|
|
| |
Replace raw autoload calls with a `use-package` declaration so
`use-package-always-ensure` installs gptel-magit on machines that
haven't run `package-install`, fixing the "Cannot open load file"
error on transient setup.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The shape (if (not (abbrev-mode)) (abbrev-mode)) calls abbrev-mode with
no argument -- that's the toggle signature, not a query. When the mode
was already on the function flipped it off then on instead of being a
no-op. Replaced with (unless (bound-and-true-p VAR) (MODE 1)) for both
abbrev-mode and flycheck-mode. 4 ERT tests cover both-off, both-on, and
the two mixed states.
Also ran the module hardening pass across 24 newly-added modules,
renamed the six completed Review sub-tasks to Harden, filed 11 new
findings under their Harden parents, and broke three design specs
(EMMS-free music, dev F-keys, dev-setup-project) into 20
dependency-ordered sub-tasks via parallel subagents. Verified the
sqlite finalizer bug from 2026-04-26 is gone and closed its tracking
entry.
|
| |
|
|
|
|
|
|
| |
The three integration tests in test-video-audio-recording-process-cleanup spawn wf-recorder via cj/ffmpeg-record-video and assert on pgrep counts. They guard with executable-find and XDG_SESSION_TYPE checks, but neither catches the case where the subprocess can run wf-recorder yet lacks Wayland screencast permission. wf-recorder picks a region, retries "Failed to copy frame" 17 times, then exits with code 183 inside a second. The assertion fires against an empty pgrep.
I added test-cleanup--can-capture-frames, which calls cj/ffmpeg-record-video against a temp dir, waits 1s, and checks pgrep. If wf-recorder didn't survive, the three integration tests skip. The result is cached, so the ~2.5s cost is paid once per batch.
I added the same guard to test-integration-video-recording-multiple-start-stop-cycles. Its assertion is (= count initial-count), so it trivially passed in any environment where capture didn't work. Skipping is more honest than passing for the wrong reason.
|
| |
|
|
|
|
| |
`--archive-done` moved two completed level-2 PROJECTs into Resolved: the `<cj` universal yasnippet conversion and the LSP file-watch ignored-directories task — both closed earlier this session.
`lint-org` applied a small batch of mechanical heading-line merges. The remaining judgment items (broken file links + one invalid fuzzy link) went to the lint follow-ups file for later review. `--sync-child-priority` is a no-op now that the two top-level review PROJECTs carry `:no-sync:` and the inheritance fix lives upstream in claude-templates.
|
| |
|
|
|
|
|
|
|
|
|
| |
All four sub-tasks complete, parent advances to DONE + CLOSED:
- Wired yasnippet for universal availability (yas-global-mode + the fundamental-mode extras hook).
- Created the <cj snippet at snippets/fundamental-mode/.
- Removed the org-tempo cj entry.
- Audited existing per-mode snippets — all 28 are correctly mode-scoped, no movers.
Sub-task headings landed as dated event-log entries per todo-format.md's depth-based completion rule. The parent stays task-shaped at level-2 for agenda visibility.
|
| |
|
|
| |
Now that yasnippet handles `<cj` + TAB in every buffer including org-mode, the `cj` entry in `org-structure-template-alist` is redundant. I removed it and left a one-line comment pointing to where the marker now lives.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
New `snippets/fundamental-mode/cj-comment-block` expands `<cj` + TAB to the literal three-line marker block:
#+begin_src cj: comment
<cursor>
#+end_src
It lives in `fundamental-mode/` so yas's parent-chain lookup finds it in every buffer, plus the activation hook from the previous commit explicitly turns on `fundamental-mode` as an extra mode in every buffer — so even modes that don't descend from `fundamental-mode` (like `special-mode`-derived buffers) pick it up.
The marker is what my Python scanner skill picks out across files. Now I can plant it in any buffer, not just org where the old org-tempo entry lived.
|
| |
|
|
|
|
|
|
| |
The yasnippet use-package block switches from `:hook (prog-mode . yas-minor-mode)` to `:demand t` + `(yas-global-mode 1)`. That makes yas-minor-mode active in every buffer, not just prog-mode-derived ones.
I added a small helper, `cj/--yas-activate-fundamental-extras`, attached as `:hook (yas-minor-mode . ...)`. It calls `(yas-activate-extra-mode 'fundamental-mode)` so the snippet table at `snippets/fundamental-mode/` is consulted in every buffer regardless of the buffer's own major mode. That's what makes universal triggers like `<cj` work everywhere.
The new `tests/test-prog-general-yas-activation.el` covers both wiring (yas-global-mode on, fundamental-mode in yas-extra-modes, yas-minor-mode active in org/text buffers) and end-to-end expansion (the marker snippet expands correctly in fundamental, text, org, emacs-lisp, and python-ts modes). 9 tests, all green; full unit suite green with no regressions.
|
| |
|
|
|
|
| |
Today <cj only expands in org-mode via org-structure-template-alist. A Claude skill scans for the literal #+begin_src cj: comment marker across files, so I need to insert the exact same text in any buffer regardless of major mode.
The new task captures four sub-tasks: wire yasnippet to yas-global-mode + activate fundamental-mode as an extra mode in every buffer, create the snippet at snippets/fundamental-mode/, remove the now-redundant org-tempo entry from modules/org-babel-config.el:144, and a smaller follow-up audit of existing per-mode snippets that should probably live in fundamental-mode/.
|
| |
|
|
|
|
|
|
|
|
| |
The Gptel Work heading was a one-line placeholder. I filled it in with nine sub-tasks after deciding to keep gptel for one-off conversations, impromptu help, and the rewrite-region helper (workflow distinct from the F9 ai-vterm agents, so per-project sessions stay uncluttered).
Bumped the parent to PROJECT [#B] and merged the standalone "Investigate gptel-magit not working properly" task in as a sub-task — the gptel-magit work belongs with the rest of the gptel surface.
Four [#B] work items: wire the existing update_text_file tool into cj/gptel-local-tool-features, fix the three gptel-magit triggers, add ERT coverage for ai-conversations.el (zero today), add ERT coverage for the gptel-tools .el files (also zero).
Five [#C] proposals for review: research and shortlist additional gptel tools, promote gptel-rewrite ergonomics with a directive picker, build a saved-conversations browser, add a one-shot quick-ask command, and ship an autosave toggle + mode-line indicator.
|
| |
|
|
| |
The implementation shipped 2026-04-26 in 781b46e (cj/lsp-file-watch-ignored-extras plus the hook in modules/prog-lsp.el, 10/10 tests green). The manual smoke check in the dashboard MVP and the redundant .dir-locals.el cleanup on velox are tracked separately as active reminders, so todo.org doesn't need to carry the VERIFY state.
|
| |
|
|
|
|
|
|
|
|
|
| |
All five children shipped earlier this month but were left tagged TODO/VERIFY. I verified each against the repo, marked them DONE with their actual closure dates, and closed the PROJECT today.
Children closed:
- Write full utility consolidation design spec — 2026-05-04 (b3ef232a)
- Inventory private helpers across modules — 2026-05-10 (502bcf41)
- Extract executable lookup with warning helper — cj/executable-find-or-warn in system-lib.el (c75e36f4)
- Extract argv-based process runner helper — cj/process-output-or-error and cj/git-output-or-error in system-lib.el (57e558ce)
- Extract Org-safe text sanitizers — cj-org-text-lib.el (0f9e3087)
|
| |
|
|
|
|
|
|
| |
I ran the wrap-up hygiene that the previous session skipped.
`--archive-done` moved 7 DONE subtrees out of Open Work into Resolved. `lint-org` applied 8 mechanical heading-line merges. 12 remaining judgment items (mostly broken file links and one malformed timestamp) went to the lint follow-ups file for later.
I tagged the two top-level review projects `:no-sync:` so their subtrees stop cascading priority bumps — `:refactor:no-sync:` on the architecture review project and `:review:no-sync:` on the module-by-module review project. The latter also dropped from `[#A]` to `[#B]`. It was sitting at A only because the cascade was forcing it. B is where the work belongs now that the opt-out works.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
added modules
Second-pass review of the existing six review tracks (foundation, custom
editing, UI / navigation, Org workflow, programming workflow,
integrations) walked each track as if it had not been reviewed before.
Existing child tasks were deliberately excluded from re-reporting; only
concrete, file-cited, actionable findings not already on the parent
project's task list were filed.
Findings filed:
- Foundation (5): duplicate user-home-dir constant; redundant
eval-when-compile alongside autoload in system-defaults; defcustom
conversions for cj/debug-modules and cj/use-online-repos; surface
the custom-file trash-binning; name package-archive priorities.
- Custom editing (6): region-or-buffer scope inconsistency between
text-enclose and ordering helpers; trailing-newline preservation
in custom-ordering; non-file-buffer guard for
cj/duplicate-line-and-comment; idempotent advice setup in
external-open; comment-delimiter validation in custom-comments;
state-machine coverage for cj/title-case-region.
- UI / navigation (9): popper-mode :init activation defeats
:disabled t; vc-git--symbolic-ref fallback in modeline; theme-aware
face in cj/display-available-fonts; TTY-first frame race in font
setup; per-profile cache in mousetrap-mode keymap rebuilds;
symlink-aware VC modeline cache invalidation; C-s binding shadows
isearch; cursor-color hook guard for non-GUI frames; nerd-icons
advice deferral via with-eval-after-load.
- Org workflow (3): de-duplicate org-protocol handler registration
in org-webclipper; validate :url and :title in
cj/org-protocol-webclip; replace global cj/webclip-current-* with
structured per-invocation state.
- Programming (6): load-time executable-find checks for prettier
and pyright; idempotent server-after-make-frame-hook in
keyboard-compat; dev-fkeys F6 typescript runner clause; prog-lsp
eldoc-provider removal scope; externalize flycheck LanguageTool
script path; externalize latex-config Zathura viewer.
- Integrations (2): markdown-preview httpd-start side effect;
externalize hardcoded SSH hostnames in eshell-config.
Plus: a new "Review newly added modules" sibling task lists all
24 modules that were either added after the parent task was written
(post-2026-04) or fell outside the original review-track scope lists.
Each is routed to its target track; module-specific findings are
filed under the relevant track above.
The integrations track re-review subagent derailed mid-run; rather
than re-issue it, the track's existing 16 child tasks plus the
newly-added-modules findings are deemed adequate coverage. Future
re-review passes can revisit if needed.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`cj/org-roam-filter-by-tag' called `org-roam-node-tags' directly.
That accessor is generated by `cl-defstruct' and ships with a
compiler-macro that inlines the call to an `aref' against the
`cl-struct-org-roam-node-tags' tag variable at byte-compile time.
In tests, `cl-letf' on `(symbol-function 'org-roam-node-tags)' sets
the function cell but the byte-compiled call site never consults it
-- it executes the inlined `aref' instead. When org-roam isn't
loaded (legitimate for a tag-filter unit test), the inlined code
fails with `void-variable cl-struct-org-roam-node-tags'.
Wrap the accessor in `cj/--org-roam-node-tags' that calls through
`funcall' with a quoted symbol. Quoted symbols skip the
compiler-macro (which only fires on direct call forms), so the
funcall resolves the function cell at runtime and picks up the
test's `cl-letf' stub. Production behavior is unchanged; tests
no longer need org-roam loaded.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`org-web-tools-pandoc-sleep-time' is a plain float with no
custom-set handler that needs to fire. `setopt' adds the entire
customize-variable validation machinery -- which, lazily, depends on
wid-edit being loaded.
The handler's tests stub `require' so org-web-tools never really
loads, then mock `setopt' via `cl-letf' on the function cell. That
mock has no effect on byte-compiled code because `setopt' is a
macro: the production handler has already expanded to a call into
`setopt--set'. When `setopt--set' runs, it walks into the customize
machinery and hits an unbound `widget-field-keymap' (wid-edit not
loaded), and the test fails with a confusing wrong-type-argument.
`setq' has identical runtime effect for this variable and dodges
the customize machinery entirely. Tests now pass without contorted
mocking.
|