diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-30 17:20:24 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-30 17:20:24 -0500 |
| commit | 1f79945dd4ab386a9ea4ac58fb2161c174a26bba (patch) | |
| tree | 0344827c0793c636a6f1e3f2e3ebc88724ab4f89 | |
| parent | 4b24597eee171ccbc5f5fd7067fdd52b87931986 (diff) | |
| download | rulesets-1f79945dd4ab386a9ea4ac58fb2161c174a26bba.tar.gz rulesets-1f79945dd4ab386a9ea4ac58fb2161c174a26bba.zip | |
fix(lint-org): suppress verbatim-asterisk misplaced-heading false positives
org-lint reads an =** Foo= verbatim span in body prose as a possible misplaced heading, but verbatim markup is never a real heading. lint-org kept surfacing these as judgment items, so they recurred in lint-followups.org on every wrap and could never be acted on, since the todo.org content was already correct.
I added lo--verbatim-asterisk-at-line-p, which mirrors the markdown-bold detector: it checks the reported line and the one before it, since org-lint marks the blank line after the offender. A match is now suppressed silently, the same way the cj-comment false positives already are. I flipped the two tests that pinned the old judgment behavior, and confirmed todo.org lints clean (judgment=0). This resolves the checker-bug report I filed in the inbox earlier, which I removed.
| -rw-r--r-- | .ai/scripts/lint-org.el | 39 | ||||
| -rw-r--r-- | .ai/scripts/tests/test-lint-org.el | 13 | ||||
| -rw-r--r-- | claude-templates/.ai/scripts/lint-org.el | 39 | ||||
| -rw-r--r-- | claude-templates/.ai/scripts/tests/test-lint-org.el | 13 | ||||
| -rw-r--r-- | inbox/2026-05-30-lint-org-verbatim-asterisk-false-positive.org | 25 |
5 files changed, 80 insertions, 49 deletions
diff --git a/.ai/scripts/lint-org.el b/.ai/scripts/lint-org.el index 64b78d0..2e97db0 100644 --- a/.ai/scripts/lint-org.el +++ b/.ai/scripts/lint-org.el @@ -19,8 +19,13 @@ ;; misplaced-planning-info merge multi-line CLOSED:/DEADLINE:/SCHEDULED: ;; misplaced-heading (markdown-bold) **X.** at start of line → *X.* ;; +;; Suppressed (false positives — neither fixed nor surfaced): +;; misplaced-heading (verbatim-*) =*** Foo= inside body prose — verbatim +;; asterisks are never a real misplaced +;; heading, so the item is dropped silently +;; cj-comment src-block false flags see `lo--cj-comment-block-opener-p' +;; ;; Judgment categories (emitted on stdout): -;; misplaced-heading (verbatim-*) =*** Foo= inside body prose ;; link-to-local-file broken file: links ;; invalid-fuzzy-link broken *Heading refs ;; suspicious-language-in-src-block unknown source-block language @@ -56,8 +61,9 @@ path as an org section dated today. The file is created if missing.") '(item-number missing-language-in-src-block misplaced-planning-info) "org-lint checker names that are always treated as mechanical.") -;; misplaced-heading is split case-by-case (markdown-bold vs verbatim-asterisk) -;; in `lo--handle-item'. +;; misplaced-heading is split case-by-case in `lo--handle-item': markdown-bold +;; is auto-fixed, verbatim-asterisk is suppressed as a false positive, anything +;; else is surfaced as judgment. ;;; --------------------------------------------------------------------------- ;;; org-lint result accessors @@ -182,6 +188,21 @@ misplaced-heading. Pattern: `**X**` at the start of the line, X a short prose run without asterisks." (and (lo--find-markdown-bold-line line) t)) +(defun lo--verbatim-asterisk-at-line-p (line) + "Non-nil if LINE (or LINE - 1) carries an =...=-wrapped run of heading +asterisks inside body prose, e.g. =** DONE= or =*** Foo=. org-lint reads the +verbatim asterisks as a possible heading and flags the line, but verbatim +markup is never a real misplaced heading. Like `lo--find-markdown-bold-line', +this checks LINE - 1 too, since org-lint often marks the blank line after the +offender. The match is anywhere on the line (the span sits mid-sentence)." + (save-excursion + (cl-loop for candidate in (list (1- line) line) + when (and (>= candidate 1) + (progn (lo--goto-line candidate) + (re-search-forward "=\\*+ [^=\n]*=" + (line-end-position) t))) + return candidate))) + (defun lo-fix-markdown-bold (line) "Convert a leading `**X**` near LINE to `*X*` (org single-asterisk bold). Uses `lo--find-markdown-bold-line' to locate the actual offender, since @@ -251,9 +272,15 @@ Craig-specific annotation marker rather than Babel src-block syntax." ((eq name 'misplaced-planning-info) (lo--apply-or-preview name line msg #'lo-fix-misplaced-planning)) ((eq name 'misplaced-heading) - (if (lo--markdown-bold-at-line-p line) - (lo--apply-or-preview name line msg #'lo-fix-markdown-bold) - (lo--emit-judgment name line msg))) + (cond + ((lo--markdown-bold-at-line-p line) + (lo--apply-or-preview name line msg #'lo-fix-markdown-bold)) + ;; Verbatim =** Foo= inside prose is never a real misplaced heading; + ;; suppress silently like the cj-comment case — no fix, no judgment. + ((lo--verbatim-asterisk-at-line-p line) + nil) + (t + (lo--emit-judgment name line msg)))) (t (lo--emit-judgment name line msg))))) diff --git a/.ai/scripts/tests/test-lint-org.el b/.ai/scripts/tests/test-lint-org.el index 9328064..416f4f6 100644 --- a/.ai/scripts/tests/test-lint-org.el +++ b/.ai/scripts/tests/test-lint-org.el @@ -303,15 +303,16 @@ content (twice (plist-get (lo-test--run lo-test--md-bold 2) :result))) (should (equal once twice)))) -(ert-deftest lo-verbatim-asterisk-is-judgment () +(ert-deftest lo-verbatim-asterisk-is-suppressed () (let* ((out (lo-test--run lo-test--verbatim-asterisk)) (res (plist-get out :result)) (judgments (lo-test--judgments (plist-get out :issues)))) ;; File untouched. (should (equal lo-test--verbatim-asterisk res)) (should (= 0 (plist-get out :fixes))) - ;; Emitted as judgment with the misplaced-heading checker. - (should (member 'misplaced-heading (lo-test--checkers judgments))))) + ;; Verbatim =*** Foo= inside prose is never a real misplaced heading, so it + ;; is suppressed — no judgment emitted (compare the cj-comment suppression). + (should-not (member 'misplaced-heading (lo-test--checkers judgments))))) ;;; --------------------------------------------------------------------------- ;;; Judgment-category emission tests @@ -407,12 +408,12 @@ suspicious-language judgment." (should (string-match-p "CLOSED: \\[2026-05-14\\][^\n]*DEADLINE: <2026-05-20" res)) - ;; Judgment: every flagged broken link, suspicious-language, verbatim-asterisk - ;; emitted untouched. + ;; Judgment: every flagged broken link and suspicious-language emitted untouched. (should (member 'link-to-local-file judgment-checkers)) (should (member 'invalid-fuzzy-link judgment-checkers)) (should (member 'suspicious-language-in-src-block judgment-checkers)) - (should (member 'misplaced-heading judgment-checkers)) + ;; The verbatim-asterisk misplaced-heading is suppressed, not surfaced. + (should-not (member 'misplaced-heading judgment-checkers)) ;; Verbatim-asterisk untouched in the file. (should (lo-test--has res "=*** Foo=")))) diff --git a/claude-templates/.ai/scripts/lint-org.el b/claude-templates/.ai/scripts/lint-org.el index 64b78d0..2e97db0 100644 --- a/claude-templates/.ai/scripts/lint-org.el +++ b/claude-templates/.ai/scripts/lint-org.el @@ -19,8 +19,13 @@ ;; misplaced-planning-info merge multi-line CLOSED:/DEADLINE:/SCHEDULED: ;; misplaced-heading (markdown-bold) **X.** at start of line → *X.* ;; +;; Suppressed (false positives — neither fixed nor surfaced): +;; misplaced-heading (verbatim-*) =*** Foo= inside body prose — verbatim +;; asterisks are never a real misplaced +;; heading, so the item is dropped silently +;; cj-comment src-block false flags see `lo--cj-comment-block-opener-p' +;; ;; Judgment categories (emitted on stdout): -;; misplaced-heading (verbatim-*) =*** Foo= inside body prose ;; link-to-local-file broken file: links ;; invalid-fuzzy-link broken *Heading refs ;; suspicious-language-in-src-block unknown source-block language @@ -56,8 +61,9 @@ path as an org section dated today. The file is created if missing.") '(item-number missing-language-in-src-block misplaced-planning-info) "org-lint checker names that are always treated as mechanical.") -;; misplaced-heading is split case-by-case (markdown-bold vs verbatim-asterisk) -;; in `lo--handle-item'. +;; misplaced-heading is split case-by-case in `lo--handle-item': markdown-bold +;; is auto-fixed, verbatim-asterisk is suppressed as a false positive, anything +;; else is surfaced as judgment. ;;; --------------------------------------------------------------------------- ;;; org-lint result accessors @@ -182,6 +188,21 @@ misplaced-heading. Pattern: `**X**` at the start of the line, X a short prose run without asterisks." (and (lo--find-markdown-bold-line line) t)) +(defun lo--verbatim-asterisk-at-line-p (line) + "Non-nil if LINE (or LINE - 1) carries an =...=-wrapped run of heading +asterisks inside body prose, e.g. =** DONE= or =*** Foo=. org-lint reads the +verbatim asterisks as a possible heading and flags the line, but verbatim +markup is never a real misplaced heading. Like `lo--find-markdown-bold-line', +this checks LINE - 1 too, since org-lint often marks the blank line after the +offender. The match is anywhere on the line (the span sits mid-sentence)." + (save-excursion + (cl-loop for candidate in (list (1- line) line) + when (and (>= candidate 1) + (progn (lo--goto-line candidate) + (re-search-forward "=\\*+ [^=\n]*=" + (line-end-position) t))) + return candidate))) + (defun lo-fix-markdown-bold (line) "Convert a leading `**X**` near LINE to `*X*` (org single-asterisk bold). Uses `lo--find-markdown-bold-line' to locate the actual offender, since @@ -251,9 +272,15 @@ Craig-specific annotation marker rather than Babel src-block syntax." ((eq name 'misplaced-planning-info) (lo--apply-or-preview name line msg #'lo-fix-misplaced-planning)) ((eq name 'misplaced-heading) - (if (lo--markdown-bold-at-line-p line) - (lo--apply-or-preview name line msg #'lo-fix-markdown-bold) - (lo--emit-judgment name line msg))) + (cond + ((lo--markdown-bold-at-line-p line) + (lo--apply-or-preview name line msg #'lo-fix-markdown-bold)) + ;; Verbatim =** Foo= inside prose is never a real misplaced heading; + ;; suppress silently like the cj-comment case — no fix, no judgment. + ((lo--verbatim-asterisk-at-line-p line) + nil) + (t + (lo--emit-judgment name line msg)))) (t (lo--emit-judgment name line msg))))) diff --git a/claude-templates/.ai/scripts/tests/test-lint-org.el b/claude-templates/.ai/scripts/tests/test-lint-org.el index 9328064..416f4f6 100644 --- a/claude-templates/.ai/scripts/tests/test-lint-org.el +++ b/claude-templates/.ai/scripts/tests/test-lint-org.el @@ -303,15 +303,16 @@ content (twice (plist-get (lo-test--run lo-test--md-bold 2) :result))) (should (equal once twice)))) -(ert-deftest lo-verbatim-asterisk-is-judgment () +(ert-deftest lo-verbatim-asterisk-is-suppressed () (let* ((out (lo-test--run lo-test--verbatim-asterisk)) (res (plist-get out :result)) (judgments (lo-test--judgments (plist-get out :issues)))) ;; File untouched. (should (equal lo-test--verbatim-asterisk res)) (should (= 0 (plist-get out :fixes))) - ;; Emitted as judgment with the misplaced-heading checker. - (should (member 'misplaced-heading (lo-test--checkers judgments))))) + ;; Verbatim =*** Foo= inside prose is never a real misplaced heading, so it + ;; is suppressed — no judgment emitted (compare the cj-comment suppression). + (should-not (member 'misplaced-heading (lo-test--checkers judgments))))) ;;; --------------------------------------------------------------------------- ;;; Judgment-category emission tests @@ -407,12 +408,12 @@ suspicious-language judgment." (should (string-match-p "CLOSED: \\[2026-05-14\\][^\n]*DEADLINE: <2026-05-20" res)) - ;; Judgment: every flagged broken link, suspicious-language, verbatim-asterisk - ;; emitted untouched. + ;; Judgment: every flagged broken link and suspicious-language emitted untouched. (should (member 'link-to-local-file judgment-checkers)) (should (member 'invalid-fuzzy-link judgment-checkers)) (should (member 'suspicious-language-in-src-block judgment-checkers)) - (should (member 'misplaced-heading judgment-checkers)) + ;; The verbatim-asterisk misplaced-heading is suppressed, not surfaced. + (should-not (member 'misplaced-heading judgment-checkers)) ;; Verbatim-asterisk untouched in the file. (should (lo-test--has res "=*** Foo=")))) diff --git a/inbox/2026-05-30-lint-org-verbatim-asterisk-false-positive.org b/inbox/2026-05-30-lint-org-verbatim-asterisk-false-positive.org deleted file mode 100644 index d47dde7..0000000 --- a/inbox/2026-05-30-lint-org-verbatim-asterisk-false-positive.org +++ /dev/null @@ -1,25 +0,0 @@ -#+TITLE: lint-org misplaced-heading verbatim-asterisk false positives -#+SOURCE: wrap-up lint pass (rulesets, self-filed) -#+DATE: 2026-05-30 - -* TODO [#B] lint-org: stop flagging unfixable verbatim-asterisk misplaced-heading items :tooling:bug: - -** The error -Every wrap-up lint pass on =todo.org= re-surfaces =misplaced-heading — Possibly misplaced heading line= judgment items for legitimate verbatim org markup inside prose. Current examples: -- =todo.org= line ~2369: the =** DONE= reference ("a level-2 task ... sits as =** DONE= under Open Work"). -- =todo.org= line ~1717: the =** Startup Pull Ordering= reference. - -These are intentional =...=-wrapped heading references in sentences, not actual misplaced headings. The content is correct, so they can't be fixed in =todo.org=, yet they reappend to =inbox/lint-followups.org= on every wrap and can never be actioned. - -** The rule / checker -=.ai/scripts/lint-org.el= (canonical =claude-templates/.ai/scripts/lint-org.el=), the =misplaced-heading= branch that classifies =** Foo= / =*** Foo= inside body prose as a judgment item (search =verbatim-asterisk= / =misplaced-heading= in the script). It currently appends these to the follow-ups file as actionable TODOs, but they're never actionable. - -** Requested fix -Per the principle that linting should not flag items that aren't fixable (Craig, 2026-05-30): -1. Suppress the verbatim-asterisk =misplaced-heading= class so =...=-wrapped asterisk markup is never surfaced — it's never a real misplaced heading. -2. More broadly, when lint hits a genuinely unfixable item, route a note to rulesets' inbox (this channel) rather than recurring it in the local =lint-followups.org=. Fixable items still get fixed or surfaced as before. - -Add a test to =test-lint-org.el= pinning that a ==** Foo== verbatim span in body prose produces no judgment. - -** Done this session -Cleared the two recurring items from =inbox/lint-followups.org=. They will recur on the next wrap until this checker change lands. |
