diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-06 21:59:52 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-06 21:59:52 -0500 |
| commit | d81b23ad6b6e437dfe3c338a00a4be39bc555146 (patch) | |
| tree | 2d4b0d7890fd1fc70d81282b81fed2808c28a106 /.ai/scripts/daily-prep-agenda.el | |
| parent | 201377f57430ef28d02e703a2191434bbee55c75 (diff) | |
| download | rulesets-d81b23ad6b6e437dfe3c338a00a4be39bc555146.tar.gz rulesets-d81b23ad6b6e437dfe3c338a00a4be39bc555146.zip | |
chore(ai): initialize project notes and Claude tooling surfaces
Replace the seed notes.org with project-specific context (layout, install modes, task tracker location, recent inflection point). Bring in the synced template surfaces (protocols, workflows, scripts, references, retrospectives, someday-maybe) as tracked content for this content/documentation project.
Diffstat (limited to '.ai/scripts/daily-prep-agenda.el')
| -rw-r--r-- | .ai/scripts/daily-prep-agenda.el | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/.ai/scripts/daily-prep-agenda.el b/.ai/scripts/daily-prep-agenda.el new file mode 100644 index 0000000..4c6041c --- /dev/null +++ b/.ai/scripts/daily-prep-agenda.el @@ -0,0 +1,142 @@ +;;; daily-prep-agenda.el --- Standalone batch agenda extractor for daily-prep +;; +;; Usage: +;; emacs --batch -q -l daily-prep-agenda.el todo.org [pcal.org ...] +;; +;; Filters entries to TODO/DOING/WAITING/NEXT with [#A]/[#B] priority OR +;; DEADLINE/SCHEDULED present. Bucketizes into Overdue, Today, This Week, +;; Priority A (no date), Priority B (no date). Emits heading + body for each. + +(require 'org) +(require 'cl-lib) + +;; Declare the TODO keywords used across Craig's org files so org-mode parses +;; "DOING", "WAITING", "NEXT", "CANCELLED" headings as TODO states. With `-q`, +;; org-mode defaults to just "TODO"/"DONE" and will treat the others as plain +;; heading text (state comes back as nil). +(setq org-todo-keywords + '((sequence "TODO" "DOING" "WAITING" "NEXT" "|" "DONE" "CANCELLED"))) + +(defvar dp-today (format-time-string "%Y-%m-%d")) +(defvar dp-week-end + (format-time-string "%Y-%m-%d" (time-add (current-time) (days-to-time 7)))) + +(defun dp-iso-date (org-ts) + "Extract YYYY-MM-DD from an org timestamp string like '<2026-04-25 Sat 16:00>'." + (when (and org-ts (string-match "\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)" org-ts)) + (match-string 1 org-ts))) + +(defun dp-entry-info () + "Return plist of metadata + body for org entry at point." + (let* ((state (org-get-todo-state)) + (el (org-element-at-point)) + (priority (org-element-property :priority el)) + (deadline-raw (org-entry-get (point) "DEADLINE")) + (scheduled-raw (org-entry-get (point) "SCHEDULED")) + (deadline (dp-iso-date deadline-raw)) + (scheduled (dp-iso-date scheduled-raw)) + (heading (org-get-heading t t t t)) + (line (line-number-at-pos)) + (file (or (buffer-file-name) (buffer-name))) + (start (save-excursion (org-end-of-meta-data t) (point))) + (end (save-excursion + (or (outline-next-heading) (goto-char (point-max))) + (point))) + (body (and (< start end) + (string-trim (buffer-substring-no-properties start end))))) + (list :state state + :priority priority + :deadline deadline + :deadline-raw deadline-raw + :scheduled scheduled + :scheduled-raw scheduled-raw + :heading heading + :line line + :file file + :body body))) + +(defun dp-active-candidate-p () + "True if entry at point is an active state with [#A]/[#B] OR has DEADLINE/SCHEDULED." + (let* ((state (org-get-todo-state)) + (el (org-element-at-point)) + (pri (org-element-property :priority el)) + (dl (org-entry-get (point) "DEADLINE")) + (sc (org-entry-get (point) "SCHEDULED"))) + (and (member state '("TODO" "DOING" "WAITING" "NEXT")) + (or (memq pri '(?A ?B)) dl sc)))) + +(defun dp-collect (files) + "Walk FILES, return list of dp-entry-info plists for matching entries." + (let (entries) + (dolist (file files) + (when (file-readable-p file) + (with-current-buffer (find-file-noselect file) + (org-mode) + (org-map-entries + (lambda () + (when (dp-active-candidate-p) + (push (dp-entry-info) entries))) + nil 'file)))) + (nreverse entries))) + +(defun dp-bucket (e) + "Return bucket name for entry plist E." + (let ((dl (plist-get e :deadline)) + (sc (plist-get e :scheduled)) + (pri (plist-get e :priority))) + (cond + ((and dl (string< dl dp-today)) 'overdue) + ((or (equal dl dp-today) (equal sc dp-today)) 'today) + ((and sc (string< sc dp-today)) 'overdue) + ((or (and dl (string< dl dp-week-end)) + (and sc (string< sc dp-week-end))) 'this-week) + ((eq pri ?A) 'pri-a) + ((eq pri ?B) 'pri-b) + (t 'other)))) + +(defun dp-format-entry (e) + "Format entry plist E as org-mode text." + (concat + (format "** %s%s %s\n" + (or (plist-get e :state) "") + (if-let ((p (plist-get e :priority))) (format " [#%c]" p) "") + (plist-get e :heading)) + (format " :LOC: %s:%d\n" + (file-name-nondirectory (plist-get e :file)) + (plist-get e :line)) + (when-let ((d (plist-get e :deadline-raw))) (format " DEADLINE: %s\n" d)) + (when-let ((s (plist-get e :scheduled-raw))) (format " SCHEDULED: %s\n" s)) + (let ((b (plist-get e :body))) + (if (and b (not (string-empty-p b))) + (concat (replace-regexp-in-string "^" " " b) "\n") + "")) + "\n")) + +(defun dp-emit-bucket (label entries) + (when entries + (princ (format "* %s (%d)\n\n" label (length entries))) + (dolist (e entries) + (princ (dp-format-entry e))))) + +;; Main entrypoint +(when noninteractive + (let* ((files command-line-args-left) + (entries (dp-collect files)) + (groups (seq-group-by #'dp-bucket entries))) + (princ (format "# Daily-Prep Extract — %s\n# Files: %s\n# Total candidates: %d\n\n" + dp-today + (mapconcat #'file-name-nondirectory files ", ") + (length entries))) + (dolist (bucket '(overdue today this-week pri-a pri-b other)) + (dp-emit-bucket + (pcase bucket + ('overdue "Overdue") + ('today "Today") + ('this-week "This Week") + ('pri-a "Priority A (undated)") + ('pri-b "Priority B (undated)") + ('other "Other")) + (alist-get bucket groups))))) + +(provide 'daily-prep-agenda) +;;; daily-prep-agenda.el ends here |
