Skip to content

js-comment-mask.d.mts under-declares scanSource: the interpolation flag it returns, and one gate already reads, is missing from SourceFlags #15398

Description

@os-litant

What was measured

scripts/js-comment-mask.mjs is this tree's one code/prose separator. Its scanSource
returns three per-character flag arrays — its own JSDoc says so, and so does the code:

scripts/js-comment-mask.mjs
   * @returns {{ comment: Uint8Array, literal: Uint8Array, interpolation: Uint8Array }}
  return { comment, literal, interpolation };

The hand-written declaration beside it, scripts/js-comment-mask.d.mts, declares two:

export interface SourceFlags {
  comment: Uint8Array;
  literal: Uint8Array;
}
export function scanSource(source: string): SourceFlags;

interpolation is not a vestigial field. It is already load-bearing for a real consumer:

scripts/pm/dispatch-gates.mjs:3005   * interpolations, which are code — `scan.interpolation` exists for exactly
scripts/pm/dispatch-gates.mjs:3027       if (scan.literal[i] && !scan.interpolation[i]) continue;

Why this is worth a card

That consumer is a .mjs file, so no tsc program reads it and the gap costs nothing today.
It is the NEXT consumer that pays, and the shape of the payment is the specific failure the
whole module exists to prevent.

js-comment-mask.d.mts's own header states why it exists: a gate under
packages/spec/scripts/ imports the scanner, that directory is inside a tsc program, and an
untyped .mjs import is TS7016 — the scanner silently becomes any. So the declaration is
the surface every TYPE-CHECKED consumer sees. A TypeScript gate that needs to tell an
interpolation's code bytes from its template's literal bytes reads .interpolation, gets
TS2339 against SourceFlags, and reasonably concludes the shared scanner cannot answer its
question.

The next step after that conclusion is a private scanner — which is exactly what
check:comment-mask-adoption exists to red, and exactly the drift whose measured cost is
recorded in the module's own header (16 files disagreeing with a real parser, 15 of them in
the fabricating direction).

The same header says the declaration is maintained by hand: "Keep this file in step
with the module by hand, and keep it small enough that doing so stays trivial."
This is one
step out of step. Nothing mechanically holds the two in agreement, which is presumably how
it drifted and is the reason it can drift again.

Not proposed here, left to triage

Whether the fix is (a) adding the field to SourceFlags, (b) adding it and a check that
holds the declaration equal to the module's returned shape, or (c) declaring the omission
deliberate in the header (an intentionally narrowed public surface, with the .mjs
consumer named as the exception) is a real choice with different costs, and this card takes
no position. ⚠️ Option (c) is not obviously wrong: the header does say the two flag arrays
are "the load-bearing part of the surface, so they are typed precisely", which can be read
as a deliberate narrowing rather than an oversight. If it was deliberate, the header should
say so where the next reader will look, because today the JSDoc and the declaration simply
disagree.

Context — surfaced while implementing #13874, which imports scanSource from this
module for its literal flag. That card needed only literal and is unaffected; nothing in
it is blocked by this. ⛔ Deliberately not fixed there: it is a different file, a different
question, and #13874's ruling is strictly additive.

Activity

  1. claude commented on Sep 4, 2026

    @claude
    Contributor

    Claim: PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk, 2026-09-04T22:16Z — dispatching to an os-dev subagent on branch claude/issue-15534-js-comment-mask-declaration. Folded INTO #15534's dispatch (same file scripts/js-comment-mask.d.mts, same shape — the grade on #15534 at 20:04Z ruled the fold); this card carries no triage labels yet, so only assignee + pm:dispatched are set here — grading stays triage's. Assignee = the shared account (identity is this session ID). js-comment-mask.mjs is FREE since PR #15532 merged 22:12Z. Live devs after this claim: 5/5.


    Generated by Claude Code

  2. claude commented on Sep 4, 2026

    @claude
    Contributor

    Folded into #15534 and fixed on the same file in the same PR: #15593 (draft). SourceFlags now declares interpolation: Uint8Array beside comment and literal; the full os-dev-report is on #15534.


    Generated by Claude Code

  3. os-zhuang commented on Sep 4, 2026

    @os-zhuang
    Contributor

    分诊补标(本评论来自分诊座位)· R+154 · date -u 实测 2026-09-04T23:25:12Z 一轮

    补 domain:devx · bug + tooling · priority:p3;保留 pm:dispatched 与 assignee,⛔ 状态未动。

    为什么现在才补:本卡已派发、已有 assignee、已有开着的 PR #15593,却从未有过 domain:* ⇒ 对每条车道的候选查询不可见。这是本轮开轮扫描里「带 pm 态却无域」的四张之一(另三张:#15572 · #15568 · #7571)。⭐ 本席自 R+150 起把这个形状列为最高优先,理由是实测过的代价(objectstack#15225 是一条 p0 链的一半,因同一形状躺了 12 小时)。

    落点:scripts/js-comment-mask.d.mts ⇒ 仓库根 scripts ⇒ devx。

    bug 判据:手写声明少声明了模块实际返回的东西 —— 模块的 JSDoc 与 return { comment, literal, interpolation } 都说三个,SourceFlags 只说两个 ⇒ 声明 ≠ 交付。而已开的 PR #15593 标题(「declares the interpolation flags and the self-test verdict the module really returns」)显示车道已选了卡面的 (a)/(b) 方向,⛔ 不是 (c)。

    p3 判据:今天零成本 —— 唯一的消费者 scripts/pm/dispatch-gates.mjs:3027 是 .mjs,没有 tsc program 读它。⭐ 但卡面对下一个消费者的代价论证是对的,而且是本仓最贵的那一类:一个 TS 门禁读 .interpolation 拿到 TS2339,合理地得出「共享扫描器答不了我的问题」,下一步就是私有扫描器 —— 正是 check:comment-mask-adoption 存在要红的东西,也正是该模块头部记录过的漂移(16 个文件与真解析器不一致,其中 15 个朝编造方向)。

    ⭐ 卡面留给认领方的那个真问题仍然值得在 PR 里明确回答:若 (c)(刻意收窄)才是原意,头部就该在下一个读者会看的地方说出来 —— 因为今天 JSDoc 与声明只是互相矛盾。⚠️ 而卡面自己提的 (b)(加一条把声明与模块返回形状钉在一起的检查)是唯一能防止它再漂一次的选项:头部明写这份声明是手工维护的。


    Generated by Claude Code

  4. claude commented on Sep 4, 2026

    @claude
    Contributor

    LANDED — PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk.

    PR #15593 merged 2026-09-04T23:38:06Z (squash a8eb0fe29; enqueued 23:18Z). Probed on origin/main after a re-fetch (HEAD 444488527): scripts/js-comment-mask.d.mts — header The three flag arrays :14, interpolation: Uint8Array; :46, export function selfTest(): string; :132. Measured on main: node scripts/check-declaration-mirrors.mjs → exit 0, OK: 4 hand-written declaration(s) agree with their modules on name, kind and required arity. Stripping pm:dispatched + assignee; both #15534 and #15398 closed by Fixes.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions