From d9d8ce79da82b8c0fbbc8d6090548cd1f508b4c0 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Tue, 30 Jun 2026 13:50:22 -0400 Subject: feat(lint-org): add four structural heading checkers org-lint misses org-lint validates links, drawers, blocks, and babel, but not heading well-formedness. These four catch hand-edit defects it stays silent on: an indented heading demoted to body text (the task vanishes from the agenda and never archives), bare stars with no title, a malformed priority cookie org rejected, and a level-2 DONE/CANCELLED with no CLOSED line. All judgment-only and regex-based, wired in after the existing dated-header check. The last one pairs with the new aging step, which archives an undated completed task immediately. I tightened the indented-heading check to two-or-more stars. The proposed one-or-more-stars regex flagged indented single-star lines, but an indented single * is a valid plain-list bullet, not a lost heading, so it false-positived on legitimate lists (confirmed: three valid bullets flagged). A ** is never a bullet, so an indented one is unambiguously a demoted heading. Added a test that a single-star list stays silent. --- ...06-29-lint-org-structural-checkers-proposal.org | 55 ++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 docs/design/2026-06-29-lint-org-structural-checkers-proposal.org (limited to 'docs') diff --git a/docs/design/2026-06-29-lint-org-structural-checkers-proposal.org b/docs/design/2026-06-29-lint-org-structural-checkers-proposal.org new file mode 100644 index 0000000..c464aca --- /dev/null +++ b/docs/design/2026-06-29-lint-org-structural-checkers-proposal.org @@ -0,0 +1,55 @@ +#+TITLE: lint-org.el — four structural heading checkers org-lint doesn't cover + +* What changed (from .emacs.d, 2026-06-29) + +Added four custom judgment checkers to =lint-org.el=, following the existing +=lo--check-tables= / =lo--check-level2-dated-headers= pattern (custom scans run +after the org-lint pass, emitting judgment items, never auto-fixed): + +- =indented-heading= — a line of whitespace + stars + space OUTSIDE any block. + org parses a heading only at column 0, so leading whitespace silently demotes + a would-be heading to body text: the task vanishes from the agenda and never + archives. The worst defect class (an invisible task) and entirely silent + today. Skips indented stars inside =#+begin_/#+end_= blocks (legit content). +- =empty-heading= — a line of bare stars with no title. +- =malformed-priority-cookie= — a =[#x]=-shaped token org rejected (lowercase, + multi-char, non-letter) left stranded where a cookie would be. Checks only the + first cookie token per heading; skips verbatim-wrapped =[#D]= in dated-log + titles. +- =level2-done-without-closed= — a level-2 DONE/CANCELLED with no CLOSED line. + Directly supports the todo-cleanup aging step (sent separately today): an + undated completed task gets force-archived immediately, so flagging it lets + the human add CLOSED first. + +Two attached files (edited canonical candidates): =lint-org.el=, +=tests/test-lint-org.el=. + +* Why + +org-lint validates links, drawers, blocks, and babel — but NOT heading +well-formedness. On Craig's .emacs.d todo.org a missing org-bullet in the live +buffer prompted the question "is the file structurally okay?", and org-lint +(even unfiltered, all checkers) reported nothing actionable. These four close +the gap. They are general (any org file), not project-specific. + +* Design notes for the canonical + +- All four are regex-based, NOT org-element/keyword-based, so they don't depend + on which TODO keywords the batch Emacs happens to recognize (lint-org.el does + not set =org-todo-keywords=). The =level2-done-without-closed= done set is a + defconst =lo-done-keywords= (DONE/CANCELLED) for easy extension. +- *Gotcha worth carrying in the canonical:* =case-fold-search= defaults to t, so + a naive =[A-Z]= cookie check accepts =[#a]= as valid and =\(DONE\|CANCELLED\)= + matches the title words "done"/"cancelled". Both letter-sensitive checkers + bind =case-fold-search nil=. (Caught by a failing test before it shipped.) +- Wired into =lo-process-file= after =lo--check-level2-dated-headers=. Judgment + output already flows through the existing report + followups-file machinery. +- 8 new ERT tests (good-input-silent + bad-input-flagged for each, plus + block-skip and verbatim-skip boundary cases). 44/44 green. Zero false + positives on a real 5600-line todo.org. + +* Note + +=make task-sorted= in .emacs.d now runs =lint-org.el todo.org= after the +archive, so these checkers also gate the task-hygiene target. Makefiles aren't +template-synced; that wiring is project-local (noted for context). -- cgit v1.2.3