Skip to content

fix(ai): stop /format from rewriting lines inside code fences - #312

Merged
petertzy merged 2 commits into
petertzy:mainfrom
harsh-thakkar7:fix/format-rules-skip-fenced-code
Oct 2, 2026
Merged

petertzy merged 2 commits into
petertzy:mainfrom
harsh-thakkar7:fix/format-rules-skip-fenced-code

Conversation

@harsh-thakkar7

@harsh-thakkar7 harsh-thakkar7 commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

/format rewrote lines inside fenced code blocks, corrupting executable
code. A script's shebang was made invalid, and numeric literals were split.

Before

_apply_markdown_formatting_rules() normalised every line independently, with
no notion of code fences:

for raw_line in normalized.split("\n"):
    line = raw_line.rstrip()
    line = re.sub(r"^(#{1,6})([^\s#])", r"\1 \2", line)   # space after a heading
    line = re.sub(r"^(\s*)([-*+])(\S)", r"\1\2 \3", line) # space after a bullet
    line = re.sub(r"^(\s*\d+\.)(\S)", r"\1 \2", line)      # space after "1."

Inside a fence those prefixes mean something else entirely:

input after /format effect
#!/usr/bin/env bash # !/usr/bin/env bash script no longer runs
1.5 1. 5 float silently becomes two tokens
#comment # comment usually harmless, but still an edit
#cfg: true # cfg: true YAML comment rewritten

The shebang is the serious one: this is applied with no confirmation, so running
"format" on a shell-script snippet produces something that no longer executes.

Changes

backend/ai_logic.py — track fence state in the loop and pass fenced lines
through untouched.

if _CODE_FENCE_RE.match(raw_line):
    marker = _CODE_FENCE_RE.match(raw_line).group(1)
    if fence is None:
        fence = marker[0] * 3
    elif marker[0] == fence[0] and len(marker) >= len(fence):
        fence = None
    lines.append(raw_line.rstrip())
    continue
if fence is not None:
    lines.append(raw_line.rstrip())
    continue

Details that matter:

  • _CODE_FENCE_RE allows up to three leading spaces, per CommonMark, and
    both ``` and ~~~
  • a closing fence must use the same character and be at least as long as
    the opener, so a ~~~ inside a ``` block is treated as code
  • an unclosed fence protects the remainder of the document, matching how the
    renderer treats it

Fenced lines still get .rstrip(), so the blank-line collapsing below is
unchanged and output is stable.

Prose behaviour is byte-identical to before. I verified the original and
patched functions agree exactly on #heading, ####deep, -item, *star,
1.item and ###x.

Testing

tests/test_ai_automation_logic.py::TestFormattingRulesSkipFencedCode, 7 cases:

  • a shebang is not turned into a heading
  • a numeric literal is not split
  • comments, YAML fences, tilde fences and 4-space-indented lines are untouched
  • prose normalisation is unchanged, asserted line-by-line
  • prose around a fence is still normalised (the fix is not over-broad)
  • an unclosed fence protects the rest
  • ~~~ inside a ``` block is code

Verified 6 of the 7 fail against the unmodified ai_logic.py; the seventh
is the prose-baseline guard, which must pass both before and after.

Verification

  • backend pytest — 202 passed (was 195 on main; this PR adds 7)
  • ruff check / ruff format --check on changed files — clean
  • npm test — 23 passed, unchanged
  • npm run lint / npm run build — clean

harsh-thakkar7 and others added 2 commits October 1, 2026 11:06
_apply_markdown_formatting_rules() normalised every line independently with
no notion of fences, so a leading '#', '-' or '1.' inside a code block was
read as heading/list syntax and rewritten.

A shell shebang became '# !/usr/bin/env bash' -- the script no longer runs --
and the float 1.5 became '1. 5'. Both are applied with no confirmation, so
running 'format' over a snippet silently produces broken code.

Track fence state in the loop and pass fenced lines through. The opener is
``` or ~~~ indented up to 3 spaces (CommonMark); the closer must use the
same char and be at least as long; an unclosed fence protects the rest, as
the renderer does. Fenced lines still get rstrip() so blank-line collapsing
is unchanged.

Prose behaviour is byte-identical -- verified original vs patched on
'#heading', '####deep', '-item', '*star', '1.item', '   ###x'.

Adds 7 cases; verified 6 fail against the unmodified ai_logic.py (the 7th is
the prose-baseline guard, which must pass either way).
@petertzy

petertzy commented Oct 2, 2026

Copy link
Copy Markdown
Owner

Approve. This change correctly prevents /format from rewriting content inside fenced code blocks while preserving existing prose formatting behavior. I also fixed an edge case where a fence line with an info string, such as ```python, could be incorrectly treated as a closing fence. All backend tests pass (265 passed), and Ruff checks are clean.

@petertzy
petertzy merged commit 9b0e1fa into petertzy:main Oct 2, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants