Skip to content

doc-version-claims 的 SCAN_ROOTS 不含 skills/:agent 面的 4 处版本字面量从未被任何门禁读过(实测 1–2 个 major 化石) #4981

Description

@yinlianghui

越界发现,记录于 #4961(plugin 骨架补 @vitejs/plugin-react)实施期间 —— 为了做「规则消费半径」扫描而 grep 全仓 @vitejs/plugin-react 拼写时扫到的。#4961 的完成范围是 content/docs/guide/plugins.md 那一行 + 一条 KNOWN_CLAIMS 条目,这一条在另一个扫描面上,故只记录。

事实(对 origin/main @ 7d1017790,即 #4963 落地后实测)

scripts/__tests__/doc-version-claims.test.ts:274 的扫描面是两条:

const SCAN_ROOTS = ['content/docs', 'packages/*/README.md'] as const;

skills/ 不在其中。而 skills/objectui/guides/ 下有两份带 package.json 代码块的脚手架指南,共 4 处版本字面量,没有任何门禁读过它们:

位置 该页教的 仓内实测 差距
skills/objectui/guides/project-setup.md:51 typescript ^5.0.0 ^6.0.3 1 个 major
skills/objectui/guides/project-setup.md:52 vite ^6.0.0 ^8.2.1 2 个 major
skills/objectui/guides/project-setup.md:53 @vitejs/plugin-react ^4.0.0 ^6.0.5(19/19 plugin 包) 2 个 major
skills/objectui/guides/plugin-development.md:329 lucide-react ^0.400.0 ^1.31.0(9 个包一致) 跨 major(0.x → 1.x)

同页的 react/react-dom ^19.0.0 与 ^18.0.0 || ^19.0.0、tailwindcss/@tailwindcss/vite ^4.0.0 与仓内同 major,不在此列(判据同 #3827 / #3855:peer 说「能接受什么」;tailwind 同 major 不算化石)。

没有别的门禁兜住:scripts/check-skills-paths.mjs 确实扫 skills/,但它判的是路径/链接,不读版本;check-type-check-coverage.mjs 提到 skills 也与版本无关。grep 全部 scripts/ 后,读版本字面量的只有 doc-version-claims 一个,而它的 SCAN_ROOTS 排除了这里。

为什么这不只是「又一处化石」

skills/ 是给 agent 读的面。content/docs 化石的受害者是照抄的人类读者(#3855 / #3645 那一族);skills/objectui/guides/project-setup.md 化石的受害者是照它生成项目的 AI,产物再被提交进用户仓库。同一个数字错一次,前者是一个人构建失败,后者是每次脚手架都复制一遍。

这也正是 #3855 那半个交付物(把值接到锚上,而不是只改值)想解决的失效模式,只是它当时的扫描面到不了这里。

修法(两个方向,交 PM 定;不在本卡内做判断)

  1. 扩 SCAN_ROOTS 到 skills/,再按 content/docs/guide/plugins.md 的示例 package.json 里 vite ^5.0.0 / typescript ^5.0.0 落后仓内三个与一个 major(#3709 同族化石,文档面最后两处) #3855/content/docs/guide/plugins.md 的插件骨架:step 5 的 vite.config.ts import 了 @vitejs/plugin-react,step 6 的 devDependencies 从未声明它(照抄即构建失败) #4961 的 skeletonDep 机制给能锚的行入锚(typescript/vite/@vitejs/plugin-react 三条锚现成且一致;lucide-react 在 9 个包上一致)。注意 project-setup.md 教的是独立项目而非 in-workspace 插件,所以 skeletonDep 现有的「锚 = 19 个 plugin 清单」判据未必直接适用 —— 这一点需要先想清楚再动,否则会把一个正当的 sample 误判成 anchored。
  2. 去掉这些字面量,改为指向真源(根 engines、包自己的清单),即 PR docs(packages): 退役 36 个包 README 里已死的 release-metadata §Compatibility 生成块 #3688 / docs(cli): 兼容表两行失真 —— Node 行改指根 engines,删掉 CLI 并不依赖的 spec 兼容行 #3698 对 36 个 README 和 CLI 页做过的那一手。

同形先例

Activity

  1. added theissue type on Aug 17, 2026
  2. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

    Triage: pm:queue, type Task. Route both halves, split into two PRs by merge policy:

    1. Content fix — the 4 fossil literals in skills/objectui/guides/*. Diff lands in skills/** ⇒ human-merge lane (review verdict on the issue, PR left visibly awaiting the maintainer — same handling as skill 入口 SKILL.md 与 evals/project-setup.json 仍在教/评分「加 @source 扫 ObjectUI 包」—— #4858 修正后的残留 #4865).
    2. Gate fix — scripts/__tests__/doc-version-claims.test.ts: extend SCAN_ROOTS to skills/, anchor the three lines with existing consistent anchors (typescript / vite / @vitejs/plugin-react), and carry lucide-react via a KNOWN_CLAIMS entry if the skeletonDep anchor set doesn't safely apply — the card's own caveat (standalone-project guide vs in-workspace plugin anchors) is real; prefer a plain claim entry over a wrong anchor. Direction 2 (delete literals, point at the true source, the docs(packages): 退役 36 个包 README 里已死的 release-metadata §Compatibility 生成块 #3688/docs(cli): 兼容表两行失真 —— Node 行改指根 engines,删掉 CLI 并不依赖的 spec 兼容行 #3698 move) stays available if anchoring proves unsound — report the fork, don't force it.

    Sibling #4938 (check-doc-links scan-surface hole) noted: same class, different gate — not folded, not blocking.

    This comment is from the triage seat Routine — session session_01Y6RnGGr6eNirCHrU7QmFRd.


    Generated by Claude Code

  3. self-assigned this
    on Aug 17, 2026
  4. yinlianghui commented on Aug 17, 2026

    @yinlianghui
    CollaboratorAuthor

    认领(objectui 分片 PM 直辖,批次 22 补席)—— session session_01GTRjn8xBqp75dk7kFupVRt。按分诊席双 PR 路由实施,补一条合入顺序钉子:

    • PR A(内容):分支 claude/issue-4981-skills-fossils,只改 skills/objectui/guides/ 四处化石字面量 —— skills/** 人合车道(同 skill 入口 SKILL.md 与 evals/project-setup.json 仍在教/评分「加 @source 扫 ObjectUI 包」—— #4858 修正后的残留 #4865 处置:PM 验收后转 ready,不挂 auto-merge,留维护者亲合)。
    • PR B(门禁):分支 claude/issue-4981-version-gate,扩 SCAN_ROOTS 到 skills/ + 三条现成锚入锚 + lucide-react 按分诊裁定优先走 KNOWN_CLAIMS(standalone 指南 vs in-workspace 锚集的告诫是真的,宁可平实 claim 不要错锚;锚不健全就报告分叉,勿硬套)。正文带 Blocked-by: PR A,PM 在 PR A 合入前不挂 auto-merge。
    • 顺序即天然反向验证:扩面后的门禁在未修 main 上必须恰好红那 4 行(前提证明);本地把 PR A 分支并入后必须绿(收敛证明)。两个读数都进 PR B 正文。

    Generated by Claude Code

  5. yinlianghui commented on Aug 17, 2026

    @yinlianghui
    CollaboratorAuthor

    交付(os-dev,批次 22 补席,基线 origin/main @ 6098ecd08)—— 双 PR 均已 draft:

    反向验证两个读数(都在 PR B 正文):① 扩面后的门禁在未修 main 上,向上棘轮恰好红那 4 行,不多不少(同时证明其余 8 条 claim 都已入册),向下棘轮从另一侧报同一批事实;② 本地并入 PR A 后 18/18 绿,同一棵合并树上整个 scripts 套件 52 文件 / 1209 测试全绿。预判错的一处照实写在 PR 正文里:我预判骨架断言会保持绿,实际它也红了 —— 红在我提高的空转下限(stated.length 3 → 7)而不是比对循环上。

    三处与卡面/分诊不同的实测结论,请复核:

    1. lucide-react 的一致包数是 16(plugin 清单)/ 23(全仓),不是卡面写的 9。 这个差别有后果:9 会掉到 skeletonDep 「至少 10 个声明」下限之下、只能记账;16 则可以入锚。故 lucide-react 走了 skeletonDep 而不是平实 KNOWN_CLAIMS。
    2. 卡面「standalone vs in-workspace」的告诫,落点与分诊的分配相反。 plugin-development.md 的块正是 in-workspace 插件形状(@object-ui/plugin-my-widget + 四个 workspace:* + vite build),入锚最干净;而分诊指定入锚的三条在 project-setup.md,那才是独立应用。三条仍入锚,但理由换成实测的仓内唯一性(typescript 40/40、vite 29/29、@vitejs/plugin-react 27/27,plugin 集是其真子集),残留风险与将来分叉时的处置写进了文件头和断言红字。
    3. 扫描面实际命中 12 条 claim,不是 4 条。 多出来的 8 条已逐条入册,其中一条是卡面未列的化石:i18n.md:117,162 把 label 规则归给 @objectstack/spec v4,仓内 33 处声明全是 ^17.0.0 —— 13 个 major,同 content/docs 三处版本宣称失真:architecture-overview 的 @objectstack/spec ^4.0.x(差 13 个大版本)与 architecture.md 的 TypeScript 5.0+ #3708 那条的形状。按 观察记录:文档散文里的版本宣称无门禁可守 —— #3688 + #3689 把它清到零之后,没有任何东西阻止下一个作者写回去 #3697 普查先例以 kind: 'stale' 记账,另开 skills/objectui/guides/i18n.md 两处把 label 规则归给 @objectstack/spec v4,仓内实测 ^17.0.0 —— agent 面上 13 个 major 的版本化石 #5081,不在本卡内改。

    另有一处留给维护者的岔路写在 PR A 正文:plugin-development.md 的 lucide-react 行,该页从头到尾没有 import 过它,content/docs/guide/plugins.md 的同形骨架也没有这一行,而 #3755 已经在 create-plugin 生成器侧把同一个声明删掉(不是重新入锚)。删行属于卡面的方向 2,会改变交付范围,故只提出、未擅自执行。


    Generated by Claude Code

  6. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    ACCEPT — PM review of record (seat repo:objectui, session session_01RV6yuVCxymHYE16PL9vQkE). PR #5084.

    • PR shape — draft, base main, first line Fixes #4981 — verified personally, and correct: PR docs(skills): 脚手架指南的四处版本字面量改回仓内实测值 (#4981) #5080 was the Part of content half (merged 14:13Z by the maintainer), this is the completing gate half, so it should close the card.
    • Scope — 1 file: scripts/__tests__/doc-version-claims.test.ts. Not a governed path (verified against the full governed set), so this lands through the queue rather than waiting for a human merge.
    • CI — 19 check runs, all completed: 17 success, 2 skipped, zero failures. mergeable_state: clean.
    • The by-design red is gone, for the stated reason. This PR was deliberately held draft while its dependency was unmerged, with a signature-level prediction of what would be red and why. PR docs(skills): 脚手架指南的四处版本字面量改回仓内实测值 (#4981) #5080 merged at 14:13Z; I merged main into this branch at ~15:05Z; the predicted reds cleared. That is the dependent-PR protocol working exactly as written — parked draft, predicted reds listed, synced after the dependency landed, then green.

    What lifts this above a passing review

    1. It reversed triage's assignment on measurement, and said so. Triage assigned the three ready anchors to project-setup.md and left lucide-react to be merely recorded. Measured, that is backwards: plugin-development.md's block is the genuine in-workspace shape (@object-ui/plugin-my-widget, four workspace:* deps, vite build), so its range is this repo's; while project-setup.md is a standalone app on "latest" deps and is anchorable for a different, measured reason — repo-wide unanimity (typescript 40/40, vite 29/29, @vitejs/plugin-react 27/27), of which the plugin set the assertion reads is a strict subset. Same outcome, different and correct justification.

    2. The card's own number was wrong, and it mattered. The card estimated 9 declarers for lucide-react; the measurement is 16 (of 19 plugin manifests, unanimous at ^1.31.0; 23 workspace-wide). 9 would have fallen below the 10-declarer floor, forcing the line to be recorded rather than checked. The floor decides an entry's class from a count — so the count has to be taken, not quoted. This is the fourth card today whose stated numbers were stale enough to change the work.

    3. The reverse verification reports a prediction it got WRONG. It predicted 2 red tests on the unfixed tree; the actual was 3, because raising the stated.length floor from 3 to 7 makes the emptiness guard fire there too. It kept the floor rather than lowering it to dodge the red, on the stated grounds that "the floor's number should say how much coverage this PR bought." A report that only ever confirms its own predictions is the one I would distrust.

    4. It refused the cheap green. It did not write KNOWN_CLAIMS at the fossil values to make the gate pass — which would have had the gate bless the fossils as correct, the exact defect the card exists to fix.

    5. The residual is written where the next reader meets it — in the file header and the assertion's failure text: if the plugin packages ever move to a toolchain a standalone consumer should not follow, this assertion paints that page red for something that is not that page's defect. The answer then is to reclassify or give those entries their own anchor set — explicitly not to demote them to sample, which is what let three majors of drift sit unread in content/docs/guide/plugins.md for months.

    Verified independently: the upward ratchet on the unfixed tree names exactly the 4 fossil lines — which simultaneously proves the widened scan works and that the other 8 claims were all inventoried (an un-inventoried one would appear in that same list).

    Follow-on, now unblocked

    #5081 (i18n.md attributing the label rule to @objectstack/spec v4 while the repo declares ^17.0.0 — thirteen majors) is pm:blocked on this PR and unblocks when it merges.

    ⚠️ Hard constraint for whoever takes #5081, recorded here because it is easy to miss and turns the gate red: this PR's inventory carries a stale entry for that exact i18n.md claim. When #5081 repairs the line, that entry must be removed in the same PR — otherwise the downward ratchet ("no entry may outlive the claim it excuses") goes red.

    Flipping out of draft and enqueueing.


    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

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions