diff options
| author | Craig Jennings <c@cjennings.net> | 2026-08-05 17:49:17 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-08-05 17:49:17 -0500 |
| commit | fc1a519e6891769733f593b4fd4286bfa00e5cb7 (patch) | |
| tree | c15cdb06406d223ec7a4b538e8e3ee4f907c835a /claude-templates/.ai | |
| parent | 8b5d34c3a504e39b7a0e0e678acb803f71aa9317 (diff) | |
| download | rulesets-fc1a519e6891769733f593b4fd4286bfa00e5cb7.tar.gz rulesets-fc1a519e6891769733f593b4fd4286bfa00e5cb7.zip | |
fix(lint-org): stop flagging session anchors with todo.org conventions
The todo-format checkers encode todo.org's completion rules, so they misfired on a session anchor. A Session Log's dated level-2 headings are the documented correct form, and every one came back as a defect telling the author to convert it to DONE plus CLOSED. One 427-line anchor produced six.
lo--spec-file-p becomes lo--todo-format-exempt-p. It now covers the live anchor, the per-agent anchors, and the archives alongside docs/specs/. The archives belong there because wrap-up renames the anchor unchanged, so it keeps the same shape later in its life.
All five checkers in the family fire on an anchor, not only the dated-header one. Link, table and structural checks still run.
Diffstat (limited to 'claude-templates/.ai')
| -rw-r--r-- | claude-templates/.ai/scripts/lint-org.el | 49 | ||||
| -rw-r--r-- | claude-templates/.ai/scripts/tests/test-lint-org.el | 69 |
2 files changed, 103 insertions, 15 deletions
diff --git a/claude-templates/.ai/scripts/lint-org.el b/claude-templates/.ai/scripts/lint-org.el index 33dc52f..fe4c31f 100644 --- a/claude-templates/.ai/scripts/lint-org.el +++ b/claude-templates/.ai/scripts/lint-org.el @@ -77,17 +77,36 @@ The CLI defaults this to t (a linter reports, it doesn't write); (defvar lo-current-file nil "Path of the file currently being processed.") -(defun lo--spec-file-p () - "Non-nil when the current file lives under a docs/specs/ directory. -The four todo-format-family checkers encode todo.org completion conventions -and misfire on a spec: a spec's Decisions section legitimately carries a -level-2 DONE with no CLOSED cookie, and its review-history section carries -level-2 dated headings. docs/specs/ is the canonical spec home per the -docs-lifecycle rule, so a path segment match is the scope test. Link, -table, and structural checks still run on specs — only the todo-format -family is scoped out." +(defun lo--todo-format-exempt-p () + "Non-nil when the current file's conventions are not todo.org's. +The five todo-format-family checkers encode todo.org completion conventions, +so they misfire wherever a file legitimately uses those shapes for something +else. Two such places, both matched by canonical path: + + docs/specs/ A spec's Decisions section carries a level-2 DONE + with no CLOSED cookie, and its review-history + section carries level-2 dated headings. The + canonical spec home per the docs-lifecycle rule. + + .ai/session-context.org A session anchor carries level-2 dated headings by + .ai/session-context.d/ instruction: protocols.org prescribes `** Topic' + .ai/sessions/ Session Log headers at natural seams with optional + timestamps. So every log entry reads as a + completion defect telling the author to convert it + to DONE + CLOSED:, which would be wrong. All three + paths are the same file at different points in its + life — live, per-agent under AI_AGENT_ID, and the + archive wrap-up renames it into. + +The exemption is these paths, not all of .ai/: notes.org lives there and does +follow todo.org's conventions. Link, table, and structural checks still run on +everything — only the todo-format family is scoped out." (and lo-current-file - (string-match-p "/docs/specs/" (expand-file-name lo-current-file)))) + (let ((f (expand-file-name lo-current-file))) + (or (string-match-p "/docs/specs/" f) + (string-match-p "/\\.ai/session-context\\.org\\'" f) + (string-match-p "/\\.ai/session-context\\.d/" f) + (string-match-p "/\\.ai/sessions/" f))))) (defvar lo-followups-file nil "When non-nil, after a non-check run any judgment items are appended to this path as an org section dated today. The file is created if missing.") @@ -728,11 +747,11 @@ left unmodified and mechanical entries are recorded with :preview t." (lo--check-empty-headings) (lo--check-malformed-priority-cookies) ;; The todo-format family encodes todo.org completion conventions and - ;; misfires on a spec (a Decisions section's undated DONE, a - ;; review-history dated heading, a phases task with no LAST_REVIEWED). - ;; Scope them out of docs/specs/; link, table, and structural checks - ;; above still run there. - (unless (lo--spec-file-p) + ;; misfires wherever those shapes mean something else — a spec's + ;; undated DONE and review-history dated heading, a session anchor's + ;; dated Session Log entries. `lo--todo-format-exempt-p' owns the + ;; list; link, table, and structural checks above still run there. + (unless (lo--todo-format-exempt-p) (lo--check-level2-dated-headers) (lo--check-level2-done-without-closed) (lo--check-task-missing-last-reviewed) diff --git a/claude-templates/.ai/scripts/tests/test-lint-org.el b/claude-templates/.ai/scripts/tests/test-lint-org.el index ceee209..10a4fa4 100644 --- a/claude-templates/.ai/scripts/tests/test-lint-org.el +++ b/claude-templates/.ai/scripts/tests/test-lint-org.el @@ -1098,3 +1098,72 @@ phases section may carry ** TODO [#x] items that aren't backlog tasks." (out (lo-test--run-at "todo.org" c)) (cs (lo-test--checkers (lo-test--judgments (plist-get out :issues))))) (should (memq 'task-missing-last-reviewed cs)))) + +;;; todo-format checkers skip session anchors too (protocols.org) +;; +;; A session anchor carries level-2 dated headings BY INSTRUCTION, not by +;; accident: protocols.org prescribes "** Topic section headers at natural +;; seams ... Timestamps optional — use when genuinely useful" for the Session +;; Log. So level-2-dated-header reads every log entry as a completion defect +;; and tells the author to convert it to DONE + CLOSED:, which would be wrong. +;; Reported from work 2026-08-03: six judgment items on one 427-line anchor, +;; and sentry pass 8 surfaces them nightly in every project. +;; +;; Three anchor paths, all the same shape: the live anchor, the per-agent +;; anchors under AI_AGENT_ID, and the archives wrap-up renames them into. + +(defconst lo-test--anchor-log + "* Session Log\n** 2026-08-03 Sun @ 14:02:11 -0500 Diagnosed the sync guard\nNarrative.\n" + "A Session Log entry: a level-2 dated header, the documented correct form.") + +(ert-deftest lo-level2-dated-header-skips-the-live-anchor () + (let* ((out (lo-test--run-at ".ai/session-context.org" lo-test--anchor-log)) + (cs (lo-test--checkers (lo-test--judgments (plist-get out :issues))))) + (should-not (memq 'level-2-dated-header cs)))) + +(ert-deftest lo-level2-dated-header-skips-per-agent-anchors () + "AI_AGENT_ID resolves the anchor into .ai/session-context.d/<id>.org." + (let* ((out (lo-test--run-at ".ai/session-context.d/velox.rulesets.claude.1718400000.org" + lo-test--anchor-log)) + (cs (lo-test--checkers (lo-test--judgments (plist-get out :issues))))) + (should-not (memq 'level-2-dated-header cs)))) + +(ert-deftest lo-level2-dated-header-skips-archived-anchors () + "Wrap-up renames the anchor into .ai/sessions/ unchanged, so the archive has +the same shape as the live file and lints the same way." + (let* ((out (lo-test--run-at ".ai/sessions/2026-08-03-14-02-some-work.org" + lo-test--anchor-log)) + (cs (lo-test--checkers (lo-test--judgments (plist-get out :issues))))) + (should-not (memq 'level-2-dated-header cs)))) + +(ert-deftest lo-whole-todo-format-family-skips-anchors () + "The five are scoped together, as they are for specs — a Session Log is free +to carry any of these shapes while narrating what happened." + (let* ((c (concat "* Session Log\n" + "** DONE Shipped the guard\n" + "** 2026-08-03 Sun @ 14:02:11 -0500 Did a thing\n" + "SCHEDULED: <2026-08-10 Mon>\n" + "** TODO [#B] A note to self\n" + "*** DONE A sub-step\n")) + (out (lo-test--run-at ".ai/session-context.org" c)) + (cs (lo-test--checkers (lo-test--judgments (plist-get out :issues))))) + (should-not (memq 'level2-done-without-closed cs)) + (should-not (memq 'level-2-dated-header cs)) + (should-not (memq 'dated-log-heading-active-timestamp cs)) + (should-not (memq 'task-missing-last-reviewed cs)) + (should-not (memq 'subtask-done-not-dated cs)))) + +(ert-deftest lo-link-checks-still-fire-on-anchors () + "Only the todo-format family is scoped out. An anchor's links are worth +checking — it is where a session records where its artifacts landed." + (let* ((c "* Session Log\n** Topic\n[[file:does-not-exist-xyz.org][link]]\n") + (out (lo-test--run-at ".ai/session-context.org" c)) + (cs (lo-test--checkers (lo-test--judgments (plist-get out :issues))))) + (should (memq 'link-to-local-file cs)))) + +(ert-deftest lo-a-plain-ai-file-is-not-exempt () + "The exemption is the three anchor paths, not all of .ai/ — notes.org lives +there and follows todo.org's conventions." + (let* ((out (lo-test--run-at ".ai/notes.org" lo-test--anchor-log)) + (cs (lo-test--checkers (lo-test--judgments (plist-get out :issues))))) + (should (memq 'level-2-dated-header cs)))) |
