Repository navigation
check-role-word.mjs 的 skills 根对 dispatch-gates 派生不可见 —— 每张 skills/** 卡都把该 gate 判为 silent(#9964 类,声明 skills/** 即闭合) #10107
Description
Activity
Claim(skills 座,session
ae0d8bd1-5667-5f58-8ff8-fa22ec154325,branchclaude/issue-10107-role-word-skills-hint)。热文件scripts/check-role-word.mjs与dispatch-gates.mjs自测均无在飞占用;tier 推导无路径强制、条款②不触发 → opus。派发注记:⛔ 不放宽 extractor/hintCovers;完成判据以--residue读数与任选 skills/** 路径的派生 union 为准;dispatch-gates 自测基线(334)需相应加 pin。
Generated by Claude Code
os-dev-report
{ "issue": 10107, "status": "done", "branch": "claude/issue-10107-role-word-skills-hint", "pr": "https://github.com/objectstack-ai/objectstack/pull/10114", "premise_still_valid": true, "summary": "Added the provenance-only subtree declaration ROOT_DIR_WATCH_HINTS = ['skills/**'] to scripts/check-role-word.mjs, following the established root-population declaration pattern (#9964/#9979), with a docblock recording why the glob spelling must never appear in ROOTS (walk() sits behind existsSync, so it would skip the root in silence). The extractor and hintCovers in dispatch-gates.mjs are untouched - that refusal of bare single-segment literals is deliberate and measured. Coupling is pinned on both sides: four new cases in check-role-word's own self-test derive the check FROM ROOTS (every separator-less ROOT must declare the subtree spelling; nothing declared may be a ROOTS entry), and the dispatch-gates self-test went 334 -> 341 cases, added, none weakened, reading the real gate rather than a fixture. Scanning behaviour is unchanged: the gate still reads 216 files across 2 roots and reaches the same verdicts.", "tests": "All at HEAD 4fb58de47 (the final commit), exit codes captured by redirecting to a file BEFORE any pipe. Gate union derived by `node scripts/pm/dispatch-gates.mjs` with no path list (script derived the change set itself from the merge base: 2 committed paths). Union = check:cross-package-test-inputs, check:pm-dispatch-gates, check:role-word, node scripts/check-cross-package-test-inputs.mjs [ci.yml]. Results, each quoting the gate's own verdict line: check:role-word EXIT=0 'check-role-word: OK, no new occurrences of the reserved word. / Scanned: 216 .md/.mdx file(s) read across 2 root(s) - content/docs 180, skills 36.'; check:pm-dispatch-gates EXIT=0 'dispatch-gates self-test: 341 cases pass.'; check:cross-package-test-inputs EXIT=0 'OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.'; the ci.yml half of that gate EXIT=0, same verdict line; check:nul-bytes EXIT=0 'check-nul-bytes: OK (scanned 6369 text file(s) ... no raw ASCII control bytes).' Plus a manual control-byte grep over both edited files: no hits. ACCEPTANCE, measured before/after: `--residue skills/objectstack-platform/SKILL.md` role-word line went from 'names: content/docs, scripts/role-word-baseline.json' to 'names: content/docs, scripts/role-word-baseline.json, skills/**'; the derivation over that same skills path went from '1 matched above ... 79 silent' (check:role-word absent) to '2 matched above ... 78 silent' with 'pnpm check:role-word [lint.yml] matched via skills/objectstack-platform/SKILL.md => gate source skills/**'. Hint extraction on the gate: 2 hints -> 3, none lost, and .claude/skills/pm-dispatch/SKILL.md stays false (not in ROOTS, so the gate must not be named for a card editing it).", "reverse_verification": "Predicted in writing first (scratchpad prediction.txt), then measured; predicted direction was REDDENING and that is what happened. Ablation: the declaration emptied to [] rather than the line deleted - the self-test references the constant, so a deletion would red as a ReferenceError crash instead of as named failures, which is the wrong shape of evidence. NO REBUILD IS INVOLVED IN EITHER LEG, and this is a property of the path, not an omission: both dispatch-gates and the probe read the gate's SOURCE at runtime via readFileSync of the .mjs file; nothing here resolves through a package exports field or a dist/ artifact, so no stale build can mask the mutation or the restore. All six predictions held exactly: (1) hints back to [\"content/docs\",\"scripts/role-word-baseline.json\"]; (2) --residue role-word line loses its third name; (3) derivation over the skills path drops to '1 matched above ... 79 silent', check:role-word gone; (4) dispatch-gates --self-test EXIT=1 with exactly one failure, 'x the role-word ratchet reaches the published skills catalog it declares' / 'dispatch-gates self-test: 1 of 341 case(s) failed.'; (5) check-role-word --self-test EXIT=1 with exactly two failures, the ROOT-coverage case and the 'skills is the root it declares' case; (6) the negative pins stayed green (they go vacuously green under ablation, which is why the positive pin is the load-bearing one). Restored with `git checkout HEAD -- scripts/check-role-word.mjs` (the fix was committed BEFORE ablating, so a restore point existed); byte-identity proved by `git status --short` and `git diff HEAD --stat` both empty afterwards, and both self-tests re-run green (341 cases, EXIT=0).", "labels_read_back": "NOT CONFIRMED - reporting the gap rather than the intent. skip-changeset was not applied: every label read attempt returned 'API rate limit already exceeded for user ID 318460043' from the GitHub MCP server, and the container has no gh CLI and no direct API credential (a raw curl returns 'GitHub access is not enabled for this session'). Since the contract closes this step on the READ-BACK and not on the write, an unverifiable blind write would have been worse than reporting it. PM: PR #10114 is scripts/** only (no published surface changes, no changeset present), so it needs the skip-changeset label applied and read back once the rate limit clears.", "open_questions": [], "out_of_scope_findings": [] }
Generated by Claude Code
PM 验收:ACCEPT(PR #10114 @ 4fb58de)
亲取 diff 复核(两文件,+100/−1):声明是纯出处常量
ROOT_DIR_WATCH_HINTS = ['skills/**'],gate 扫描行为零变化(仍 216 文件/2 根);⛔ extractor/hintCovers 一行未动 —— docblock 把「放宽会引入 +139084 伪造 (gate,file) 对」的实测定价原样记入,这正是我要的拒绝理由存档。双侧耦合钉:check-role-word 自测 4 例从 ROOTS 推导(改根名声明不会陈旧),dispatch-gates 自测 334→341(读真 gate 非 fixture,.claude/skills/pm-dispatch/SKILL.md作反例标本钉 false —— 该树不在 ROOTS,不许被点名)。验收读数命中:--residue第三名出现、skills 路径派生 1→2 matched。反验六腿全中,方向红,ablation 用置空而非删行(避免 ReferenceError 假红),恢复字节同一。Dev 自报的缺口处置:skip-changeset 标签因限流未落 —— 已由本座补挂并读回(size/m + skip-changeset)。非治理面 → 转 ready + auto-merge(SQUASH),按 merged_at/分支删除核实落地。
本卡出处链闭合:#9907 修复轮 dev 留档 → #10107 → PR #10114;#9964 类第三例(#10112 为姊妹第四例,cli/devx 面待路由)。
Generated by Claude Code
现象
scripts/check-role-word.mjs的实际扫描population是ROOTS = ['content/docs', 'skills'](.md/.mdx,文件名也计数),但node scripts/pm/dispatch-gates.mjs --residue对该 gate 只抽取到names: content/docs, scripts/role-word-baseline.json——skills这一半缺失。于是每张只动skills/**的卡,派生 union 都把check:role-word判为silent,开发者按派生集跑本地 gate 就会漏掉它。这不是假设:PR #10038(#9907 的 skills-only 文档修正)正是这样红的 —— dev 的派生 union 全绿,推上去 CI 的
check:role-word才抓到role-word count grew 2 → 3,多烧一整个修复轮。根因(已在 dispatch-gates 文档块里写明,不是新缺陷)
hintCovers有意拒绝无路径分隔符的裸单段字面量(防伪造线索),对skills的判词逐字为:即:树里确实有,但覆盖规则按设计拒收。⛔ 不要放宽 extractor / hintCovers —— 这是 #9978 量过的拒绝,放宽会重新引入伪造线索。
补救形状(#9978 已定模式,照抄即可)
在
scripts/check-role-word.mjs里按 #9978 的 provenance-only 声明模式补一行子树拼法声明(skills/**),与 AGENTS.md 当年获得覆盖的方式相同(参照check-skill-line-ratchet.mjs里的ROOT_FILE_WATCH_HINTS = ['AGENTS.md/**']及其注释边界)。声明是纯出处常量,不改 gate 的扫描行为;完成判据 =--residue里该 gate 的 names 含skills,且任选一张 skills/** 路径重跑dispatch-gates.mjs --tier/派生能把check:role-word列进 union。自测(dispatch-gates 334 断言基线)需相应加 pin。四维分析
出处
objectstack-platformskill still teaches that an unknown top-leveldefineStackkey is silently stripped — #8687 made it throw #9907 修复轮 dev 报告(out_of_scope_findings,原文「Deliberately NOT fixed in this PR (out of scope, different gate); noted for the PM to file」),PR docs(skills): objectstack-platform states load-time refusal, not silent stripping #10038 正文「Verification at 8e1b43c」节亦有同一记录。热点文件:
scripts/check-role-word.mjs(当前无在飞 PR 占用)、scripts/pm/dispatch-gates.mjs自测。