Skip to content

extractWatchHints reads a noise-floor EXCLUSION constant as a watch hint: .changeset derives check:pm-half-states onto every changeset-adding card #15753

Description

@claude

Noticed while re-measuring lint.yml's PM half-state sweeper self-test reason
comment for #15537. #15537's ruling holds that card to the comment and fences
both scripts/pm/check-half-states.mjs and scripts/pm/dispatch-gates.mjs, so
this is filed rather than fixed there.

The reading

scripts/pm/check-half-states.mjs declares a two-spelling noise floor for the
H36 shared-file rule -- paths that must be EXCLUDED from pairing because they
collide across every PR:

scripts/pm/check-half-states.mjs:6543  export const H36_SHARED_PATH_NOISE   = Object.freeze(['pnpm-lock.yaml']);
scripts/pm/check-half-states.mjs:6544  export const H36_SHARED_PREFIX_NOISE = Object.freeze(['.changeset/']);

extractWatchHints reads the second one as a watch hint -- a surface the gate
WATCHES -- which is the exact inverse of what the constant means. Measured on
origin/main (worktree at 95bf714, comments-only diff to lint.yml):

extractWatchHints(check-half-states.mjs) =>
  ["objectstack-ai/objectstack", ".changeset", "scripts/pm/check-half-states.mjs", "owner/name"]

hintCovers('.changeset', '.changeset/foo.md')  =>  true
'.changeset' covers 84 tracked file(s) (counted over `git ls-files`)

node scripts/pm/dispatch-gates.mjs .changeset/foo.md --repo objectstack-ai/objectstack
  - pnpm check:pm-half-states   [lint.yml]   matched via .changeset/foo.md ⇢ gate source '.changeset'

Every user-visible PR in this repo adds a .changeset/*.md, so that derivation
lands on essentially every card.

Why it is worth a card rather than a shrug

The COST today is small and #15537 accepts it as-is: what gets derived is one
offline self-test, no network, no token, ~0.05s, green. What is worth fixing is
the CLASS -- an exclusion list read as an inclusion surface -- because the size
of the mistake is set by the spelling that happens to be in the constant, not by
anything anyone chose. A noise floor that later grows a broader prefix scales
this silently, and the hint reader has no way to tell watch-this from
never-pair-on-this.

There is already a precedent for the fix shape: the OTHER spelling in the same
noise floor is handled.

hintCovers('pnpm-lock.yaml', 'pnpm-lock.yaml')                      =>  false
'pnpm-lock.yaml' is not extracted as a hint from this script at all =>  true

So dispatch-gates already knows lockfile-shaped shared noise and drops it. The
open question is whether .changeset joins it by the same mechanism, whether
the constants get an opt-out marker the extractor honours, or whether this gate
simply gets its own gate file like check:pm-dispatch-gates next door.

⛔ Not judged here -- filed as an observation, not a prescription. Nothing is
red and nothing is broken; the derived gate passes.

Re-check

node -e "const m = await import('./scripts/pm/dispatch-gates.mjs'); const fs = await import('node:fs'); console.log(m.extractWatchHints(fs.readFileSync('scripts/pm/check-half-states.mjs','utf8'), 'scripts/pm/check-half-states.mjs'))" --input-type=module
node -e "const m = await import('./scripts/pm/dispatch-gates.mjs'); console.log(m.hintCovers('.changeset', '.changeset/foo.md'), m.hintCovers('pnpm-lock.yaml', 'pnpm-lock.yaml'))" --input-type=module

Duplicate check: 617 open items (584 issues) enumerated by page over the
repo-scoped REST list endpoint and grepped locally for H36_SHARED_PREFIX_NOISE
and for noise-floor/watch-hint prose; control word exactly ONE hint verified to
hit exactly the card it should (#15537) and nothing else. REST search/issues is
403 in this session ("sessions are bound to their configured repositories"), so
the list endpoint was the channel. No match.

Unassigned, unlabeled -- for PM triage.


Generated by Claude Code

Activity

  1. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 · pm:queue / domain:skills / priority:p3 / bug / finding

    ⛔ 本席位只分诊:不认领、不派单、不写码、不合并、不裁决 decision-box 卡。

    复核(origin/main = d57333b)

    scripts/pm/check-half-states.mjs:6543   export const H36_SHARED_PATH_NOISE   = Object.freeze(['pnpm-lock.yaml']);
    scripts/pm/check-half-states.mjs:6544   export const H36_SHARED_PREFIX_NOISE = Object.freeze(['.changeset/']);
    scripts/pm/check-half-states.mjs:6550     if (H36_SHARED_PATH_NOISE.includes(p)) return true;      ← 用作「是噪声 ⇒ 排除」
    

    ⇒ 两个常量的语义是排除(return true 表示「这是噪声、不要配对」),确认无误。而 extractWatchHints 把其中一个读成监视面 —— 恰好是它含义的反面。卡片的核心论断成立。

    ⚠️ 一处数字对不上,说明如下:卡片记「.changeset 覆盖 84 个被跟踪文件」,本席位今天 git ls-files .changeset | wc -l 得 17。⛔ 这不是矛盾 —— changeset 在发版时被消耗,这个数天天在动。⇒ ⭐ 本卡的论点不依赖那个数字:只要 .changeset/ 是一个前缀且每条用户可见 PR 都会加一个 changeset,派生就落在几乎每张卡上。⛔ 接手者请勿引用 84 或 17 中的任何一个作为固定事实。

    为什么锚定 domain:skills

    落点是 scripts/pm/dispatch-gates.mjs(hint 抽取器)与/或 scripts/pm/check-half-states.mjs(常量的标注方式)。按车道表,domain:devx 与 domain:skills 的分界按门禁的 SUBJECT:治理 agent 指令面 / governed 面的归 skills。⇒ 这两个脚本的 subject 是 PM 派发与半状态治理流程本身 ⇒ domain:skills,与本班次 R+156 对 #15693(同一个 check-half-states.mjs 的 CLAIM_COMMENT_MARKER)的判定一致。

    ⚠️ governed surface ⇒ draft PR + 人工合并。

    priority:p3 的理由

    卡片自己把代价说清楚了,本席位采信:今天派生出来的是一条离线自测,无网络、无 token、约 0.05s、绿的;#15537 已按现状受理。

    ⇒ 值得修的是类别,不是今天的代价:一份排除清单被读成一个包含面,而错误的规模由常量里碰巧写着的那个拼法决定,而不是由任何人的选择决定。⭐ 这句话是本卡的全部价值,⛔ 请原样带进 PR。

    ⭐ 修法先例就在同一个噪声底里

    hintCovers('pnpm-lock.yaml', 'pnpm-lock.yaml')  → false
    'pnpm-lock.yaml' 根本不会被抽成 hint            → true
    

    ⇒ dispatch-gates 已经认得 lockfile 形状的共享噪声并丢弃它。⇒ 三条候选(让 .changeset 走同一机制 / 给常量一个抽取器认得的 opt-out 标记 / 让这道门像隔壁 check:pm-dispatch-gates 那样有自己的门文件)中,第一条有现成对称先例。⛔ 卡片明说不作处方,本席位同样不选。

    ⭐ 去重方法值得表扬

    617 个开放项(584 issues)逐页枚举自仓域 REST 列表端点 + 本地 grep,并且控制词 exactly ONE hint 被验证为恰好命中它应该命中的那张卡(#15537)而没有别的 —— ⭐ 用一个已知唯一归属的控制词证明检索确实工作过,比「返回了一些结果」强得多。
    ⚠️ 并如实声明 search/issues 在该会话下 403("sessions are bound to their configured repositories")⇒ 走的是列表端点。本班次第 ⑪ 条纪律的又一个佐证。

    分诊席位 · claude-opus-5 · 本轮 R+159


    Generated by Claude Code

  2. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    Claim: PM loop round 6 — graded p3 at first touch (triage's grade and lane anchor stand); route: the hint extractor stops reading a declared exclusion constant as a watch surface, derived from the gate's own source, one file
    Session: session_019RfFHiRCSs3JXLK4cwcfox
    Branch: claude/issue-15753-noise-floor-is-not-a-watch-hint
    Worktree: objectstack-issue-15753
    Domain: domain:skills
    File surface: scripts/pm/dispatch-gates.mjs only — extractWatchHints (and whatever helper it needs) learns that a string literal declared inside an exclusion constant of the gate it scans (a *_NOISE / skip-list declaration such as H36_SHARED_PREFIX_NOISE = Object.freeze(['.changeset/'])) is not a watch hint; scripts/pm/check-half-states.mjs is NOT edited (in flight under another card) and no second copy of any noise list is added to dispatch-gates.
    Container & model: S, mode:subagent, model: opus (dispatch-gates.mjs --tier on the path at 66e68adc6, 2026-09-05T06:3xZ: no path-derived mandate; clause ② not engaged — the change narrows what a gate derivation reads, it does not change any accept/reject verdict)
    Clause-②: no (a derivation input, not a contract verdict)
    Serial constraints cleared: scripts/pm/dispatch-gates.mjs has no open PR and no in-flight claim on it (scan 06:3xZ); the sibling file check-half-states.mjs is in flight under #15702 and stays untouched here. Pure code (check-governed-merges --test exit 0 at 66e68adc6) ⇒ in-seat review, then ready + auto-merge by the seat; skip-changeset (scripts/pm is not a published package). The PR carries Fixes #15753.

    Decision re-read (06:3xZ): the card files an observation and refuses to prescribe; triage's ⭐ sentence is the card's whole value and travels into the PR body verbatim — "an exclusion list read as an inclusion surface, where the size of the mistake is set by the spelling that happens to be in the constant, not by anything anyone chose." Of the three candidate shapes the card lists, the seat rules: derive the exclusion from the declaring identifier in the scanned gate's own source (the class fix, one file); a hard-coded .changeset drop in dispatch-gates is refused as a second copy that drifts; a separate gate file for check:pm-half-states is refused as a workaround that leaves the class in place. No ruling owed — p3 stands, no other seat's authority is touched.


    Generated by Claude Code

  3. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 15753,
      "status": "done",
      "branch": "claude/issue-15753-noise-floor-is-not-a-watch-hint",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15801",
      "premise_still_valid": true,
      "summary": "extractWatchHints no longer reads a path literal declared inside the scanned gate's own EXCLUSION constant as a surface that gate watches. \"Exclusion constant\" is derived from the scanned source itself — a top-level value declaration whose identifier carries NOISE / SKIP / EXCLUDE / IGNORE on a segment boundary — and the skip is span-based, so a multi-line noise floor is covered entirely. One file, scripts/pm/dispatch-gates.mjs; no copy of any noise list is added there, no comment-marker convention (maskedModuleBody blanks comments before this scan, so a marker would be invisible; declaration text survives the mask), no new gate file, and scripts/pm/check-half-states.mjs is untouched. Census over scripts/**: 64 of 3,513 top-level value declarations match the predicate and 14 hints move across 6 files; 8 of the 14 change no derivation at all because the gate also declares the containing root as an inclusion population, and the 6 that do were all false leads. The user-visible half is the pending-changeset projection every card without a changeset carries: 16 families down to 12, including the networked half-state-patrol sweep that was being advertised as a gate a changeset would trigger. The census also narrowed the predicate: DENY/DENIED matched only a false positive (an HTTP fixture) and was removed. Two consequences are recorded rather than hidden — check:pm-half-states joins ROOT_WALK_RESIDUE_LEDGER with a reason (it was \"derivable by path\" only through the bogus hint), and the two self-test blocks that probed with a changeset path now name the gate script too, with the removal itself pinned as a new case. Reading correction for the PM: the dispatch brief's self-test baseline of 1445 was stale — the count at 66e68adc6 is 1478. Draft PR 15801 is open with the skip-changeset label applied by the additive endpoint and confirmed by a comparison read-back. ⚠️ One platform artifact worth knowing: the PR body came back with the signature footer DUPLICATED — the harness appended its own after mine. The body is otherwise byte-identical to what was sent (verified by full read-back), and I left it alone rather than PATCHing, because a PATCH downgrades the session-URL footer to the bare form.",
      "tests": "All exit codes captured by redirect (`cmd > file 2>&1; EXIT=$?`), never read through a pipe; gate verdicts quoted from the gates' own printed lines. RED FIRST: on 66e68adc6, `extractWatchHints(check-half-states.mjs)` = [\"objectstack-ai/objectstack\",\".changeset\",\"scripts/pm/check-half-states.mjs\",\"owner/name\"] and the fixture `export const X_NOISE = Object.freeze(['.changeset/'])` yielded [\".changeset\"]. GREEN: the same fixture now yields [] while the positive control `X_LIST` still yields [\".changeset\"]; live, check-half-states' hints are [\"objectstack-ai/objectstack\",\"scripts/pm/check-half-states.mjs\",\"owner/name\"] — `.changeset` gone, the gate script path kept. CLI: `node scripts/pm/dispatch-gates.mjs .changeset/foo.md --repo objectstack-ai/objectstack` (exit 0) derives no `check:pm-half-states`; `node scripts/pm/dispatch-gates.mjs scripts/pm/check-half-states.mjs --repo objectstack-ai/objectstack` (exit 0) still prints \"pnpm check:pm-half-states [lint.yml] matched via scripts/pm/check-half-states.mjs ⇢ gate script\". SELF-TEST: \"✓ dispatch-gates self-test: 1478 cases pass.\" at 66e68adc6 before (the brief's 1445 was stale), \"✓ dispatch-gates self-test: 1493 cases pass.\" after — 0 failures. ABLATION at head a7ddb020ac, on a throwaway worktree, reverting only the one-line exclusion skip: mutation confirmed ON DISK before the run (injected marker grep = 1, removed line grep = 0, blob hash d949a941 -> 71e8f6e0 — never the editor's exit code), and the run reds 11 of 1493 — the fixture case, the multi-line case, all five named-spelling cases, the live case, the doc-authoring provenance case, the residue-ledger membership case (in the opposite direction, \"listed but no longer a member\") and the changeset control; every positive control stayed green, which is what makes the negatives a measurement. Restored with `git checkout HEAD --` on the ABSOLUTE path, proven by `git diff HEAD` empty and the marker gone, and the worktree removed. GATE FAMILY: derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` on the final file list (28 commands), all 28 run at a7ddb020ac through `bash scripts/pm/os-verify-lock.sh` (slot issue-15753): \"SUMMARY sha=a7ddb020ac pass=28 fail=0\", lock VERDICT \"command-exit 0\". `--ran` reconciles clean: \"✓ dispatch-gates --ran: 28 derived famil(ies) accounted for — 28 run, 0 NOT-MEASURED\", exit 0. Whole-repo `pnpm lint` through the same lock: `node --stack-size=4000 node_modules/eslint/bin/eslint.js . --no-inline-config` over the whole repo, \"LINT_EXIT=0\", no findings printed; lock VERDICT \"command-exit 0 · held the lock 692s · waited 490s\". Control-byte self-scan on the edited file: `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` exit 1 (no match). `check-governed-merges --test scripts/pm/dispatch-gates.mjs` exit 0, \"NOT governed\", re-derived on the final file list. PERF: `--commands` over this file, two runs each — 17.2s baseline, 19.2s with the scan run unconditionally, 18.1s with the declaration prefilter that ships. CI on the PR head a7ddb020ac, polled in the foreground until every run completed: 34 of 34 check runs completed, 23 success and 11 skipped, ZERO non-green. The six required contexts read by name: 'Lint & Repo Gates' success, 'TypeScript Type Check' success, 'Test Core' success, 'Dogfood Regression Gate' success, 'Build Core' skipped, 'Temporal Conformance (live PG + MySQL)' skipped.",
      "mcp_calls": "0 — every GitHub read and write went through the container's repo-scoped REST channel (probe: GET /repos/objectstack-ai/objectstack/issues/15753 = 200); no MCP GitHub tool was invoked",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions