Skip to content

fix(devx): read surface-2 docblocks from the shared comment scanner, not a lazy regex - #12880

Merged
os-elon merged 1 commit into
mainfrom
claude/issue-12833-docblock-shared-scanner
Aug 28, 2026
Merged

fix(devx): read surface-2 docblocks from the shared comment scanner, not a lazy regex#12880
os-elon merged 1 commit into
mainfrom
claude/issue-12833-docblock-shared-scanner

Conversation

@os-elon

@os-elon os-elon commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Fixes #12833

tsdocExampleBodies() in packages/lint/scripts/check-doc-formula-expressions.mjs split docblocks with /\/\*\*[\s\S]*?\*\//g — the naive block-comment strip that scripts/js-comment-mask.mjs exists to retire. A /** inside a string, template or regex literal opens a phantom docblock that runs to the next real terminator, so surface 2 read spans of live code as prose and could miss @example bodies it believed it had read. Docblock runs now come from scanSource(text).comment, which is string-, template- and regex-literal-aware.

The single matching row is deleted from the shrink-only ledger in scripts/check-comment-mask-adoption.mjs in this PR, and the shrink is recorded in that ledger's header in the FIRST SHRINK's idiom.

Premise re-measured on the branch base, not quoted from the card

The card's file lists were taken at f75a38afa; this branch is cut from 28a5c3e00. Everything below was re-measured there. Both of the card's numbers reproduce exactly, on a population that has grown to 1,061 files / 13.7 MB (the card read 1,053 / 13.4 MB).

The two readings are two different sets, as the card and the triage comment both insist. They are both of size 9 and they overlap in 5 files, not 9.

Reading 1 — the regex's claimed docblock spans hold characters the shared scanner does not call comment (9 files):

packages/spec/src/api/plugin-rest-api.zod.ts                            12
packages/spec/src/api/protocol.zod.ts                                   18   (the card's 8 + 10)
packages/spec/src/contracts/scoped-context.test.ts                     144
packages/spec/src/identity/position-delegatable-enforcer.pin.test.ts    46
packages/spec/src/kernel/manifest.test.ts                           13,139
packages/spec/src/kernel/metadata-loader.test.ts                        10
packages/spec/src/security/rls-predicate-grammar-docs.pin.test.ts      129
packages/spec/src/shared/alias-integrity.test.ts                        73
packages/spec/src/shared/retired-key-migrate-sentence.test.ts           94

Reading 2 — the docblock COUNT moves (9 files, regex count to scanner count):

packages/spec/src/data/hook-body.zod.ts                              13 -> 11
packages/spec/src/data/query.zod.ts                                  35 -> 34
packages/spec/src/identity/position-delegatable-enforcer.pin.test.ts   7 -> 6
packages/spec/src/kernel/manifest.test.ts                             1 -> 0
packages/spec/src/kernel/metadata-loader.test.ts                      1 -> 0
packages/spec/src/shared/alias-integrity.test.ts                     16 -> 15
packages/spec/src/shared/retired-key-migrate-sentence.test.ts        17 -> 16
packages/spec/src/ui/action.zod.ts                                   84 -> 82
packages/spec/src/ui/dashboard.zod.ts                                79 -> 77

A third reading the row could not have predicted: the scan recovers 5 real docblocks the regex was swallowing whole — in api/plugin-rest-api.zod.ts, api/protocol.zod.ts (two), contracts/scoped-context.test.ts and security/rls-predicate-grammar-docs.pin.test.ts.

The gate does NOT get louder on this tree, and that is reported rather than hidden

The measuring round expected the @example count to go UP and the new bodies to be the interesting part of the diff. Measured: it does not move. 416 @example bodies either way, byte for byte identical — same bodies, same startLines, 0 files with any difference — because none of the 5 recovered docblocks carries an @example and none of the phantoms fabricated one.

So the gate's own output is unchanged, and that was verified end to end rather than argued: the pre-change gate was run from a byte-identical copy of the base file (git hash-object matched HEAD's blob) placed at the same depth so its REPO_ROOT resolved identically, and its full stdout+stderr diffs empty against the patched gate's.

⛔ This is explicitly not a reason to narrow the extractor back, and nothing here does. The swallowed span in kernel/manifest.test.ts is 13 KB wide; the next @example written behind one is invisible under the regex and judged under this scan. "surface 2 admits 0 sites today" was the output of an extractor provably blind to part of its own population; it is a reading now. That reasoning is written into both files' headers so nobody re-derives the fix from the unchanged count.

Because the count did not move, no formula site surfaced that had never been judged, so there is nothing to fix and no per-site card to file under the dispatch fence.

Instrument, falsified before it was trusted

Every zero here sits beside a non-zero from the same instrument in the same run.

  • The span probe returns 9 files against the old extractor and 0 against the new one, in one pass over the same population. The 0 is a reading because the 9 proves the probe can speak.
  • The seven new --self-test cases were run against a re-injected lazy regex (marker ABLATION_OLD_REGEX, mutation confirmed on disk by marker count and by git hash-object differing from the HEAD blob): 4 of the 7 go red, including the SWALLOWED case, and the POSITIVE CONTROL stays green. Restored with git checkout HEAD -- ..., then proved restored: git diff HEAD empty and the blob hash back to HEAD's.
  • The two halves of the ledger contract were each ablated the same way. Row re-inserted with the file converted: 1 recorded row(s) the scan no longer finds. Regex re-injected with the row deleted: 1 NEW private comment-stripper(s). Both restored and proved restored by hash.

No rebuild leg is owed by those ablations: both mutated files are .mjs scripts node executes from source, resolved by path and not through any package's exports or dist, so no stale build output can mask a mutation. The dependency closure pnpm --filter '@objectstack/lint...' build was built first regardless, because the gate imports @objectstack/formula and @objectstack/lint compiled output and refuses loudly without it.

Self-test

Seven cases added, both directions, reduced from shapes measured live rather than invented:

  • SWALLOWED — a glob string opens no docblock, so the inline @example behind it is read. The regex returned nothing for this fixture.
  • FABRICATED — an @example quoted inside a string literal, and inside a template literal, is prose and not a site.
  • FABRICATED — the house-style note that spells the empty-docblock token inside a // comment is one line comment, not a second docblock. This is the live over-count on data/hook-body.zod.ts and ui/action.zod.ts.
  • Two abutting docblocks are two runs, not the one contiguous comment span the scanner flags.
  • An empty block comment is not a docblock — parity with the regex this replaced, which did not match it either.
  • POSITIVE CONTROL — an ordinary inline @example is still extracted, at its own line. Without it an extractor that returned nothing at all would pass the block.

57 cases passed, up from 50.

Changeset: none, deliberately

Derived rather than assumed, because packages/lint is a published package:

  1. AGENTS.md: a changeset is for feature work; pure bug fixes do not require one.
  2. packages/lint's files whitelist is dist, README.md, CHANGELOG.md. packages/lint/scripts/ ships to nobody, and packages/lint/src and dist are untouched — check:published-files re-states that whitelist discipline in its own passing verdict.
  3. git log receipt on the directly comparable change: 904c0afe (the FIRST SHRINK) converted three files inside published packages onto this same shared mask and deleted their ledger rows, carrying zero changesets. f907fbe9 (the measuring round on this same ledger) and 833fd1ce (the ledger gate itself) likewise.

lint.yml's own note calls a PR that edits a CI-internal script the textbook skip-changeset case, so the label is applied rather than an empty changeset written.

⚠️ Label-write caveat for whoever lands this: repo-scoped REST is closed for this container seat (403 GitHub access is not enabled for this session), and the MCP issue-labels path cannot resolve a PR number, so skip-changeset went on as a whole-set write with no usable pre-read. If a size or path label applied by automation in that window is missing, it was stripped by that write and should simply be re-added — the labelers here are additive and will restore what they own on their next run.

Verification

Union run on the final commit, d5a2b5e1, tree clean.

Gate family re-derived from the actual diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack — it reproduced the dispatch brief's 20 families and added two the brief could not name, both convention-triggered by editing a gate script. Both were run and both pass:

node scripts/pm/bare-root-worklist.mjs --self-test
pnpm check:pm-dispatch-gates

All 23 gates exit 0. Verdict lines quoted from the gates themselves, never from a bare shell status:

✓ check:doc-formula-expressions self-test: 57 cases passed
✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 424 files / 1453 TS blocks judged clean
✓ check:doc-formula-expressions (spec TSDoc, #6763): 9 @example(s) judged clean across 1061 packages/spec/src files
OK  check:comment-mask-adoption — 19 private comment-stripper(s) ... all 19 recorded and every recorded row still reached
OK  self-test: 46 live row(s), 39 unreachable as spelled, 39 recorded verdict(s) — none stale, none missing, none contradicted
✓ dispatch-gates self-test: 810 cases pass.

check:test-source-alias and check:type-source-resolution both pass and are untouched — flagged in dispatch as PR #12768's surface.

Repo-wide pnpm lint (eslint . --no-inline-config) run in full, exit 0 — no narrowing to declare. pnpm --filter @objectstack/lint typecheck exit 0 and pnpm --filter @objectstack/lint test 81 files / 2300 tests passed. ⚠️ Read honestly: that package's tsconfig.json includes src/**/* only, and tsc --noEmit --listFiles returns 0 hits for the edited .mjs — typecheck says nothing about it. The edited file's coverage is node --check, its own --self-test, pnpm lint, and the base-versus-patched output diff above.

No files outside the declared surface were touched.

Authored by a Claude Code dev seat in session session_01CPrUz21stTFhJRUirdc4ywhttps://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw

…not a lazy regex

`tsdocExampleBodies()` in check-doc-formula-expressions.mjs split docblocks with
`/\/\*\*[\s\S]*?\*\//g` — the naive block-comment strip `scripts/js-comment-mask.mjs`
exists to retire. A `/**` inside a string, template or regex literal opens a phantom
docblock that runs to the next real terminator, so the gate read spans of live code as
prose and could miss `@example` bodies it believed it had read.

Measured on packages/spec/src (1,061 files, 13.7 MB) at 28a5c3e: 9 files where the
regex's claimed spans hold characters the shared scanner does not call comment (13,139
of them in kernel/manifest.test.ts), 9 files — an overlapping but different set, 5 in
common — where the docblock count moves, and 5 real docblocks the regex was swallowing.

The gate's own verdict does not move: 416 `@example` bodies either way, byte identical,
because none of the recovered docblocks carries an `@example`. Recorded rather than
hidden, so nobody re-derives the fix from the unchanged count.

Deletes this file's row from the shrink-only ledger in the same PR, and records the
shrink in that ledger's header.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw
@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.

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 bbf88be8525cb112113913a42d796a3c42f5f770packageMentionDocs.

@os-elon os-elon added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed size/m labels Aug 28, 2026 — with Claude
@os-elon
os-elon marked this pull request as ready for review August 28, 2026 06:51
@os-elon
os-elon enabled auto-merge August 28, 2026 06:51
@os-elon
os-elon added this pull request to the merge queue Aug 28, 2026
Merged via the queue into main with commit 96732b0 Aug 28, 2026
43 checks passed
@os-elon
os-elon deleted the claude/issue-12833-docblock-shared-scanner branch August 28, 2026 07:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check-doc-formula-expressions reads a glob string as a docblock and under-reads the @example bodies it believes it read

2 participants