Skip to content

test(cli,create-plugin): 两处生成器锚规则改累积报全部漂移,前置与漂移分开 (#4974) - #4993

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-4974-anchor-cumulative-report
Aug 17, 2026
Merged

test(cli,create-plugin): 两处生成器锚规则改累积报全部漂移,前置与漂移分开 (#4974)#4993
yinlianghui merged 1 commit into
mainfrom
claude/issue-4974-anchor-cumulative-report

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes#4974

现象与根因

全仓两个「生成器模板区间 = 仓内区间」的 ratchet 规则,都在 for 循环里逐名 expect:

  • packages/cli/src/__tests__/app-generator.test.tssources every range from this repo instead of inventing one(DEPENDENCY_ANCHORS,22 个名字 × 三份生成清单)
  • packages/create-plugin/src/__tests__/templates.test.tssources every devDependency range from this repo instead of inventing them(DEV_DEPENDENCY_ANCHORS,8 个名字)

expect 失败即抛,表又是插入序遍历的,所以一轮只报第一个不匹配的名字,而被报的是「谁排在前面」,不是「谁更严重」。这条代价兑现过两次:#4098 有五个同窗 bump 藏在第一个后面;#4968lucide-react 排在 postcss / react / tailwindcss / typescript / vite 之前,遮住后面六个名字的判定 —— 当时只能另写脚本把两张表全量算一遍,才敢说这批 dependabot 到底漂了几处。

改法:两趟,前置与漂移分开

两处都拆成两趟(卡面要点 1):

  1. 前置断言 —— 「名字至少被一个生成器声明」「root 锚必须在根清单里」「仓内区间必须一致」都是关于仓库状态的前提,保持首败即抛;并且整趟跑在任何区间比对之前。锚不可解析时本来也没有可累积的期望值,更重要的是:前置败时不产出任何漂移行,不会让读者以为模板错了(见反向验证 ②)。
  2. 漂移断言 —— 收齐全部不匹配后一次断言空集,逐条带生成器、名字、实得区间、应得区间与锚来源。

顺带把两张锚表的文档块补上那对互补关系(卡面末段):范围规则只判本表内的名字,完备性规则要求生成清单的键集恰好等于本表 —— 两条规则分居两个 it,读任一处都看不到另一处。

反向验证(方向先预判,后跑;commit 后变异,git checkout 还原)

① 同时漂多处(核心验收,复刻 #4968 形状) —— 把 cli 侧 lucide-react 改回 ^1.29.0typescript 改回 ^5.9.3(排位在 lucide 之后,正是旧形态下被遮的那一类),create-plugin 侧 @testing-library/jest-dom 改回 ^7.0.0。预判「一轮全报」,实测新形态一次运行给出:

FAIL packages/cli/.../app-generator.test.ts > sources every range from this repo instead of inventing one
AssertionError: expected [ …(4) ] to deeply equal []
+ "routed manifest's lucide-react: ^1.29.0 must match its in-repo range, ^1.31.0",
+ "routed manifest's typescript: ^5.9.3 must match the repo root, ^6.0.3",
+ "plain manifest's typescript: ^5.9.3 must match the repo root, ^6.0.3",
+ "init manifest's typescript: ^5.9.3 must match the repo root, ^6.0.3",
FAIL packages/create-plugin/.../templates.test.ts > sources every devDependency range …
+ "@testing-library/jest-dom: ^7.0.0 must match the repo root, ^7.0.1",

同一变异下把两个测试文件换回 base sha 的旧版本(源码变异不动),旧形态实测:

AssertionError: routed manifest's lucide-react must match its in-repo range: expected '^1.29.0' to be '^1.31.0'
AssertionError: @testing-library/jest-dom range must match the repo root: expected '^7.0.0' to be '^7.0.1'

—— typescript 的三份清单一个字都没报。这就是「一轮修一个」的直接测量。

② 前置破坏 → 只红前置、不红漂移,两半都跑了:

  • ②a 锚表抠一条 —— 从 DEPENDENCY_ANCHORS 删掉 typescript 一行。预判「完备性红、漂移绿」,实测 1 failed | 40 passed:红的只有 keeps all three generated dependency maps under one anchor table(报文点名多出的 typescript),漂移那条绿 —— 该名字退出判定面,不产出任何漂移行,也不冒充漂移。
  • ②b 前置与漂移同时存在,且前置名次在后 —— 把一份 plugin 清单的 lucide-react 改成 ^1.30.0(制造仓内分裂,表内第 15 位),同时让 @tailwindcss/postcss(第 10 位)漂。预判「新形态只报前置」,实测新形态报文只有 in-repo manifests disagree on lucide-react: {…} — settle on one range first,没有一条 postcss 漂移行;旧形态同一变异只报 routed manifest's @tailwindcss/postcss must match its in-repo range: expected '^4.1.18' to be '^4.3.3',仓内分裂完全不可见 —— 照旧形态报文去改模板,改完会再撞上「锚本身有歧义」,正是卡面担心的串扰误导。

③ 单漂对照(报文可读性等价) —— 只漂 lucide-react。新形态 routed manifest's lucide-react: ^1.29.0 must match its in-repo range, ^1.31.0;旧形态 routed manifest's lucide-react must match its in-repo range: expected '^1.29.0' to be '^1.31.0'。名字、锚来源、实得、期望四项齐全,信息量未减,只是从 vitest 的 Object.is 差异挪进报文的一行。

刻意没动的一处

同文件的 writes a manifest whose @object-ui ranges name this CLI version 也是逐名 expect(9 个平台包,末尾另有一条 lucide 断言)。没改,理由是它判的是同一批事实:那 9 个区间同出一源(CLI 自身版本),而 lucide 那条与本 PR 改过的累积规则重合 —— 任何一处漂移都会被上面那条断言完整列出,所以此处遮蔽不增加修复轮数。它的另一个问题(取仓内区间的任意一员而不断言一致性)已单独立卡 #4991,不并入本 PR。

测试

pnpm exec vitest run packages/cli packages/create-plugin --maxWorkers=2
Test Files 6 passed (6) Tests 138 passed (138)
turbo run type-check --concurrency=2
Tasks: 81 successful, 81 total Time: 5m45s
node scripts/check-control-bytes.mjs
OK (scanned 4439 tracked text file(s))

另对改动的三个文件做了一次控制字符自扫(超出该门扫描面的那些码位),零命中。changeset 为测试-only 的空 frontmatter 声明,照 .changeset/app-shell-props-block-4808.md 先例。


Generated by Claude Code

`packages/cli` 的 `sources every range from this repo instead of inventing one` 与
`packages/create-plugin` 的 `sources every devDependency range from this repo instead of
inventing them` 都在 `for` 循环里逐名 `expect`。`expect` 失败即抛,所以一次只报第一个
不匹配的名字,而表是插入序遍历的 —— 被报的是「谁排在前面」,不是「谁更严重」。
代价兑现过两次:#4098 有五个同窗 bump 藏在第一个后面;#4968 里 `lucide-react` 排在
`postcss`/`react`/`tailwindcss`/`typescript`/`vite` 之前,遮住后面六个名字的判定,
只能另写脚本才敢说这批到底漂了几处。跨包那半更贵:create-plugin 那处当时在 `main`
上也是红的,但按包边界扫 cli 时没有任何东西报它,落地后仍要第二轮。
两处都改成两趟:
- 前置断言(锚必须能解析:名字至少被一个生成器声明、`root` 锚在根清单里、仓内区间
必须一致)保持首败即抛,并且整趟跑在任何区间比对之前 —— 前置败时不产出任何漂移行,
不与漂移串扰。锚不可解析时也没有可累积的期望值。
- 漂移断言累积后一次报空集,逐条带生成器、名字、实得区间与应得区间。
顺带把两张锚表的文档块补上「范围规则只判本表内的名字、完备性规则要求生成清单的键集
恰好等于本表」这一对互补关系 —— 两条规则分居两个 `it`,读任一处都看不到另一处。
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)24.7 KB350 KB
Entry fileindex-C3ETHWQr.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)9.56KB3.59KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)8.92KB3.41KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)25.13KB5.40KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.79KB
auth (createAuthenticatedFetch.js)6.34KB2.43KB
auth (index.js)2.71KB1.22KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.02KB0.88KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)26.07KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.65KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)498.83KB111.26KB
core (index.js)4.11KB1.62KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)159.03KB44.08KB
fields (index.js)233.27KB58.22KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.35KB1.38KB
i18n (pickLocalized.js)3.69KB1.73KB
i18n (provider.js)23.12KB7.62KB
i18n (useDisplayLocale.js)2.84KB1.45KB
i18n (useObjectLabel.js)27.59KB6.63KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)39.16KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.35KB3.31KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.42KB1.42KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.81KB0.83KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.75KB18.37KB
plugin-chatbot (index.js)181.21KB43.14KB
plugin-dashboard (index.js)127.85KB32.73KB
plugin-designer (index.js)212.39KB42.83KB
plugin-detail (index.js)240.05KB60.05KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)120.42KB29.03KB
plugin-gantt (index.js)164.10KB39.87KB
plugin-grid (index.js)197.61KB53.03KB
plugin-kanban (index.js)52.72KB14.54KB
plugin-list (index.js)111.22KB26.98KB
plugin-map (index.js)17.91KB5.72KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)41.97KB11.33KB
plugin-timeline (index.js)26.68KB7.66KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)83.81KB20.49KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)27.53KB9.41KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.28KB0.68KB
react (schema-input.js)1.45KB0.83KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (index.js)4.77KB2.16KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)10.76KB3.17KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)6.92KB2.40KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.05KB1.52KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@yinlianghuiClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM 验收 ✅ ACCEPT(#4974,批次 20)

实物核验(merge-base 7d1017790 与报告一致):3 files,+114/−29 —— 两处生成器锚规则改两趟制:前置断言(名字被生成器声明 / root 锚在根清单 / 仓内区间一致)保持首败即抛且整趟跑在任何区间比对之前,漂移收齐后一次断言空集、逐条带生成器+名字+实得+锚来源+应得。模型标识 msg/diff 双 0;releases/ 零触碰;changeset 空 frontmatter 按 test-only 先例。

CI 亲读:20/20 check runs completed,零失败(两项 path-filter skipped 计绿)。

反向验证读数:核心验收成立且旧形态的遮蔽被直接测量 —— 同一个三处同窗漂移变异,新形态一轮全报 5 行,base 版旧形态只报 2 条、typescript 三份清单一字不报。②b(前置名次在后 + 漂移名次在前)是 dev 自加的半边,证明了「分开」若仍交错会把仓内分裂整条藏住 —— 比卡面要求更强的形态有实测必要性支撑,deviation 记账成立。基线未 rebase 但用 merge --no-commit 在最新 main 内容下复验 138/138 绿后 abort,分支未污染,处置干净。

串台警报的 PM 核实:main 上 cf4f8a6e4(PR #4988 的 squash)提交信息确为本卡文案、diff 为 #4016 的 8 个文件 —— 已亲核 无错误关单(标题只有 (#4974) 引用、无关闭关键字;#4974 保持 open 由本 PR 关闭,#4016 由 PR4988 正常关闭),伤害面为 git 考古误导,不改写 main 历史;根因卡 #4994 待 PM 定级,防撞名条款已进后续派发词。finding #4991(锚表最小成员取值的自洽缺口,observation)入池。

undraft + auto-merge(squash)。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 17, 2026 13:08
@yinlianghui
yinlianghui added this pull request to the merge queueAug 17, 2026
Merged via the queue into main with commit ec1f4d7Aug 17, 2026
21 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-4974-anchor-cumulative-report branch August 17, 2026 13:09
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@yinlianghui@claude