From ef4011209ad6e735c8f45597a9c7a627d4a29314 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 19:50:04 +0000 Subject: [PATCH 1/4] Initial plan From 131119e48965f536f29fdec4fbf56cc7b7d13425 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 20:06:32 +0000 Subject: [PATCH 2/4] WIP: Fix code region balancer indentation handling Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../js/markdown_code_region_balancer.cjs | 47 ++++++++++------ .../js/markdown_code_region_balancer.test.cjs | 55 ++++++++++++++----- 2 files changed, 73 insertions(+), 29 deletions(-) diff --git a/actions/setup/js/markdown_code_region_balancer.cjs b/actions/setup/js/markdown_code_region_balancer.cjs index a732c971fc9..2ffa66c8fe7 100644 --- a/actions/setup/js/markdown_code_region_balancer.cjs +++ b/actions/setup/js/markdown_code_region_balancer.cjs @@ -104,8 +104,9 @@ function balanceCodeRegions(markdown) { } // Third pass: Match fences, detecting and fixing nested patterns - // Key insight: Find ALL valid closers for each opener. If there are multiple, - // use the LAST one and increase fence length so middle ones become invalid. + // Strategy: For each opening fence, find ALL potential closers that are at the same or less indentation. + // If there are multiple such closers, use the LAST one and escape the middle ones. + // Fences that are MORE indented than the opener are treated as content (examples in documentation). const fenceLengthAdjustments = new Map(); // lineIndex -> new length const processed = new Set(); const unclosedFences = []; @@ -121,8 +122,9 @@ function balanceCodeRegions(markdown) { const openFence = fences[i]; processed.add(i); - // Look for ALL valid closers - const allMatchingClosers = []; // Track all potential closers + // Find ALL potential closers at same or less indentation + const potentialClosers = []; + const openIndentLength = openFence.indent.length; for (let j = i + 1; j < fences.length; j++) { if (processed.has(j)) continue; @@ -134,11 +136,17 @@ function balanceCodeRegions(markdown) { // Process this nested block with language processed.add(j); - // Find its closer + // Find its closer - must be at the SAME indentation level + const nestedIndentLength = fence.indent.length; for (let k = j + 1; k < fences.length; k++) { if (processed.has(k)) continue; const nestedCloser = fences[k]; - if (nestedCloser.char === fence.char && nestedCloser.length >= fence.length && nestedCloser.language === "") { + if ( + nestedCloser.char === fence.char && + nestedCloser.length >= fence.length && + nestedCloser.language === "" && + nestedCloser.indent.length === nestedIndentLength + ) { processed.add(k); break; } @@ -150,13 +158,20 @@ function balanceCodeRegions(markdown) { const canClose = fence.char === openFence.char && fence.length >= openFence.length && fence.language === ""; if (canClose) { - allMatchingClosers.push({ index: j, length: fence.length }); + const fenceIndentLength = fence.indent.length; + + // Only consider fences at the SAME indentation as potential closers + // Fences with MORE indentation are treated as content (e.g., examples in markdown blocks) + // Fences with LESS indentation are likely closing an outer block, so skip them + if (fenceIndentLength === openIndentLength) { + potentialClosers.push({ index: j, length: fence.length }); + } } } - if (allMatchingClosers.length > 0) { - // Use the LAST valid closer - const closerIndex = allMatchingClosers[allMatchingClosers.length - 1].index; + if (potentialClosers.length > 0) { + // Use the LAST potential closer (farthest from opener) + const closerIndex = potentialClosers[potentialClosers.length - 1].index; processed.add(closerIndex); pairedBlocks.push({ @@ -166,17 +181,17 @@ function balanceCodeRegions(markdown) { closeIndex: closerIndex, }); - // If there are multiple closers, we have nested fences - if (allMatchingClosers.length > 1) { + // If there are multiple potential closers, we have nested fences that need escaping + if (potentialClosers.length > 1) { // Increase fence length so middle closers can no longer close - const maxLength = Math.max(...allMatchingClosers.map(c => c.length), openFence.length); + const maxLength = Math.max(...potentialClosers.map(c => c.length), openFence.length); const newLength = maxLength + 1; fenceLengthAdjustments.set(fences[i].lineIndex, newLength); fenceLengthAdjustments.set(fences[closerIndex].lineIndex, newLength); - // Mark middle closers as processed - for (let k = 0; k < allMatchingClosers.length - 1; k++) { - processed.add(allMatchingClosers[k].index); + // Mark middle closers as processed (they're now treated as content) + for (let k = 0; k < potentialClosers.length - 1; k++) { + processed.add(potentialClosers[k].index); } } diff --git a/actions/setup/js/markdown_code_region_balancer.test.cjs b/actions/setup/js/markdown_code_region_balancer.test.cjs index c6594f63c74..e171997e20b 100644 --- a/actions/setup/js/markdown_code_region_balancer.test.cjs +++ b/actions/setup/js/markdown_code_region_balancer.test.cjs @@ -222,13 +222,9 @@ Example: nested \`\`\` \`\`\``; - const expected = `\`\`\`\`markdown -Example: - \`\`\` - nested - \`\`\` -\`\`\`\``; - expect(balancer.balanceCodeRegions(input)).toBe(expected); + // Indented fences inside a markdown block are treated as content (examples), not active fences + // No escaping needed + expect(balancer.balanceCodeRegions(input)).toBe(input); }); it("should preserve indentation when escaping", () => { @@ -237,12 +233,9 @@ Example: indented nested \`\`\` \`\`\``; - const expected = `\`\`\`\`markdown - \`\`\` - indented nested - \`\`\` -\`\`\`\``; - expect(balancer.balanceCodeRegions(input)).toBe(expected); + // Indented fences inside a markdown block are treated as content (examples), not active fences + // No escaping needed + expect(balancer.balanceCodeRegions(input)).toBe(input); }); }); @@ -389,6 +382,42 @@ More text // No changes expected - the javascript block is separate from the markdown block expect(balancer.balanceCodeRegions(input)).toBe(input); }); + + it("should not modify markdown block containing indented bare fences as examples (issue #11081)", () => { + // This reproduces the issue from GitHub issue #11081 + // A markdown code block containing examples of code blocks with indentation + const input = `**Add to AGENTS.md:** + +\`\`\`markdown +## Safe Outputs Schema Synchronization + +**CRITICAL: When modifying safe output templates or handlers:** + +1. **Update all related files:** + - Source: \`actions/setup/js/handle_*.cjs\` + - Schema: \`pkg/workflow/js/safe_outputs_tools.json\` + +2. **Schema sync checklist:** + \`\`\` + # After modifying any handle_*.cjs file: + cd actions/setup/js + npm test # MUST pass + \`\`\` + +3. **Common pitfalls:** + - ❌ Changing issue titles without updating schema + +4. **Pattern to follow:** + \`\`\` + # Find all related definitions + grep -r "your-new-text" actions/setup/js/ + \`\`\` +\`\`\` + +## Historical Context`; + // No changes expected - the indented bare ``` inside the markdown block are examples + expect(balancer.balanceCodeRegions(input)).toBe(input); + }); }); describe("edge cases", () => { From 337e1e55ce4a07de5b28c80f53670d3b61ad10a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 20:09:00 +0000 Subject: [PATCH 3/4] WIP: Refactor balancer to use first closer approach Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../js/markdown_code_region_balancer.cjs | 57 +++++++------------ 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/actions/setup/js/markdown_code_region_balancer.cjs b/actions/setup/js/markdown_code_region_balancer.cjs index 2ffa66c8fe7..e6fadb38516 100644 --- a/actions/setup/js/markdown_code_region_balancer.cjs +++ b/actions/setup/js/markdown_code_region_balancer.cjs @@ -104,13 +104,26 @@ function balanceCodeRegions(markdown) { } // Third pass: Match fences, detecting and fixing nested patterns - // Strategy: For each opening fence, find ALL potential closers that are at the same or less indentation. - // If there are multiple such closers, use the LAST one and escape the middle ones. - // Fences that are MORE indented than the opener are treated as content (examples in documentation). + // Strategy: + // 1. Process fences in order + // 2. For each opener, find ALL potential closers at the same indentation + // 3. If there are multiple closers, the user intended the LAST one, so escape middle ones + // 4. Skip closers inside already-paired blocks + // 5. Respect indentation: only match fences at the same indentation level const fenceLengthAdjustments = new Map(); // lineIndex -> new length const processed = new Set(); const unclosedFences = []; - const pairedBlocks = []; // Track paired blocks + const pairedBlocks = []; // Track paired blocks with their line ranges + + // Helper function to check if a line is inside any paired block + const isInsideBlock = lineIndex => { + for (const block of pairedBlocks) { + if (lineIndex > block.start && lineIndex < block.end) { + return true; + } + } + return false; + }; let i = 0; while (i < fences.length) { @@ -122,7 +135,7 @@ function balanceCodeRegions(markdown) { const openFence = fences[i]; processed.add(i); - // Find ALL potential closers at same or less indentation + // Find ALL potential closers at same indentation that are NOT inside existing blocks const potentialClosers = []; const openIndentLength = openFence.indent.length; @@ -131,26 +144,8 @@ function balanceCodeRegions(markdown) { const fence = fences[j]; - // If this fence has a language specifier and matches our char, it's a nested block - if (fence.language !== "" && fence.char === openFence.char) { - // Process this nested block with language - processed.add(j); - - // Find its closer - must be at the SAME indentation level - const nestedIndentLength = fence.indent.length; - for (let k = j + 1; k < fences.length; k++) { - if (processed.has(k)) continue; - const nestedCloser = fences[k]; - if ( - nestedCloser.char === fence.char && - nestedCloser.length >= fence.length && - nestedCloser.language === "" && - nestedCloser.indent.length === nestedIndentLength - ) { - processed.add(k); - break; - } - } + // Skip if this fence is inside a paired block + if (isInsideBlock(fence.lineIndex)) { continue; } @@ -161,8 +156,6 @@ function balanceCodeRegions(markdown) { const fenceIndentLength = fence.indent.length; // Only consider fences at the SAME indentation as potential closers - // Fences with MORE indentation are treated as content (e.g., examples in markdown blocks) - // Fences with LESS indentation are likely closing an outer block, so skip them if (fenceIndentLength === openIndentLength) { potentialClosers.push({ index: j, length: fence.length }); } @@ -200,16 +193,8 @@ function balanceCodeRegions(markdown) { } else { // No closer found - check if this fence is inside a paired block const fenceLine = fences[i].lineIndex; - let isInsideBlock = false; - - for (const block of pairedBlocks) { - if (fenceLine > block.start && fenceLine < block.end) { - isInsideBlock = true; - break; - } - } - if (!isInsideBlock) { + if (!isInsideBlock(fenceLine)) { unclosedFences.push(openFence); } From 36723f6853d9e0ecb0d8892b73d36713d25a8b65 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 20:48:39 +0000 Subject: [PATCH 4/4] Format JavaScript files (remove trailing whitespace) Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/markdown_code_region_balancer.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/setup/js/markdown_code_region_balancer.cjs b/actions/setup/js/markdown_code_region_balancer.cjs index e6fadb38516..995efbca872 100644 --- a/actions/setup/js/markdown_code_region_balancer.cjs +++ b/actions/setup/js/markdown_code_region_balancer.cjs @@ -154,7 +154,7 @@ function balanceCodeRegions(markdown) { if (canClose) { const fenceIndentLength = fence.indent.length; - + // Only consider fences at the SAME indentation as potential closers if (fenceIndentLength === openIndentLength) { potentialClosers.push({ index: j, length: fence.length });