6.4 KiB
AGENTS.md — org-roam-project
Guidance for AI coding agents working in this repository.
Project overview
org-roam-project is an Emacs Lisp package that extends
org-roam with per-project databases using
Emacs' built-in project.el. Each project gets its own isolated notes
directory and SQLite database. The global zettelkasten is never touched.
Language: Emacs Lisp
Dependencies: Emacs >= 28.1, org-roam >= 2.2.0
License: GPL-3.0-or-later
Repository layout
org-roam-project.el Main package source (~355 lines)
org-roam-project-test.el ERT test suite (~430 lines)
Makefile Build and test runner
flake.nix / shell.nix Nix dev environment
Build commands
make all # Byte-compile + run tests
make compile # Byte-compile org-roam-project.el
make test # Run the full ERT test suite in batch mode
make clean # Remove .elc files
Running a single test
emacs --batch \
-l ert \
-l org-roam \
--eval "(add-to-list 'load-path \".\")" \
-l org-roam-project \
-l org-roam-project-test \
--eval "(ert-run-tests-batch-and-exit '\"orp-test-NAME\")"
Replace orp-test-NAME with the test name (e.g. orp-test-default-notes-subdir).
The string is an ERT selector regexp matching test names.
Dev environment
The project uses Nix flakes. Run nix develop (or let direnv activate
via .envrc) to get an Emacs with org-roam and all dev tools on $PATH.
Code style guidelines
Lexical binding
Every .el file MUST have lexical-binding: t in the first line:
;;; file.el --- Description -*- lexical-binding: t; -*-
File structure
Follow this order in every source file:
- First line with description and
lexical-binding: t - Copyright / Author / Version / Package-Requires / Keywords / URL
- License boilerplate (GPL-3.0-or-later)
;;; Commentary:section;;; Code:marker(require ...)forms;;; Sectionheaders (three semicolons) grouping related code(provide 'feature)at the end;;; file.el ends herefooter
Namespace prefix
All symbols use the org-roam-project prefix:
- Public:
org-roam-project-<name>(single dash) - Private:
org-roam-project--<name>(double dash) - Test public:
orp-test-<name> - Test private:
orp-test--<name>
Never introduce unprefixed top-level symbols.
Requires / imports
- Place all
(require ...)forms at the top of;;; Code:. - Only hard runtime dependencies. No
eval-when-compileunless truly needed for macros. - Do not use
cl-libin production code. Use only built-in Emacs Lisp primitives (let,let*,when,unless,cond,cons,car,cdr,alist-get,dolist, etc.).cl-libis acceptable in the test file only.
Formatting
- Line length: ~80 columns. String literals may slightly exceed this.
- Indentation: Standard Emacs Lisp indentation (2-space aligned
letbindings, standarddefun/defmacro/condindentation). - Parentheses: Closing parens always on the same line as the last form — never on their own line.
- Blank lines: One blank line between top-level forms. Section
headers (
;;;) get blank lines before and after. - Function references: Always use sharp-quote:
#'function-name. letvslet*: Uselet*only when bindings depend on earlier bindings in the same form. Use plainletotherwise.
Docstrings
Every public and private function, macro, and variable must have a docstring.
- First line: complete imperative sentence ("Return the context…").
- Arguments referenced in UPPER CASE:
"DIR defaults to…". - Cross-reference other symbols with backtick-quote:
`org-roam-project-init'. - Document return values and nil-return conditions explicitly.
Inline comments
- Use
;;(two semicolons) on a line by itself above the code. - Section headers use
;;;(three semicolons). - Avoid end-of-line comments in production code (acceptable in tests for brief annotations).
Defcustom conventions
(defcustom org-roam-project-XXXX default-value
"Docstring."
:type 'TYPE
:group 'org-roam-project)
- Always specify
:typeand:group. - If the variable is usable in
.dir-locals.el, add asafe-local-variabledeclaration with an;;;###autoloadcookie:
;;;###autoload
(put 'org-roam-project-XXXX 'safe-local-variable #'stringp)
Autoloads
Place ;;;###autoload on:
- All interactive commands
- The minor mode definition (
define-minor-mode) safe-local-variableputforms
Never autoload internal (--) functions.
Error handling
Two-tier pattern:
- Internal functions return
nilon failure — never signal errors. Let callers decide behavior. - Interactive commands call
org-roam-project--require-contextwhich signalsuser-error(noterror) with actionable messages.
Always use user-error for user-facing errors, never bare error.
Macros
- Always declare
(declare (indent 0) (debug t))as the first form. - Accept
&rest body, splice with,@body. - Use
unwind-protectfor cleanup in test macros.
Dynamic scoping pattern
The core architectural pattern: temporarily rebind org-roam-directory
and org-roam-db-location via let — never mutate them with setq.
(let ((org-roam-directory (car ctx))
(org-roam-db-location (cdr ctx)))
(org-roam-node-find))
Testing conventions
- Framework: ERT (Emacs Regression Testing), built-in.
- Test file:
org-roam-project-test.el - Test names:
orp-test-<descriptive-name>(no double dash). - Helpers/macros:
orp-test--<name>(double dash). - Tests create real temporary Git repos via
git initsoproject.elrecognizes them. Always clean up withorp-test--cleanup. - Use
orp-test--with-projectororp-test--with-initialized-projectmacros to set up and tear down test fixtures. - Test
user-errorconditions with(should-error ... :type 'user-error). - Tests use real org-roam (not mocked) to catch compatibility issues.
cl-libfunctions (cl-remove-if,cl-find,cl-count, etc.) are allowed in tests.
Section headers in test file
Tests are numbered in groups with decorative Unicode headers:
;;; ─── 1. Customisation defaults ───────────────
Maintain this grouping style when adding new test sections.