| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
| |
cj/git-clone-clipboard-url shelled out via shell-command and derived the clone directory with file-name-nondirectory, which mishandles scp-style SSH URLs with no slash (git@host:repo.git became git@host:repo). It also ran git in default-directory and only checked whether the clone dir appeared afterward, so a failed clone was silent.
The clone now runs as a direct git process (call-process, no shell) with clone -- url dir so a URL beginning with - cannot be read as a flag. The destination path comes from cj/--git-clone-dir-name, which takes the last component splitting on / and :, handling HTTPS, scp-style and ssh:// SSH, and local paths. It validates the clipboard is non-empty and the target is a writable directory that does not already contain the destination, and surfaces a non-zero git exit as a user-error with the *git-clone* output. Tests cover the deriver across URL schemes plus the empty-clipboard and clone-failure paths.
|
| |
|
|
| |
org-capture-config.el and org-drill-config.el each scanned drill-dir with an inline directory-files call, so a missing, empty, or unreadable drill-dir surfaced as a low-level directory-files error or an empty completing-read, depending on which command ran. Added cj/--drill-files-or-error, the single validated entry point: it signals a clear user-error when the directory is missing, unreadable, or has no drill files, and otherwise returns the list. cj/--drill-pick-file and both drill capture templates now route through it. The pure cj/--drill-files-in primitive and its tests are unchanged. Tests cover missing dir, empty dir, a non-org-only dir, and a normal listing.
|
| |
|
|
| |
7-Zip 26.01 reads the encryption password only from its controlling TTY, not stdin or a file — a piped password silently becomes an empty one — so it has to go on argv and is briefly visible in the process list. Rather than switch off the .7z format to gpg-wrapped tar, the exposure is accepted: single-user workstation, short-lived process, password already kept out of shell history by the mode-600 temp file. Documented the evaluated tradeoff in both encrypt/decrypt docstrings so it's visible at the call site.
|
| | |
|
| |
|
|
|
|
| |
Restart-Emacs scheduled an unconditional kill-emacs one second after firing the systemctl restart. If the service was missing or the restart failed, the session still got killed with nothing to replace it. Restart now guards on (daemonp) and a present emacs.service before doing anything, and drops the separate kill-emacs entirely — systemctl restart cycles the daemon itself, so a failed restart leaves the current Emacs alive. Added cj/system-cmd--emacs-service-available-p (systemctl --user cat) for the guard.
Shutdown and reboot now use a strong yes-or-no-p confirm instead of the quick (Y/n) read-char, where RET or space counted as yes — a stray Enter at the prompt could power off the machine. Logout and suspend keep the quick confirm since they are recoverable. The confirm tier rides on a property set by cj/defsystem-command. Tests cover service detection, both restart guards, and the strong-confirm accept/decline paths with the system primitives stubbed.
|
| |
|
|
| |
Closed the X11/audio shell-quoting, the scoped wf-recorder stop signal, and the selected-directory creation as dated event-log entries under the recording hardening tree.
|
| |
|
|
|
|
| |
The recording toggles took a directory from the prefix-arg prompt (or the default), then ran (file-name-directory location) before make-directory. For a path without a trailing slash that returns the parent, so make-directory created the parent and left the selected directory uncreated — ffmpeg then failed to write into it.
Both toggles now route the destination through cj/recording--normalize-recording-dir, which expands and applies file-name-as-directory, then call make-directory on that normalized path. The selected directory itself is created (parents=t is a no-op when it already exists), including names with spaces. Tests cover trailing-slash normalization, idempotence, spaces, and relative-to-absolute expansion.
|
| |
|
|
|
|
| |
Stopping a Wayland recording ran pkill -INT wf-recorder, which signals every wf-recorder on the system — including an unrelated screen capture the user started outside Emacs. The stop path now scopes the producer-first interrupt to the wf-recorder child of our own recording shell via pkill -P <shell-pid>, in the new cj/recording--interrupt-child-wf-recorder helper.
The producer-first ordering is unchanged: wf-recorder still gets SIGINT before the process-group signal so ffmpeg sees a clean EOF on pipe:0 and finalizes the MKV. The orphan-cleanup at recording start stays a broad by-name kill on purpose — those leftover recorders come from crashed sessions whose shells are already dead, so there is no live PID to scope to. Tests cover the scoped call, the nil-PID no-op, and that the bare system-wide form is never used.
|
| |
|
|
|
|
| |
The X11 video path and the audio path interpolated the mic device, system device, and output filename straight into the shell command, so a device name or recording directory with a space (or other shell metacharacter) would break the command or mishandle the path. The Wayland video branch already quoted these; the other two did not.
I wrapped all three in shell-quote-argument on both paths. To make the audio command testable, I extracted it into cj/recording--build-audio-command mirroring the existing cj/recording--build-video-command, then quoted there. Tests cover device names and filenames with spaces on both the X11 and audio builders.
|
| |
|
|
| |
While isearch is active, overriding-terminal-local-map is isearch-mode-map, so C-s resolves to isearch-repeat-forward and the global cj/consult-line-or-repeat binding can't shadow it. isearch-mode-map already binds C-s to that default, so I left selection-framework.el unchanged.
|
| |
|
|
|
|
| |
The latest design review was a UX and performance pass, and I folded its findings into the spec and the implementation tasks. The important one: human Emacs edits now use the same write path as agent writes. An ai-kb minor mode runs index, full lint, and commit under flock on after-save, so a hand edit can't quietly skip the safety gate. The rest: the generated index.org is now invisible to backlink and orphan logic (excluded from the scan, referenced as plain text rather than id-links), a required :SUMMARY: property feeds the index and query without inference, query gains lexical ranking with recency only as a tie-break, the switch installs a full org-roam profile rather than a two-variable swap, and the browsing surface (dashboard, find, search, show, backlinks, map) is named.
I also answered the six build-time decisions: concrete raw and curation limits, performance budgets for the perf fixtures, the lexical scoring weights, org-roam-graph as the first map implementation, the after-save failure UX (the save always lands, the commit is gated, and a failure shows without trapping the buffer), and the after-save recursion guard. The numeric limits and budgets are starting points to calibrate. The rest are firm. Step 1 stays buildable.
|
| |
|
|
|
|
| |
hardening
I reorganized the Implement ai-kb children into the Step 1a / 1b phases from the spec: 1a is the safe write path (store, contract, the index/lint/remember/doctor CLI, the adapter, provisioning), and 1b is query/curate/sync, the push timer, and the curation workflow. The remember task now gates the commit on the full ai-kb lint rather than node org-lint, the test tasks target the org-lint fatal-check list and the query --json contract, the push task carries the failure-observability surfaces, and the pointer convention is ID-first throughout.
|
| |
|
|
|
|
| |
Review 5 was implementation-hardening, all of it sound, so I folded in all six findings. The important one: the commit gate now runs the full ai-kb lint over the change (index freshness, duplicate IDs, broken links, and a secret scan of nodes and raw/), not just org-lint on the edited node. If the write path is the safety boundary, gating only on single-node syntax would let a stale index or a leaked secret through.
The rest, all adopted: an explicit org-lint fatal-check list so a future org-lint change can't silently move the gate, observable push failures surfaced through a state-file log and ai-kb doctor and a startup nudge so the KB can't go quietly local-only, a testable ai-kb query contract with text and --json output, and ID-first durable pointers since filenames change in curation but IDs don't. I also split the build plan into Step 1a (the safe write path) and 1b (query, curate, sync, push timer, workflow), since remember depends on index and lint and the adapter depends on remember.
|
| |
|
|
|
|
| |
I resolved the four open decisions and baked the answers in: the store lives at ~/.local/share/ai-kb (XDG), the ai-kb CLI is a shell wrapper that calls emacs --batch for the org-lint and sync steps, the push runs off a background systemd --user timer rather than firing on every write (remember only commits locally), and curation is node-count-triggered with the workflow living in the rulesets .ai/workflows/ directory.
I also refreshed the provisioning and Step 1 sections to match, since the push timer was a new piece: make ai-kb-init now installs and enables the ai-kb-push timer and service units, and doctor checks for them. Open decisions is empty now and the spec is fully decided.
|
| |
|
|
|
|
| |
Reviews 3 (Codex, via Nexus/GraphRAG/Letta research) and 4 pushed on the write loop and the access layer rather than scope. I folded both in. The write path is now a real protocol: fetch and fast-forward before writing, org-lint the node, regenerate the index, commit locally always, and treat the push as best-effort and non-blocking so a failed push never errors or hangs the agent. That's the exact gpg-agent failure we hit earlier today. The index is regenerated from node properties by a script rather than hand-maintained, so it can't drift from the nodes.
The access layer became an agent-neutral contract that lives in the repo, fronted by a minimal ai-kb CLI (doctor, query, remember, lint, curate, sync) with destructive operations human-only. That earns its place on Claude-only grounds: it's the clean home for the safe-write protocol and the lint and index steps. Cross-agent use is not a near-term goal, so Codex and Ollama adapters are deferred to vNext. The contract stays neutral in shape, so they're additive later. Added provenance fields, the T1/T2/T3 tier names, and the review dispositions. The spec is now Ready.
|
| |
|
|
|
|
| |
ai-kb is a global, durable, cross-project memory store for Claude Code: org-roam nodes holding lessons, principles, my preferences, and reusable procedures, distinct from the per-project memory files (which shrink to an index pointing into it). The spec covers the two-layer model (a git-versioned file store the agent reads/writes, and an Emacs switch command so I can browse it with backlinks), the sync model, the routing and proactive-write rules, the node format, and the startup retrieval contract.
It folds in two reviews. The scope decision: v1 is the memory store, not a full Karpathy LLM Wiki. The heavy machinery (compiled wiki layer, source hashes, formal ingest pipeline, embedding search) is deferred to vNext, each with a reason. Storage is a dedicated private git repo at an XDG path rather than Syncthing or the public emacs-config repo, which would leak personal notes. Two Karpathy ideas earned their way into v1 because they pay off now: capturing the raw source when a node is compiled from external material, and an org-lint validity check on every write so malformed org never reaches the index. Review dispositions and the open decisions are recorded in the spec.
|
| |
|
|
| |
The C-; O prefix (cj/org-map) had no which-key labels, so the popup just showed raw command names, and nothing at all for the d (finalize-task) binding. I added labels for the whole prefix, including the r/c table sub-prefixes. The two org-show-all bindings are labeled "cancel sparse tree" (S) and "cancel todo tree" (T) so the popup shows what each one cancels rather than two identical "show all" entries.
|
| |
|
|
| |
org-tidy's default inline marker is the music sharp (♯), which reads as a full-size # next to a heading. I set org-tidy-properties-inline-symbol to a middle dot (·) so the collapsed drawer is marked with something far less visually heavy.
|
| |
|
|
| |
The tooltip looked ahead a full week (chime-tooltip-lookahead-hours was 7 * 24), which crowded it with events I don't need at a glance. I dropped it to 3 * 24, so it shows today, tomorrow, and the next day only. I also fixed the comment above it, which still claimed 10 events within 6 days when the code already said 20 within 7.
|
| |
|
|
|
|
| |
I added a Linear entry to the launcher table, keyed l, with the nf-oct-issue_tracks octicon, opening the issue list via linear-emacs-list-issues. That makes 13 launchers, which no longer divides into the old rigid 4-per-row grid.
So I replaced the fixed chunk-by-4 with an explicit cj/dashboard--row-sizes (4 4 3 2) and reordered the table so Telegram comes before Slack, putting Slack and Linear together on the last row. The button shape moved into cj/dashboard--navigator-button, shared by the grouped loop and a fallback row for any launchers the sizes don't cover. A test pins the row sizes to the launcher count so they can't drift.
|
| |
|
|
|
|
|
|
| |
linear-emacs grew a lot of new commands in its rework: filtered lists, saved queries, Custom Views, open-in-browser, comments, delete, and set-assignee/state/priority/labels on the issue at point. The config still listed and bound only the original seven, and the init.el require was commented out while the package was in flux.
I re-enabled the require, expanded :commands to all 25 autoloaded commands, and rebuilt the C-; L keymap around them: lists and views up top, an o/r/D set for the issue at point, sync on s/S/u/U, and a C-; L e sub-prefix for editing the issue's fields. The lazy authinfo key-load advice and the data/linear.org path carry over unchanged.
I verified the dependency symbols still exist before wiring, but the live connection check (C-; L ?) is still yours to run.
|
| | |
|
| | |
|
| |
|
|
|
|
|
|
| |
Two commands did less, or more, than their names implied.
remove-empty-directories ran find . -type d -empty -delete from whatever the current directory happened to be, so its scope was implicit and easy to misjudge. It now prompts for a root, names that root in the confirmation, and runs find against the shell-quoted root via cj/dwim-shell--empty-dirs-command.
secure-delete ran shred without -u, so it overwrote a file's contents but left the file in place, not the deletion the name and the "permanently destroy" prompt promise. Added -u so it unlinks after overwriting.
|
| | |
|
| |
|
|
|
|
| |
cj/dwim-shell-commands-concatenate-videos built the ffmpeg concat list with echo '<<*>>' | tr ' ' '\n' | sed 's/^/file /'. That splits on spaces, so any video whose name contains a space produced a broken list, and a name with a quote broke the echo outright.
I extracted cj/dwim-shell--build-concat-filelist, which renders each path as an escaped file '...' line. I write that list to a temp file and run ffmpeg against the quoted listfile, with a trailing rm to clean up after the process exits. The <<*>> token stays only as an inert shell comment, since dwim-shell needs it to run one command over all marked files instead of once per file.
|
| | |
|
| |
|
|
|
|
|
|
| |
org-babel-config set org-confirm-babel-evaluate to nil globally, so a source block in any Org file (a cloned repo, a downloaded note, a web clip) ran with no prompt. That's arbitrary code execution on opening the wrong file and hitting C-c C-c.
I set the default to t (confirm) and replaced the old babel-confirm command, which only toggled under a prefix arg, with cj/org-babel-toggle-confirm. It flips confirmation off for the session when I'm in trusted files and back on when I'm done, bound to C-; k.
The C-; k binding is a placeholder. I filed a follow-up to give it a permanent Org-prefixed home.
|
| | |
|
| |
|
|
|
|
|
|
| |
Several dwim-shell commands interpolated user-controlled strings straight into shell templates, so a value with spaces, quotes, or shell metacharacters could break out of the command. The worst was git-clone-clipboard-url, which dropped raw clipboard contents into "git clone <<cb>>".
I added three pure validators (git URL, ffmpeg timestamp, rename prefix) and fixed the interpolation sites. git-clone now validates the clipboard and passes the URL through shell-quote-argument instead of <<cb>>. The GPG recipient and the 7z archive name go through shell-quote-argument instead of hand-written single quotes. The thumbnail timestamp and the rename prefix are validated to a safe shape before they reach the command, so the unquoted interpolation that remains is constrained to digits, colons, and filename-safe characters.
The fifth case in the ticket, the video-concat filelist built with echo/tr/sed, is a redesign rather than a quoting fix and is filed as a follow-up.
|
| | |
|
| |
|
|
|
|
|
|
| |
The four password commands (PDF protect/unprotect, remove-zip-encryption, create-encrypted-zip) wrote the password to a temp file, launched an async dwim-shell command, then deleted the file in unwind-protect. Since the command is async, that delete ran the instant it launched, so qpdf or 7z could start after the password file was already gone.
I extracted cj/dwim-shell--run-with-password-file and cj/dwim-shell--password-cleanup-callback. The temp file (mode 600) is now deleted from an :on-completion callback that fires after the process exits, on both success and failure, and the synchronous unwind-protect stays only as a backstop for a throw before the async launch. All four commands now go through the one helper.
qpdf already reads the password via --password-file, so it stays out of the argv. 7z still takes it as -p"$(cat ...)", which lands on its command line. That's tracked as a separate follow-up.
|
| | |
|
| |
|
|
|
|
| |
cj/restclient-skyfi-buffer opened the SkyFi template in a file-visiting buffer and rewrote the :skyfi-key line with the live key from authinfo. An accidental save would then persist the plaintext key to disk, which breaks the module's own "key never stored on disk" promise. The template file was gitignored and never tracked, so the exposure was local only, not a repo leak.
I removed the feature rather than hardening it: cj/skyfi-api-key, cj/restclient--inject-skyfi-key, cj/restclient-skyfi-buffer, the C-; R s binding, and the two SkyFi test files are gone, along with the local template. The generic restclient setup stays: scratch buffer on C-; R n, open a .rest file on C-; R o.
|
| |
|
|
|
|
|
|
| |
linear-emacs-check-setup read linear-emacs-api-key directly and bailed to "API key is not set" before making any request, so the lazy :before advice on the GraphQL request never fired for it. A fresh session always reported the key missing even though it was in authinfo. I extracted cj/linear--install-key-advice and put the loader on check-setup as well as the request entry point, with a regression test.
I also pinned linear-emacs-org-file-path to data/linear.org inside emacs home, next to the calendar-sync output. Left to its default it falls back to org-directory/gtd/linear.org and silently created a stray ~/org tree on the first pull.
The init.el require is commented out for now while linear-emacs is reworked. The config will need rework once that lands.
|
| |
|
|
|
|
| |
I added modules/linear-config.el to load the local linear-emacs checkout (the same :load-path + :ensure nil shape gptel and org-drill use) and point it at DeepSat's Linear workspace. The personal API key comes from authinfo.gpg, loaded lazily by a named :before advice on the request function, so there's no GPG prompt at startup. The default team is DeepSat's Software Engineering team, and the commands sit under a C-; L prefix.
Verified live against DeepSat: the connection authenticates, lists all five workspace teams, and pulls real issues (SE-*, DEE-*). Tests cover the key-loader (loads when unset, keeps an existing key, stays nil when absent) and the keymap wiring.
|
| |
|
|
| |
I triaged the ~50 open hardening findings against the codebase. Closed five whose premise was stale because the work already shipped: the personal-calendar-config split, the shared cache helper, the cache idle-timer gating, and AI-conversation-persistence coverage. Tagged 31 findings :solo: — bounded work an agent can finish and verify without a decision from me. The rest carry a preference, policy, or design call and stay untagged.
|
| |
|
|
| |
I added docs/mu4e-org-msg-compose-buffer-cleanup.org explaining the one setting that controls whether mail compose buffers close on exit (message-kill-buffer-on-exit), why org-msg needs no setting of its own (it reads the variable, never sets it), and the trap that a stray setter in org-msg's :config silently wins over the mu4e one.
|
| |
|
|
| |
org-msg only reads message-kill-buffer-on-exit (in org-msg--widen-and-undo) and never sets it, so the duplicate setq in org-msg's :config was redundant. I removed it and kept the single t in the mu4e :config, with a comment noting org-msg honors whatever mu4e leaves in place. No behavior change. Compose buffers still kill on exit.
|
| | |
|
| |
|
|
|
|
| |
The earlier setup kept compose buffers after exit (org-msg set message-kill-buffer-on-exit to nil), so HTML draft buffers lingered after a message was sent or aborted. I want them cleaned up, so I set the org-msg value to t to match the mu4e default. Both composers now kill the buffer on exit.
The modules byte-compile and the mail-config tests stay green. The kill-on-exit behavior itself only shows up in live use, not in batch.
|
| | |
|
| |
|
|
|
|
| |
mail-config.el set message-kill-buffer-on-exit to t in mu4e's config and nil in org-msg's, with no note on which wins. org-msg-mode runs in every compose buffer, so org-msg's nil is the effective policy: compose buffers are kept, not killed. The org-msg comment said "always kill buffers on exit", which is backwards.
I rewrote both comments. The mu4e t is the plain-mu4e fallback that only matters if org-msg is ever disabled, and org-msg owns the live policy of keeping a draft buffer on exit so an in-progress HTML message isn't lost. No behavior change.
|
| | |
|
| |
|
|
| |
The `<java` Org Tempo template expanded to "#+begin_src javas", a typo for "java", so a Java source block came out tagged with a bogus language. I fixed the entry to "src java" and added a test that pins seven common aliases (bash, zsh, el, py, json, yaml, java) to their intended src language names, so the next typo in the template list gets caught.
|
| | |
|
| |
|
|
|
|
| |
flyspell-and-abbrev.el had no tests. I added eight: cj/--require-spell-checker (errors when no checker is on PATH, passes when one is), cj/find-previous-flyspell-overlay against synthetic overlays (finds the closest previous flyspell overlay, ignores non-flyspell ones, returns nil when none exist), and cj/flyspell-on-for-buffer-type dispatching to flyspell-prog-mode in code buffers and flyspell-mode in text buffers.
I left cj/flyspell-then-abbrev to manual testing. Pinning its flyspell-UI orchestration would mean mocking flyspell internals rather than our own logic.
|
| | |
|
| |
|
|
| |
media-utils.el had no tests. I added eight: cj/get-available-media-players filtering by executable-find, cj/media-play-it building a direct command versus the yt-dlp -g stream-URL wrap (plus the missing-player error), and cj/yt-dl-it erroring when yt-dlp or tsp is absent and queueing through tsp + yt-dlp when both are present. Every external boundary is mocked, so nothing launches.
|
| | |
|