aboutsummaryrefslogtreecommitdiff
path: root/.ai/scripts/lint-org.el
diff options
context:
space:
mode:
Diffstat (limited to '.ai/scripts/lint-org.el')
-rw-r--r--.ai/scripts/lint-org.el37
1 files changed, 37 insertions, 0 deletions
diff --git a/.ai/scripts/lint-org.el b/.ai/scripts/lint-org.el
index 55727ef..47e8bf1 100644
--- a/.ai/scripts/lint-org.el
+++ b/.ai/scripts/lint-org.el
@@ -39,6 +39,7 @@
;; malformed-priority-cookie [#x]-shaped token org rejected
;; level2-done-without-closed completed level-2 task with no CLOSED
;; subtask-done-not-dated level-3+ done sub-task still a DONE keyword
+;; dated-log-heading-active-timestamp dated-log heading with a live SCHEDULED/DEADLINE
;; (anything else) surfaced as judgment with checker name
;;
;; Output format on stdout:
@@ -551,6 +552,41 @@ Emits one judgment item per offending heading (checker
"level-3+ done sub-task should be a dated event-log entry (todo-format.md): run todo-cleanup.el --convert-subtasks to rewrite it")))))
;;; ---------------------------------------------------------------------------
+;;; dated-log heading with a stale active planning timestamp (todo-format.md)
+;;
+;; The mechanical backstop for the planning-line-strip rule. A dated event-log
+;; heading (`<stars> YYYY-MM-DD Day @ ...', no TODO keyword) records completed
+;; work — its date lives in the heading. An active `<...>' SCHEDULED or DEADLINE
+;; left on it pins the entry to the agenda forever: org renders any headline with
+;; an active planning timestamp, keyword or not, so a stale SCHEDULED shows as
+;; weeks-overdue long after the work is done. Invisible to a keyword scan (no
+;; TODO) and it survives --archive-done, so nothing else catches it. The
+;; completion rewrite and todo-cleanup --convert-subtasks now strip the planning
+;; line; this flags any that slipped through before that landed, the same way
+;; subtask-done-not-dated backstops the depth rule. Judgment-only.
+
+(defun lo--check-dated-log-active-timestamp ()
+ "Flag a dated event-log heading that still carries an active SCHEDULED/DEADLINE.
+The heading matches `<stars> YYYY-MM-DD Day @ ...' with no TODO keyword; an
+active `<...>' planning timestamp in its entry is the defect. An inactive
+`[...]' timestamp is ignored (org doesn't render it on the agenda). Emits one
+judgment item per offending heading (checker `dated-log-heading-active-timestamp')."
+ (save-excursion
+ (goto-char (point-min))
+ (let ((case-fold-search nil))
+ (while (re-search-forward
+ "^\\*+ [0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [A-Za-z]+ @ " nil t)
+ (let ((hline (line-number-at-pos))
+ (entry-end (save-excursion (outline-next-heading) (point))))
+ (save-excursion
+ (forward-line 1)
+ (when (re-search-forward
+ "^[ \t]*\\(?:SCHEDULED\\|DEADLINE\\):[ \t]*<" entry-end t)
+ (lo--emit-judgment
+ 'dated-log-heading-active-timestamp hline
+ "dated-log heading carries an active SCHEDULED/DEADLINE — org renders any active planning timestamp (keyword or not), so it stays on the agenda as weeks-overdue; delete the planning line (todo-format.md)"))))))))
+
+;;; ---------------------------------------------------------------------------
;;; File processing
(defun lo--backup (file)
@@ -592,6 +628,7 @@ left unmodified and mechanical entries are recorded with :preview t."
(lo--check-malformed-priority-cookies)
(lo--check-level2-done-without-closed)
(lo--check-subtask-done-not-dated)
+ (lo--check-dated-log-active-timestamp)
(when (and (not lo-check-only) (buffer-modified-p))
(save-buffer)))
(with-current-buffer buf (set-buffer-modified-p nil))