diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-02 21:31:37 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-02 21:31:37 -0500 |
| commit | ac693a6b7fa7abe88f7778f8e793d5ddfd32f24e (patch) | |
| tree | ac0e8babf829ffd3ab64bb3b2bb5e9498d1f2f13 /.ai/scripts/tests | |
| parent | 291103803495cd1937244dc7c993faaaf00023ab (diff) | |
| download | rulesets-ac693a6b7fa7abe88f7778f8e793d5ddfd32f24e.tar.gz rulesets-ac693a6b7fa7abe88f7778f8e793d5ddfd32f24e.zip | |
feat(lint-org): reconcile follow-ups on write instead of appending
Every run appended a fresh dated "lint-org follow-ups" section with line-number-keyed entries, so the follow-ups file grew an unbounded pile of near-duplicate sections, kept entries whose finding had since resolved, and broke whenever the target file's line numbers shifted. Running an audit against a large todo.org surfaced exactly that drift: dead-link flags pointing at docs that now exist, and three stacked dated runs for one file.
Now lint-org rewrites the current file's section from the current run. Findings that no longer reproduce simply are not re-emitted, re-runs dedupe to one section, and entries key on checker plus message with the line as a trailing annotation, so a finding survives line shifts as the same entry. Other files' sections are left intact, and the strip step tolerates the old dated-header shape so existing follow-ups files migrate on first run. This changes the follow-ups file from an append-only log to the current outstanding findings per file.
task-audit's Phase C link-hygiene step now also reaps a matching dead-link entry when it fixes or verifies the link, scoped strictly to dead-link entries, so the audit and the follow-ups file stop drifting between lint runs.
Five follow-ups tests cover record-by-content, dedupe across runs, drop-on-resolve, and preserve-other-files. Mirrors synced.
Diffstat (limited to '.ai/scripts/tests')
| -rw-r--r-- | .ai/scripts/tests/test-lint-org.el | 105 |
1 files changed, 93 insertions, 12 deletions
diff --git a/.ai/scripts/tests/test-lint-org.el b/.ai/scripts/tests/test-lint-org.el index 416f4f6..60deb8c 100644 --- a/.ai/scripts/tests/test-lint-org.el +++ b/.ai/scripts/tests/test-lint-org.el @@ -428,7 +428,9 @@ suspicious-language judgment." ;;; --------------------------------------------------------------------------- ;;; Follow-ups file behavior -(ert-deftest lo-followups-file-appends-judgments () +(ert-deftest lo-followups-records-judgments-by-content () + "Each judgment is recorded under a per-file section, keyed by checker + +message with the line as a trailing annotation rather than the entry's identity." (let ((followups (make-temp-file "lo-followups-" nil ".org")) (file (make-temp-file "lo-test-fup-" nil ".org"))) (unwind-protect @@ -442,17 +444,96 @@ suspicious-language judgment." (let ((content (with-temp-buffer (insert-file-contents followups) (buffer-string)))) - ;; Dated section header. - (should (string-match-p - (format "^\\* %s lint-org follow-ups" - (format-time-string "%Y-%m-%d")) - content)) - ;; Each judgment is a TODO line referencing checker + line number. - (should (string-match-p "TODO line [0-9]+ — link-to-local-file" content)) - (should (string-match-p "TODO line [0-9]+ — invalid-fuzzy-link" content)) - (should (string-match-p - "TODO line [0-9]+ — suspicious-language-in-src-block" - content)))) + ;; Per-file section header naming the file. + (should (string-match-p "^\\* lint-org follow-ups — " content)) + ;; Entries lead with the checker (the content key); line is annotation. + (should (lo-test--has content "TODO link-to-local-file — ")) + (should (lo-test--has content "TODO invalid-fuzzy-link — ")) + (should (lo-test--has content "TODO suspicious-language-in-src-block — ")) + (should (string-match-p "(line [0-9]+)" content)))) + (lo-test--drop-buffer file) + (when (file-exists-p file) (delete-file file)) + (when (file-exists-p followups) (delete-file followups))))) + +(ert-deftest lo-followups-dedupes-across-runs () + "Running twice on the same file reconciles to one section, not two appended +sections." + (let ((followups (make-temp-file "lo-followups-" nil ".org")) + (file (make-temp-file "lo-test-fup-ded-" nil ".org"))) + (unwind-protect + (progn + (with-temp-file file (insert lo-test--mixed)) + (with-temp-file followups (insert "")) + (dotimes (_ 2) + (lo-test--reset nil followups) + (lo-process-file file) + (lo-emit-report) + (lo-test--drop-buffer file)) + (let ((content (with-temp-buffer + (insert-file-contents followups) + (buffer-string))) + (n 0) (start 0)) + (while (string-match "^\\* lint-org follow-ups — " content start) + (setq n (1+ n) start (match-end 0))) + (should (= n 1)))) + (lo-test--drop-buffer file) + (when (file-exists-p file) (delete-file file)) + (when (file-exists-p followups) (delete-file followups))))) + +(ert-deftest lo-followups-drops-resolved-finding () + "A finding that no longer reproduces (its source was fixed) is dropped from the +followups file on the next run." + (let ((followups (make-temp-file "lo-followups-" nil ".org")) + (file (make-temp-file "lo-test-fup-drop-" nil ".org"))) + (unwind-protect + (progn + ;; First run: a broken file link produces a judgment. + (with-temp-file file + (insert "#+TITLE: t\n* H\n[[file:does-not-exist-zzz.org][broken]]\n")) + (with-temp-file followups (insert "")) + (lo-test--reset nil followups) + (lo-process-file file) + (lo-emit-report) + (lo-test--drop-buffer file) + (should (lo-test--has + (with-temp-buffer (insert-file-contents followups) (buffer-string)) + "link-to-local-file")) + ;; Fix the source, re-run: the finding no longer reproduces. + (with-temp-file file (insert "#+TITLE: t\n* H\nclean now\n")) + (lo-test--reset nil followups) + (lo-process-file file) + (lo-emit-report) + (lo-test--drop-buffer file) + (let ((content (with-temp-buffer + (insert-file-contents followups) + (buffer-string)))) + (should-not (lo-test--has content "link-to-local-file")))) + (lo-test--drop-buffer file) + (when (file-exists-p file) (delete-file file)) + (when (file-exists-p followups) (delete-file followups))))) + +(ert-deftest lo-followups-preserves-other-files-sections () + "Reconciling one file's section leaves other files' sections intact." + (let ((followups (make-temp-file "lo-followups-" nil ".org")) + (file (make-temp-file "lo-test-fup-other-" nil ".org"))) + (unwind-protect + (progn + (with-temp-file followups + (insert "* lint-org follow-ups — other-file.org (2026-01-01)\n" + "** TODO some-checker — a prior finding (line 5)\n")) + (with-temp-file file (insert lo-test--mixed)) + (lo-test--reset nil followups) + (lo-process-file file) + (lo-emit-report) + (lo-test--drop-buffer file) + (let ((content (with-temp-buffer + (insert-file-contents followups) + (buffer-string)))) + ;; The unrelated file's section survives. + (should (lo-test--has content "other-file.org")) + (should (lo-test--has content "a prior finding")) + ;; This file's section was added alongside it. + (should (lo-test--has content "link-to-local-file")))) (lo-test--drop-buffer file) (when (file-exists-p file) (delete-file file)) (when (file-exists-p followups) (delete-file followups))))) |
