# AGENTS.md — Doom Emacs Configuration This is a Doom Emacs 3 personal configuration at `~/.config/doom/`. ## Repository Structure ``` doom/ ├── init.el # Module declarations (doom! macro) ├── config.el # Main config, loads modular files via load! ├── packages.el # Package declarations (package! macro) ├── core.el # Core settings and super-save ├── custom.el # Emacs custom variables (auto-generated) ├── security.el # Security settings ├── org.el # Org-mode configuration ├── metrics.el # Usage tracking (keyfreq) ├── theme.el # Theme configuration ├── inbox.el # Quick capture inbox ├── ai/ │ ├── agents.el # Claude Code & Gemini CLI configuration │ └── gptel.el # GPTel (ChatGPT) configuration ├── development/ │ ├── lsp.el # LSP/LSP-UI configuration │ ├── cpp.el # C++ development settings │ ├── nix.el # Nix development settings │ └── misc.el # Miscellaneous dev settings ├── docs/ │ └── doom-macros-reference.md # Doom macro documentation └── snippets/ # Yasnippet snippets ``` ## Doom Commands ### Essential Commands ```bash # Sync config (run after ANY change to init.el, packages.el, or package configs) doom sync # Rebuild all packages doom build # Diagnose issues doom doctor # Update all packages doom sync -u # Show verbose output doom -v sync # Run in batch mode (for CI/testing) emacs --batch -l ~/.config/doom/config.el --eval "(message \"test\")" ``` ### Package Management ```bash # Force sync without prompts doom sync -! # Clean up orphaned packages doom sync --gc # Install packages only (no rebuild) doom install ``` ### Testing Individual Config Files ```bash # Load a specific config file and check for errors emacs --batch -l ~/.config/emacs/bin/doom -l config.el -l ai/agents.el --eval "(message \"Loaded successfully\")" # Check Elisp syntax emacs --batch --eval "(setq lexical-binding t)" -l ai/agents.el --eval "(message \"Syntax OK\")" ``` ## Code Style Guidelines (Emacs Lisp) ### File Headers Every `.el` file should start with a standard header: ```elisp ;;; filename.el -*- lexical-binding: t; -*- ;;; Commentary: ;;; Description of what this file does ;;; Code: ``` ### Lexical Binding Always use `lexical-binding: t` (note the dash, not underscore in the file-local variable). ```elisp ;;; init.el -*- lexical-binding: t; -*- ``` ### Naming Conventions | Element | Convention | Example | |---------|------------|---------| | Private functions | `+prefix--name` | `+org--restart-mode-h` | | Hook functions | `+module-hook-function-h` | `+org-exclude-agenda-buffers-from-workspace-h` | | Variables | `+module-variable-name` | `+org--restart-mode-h` | | Package hooks | `-module-hook-function-h` | `-org-exclude-agenda-buffers-from-workspace-h` | | Doom modules | `:module` | `:lang`, `:completion` | | Module flags | `+flag` | `+orderless`, `+lsp` | | Constants | `SCREAMING-SNAKE` | `MAX-HEIGHT` | ### Keybinding Syntax (map!) ```elisp ;; Basic keybindings (map! :n "C-s" #'consult-line) ;; With lambda (map! :n "M-s" (lambda () (interactive) (consult-line (thing-at-point 'symbol)))) ;; Leader key with prefix (map! :leader (:prefix ("C" . "claude-code") :desc "Start Claude" "c" #'claude-code :desc "Kill Claude" "k" #'claude-code-kill)) ;; State modifiers: :n (normal), :v (visual), :i (insert), :e (emacs) (map! :nv "C-j" #'something) ``` ### use-package! vs use-package - Use `use-package!` in Doom configs (macro version) - Use `use-package` when referring to the built-in package.el macro ```elisp ;; Doom configuration (use-package! org-roam :after org :custom (org-roam-directory "~/org/roam") :config (org-roam-db-autosync-mode)) ;; Standard Emacs (when needed) (use-package org :init (setq org-todo-keywords '("TODO" "DONE")) :config (org-mode 1)) ``` ### Hook Registration ```elisp ;; Anonymous function with defun inside (add-hook! 'org-agenda-finalize-hook (defun +org-exclude-agenda-buffers-from-workspace-h () "Don't associate temporary agenda buffers with current workspace." (when org-agenda-new-buffers (persp-remove-buffer org-agenda-new-buffers (get-current-persp) nil)))) ;; :local makes hook buffer-local (add-hook! :local 'some-mode-hook (defun my-local-hook () (do-something))) ``` ### Conditional Loading ```elisp ;; After package loads (preferred in Doom) (after! lsp-ui (setq lsp-ui-doc-enable t)) ;; With feature check (when (featurep! :system 'macos) (setq mac-command-modifier 'meta)) ;; After multiple packages (after! (org org-roam) (require 'org-roam-protocol)) ``` ### Defcustom vs Defvar - Use `defcustom` for user-configurable settings that belong in `custom.el` - Use `defvar` or `setq` for internal variables ```elisp ;; In packages.el (for user customization) (setq org-directory "~/org/") ;; Not a defcustom unless it belongs in custom.el ``` ### Error Handling ```elisp ;; Use quiet! to suppress output (quiet! (org-mode-restart)) ;; Use fn! or #' for function references (advice-add #'org-capture :around (lambda (fun &rest args) (letf! ((#'+org--restart-mode-h #'ignore)) (apply fun args)))) ;; Use ignore when function should do nothing (defalias '+org--restart-mode-h #'ignore) ``` ### Package Recipes (packages.el) ```elisp ;; Simple package from Melpa (package! super-save) ;; GitHub recipe with custom files (package! copilot :recipe (:host github :repo "copilot-emacs/copilot.el" :files ("*.el"))) ;; GitHub with exclusion patterns (package! eat :recipe (:host codeberg :repo "akib/emacs-eat" :files ("*.el" ("term" "term/*.el") "*.texi" "*.ti" ("terminfo/e" "terminfo/e/*") (:exclude ".dir-locals.el" "*-tests.el")))) ;; Disable a package (package! some-package :disable t) ``` ## Common Patterns ### Restart/Reload Functions ```elisp (defun +org--restart-mode-h () "Restart org-mode on buffer switch." (remove-hook 'doom-switch-buffer-hook #'+org--restart-mode-h 'local) (cl-delete (current-buffer) org-agenda-new-buffers :test 'eq) (quiet! (org-mode-restart)) (run-hooks 'find-file-hook)) ``` ### Advice Around Functions ```elisp (advice-add #'org-capture :around (lambda (fun &rest args) (letf! ((#'+org--restart-mode-h #'ignore)) (apply fun args)))) ``` ### Buffer Display Configuration ```elisp (add-to-list 'display-buffer-alist '("^\\*claude" (display-buffer-in-side-window) (side . right) (window-width . 90))) ``` ### Environment Variables ```elisp ;; Unset for subprocess access (setenv "CLAUDECODE" nil) ;; Set with path (setenv "PATH" (concat "/usr/local/bin:" (getenv "PATH"))) ``` ## Doom Module System The `init.el` uses the `doom!` macro to declare enabled modules: ```elisp (doom! :category (module +flag) :another-category) ``` Modules referenced in this config: - `:completion` — Vertico, Corfu - `:ui` — Doom dashboard, treemacs, workspaces - `:editor` — Evil, format+, multiple-cursors - `:emacs` — Dired, ibuffer, undo - `:term` — Eshell, vterm - `:checkers` — Syntax, spell - `:tools` — LSP, magit, direnv, docker - `:lang` — Python, Go, Rust, Org, JavaScript, etc. - `:config` — Default bindings, smartparens ## Important Notes 1. Run `doom sync` after ANY change to `init.el`, `packages.el`, or `config.el` 2. Use `doom build` to rebuild if byte-compilation errors occur 3. This config uses straight.el for package management (via Doom) 4. Custom variables are auto-generated to `custom.el` — don't edit manually 5. The `load!` macro loads relative to `doom-private-dir` (this config directory)