Found while clearing @object-ui/app-shell for #5357. Not fixed there: that card's file surface is packages/app-shell/src/** plus the SPECIFIER_DEBT entry, and this is a different defect class in the gate's own scanner. Same family as #5367 — a comment-stripping regex in this script that is not aware of the context it is stripping from.
The defect
withoutCommentedCode() in scripts/check-node-esm-load.mjs blanks block comments with
source.replace(/\/\*[\s\S]*?\*\//g, (block) => block.replace(/[^\n]/g, ' '))
applied before line comments are handled. The pattern has no notion of already being inside a // line, so any /* that appears in ordinary line-comment prose opens a block comment, and the mask then runs to the next * / anywhere in the file — blanking every line in between, live code included.
Measured, 2026-08-20, on main at f2e11ae6f
packages/app-shell/src/preview/DraftChangesPanel.tsx line 44 is a line comment that mentions a glob:
// `vendor-objectstack` chunk group claims every `@objectstack/*` module except
The backtick-quoted @objectstack/* opens the fake block comment. It closes at the * / ending the next JSDoc block, around line 55. Lines 51 and 52 are blanked — and line 52 is a live import:
import { diffFields } from '../views/metadata-admin/previews/object-fields-io';
Taking the same specifier set two ways over the same 424 emitted source files:
| method |
relative specifiers found |
the gate's RELATIVE_SPECIFIER over withoutCommentedCode(source) |
1270 |
the TypeScript parser (ts.createSourceFile, module-specifier nodes) |
1271 |
The set difference is exactly one entry, in one direction: the parser sees DraftChangesPanel.tsx:52 and the gate does not. The gate produced zero false positives — the two genuine prose specifiers in that package are correctly invisible to both.
Why it matters more now than it did
While @object-ui/app-shell sat in SPECIFIER_DEBT the miss was harmless: the package was red-listed anyway. After #5357 the ledger is empty and the specifier leg is a hard requirement for every specifier-preserving package. A blind spot in a hard requirement is a hole a regression can sit in permanently, and the failure direction is the bad one: the gate reports clean, the artifact ships broken, and only the nightly load leg can see it — attributed to whichever module the entry happened to reach first.
It is not exotic prose either. A line comment naming a package glob (@scope/*), a path pattern, or a wildcard import is ordinary in this repository.
Suggested shape
The cheap correct fix is to mask in ONE pass that knows which context it is in, rather than two independent replace calls whose order decides the answer — a scanner that walks the source once and blanks whichever of // or /* opens first, honouring the newline-preservation the current implementation is careful about (that care is load-bearing; see the note above withoutCommentedCode).
The stronger option is to take the specifier set from the TypeScript parser outright, since typescript is already a dependency and emittedSources() already knows exactly which files to hand it. That removes the whole class — line comments, block comments, template literals and regex literals stop being questions at all — at the cost of parsing 400-odd files instead of running a regex over them, which measured under two seconds for app-shell.
Whichever shape is chosen, the pin worth having is the measured one: a fixture whose line comment contains @scope/* followed by a real extensionless import, asserting the import is still found.
Related: #5367 (readTsconfig()'s comment stripper, same script, same class), #5357 (where this was found), #4538 (the gate's origin).
Found while clearing
@object-ui/app-shellfor #5357. Not fixed there: that card's file surface ispackages/app-shell/src/**plus theSPECIFIER_DEBTentry, and this is a different defect class in the gate's own scanner. Same family as #5367 — a comment-stripping regex in this script that is not aware of the context it is stripping from.The defect
withoutCommentedCode()inscripts/check-node-esm-load.mjsblanks block comments withapplied before line comments are handled. The pattern has no notion of already being inside a
//line, so any/*that appears in ordinary line-comment prose opens a block comment, and the mask then runs to the next*/anywhere in the file — blanking every line in between, live code included.Measured, 2026-08-20, on
mainatf2e11ae6fpackages/app-shell/src/preview/DraftChangesPanel.tsxline 44 is a line comment that mentions a glob:The backtick-quoted
@objectstack/*opens the fake block comment. It closes at the*/ending the next JSDoc block, around line 55. Lines 51 and 52 are blanked — and line 52 is a live import:Taking the same specifier set two ways over the same 424 emitted source files:
RELATIVE_SPECIFIERoverwithoutCommentedCode(source)ts.createSourceFile, module-specifier nodes)The set difference is exactly one entry, in one direction: the parser sees
DraftChangesPanel.tsx:52and the gate does not. The gate produced zero false positives — the two genuine prose specifiers in that package are correctly invisible to both.Why it matters more now than it did
While
@object-ui/app-shellsat inSPECIFIER_DEBTthe miss was harmless: the package was red-listed anyway. After #5357 the ledger is empty and the specifier leg is a hard requirement for every specifier-preserving package. A blind spot in a hard requirement is a hole a regression can sit in permanently, and the failure direction is the bad one: the gate reports clean, the artifact ships broken, and only the nightly load leg can see it — attributed to whichever module the entry happened to reach first.It is not exotic prose either. A line comment naming a package glob (
@scope/*), a path pattern, or a wildcard import is ordinary in this repository.Suggested shape
The cheap correct fix is to mask in ONE pass that knows which context it is in, rather than two independent
replacecalls whose order decides the answer — a scanner that walks the source once and blanks whichever of//or/*opens first, honouring the newline-preservation the current implementation is careful about (that care is load-bearing; see the note abovewithoutCommentedCode).The stronger option is to take the specifier set from the TypeScript parser outright, since
typescriptis already a dependency andemittedSources()already knows exactly which files to hand it. That removes the whole class — line comments, block comments, template literals and regex literals stop being questions at all — at the cost of parsing 400-odd files instead of running a regex over them, which measured under two seconds forapp-shell.Whichever shape is chosen, the pin worth having is the measured one: a fixture whose line comment contains
@scope/*followed by a real extensionless import, asserting the import is still found.Related: #5367 (
readTsconfig()'s comment stripper, same script, same class), #5357 (where this was found), #4538 (the gate's origin).