Skip to content

fix(spec): share one fence-closer predicate between check-skill-examples's walk and extraction - #12175

Merged
os-litant merged 2 commits into
mainfrom
claude/issue-11690-fence-closer-predicate
Aug 25, 2026
Merged

fix(spec): share one fence-closer predicate between check-skill-examples's walk and extraction#12175
os-litant merged 2 commits into
mainfrom
claude/issue-11690-fence-closer-predicate

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#11690

The bug

check-skill-examples.ts's fenceOwners() walk and its extractFromFile() extraction
loop disagreed about where a fenced block's body ends. The walk used an
indent/run-length-aware closer (^ {0,3}\{run,}[ \t]$); extraction re-derived a body end with a second, looser ^```\s$` (exactly three backticks, column 0). The two diverged in
both directions:

  • An indented (≤3-space) or four-or-more-backtick closing line closed the walk's span but
    not extraction's — extraction's body ran past the real close to the next bare column-0
    fence (or to EOF), swallowing whatever followed.
  • A CR-trailing closing line (a CRLF file) closed extraction's old regex but not the
    walk's — the walk read the fence as unclosed, which (per CommonMark) silently suppressed
    extraction and orphan-reporting for every later block in the file.

Latent, no occurrence in the real corpus (confirmed again in this PR: 0 files with a raw
CR byte across all 878 scanned files).

The fix

One shared closer predicate, per the issue's own suggested shape and the fenceOwners()
precedent already used correctly by the sibling gate scripts/check-role-word.mjs.
fenceOwners() now returns each opener's closeLine alongside owners, and
extractFromFile reads it directly instead of re-deriving a body end with its own regex.
The old FENCE_CLOSE_RE is removed — there is exactly one closer now, not two that can
drift apart.

Self-tests

Four new fixtures pin the divergent closing-line spellings the issue enumerates, each
verified both ways (reverse-verified: committed the fix, temporarily reverted just the
closer logic while keeping these fixtures, confirmed all four fail with the expected
messages, then restored the fix):

  • indented closer (2-space indent) — previously merged two real blocks into one
  • four-or-more-backtick closer — same merge-past-the-close failure, over-long run
  • combined indent + over-long closer — both attributes at once, the boundary the shared
    regex's {0,3} / {run,} quantifiers must both clear together
  • CR-trailing closer (CRLF) — previously the two loops silently disagreed (extraction
    reported a short, plausible body; the walk had already decided the fence never closed);
    now both loops agree the fence is unclosed, so extraction's reported body honestly
    matches what the walk believes this span contains instead of masking the mismatch

Corpus before/after (mechanism assumption #2)

check:skill-examples's real-corpus run is identical before and after, across all 5 real
source roots (skills/, content/docs/, packages/spec/src, packages/client-react/src,
packages/client/src):

BEFORE (origin/main @ 577fabf): 256 marked example(s) across 99 file(s), 3 surface(s) — all type-check
AFTER (this branch): 256 marked example(s) across 99 file(s), 3 surface(s) — all type-check

0 divergences anywhere in the real corpus; the only behavior changes are on the four
synthetic fixtures above.

Tests

pnpm --filter @objectstack/spec check:skill-examples
✅ self-test (14 fixtures, including the 4 new ones)
✅ 256 prose examples type-check across 3 surface(s)
pnpm check:doc-authoring ✅ 389 + 38 files clean
pnpm --filter @objectstack/spec check:liveness ✅
pnpm --filter @objectstack/spec check:empty-state ✅
pnpm --filter @objectstack/spec check:variant-docs ✅
pnpm --filter @objectstack/spec check:strictness-ledger ✅
pnpm --filter @objectstack/spec check:scripts-typecheck ✅ (tsc --noEmit -p tsconfig.scripts.json)
pnpm check:merge-driver ✅
pnpm check:published-files ✅
pnpm check:slot-lookup ✅
pnpm check:test-source-alias ✅
pnpm check:type-source-resolution ✅
node scripts/check-dev-prereqs.mjs --self-test ✅ (CI-required half; the real,
non-self-test half needs a full 67-package `pnpm build` this worktree hasn't done and
which is unrelated to this diff — CI's lint.yml wires only `--self-test`)
node scripts/check-plugin-teardown-shape.mjs ✅
node scripts/docs-audit/check-affected-docs.mjs ✅
node scripts/docs-audit/check-drift-comment.mjs ✅

All at 90d510d.

Scope

File surface is exactly packages/spec/scripts/check-skill-examples.ts (+ its own
self-tests), per the dispatch's serial-ordering note: #12051 and #12048 share this file on
a different defect class each and hold until this lands.

No changeset — pure internal dev-tooling/CI-script bugfix, no published-package or
user-facing behavior change (packages/spec/scripts/** is not in the package's publish
files allowlist, confirmed by check:published-files above).

Generated by Claude Code


Generated by Claude Code

…les's walk and extraction
fenceOwners() already computed each top-level fence's close line with an
indent/run-length-aware regex; extraction re-derived a body end with a
second, looser `^```\s*$` (exactly three backticks, column 0) that
disagreed with it in both directions — an indented or four-or-more-backtick
closing line closed the walk's span but not extraction's, and a
CR-trailing one (a CRLF file) closed extraction's but not the walk's.
fenceOwners() now returns each opener's closeLine alongside owners, and
extraction reads it directly instead of re-deriving one.
Adds four self-test fixtures pinning the previously-divergent closing-line
spellings: indented, four-or-more-backtick, the two combined, and
CR-trailing.
@github-actions

github-actionsBot commented Aug 25, 2026

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.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 494279cb31f1d92adab959763085e19c923a8652packageMentionDocs.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gatetooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] check:skill-examples: extraction's body-close regex and the fence walk's closer disagree on where a fence ends

2 participants

@os-litant@claude