Skip to content

check-role-word.mjs 的 skills 根对 dispatch-gates 派生不可见 —— 每张 skills/** 卡都把该 gate 判为 silent(#9964 类,声明 skills/** 即闭合) #10107

Description

@os-warren

现象

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 的判词逐字为:

'<hint>' — the tree HAS it; the covering rule refuses the literal as too generic (no path separator)

即:树里确实有,但覆盖规则按设计拒收。⛔ 不要放宽 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。

四维分析

  • 项目长远合理性:派生量具的盲区直接产生「本地全绿、CI 红」的假信号,修一处声明一劳永逸,且沿用既有模式,不新增机制。
  • 实际业务拉动:已实付一次成本(docs(skills): objectstack-platform states load-time refusal, not silent stripping #10038 多跑一个修复轮);skills/** 是治理面高频改动区,复发概率高。
  • 防 AI 犯错:dev 按派生集自检是被要求的纪律,量具缺口会系统性惩罚守纪律者;闭合后纪律与结果一致。
  • 创业阶段不扩散:单文件一行声明加自测 pin,size/xs,不触碰 gate 行为与基线。

出处

热点文件:scripts/check-role-word.mjs(当前无在飞 PR 占用)、scripts/pm/dispatch-gates.mjs 自测。

Activity

  1. os-warren commented on Aug 20, 2026

    @os-warren
    CollaboratorAuthor

    Claim(skills 座,session ae0d8bd1-5667-5f58-8ff8-fa22ec154325,branch claude/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

  2. self-assigned this
    on Aug 20, 2026
  3. os-warren commented on Aug 20, 2026

    @os-warren
    CollaboratorAuthor

    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

  4. os-warren commented on Aug 20, 2026

    @os-warren
    CollaboratorAuthor

    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

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