https://github.com/cuttlefisch/or-east
Org Roam Extended Attribute Stat Tracking, OR-EAST
https://github.com/cuttlefisch/or-east
emacs-lisp emacs-mode
Last synced: about 1 month ago
JSON representation
Org Roam Extended Attribute Stat Tracking, OR-EAST
- Host: GitHub
- URL: https://github.com/cuttlefisch/or-east
- Owner: cuttlefisch
- License: gpl-3.0
- Created: 2022-12-29T02:14:39.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2026-03-16T10:08:29.000Z (3 months ago)
- Last Synced: 2026-03-16T10:52:30.654Z (3 months ago)
- Topics: emacs-lisp, emacs-mode
- Language: Emacs Lisp
- Homepage:
- Size: 32.2 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.org
- Contributing: CONTRIBUTING.org
- License: LICENSE
Awesome Lists containing this project
README
#+TITLE: or-east
*Org Roam Extended Attribute Stat Tracking*
=or-east= is an Emacs minor mode that automatically tracks usage statistics on
[[https://github.com/org-roam/org-roam][org-roam]] nodes. It records timestamps in each node's property drawer whenever
you access, modify, or link to a node — no manual bookkeeping required.
* Tracked Properties
| Property | Updated when | Hook |
|-----------------+------------------------------------------------------+-------------------------------------|
| =last-accessed= | Node is opened via =org-roam-find-file= | =org-roam-find-file-hook= |
| =last-modified= | Body content changes (detected via =buffer-hash=) | =after-save-hook= (buffer-local) |
| =last-linked= | Another node inserts a link to this node | =org-roam-post-node-insert-hook= |
* How it works
When =or-east-mode= is enabled, it installs three hooks:
1. *Access tracking*: =org-roam-find-file-hook= runs
=or-east-node-update-access-time-by-id=, which sets =last-accessed= to the
current timestamp whenever you open a node.
2. *Modification tracking*: The same hook also runs
=or-east--setup-modified-tracking=, which adds =or-east-node-update-stats= to
the buffer-local =after-save-hook=. On each save, or-east computes a
=buffer-hash= of the file content from the =#+title= keyword onward and
compares it to a stored =hash= property. If they differ, both =hash= and
=last-modified= are updated. This means property-drawer-only changes (like
the timestamps themselves) do not trigger a modification.
3. *Link tracking*: =org-roam-post-node-insert-hook= runs
=or-east-node-update-link-time-by-id= on the /target/ node whenever a link
is inserted from another node.
All saves performed by or-east use an inhibit flag to prevent recursive
=after-save-hook= triggers.
* Installation
** straight.el / Doom Emacs
#+begin_src elisp
;; In packages.el (Doom)
(package! or-east :recipe (:host github :repo "cuttlefisch/or-east"))
;; In config.el
(use-package! or-east-mode
:hook (org-roam-find-file . or-east-mode))
#+end_src
** use-package + straight
#+begin_src elisp
(use-package or-east-mode
:straight (:host github :repo "cuttlefisch/or-east")
:hook (org-roam-find-file . or-east-mode))
#+end_src
** Manual
Clone the repository, add it to your =load-path=, and require it:
#+begin_src elisp
(add-to-list 'load-path "/path/to/or-east")
(require 'or-east-mode)
(add-hook 'org-roam-find-file-hook #'or-east-mode)
#+end_src
* Usage
Once =or-east-mode= is active in an org-roam buffer, the three properties update
transparently during normal use. You can also call functions directly:
- =or-east-enable= / =or-east-disable= — activate or deactivate the mode
- =or-east-node-update-stats= — manually trigger a stat update (=M-x=)
- =or-east-normalize-timestamps= — batch-rewrite all node timestamps to the current format
* Configuration
| Variable | Default | Description |
|-----------------------------------------+---------+------------------------------------|
| =or-east-node-stat-format-time-string= | ="%Y-%m-%d"= | Format string for timestamps (passed to =format-time-string=) |
| =or-east-activity-weights= | see below | Weights for each tracked property in the activity score |
| =or-east-activity-decay-rate= | =0.01= | How quickly scores decay with age (higher = faster decay) |
The default format ="%Y-%m-%d"= produces ISO 8601 dates like =2026-03-21=.
Change it to suit your preference:
#+begin_src elisp
(setq or-east-node-stat-format-time-string "%Y-%m-%d %H:%M")
#+end_src
* Activity-based search prioritization
=or-east= can sort =org-roam-node-find= results by activity, so recently
modified/accessed/linked nodes appear first. Each tracked property
contributes a weighted, time-decayed score:
#+begin_src elisp
;; Default weights — modify contributes most, linking least
(setq or-east-activity-weights
'((last-accessed . 1.0)
(last-modified . 2.0)
(last-linked . 0.5)))
;; Decay rate controls how fast old activity fades
;; 0.01 = gradual (50% at ~100 days), 0.05 = aggressive (50% at ~20 days)
(setq or-east-activity-decay-rate 0.01)
#+end_src
To enable activity sorting as the default for =org-roam-node-find=:
#+begin_src elisp
(setq org-roam-node-default-sort 'activity)
#+end_src
This works because =or-east= registers =org-roam-node-read-sort-by-activity=
following org-roam's naming convention. You can also pass the sort
function directly:
#+begin_src elisp
(org-roam-node-find nil nil #'or-east-node-sort-by-activity)
#+end_src
** Freshness-weighted search
Use activity sorting as a tiebreaker so that equally-matched search results
are ranked by recency:
#+begin_src elisp
;; Make org-roam-node-find default to activity-based ordering
(setq org-roam-node-default-sort 'activity)
#+end_src
With this setting, when you type a query that matches several nodes equally
well, the ones you've touched most recently float to the top.
** Custom weight profiles
Tailor the weights to your workflow. For example, a research-heavy config
that prioritizes discovery through links:
#+begin_src elisp
;; Research mode: heavily weight links for discovery
(setq or-east-activity-weights
'((last-accessed . 0.5)
(last-modified . 1.0)
(last-linked . 3.0)))
#+end_src
Or a daily-driver config that favors nodes you open frequently:
#+begin_src elisp
;; Daily driver: favor recently accessed nodes
(setq or-east-activity-weights
'((last-accessed . 3.0)
(last-modified . 1.0)
(last-linked . 0.5)))
#+end_src
** Agenda/dashboard integration
Query nodes by activity score for a "recently active nodes" dashboard:
#+begin_src elisp
(defun my/or-east-top-nodes (&optional n)
"Return the top N most active org-roam nodes (default 10)."
(let* ((n (or n 10))
(nodes (org-roam-node-list))
(scored (sort nodes
(lambda (a b)
(> (or-east-node-activity-score a)
(or-east-node-activity-score b))))))
(seq-take scored n)))
;; Example: print titles of your 5 most active nodes
(dolist (node (my/or-east-top-nodes 5))
(message "%s (score: %.2f)"
(org-roam-node-title node)
(or-east-node-activity-score node)))
#+end_src
* Development
#+begin_src shell
cask install
make test
#+end_src
See [[file:CONTRIBUTING.org][CONTRIBUTING.org]] for full development guidelines.
* Changelog
See [[file:CHANGELOG.org][CHANGELOG.org]] for a list of notable changes.
* License
[[file:LICENSE][GNU General Public License v3.0]]