From 6e6abd0592e8992938b0b9f0d8a43d6470155aea Mon Sep 17 00:00:00 2001 From: JohnnyT Date: Sun, 11 Oct 2026 10:06:34 -0600 Subject: [PATCH] Pins the ADR guard's two-line citation window The ADR guard clears a finding when an ADR citation (or, for the ADR-0018 check, the ADR-0018-exempt marker) sits on the flagged line or on the line directly above it in the same hunk. Nothing pinned the upper edge of that window, and the moduledocs said only "on or above the line", which reads as any line above. - Mix.Statifier.AdrGuard's moduledoc and analyze/1's doc, and Mix.Tasks.Adr.Check's moduledoc, now state the window and where a citation must sit: a citation two or more lines above the flagged line, such as one above a defp head whose first body line is the flagged call, clears nothing. The window stays as it is; widening it changes what the gate accepts, which ADR-0011 leaves to a human (decided by the conductor under a standing consent, 2026-10-10). - Adds two tests to test/mix/statifier/adr_guard_test.exs: an ADR-0003 citation two lines above a receive does not clear the effects finding, and the ADR-0018-exempt marker two lines above a bead-id comment does not clear that finding. Carrying every earlier hunk line in parse_line/2's previous (a wider window) turns both red; restored from a copy, byte-equal. No guard behaviour changes. Gate tooling docs and tests; no changelog fragment. After the rebase onto main, full mix quality (mix gate.verify) and mix quality --profile merge ran green on this commit's tree (write-tree ef02975e). Refs: st-djjm --- lib/mix/statifier/adr_guard.ex | 25 ++++++++++++++++++------ lib/mix/tasks/adr.check.ex | 12 +++++++----- test/mix/statifier/adr_guard_test.exs | 28 +++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/lib/mix/statifier/adr_guard.ex b/lib/mix/statifier/adr_guard.ex index 5a9de254..3f4bad24 100644 --- a/lib/mix/statifier/adr_guard.ex +++ b/lib/mix/statifier/adr_guard.ex @@ -8,8 +8,17 @@ defmodule Mix.Statifier.AdrGuard do comments), and ADR-0058 (ADR number collisions). Each of the first five checks is a name or call-site pattern over the lines a diff adds - deliberately not an AST pass - so a false positive is cleared the way this - project already clears an Appendix D deviation: an inline comment on or - above the flagged line naming an ADR or the word "deviation". + project already clears an Appendix D deviation: an inline comment naming an + ADR or the word "deviation", on the flagged line itself or on the line + directly above it in the same hunk. + + That two-line window is the whole of what the guard reads, and it is kept + that narrow on purpose: a wider one would make the guard easier to satisfy + by accident, and widening it is a change to what the gate accepts, so + ADR-0011 makes it a human call. A citation two or more lines above the + flagged line clears nothing - for instance one written above a + `defp ... do` line whose first body line is the flagged call. Put the + citation on the flagged line, or on the line directly above it. ADR-0058's tree-local checks, `adr-0058-duplicate-number` and `adr-0058-readme-index`, are different in kind from all five: they are @@ -50,8 +59,10 @@ defmodule Mix.Statifier.AdrGuard do kind of line ADR-0018's Consequences show clearing itself by accident (a legitimate ADR citation that happens to sit beside an unrelated bead mention), so this check has its own marker, `ADR-0018-exempt`, and nothing - else clears it. Being line-based rather than AST-based, it can only tell a - comment/doc line from a code line by shape: a `#` line, a single-line + else clears it. The marker is read in the same two-line window: on the + flagged line or the line directly above it, and nowhere further up. Being + line-based rather than AST-based, it can only tell a comment/doc line from + a code line by shape: a `#` line, a single-line `@moduledoc`/`@doc`/`@typedoc "..."`, a `test "..." do` description, or a line inside a `\"""` doc heredoc. A doc heredoc's extent is read from the file's post-image text carried on `source` (populated by `collect/1`), not @@ -198,7 +209,8 @@ defmodule Mix.Statifier.AdrGuard do A finding is dropped when the line it fires on, or the line above it in the same hunk, cites an ADR number or the word "deviation" - the same inline - justification CLAUDE.md already asks for. + justification CLAUDE.md already asks for. A citation two or more lines + above the flagged line does not clear it (see the moduledoc). """ @spec analyze(source :: source()) :: [finding()] def analyze(source) do @@ -774,7 +786,8 @@ defmodule Mix.Statifier.AdrGuard do # Only added lines are checked: a check about what the code now does has # nothing to say about a line the diff removes. `previous` is the line above - # in the same hunk, which is where an inline citation usually sits. + # in the same hunk, and it is the only line above a flagged line that the + # escape checks read: the citation window is that line and the flagged one. defp parse_diff(diff) do {_path, _line, _previous, acc} = diff diff --git a/lib/mix/tasks/adr.check.ex b/lib/mix/tasks/adr.check.ex index 9fa6862f..e62a31f6 100644 --- a/lib/mix/tasks/adr.check.ex +++ b/lib/mix/tasks/adr.check.ex @@ -14,11 +14,13 @@ defmodule Mix.Tasks.Adr.Check do It runs as the `ADR guard` custom stage of `mix quality`, so drift from an accepted decision is a named gate failure rather than something review has to catch. Every finding names the ADR it is about. The ADR-0002/0003/0004/0008 - findings clear with an inline comment on or above the line citing that ADR or - the word "deviation" - the same justification CLAUDE.md already asks for. The - ADR-0018 bead-ID finding does not: an ADR citation is exactly what it is - checking against, so it clears only with its own marker, `ADR-0018-exempt`, - placed on or above the line. + findings clear with an inline comment citing that ADR or the word + "deviation", on the flagged line or on the line directly above it - the same + justification CLAUDE.md already asks for. The ADR-0018 bead-ID finding does + not: an ADR citation is exactly what it is checking against, so it clears + only with its own marker, `ADR-0018-exempt`, placed in the same place. A + citation two or more lines above the flagged line clears neither kind of + finding. The ADR-0058 numbering findings (`adr-0058-duplicate-number`, `adr-0058-readme-index`, `adr-0058-base-number`) clear on neither escape diff --git a/test/mix/statifier/adr_guard_test.exs b/test/mix/statifier/adr_guard_test.exs index 5ba604cb..57af44c2 100644 --- a/test/mix/statifier/adr_guard_test.exs +++ b/test/mix/statifier/adr_guard_test.exs @@ -216,6 +216,21 @@ defmodule Mix.Statifier.AdrGuardTest do ]) == [] end + # The window is the flagged line and the one directly above it, on + # purpose: a citation above a function head does not reach the flagged + # call in its body. Widening the window is a change to what the gate + # accepts (ADR-0011), not a fix. + # sabotage: have parse_line/2 carry every earlier line of the hunk in + # `previous`, widening the window -> red + test "a citation two lines above the flagged line does not clear it" do + assert [%{line: 12, check: "adr-0003-effects"}] = + analyze("lib/statifier/document.ex", [ + " # ADR-0003: deviation, the reply is awaited in place", + " defp await_reply(ref) do", + " receive do" + ]) + end + # sabotage: loosen @escape_pattern to any `#` comment -> red test "a comment that cites nothing clears nothing" do assert [%{check: "adr-0003-effects"}] = @@ -460,6 +475,19 @@ defmodule Mix.Statifier.AdrGuardTest do ]) == [] end + # The marker is read in the same window as an ADR citation, and no + # further up. + # sabotage: have parse_line/2 carry every earlier line of the hunk in + # `previous`, widening the window -> red + test "the ADR-0018-exempt marker two lines above does not clear the finding" do + assert [%{line: 12, check: "adr-0018-bead-id"}] = + analyze("lib/statifier/document.ex", [ + " # ADR-0018-exempt: named for git blame", + " value = compute(input)", + " # st-af3 replaces this function's body" + ]) + end + # This is the exact accident ADR-0018's Consequences document at e2524fc: # a correct, unrelated ADR citation must not clear a bead-ID finding. # sabotage: have bead_cited?/1 call the shared cited?/1 (@escape_pattern)