From 208c22ce0c2716f9e2da3eebbd5f09e4d06f9499 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Fri, 18 Sep 2026 11:34:37 +0800 Subject: [PATCH] fix(04): enforce D-18 boundary and baseline-relative read-only gate in 04-02 --- .planning/phases/04-render-adaptation/04-02-PLAN.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.planning/phases/04-render-adaptation/04-02-PLAN.md b/.planning/phases/04-render-adaptation/04-02-PLAN.md index 805a9c6..1edff14 100644 --- a/.planning/phases/04-render-adaptation/04-02-PLAN.md +++ b/.planning/phases/04-render-adaptation/04-02-PLAN.md @@ -141,7 +141,7 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产 (2) 写 `## 背景`:D-17(对账暴露的主要失配点在勇士移动;下一步先接口探索并找出缺失接口,再适配)、本步只读、明确本步不实施适配、不裁决缺失接口(D-13)、不涉 `HeroRendering` 渲染状态(D-18)与 `core.*`(D-12)。同时写明范围的**另一侧**:勇士移动契约与移动钩子(`IObjectMover` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks`,`onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 等)在本步范围内;`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 不在范围内(D-18)。 (3) 写 `## 方法`(步骤 (0) 记录的「扫描前基线」bullet 也在本节):枚举两个面 ——(a)**数据端现行接口面** = `data-common` 移动器族(`IObjectMovable` / `IObjectMover` / `IObjectMoverHooks` / `IMoverController` / `ObjectMoveStep` 各族 / `ObjectAnimDirection`)+ `data-base/hero` 位置移动族(`IHeroState` / `IHeroLocation` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks` / `HeroMover`)+ 各 `index.ts` barrel 实际导出;(b)**渲染端勇士移动使用面** = `render/map/extension/{hero,types,manager}.ts` + `action/move.ts` + `client.ts` 接线点,`render/map/{moving,status,renderer}.ts` 仅作移动器契约参考(不登记为独立对账对象)。判定三态固定为 **匹配 / 渲染端需改 / 数据端缺失**;带 `// @ts-expect-error 需要重构` 的 import 一律先归「渲染端需改」,不归「数据端缺失」。证据纪律:每条含 `接口/成员名 + 两侧 file:line`;静态阅读不能定论者进「未能从阅读确定(未猜测)」,不写成缺失或错配。允许按 D-09 派只读子代理(仅回传文本、不得写文件)。 (4) 补齐 8 个二级标题(`## 背景` 与 `## 方法` 已在前两步写入,此处补齐其余 6 个占位,其后 Task 2/3 在同一骨架上横向补齐,不再改结构):`## 背景` / `## 方法` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`。 - (5) **端到端切片(本任务唯一实测对账行)**:以 `MapHeroRenderer` 构造期的钩子注册为对象,从渲染端 `packages-user/client-modules/src/render/map/extension/hero.ts:81`(`hero.addHook(new MapHeroHook(this))`)与 :457-516(`MapHeroHook implements Partial`)出发,打开数据端基准核对。**成员范围**:只登记移动/位置/跟随者钩子 —— `onSetPosition` / `onTurnHero` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onAddFollower` / `onRemoveFollower` / `onRemoveAllFollowers`;`onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 属 `HeroRendering` 贴图/alpha 子系统,按 D-18 **排除**(不写对账行,仅在范围排除说明中提及)。数据端核对:`addHook` 来自 `IHookable`(经 `packages-user/data-common/src/common/mover.ts:235` 的 `IObjectMover extends IHookable<...>` 承接),`onSetPos` 在 `IHeroLocationHooks`(`packages-user/data-base/src/hero/types.ts:283`)与 `IObjectMoverHooks`(`packages-user/data-common/src/common/mover.ts:216`)各有对位,而 `onSetPosition`/`onStartMove`/`onEndMove`/`onMoveHero`/`onJumpHero`/`onTurnHero`/`onAddFollower`/`onRemoveFollower`/`onRemoveAllFollowers` 在数据端无同名对位。据此在 `## 逐成员对账(...)` 下写表头 `| ID | 成员 | 渲染端锚点 | 数据端锚点 | 对账结论 | 说明 |` 与 `#04-02-R-01` 一行:成员=`IHeroMoveControllerHooks` 钩子族(移动/位置/跟随者子集);渲染端锚点=hero.ts:81,457-516;数据端锚点=`data-common/src/common/mover.ts:166,216` 与 `data-base/src/hero/types.ts:277,283`(逐项写「有对位」或「无对位,最接近:」);结论=`渲染端需改`(随 `IHeroMoveControllerHooks` 整体重构);说明中注明贴图/alpha 钩子按 D-18 不在本行范围。同时在 `## 数据端现行接口` 与 `## 渲染端现状` 各写至少 1 条带 `file:line` 的最小条目,证明三段相互可达。 + (5) **端到端切片(本任务唯一实测对账行)**:以 `MapHeroRenderer` 构造期的钩子注册为对象,从渲染端 `packages-user/client-modules/src/render/map/extension/hero.ts:81`(`hero.addHook(new MapHeroHook(this))`)与 :457-516(`MapHeroHook implements Partial`)出发,打开数据端基准核对。**成员范围**:只登记移动/位置/跟随者钩子 —— `onSetPosition` / `onTurnHero` / `onStartMove` / `onMoveHero` / `onEndMove` / `onJumpHero` / `onAddFollower` / `onRemoveFollower` / `onRemoveAllFollowers`;`onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 属 `HeroRendering` 贴图/alpha 子系统,按 D-18 **排除**(不写对账行,仅在范围排除说明中提及)。数据端核对:`addHook` 来自 `IHookable`(经 `packages-user/data-common/src/common/mover.ts:235` 的 `IObjectMover extends IHookable<...>` 承接),`onSetPos` 在 `IHeroLocationHooks`(`packages-user/data-base/src/hero/types.ts:283`)与 `IObjectMoverHooks`(`packages-user/data-common/src/common/mover.ts:216`)各有对位,而 `onSetPosition`/`onStartMove`/`onEndMove`/`onMoveHero`/`onJumpHero`/`onTurnHero`/`onAddFollower`/`onRemoveFollower`/`onRemoveAllFollowers` 在数据端无同名对位。据此在 `## 逐成员对账(...)` 下写表头 `| ID | 成员 | 渲染端锚点 | 数据端锚点 | 对账结论 | 说明 |` 与 `#04-02-R-01` 一行:成员=`IHeroMoveControllerHooks` 钩子族(移动/位置/跟随者子集);渲染端锚点=hero.ts:81,457-516;数据端锚点=`data-common/src/common/mover.ts:166,216` 与 `data-base/src/hero/types.ts:277,283`(逐项写「有对位」或「无对位,最接近:」);结论=`渲染端需改`(随 `IHeroMoveControllerHooks` 整体重构);说明中只写「贴图/alpha 钩子按 D-18 排除,不在本行范围」这类表述,**不得在本行内出现 `IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 字样**(这些字样只允许出现在「处置」的排除说明中,由 Task 3 门禁核对)。同时在 `## 数据端现行接口` 与 `## 渲染端现状` 各写至少 1 条带 `file:line` 的最小条目,证明三段相互可达。 (6) 写 `## 处置` 初稿:本步只读、未改任何生产代码;被查渲染端包相对「扫描前基线」零新增改动(用户既有 `packages-user/client-base/src/load/**` 改动保持原样,不回滚 / 不暂存 / 不提交);适配工作(04-03+)不在本 run 规划;明确 `HeroRendering` 渲染状态(D-18:`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`)与 `core.*`(D-12)为排除项,并说明勇士移动契约与移动钩子在范围内;缺失接口候选须经用户逐步裁决(D-13);`getHeroStatusOn` 类裸函数(D-11)不属本增量范围。 (7) 全程不得修改任何被查源码(含用户既有的 `packages-user/client-base/src/load/**` 未提交改动);本任务只写该交付文档一个文件。 @@ -219,15 +219,15 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产 - dev.md(「双端分离」「其他要求」) - (1) **逐成员对账段定稿**:为每个渲染端使用成员写一行 `#04-02-R-N`(N 从 2 递增,`R-01` 由 Task 1 占用),结论取 匹配 / 渲染端需改 / 数据端缺失 三态之一,两侧均给 `file:line`(数据端无对位时写「无对位,最接近:」)。至少覆盖:`HeroMover` / `IMoveController` 的 import 源与类型名(`action/move.ts:4` vs `data-base/src/hero/mover.ts:22`、`data-common/src/common/mover.ts:142`)→ 渲染端需改;`HeroAnimateDirection`(hero.ts:6,53,97,170,372,437;types.ts:5,147)→ 渲染端需改(改 `ObjectAnimDirection`,`data-common/src/common/mover.ts:40`);`IHeroMoveController`(hero.ts:8,79;types.ts:7,15,27,35;manager.ts:2,18,27,39)→ 渲染端需改(拆为 `IHeroLocation` `data-base/src/hero/types.ts:303` / `IHeroMover` `:399`);`IHeroMoveControllerHooks`(hero.ts:10,61,457)→ 渲染端需改(拆为 `IObjectMoverHooks` `data-common/src/common/mover.ts:166` / `IHeroLocationHooks` `data-base/src/hero/types.ts:277` / `IHeroFollowersControllerHooks` `:486`);`hero.x`/`hero.y`(hero.ts:87-88,117,122,127,129)→ 匹配(`IObjectMovable.x/y` `data-common/src/common/mover.ts:48-51`);`hero.direction`(hero.ts:89,125,193)→ 渲染端需改(`IObjectMover.faceDirection`/`moveDirection` `:243,245`);`hero.addHook`(hero.ts:81)→ 匹配(`IHookable`,经 `IObjectMover extends IHookable` `data-common/src/common/mover.ts:235`);跟随者增删钩子(`onAddFollower`/`onRemoveFollower`/`onRemoveAllFollowers`,hero.ts:341-429,473-500)→ 渲染端需改(对到 `IHeroFollowersControllerHooks` `data-base/src/hero/types.ts:486-506`);`HeroKeyMover` 移动器期望(`action/move.ts:125-127,130-134,137,169-171,180`)逐成员对到 `IObjectMover.step/start`(`:293,350`)与 `IMoverController.push(...ObjectMoveStep[])`(`:152`)、`done`(`:144`)、`onEnd`(`:146`)→ 渲染端需改;`manager.ts:18` `heroMap` 键类型 → 渲染端需改。**范围外(D-18)**:`hero.image` 贴图别名(hero.ts:115,119,121,143)与 `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 属 `HeroRendering` 渲染状态,**不写对账行**(04-01 的 `#04-01-M-05` 已登记该错配,本步不重复),只在「处置」排除说明中提及。 - (2) **缺失接口候选段**:把渲染端需要而数据端无对位的成员(例如按方向/时长的**移动语义钩子** `onMoveHero(direction, time)` / `onJumpHero(x, y, time, waitFollower)` / `onTurnHero` / `onStartMove` / `onEndMove`,以及 `HeroKeyMover` 依赖的 `oneStep` 与 `controller.queue`)逐条登记为 `### #04-02-G-N:标题` + `- **渲染端需求**:` / `- **数据端现状**:` / `- **待确认**:`;数据端现状必须引用最接近的现行成员 `file:line`(例如 `data-common/src/common/mover.ts:166-233` 的 hooks 方法集 / `:142-164` 的控制器成员集)。上述移动语义钩子属勇士移动契约,**在范围内**——D-18 的排除只覆盖 `IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`,不得把贴图/alpha 项登记为候选。**只记录候选**:不设计、不修复、不改数据端接口(D-13)。节末写固定结论行 `**候选结论:** 本步登记 N 项缺失接口候选(未经用户裁决,不视为最终缺失清单,见 D-13)`。 + (1) **逐成员对账段定稿**:为每个渲染端使用成员写一行 `#04-02-R-N`(N 从 2 递增,`R-01` 由 Task 1 占用),结论取 匹配 / 渲染端需改 / 数据端缺失 三态之一,两侧均给 `file:line`(数据端无对位时写「无对位,最接近:」)。至少覆盖:`HeroMover` / `IMoveController` 的 import 源与类型名(`action/move.ts:4` vs `data-base/src/hero/mover.ts:22`、`data-common/src/common/mover.ts:142`)→ 渲染端需改;`HeroAnimateDirection`(hero.ts:6,53,97,170,372,437;types.ts:5,147)→ 渲染端需改(改 `ObjectAnimDirection`,`data-common/src/common/mover.ts:40`);`IHeroMoveController`(hero.ts:8,79;types.ts:7,15,27,35;manager.ts:2,18,27,39)→ 渲染端需改(拆为 `IHeroLocation` `data-base/src/hero/types.ts:303` / `IHeroMover` `:399`);`IHeroMoveControllerHooks`(hero.ts:10,61,457)→ 渲染端需改(拆为 `IObjectMoverHooks` `data-common/src/common/mover.ts:166` / `IHeroLocationHooks` `data-base/src/hero/types.ts:277` / `IHeroFollowersControllerHooks` `:486`);`hero.x`/`hero.y`(hero.ts:87-88,117,122,127,129)→ 匹配(`IObjectMovable.x/y` `data-common/src/common/mover.ts:48-51`);`hero.direction`(hero.ts:89,125,193)→ 渲染端需改(`IObjectMover.faceDirection`/`moveDirection` `:243,245`);`hero.addHook`(hero.ts:81)→ 匹配(`IHookable`,经 `IObjectMover extends IHookable` `data-common/src/common/mover.ts:235`);跟随者增删钩子(`onAddFollower`/`onRemoveFollower`/`onRemoveAllFollowers`,hero.ts:341-429,473-500)→ 渲染端需改(对到 `IHeroFollowersControllerHooks` `data-base/src/hero/types.ts:486-506`);`HeroKeyMover` 移动器期望(`action/move.ts:125-127,130-134,137,169-171,180`)逐成员对到 `IObjectMover.step/start`(`:293,350`)与 `IMoverController.push(...ObjectMoveStep[])`(`:152`)、`done`(`:144`)、`onEnd`(`:146`)→ 渲染端需改;`manager.ts:18` `heroMap` 键类型 → 渲染端需改。**范围外(D-18)**:`hero.image` 贴图别名(hero.ts:115,119,121,143)与 `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 属 `HeroRendering` 渲染状态,**不写对账行**(04-01 的 `#04-01-M-05` 已登记该错配,本步不重复),只在「处置」排除说明中提及。**对账行内不得出现 `IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 字样**(只写「贴图/alpha 钩子」这类通用指代)。 + (2) **缺失接口候选段**:把渲染端需要而数据端无对位的成员(例如按方向/时长的**移动语义钩子** `onMoveHero(direction, time)` / `onJumpHero(x, y, time, waitFollower)` / `onTurnHero` / `onStartMove` / `onEndMove`,以及 `HeroKeyMover` 依赖的 `oneStep` 与 `controller.queue`)逐条登记为 `### #04-02-G-N:标题` + `- **渲染端需求**:` / `- **数据端现状**:` / `- **待确认**:`;数据端现状必须引用最接近的现行成员 `file:line`(例如 `data-common/src/common/mover.ts:166-233` 的 hooks 方法集 / `:142-164` 的控制器成员集)。上述移动语义钩子属勇士移动契约,**在范围内**——D-18 的排除只覆盖 `IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`,不得把贴图/alpha 项登记为候选。**候选的标题与三段正文均不得出现 `IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 字样**(避免把排除项误登记为候选)。**只记录候选**:不设计、不修复、不改数据端接口(D-13)。节末写固定结论行 `**候选结论:** 本步登记 N 项缺失接口候选(未经用户裁决,不视为最终缺失清单,见 D-13)`。 (3) **未能从阅读确定段**:逐条登记无法静态定论项,例如 `controller.queue`(`action/move.ts:169`)的运行时形状与 `IMoverController` 的可达性、`state.hero.location` 是否为当前渲染端可取的入口、移动语义钩子候选与 04-RENDER-INTERFACE-AUDIT.md「未能从阅读确定」第 2 条候选的重叠关系(该条中贴图/alpha 子项按 D-18 排除,只保留移动子项在本步登记)。不得写成缺失或错配;**不得把 `hero.image` / 贴图 / alpha 钩子写进本节**(按 D-18 只在「处置」排除说明中提及)。 (4) **处置段定稿**:本步只读、相对「扫描前基线」零新增生产代码改动(用户既有 `packages-user/client-base/src/load/**` 改动保持原样,不回滚 / 不暂存 / 不提交);适配工作(04-03+)由用户在本探索结果与候选裁决后另行规划(D-17/D-13);显式排除 `HeroRendering` 渲染状态(D-18:`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`)与 `core.*`(D-12),并写明范围的另一侧(勇士移动契约与移动钩子在范围内);`getHeroStatusOn` 类裸函数(D-11)不属本增量。 (5) **门禁与提交**:运行只读门禁(相对「扫描前基线」无**新增**条目)、CRLF 门禁、范围门禁(无 04-03+ 计划文件);随后以 `docs(04-02): 勇士移动接口探索(只读,D-17)` 提交**仅**该交付文档与 `04-02-PLAN.md`,不得提交任何生产源码改动(尤其不得把用户既有的 `client-base/src/load/**` 改动纳入本次提交)。 - node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','utf8');const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-02-R-\d+\s*\|/.test(l));if(rows.length<10){console.error('TOO FEW RECON ROWS '+rows.length);process.exit(1)}const bad=rows.filter(l=>!/client-(base|modules)\/[^\s|]*:\d+/.test(l)||!/data-(common|base|state)\/[^\s|]*:\d+/.test(l));if(bad.length>0){console.error('ROW MISSING TWO-SIDED ANCHOR '+bad.length);process.exit(1)}for(const v of ['\u5339\u914d','\u6e32\u67d3\u7aef\u9700\u6539','\u6570\u636e\u7aef\u7f3a\u5931']){if(t.indexOf(v)<0){console.error('MISSING VERDICT VOCAB '+v);process.exit(1)}}const lines=t.split(/\r?\n/);const gh=lines.map((l,i)=>({l,i})).filter(o=>/^### #04-02-G-\d+/.test(o.l));for(const g of gh){const body=lines.slice(g.i+1,g.i+8).join('\n');if(body.indexOf('\u6e32\u67d3\u7aef\u9700\u6c42')<0||body.indexOf('\u6570\u636e\u7aef\u73b0\u72b6')<0||body.indexOf('\u5f85\u786e\u8ba4')<0){console.error('GAP CANDIDATE MISSING FIELDS');process.exit(1)}}if(t.indexOf('\u5019\u9009\u7ed3\u8bba')<0){console.error('GAP SECTION WITHOUT CONCLUSION');process.exit(1)}console.log('OK recRows '+rows.length+' gapCandidates '+gh.length)" - non-zero exit, 或 stderr 含 "TOO FEW RECON ROWS" / "ROW MISSING TWO-SIDED ANCHOR" / "MISSING VERDICT VOCAB" / "GAP CANDIDATE MISSING FIELDS" / "GAP SECTION WITHOUT CONCLUSION" + node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','utf8');const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-02-R-\d+\s*\|/.test(l));if(rows.length<10){console.error('TOO FEW RECON ROWS '+rows.length);process.exit(1)}const bad=rows.filter(l=>!/client-(base|modules)\/[^\s|]*:\d+/.test(l)||!/data-(common|base|state)\/[^\s|]*:\d+/.test(l));if(bad.length>0){console.error('ROW MISSING TWO-SIDED ANCHOR '+bad.length);process.exit(1)}const banned=/IHeroRendering|hero\.image|onSetImage|onSetAlpha|onSetFollowerAlpha/;const rowBad=rows.filter(l=>banned.test(l));if(rowBad.length>0){console.error('D18 TOKEN IN RECON ROW '+rowBad.length);process.exit(1)}for(const v of ['\u5339\u914d','\u6e32\u67d3\u7aef\u9700\u6539','\u6570\u636e\u7aef\u7f3a\u5931']){if(t.indexOf(v)<0){console.error('MISSING VERDICT VOCAB '+v);process.exit(1)}}const lines=t.split(/\r?\n/);const gh=lines.map((l,i)=>({l,i})).filter(o=>/^### #04-02-G-\d+/.test(o.l));for(const g of gh){const body=lines.slice(g.i+1,g.i+8).join('\n');if(body.indexOf('\u6e32\u67d3\u7aef\u9700\u6c42')<0||body.indexOf('\u6570\u636e\u7aef\u73b0\u72b6')<0||body.indexOf('\u5f85\u786e\u8ba4')<0){console.error('GAP CANDIDATE MISSING FIELDS');process.exit(1)}if(banned.test(g.l)||banned.test(body)){console.error('D18 TOKEN IN GAP CANDIDATE');process.exit(1)}}if(t.indexOf('\u5019\u9009\u7ed3\u8bba')<0){console.error('GAP SECTION WITHOUT CONCLUSION');process.exit(1)}console.log('OK recRows '+rows.length+' gapCandidates '+gh.length)" + non-zero exit, 或 stderr 含 "TOO FEW RECON ROWS" / "ROW MISSING TWO-SIDED ANCHOR" / "MISSING VERDICT VOCAB" / "GAP CANDIDATE MISSING FIELDS" / "GAP SECTION WITHOUT CONCLUSION" / "D18 TOKEN IN RECON ROW" / "D18 TOKEN IN GAP CANDIDATE" node -e "const fs=require('fs');const s=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','utf8');const lf=(s.match(/\n/g)||[]).length;const crlf=(s.match(/\r\n/g)||[]).length;if(lf!==crlf){console.error('NON-CRLF LINE ENDINGS '+(lf-crlf));process.exit(1)}console.log('OK CRLF lines '+crlf)" non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(交付文档出现非 CRLF 换行) node -e "const{execSync}=require('child_process');const out=execSync('git status --porcelain -- packages-user/client-base packages-user/client-modules',{encoding:'utf8'}).replace(/\s+$/,'');const lines=out?out.split(/\r?\n/):[];const allowed=/^.. packages-user\/client-base\/src\/load\//;const extra=lines.filter(l=>!allowed.test(l));if(extra.length>0){console.error('NEW RENDER PKG CHANGES '+extra.length+' '+extra.join(' ; '));process.exit(1)}console.log('OK no new render-pkg changes; pre-existing baseline entries '+lines.length)"