| Commit message (Collapse) | Author | Age | Files | Lines |
| ... | |
| |
|
|
|
|
| |
cj/toggle-gptel and gptel chat errored with "Symbol's function definition is void: gptel-make-anthropic". The local gptel fork on :load-path with :ensure nil ships no generated autoloads, so (require 'gptel) loads gptel.el but never gptel-anthropic.el or gptel-openai.el, where the gptel-make-* constructors live. cj/ensure-gptel-backends then reached gptel-make-anthropic before it was defined.
cj/ensure-gptel-backends now requires gptel-anthropic and gptel-openai first, through a small cj/--gptel-load-backend-libs helper. Verified end-to-end: with the fork on load-path, the constructors are fbound and both backends build.
|
| |
|
|
|
|
|
|
|
|
| |
I added a command on C-; O d that finalizes the task at point. It prompts for a finalized keyword from org-done-keywords, so the picker tracks org-todo-keywords automatically. Marking the task done fires the org-roam journal-copy hook, so the completed task lands in today's daily.
Then the heading is reshaped by depth. A sub-task (level 3 or deeper, or a VERIFY at any depth) becomes a dated log entry: the keyword and priority cookie are stripped, a sortable timestamp is prepended, and the tags are kept. A top-level task keeps its keyword and gains a date-only CLOSED line.
The command binds org-inhibit-logging around the org-todo call so it owns the CLOSED line rather than depending on org-log-done, which is set inconsistently across two modules. The journal hook keys off org-state, not org-log-done, so the copy still fires.
Tests run in org temp-buffers with the journal hook bound to nil, exercise the real org primitives, and inject a fixed time so the stamp shape is deterministic.
|
| |
|
|
|
|
|
|
| |
At startup the agenda-finalize hook ran cj/org-contacts-anniversaries-safe, which calls org-contacts-anniversaries, which calls org-contacts-files. That function messages "[org-contacts] ERROR: Your custom variable `org-contacts-files' is nil." when the variable is nil, and at that point it was nil. The value was set via the use-package :custom, which only applies when org-contacts loads, and that load is deferred behind :after (org mu4e) — later than the first agenda finalize.
I set org-contacts-files eagerly at require time instead, so it's never nil by the time the hook fires. I also guarded the wrapper: org-contacts-files emits a message rather than signaling, so ignore-errors couldn't suppress it on its own. Now the call only runs when the variable is set.
Three tests cover the eager set, the guard skipping when files are nil, and the wrapper running when they're set. Full suite green.
|
| |
|
|
|
|
|
|
| |
cj/ai-vterm-close tears an agent down cleanly: it kills the agent's tmux session (stopping the process), removes the vterm window when it isn't the only one in the frame, then kills the buffer. It targets the current agent buffer, the sole live agent, or prompts among several, and confirms before killing since that interrupts work in progress.
I also folded the whole F9 family onto ai-vterm. M-f9 used to run cj/toggle-gptel, but gptel is broken right now (the local fork doesn't load, so gptel-make-anthropic is void), and grouping every ai-vterm command under F9 reads better anyway. M-f9 is the primary close binding. C-S-f9 is a second binding that the Wayland/PGTK layer may swallow on some machines.
I covered it with 7 tests over the tmux-kill helper, the per-buffer teardown, and target selection, mocking process-file and the prompt at the boundary.
|
| |
|
|
|
|
| |
A calendar's .ics feed URL is a secret token, so I'd rather not keep it in a plaintext config file. A calendar can now name a :secret-host, and calendar-sync--calendar-url looks the URL up in auth-source (~/.authinfo.gpg) at sync time. Inline :url still works and wins when both are set, so existing configs are unaffected.
I added 7 tests covering the explicit-url, string-secret, function-secret, precedence, and no-match paths, and switched the .example template to the :secret-host shape.
|
| |
|
|
|
|
| |
The dashboard often opened already scrolled: content sat partly above the visible window with empty lines stranded at the bottom. There were two causes. The startupify list inserted five padding newlines that pushed the content past one screenful, and cj/dashboard-only moved point to point-min without resetting window-start, so a previously-scrolled view leaked into the next display.
I trimmed the padding to one newline after the banner title and one before the items, and added a set-window-start to point-min in cj/dashboard-only so the view always starts at the top. A characterization test locks the window-start reset.
|
| |
|
|
|
|
|
|
| |
The agent window's default placement was hardcoded to a right-side split at 50% width. That's wrong on a laptop, where the screen is shorter and a bottom split with more height fits better than a narrow side panel.
Pick the default from the host: bottom at 75% height on a laptop, right at 50% width on a desktop, branching on env-laptop-p in cj/--ai-vterm-default-direction and cj/--ai-vterm-default-size. The defaults still feed the existing toggle-capture mechanism, so re-orienting the window mid-session sticks the same way it did before.
Renamed cj/ai-vterm-window-width to cj/ai-vterm-desktop-width and added cj/ai-vterm-laptop-height so each axis has its own knob.
|
| |
|
|
|
|
|
|
|
|
| |
The Python helper from d6a995b could fetch and render on its own, but nothing in Emacs called it. This wires it in. Each entry in calendar-sync-calendars now takes a :fetcher key. 'api routes through the helper, and the default 'ics keeps the existing curl + Elisp parser path. Proton and any plain .ics feed work unchanged because the key defaults to 'ics.
The 'api path reads :account and :calendar-id off the calendar plist, builds the helper command (honoring the past/future window and the calendar-sync-skip-declined toggle), and runs it through make-process. The script writes the org file directly, so the sentinel only handles state bookkeeping and failure reporting, the same as the .ics worker.
I split the old --sync-calendar body into --sync-calendar-ics and turned --sync-calendar into a dispatcher. The command builder and script-path resolution are pure functions, tested directly. The dispatch routing is tested with both leaf syncers stubbed, so no process runs. I added 14 tests across the two new files, and the full suite is green.
Running the 'api path still needs the one-time OAuth bootstrap from docs/calendar-sync-api-setup.org.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Google's .ics export drops per-occurrence response statuses on recurring events. When OOO auto-declines a meeting, the master event keeps PARTSTAT=ACCEPTED and declined instances inherit it. The .ics path can't filter the declines out. The API path expands recurrences server-side via singleEvents=True, and each occurrence carries its own attendees[].self.responseStatus.
scripts/calendar_sync_api.py fetches events and renders them as org entries. OAuth is one-time per account. The refresh token lives at ~/.config/calendar-sync/token-<account>.json under 0600. Output matches the existing .ics shape: heading sanitization, LOCATION/ORGANIZER/STATUS/URL property drawer, HTML-stripped descriptions, org timestamps with weekday abbreviations.
I wrote 30 stdlib-unittest tests against fixture JSON, covering rendering, filtering, timestamp formatting, and HTML cleanup. I left auth and HTTP uncovered — they're thin wrappers around the Google client libraries, best checked by running the script once after OAuth setup.
docs/calendar-sync-api-setup.org walks through the Google Cloud OAuth client setup and the per-account auth bootstrap. .gitignore picks up Python bytecode now that the project has a Python helper.
The Elisp dispatch (:fetcher 'api routing in calendar-sync.el) lands in a follow-up commit.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The sync parsed PARTSTAT into a :STATUS: declined property but kept
the event. Meetings I'd declined still landed in dcal.org / gcal.org
and showed on the agenda. I added a pure --filter-declined helper
called inside --parse-ics after event collection, plus the
calendar-sync-skip-declined defvar (default t) so it can be flipped
off without code changes.
The .ics feed and the Calendar API can disagree on PARTSTAT. OOO
auto-declines sometimes only write API-side, so a few declined
events may still slip through. I'm calling this out because the
filter looks absolute from the agenda but isn't.
Tests cover Normal/Boundary/Error (11 cases). Full suite is green.
|
| |
|
|
|
|
|
|
| |
`<escape>' is bound globally to `keyboard-escape-quit' in
modules/keybindings.el, so Emacs swallows the key before it can reach the
pty. Bind it in vterm-mode-map to cj/vterm-send-escape, which writes a
literal ESC byte via vterm-send-string. tmux's copy-mode `cancel' binding
then fires; vi-mode exits, fzf cancel, etc., also work as expected.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
vterm-mode-map binds only mouse-1 and mouse-yank-primary, so wheel events
fall through to Emacs scrolling and never reach the pty. tmux's `set -g
mouse on' never sees them. Bind wheel-up / wheel-down (and X11 mouse-4 /
mouse-5) to send SGR mouse-wheel escapes via vterm-send-string. tmux's
existing WheelUpPane / WheelDownPane bindings route into copy-mode from
there.
For keyboard parity, route C-; x c through cj/vterm-copy-mode-dwim, which
sends C-b [ when a tmux client is attached and falls back to vterm-copy-mode
otherwise. tmux's history-limit is now reachable from either entry point.
The matching copy-mode keys (M-w stays, C-g / q / Escape exit, Enter
unbound) land in the dotfiles repo alongside.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
I switched the gptel use-package form to `:load-path "~/code/gptel"`
with `:ensure nil` so Emacs loads from the fork instead of the MELPA
release. The fork now carries the narrow `tab-width' copy in
`gptel-org--create-prompt' that karthink redirected the upstream PR
to, which replaces the local `:around' advice on
`gptel--with-buffer-copy-internal' I'd been carrying.
I also dropped the stale test file
`tests/test-ai-config-gptel-prompt-tab-width.el' and the matching
stub in `tests/testutil-ai-config.el'. Both existed only to test the
advice I removed.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
First of nine phases for wiring mcp.el into GPTel. I scoped this
phase to sections 1 (constants and defcustoms) and 3 (pure
helpers) of the seven-section outline in the design doc. The
other five sections ship in later phases. The module loads only
under the test harness for now. init.el wiring waits for Phase 4.
What I added:
- cj/mcp-server-specs defconst: secret-free description of the 9
servers (linear, notion, figma, slack-deepsat, drawio,
google-calendar, google-docs-personal, google-docs-work,
google-keep).
- Seven defcustoms: claude-config path, enabled-servers list,
start-on-entry-points scope, two timeouts, per-tool confirm
overrides, audit-log toggle.
- cj/mcp--read-claude-config with an mtime cache and structured
(:ok t/nil :reason ...) returns.
- cj/mcp--get-server-entry, get-env, and get-secret-arg for
pulling server data from the parsed config (figma's API key
lives in args, not env).
- cj/mcp--build-server-alist: pure transformer from specs plus
config to the alist mcp-hub-servers expects.
- cj/mcp--confirm-p classifier with write-pattern, read-pattern,
and unknown-fails-closed branches, plus a
cj/mcp-tool-confirm-overrides alist override.
- cj/mcp--normalize-description prefixing tool descriptions with
[SERVER], [SERVER WRITE], or [SERVER ?].
- cj/mcp--redact masking --token, --secret, --password, and
--figma-api-key flags, Authorization headers, and ?token= URL
params.
Tests in tests/test-ai-mcp-helpers.el (41 ERT tests, all green):
fixtures via make-temp-file, no real ~/.claude.json reads, no
subprocesses, no network. Sentinel REDACTED_TEST_SECRET never
appears in any redactor output.
Design doc: docs/design/mcp-el-gptel-integration.org
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- 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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The Info-mode entry in cj/buffer-source-functions copied the bare
target string info:(manual)Node. Per the task body that introduced
the dispatcher, the intended output is the labeled org-link form
[[info:(manual)Node][(manual) Node]] -- a paste into notes lands as
a clickable link with a human-readable label, not a bare URI.
The label uses (manual) Node so the manual name and node name are
both grep-friendly in note files.
Existing test on a compressed .info.gz file now asserts the bracket
form. Added a boundary test for an uncompressed .info file (the
other branch of the suffix-stripping logic) so both compression
shapes are locked in.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
make compile warned that cj/toggle-gptel is not known to be defined
when ai-vterm.el is byte-compiled. The M-F9 binding still worked
during normal startup because init.el loads ai-config.el after
ai-vterm.el, but the dependency was implicit -- byte-compile saw the
function symbol unresolved, and loading ai-vterm.el in isolation
left M-F9 bound to an undefined function.
Declare cj/toggle-gptel as an interactive autoload pointing at
ai-config. This silences the warning, keeps ai-vterm.el free of a
load-time (require 'ai-config), and makes the load-order contract
explicit: the binding works as long as ai-config eventually loads.
Test asserts that requiring ai-vterm in isolation leaves
cj/toggle-gptel fboundp as an autoload sigil (not a real function).
A regression that adds (require 'ai-config) at the top of
ai-vterm.el would flip this, and a regression that drops the
autoload form would leave fboundp nil.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Add a tiny source-level architecture suite at
tests/test-architecture-startup-contracts.el with two checks:
- Only keybindings.el may globally own the exact C-; prefix. Catches
accidental cross-module rebinding before it ships.
- Top-level timer scheduling (run-with-timer / run-at-time /
run-with-idle-timer) must be guarded by (unless noninteractive ...)
so requiring a module in batch / test mode does not schedule
startup timers. Timer calls inside defuns are exempt -- the test
only rejects forms that execute their body when the module loads.
Four modules had unguarded top-level timer scheduling and would have
tripped the new test. Wrap their startup hooks/timers in
(unless noninteractive ...):
- modules/org-agenda-config.el: 10s idle cache build
- modules/org-refile-config.el: 5s idle cache build
- modules/quick-video-capture.el: after-init-hook + 2s fallback
- modules/wrap-up.el: emacs-startup-hook bury-buffers delay
The contract being protected is "requiring a module in batch should
not start a clock running." Test failures will now point straight at
the offending file/form.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
=make coverage= used to print a line-weighted percentage that only saw
files SimpleCov instrumented. 104 modules existed on disk but only 49
appeared in =.coverage/simplecov.json=, so the headline number was
flattering: untouched modules counted for nothing.
The summary script now adds two things on top of the existing report:
- A =Not in SimpleCov report= section listing modules present under
=modules/*.el= but absent from the SimpleCov output. Missing-module
detection is exactly direct =modules/*.el=; subdirectories and =.elc=
files are ignored.
- A =Project module coverage= line that is module-weighted across every
direct =modules/*.el= file. Tracked modules contribute their per-file
coverage percentage; missing modules contribute 0%.
The original line-weighted SimpleCov percentage stays as the
=instrumented coverage= number. The new module-weighted score is the
honest project-level reading: missing modules count as 0% without
inventing a fake executable-line denominator for them.
Tests assert the missing-module section, the new percentage, and the
ignore rules for .elc / nested files.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Add two dispatchers to cj/buffer-source-functions so C-; b p yields a
useful link form in two more major modes.
mu4e-view-mode returns "mu4e:msgid:<id>" so the result pastes into org
as a clickable link and matches mu4e's own org-protocol handler.
Falls through to buffer-file-name when point isn't on a real message.
Info-mode returns "info:(manual)node" -- the form org-info-store-link
produces. file-name-base only strips one extension, so a compressed
"emacs.info.gz" comes back as "emacs.info"; trim the trailing ".info"
to get the bare manual name. Falls through when Info hasn't populated
its current-file / current-node vars yet.
Tests cover normal + boundary fallthrough for each new mode.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`cj/org-noter-insert-note-dwim' is the most-used action in a noter
session; it deserves the doubled-prefix letter. Move it from
`C-; n i' to `C-; n n'.
Sibling-stepping moves off `n'/`p' (which were sync-next /
sync-prev) onto the angle-bracket pair `>'/`<' to free up `n' and
to read more naturally as direction. `.' stays as
sync-current-note.
Updated `which-key' labels to match. Four new ERT tests in
`tests/test-org-noter-config-keymap.el' lock the keymap shape so a
casual edit doesn't silently drift the layout.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Old behavior: `C-; b p' called `cj/copy-path-to-buffer-file-as-kill',
which only worked in file-visiting buffers and errored otherwise.
That meant the most useful "give me a clickable handle on this
buffer" key did nothing in eww, elfeed, dired (file-at-point ≠
buffer's default-directory), and other browsing-shaped modes.
Replace with a `major-mode'-aware dispatch:
- `cj/buffer-source-functions' alist maps major-mode → thunk
returning a string (or nil to fall through).
- `cj/copy-buffer-source-as-kill' looks up the current mode,
calls the thunk, falls back to `buffer-file-name', errors only
when both yield nil.
- `cj/copy-path-to-buffer-file-as-kill' kept as a `defalias' for
backwards compat (the old name is referenced in adjacent tests).
First-batch dispatches:
- eww-mode -> (eww-current-url)
- elfeed-show-mode -> (elfeed-entry-link elfeed-show-entry)
- dired-mode -> (dired-get-filename nil t)
- dirvish-mode -> same
- doc-view / pdf-view: covered by the buffer-file-name fallback
(they already set buffer-file-name correctly).
10 new ERT tests cover the dispatch paths, the
buffer-file-name fallback, the user-error on nil source, the alias
target, and the `C-; b p' keymap entry.
which-key label flipped from "copy file path" to "copy buffer
source" to match.
Deferred to a follow-up task: mu4e-view-mode, org-mode at a
heading, help-mode, Info-mode, magit-log/commit/status, xref/grep/
compilation, image-mode, archive-mode -- each needs a format
decision before implementation.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
M-F9 used to invoke `cj/ai-vterm-pick-buffer' (a buffer picker
narrowed to alive AI-agent buffers). In practice the F9 plain-key
toggle + C-F9 project picker covered the common cases, and the
buffer picker rarely earned its keystroke. Rebind M-F9 to
`cj/toggle-gptel' so the F9 family covers the two main in-Emacs AI
surfaces at one keystroke each:
<f9> ai-vterm toggle (unchanged)
C-<f9> ai-vterm picker (unchanged)
M-<f9> gptel *AI-Assistant* (NEW)
Removed entirely:
- `cj/ai-vterm-pick-buffer' (the command itself).
- `cj/--ai-vterm-pick-buffer-candidates' (its helper).
- `tests/test-ai-vterm--pick-buffer-candidates.el' (deleted).
Updated:
- `tests/test-ai-vterm--f9-in-vterm.el' binding assertions
(vterm-mode-map and global) flipped to `cj/toggle-gptel'.
- Module commentary + `cj/ai-vterm' docstring describe the new
M-F9 behavior.
- `cj/toggle-gptel' lives in `modules/ai-config.el'; the binding
stays in `ai-vterm.el' next to the rest of the F9 family so the
dispatch shape is visible in one place.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`#+begin_src markdown ... #+end_src' blocks rendered and exported
fine but `org-lint' warned on every one of them ("Unknown source
block language: 'markdown'"), and `C-c '' inside the block fell
back to `fundamental-mode' instead of opening it in
`markdown-mode' for editing.
Add a `with-eval-after-load 'org' form that pushes
`("markdown" . markdown)' onto `org-src-lang-modes'. New ERT test
in `tests/test-markdown-config.el' asserts the entry resolves to
`markdown' after `(require 'markdown-config)'.
Surfaced while clearing `org-lint' on `todo.org' from 55 issues
down to 1 -- the last one was this warning on a Linear ticket-body
draft that was genuinely markdown. Registering the language is
the right fix; relabeling the block as `text' or `example' would
lose accuracy.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Narrowing and sparse-tree commands existed in the `:bind' block
on `C-c'-style shortcuts but nothing in `cj/org-map' surfaced
them, so which-key never showed them and discoverability was
poor.
Add direct bindings under `C-; O', flat (no sub-prefixes for
narrow / sparse-tree). Lowercase creates; capital of the same
letter cancels:
- `n' / `N' narrow-to-subtree / widen
- `s' / `S' match-sparse-tree / show-all
- `t' / `T' show-todo-tree / show-all
- `>' / `<' forward / backward sibling narrow (kept as-is)
- `R' reveal-context (no lowercase pair -- `r' is the
table-row sub-prefix)
Both `S' and `T' resolve to the same `org-show-all' command so
the mental model is just "capital cancels the lowercase I just
ran" without having to recall which letter the cancel actually
lives on.
Free up F2: the old `(<f2> . org-reveal)' binding in the org-mode
`:bind' block is now redundant with `C-; O R'. Drop it; F2
becomes available for whatever wants it next.
Four new ERT assertions in `test-org-config-keymap-ownership.el'
lock the shape -- the old sparse-tree-submap test was rewritten
for the flat layout and the narrow-submap test became
narrow-bindings (also flat).
|