Skip to content

fix(gate): read CommonMark autolinks in check:published-readme-links - #10764

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-10657-readme-autolinks
Aug 21, 2026
Merged

fix(gate): read CommonMark autolinks in check:published-readme-links#10764
os-zhuang merged 1 commit into
mainfrom
claude/issue-10657-readme-autolinks

Conversation

@claude

@claude claude Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Closes #10657

The gap

extractLinks in scripts/check-published-readme-links.mjs matched exactly two shapes —
the inline link [text](dest) and the reference definition [label]: dest. It did not
match the CommonMark autolink: a bare absolute URI wrapped in angle brackets, which
GitHub and npm both render as a live link.

The gate's own header states its subject as "every outbound link in a PUBLISHED markdown
file"
. An autolink is a link by that definition, so the shape was owed to the population
and missing from it: an autolinked docs URL was read by none of the four assertions —
no page resolution, no anchor check. A 404 written in that form shipped to npm green.

Nothing was broken through this: every autolink in the tree resolves today. This is
invariant restoration on a latent hole, not a live defect.

Derived population — 13 links in 6 files

Derived from the gate's own population helper (publishedDocs) and its own prose
extraction, rather than adopted from an estimate:

package README autolinks
@objectstack/adapter-hono packages/adapters/hono/README.md 2
@objectstack/core packages/core/README.md 2
@objectstack/driver-memory packages/drivers/driver-memory/README.md 2
@objectstack/plugin-security packages/plugins/plugin-security/README.md 2
@objectstack/rest packages/rest/README.md 2
@objectstack/service-package packages/services/service-package/README.md 3
total 13

All 13 classify docs-site, all 13 are already on the canonical origin, and all 13
resolve — so the widened gate stays green.

How 13 reconciles with the two prior counts

The issue estimates ten; a repo-wide grep reported matches in seven files. Both
differ from the gate's own population, and both differences are fully explained:

  • The issue's ten omits service-package's three. Its line census lists five files
    (hono, core, driver-memory, plugin-security, rest) at 2 each. But its route list
    includes /docs/protocol — and service-package is the only file in the tree whose
    autolink carries that route. So the card's route census saw the file its line census
    dropped. 10 + 3 = 13.
  • The grep's seventh file is examples/app-crm, which is private: true with no
    files array, so it is outside the published population by design. Its one autolink is
    a localhost URL, which would classify external and be read by no assertion even if
    the file were published. 14 − 1 = 13.

What changed

One file. A third recognizer, AUTOLINK, transcribed from the CommonMark scheme
production (2–32 chars, opening with an ASCII letter, then a colon, then a run of
characters that are not ASCII control, space, or either angle bracket).

Autolinks are matched last and filtered by SPAN, not by pattern. A pointy-bracket
destination — angle brackets inside an inline link's parentheses — is a different
construct that INLINE_LINK has always read. When such a destination happens to contain
no space it is also a well-formed autolink body, so a span-blind recognizer counts it
twice and inflates the census this gate prints by one per occurrence. extractLinks now
records the range each link construct consumed and drops an autolink starting inside one.
The self-test pins this as a count, not a presence — presence alone cannot see a
double-count.

Verification

Positive control reproduced first. The shipped extractor, on the input the issue
reports, returned []:

input     Docs: LT https://objectstack.ai/docs/no-such-page-anywhere GT
before    []
after     ["https://objectstack.ai/docs/no-such-page-anywhere"]

(LT/GT stand in for the literal angle brackets, per the issue's spelling note.)

Gate delta, measured before and after against the real tree — the after extractor
reads exactly 13 more links, all of which resolve:

baseline  152 outbound link(s), 12 docs-site page(s) resolved   (origin/main extractor)
after     165 outbound link(s), 25 docs-site page(s) resolved   (+13, +13)

Reject side pinned, one case per shape that wears angle brackets without being a
link: bracketed prose with no scheme, an HTML tag, a tag with attributes, and CommonMark's
address-shaped email autolink (deliberately not matched — it can never be a docs-site
URL, so every assertion would classify it external and read no further; matching it
would widen the recognizer and change no verdict).

One finding worth naming: assertion 1 is structurally unreachable through this form.
A CommonMark autolink body must be an absolute URI, so a root-relative path in angle
brackets is not an autolink at all — GitHub and npm render it as literal text. My first
draft pinned "A1 fails on a root-relative autolink" and it went red, correctly. The pin is
now written in the direction that is actually true, with a comment explaining why, so a
later author does not read the missing A1 pin as a hole and "fix" it by dropping the
scheme requirement — which would start claiming every HTML tag in the tree. The self-test
summary line was corrected too: it now says assertions 2, 3 and 4 are re-observed failing
and A1 is pinned unreachable, rather than claiming all four.

Ablation, predicted before running. Neutralising the new pattern (AUTOLINK replaced
with a never-matching regex; mutation confirmed on disk by anchor count, removed 1→0 and
injected 0→1) was predicted to redden 11 of the 20 new pins — the 9 negative and
discrimination pins should survive, because a recognizer that matches nothing still
"correctly" stays silent on non-links. Measured: exactly 11, and the named 11 were the
predicted set. The gate itself fell back to 152 / 12 under ablation, confirming all 13
links come from the new pattern alone. The restore leg was verified in both directions —
ablation marker absent, real pattern present, tree byte-identical to the commit.

Gates

Derived with node scripts/pm/dispatch-gates.mjs (no paths — it reads the change set from
the merge base itself), then run at final commit 3713e89:

gate verdict line
check:published-readme-links 165 outbound link(s) across 60 published markdown file(s): 0 root-relative, 0 non-canonical origin(s), 25 docs-site page(s) resolved
check:published-readme-links --self-test green (20 new pins)
check:cross-package-test-inputs All 90 self-test cases passed. / OK: 13 package(s) read outside themselves
check:parse-guard green
check:nul-bytes OK (scanned 6234 text file(s) ... no raw ASCII control bytes)
check:entry-guard 125 scripts/ file(s) — every entry guard goes through invoked-as.mjs

check:published-readme-exports is red in this worktree, and it is not this branch:
all 194 of its findings are the dist/... does not exist. Build first shape from an
unbuilt fresh worktree. It does not import this module, and this branch does not touch it.

No changeset: gate tooling, publishes nothing. Labelled skip-changeset.


Generated by Claude Code

`extractLinks` matched only `[text](dest)` and `[label]: dest`, so a bare URI
in angle brackets -- the CommonMark autolink, rendered as a live link by both
GitHub and npm -- was read by none of the gate's four assertions. 13 docs-site
links across 6 published READMEs sat in that hole, every one of them in the
"Docs" / "API Reference" footer. All 13 resolve today, so nothing was broken
through it; what was missing was the population the gate's own header claims.

Adds AUTOLINK as a third recognizer, transcribed from the CommonMark scheme
production. Autolinks are matched last and filtered by SPAN, because a
pointy-bracket destination `[d](<https://x/y>)` that contains no space is also
a well-formed autolink body -- a span-blind recognizer would count it twice and
inflate the printed census by one per occurrence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 21, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 21, 2026 10:57
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 21, 2026
Merged via the queue into main with commit 1793fb6 Aug 21, 2026
32 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-10657-readme-autolinks branch August 21, 2026 11:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants