diff options
| author | Craig Jennings <c@cjennings.net> | 2026-07-18 21:03:50 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-07-18 21:03:50 -0500 |
| commit | ddbd47fbbea06e71456b7bc4228626d3a126bf28 (patch) | |
| tree | 06211963ee5fbb85b8c9eb0d4f2b860e5c74dfa1 /.ai/scripts/lint-org.el | |
| parent | d2b1bef686fd6c22a6303c13829f88bdd110fa7b (diff) | |
| download | rulesets-ddbd47fbbea06e71456b7bc4228626d3a126bf28.tar.gz rulesets-ddbd47fbbea06e71456b7bc4228626d3a126bf28.zip | |
feat(todo-cleanup): dated-seal archiving and planning-line strip on convert
Two batched changes to the todo hygiene tooling.
Dated-seal archiving: --archive-done aging now keeps one month in-file (retain default 7 → 31, named as tc-archive-retain-days-default) and a new --seal renames the working task-archive.org to resolved-YYYY-MM-DD.org beside it, letting the next aging start a fresh working file. Dating the sealed file by the seal run rather than the calendar quarter is what avoids mislabeling a task closed late in a quarter and archived after the boundary. The unparseable-CLOSED case is now explicit in the contract: a keyword-complete task with no readable close date ages out too. The sealed file inherits the todo file's gitignore status.
Planning-line strip: --convert-subtasks now drops the whole planning line when it rewrites a done sub-task to a dated event-log entry, not just the CLOSED cookie. A dated-log heading carries its date in the heading, so an active SCHEDULED/DEADLINE left on it pins the finished entry to the agenda as weeks-overdue forever (org renders any active planning timestamp, keyword or not). New lint-org checker dated-log-heading-active-timestamp backstops any that predate this. todo-format.md's sub-task and VERIFY completion rules gain the strip step.
Diffstat (limited to '.ai/scripts/lint-org.el')
| -rw-r--r-- | .ai/scripts/lint-org.el | 37 |
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)) |
