Skip to content

refactor(devx): js-comment-mask publishes maskCommentsAndLiterals, and its two callers import it (#15594) - #15774

Merged
baozhoutao merged 3 commits into
mainfrom
claude/issue-15594-mask-comments-and-literals
Sep 5, 2026
Merged

refactor(devx): js-comment-mask publishes maskCommentsAndLiterals, and its two callers import it (#15594)#15774
baozhoutao merged 3 commits into
mainfrom
claude/issue-15594-mask-comments-and-literals

Conversation

@claude

@claude claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #15594

scripts/js-comment-mask.mjs published stripComments and maskComments but no
comments+literals projection — the mask a gate needs when its signal is a bare CODE
position (new SchemaRegistry(, a property key) and a spelling inside prose or
inside a string/template must not satisfy it. Two gates on main re-derived it, under
two names, with no shared pin. This exports it once, declares it, pins it, and converts
both.

What landed

  • scripts/js-comment-mask.mjsexport function maskCommentsAndLiterals(source),
    documented beside maskComments in the module's own terms (the stripComments
    docblock already argues the module owns the projections rather than the callers, and
    names the two that existed; this is the third).
  • scripts/js-comment-mask.d.mts — declared beside maskComments.
  • Nine self-test rows under a literal roster and floor of their own
    (SELF_TEST_PROJECTION_BATTERIES, floor 9) — the shape the shared-recogniser section
    established, so a deleted or renamed row names itself in the refusal instead of
    quietly lowering a count it also supplies.
  • scripts/check-adr-0087-registration.mjs — local maskCommentsAndLiterals
    (comment.map((c, i) => c || literal[i])) replaced by the import.
  • scripts/check-registry-log-declared.mjs — local maskCode
    (comment[i] | literal[i] over a Uint8Array) replaced by the import, both call
    sites renamed, and its docblock updated: it said the copy "stays local only because
    js-comment-mask.mjs publishes no comments+literals projection yet, and hoisting one
    waits on a follow-up card". That card is this one, so the sentence moves with the
    conversion rather than becoming a stale assertion. What survives is the fact that
    belongs to this gate — that it reads two projections of one source and relies on
    them agreeing offset-for-offset.

Each conversion also drops the blank / scanSource imports that the local copy was
the only remaining user of.

Why the new rows are not in the cases table

That table asserts the OPPOSITE property for quoted text: REAL code inside a string
MUST survive maskComments and stripComments, and a row that removed one would be
reporting a bug. Under maskCommentsAndLiterals that same spelling must NOT survive.
One fixture cannot state both directions, so the rows are driven in their own section.

Behaviour byte-identical — the four diffs

Captured as > file 2>&1; echo EXIT=$?, exit code read before any pipe.

gate invocation diff before vs after
check-adr-0087-registration plain DIFF_EXIT=0
check-adr-0087-registration --self-test DIFF_EXIT=0
check-registry-log-declared plain DIFF_EXIT=0
check-registry-log-declared --self-test DIFF_EXIT=0

Verdict lines, identical on both sides:

✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (0 non-breaking changeset(s) seen).
✓ check-adr-0087-registration --self-test: 304 assertions over real temp git repos (real scan()/assertInputs() path)
OK: 72 vitest-running package(s) walked, 8 selected as engine-booting, every one declares a recognised registry log level (debug/info/warn/error/silent).
self-test OK: 16 cases + level-vocabulary read + population declaration + real-tree selection floor.

Module self-test, before and after (both EXIT=0):

before  ✓ js-comment-mask self-test: 44 cases pass (23 mask/strip corpus, 12 interpolation view, 9 shared recogniser).
after   ✓ js-comment-mask self-test: 53 cases pass (23 mask/strip corpus, 12 interpolation view, 9 shared recogniser, 9 comments+literals projection).

The new rows can fail — two ablation legs

Both legs ran from a COMMITTED implementation, mutated with an absolute-path
trap … EXIT INT TERM restore, and each mutation was confirmed on disk by counting the
removed text and the injected text separately before any reading was taken.

Leg A — rename the export. Injected-spelling count 0 → 1, original 1 → 0.

check-adr-0087-registration   EXIT=1
  SyntaxError: The requested module './js-comment-mask.mjs' does not provide an export named 'maskCommentsAndLiterals'
check-registry-log-declared   EXIT=1
  SyntaxError: The requested module './js-comment-mask.mjs' does not provide an export named 'maskCommentsAndLiterals'
check-declaration-mirrors     EXIT=1
  scripts/js-comment-mask.d.mts:89 declares `maskCommentsAndLiterals`, but scripts/js-comment-mask.mjs does not export it.

Leg B — drop the literal term (flags[k] = comment[k] | literal[k]
flags[k] = comment[k]), which is the mutation a vacuous case would sit through.
EXIT=1, four of the nine new rows red, and the corpus-wide row names the first
disagreeing fixture:

✗ ...nor one inside a STRING
✗ ...nor one inside a TEMPLATE
✗ the projection IS blank(source, comment OR literal), recomputed independently here
✗ ...and that equality holds on every row of the corpus table too -- "string containing a block-comment opener"
✗ js-comment-mask self-test: 4 failure(s) (cases and floor).

The first attempt at leg B was a no-op — a perl -0pi anchor whose \Q…\E quoting
did not match. The on-disk count caught it (removed-text count 1 -> 1), the run was
declared void and re-done with a correct anchor. Recording it because a silently re-run
ablation is the same defect one layer up.

Restore proven on both legs, not assumed:

hash-object: 7956343dd85bdef8924372cb2cdec45fcd54501d
HEAD blob : 7956343dd85bdef8924372cb2cdec45fcd54501d
git diff HEAD --stat -> empty

The 23 vitest importers cannot be affected, and that is measured

js-comment-mask.mjs is imported by 23 test files across the workspace, none of which
this worktree can run (no installed dependencies). Rather than declare them to CI on the
strength of "the change looks additive", every export that existed on origin/main was
run over the whole tracked corpus under both module versions and compared byte for byte:

differential: 5950 tracked source file(s) run through EVERY export that existed on origin/main.
✓ ZERO drift — maskComments, stripComments, blank and all three scanSource flag arrays are byte-identical base vs HEAD.

The importers name only pre-existing exports, and no pre-existing export moved. That is
the narrowing this PR relies on; CI still runs them.

Hot-file fence — scripts/measure-self-test-floor.mjs LEFT UNTOUCHED

GET /repos/objectstack-ai/objectstack/pulls/15758 at the moment that step was reached:

state: open | merged: false | merged_at: null | draft: false | mergeable_state: clean

The fence condition (merged: true) is not met, so that file keeps its own
maskCommentsAndLiterals (:258) and this PR does not touch it. The PM re-points it in
a follow-up.

The other consumer named in the ledger

Seat post #6023 §3 lists scripts/check-adr-0087-registration.mjs under PR #15724 as
well as under this card. #15724 (open, draft, governed) does edit that file — but only
to ADD two call sites of maskCommentsAndLiterals (:2484 region) and to widen
parseSymbolRef. It does not touch the function's definition, which is the only region
this PR changes, and it calls the function under the same name this PR keeps. The two
diffs are textually disjoint and semantically compatible; nothing here needs #15724 to
move first, and #15724 needs no rewrite to sit on top of this.

Gate family

Derived — not guessed — from the merged tree, with the repo asserted:

node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack
EXIT=0
dispatch-gates: gate list derived from the tree of 'objectstack-ai/objectstack' at commit 2491c9043
change set derived from git — 4 path(s) vs merge base ef60224fc of 'origin/main' and HEAD

The first derivation warned STALE TREE (.github/workflows/lint.yml had moved), so
origin/main was fetched and merged and the family re-derived; the two command lists are
identical (diff exit 0) and the second run carries no staleness warning.

Green (31): all 41 derived commands were attempted. Of the 22 bare-node ones, 17
are green, including check-comment-mask-adoption (+ --self-test),
check-declaration-mirrors (+ --self-test), check-self-test-wired (+ --self-test),
check-self-test-workflow-commands (+ --self-test), both converted gates plain and
--self-test, and js-comment-mask --self-test. Of the 19 pnpm ones, 14 are green,
including check:nul-bytes, check:parse-guard, check:entry-guard,
check:changeset-gate-self-tests, check:pm-dispatch-gates and
check:ratchet-remedy-authority.

✓ check-nul-bytes --self-test: 75 assertions over a temp git repo (real scan() path)
check-nul-bytes: OK (scanned 7612 text file(s) -- 7612 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).
OK: 4 hand-written declaration(s) agree with their modules on name, kind and required arity.

NOT MEASURED, by name — every one is a missing dependency in this worktree, never a
finding, and each is declared to CI:

command reason
scripts/check-comment-mask-corpus.mjs exit 1 — ERR_MODULE_NOT_FOUND @typescript-eslint/parser
packages/lint/scripts/check-reference-carrier-shape.mjs (+ --self-test) exit 3 — PREREQUISITE NOT MET: typescript
scripts/check-ci-filter-parity.mjs exit 3 — PREREQUISITE NOT MET: yaml
scripts/check-closing-keyword-parity.mjs (+ --self-test) exit 3 — PREREQUISITE NOT MET: yaml
pnpm check:driver-memory-census exit 3 — PREREQUISITE NOT MET: typescript
pnpm check:type-check-debt exit 3 — --re-measure refuses without a built closure; its own --self-test leg printed green
pnpm --filter @objectstack/lint run check:doc-formula-expressions exit 3 — PREREQUISITE NOT MET: typescript
pnpm --filter @objectstack/spec run check:browser-reachable-entries tsx not installed
pnpm --filter @objectstack/spec run check:error-code-provenance tsx not installed
pnpm lint (repo-wide eslint) eslint not installed here — NOT a narrowing, simply not run

dispatch-gates also printed three families whose argv takes a value only a CI run
supplies (check-cross-package-test-inputs --union-into "$RUNNER_TEMP/…",
check-shard-attestation --shard ${{ matrix.shard }}, check-test-completeness "$RUNNER_TEMP/test-core.log") — NOT MEASURED by construction.

Governance and changeset

node scripts/pm/check-governed-merges.mjs --test THE.FOUR.CHANGED.PATHS   EXIT=0
governed-surface predicate: 0 of 4 path(s) hit the register (5 surfaces, repo-agnostic).
  ✅  NOT governed — ordinary queue landing applies to a PR with exactly this file list.

scripts/check-adr-0087-registration.mjs is under scripts/, not docs/adr/, and the
register agrees.

No changeset: all four paths are root scripts/ tooling in the private workspace root
(@objectstack/spec-monorepo, private: true), so per AGENTS.md the skip-changeset
label is the right disposition — "that label is for a diff that publishes nothing from
any released package."

Out of scope, filed separately

The card says two callers re-derive this projection on main. Re-derived on this
branch's base, that is an undercount: eight more files spell the same
comment | literalblank projection under eight more names (codeOnly twice,
maskedProjections, maskLiterals, maskProse, projections, project, plus the
fenced measure-self-test-floor.mjs). None is named by the ruling, two sit under other
PRs' hot-file rows (#15758, #15762), so none is touched here. Filed as a follow-up.

🤖 Generated with Claude Code

https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk


Generated by Claude Code

…ction

`js-comment-mask.mjs` published `stripComments` and `maskComments` but no
comments+literals projection -- the mask a gate needs when its signal is a
bare CODE position and a spelling inside prose *or* inside a string must not
satisfy it. Two gates on main re-derived it under two names.

Export `maskCommentsAndLiterals(source)`, documented beside `maskComments` in
the module's own terms (the `stripComments` docblock already argues the module
owns the projections rather than the callers), declared in the `.d.mts`, and
driven by nine new self-test rows under a literal roster and floor of their
own -- the shape the recogniser section established, so a deleted or renamed
row names itself in the refusal.

The rows live outside the `cases` table on purpose: that table asserts the
OPPOSITE property for quoted text (`REAL` code inside a string MUST survive
`maskComments`), and one fixture cannot state both directions.

Self-test: 44 -> 53 cases (23 mask/strip corpus, 12 interpolation view,
9 shared recogniser, 9 comments+literals projection).

Part of #15594

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
…f re-deriving it

`check-adr-0087-registration.mjs` spelled it `maskCommentsAndLiterals`
(`comment.map((c, i) => c || literal[i])`); `check-registry-log-declared.mjs`
spelled it `maskCode` (`comment[i] | literal[i]` over a Uint8Array). Same
projection, two names, no shared pin. Both are now the module's export, and
each file's now-unused `blank`/`scanSource` imports go with the local copy.

`maskCode`'s docblock said it stays "local only because js-comment-mask.mjs
publishes no comments+literals projection yet, and hoisting one waits on a
follow-up card". That card is this one, so the docblock moves with the
conversion rather than becoming a stale assertion: what survives is the fact
that belongs to THIS gate -- that it reads two projections of one source and
relies on them agreeing offset-for-offset.

Behaviour byte-identical, proven per gate by diffing plain and `--self-test`
output before and after (four diffs, exit 0 each):

  check-adr-0087-registration  ✓ ... no declared-breaking changeset
                               ✓ ... 304 assertions over real temp git repos
  check-registry-log-declared  OK: 72 vitest-running package(s) walked, 8 ...
                               self-test OK: 16 cases + level-vocabulary read ...

Part of #15594

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 5, 2026
@github-actions github-actions Bot added the size/m label Sep 5, 2026
@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — PR #15774 (head 2491c9043, 4 files, +181/−35) reviewed against the GitHub three-dot diff, not the report.

As ruled: maskCommentsAndLiterals(source) exported from scripts/js-comment-mask.mjs, declared in js-comment-mask.d.mts beside maskComments, pinned by nine new self-test rows under their own roster + floor (deliberately outside the cases table, which asserts the opposite property for quoted text); both consumers converted (check-adr-0087-registration.mjs local copy removed; check-registry-log-declared.mjs maskCode removed with its "until this card lands" docblock moved); measure-self-test-floor.mjs correctly left alone (fence: #15758 was not merged when the dev reached that step — it has since landed as 014248855, so its copy goes to the follow-up card).

Measured by this seat: module --self-test branch → EXIT=0 ✓ js-comment-mask self-test: 53 cases pass (23 mask/strip corpus, 12 interpolation view, 9 shared recogniser, 9 comments+literals projection) vs main 44 cases pass; both consumers' plain outputs on a detached probe of the branch vs main → EXIT=0 both, byte-IDENTICAL (diff clean); git merge-tree --write-tree clean against origin/main AND against governed draft #15724's branch (which also edits check-adr-0087-registration.mjs — disjoint hunks; the seat post row is corrected to say #15724 modifies, not merely reads, that file); governed test per the report 0 of 4 … NOT governed. NOT MEASURED here: the 23 vitest importers (covered by the dev's 5950-file zero-drift differential) and the typescript / yaml / @typescript-eslint/parser / tsx gates by name.

The unfiled finding (eight MORE scripts re-derive the projection under eight names) is filed by this seat — see the card note. Part of #15594 trailers in the two commits: recorded as a deviation, not a rework (squash-merge keeps the PR body's Fixes as the relation; check:partof-closing-keyword exit 0).

Flip + arm when both required jobs report success on 2491c9043.


Generated by Claude Code

@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Flipped ready + auto-merge enabled (07:34Z, method: MERGE). Required jobs on 2491c9043: Lint & Repo Gates = success, TypeScript Type Check = success; git merge-tree --write-tree origin/main <branch> → EXIT=0 against the current main. Watched; on landing #15594 closes and #15776 (the eight remaining copies) becomes dispatchable.


Generated by Claude Code

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

Development

Successfully merging this pull request may close these issues.

[finding] hoist maskCommentsAndLiterals (comments+literals blank) into js-comment-mask.mjs + .d.mts

2 participants