aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ai/scripts/lint-org.el39
-rw-r--r--.ai/scripts/tests/test-lint-org.el13
-rw-r--r--claude-templates/.ai/scripts/lint-org.el39
-rw-r--r--claude-templates/.ai/scripts/tests/test-lint-org.el13
-rw-r--r--inbox/2026-05-30-lint-org-verbatim-asterisk-false-positive.org25
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.