| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Two new design docs in docs/design/ covering the next two GPTel
work items, plus matching task scaffolding in todo.org.
mcp-el-gptel-integration.org wires mcp.el into the config so GPTel
gets access to the nine MCP servers Claude Code already uses
(linear, notion, figma, slack-deepsat, drawio, google-calendar,
google-docs-personal, google-docs-work, google-keep). The design
covers async startup, the write-confirmation policy, a
server-enablement defcustom, a doctor with live-auth-check, the
audit buffer, and the mcp.el compatibility layer. The spec is at
revision 3 after two code-review passes flagged a critical
confirmation gap (gptel-confirm-tool-calls nil at ai-config.el:386
silently ignored per-tool :confirm slots) and several incorrect
mcp.el API assumptions. Both are addressed.
gptel-gh-tool.org wraps the gh CLI as a hybrid surface: 14 typed
read wrappers plus one general write tool gated by :confirm t.
Host/repo resolution is command-aware: --repo HOST/OWNER/REPO for
repo commands, --hostname only for api and auth status. The runner
enforces an irreversible-command blocklist, a 64KB in-flight output
cap, and a debug-record plus last-error-buffer story. The spec is
at revision 2 after a code-review pass corrected gh flag
assumptions and reframed the safety story around per-tool confirm.
todo.org gains a link to the MCP spec under the parent task plus
nine TODO sub-tasks (one per implementation phase), and a new
gh-tool TODO with the same spec-link shape.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
The existing autosave only fired after gptel-send returned, so a
conversation paused mid-thought wasn't on disk if Emacs crashed. I
added a buffer-local repeating timer that calls
cj/gptel--save-buffer-to-file every cj/gptel-autosave-interval seconds
(default 60) for as long as cj/gptel--autosave-active-p holds.
Toggle-off and kill-buffer-hook cancel it cleanly.
Tests cover start/stop idempotency, the active-p predicate, the
kill-buffer cleanup hook, and the toggle integration.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Six previously-scattered runtime state files now live under persist/
in user-emacs-directory:
- theme-file (was .emacs-theme)
- pdf-view-restore-filename (was .pdf-view-restore)
- time-zones--city-list-file (was .time-zones.el)
- calendar-sync--state-file (was data/calendar-sync-state.el)
- prescient-save-file (was var/prescient-save.el)
- org-id-locations-file (was .org-id-locations)
The defaults in each module now expand to persist/<name> instead of
the user-emacs-directory root or ad-hoc subdirs. Existing files
moved into persist/ alongside this change so the next launch picks
up the state without regenerating.
test-ui-theme-default-theme-file-is-emacs-dotfile renamed to
test-ui-theme-default-theme-file-is-under-persist and updated to
assert the new default path.
lsp-session-file is left at the root for now -- prog-lsp.el has no
(require) reference anywhere, so the use-package block that would
carry the redirect never runs. Tier 3 follow-up: confirm the module
is dead, then delete it or wire it into the load chain.
The var/ directory is now empty and removed. data/ retains the
calendar agenda content (dcal/gcal/pcal.org) and the .rest API
examples -- content, not state, stays where it is.
|
| |
|
|
|
|
|
|
|
| |
Removes custom/pdf-continuous-scroll-mode.el and the -latest.el variant
along with the commented-out use-package block that referenced them.
Two stale copies sat in custom/ unused. pdf-continuous-scroll-mode is
intentionally not enabled because of a known bad interaction with
org-noter. If that decision changes, the package can be added back
through normal use-package + ELPA channels.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Moves the Org Workflow Related Tools category up to sit directly after
Git Related Tools in the GPTel Tool Work hierarchy. The previous
ordering buried it after Messaging and File/Buffer.
Adds a new task: Build an Org-native API workspace around
restclient.el. Body carries a worked spec covering goals, primary
user flows, proposed modules, Org workspace shape, secret handling,
response handling, integration choices, testing strategy, and open
questions. Captures three timestamped session log entries (original
goals, ideas, spec) per the project's todo-format rules.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Resolves PATH through file-truename before applying home-directory and
read/write checks across the path-handling tools (git_status, git_log,
git_diff, move_to_trash, read_text_file, update_text_file,
write_text_file, list_directory_files, read_buffer, web_fetch).
Without the resolve step, a symlink under HOME pointing outside HOME
would pass the prefix check but the tool would act on the real target
-- a symlink-escape.
move_to_trash also tightens the trash-bin construction (treats empty
file extensions correctly) and switches the "critical directories"
list to truename-resolved canonical forms so a symlinked ~/.config
can't be trashed via an aliased path.
update_text_file fixes an off-by-one in the line-count derivation
when the source content is empty.
Each source change pairs with tests in tests/test-gptel-tools-*.el
and tests/test-update-text-file.el covering the realpath escape
paths, the empty-extension trash case, and the empty-content line-
count edge. Combined coverage is now 100% across all ten gptel-tools
source files: 516 / 516 executable lines, 217 tests.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|