diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-15 23:03:10 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-15 23:03:10 -0500 |
| commit | dc1661c222304dddd797bece882bb2501d2b6e76 (patch) | |
| tree | 58ebf60dc43980fef72ed3730941f68674cf1db9 /claude-templates/.ai/scripts/tests | |
| parent | 8577a880bbb84879a9297ebd28d6370c5e62727d (diff) | |
| download | rulesets-dc1661c222304dddd797bece882bb2501d2b6e76.tar.gz rulesets-dc1661c222304dddd797bece882bb2501d2b6e76.zip | |
fix(cj-scan): suppress detection inside nested non-cj begin_* blocks
cj-scan.py matched =#+begin_src cj:= / =#+end_src= line-by-line without awareness of enclosing block scopes. A cj fence embedded inside =#+begin_example= (typical when documenting what the <cj yasnippet emits) or =#+begin_src snippet= (the yasnippet definition itself) was misclassified as a live cj annotation. Two false positives surfaced from a /respond-to-cj-comments run against an org file with yasnippet docs.
Track an active wrapper_type. When the scanner sees =#+begin_<type>= for any type other than cj: (the cj-open regex is checked first), enter a wrapper state where every line is content until the matching =#+end_<type>= closer fires. Inside a wrapper, both fence patterns and legacy inline cj: lines stay suppressed. Added the TestCjScanNestedFencesIgnored class with 6 tests: nesting inside example, src <other-lang>, and quote; regression guards for clean wrapper close and unclosed-wrapper non-swallow. Canonical pytest: 302 passed, 1 skipped.
Diffstat (limited to 'claude-templates/.ai/scripts/tests')
| -rw-r--r-- | claude-templates/.ai/scripts/tests/test_cj_scan.py | 115 |
1 files changed, 115 insertions, 0 deletions
diff --git a/claude-templates/.ai/scripts/tests/test_cj_scan.py b/claude-templates/.ai/scripts/tests/test_cj_scan.py index 7844474..22bd467 100644 --- a/claude-templates/.ai/scripts/tests/test_cj_scan.py +++ b/claude-templates/.ai/scripts/tests/test_cj_scan.py @@ -248,3 +248,118 @@ class TestCjScanVerifyPlacement: ) result = run_scan(content) assert result["verify_tasks"] == [] + + +# ---------------------------------------------------------------------- +# Nested-fence suppression (no false positives inside wrapper blocks) +# ---------------------------------------------------------------------- + +class TestCjScanNestedFencesIgnored: + """A cj fence nested inside another #+begin_<type> block is content, not an annotation. + + Documentation patterns frequently embed the cj marker inside `#+begin_example` + blocks or inside `#+begin_src snippet` yasnippet definitions to *show* what + the marker looks like. A naive line-by-line scanner that only watches for + `#+begin_src cj:` and `#+end_src` matches those literally and misclassifies + the documentation as live annotations. + """ + + def test_cj_scan_fence_inside_begin_example_ignored(self, run_scan): + """Normal: cj fence inside #+begin_example ... #+end_example is documentation, not annotation.""" + content = ( + "* Section\n" + "Here is what the cj marker looks like:\n" + "#+begin_example\n" + "#+begin_src cj: comment\n" + "I am documentation, not a real annotation\n" + "#+end_src\n" + "#+end_example\n" + ) + result = run_scan(content) + assert result["cj_blocks"] == [] + assert result["unclosed_blocks"] == [] + + def test_cj_scan_fence_inside_begin_src_other_lang_ignored(self, run_scan): + """Normal: cj fence inside #+begin_src snippet (or any non-cj src block) is content. + + The outer #+begin_src snippet block claims everything until the FIRST + matching #+end_src, so the inner `#+begin_src cj:` line is literal text + inside that block — not a nested cj annotation. + """ + content = ( + "* Section\n" + "#+begin_src snippet\n" + "# name: cj-comment-block\n" + "# --\n" + "#+begin_src cj: comment\n" + "$0\n" + "#+end_src\n" + "#+end_src\n" + ) + result = run_scan(content) + assert result["cj_blocks"] == [] + + def test_cj_scan_fence_inside_begin_quote_ignored(self, run_scan): + """Boundary: cj fence inside #+begin_quote ... #+end_quote is quoted prose, not annotation.""" + content = ( + "* Section\n" + "#+begin_quote\n" + "#+begin_src cj: comment\n" + "quoted, not active\n" + "#+end_src\n" + "#+end_quote\n" + ) + result = run_scan(content) + assert result["cj_blocks"] == [] + + def test_cj_scan_real_cj_after_example_block_still_detected(self, run_scan): + """Normal: after a wrapper block closes, a subsequent real cj fence is detected. + + Regression guard: the wrapper-tracking state must reset when the + wrapper closes; otherwise everything after the first example block + would be silently swallowed. + """ + content = ( + "* Section\n" + "#+begin_example\n" + "#+begin_src cj: comment\n" + "doc, ignored\n" + "#+end_src\n" + "#+end_example\n" + "#+begin_src cj: comment\n" + "this one is real\n" + "#+end_src\n" + ) + result = run_scan(content) + assert len(result["cj_blocks"]) == 1 + assert result["cj_blocks"][0]["body"] == "this one is real" + + def test_cj_scan_legacy_inline_inside_wrapper_ignored(self, run_scan): + """Boundary: legacy `cj: ...` line inside a wrapper block is content, not annotation.""" + content = ( + "* Section\n" + "#+begin_example\n" + "cj: this is a documentation example, not a real annotation\n" + "#+end_example\n" + ) + result = run_scan(content) + assert result["cj_blocks"] == [] + + def test_cj_scan_unclosed_wrapper_does_not_swallow_rest_of_file_silently(self, run_scan): + """Error: an unclosed wrapper consumes everything after it — cj_blocks empty, not unclosed_blocks. + + Pinning current behaviour: an unclosed `#+begin_example` is a separate + org-level malformation that this scanner doesn't currently report. The + guard here is that the unclosed-wrapper case doesn't produce + false-positive cj_blocks downstream. + """ + content = ( + "* Section\n" + "#+begin_example\n" + "#+begin_src cj: comment\n" + "should be content of the unclosed example\n" + "#+end_src\n" + "more content here, no end_example\n" + ) + result = run_scan(content) + assert result["cj_blocks"] == [] |
