diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-16 01:25:50 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-16 01:25:50 -0500 |
| commit | c67b9aaed47f054b269c0244193e1189e022b939 (patch) | |
| tree | ecdc149b77e7f4ce7c071735d187ac77a89e907f /.ai/scripts/cj-scan.py | |
| parent | cd8b6e9e9c40e2fcd83104457afae1b286924a0e (diff) | |
| download | rulesets-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.py | 25 |
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({ |
