Repository navigation
Description
Summary
parseDecisionsMarkdown() treats every trimmed ### line as a decision boundary without tracking fenced code blocks. A ### heading inside a ```markdown fence is therefore parsed as a separate decision record, splitting the enclosing record in two.
Found by copilot-pull-request-reviewer on #1838 and independently verified.
Location
packages/squad-sdk/src/config/markdown-migration.ts:417-422
Reproduction
.squad/decisions.md contains a RETRO record whose body includes a normative block intended to be pasted into workflows/squad.md:
````markdown
### Shell input security contract [MANDATORY]
The `/squad` parser treats issue bodies ... as attacker-controlled.
````
DecisionsCollection.list() splits the RETRO record at that heading and returns the fenced example as its own decision.
The content is correct. A normative example legitimately contains headings. The parser is the defect — it must not be worked around by mangling the example.
Why this matters beyond cosmetics
Decision counts are the integrity check for Scribe's archival path. Rule C exists precisely because byte size is blind to content loss, so archival correctness is asserted by counting entries (N removed from source / N added to destination).
A parser that miscounts entries corrupts the only check standing between the archival path and silent data loss — the failure mode already seen twice (#1782, and the recurrence tracked in #1836).
Concretely, on the #1838 branch a naive ^### count reports 59 decisions. The true distinct count is ~54. The inflation comes from three sources, only one of which is this bug:
| Cause | Count | Status |
|---|---|---|
| Dated wrapper + undemoted duplicate source heading | 3 | content defect, fixed in #1838 |
Addendum subsection using ### |
1 | content defect, fixed in #1838 |
| Heading inside a fenced block | 1 | this issue — parser defect |
The first two are content-side and repairable by Scribe. This one is not: no amount of correct authoring prevents it, because a fenced example is supposed to contain headings.
Proposed fix
Track fence state while scanning. Handle both ``` and longer runs ( ````), since a longer fence is exactly how a block containing triple-backticks is nested — which is the case here. A closing fence must be at least as long as the opener.
Acceptance criteria
-
parseDecisionsMarkdown()ignores###inside fenced blocks - Handles fences longer than three backticks (4+), and nested/inner triple-backtick blocks
- Regression test using the real RETRO record shape: an outer
```` fence containing an inneryaml ``` block and a###heading — asserting the record is not split - Test proven capable of failing: revert the fence tracking, confirm the test goes red naming the offending heading, restore
- Enumerate callers of
parseDecisionsMarkdown()and confirm none depend on the current fence-blind behavior
Notes
Related to #1836 (archival gate reports outcomes it did not measure). Same thesis: a check that cannot observe the thing it counts is not a check.
Touches packages/squad-sdk/src/ → requires a changeset.
Activity
github-actions commentedon Aug 23, 2026
🏗️ Squad Triage — Flight (Lead)
Issue: #1839 — parseDecisionsMarkdown() is fence-blind: headings inside code fences are parsed as decisions
Assigned to: FIDO (Quality Owner)
Reason: Issue relates to testing/quality work
@copilot evaluation: 🔴 Not suitable — issue involves work outside the coding agent's capability profile.
Team roster:
squad:flightsquad:proceduressquad:eecomsquad:fidosquad:paosquad:capcomsquad:controlsquad:surgeonsquad:boostersquad:gncsquad:networksquad:retrosquad:incosquad:guidosquad:telemetrysquad:voxsquad:dskysquad:simssquad:handbooksquad:ralphsquad:copilotbradygaster commentedon Aug 23, 2026
Verified against the source, plus two details not in the original report
Read
markdown-migration.ts:417-441directly and replicated the loop against the live.squad/decisions.md.1. The regex matches
##, not just######?is##plus an optional third#— so H2 is also a decision boundary. Worth stating explicitly in the fix, because a fence-tracking patch that only reasons about###would still split records on a fenced##.Confirmed
####does not match, which is why demoting a heading to####is the correct content-side remedy.2.
.trim()runs before the match, so indentation offers no protectionAn indented heading inside an indented code block still matches. Not currently triggered in this repo (measured: 0), but it widens the blast radius — a fix that tracks only fenced blocks and ignores indented code blocks would still be incomplete.
Measured on the current file
#########headings (correctly invisible)Method: replicated
/^(###?)\s+(.+)/against.trim()ed lines while tracking```-or-longer fence state, where a closing fence must be at least as long as its opener.Of the 59, exactly 1 is this parser bug. The other 4 phantoms are content defects being fixed in #1838. True distinct decisions: ~54.
That the
##count is 0 confirms Scribe's##demotion rule is being applied correctly — this is specifically a###-and-fences problem, not a general heading-hygiene problem.Suggested addition to acceptance criteria
##boundaries as well as###fix(squad): repair BOM, CR, heading and link defects found in review
chore(squad): gh-aw wave-1 session records + repair 3 lost history ar…
github-actions commentedon Sep 10, 2026
🔄 Ralph — Auto-Triage
Assigned to: CONTROL
Reason: Matched module path "src/config/" to primary owner "CONTROL 👩💻"
Source: module-ownership
github-actions commentedon Sep 10, 2026
🔄 Ralph — Auto-Triage
Assigned to: CONTROL
Reason: Matched module path "src/config/" to primary owner "CONTROL 👩💻"
Source: module-ownership
github-actions commentedon Sep 11, 2026
🔄 Ralph — Auto-Triage
Assigned to: CONTROL
Reason: Matched module path "src/config/" to primary owner "CONTROL 👩💻"
Source: module-ownership
github-actions commentedon Sep 11, 2026
🔄 Ralph — Auto-Triage
Assigned to: CONTROL
Reason: Matched module path "src/config/" to primary owner "CONTROL 👩💻"
Source: module-ownership
github-actions commentedon Sep 13, 2026
🔄 Ralph — Auto-Triage
Assigned to: architect
Reason: No module, routing, or role keyword match — routed to Lead/Architect
Source: lead-fallback
github-actions commentedon Sep 24, 2026
📋 Assigned to sdk-runtime-dev (SDK Runtime Engineer)
Issue: #1839 — parseDecisionsMarkdown() is fence-blind: headings inside code fences are parsed as decisions
sdk-runtime-dev will pick this up in the next Copilot session.