aboutsummaryrefslogtreecommitdiff
path: root/.ai/scripts/cj-scan.py
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-16 01:25:50 -0500
committerCraig Jennings <c@cjennings.net>2026-05-16 01:25:50 -0500
commitc67b9aaed47f054b269c0244193e1189e022b939 (patch)
treeecdc149b77e7f4ce7c071735d187ac77a89e907f /.ai/scripts/cj-scan.py
parentcd8b6e9e9c40e2fcd83104457afae1b286924a0e (diff)
downloadrulesets-c67b9aaed47f054b269c0244193e1189e022b939.tar.gz
rulesets-c67b9aaed47f054b269c0244193e1189e022b939.zip
chore(ai): sync cj-scan from claude-templates
The project mirror at .ai/scripts/ was missing the wrapper_type state machine and TestCjScanNestedFencesIgnored suite that landed in dc1661c. That commit only touched claude-templates/.ai/scripts/. Phase A's startup rsync brings the mirror back in line with the canonical.
Diffstat (limited to '.ai/scripts/cj-scan.py')
-rw-r--r--.ai/scripts/cj-scan.py25
1 files changed, 25 insertions, 0 deletions
diff --git a/.ai/scripts/cj-scan.py b/.ai/scripts/cj-scan.py
index 54e2bf9..275f5ca 100644
--- a/.ai/scripts/cj-scan.py
+++ b/.ai/scripts/cj-scan.py
@@ -30,6 +30,7 @@ VALID_VERIFY_DEPTHS = {2, 3}
HEADING_RE = re.compile(r"^(\*+)\s+(.*)$")
SRC_OPEN_RE = re.compile(r"^\s*#\+begin_src\s+cj:\s*(\S*)\s*$", re.IGNORECASE)
SRC_CLOSE_RE = re.compile(r"^\s*#\+end_src\s*$", re.IGNORECASE)
+BLOCK_OPEN_RE = re.compile(r"^\s*#\+begin_(\w+)(?:\s.*)?$", re.IGNORECASE)
LEGACY_CJ_RE = re.compile(r"^\s*cj:\s*(.*)$")
VERIFY_KEYWORD_RE = re.compile(r"^VERIFY(\s|\[|$)")
@@ -66,6 +67,13 @@ def scan_file(path: Path) -> dict[str, object]:
block_label: str | None = None
block_body: list[str] = []
+ # Tracks a non-cj `#+begin_<type>` wrapper currently in scope. Inside a
+ # wrapper, cj fence patterns are *content* (documentation examples,
+ # quoted prose, snippet definitions) -- not annotations -- so we
+ # suppress matching until the wrapper closes. The closer is type-keyed:
+ # `#+end_example` for example, `#+end_src` for src, etc.
+ wrapper_type: str | None = None
+
file_str = str(path)
lines = path.read_text().splitlines()
@@ -90,6 +98,15 @@ def scan_file(path: Path) -> dict[str, object]:
block_body.append(line)
continue
+ if wrapper_type is not None:
+ wrapper_close_re = re.compile(
+ rf"^\s*#\+end_{re.escape(wrapper_type)}\s*$",
+ re.IGNORECASE,
+ )
+ if wrapper_close_re.match(line):
+ wrapper_type = None
+ continue
+
m_heading = HEADING_RE.match(line)
if m_heading:
depth = len(m_heading.group(1))
@@ -110,6 +127,9 @@ def scan_file(path: Path) -> dict[str, object]:
})
continue
+ # cj-open must be checked before the generic begin-block match: a
+ # `#+begin_src cj: ...` line matches both patterns, and cj-open is
+ # the more specific intent.
m_src_open = SRC_OPEN_RE.match(line)
if m_src_open:
in_cj_block = True
@@ -118,6 +138,11 @@ def scan_file(path: Path) -> dict[str, object]:
block_body = []
continue
+ m_block_open = BLOCK_OPEN_RE.match(line)
+ if m_block_open:
+ wrapper_type = m_block_open.group(1).lower()
+ continue
+
m_legacy = LEGACY_CJ_RE.match(line)
if m_legacy:
cj_blocks.append({