-
Notifications
You must be signed in to change notification settings - Fork 12
Render a code patch whose opening fence ends a prose line #6104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -157,6 +157,35 @@ function normalizeDecorativeBullets(markdown: string): string { | |
| // partially received plan file already renders as one block. | ||
| const FILE_URL_LINE_PATTERN = /^\s*https?:\/\/\S+(\s*\(\s*new\s*\))?\s*\r?$/; | ||
|
|
||
| // A fence opens a code block only at the start of a line. A model that ends a | ||
| // sentence and starts the patch on the same line — "Let's write the block!```json" | ||
| // — has written a patch the renderer reads as prose: the url, the markers and | ||
| // the file content collapse into one paragraph, the host finds no code block | ||
| // and applies nothing, and the bot, which counts patches by their markers, | ||
| // waits for a result that never comes. The block itself is correct; only the | ||
| // line break before the fence is missing. Put it back when the two lines after | ||
| // the fence are a file url and the SEARCH marker, which is what makes this a | ||
| // patch rather than prose that happens to end in backticks. | ||
| const FENCE_GLUED_TO_PROSE_PATTERN = /^(.*\S)(`{3,}\w*)\s*(\r?)$/; | ||
|
|
||
| export function splitCodePatchFencesGluedToProse(markdown: string): string { | ||
| let lines = markdown.split('\n'); | ||
| for (let i = 0; i + 2 < lines.length; i++) { | ||
| let glued = lines[i].match(FENCE_GLUED_TO_PROSE_PATTERN); | ||
| if ( | ||
| glued && | ||
| !CODE_FENCE_PATTERN.test(lines[i]) && | ||
| FILE_URL_LINE_PATTERN.test(lines[i + 1]) && | ||
| SEARCH_MARKER_PATTERN.test(lines[i + 2]) | ||
|
Comment on lines
+174
to
+179
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a valid outer patch—such as a Markdown or documentation patch—contains a literal example with prose glued to a fence followed by a file URL and SEARCH marker, this whole-document scan also matches inside that existing fenced block. If the example is in SEARCH content, the inserted newline prevents the patch from matching the target; in REPLACE content or a new file, it silently changes the requested file contents. Track fenced-block context and only repair top-level prose. Useful? React with 👍 / 👎. |
||
| ) { | ||
| let [, prose, fence, cr] = glued; | ||
| lines.splice(i, 1, `${prose}${cr}`, `${fence}${cr}`); | ||
| i++; | ||
| } | ||
| } | ||
| return lines.join('\n'); | ||
|
Comment on lines
+173
to
+186
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Claude Code 🤖] This scan carries no fenced-block context, so it rewrites patch content too — the bot flagged it and it is reachable. A patch that writes a document about the patch format carries the anti-example inside its own halves: ```markdown
https://example.com/realm/skill.md
╔══ SEARCH ══╗
╠══╣
Wrong — the fence must start a line:
Here it is!```json
https://example.com/realm/a.json (new)
╔══ SEARCH ══╗
╠══╣
{}
╚══ REPLACE ══╝
```
╚══ REPLACE ══╝
```I ran that through
Regression, introduced by this pass. Not a merge blocker on its own, but small enough that I'd fix it here rather than leave it as a follow-up. Generated by Claude Code |
||
| } | ||
|
|
||
| export function widenFencesAroundCodePatches(markdown: string): string { | ||
| let lines = markdown.split('\n'); | ||
| let i = 0; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Claude Code 🤖] Two edges in this pattern, both non-blocking:
A glued fence longer than three backticks splits in the wrong place.
(.*\S)is greedy, so onWriting now!\```jsonthe prose group takes the first backtick: it yields proseWriting now!`and fence```json`. The prose line then ends in a stray backtick that opens inline code across the rest of the paragraph, and the opener is three backticks where the model wrote four.(\r?)can never capture. The preceding\s*is greedy and consumes the\rfirst, so on CRLF input it is always''and both produced lines lose their carriage return. Harmless to the render, but it reads as preserving CRLF and doesn't.Both go away with a lazy prose group that can't end on a backtick, plus a horizontal-whitespace class so the
\rsurvives to its group:Checked against both inputs:
Go!\``json\r→["Go!", "```json", "\r"], and the four-backtick line →["Writing now!", "````json", ""]`.Generated by Claude Code