Repository navigation
extractWatchHints reads a noise-floor EXCLUSION constant as a watch hint: .changeset derives check:pm-half-states onto every changeset-adding card #15753
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Sep 5, 2026 分诊 ·
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
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.mjsonly —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 asH36_SHARED_PREFIX_NOISE = Object.freeze(['.changeset/'])) is not a watch hint;scripts/pm/check-half-states.mjsis 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 --tieron the path at66e68adc6, 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.mjshas no open PR and no in-flight claim on it (scan 06:3xZ); the sibling filecheck-half-states.mjsis in flight under #15702 and stays untouched here. Pure code (check-governed-merges --testexit 0 at66e68adc6) ⇒ in-seat review, then ready + auto-merge by the seat;skip-changeset(scripts/pm is not a published package). The PR carriesFixes #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
.changesetdrop in dispatch-gates is refused as a second copy that drifts; a separate gate file forcheck:pm-half-statesis 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
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
- added a commit that references this issue
on Sep 10, 2026 - added a commit that references this issue
on Sep 13, 2026
Noticed while re-measuring
lint.yml'sPM half-state sweeper self-testreasoncomment for #15537. #15537's ruling holds that card to the comment and fences
both
scripts/pm/check-half-states.mjsandscripts/pm/dispatch-gates.mjs, sothis is filed rather than fixed there.
The reading
scripts/pm/check-half-states.mjsdeclares a two-spelling noise floor for theH36 shared-file rule -- paths that must be EXCLUDED from pairing because they
collide across every PR:
extractWatchHintsreads the second one as a watch hint -- a surface the gateWATCHES -- which is the exact inverse of what the constant means. Measured on
origin/main(worktree at 95bf714, comments-only diff tolint.yml):Every user-visible PR in this repo adds a
.changeset/*.md, so that derivationlands 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.
So
dispatch-gatesalready knows lockfile-shaped shared noise and drops it. Theopen question is whether
.changesetjoins it by the same mechanism, whetherthe constants get an opt-out marker the extractor honours, or whether this gate
simply gets its own gate file like
check:pm-dispatch-gatesnext door.⛔ Not judged here -- filed as an observation, not a prescription. Nothing is
red and nothing is broken; the derived gate passes.
Re-check
Duplicate check: 617 open items (584 issues) enumerated by page over the
repo-scoped REST list endpoint and grepped locally for
H36_SHARED_PREFIX_NOISEand for noise-floor/watch-hint prose; control word
exactly ONE hintverified tohit exactly the card it should (#15537) and nothing else. REST
search/issuesis403 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