Skip to content

chore(scripts,ci): 决策框架四份副本的结构同构门禁(轴数/轴序/绑定句)(#5798) - #5865

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5798-skill-frame-sync-gate
Aug 6, 2026
Merged

chore(scripts,ci): 决策框架四份副本的结构同构门禁(轴数/轴序/绑定句)(#5798)#5865
os-zhuang merged 1 commit into
mainfrom
claude/issue-5798-skill-frame-sync-gate

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5798

按 PM 裁定的方向 A 最小形态落地:比对两份手写副本的框架结构,不逐字比对。⛔ 未做方向 B(单一来源派生)。

问题

同一套 binding 决策评估框架在仓库里手写了四份,跨三个文件:

副本 位置 面向
internal-pm .claude/skills/pm-dispatch/SKILL.md 第 8 步 内部 agent 协议
internal-dev .claude/agents/os-dev.md 内部 agent 协议
published-pm skills/objectstack-pm-dispatch/SKILL.md 正文 第三方(npx skills add 直读仓库)
published-dev 同一文件内嵌的 dev-agent 模板 第三方

此前没有任何门禁比对它们。#5130(2026-08-04)把内部框架由两轴扩为三轴,发布版镜像原封不动,分叉存在两天:装了这份 skill 的第三方 PM agent 按两轴呈报,本仓按三轴,直到人工发现,再花一个 issue 加一轮维护者裁决(#5451 / PR #5799)才补齐。本门禁是 #5130 当时缺的那个机制。

判据(结构同构,⛔ 刻意不逐字)

⛔ 不逐字比对是刻意的:#5451 裁定 B 让发布版泛化(去掉「创业项目」自我描述与本仓单号,扩张姿态改由安装方 conventions 文件声明)。逐字门禁会永久红灯,然后被删掉而不是被遵守。self-test 有一例专门钉这个方向:只改措辞必须保持绿

两条设计约束来自既有教训

提取失败即红(#4690 反模式):锚点找不到框架段就失败并点名,绝不静默跳过。

锚点不含被比对的值(#5680):锚点捕获数词(%N%)而不写死「three」。这一条初稿踩过 —— 英文绑定句的量词与轴数共变(三轴写 all three axes,两轴写 both axes),所以写死 all 的锚点等价于把轴数写进锚点。拿真实的 pre-#5799(77adf297f^)一跑,初稿报的是:

• published-pm … the BINDING sentence is not in place: its anchor matched 0 time(s)
• published-dev … the BINDING sentence is not in place: its anchor matched 0 time(s)

真相是「这份两轴、其它三轴」,而门禁说的是「找不到绑定句」—— 正是 #5680 讲的把「不相等」降级成「提取不到」。改为 %Q%(吞掉整个量词、只捕获数词)后,同一棵树报的是:

• the four copies do not agree on the NUMBER of axes — this is the #5130 drift:
      3 axes  internal-pm  (.claude/skills/pm-dispatch/SKILL.md)
      3 axes  internal-dev  (.claude/agents/os-dev.md)
      2 axes  published-pm  (skills/objectstack-pm-dispatch/SKILL.md)
      2 axes  published-dev  (skills/objectstack-pm-dispatch/SKILL.md)
• the copies do not agree on the axis NAME SEQUENCE:
      business-need → long-term-soundness → ai-authoring-safety
        in: internal-pm, internal-dev
      long-term-soundness → ai-authoring-safety
        in: published-pm, published-dev

即准确指出发布版当时缺的是 business-need 轴。这是本 PR 最强的一条证据:门禁拿真实的历史现场跑出真实的诊断,不只是过合成 fixture。self-test 里 two-axis copy phrased "both axes" → red on the COUNT, not on extraction 一例把这个教训钉住(该例还断言错误信息里不得出现「找不到」类措辞)。

分层理由(为何是根 scripts/,不是 packages/spec)

既有的 check:skill-refs / check:skill-docs 住在 spec 包,是因为它们是生成器(源为 packages/spec/src),并登记在 spec 的 check:generated 账本里。本门禁不读 spec 源、不产出任何生成物,只比对两份手写文档,其中一份(.claude/**)spec 包本不该知道;放进去还会被拖进那个「有生成器」的账本。跨仓 prose 策略门禁历来住在根 scripts/(check:role-word / check:doc-authoring / check:nul-bytes 都从这里扫 skills/)。

已跑 check:generated --reconcile-only 确认无关联:该账本只读 packages/spec/package.json,18 check: + 13 gen: 全部已分类,绿。

CI 接线

.github/workflows/lint.ymltypecheck job(必需检查、无 paths filter),紧邻 check:skill-refs,理由注释引用它那条同源理由 ——「These ship to third parties via npx skills add, so the drift is served straight to consumers' agents」。SKILL.md 正文同样这么发出去,而既有两个门禁只看 frontmatter 与生成索引。放这个 job 也是同一条反休眠理由;.claude/** 过滤器尤其会瞎掉,因为有一半副本住在那里。

验证

真仓现状比对 绿(#5799 已合,首跑即绿,符合本单排序理由):

✓ check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 files
  3 axes: business-need → long-term-soundness → ai-authoring-safety
  binding sentence present in all 4; 4 count mention(s) agree; 39 markdown files scanned for undeclared copies.

pnpm check:skill-frame-sync(self-test + 真仓,CI 即这条)—— 12 例全过,先红后绿四个方向都在:

  ✓ baseline: the four real copies are isomorphic
  ✓ one copy loses an axis, its declared count unchanged → red (count vs entries)
  ✓ one copy coherently rewritten to two axes → red (cross-copy count)
  ✓ two-axis copy phrased "both axes" → red on the COUNT, not on extraction
  ✓ an axis is renamed in one copy → red (unmapped axis name)
  ✓ axes reordered in one copy → red (name sequence)
  ✓ the binding sentence is removed → red (extraction failure, not a skip)
  ✓ the declaring sentence is removed → red (extraction failure, not a skip)
  ✓ a frame count mention drifts from the frame → red
  ✓ AXIS_MAP misaligned (zh patterns swapped) → red, proving the map is load-bearing
  ✓ an undeclared fifth copy appears → red (anti-dormancy)
  ✓ wording-only divergence (the #5451 generalization) → stays GREEN
✓ check-skill-frame-sync self-test: 12 cases pass.

fixture 用的是真实文档在内存里的变体,不是合成样本;每处变异都断言「确实改到了」,所以 fixture 漂移会响亮失败,而不是让一个期望红的用例悄悄变绿(实跑中确实触发过一次:structurally hard to 的换行位置对不上,门禁自己报了 self-test fixture drifted: mutation target not found)。

其余门禁:

  • node scripts/check-nul-bytes.mjsOK (scanned 5692 tracked text file(s) … no raw ASCII control bytes)
  • node scripts/check-workflow-status-functions.mjs(动了 workflow)→ OK (scanned 22 workflow file(s), 39 job(s), 21 job-level if: … all naming a status function),--self-test 34 断言过
  • 改动文件自扫控制字符(grep -naP 的 C0 + DEL 字符类)→ 零命中
  • npx eslint scripts/check-skill-frame-sync.mjs → 干净

changeset

.changeset/*.md:改动面为门禁脚本 + CI + 根 package.json 脚本,非发布面,对使用者无行为变化 —— 走 skip-changeset 标签路线。


Generated by Claude Code

同一套 binding 决策评估框架在仓库里手写了**四份**,跨三个文件:内部 agent
协议两份(`.claude/skills/pm-dispatch/SKILL.md` 的第 8 步、`.claude/agents/os-dev.md`),
已发布给第三方的一份文件里两份(`skills/objectstack-pm-dispatch/SKILL.md`
的正文 + 它内嵌的 dev-agent 模板)。此前**没有任何门禁比对它们**。

后果实测发生过:#5130(2026-08-04)把内部框架由两轴扩为三轴,发布版镜像
原封不动,分叉存在两天 —— 装了这份 skill 的第三方 PM agent 按两轴呈报,
本仓按三轴 —— 直到人工发现,再花一个 issue 加一轮维护者裁决(#5451 /
PR #5799)才补齐。本门禁就是 #5130 当时缺的那个机制。

判据是**结构同构,刻意不逐字**:

- 轴数(每份自洽,且四份相等);
- 轴名序列,经脚本内显式的 `AXIS_MAP`(内部中文 ⇄ 发布英文名词映射)比对,
  顺序有意义;
- 绑定句在位(声明了轴却不把推荐意见绑定到轴上,就不是同一套框架);
- 文件内其它处对轴数的引述(`the deep three-axis analysis`)与框架一致 ——
  #5130 当时也得改这一处。

⛔ 不做逐字比对:#5451 裁定 B 让发布版**刻意泛化**(去掉「创业项目」自我
描述与本仓单号,扩张姿态改由安装方 conventions 文件声明)。逐字门禁会永久
红灯,然后被删掉而不是被遵守。self-test 有一例专门钉这个方向:只改措辞
(抹掉单号、替换自我描述)必须**保持绿**。

两条设计约束来自既有教训:

- **提取失败即红**(#4690 反模式):锚点找不到框架段就失败并点名,绝不静默
  跳过。
- **锚点不含被比对的值**(#5680):锚点**捕获**数词(`%N%`)而非写死
  「three」。这一条初稿踩过:英文绑定句的量词与轴数**共变**(三轴写
  `all three axes`,两轴写 `both axes`),写死 `all` 的锚点拿真实的
  pre-#5799 树一跑,报的是「绑定句不在位」,而真相是「这份两轴、其它三轴」
  —— 正是 #5680 说的把「不相等」降级成「提取不到」。改为 `%Q%` 吞掉整个
  量词后,报的是轴数分歧,并准确指出发布版缺的是 business-need 轴。

分层:门禁放根 `scripts/`,不放 `packages/spec`。既有的 `check:skill-refs` /
`check:skill-docs` 在 spec 包里,是因为它们是**生成器**(源是
`packages/spec/src`),并登记在 spec 的 check:generated 账本里;本门禁不读
spec 源、不产出任何生成物,只比对两份手写文档,其中一份(`.claude/**`)
spec 包本不该知道。跨仓 prose 策略门禁历来住在根 scripts/
(check:role-word / check:doc-authoring / check:nul-bytes 都从这里扫 skills/)。

CI 接线在 `lint.yml` 的 typecheck job,紧邻 `check:skill-refs`,理由注释引用
它那条同源理由(「These ship to third parties via `npx skills add`, so the
drift is served straight to consumers' agents」)—— SKILL.md **正文**同样这么
发出去,而既有两个门禁只看 frontmatter 与生成索引。放这个 job 也是同一条
反休眠理由:无 paths filter 且为必需检查;`.claude/**` 过滤器尤其会瞎掉,
因为有一半副本住在那里。

另附反休眠判据:扫 `.claude/**` 与 `skills/**` 的 markdown,出现第五份带
框架指纹却未登记在 `COPIES` 的副本即红(#4291 手工输入清单腐坏的教训)。

Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 6, 2026 9:49am

Request Review

@github-actions github-actions Bot added size/l ci/cd dependencies Pull requests that update a dependency file labels Aug 6, 2026
@os-zhuang os-zhuang added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed ci/cd dependencies Pull requests that update a dependency file labels Aug 6, 2026 — with Claude
@os-zhuang
os-zhuang marked this pull request as ready for review August 6, 2026 09:56
@os-zhuang
os-zhuang enabled auto-merge August 6, 2026 09:56
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31091255496 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m scripts/strictness-ledger-doc.test.ts�[2m > �[22mthe ledger and the generated counts agree�[2m > �[22mis checked in current — the artifact on disk equals a fresh render
    

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 17 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants