- This repository is a personal Doom Emacs configuration.
- The main languages are Emacs Lisp and Org.
- Agents should optimize for small, low-risk changes that fit existing Doom idioms.
- For configuration work, edit
config.org, notconfig.el, and finish by runningdoom sync.
- Primary source of truth is
config.org, and agents should default to editing it for configuration changes. init.elis hand-maintained and declares enabled Doom modules.config.elis a tangled/generated output from the literate config and should never be edited directly.packages.elis also driven by tangledpackage!blocks fromconfig.org.custom.elis Emacs Custom output; avoid manual edits to this file!- Extra handwritten files include
my-deft-title.eland JSON files inlangserver/.
- No
.cursorrulesfile was found. - No files were found under
.cursor/rules/. - No
.github/copilot-instructions.mdfile was found. - This file is therefore the main agent guidance in the repo.
- Emacs version on this machine is
GNU Emacs 30.2. - Doom CLI is available at
~/.config/emacs/bin/doom. - If
doomis already onPATH, that is equivalent. - There is no
Makefile,package.json, CI workflow, or dedicated test directory in this repo.
- Before committing any changes to
config.org, always run~/.config/emacs/bin/doom syncfirst — this is what actually regeneratesconfig.el(via the literate module tangle hook). doom +org tangle config.orgonly updates explicitly-targeted blocks (packages.el, JSON files); it does NOT updateconfig.el.- Include
config.el(andpackages.elif changed) in the same commit asconfig.orgso the repo stays consistent. - Stage
config.org,config.el, andpackages.eltogether; never commitconfig.orgalone.
- Re-tangle the literate config after editing
config.org:~/.config/emacs/bin/doom +org tangle config.org - Run this after config, module, or package changes:
~/.config/emacs/bin/doom sync - Sync and rebuild more aggressively:
~/.config/emacs/bin/doom sync --rebuild - Check environment and common issues:
~/.config/emacs/bin/doom doctor - Launch Doom using this config:
~/.config/emacs/bin/doom emacs - Launch an Elisp REPL:
~/.config/emacs/bin/doom emacs --repl - Inspect Doom CLI help:
~/.config/emacs/bin/doom help
agile-gtdis developed at~/work/agile-gtdandorg-mcpat~/work/org-mcp.- Both are wired into Doom via
package!:local-reporecipes inconfig.org(in the** agile-gtdand** org-mcpsections). - straight.el symlinks their
.elfiles into the build directory, sodoom emacs --batchfinds them automatically without any manual load-path setup. :build (:not compile)is set for both — edits to.elfiles in the local repos are live on the next Emacs session (oreval-buffer) without rerunningdoom sync.- To switch a package from local dev to the published GitHub version (or vice-versa), swap the commented/uncommented
package!line inconfig.org, rundoom +org tangle config.org, thendoom sync -u.- Local dev active:
:recipe (:local-repo "~/work/<pkg>" :build (:not compile))is uncommented, GitHub line is commented out. - GitHub version active:
:recipe (:host github :repo "...")is uncommented, local-repo line is commented out.
- Local dev active:
Use emacsclient to query the live Emacs state without touching the session:
emacsclient -e '(length org-agenda-files)'
emacsclient -e 'org-mcp-allowed-files'
emacsclient -e '(featurep (quote agile-gtd))'Keep emacsclient calls read-only. Do not use it to call setq, load, or any mutating form — that would silently change the running session. Use it only to read variable values, check feature flags, or inspect state.
When writing multi-expression queries, put the Elisp in a temp file and load it:
emacsclient -e "(load-file \"/tmp/query.el\")"To run tests or verify config changes without a running Emacs, use the checked-in bootstrap script at test/bootstrap.el. It drives the Doom init chain manually, bypassing the noninteractive check that normally skips user config in batch mode.
emacs -q --batch -l ~/.config/doom/test/bootstrap.el -l /tmp/your-test.el 2>/dev/nullWrite temporary test scripts to /tmp/ — do not leave scratch .el files in the repo. After bootstrap, (require 'org) fires all eval-after-load 'org hooks (agile-gtd, org-mcp, etc.) exactly as in interactive Emacs. Use (message ...) for output — it goes to stderr, so redirect with 2>&1 or 2>/tmp/out.txt to capture it.
Critical constraint — do NOT set DOOMPROFILE in the environment. When DOOMPROFILE is set, doom-data-dir switches to ~/.local/share/doom/ and doom-profile-init-file resolves to a non-existent path. Leave DOOMPROFILE unset so doom-profile stays nil and doom-data-dir stays at .local/etc/ where the generated init lives.
These commands are commonly tried but do not load config.el:
| Command | Why it fails |
|---|---|
doom emacs --batch -l script.el |
Uses -q internally; no early-init, no packages on load-path, no user config |
emacs --init-directory ~/.config/emacs --batch -l script.el |
doom-initialize (not noninteractive) takes the CLI path, skipping user config |
emacs --init-directory ... --batch --eval '(require (quote org))' |
--eval with shell-special chars breaks argument parsing; use -l instead |
doom emacs --repl |
Minimal Doom CLI Emacs; doom-user-dir is empty, no user config |
- After changing
config.org, run~/.config/emacs/bin/doom sync— this retanglesconfig.orgtoconfig.eland syncs packages. doom +org tangle config.orgonly tangles blocks with explicit:tangletargets (e.g.packages.el, JSON files); it does NOT produceconfig.el.- After editing
init.el, run~/.config/emacs/bin/doom sync. - After editing
package!declarations, run~/.config/emacs/bin/doom sync. - Never patch
config.elby hand to avoid this workflow; regenerate it fromconfig.orginstead. - Because the config explicitly disables automatic literate recompilation, do not assume tangling happens for you.
- If
config.orgchanges produce updates inconfig.el,packages.el, orlangserver/*.json, keep the generated files in sync. - Use
~/.config/emacs/bin/doom sync --rebuildonly when package state, Emacs version, or stale compilation looks suspect.
- Basic repository health check:
~/.config/emacs/bin/doom doctor - Validate that the literate config still tangles before
doom sync:~/.config/emacs/bin/doom +org tangle config.org - Byte-compile a single handwritten Elisp file:
emacs --batch -Q -L . -f batch-byte-compile my-deft-title.el - Byte-compile several files:
emacs --batch -Q -L . -f batch-byte-compile init.el config.el my-deft-title.el - Load the config noninteractively when needed:
emacs --batch -Q --load init.el - For risky Emacs Lisp edits, prefer byte-compilation or batch loading before finishing.
doom doctoris the primary debug tool for config loading errors — run it and check for lines markedx:~/.config/emacs/bin/doom doctor 2>&1 | grep -A15 " x "- It reports runtime errors with backtraces, including the exact void symbol and call stack.
- Do NOT try to batch-load
config.eldirectly — Doom macros (doom!,after!, etc.) are undefined without the framework. Use the bootstrap sequence in "Loading the Full Doom Config in a Batch Sandbox" instead. after!load-order bugs: if a variable used inside(after! pkg ...)is defined later in config.el, it works in interactive Emacs (pkg loads after config) but fails indoom doctor(pkg may already be loaded, so the body runs immediately). Fix by defining the variable before theafter!block, or by adding the dependency to theafter!condition:(after! (pkg-a pkg-b) ...).- When
doom doctorreportsSymbol's value as variable is void, check whether the symbol is defined later in config.el than it is used inside anafter!body.
- There is no first-class automated test suite checked into this repo today.
- Most verification is configuration loading, tangling, and
doom syncsuccess. - Minimum validation for typical changes is: update
config.orgif needed, tangle, rundoom sync, then open Doom if the change is user-facing. - If you add ERT tests, keep them in a dedicated test file such as
test/<name>-test.el. - Run all tests in one file:
emacs --batch -Q -L . -l ert -l test/<name>-test.el --eval "(ert-run-tests-batch-and-exit t)" - Run a single ERT test by exact name:
emacs --batch -Q -L . -l ert -l test/<name>-test.el --eval "(ert-run-tests-batch-and-exit '^test-name$')" - Run tests after loading Doom if the test depends on Doom macros or modules: use the bootstrap sequence (
emacs -q --batch -l ~/.config/doom/test/bootstrap.el -l /tmp/test-file.el).
- Prefer ERT for any new automated tests.
- Name tests so they can be selected by regex without ambiguity.
- For one failing behavior, create or run one narrowly scoped ERT test instead of a broad batch.
- If a change only affects one helper function, validate that helper directly with one ERT test and byte-compilation.
- Always edit
config.orgfor literate configuration changes. - Never edit
config.eldirectly; regenerate it fromconfig.orginstead. - If a setting already lives in
config.org, update it there even if the generatedconfig.ellooks easier to patch. - Edit
init.eldirectly for Doom module selection. - Edit
my-deft-title.eldirectly; it is a normal handwritten library. - Avoid hand-editing
custom.elunless required by the task. - Treat
langserver/*.jsonas generated if the corresponding Org source block exists inconfig.org.
- Follow existing Doom Emacs conventions instead of introducing generic Emacs Lisp patterns.
- Use lexical binding where the file already enables it; do not remove it.
- Prefer standard Emacs Lisp indentation and alignment.
- Use spaces for indentation in source; do not introduce hard tabs in code.
- Keep comments sparse and useful; most current code is minimally commented.
- Preserve the repo's direct, pragmatic style over abstract framework-building.
- When inspecting dependency implementations, always prefer the local straight checkouts under
~/.config/emacs/.local/straight/repos/*as the source of truth. - Prefer plain
with-eval-after-load,use-package, andsetoptin private config, following recent Doom upstream guidance. - Use Doom-specific macros such as
map!,add-hook!, anddefadvice!where they remain the clearest fit. - Put package-specific configuration inside
with-eval-after-loadoruse-packageblocks unless a Doom-only form is required. - Use
requireonly when eager loading is actually needed. - Add new packages in tangled
package!blocks, not ad hoc runtime installs. - Disabled packages are expressed in
packages.elas:disable t.
- Prefer
setoptfor customizable variables andsetqelsewhere. - Existing code still contains legacy
setq!; do not mass-convert it unless the task calls for it. - Keep related settings grouped inside one form when it improves readability.
- Multi-line forms usually place one binding per line.
- Keybinding blocks are usually grouped with one
map!per context. - Preserve quote style already used in nearby code.
- Match surrounding whitespace instead of reformatting unrelated code.
- Emacs Lisp in this repo is dynamically typed.
- Be explicit about expected shapes in docstrings when a function takes complex plist/alist data.
- Existing code heavily uses lists, plists, alists, markers, and Org elements.
- When extending data structures, preserve current key names and value formats.
- Prefix repo-specific functions and variables with
stfl/. - Preserve existing third-party or borrowed prefixes such as
my-deft/andibizaman/. - Use kebab-case for function and variable names.
- Use
--for private helpers only when following an existing local pattern. - Interactive commands should usually have clear verb-based names.
- Use
user-errorfor bad interactive input when the user can correct it. - Use
errorfor invariant violations or truly unexpected states. - Use
ignore-errorsorignore-errorsparingly and only around best-effort behavior. - Preserve existing interactive safety checks instead of silently swallowing failures.
- If a function mutates user data or Org state, prefer explicit failure over partial silent success.
- Many settings are wrapped in
after! org,after! org-roam, or other package-specific blocks. - Keybindings are organized with
:leader,:localleader,:prefix, and mode maps. - Org code relies heavily on agenda queries, custom commands, capture templates, and property drawers.
- Org-specific helpers often assume agenda markers, headline context, or inherited properties.
- When changing Org behavior, watch for interactions with
org-agenda,org-roam,org-ql, andorg-super-agenda.
- Read the surrounding block before editing; many sections are tightly coupled.
- Keep changes local and incremental.
- Do not perform broad stylistic rewrites.
- Default to
config.orgfor config changes and regenerate derived files instead of patching generated outputs. - Treat direct edits to
config.elas incorrect unless the task is explicitly about generated output debugging. - Do not replace Doom macros with vanilla alternatives unless there is a strong repo-specific reason.
- This repo still contains legacy
after!,use-package!, andsetq!usage; prefer newer forms in touched code when low-risk, but do not perform broad mechanical rewrites unless requested. - When changing literate config, update the Org source first and then regenerate outputs.
- Mention any generated-file updates in your final note.
- After a change, run
doom sync, and include the updatedconfig.el(andpackages.elif changed) in the commit. - If you touched generated files because tangling updated them, verify they came from
config.organd were not edited by hand. - If you changed handwritten Elisp helpers, byte-compile or batch-load the touched file.
- If behavior is interactive, open Doom and smoke-test the exact command or keybinding you changed.
- If you added tests, include the exact single-test command in your handoff.