Skip to content

Commit dbbb833

Browse files
rammiczclaude
andcommitted
fix(compress): match fences nested in list items
FENCE_OPEN_REGEX bounded indent at 0-3, correct only for top-level fences. Inside a list item indent counts from the item's content column, so a fence under "- " sits at 4 absolute and was missed. Two effects. Its body backticks leaked into inline-code pairing, so whole paragraphs came back as lost inline code and validation failed unfixably. And extract_code_blocks never saw the block, so validate_code_blocks could not tell it had been rewritten - the copied-EXACTLY guarantee did not hold for any fence under a bullet. Refs #619 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent ec83e5b commit dbbb833

3 files changed

Lines changed: 65 additions & 1 deletion

File tree

skills/caveman-compress/scripts/validate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pathlib import Path
55

66
URL_REGEX = re.compile(r"https?://[^\s)]+")
7-
FENCE_OPEN_REGEX = re.compile(r"^(\s{0,3})(`{3,}|~{3,})(.*)$")
7+
FENCE_OPEN_REGEX = re.compile(r"^([ \t]*)(`{3,}|~{3,})(.*)$")
88
HEADING_REGEX = re.compile(r"^(#{1,6})\s+(.*)", re.MULTILINE)
99
BULLET_REGEX = re.compile(r"^\s*[-*+]\s+", re.MULTILINE)
1010

tests/test_validate_code_blocks.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import sys
2+
import unittest
3+
from pathlib import Path
4+
5+
REPO_ROOT = Path(__file__).resolve().parent.parent
6+
sys.path.insert(0, str(REPO_ROOT / "skills" / "caveman-compress"))
7+
8+
from scripts.validate import ( # noqa: E402
9+
ValidationResult,
10+
extract_code_blocks,
11+
validate_code_blocks,
12+
)
13+
14+
MANGLED = "- step:\n{i}```\n{i}rm -rf / # rewritten\n{i}```\n"
15+
INTACT = "- step:\n{i}```\n{i}rm -rf /important\n{i}```\n"
16+
17+
18+
class TestExtractCodeBlocks(unittest.TestCase):
19+
def test_top_level_fence(self):
20+
self.assertEqual(len(extract_code_blocks("```\ncode\n```\n")), 1)
21+
22+
def test_fence_indented_within_list_item(self):
23+
self.assertEqual(len(extract_code_blocks(INTACT.format(i=" "))), 1)
24+
25+
def test_fence_indented_within_nested_list_item(self):
26+
text = "- a:\n - b:\n ```\n code\n ```\n"
27+
self.assertEqual(len(extract_code_blocks(text)), 1)
28+
29+
def test_tilde_fence_indented_within_list_item(self):
30+
text = "- step:\n ~~~\n code\n ~~~\n"
31+
self.assertEqual(len(extract_code_blocks(text)), 1)
32+
33+
34+
class TestValidateCodeBlocks(unittest.TestCase):
35+
def test_rewritten_block_is_rejected_at_every_indent(self):
36+
for indent in ("", " ", " ", " "):
37+
with self.subTest(indent=len(indent)):
38+
result = ValidationResult()
39+
validate_code_blocks(
40+
INTACT.format(i=indent), MANGLED.format(i=indent), result
41+
)
42+
self.assertFalse(result.is_valid)
43+
44+
def test_untouched_block_passes(self):
45+
for indent in ("", " ", " ", " "):
46+
with self.subTest(indent=len(indent)):
47+
result = ValidationResult()
48+
text = INTACT.format(i=indent)
49+
validate_code_blocks(text, text, result)
50+
self.assertTrue(result.is_valid)
51+
52+
53+
if __name__ == "__main__":
54+
unittest.main()

tests/test_validate_inline.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ def test_indented_fence_backtick_not_leaked_as_inline(self):
5252
result = extract_inline_codes(text)
5353
self.assertEqual(result, ["inline"])
5454

55+
def test_list_nested_fence_backtick_not_leaked_as_inline(self):
56+
text = "- step:\n ```\n `weird`\n ```\nReal `inline` span here."
57+
result = extract_inline_codes(text)
58+
self.assertEqual(result, ["inline"])
59+
60+
def test_deeply_nested_fence_backtick_not_leaked_as_inline(self):
61+
text = "- a:\n - b:\n ```\n `weird`\n ```\nReal `inline` span."
62+
result = extract_inline_codes(text)
63+
self.assertEqual(result, ["inline"])
64+
5565

5666
class TestValidateInlineCodes(unittest.TestCase):
5767
def test_match(self):

0 commit comments

Comments
 (0)