Skip to content

fix(spec): make check:skill-examples' marked-block extraction fence-aware - #11689

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-11355-extraction-fence-aware
Aug 24, 2026
Merged

fix(spec): make check:skill-examples' marked-block extraction fence-aware#11689
os-warren merged 1 commit into
mainfrom
claude/issue-11355-extraction-fence-aware

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#11355

The gap

check:skill-examples has two loops over the same lines. #10791 made the orphan scan
fence-aware, so a marker shown as example text inside a wrapping fence claims nothing — that
is what lets this gate's own convention be documented in the very roots it governs. It
deliberately left the marked-block extraction loop alone, so extraction still matched a
bare ts / tsx / typescript fence-open line wherever it appeared, with no notion of sitting
inside another fence.

A marker alone nested in an illustration was therefore handled correctly. A fully worked
one — the marker AND a real ts fence, both written as example text inside a wrapper — was
extracted and handed to tsc as a genuine example: compiling by luck, or failing the whole
gate with a diagnostic pointing at documentation prose.

Triage ruling on the card (2026-08-24) chose option 1 of the body — share the fence walk —
and asked for the nested-illustration self-test on the #10533 precedent in
scripts/check-role-word.mjs. Option 2 (document the limitation) was not chosen.

The fix

Both loops now read onefenceOwners() walk — the same walk #10791 landed, returning
which top-level fence owns each line rather than merely whether one does:

The extraction loop's fence-open pattern is a strict subset of the CommonMark one the walk
uses (three backticks plus an info string containing no backtick), so a genuine top-level
block always owns itself and the new guard cannot suppress one. Two independent notions of
"am I in a fence" in one file is how the halves drifted apart in the first place, so there is
now only one.

Verification

Local, all at 478b817e6 with a clean tree.

Consumer duty — the gate against the real corpus, before and after. Byte-identical apart
from the self-test's own success line (which now names the new pinned behaviour); no corpus
occurrence trips this, exactly as the card predicted:

 257 marked example(s) across 99 file(s), 3 surface(s):
• skills + docs (@objectstack/spec): 228 block(s)
• spec source TSDoc (@objectstack/spec): 10 block(s)
• client SDK (@objectstack/client-react, @objectstack/client): 19 block(s)
✅ 257 prose examples type-check across 3 surface(s)

Where the defect is measurable: the self-test. Reverse-verified from the committed state
by deleting only the guard line (perl -i, restore armed as a trap before the edit).
Mutation proved on disk by anchored counts — deleted text 1 → 0, neighbouring anchor 1 → 1,
1846 → 1845 lines — and restoration proved byte-identical (git hash-object equals the HEAD
blob, git status --porcelain empty). No build is interposed in either leg: the gate runs as
tsx scripts/check-skill-examples.ts, straight off the source, and the self-test half never
reads dist at all. Ablated, the self-test goes red naming the defect:

✗ nested-illustration fixture: extracted 3 block(s), expected 1
✗ nested-illustration fixture: an illustrated payload reached an extracted block —
["const poison: number = 'not a number';","const alsoPoison: number = 'not a number either';","const real: number = 1;"]
✗ gutter-wrapped nested fixture: extracted [poison, real], expected only [real]

Those two poison payloads are precisely what would have reached tsc. Restored, exit 0.

The un-nested control passes in both legs, deliberately: it is what proves the nested red
comes from the nesting and not from the payload or the marking (the #10533 (B3) shape). Both
poison payloads also sit after an inner close but before the wrapper's own, so a length-blind
closer surfaces as a second extracted block rather than as silence. The third fixture repeats
the pair inside a JSDoc-gutter-wrapped docblock — nothing else would catch ownership being
computed over raw instead of gutter-stripped lines, which would return the defect on exactly
the roots whose prose lives in docblocks.

Gate families — derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
from the real change set (not a hand-written path list). All 18 matched families green, plus
check:nul-bytes and pnpm --filter @objectstack/spec typecheck (which is what type-checks
scripts/**, via check:scripts-typecheck).

One family is red for a reason that is not this diff: check-dev-prereqs reports "The
workspace is not built"
. It measures workspace build completeness — this worktree built only
the @objectstack/client-react... closure the gate needs. Run against the pristine shared
checkout, which carries none of these changes, it fails the same way and harder (67 of 67
packages unbuilt, versus 34 of 67 here). CI builds the workspace before gates.

Notes

  • Changeset written rather than a skip-changeset label: the changeset check is path-blind
    (any PR with no newly added .changeset/*.md fails), and every prior change to this gate
    shipped a @objectstack/spec patch changeset. The label remains the PM's call.
  • File surface held to the card: packages/spec/scripts/check-skill-examples.ts plus its
    self-test, which live in the same file, and the changeset. No fixture content was copied
    from the card's rendered body — the body carries a live specimen of this very trap and its
    own illustration is mangled by GitHub; all fixtures are constructed in code.

Generated by Claude Code

…ware
The orphan-marker scan learned to ignore an `os:check` marker shown as example
text inside a wrapping fence, so this gate's own convention could be documented
in the roots it governs. The extraction loop was deliberately left out of that
fix and kept recognising a bare ```ts / ```tsx / ```typescript fence-open line
wherever it appeared, with no notion of sitting inside another fence. A marker
ALONE nested in an illustration was therefore handled correctly, while a fully
worked one -- marker AND a real ts fence, both example text inside a wrapper --
was extracted and handed to tsc as a genuine example: compiling by luck, or
failing the whole gate against documentation prose.
Both loops now read one `fenceOwners()` walk, which records per line which
top-level fence owns it: `owners[i] >= 0` answers the orphan scan's question,
`owners[i] === i` answers extraction's. The extraction loop's fence-open
pattern is a strict subset of the CommonMark one the walk uses, so a genuine
top-level block always owns itself and the guard cannot suppress one.
No corpus occurrence trips this, so the counts are identical either side of the
fix (257 marked examples across 99 files, three surfaces). The self-test is
where the defect is measurable: a nested worked illustration with deliberately
uncompilable payloads extracts nothing, while the identical payloads with the
wrapper removed all extract -- and the same pair is pinned inside a JSDoc
gutter-wrapped docblock, where ownership must be judged on stripped lines.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rxnd8cyFnoU8V5y21PaTsy
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tooling labels Aug 24, 2026
@os-warren
os-warren marked this pull request as ready for review August 24, 2026 13:21
@os-warren
os-warren added this pull request to the merge queueAug 24, 2026
Merged via the queue into main with commit ee17d86Aug 24, 2026
33 checks passed
@os-warren
os-warren deleted the claude/issue-11355-extraction-fence-aware branch August 24, 2026 13:48
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mtooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] check:skill-examples's marked-block EXTRACTION (not just the orphan scan) is not fence-aware — a fully worked nested example could misfire

2 participants

@os-warren@claude