diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-14 08:25:19 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-14 08:25:19 -0500 |
| commit | 686d6471191888d05bcdd1f194a9d5dd5ee65d7a (patch) | |
| tree | cc448527b706ff3ec66450b0d41bc57e27a69ce6 /.ai/scripts | |
| parent | db87d0793a04b88fa1347ca5259f537aa8838a79 (diff) | |
| download | rulesets-686d6471191888d05bcdd1f194a9d5dd5ee65d7a.tar.gz rulesets-686d6471191888d05bcdd1f194a9d5dd5ee65d7a.zip | |
fix(todo-cleanup): read priority via org-heading-components, not regex
The first take walked the heading line with a plain `\[#\([A-Z]\)\]` regex, which matched any cookie-shaped substring anywhere in the line. Dated-log headings can carry that shape inside the title — e.g. "Reprioritized children =[#D]= → =[#B]= to match parent" quotes earlier and new priorities verbatim, and the script read =[#D]= as if it were the heading's own priority cookie.
`(nth 3 (org-heading-components))` only returns a priority when the cookie sits in canonical position (right after the stars or the optional TODO keyword), which is the only place org itself recognizes it. That's the right primitive here.
Surfaced via smoke-testing --check-child-priority against ~/projects/work/todo.org: 10 candidates dropped to 9 once the dated-log heading inside the Whisper parent stopped getting flagged. New ert test tc-sync-ignores-cookie-shaped-text-in-title covers the case directly.
Diffstat (limited to '.ai/scripts')
| -rw-r--r-- | .ai/scripts/tests/test-todo-cleanup.el | 24 | ||||
| -rw-r--r-- | .ai/scripts/todo-cleanup.el | 12 |
2 files changed, 31 insertions, 5 deletions
diff --git a/.ai/scripts/tests/test-todo-cleanup.el b/.ai/scripts/tests/test-todo-cleanup.el index 2a9184d..5d43f97 100644 --- a/.ai/scripts/tests/test-todo-cleanup.el +++ b/.ai/scripts/tests/test-todo-cleanup.el @@ -417,6 +417,16 @@ in ISSUES, in document order." *** TODO [#B] Child equal ") +;; A dated-log heading inside a parent task whose title quotes other priorities +;; in =[#X]= verbatim. Those quoted cookies must NOT be read as the heading's +;; own priority — the cookie has to sit in canonical position to count. +(defconst tc-test--sync-cookie-in-title "\ +* Open Work +** TODO [#B] Parent +*** 2026-05-14 Reprioritized children =[#D]= → =[#B]= to match parent +*** TODO [#D] Regular drifted child +") + ;;; --------------------------------------------------------------------------- ;;; Sync-child-priority tests @@ -466,13 +476,25 @@ in ISSUES, in document order." (should (= 0 (plist-get out :bumped))) (should (equal tc-test--sync-no-change (plist-get out :result))))) +(ert-deftest tc-sync-ignores-cookie-shaped-text-in-title () + (let* ((out (tc-test--sync tc-test--sync-cookie-in-title)) + (res (plist-get out :result))) + ;; Only the real drifted child bumps; the dated-log heading with + ;; =[#D]= / =[#B]= verbatim text in its title is untouched. + (should (= 1 (plist-get out :bumped))) + (should (equal "B" (tc-test--priority-of res "Regular drifted child"))) + ;; Substring still appears in the dated-log heading; the heading itself + ;; was not rewritten. + (should (string-match-p "Reprioritized children =\\[#D\\]= → =\\[#B\\]= to match parent" res)))) + (ert-deftest tc-sync-is-idempotent () (dolist (fixture (list tc-test--sync-basic tc-test--sync-multi tc-test--sync-no-sync-tag tc-test--sync-priority-less-parent tc-test--sync-cascade - tc-test--sync-no-change)) + tc-test--sync-no-change + tc-test--sync-cookie-in-title)) (let ((once (plist-get (tc-test--sync fixture 1) :result)) (twice (plist-get (tc-test--sync fixture 2) :result))) (should (equal once twice))))) diff --git a/.ai/scripts/todo-cleanup.el b/.ai/scripts/todo-cleanup.el index 97be96e..11851b2 100644 --- a/.ai/scripts/todo-cleanup.el +++ b/.ai/scripts/todo-cleanup.el @@ -254,12 +254,16 @@ are reported but not performed." (defun tc--heading-priority-letter () "Return the priority letter (a character) on the heading at point, or nil -if the heading has no priority cookie." +if the heading has no priority cookie in canonical position. + +Uses `org-heading-components' rather than regexing the whole line, because +the cookie must sit right after the stars or the optional TODO keyword — +otherwise `[#X]'-shaped text inside the title (a dated log entry like +\"... reprioritized =[#D]= → =[#B]= to match parent\") gets misread as a +real cookie." (save-excursion (org-back-to-heading t) - (let ((eol (line-end-position))) - (when (re-search-forward tc--priority-cookie-regexp eol t) - (string-to-char (match-string 1)))))) + (nth 3 (org-heading-components)))) (defun tc--priority-lower-p (child parent) "Non-nil when CHILD priority letter ranks lower than PARENT — i.e. later in |
