Skip to content

Commit a0ab3e3

Browse files
committed
chore(scripts,ci): 决策框架四份副本的结构同构门禁(轴数/轴序/绑定句)(#5798)
同一套 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
1 parent 72c3c86 commit a0ab3e3

3 files changed

Lines changed: 793 additions & 0 deletions

File tree

.github/workflows/lint.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,30 @@ jobs:
616616
- name: Check generated skill references are in sync with the spec
617617
run: pnpm --filter @objectstack/spec check:skill-refs
618618

619+
# Same third-party reason as the two gates above, applied to the SKILL.md
620+
# PROSE those gates never look at: they compare frontmatter and generated
621+
# indexes only, so the binding decision frame in the body — written out four
622+
# times across `.claude/**` (internal) and `skills/**` (published) — had no
623+
# gate at all. #5130 widened the internal frame from two axes to three and
624+
# the published mirror stayed at two for two days, which is the drift
625+
# "served straight to consumers' agents" in its purest form: third-party PM
626+
# agents escalated on two axes while this repo ruled on three. #5451 /
627+
# PR #5799 re-converged them by hand; this keeps them converged.
628+
#
629+
# Structure only — axis count, axis order, the binding sentence. The
630+
# published copy is deliberately generalized (#5451 route B: no startup
631+
# self-description, no this-repo issue numbers), so a wording comparison
632+
# would be permanently red and would get deleted instead of obeyed.
633+
#
634+
# Lives in this job for the same reason as its neighbours: no paths filter
635+
# and required, so it cannot go dormant on exactly the PR that breaks it —
636+
# and a `.claude/**` filter in particular would blind it, since half the
637+
# copies live there. It is a root script (not `--filter @objectstack/spec`)
638+
# because it reads no spec source and generates no artifact; see the
639+
# script header on layering.
640+
- name: Check the decision frame is in sync across its four copies
641+
run: pnpm check:skill-frame-sync
642+
619643
- name: Check the react-blocks contract is in sync with the spec
620644
run: pnpm --filter @objectstack/spec check:react-blocks
621645

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"check:doc-authoring": "node scripts/check-doc-authoring.mjs --self-test && node scripts/check-doc-authoring.mjs",
3737
"check:docs-audit-scope": "node scripts/docs-audit/affected-docs.mjs --self-test && node scripts/docs-audit/check-audit-scope.mjs --self-test && node scripts/docs-audit/check-audit-scope.mjs",
3838
"check:role-word": "node scripts/check-role-word.mjs",
39+
"check:skill-frame-sync": "node scripts/check-skill-frame-sync.mjs --self-test && node scripts/check-skill-frame-sync.mjs",
3940
"check:adr-anchors": "node scripts/check-adr-anchors.mjs",
4041
"check:org-identifier": "node scripts/check-org-identifier.mjs",
4142
"check:authz-resolver": "node scripts/check-single-authz-resolver.mjs --self-test && node scripts/check-single-authz-resolver.mjs",

0 commit comments

Comments
 (0)