Files
org-roam-project/AGENTS.md

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:

  1. First line with description and lexical-binding: t
  2. Copyright / Author / Version / Package-Requires / Keywords / URL
  3. License boilerplate (GPL-3.0-or-later)
  4. ;;; Commentary: section
  5. ;;; Code: marker
  6. (require ...) forms
  7. ;;; Section headers (three semicolons) grouping related code
  8. (provide 'feature) at the end
  9. ;;; file.el ends here footer

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-compile unless truly needed for macros.
  • Do not use cl-lib in production code. Use only built-in Emacs Lisp primitives (let, let*, when, unless, cond, cons, car, cdr, alist-get, dolist, etc.). cl-lib is 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 let bindings, standard defun/defmacro/cond indentation).
  • 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.
  • let vs let*: Use let* only when bindings depend on earlier bindings in the same form. Use plain let otherwise.

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 :type and :group.
  • If the variable is usable in .dir-locals.el, add a safe-local-variable declaration with an ;;;###autoload cookie:
;;;###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-variable put forms

Never autoload internal (--) functions.

Error handling

Two-tier pattern:

  1. Internal functions return nil on failure — never signal errors. Let callers decide behavior.
  2. Interactive commands call org-roam-project--require-context which signals user-error (not error) 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-protect for 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 init so project.el recognizes them. Always clean up with orp-test--cleanup.
  • Use orp-test--with-project or orp-test--with-initialized-project macros to set up and tear down test fixtures.
  • Test user-error conditions with (should-error ... :type 'user-error).
  • Tests use real org-roam (not mocked) to catch compatibility issues.
  • cl-lib functions (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.