fix(04): revise plan 04-02 per checker feedback (AGENTS.md gate + verification Derivation)

This commit is contained in:
unanmed 2026-09-18 11:54:35 +08:00
parent 208c22ce0c
commit a65d93221f
2 changed files with 100 additions and 37 deletions

View File

@ -7,24 +7,25 @@ depends_on:
- 04-01
files_modified:
- .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
autonomous: true
autonomous: false
requirements:
- REND-01
- REND-02
estimate:
tokens: 58000
raw_tokens: 58000
tasks: 3
tokens: 60000
raw_tokens: 60000
tasks: 4
confidence: low
must_haves:
truths:
- "`.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md` 存在,含 8 个二级标题:`## 背景` / `## 方法` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`;`## 方法` 含「扫描前基线」条目(原样记录起始 `git status --porcelain` 输出);全文为 CRLF 换行(dev.md「换行使用 CRLF 格式」)"
- "本计划的**第一个 block 点**是 Task 0 的 `blocking-human` 汇报关卡:先向用户汇报本计划要解决的问题、解决方案与范围边界,然后必须**停下等待**用户明确回复「可以执行」四个字,收到该回复前不得执行任何任务、不得派发任何执行子代理;无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `yolo`、`workflow.auto_advance`)如何,均不得自动放行(AGENTS.md)"
- "`.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md` 存在,含 8 个二级标题:`## 背景` / `## 方法` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`;`## 方法` 含「扫描前基线」条目(原样逐行记录起始 `git status --porcelain` 输出条目)与 `基线摘要` sha256 行(Task 3 只读门禁据此判定相对基线零变化);全文为 CRLF 换行(dev.md「换行使用 CRLF 格式」)"
- "逐成员对账表每条 `#04-02-R-N` 精确到接口/成员名,同行同时含渲染端 `client-base|client-modules` 与数据端 `data-common|data-base|data-state` 两侧 `file:line` 锚点(数据端无对位时写「无对位,最接近:<file:line>」),结论取 匹配 / 渲染端需改 / 数据端缺失 三态之一"
- "带 `// @ts-expect-error 需要重构` 的 import 一律先归「渲染端需改」(用户规则,见 04-RENDER-INTERFACE-AUDIT.md 处置口径),不归「数据端缺失」"
- "「缺失接口候选」独立成节,每条 `### #04-02-G-N:标题` 含 `**渲染端需求**` / `**数据端现状**` / `**待确认**` 三段,数据端现状引用最接近的现行成员 `file:line`;候选只记录、不设计、不修复(D-13),未经用户裁决不视为最终缺失清单"
- "「缺失接口候选」独立成节,**至少登记 1 条**(本节确有「渲染端需要而数据端无对位」的移动语义钩子,空节即失败),每条 `### #04-02-G-N:标题` 含 `**渲染端需求**` / `**数据端现状**` / `**待确认**` 三段,数据端现状引用最接近的现行成员 `file:line`;节末固定结论行的 N 必须等于实际候选条数且 ≥ 1;候选只记录、不设计、不修复(D-13),未经用户裁决不视为最终缺失清单"
- "`HeroRendering` **渲染状态**相关成员(`IHeroRendering`、`hero.image` / 贴图别名、`onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`)与 legacy `core.*` 为显式排除项(D-18 / D-12):文中不出现针对它们的对账行或缺失候选,仅在「处置」与范围排除说明中登记排除理由。边界另一侧——勇士移动契约与移动钩子(`IObjectMover` / `IObjectMoverHooks` / `IMoverController` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks`,以及 `onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 与 step 钩子)——**仍在范围内**,照常登记对账与候选"
- "生产代码相对规划时基线零**新增**改动:`git status --porcelain -- packages-user/client-base packages-user/client-modules` 只含规划时既有的、与勇士移动无关的用户未提交改动(`packages-user/client-base/src/load/**`,D-17 范围外,**不得回滚 / 暂存 / 提交**),不出现任何新增条目;本次只新增交付文档与 `04-02-PLAN.md`,不新增任何适配计划(04-03+ 未规划)"
- "无法从静态阅读定论的链路进入「未能从阅读确定(未猜测)」而非写成缺失或错配;文档引用的生产路径均为 tracked 源码路径(可被 `git ls-files` 命中)"
- "生产代码相对**记录在 `## 方法` 的扫描前基线**零变化:当前 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 的条目集合与基线逐条一致(不新增、不消失、状态码不变),且 `基线摘要` 的 `sha256`(对 status 条目 + 各基线条目文件内容取摘要,能发现基线文件内容被改动)未变;规划时既有、与勇士移动无关的用户未提交改动(`packages-user/client-base/src/load/**`,D-17 范围外)**不得回滚 / 暂存 / 提交**;本次只新增交付文档与 `04-02-PLAN.md`,不新增任何适配计划(04-03+ 未规划)"
- "无法从静态阅读定论的链路进入「未能从阅读确定(未猜测)」而非写成缺失或错配,且该节不出现 D-18 排除项字样(`IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`,由 Task 3 门禁机器判定);文档引用的生产路径均为 tracked 源码路径(可被 `git ls-files` 命中)"
artifacts:
- path: .planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md
provides: "勇士移动子系统的只读接口探索账本:数据端现行接口清单 + 渲染端现状清单 + 逐成员三态对账(匹配 / 渲染端需改 / 数据端缺失) + 缺失接口候选 + 未确定项 + 处置;供用户裁决缺失候选并据此规划后续适配(D-17/D-13)"
@ -39,8 +40,9 @@ must_haves:
- "**FLAGGED ASSUMPTION(specless fallback)— REND-02 edge unresolved:** 未提供 REND-02(移动端与桌面端双布局)的验收边界;本计划不涉及双布局实现,也不发明其验收阈值。"
- "数据端接口签名在规划后可能仍被用户并行修改**实现**但不改签名(D-05);本计划以执行当日基准文件的现行签名为准,若发现签名已变,以当日文件为准并在「未能从阅读确定(未猜测)」记录该变化。"
- "数据端包(`data-common` / `data-base` / `data-state`)工作树可能因用户并发重构而非空;只读门禁不对数据端工作树状态做机器断言(否则会与用户的进行中工作冲突)。"
- "被查渲染端两包(`client-base` / `client-modules`)在规划时已带**与勇士移动无关**的用户未提交改动:` M packages-user/client-base/src/load/loader.ts`、` M packages-user/client-base/src/load/types.ts`(`git status --porcelain` 基线两条)。本计划**不回滚、不暂存、不提交**它们;只读门禁以「相对该基线无**新增**条目」为准(仅允许 `packages-user/client-base/src/load/` 下条目,其余任何条目即失败)。"
- "被查渲染端两包(`client-base` / `client-modules`)在规划时已带**与勇士移动无关**的用户未提交改动:` M packages-user/client-base/src/load/loader.ts`、` M packages-user/client-base/src/load/types.ts`(`git status --porcelain` 基线两条)。本计划**不回滚、不暂存、不提交**它们;只读门禁以「相对**记录在 `## 方法` 的扫描前基线**零变化」为准——当前 porcelain 条目集合须与基线记录**逐条一致**(不新增、不消失、状态码不变),且 `基线摘要 sha256`(status 条目 + 各基线条目文件内容的 sha256)未变;该机制不再按 `packages-user/client-base/src/load/` 路径前缀放行,因此基线目录内的新增/消失条目或基线文件内容被改动都会失败。"
prohibitions:
- "不得在用户明确回复「可以执行」四个字之前执行本计划的任何任务、或派发任何执行子代理(AGENTS.md 的第一个 block 点约束;该约束不因触发方式或 `yolo`/`auto_advance` 模式而放宽)。"
- "不得修改或新建任何生产代码(`packages-user/client-base`、`packages-user/client-modules`、`packages-user/data-common`、`packages-user/data-base`、`packages-user/data-state`)——本步是只读接口探索(D-17)。"
- "不得规划、设计或实施勇士移动适配工作(04-03+ 适配计划不在本 run)。"
- "不得把 `HeroRendering` **渲染状态**相关成员(`IHeroRendering`、`hero.image` / 勇士贴图别名、`onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`)纳入对账行或缺失候选(D-18)——它们属 D-10 待删除的数据端渲染状态子系统。勇士移动契约与移动钩子(`IObjectMover` / `IHeroMover` / `IHeroLocationHooks` / `IHeroFollowersControllerHooks` / `IHeroStateHooks`,`onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 等)**在范围内,不适用本条**。"
@ -61,15 +63,18 @@ Phase 4 第二个增量(D-17):对**勇士移动子系统**做**只读接
**后续适配(04-03+)在本 run 不规划**:适配依赖本探索输出,且需用户先裁决缺失候选(D-13/D-17)。
**执行前置关卡(AGENTS.md,第一个 block 点):** 本计划第一个执行步骤是 Task 0 —— 先向用户汇报「本计划要解决的问题 / 大致解决方案 / 范围边界与禁令」,然后**停下等待**用户明确回复「可以执行」四个字。收到该回复前,任何任务都不得执行,也不得派发执行子代理;无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `yolo`、`workflow.auto_advance`)如何,均不得自动放行。故本计划 `autonomous: false`。
Purpose: 勇士移动是本次对账暴露的主要失配点(D-17)。在动适配代码前先把「数据端到底提供什么、渲染端到底需要什么、差在哪」钉死到接口成员级,避免按旧形状直接改渲染端。
Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产代码改动)。
Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产代码改动)。Task 0 为只读汇报关卡,不写任何文件。
**Artifacts this phase produces(本计划产出物)**:
- **文件**:`.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md`(新建,唯一写入目标)。
- **章节**(8 个二级标题,下游验证按标题名核对):`## 背景` / `## 方法` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`。
- **元信息块**(标题下 5 行,对照 04-RENDER-INTERFACE-AUDIT.md:3-6):`**日期:**` / `**范围:**`(勇士移动子系统;排除 `HeroRendering` 渲染状态(`IHeroRendering` / 贴图别名 / alpha 钩子)与 `core.*`) / `**性质:**`(只读接口探索,未修改任何生产代码;相对规划时基线零新增改动) / `**接口基准:**`(`data-common/src/common/mover.ts`、`data-base/src/hero/{types,mover}.ts` 及 barrel 现行导出) / `**输入账本:**`(`04-RENDER-INTERFACE-AUDIT.md` 的 `#04-01-M-01..05`)。
- **基线记录格式**(写在 `## 方法`,Task 3 只读门禁按此读取):`- **扫描前基线**:` bullet + 原样逐行抄录的 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 输出条目(围栏 `text` 代码块内,每条一行、行首为两字符状态码;输出为空则写 `(无)`)+ `- **基线摘要**:sha256=<64hex>` bullet(由固定命令对 status 条目 + 各基线条目**文件内容的 sha256** 取摘要,能发现基线文件内容被改动)。只读门禁要求「当前 porcelain 条目集合 == 基线记录条目集合」且该 `sha256` 未变。
- **逐成员对账表列**(固定顺序):`| ID | 成员 | 渲染端锚点 | 数据端锚点 | 对账结论 | 说明 |`。
- **记录 ID 前缀**(下游按前缀检索):`#04-02-R-N` —— 逐成员对账行(结论 ∈ 匹配 / 渲染端需改 / 数据端缺失);`#04-02-G-N` —— 缺失接口候选(独立节,`### #04-02-G-N:标题` + `**渲染端需求**` / `**数据端现状**` / `**待确认**`)。
- **缺失候选节结论行**(固定写法,必须出现):`**候选结论:** 本步登记 N 项缺失接口候选(未经用户裁决,不视为最终缺失清单,见 D-13)`。
@ -93,7 +98,7 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
## Estimate Basis(估算依据)
`estimate-calibration` 返回 `factor: 1`、`sample_count: 0`、`confidence: low`(项目无历史样本),故 `tokens == raw_tokens`,并如实标注 `confidence: low`。本计划按「执行者实际触及量」估算:Task 1 读交付文档结构范式 + 两个基准切开面 + 写骨架(约 14k);Task 2 数据端/渲染端两侧清单(约 22k,重型整文件读取按 D-09 委派只读子代理);Task 3 对账、缺失候选、未确定、处置与门禁(约 18k);固定开销约 4k;合计约 58k,落在 smart-zone 预算内。
`estimate-calibration` 返回 `factor: 1`、`sample_count: 0`、`confidence: low`(项目无历史样本),故 `tokens == raw_tokens`,并如实标注 `confidence: low`。本计划按「执行者实际触及量」估算(`estimate.tasks: 4` = Task 0 汇报关卡 + 原三个任务):Task 0 只读汇报并等待用户确认(约 2k,不写文件);Task 1 读交付文档结构范式 + 两个基准切开面 + 写骨架(约 14k);Task 2 数据端/渲染端两侧清单(约 22k,重型整文件读取按 D-09 委派只读子代理);Task 3 对账、缺失候选、未确定、处置与门禁(约 18k);固定开销约 4k;合计约 60k,落在 smart-zone 预算内。
</objective>
@ -119,6 +124,57 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
<tasks>
<task type="checkpoint:decision" gate="blocking-human">
<name>Task 0: 预执行汇报关卡 —— 汇报本计划要解决的问题与解决方案,等待用户回复「可以执行」</name>
<files>(只读汇报,不修改任何文件)</files>
<read_first>
- AGENTS.md(「任何计划的执行,其第一个 block 点必须是向我汇报计划内容并等待我确认」;只有明确回复「可以执行」四个字之后才允许开始执行)
- .planning/phases/04-render-adaptation/04-02-PLAN.md(本计划全文:objective / tasks / 禁令 / 门禁)
- .planning/phases/04-render-adaptation/04-CONTEXT.md(D-11 / D-12 / D-13 / D-17 / D-18)
- .planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md:30-35,110-127(本计划的输入:`#04-01-M-01..05` 勇士移动既有错配登记与相关「未能从阅读确定」条目)
</read_first>
<action>
**停下并向用户汇报,等待明确回复后才继续;汇报本身只读,不写任何文件、不改任何生产代码、不派发子代理。**
① **要解决的问题**:Phase 4 第二步(D-17)要在「适配勇士移动」之前先把差异钉死到接口成员级 —— 04-01 的对账显示主要失配点在勇士移动(`#04-01-M-01..05`),但渲染端到底需要哪些数据端接口、哪些是渲染端需改、哪些是数据端缺失,尚无成员级结论;不先探索就直接改渲染端会按旧形状改错。
② **大致解决方案**:本 run 只做**只读接口探索**,产出唯一交付物 `.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md`:8 段骨架(背景 / 方法 / 数据端现行接口 / 渲染端现状 / 逐成员对账 / 缺失接口候选 / 未能从阅读确定 / 处置);逐成员三态对账(匹配 / 渲染端需改 / 数据端缺失),每条两侧 `file:line`;缺失接口候选独立成节、只登记不设计不修复(D-13);重型整文件读取按 D-09 委派只读子代理(仅回传事实表、不写文件)。
③ **边界与禁令**:零生产代码改动(D-17);`HeroRendering` 渲染状态(`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`)按 D-18 显式排除,legacy `core.*` 按 D-12 排除(属第五阶段),但**勇士移动契约与移动钩子仍在范围内**;不规划、不设计、不实施适配工作(04-03+ 不属本 run);被查渲染端包相对「扫描前基线」(`packages-user/client-base/src/load/**` 的用户既有未提交改动)必须零变化(不新增、不消失、不改内容),且**不得回滚 / 暂存 / 提交**这些既有改动。
④ **关卡性质(必须原样告知用户)**:无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `yolo`、`workflow.auto_advance`)如何,**执行者/子代理都必须在此停下并等待**用户明确回复「可以执行」四个字;未收到这四个字前不得执行任何任务、不得派发执行子代理,也不得以「自动化模式」为由自动放行。
⑤ **若用户给出修订意见**:退出本计划、修订 `04-02-PLAN.md` 后重新汇报,不得自行改道执行。
</action>
<decision>是否按本计划(只读勇士移动接口探索,产出 `04-HERO-MOVER-INTERFACE.md`,零生产代码改动,不规划 04-03+ 适配)开始执行</decision>
<context>
AGENTS.md 要求任何计划的第一个 block 点必须是向用户汇报计划内容并等待用户确认;本计划虽是只读探索,仍不得例外。`.planning/config.json` 为 `mode: yolo`(execute-plan 默认自动推进),因此该关卡必须显式声明为 `blocking-human` 并置 `autonomous: false`,否则计划层面不提供任何 block 点。
</context>
<options>
<option id="execute">
<name>可以执行</name>
<pros>按本计划执行:只读探索,零生产代码改动,唯一写入为交付文档;结果供用户裁决缺失候选</pros>
<cons>无(只读;被查渲染端包相对基线的零变化由 Task 3 门禁机器判定)</cons>
</option>
<option id="revise">
<name>修订方案/范围后再执行</name>
<pros>避免带着错误的范围或口径落地</pros>
<cons>退出本计划并修订 PLAN.md 后重新汇报,增加一轮往返</cons>
</option>
</options>
<resume-signal>回复「可以执行」四个字,或给出修订意见</resume-signal>
<verify>
<human-check>用户在对话中明确回复「可以执行」四个字</human-check>
<fails_when>用户未回复「可以执行」即继续,或回复的是其它内容(含 yolo/auto 模式的自动推进)—— 必须停止执行,不派发任何执行子代理</fails_when>
</verify>
<acceptance_criteria>
- 用户回复中明确包含「可以执行」四个字
- 汇报内容涵盖:本计划要解决的问题(勇士移动成员级差异未钉死)、大致解决方案(只读三态对账 + 缺失接口候选)、只读边界与唯一写入目标、排除项(D-12/D-18)与「04-03+ 适配未规划」
- 汇报中已原样告知关卡性质:执行者/子代理必须停下等待,不因触发方式或 yolo/auto 模式自动放行
</acceptance_criteria>
<done>用户明确回复「可以执行」;在此之前未执行任何任务、未修改任何文件、未派发任何执行子代理</done>
</task>
<task type="tracer">
<name>Task 1(tracer):交付文档骨架 + 勇士移动一条端到端对账切片 —— 8 段结构(含 `## 方法`)+ `#04-02-R-01` 双锚点实测 + 结构/CRLF 门禁转绿</name>
<files>.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md</files>
@ -136,7 +192,7 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
- dev.md(「双端分离」「命名规则」「注释规范」;交付文档换行 CRLF)
</read_first>
<action>
(0) **起始基线快照(只读前置)**:先执行 `git status --porcelain -- packages-user/client-base packages-user/client-modules`,把输出原样记为「扫描前基线」并写入 `## 方法` 的一条 bullet(写法对照 04-RENDER-INTERFACE-AUDIT.md:21)。规划时该输出为两条与勇士移动无关的用户未提交改动:` M packages-user/client-base/src/load/loader.ts`、` M packages-user/client-base/src/load/types.ts`。**不得回滚、暂存、提交或修改这些既有改动**;若基线中出现 `packages-user/client-base/src/load/` 之外的条目,停下向用户报告,不得继续(后续 Task 3 的只读门禁以该基线为基准,只断言无**新增**条目)。
(0) **起始基线快照(只读前置,先做这一步)**:执行 `git status --porcelain -- packages-user/client-base packages-user/client-modules`,把输出**原样逐行**记为「扫描前基线」写入 `## 方法`:先写 bullet `- **扫描前基线**:`,其后以围栏 `text` 代码块逐行抄录输出(每条一行,行首为两字符状态码;输出为空则写 `(无)`),再写一条 `- **基线摘要**:sha256=<hex>` bullet,hex 由这条固定命令产出:node -e "const fs=require('fs'),c=require('crypto'),{execSync}=require('child_process');const out=execSync('git status --porcelain -- packages-user/client-base packages-user/client-modules',{encoding:'utf8'}).replace(/\s+$/,'');const cur=out?out.split(/\r?\n/).map(s=>s.replace(/\s+$/,'')):[];const parts=cur.slice().sort().map(l=>{let h='MISSING';try{h=c.createHash('sha256').update(fs.readFileSync(l.slice(3))).digest('hex')}catch(e){}return l+' '+h});console.log(c.createHash('sha256').update(parts.join('\n')).digest('hex'))"(对照 04-RENDER-INTERFACE-AUDIT.md:21 的既有基线写法)。规划时该输出为两条与勇士移动无关的用户未提交改动:` M packages-user/client-base/src/load/loader.ts`、` M packages-user/client-base/src/load/types.ts`。**不得回滚、暂存、提交或修改这些既有改动**;若基线中出现 `packages-user/client-base/src/load/` 之外的条目,停下向用户报告,不得继续。Task 3 的只读门禁以「当前 porcelain 条目集合 == 基线记录条目集合」且「基线摘要 `sha256` 未变(status 条目 + 各基线条目文件内容的 sha256,能发现基线文件内容被改动)」判定相对基线零变化。
(1) 新建 `04-HERO-MOVER-INTERFACE.md`,先写标题与 5 行元信息块:`**日期:** 2026-09-18`;`**范围:**` 勇士移动子系统(渲染端使用点 → 数据端现行接口),排除 `HeroRendering` 渲染状态(`IHeroRendering` / 贴图别名 / alpha 钩子,D-18)与 `core.*`(D-12);`**性质:**` 只读接口探索,未修改任何生产代码,相对规划时基线零新增改动(D-17);`**接口基准:**` `packages-user/data-common/src/common/mover.ts`、`packages-user/data-base/src/hero/{types,mover}.ts` 及各自 barrel 现行导出(D-05);`**输入账本:**` `04-RENDER-INTERFACE-AUDIT.md` 的 `#04-01-M-01..05`。**全文使用 CRLF 换行**(dev.md「换行使用 CRLF 格式」);Task 2/3 追加内容同样保持 CRLF,不得出现孤立 `LF`。
(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 派只读子代理(仅回传文本、不得写文件)。
@ -147,19 +203,21 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
</action>
<reversibility rating="reversible">交付文档是只读探索的记录,可随时修订或废弃;它不引入生产代码、公共接口或契约约束,因此这一步的任何取舍都不难回退。</reversibility>
<verify>
<automated>node -e "const fs=require('fs');const p='.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md';const t=fs.readFileSync(p,'utf8');const need=['## \u80cc\u666f','## \u65b9\u6cd5','## \u6570\u636e\u7aef\u73b0\u884c\u63a5\u53e3','## \u6e32\u67d3\u7aef\u73b0\u72b6','## \u9010\u6210\u5458\u5bf9\u8d26\uff08\u5339\u914d / \u6e32\u67d3\u7aef\u9700\u6539 / \u6570\u636e\u7aef\u7f3a\u5931\uff09','## \u7f3a\u5931\u63a5\u53e3\u5019\u9009','## \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a\uff08\u672a\u731c\u6d4b\uff09','## \u5904\u7f6e'];const miss=need.filter(h=>t.indexOf(h)<0);if(miss.length>0){console.error('MISSING HEADINGS '+miss.length);process.exit(1)}if(t.indexOf('\u626b\u63cf\u524d\u57fa\u7ebf')<0){console.error('MISSING BASELINE SNAPSHOT');process.exit(1)}const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-02-R-\d+\s*\|/.test(l));if(rows.length<1){console.error('NO RECON ROWS');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)}if(t.indexOf('HeroRendering')<0||t.indexOf('core.*')<0){console.error('MISSING SCOPE EXCLUSION');process.exit(1)}console.log('OK headings 8 recRows '+rows.length)"</automated>
<fails_when>non-zero exit, 或 stderr 含 "MISSING HEADINGS" / "MISSING BASELINE SNAPSHOT" / "NO RECON ROWS" / "ROW MISSING TWO-SIDED ANCHOR" / "MISSING SCOPE EXCLUSION"</fails_when>
<automated>node -e "const fs=require('fs');const p='.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md';const t=fs.readFileSync(p,'utf8');const need=['## \u80cc\u666f','## \u65b9\u6cd5','## \u6570\u636e\u7aef\u73b0\u884c\u63a5\u53e3','## \u6e32\u67d3\u7aef\u73b0\u72b6','## \u9010\u6210\u5458\u5bf9\u8d26\uff08\u5339\u914d / \u6e32\u67d3\u7aef\u9700\u6539 / \u6570\u636e\u7aef\u7f3a\u5931\uff09','## \u7f3a\u5931\u63a5\u53e3\u5019\u9009','## \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a\uff08\u672a\u731c\u6d4b\uff09','## \u5904\u7f6e'];const miss=need.filter(h=>t.indexOf(h)<0);if(miss.length>0){console.error('MISSING HEADINGS '+miss.length);process.exit(1)}if(t.indexOf('\u626b\u63cf\u524d\u57fa\u7ebf')<0){console.error('MISSING BASELINE SNAPSHOT');process.exit(1)}const seg=(h)=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const ms=seg('## \u65b9\u6cd5');if(!/\u57fa\u7ebf\u6458\u8981[^\r\n]*?sha256=[0-9a-f]{64}/.test(ms)){console.error('MISSING BASELINE DIGEST');process.exit(1)}const rows=t.split(/\r?\n/).filter(l=>/^\|\s*#04-02-R-\d+\s*\|/.test(l));if(rows.length<1){console.error('NO RECON ROWS');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)}if(t.indexOf('HeroRendering')<0||t.indexOf('core.*')<0){console.error('MISSING SCOPE EXCLUSION');process.exit(1)}console.log('OK headings 8 recRows '+rows.length)"</automated>
<fails_when>non-zero exit, 或 stderr 含 "MISSING HEADINGS" / "MISSING BASELINE SNAPSHOT" / "MISSING BASELINE DIGEST" / "BASELINE NOT RECORDED VERBATIM" / "NO RECON ROWS" / "ROW MISSING TWO-SIDED ANCHOR" / "MISSING SCOPE EXCLUSION"(基线未逐条原样记录或基线摘要缺失/与当前状态不符)</fails_when>
<automated>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)"</automated>
<fails_when>non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(交付文档出现非 CRLF 换行,违反 dev.md「换行使用 CRLF 格式」)</fails_when>
<automated>node -e "const fs=require('fs');const t=fs.readFileSync('.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md','utf8');const seg=(h)=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const ms=seg('## \u65b9\u6cd5');const rm=ms.match(/^[ MARCUD?!]{2} packages-user\/[^\r\n]*/gm);const rec=(rm||[]).map(s=>s.replace(/\s+$/,''));const{execSync}=require('child_process');const out=execSync('git status --porcelain -- packages-user/client-base packages-user/client-modules',{encoding:'utf8'}).replace(/\s+$/,'');const cur=out?out.split(/\r?\n/).map(s=>s.replace(/\s+$/,'')):[];const extra=cur.filter(l=>rec.indexOf(l)<0);const gone=rec.filter(l=>cur.indexOf(l)<0);if(extra.length+gone.length>0){console.error('BASELINE NOT RECORDED VERBATIM extra=['+extra.join(' ; ')+'] missing=['+gone.join(' ; ')+']');process.exit(1)}console.log('OK baseline verbatim entries '+cur.length)"</automated>
<fails_when>non-zero exit, 或 stderr 含 "BASELINE NOT RECORDED VERBATIM"(`## 方法` 的「扫描前基线」没有把起始 `git status --porcelain` 输出条目逐行原样记录,或记录内容与记录时的实际状态不一致)</fails_when>
</verify>
<acceptance_criteria>
- 交付文档存在于约定路径,8 个二级标题逐字存在(`## 背景` / `## 方法` / `## 数据端现行接口` / `## 渲染端现状` / `## 逐成员对账(匹配 / 渲染端需改 / 数据端缺失)` / `## 缺失接口候选` / `## 未能从阅读确定(未猜测)` / `## 处置`)
- `## 方法` 含「扫描前基线」条目,原样记录起始 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 输出(规划时为 `client-base/src/load/**` 两条无关用户改动),并明写不破坏该基线
- `## 方法` 含「扫描前基线」条目,**逐行原样**记录起始 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 输出条目(规划时为 `client-base/src/load/**` 两条无关用户改动,逐条一行;为空写「(无)」),并含 `- **基线摘要**:sha256=<64hex>` bullet(由固定命令对 status 条目 + 各基线条目文件内容的 sha256 取摘要);门禁断言记录条目与当前状态逐条一致(Task 3 据此判定相对基线零变化),并明写不破坏该基线
- 元信息块 5 行齐备(日期 / 范围 / 性质 / 接口基准 / 输入账本),且「范围」明写排除 `HeroRendering` 渲染状态(`IHeroRendering` / 贴图别名 / alpha 钩子)与 `core.*`
- `## 逐成员对账(...)` 表列序为 `ID | 成员 | 渲染端锚点 | 数据端锚点 | 对账结论 | 说明`,至少含 1 条 `#04-02-R-*`,同行两侧均有 `file:line` 锚点,结论取自三态
- `#04-02-R-01` 的钩子族判定来自实际打开基准文件核对(hero.ts 的钩子方法名与 `data-common`/`data-base` 现行 hooks 逐项比对),而非照抄 04-RENDER-INTERFACE-AUDIT.md;且该行**不含** `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`(按 D-18 排除)
- `## 数据端现行接口` 与 `## 渲染端现状` 各至少 1 条带 `file:line` 条目,三段(数据端 / 渲染端 / 对账)相互可达
- `## 处置` 含 D-12 / D-13 / D-17 / D-18 的明确表述与范围两侧边界;全文 CRLF;相对「扫描前基线」无新增生产代码改动(不回滚用户既有 `client-base/src/load/**` 改动)
- `## 处置` 含 D-12 / D-13 / D-17 / D-18 的明确表述与范围两侧边界;全文 CRLF;相对记录在 `## 方法` 的扫描前基线(条目逐条一致 + `基线摘要 sha256` 未变)零变化,不回滚用户既有 `client-base/src/load/**` 改动
</acceptance_criteria>
<done>交付文档骨架与一条勇士移动端到端对账切片贯通,8 段结构(含 `## 方法`)+ 双锚点对账行 + CRLF 门禁转绿;Task 2/3 只需在同一模板上横向补齐</done>
</task>
@ -199,7 +257,7 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
- 段末列出全部 `// @ts-expect-error 需要重构` 出现位置(文件 + 行号)
- `HeroRendering` 贴图/alpha 使用点(`setImage` / `setAlpha` / `setFollowerAlpha` / `hero.image`)只作现状段的排除标记,未产生任何对账行或候选(D-18)
- 相关渲染文件 `render/map/{moving,status,renderer}.ts` 仅作为移动器契约参考登记,未被当作独立对账对象
- 被查两渲染端包相对「扫描前基线」零新增改动;未新增任何计划文件;全文 CRLF
- 被查两渲染端包相对记录在 `## 方法` 的扫描前基线零变化(porcelain 条目逐条一致 + `基线摘要 sha256` 未变);未新增任何计划文件;全文 CRLF
</acceptance_criteria>
<done>数据端现行接口与渲染端现状两侧清单成员级落位,每条可追溯到 `file:line`,barrel 导出与 `@ts-expect-error` 标记清单齐备</done>
</task>
@ -220,28 +278,28 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
</read_first>
<action>
(1) **逐成员对账段定稿**:为每个渲染端使用成员写一行 `#04-02-R-N`(N 从 2 递增,`R-01` 由 Task 1 占用),结论取 匹配 / 渲染端需改 / 数据端缺失 三态之一,两侧均给 `file:line`(数据端无对位时写「无对位,最接近:<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 只在「处置」排除说明中提及)。
(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)`。**本节至少登记 1 条候选**(移动语义钩子族确无对位,空节即失败),且结论行的 N 必须等于实际候选条数且 ≥ 1(由门禁机器判定,不得为了过门禁而臆造或漏算)。
(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 只在「处置」排除说明中提及)——门禁对本节做 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/**` 改动纳入本次提交)。
(5) **门禁与提交**:运行只读门禁(当前 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 条目集合必须与 `## 方法` 记录的扫描前基线条目**逐条一致**,且 `基线摘要 sha256`(status 条目 + 各基线条目文件内容的 sha256)未变 —— 能发现基线目录内的新增/消失条目与基线文件内容被改动)、CRLF 门禁、范围门禁(无 04-03+ 计划文件);随后以 `docs(04-02): 勇士移动接口探索(只读,D-17)` 提交**仅**该交付文档与 `04-02-PLAN.md`,不得提交任何生产源码改动(尤其不得把用户既有的 `client-base/src/load/**` 改动纳入本次提交)。
</action>
<verify>
<automated>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)"</automated>
<fails_when>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"</fails_when>
<automated>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));if(gh.length<1){console.error('NO GAP CANDIDATES');process.exit(1)}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)}}const cm=t.match(/\*\*\u5019\u9009\u7ed3\u8bba\uff1a\*\*\s*\u672c\u6b65\u767b\u8bb0\s*(\d+)\s*\u9879/);if(!cm){console.error('GAP SECTION WITHOUT CONCLUSION');process.exit(1)}if(Number(cm[1])<1||Number(cm[1])!==gh.length){console.error('GAP CONCLUSION COUNT MISMATCH '+cm[1]+' vs '+gh.length);process.exit(1)}const seg=(h)=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const und=seg('## \u672a\u80fd\u4ece\u9605\u8bfb\u786e\u5b9a\uff08\u672a\u731c\u6d4b\uff09');if(und===''){console.error('MISSING UNDETERMINED SECTION');process.exit(1)}if(banned.test(und)){console.error('D18 TOKEN IN UNDETERMINED SECTION');process.exit(1)}console.log('OK recRows '+rows.length+' gapCandidates '+gh.length)"</automated>
<fails_when>non-zero exit, 或 stderr 含 "TOO FEW RECON ROWS" / "ROW MISSING TWO-SIDED ANCHOR" / "MISSING VERDICT VOCAB" / "GAP CANDIDATE MISSING FIELDS" / "NO GAP CANDIDATES" / "GAP SECTION WITHOUT CONCLUSION" / "GAP CONCLUSION COUNT MISMATCH" / "MISSING UNDETERMINED SECTION" / "D18 TOKEN IN RECON ROW" / "D18 TOKEN IN GAP CANDIDATE" / "D18 TOKEN IN UNDETERMINED SECTION"</fails_when>
<automated>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)"</automated>
<fails_when>non-zero exit, 或 stderr 含 "NON-CRLF LINE ENDINGS"(交付文档出现非 CRLF 换行)</fails_when>
<automated>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)"</automated>
<fails_when>non-zero exit, 或 stderr 含 "NEW RENDER PKG CHANGES"(相对规划时基线出现了新增的渲染端包改动/新增文件 —— 违反只读约束;规划时既有的 `packages-user/client-base/src/load/**` 用户改动不算,且不得回滚它们)</fails_when>
<automated>node -e "const fs=require('fs'),{execSync}=require('child_process'),c=require('crypto');const p='.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md';const t=fs.readFileSync(p,'utf8');const seg=(h)=>{const i=t.indexOf(h);if(i<0)return '';const rest=t.slice(i+h.length);const j=rest.search(/\r?\n## /);return j<0?rest:rest.slice(0,j)};const ms=seg('## \u65b9\u6cd5');const rm=ms.match(/^[ MARCUD?!]{2} packages-user\/[^\r\n]*/gm);const rec=(rm||[]).map(s=>s.replace(/\s+$/,''));const digest=(lines)=>{const parts=lines.slice().sort().map(l=>{let h='MISSING';try{h=c.createHash('sha256').update(fs.readFileSync(l.slice(3))).digest('hex')}catch(e){}return l+' '+h});return c.createHash('sha256').update(parts.join('\n')).digest('hex')};const out=execSync('git status --porcelain -- packages-user/client-base packages-user/client-modules',{encoding:'utf8'}).replace(/\s+$/,'');const cur=out?out.split(/\r?\n/).map(s=>s.replace(/\s+$/,'')):[];const extra=cur.filter(l=>rec.indexOf(l)<0);const gone=rec.filter(l=>cur.indexOf(l)<0);if(extra.length+gone.length>0){console.error('BASELINE ENTRY SET CHANGED extra=['+extra.join(' ; ')+'] missing=['+gone.join(' ; ')+']');process.exit(1)}const dm=ms.match(/\u57fa\u7ebf\u6458\u8981[^\r\n]*?sha256=([0-9a-f]{64})/);if(!dm){console.error('NO BASELINE DIGEST');process.exit(1)}const curDigest=digest(cur);if(dm[1]!==curDigest){console.error('BASELINE DIGEST CHANGED '+dm[1]+' vs '+curDigest);process.exit(1)}console.log('OK baseline unchanged entries '+cur.length+' digest '+curDigest.slice(0,12))"</automated>
<fails_when>non-zero exit, 或 stderr 含 "BASELINE ENTRY SET CHANGED" / "NO BASELINE DIGEST" / "BASELINE DIGEST CHANGED"(相对记录在 `## 方法` 的扫描前基线出现了条目新增/消失/状态码变化,或基线文件内容被改动 —— 违反只读约束;规划时既有的 `packages-user/client-base/src/load/**` 用户改动必须原样保留,不得回滚/暂存/提交,也不得在其中追加改动)</fails_when>
<automated>node -e "const fs=require('fs');const dir='.planning/phases/04-render-adaptation';const plans=fs.readdirSync(dir).filter(f=>/-PLAN\.md$/.test(f)).sort();const allowed=['04-01-PLAN.md','04-02-PLAN.md'];const extra=plans.filter(p=>allowed.indexOf(p)<0);if(extra.length>0){console.error('UNEXPECTED PLAN FILES '+extra.join(','));process.exit(1)}console.log('OK plan scope '+plans.join(','))"</automated>
<fails_when>non-zero exit, 或 stderr 含 "UNEXPECTED PLAN FILES"(出现了 04-03+ 适配计划文件,即提前规划了未授权的适配工作)</fails_when>
</verify>
<acceptance_criteria>
- 逐成员对账行 ≥ 10 条,每条含两侧 `file:line`;三态词表(匹配 / 渲染端需改 / 数据端缺失)在文档中出现
- `HeroMover`/`IMoveController`、`HeroAnimateDirection`、`IHeroMoveController`、`IHeroMoveControllerHooks`、`hero.x/y/direction/addHook`、跟随者增删钩子、`HeroKeyMover` 的移动器期望、`manager.ts` heroMap 键类型均已逐成员落位
- 「缺失接口候选」独立成节,每条 `### #04-02-G-*` 含 `渲染端需求` / `数据端现状` / `待确认`,数据端现状带 `file:line`;节末含固定结论行;候选未设计/未修复(D-13);候选只含移动语义 / 移动器成员,不含 `IHeroRendering` / 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`(D-18)
- 「未能从阅读确定(未猜测)」非空,且 `controller.queue` 等不确定项只登记不判定;不出现 `hero.image` / 贴图 / alpha 钩子条目(按 D-18 只在「处置」排除说明中提及)
- 「缺失接口候选」独立成节且**至少 1 条**(空节即失败),每条 `### #04-02-G-*` 含 `渲染端需求` / `数据端现状` / `待确认`,数据端现状带 `file:line`;节末含固定结论行且其 N 等于实际候选条数、≥ 1;候选未设计/未修复(D-13);候选只含移动语义 / 移动器成员,不含 `IHeroRendering` / 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`(D-18)
- 「未能从阅读确定(未猜测)」非空,且 `controller.queue` 等不确定项只登记不判定;该节**不含** `IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha` 字样(门禁机器判定;按 D-18 只在「处置」排除说明中以通用措辞提及)
- `## 处置` 含 D-11 / D-12 / D-13 / D-17 / D-18 的明确表述与范围两侧边界,并明写适配工作(04-03+)未规划
- 相对规划时基线无新增渲染端包改动(`git status --porcelain -- packages-user/client-base packages-user/client-modules` 只含既有 `packages-user/client-base/src/load/**` 条目,且未被回滚/暂存/提交);`.planning/phases/04-render-adaptation` 下计划文件仅 `04-01-PLAN.md` 与 `04-02-PLAN.md`;全文 CRLF
- 相对记录在 `## 方法` 的扫描前基线**零变化**:当前 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 条目集合与基线记录逐条一致(不新增、不消失、状态码不变),且 `基线摘要 sha256`(status 条目 + 各基线条目文件内容的 sha256)未变;既有 `packages-user/client-base/src/load/**` 用户改动未被回滚/暂存/提交、内容未被改动;`.planning/phases/04-render-adaptation` 下计划文件仅 `04-01-PLAN.md` 与 `04-02-PLAN.md`;全文 CRLF
- 交付文档已以 `docs(04-02)` 提交,提交内容仅含该文档与 `04-02-PLAN.md`
</acceptance_criteria>
<done>逐成员三态对账、缺失接口候选、未确定与处置全部定稿;只读/CRLF/范围门禁全绿;交付文档已提交,用户可据此裁决缺失候选并规划后续勇士移动适配</done>
@ -256,7 +314,8 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
|----------|-------------|
| 数据端接口基准 → 探索文档 | 只读引用 `data-common` / `data-base` / `data-state` 的接口与 barrel;基准不变,仅记录差异 |
| 渲染端源码 → 探索文档 | 只读引用 `client-base` / `client-modules`;不得产生任何写回 |
| 执行者 → 仓库工作树 | 本 run 唯一允许写入的交付文件是探索文档;被查渲染端包相对规划时基线必须零**新增**改动(规划时既有的 `client-base/src/load/**` 用户改动原样保留、不回滚) |
| 执行者/子代理 → 执行前置关卡 | AGENTS.md 要求第一个 block 点先汇报并等待用户回复「可以执行」;`yolo`/`auto_advance` 或子代理派发都不是放行理由(Task 0 为 `blocking-human`、`autonomous: false`) |
| 执行者 → 仓库工作树 | 本 run 唯一允许写入的交付文件是探索文档;被查渲染端包相对记录在 `## 方法` 的扫描前基线必须零变化(条目逐条一致 + `基线摘要 sha256` 未变;规划时既有的 `client-base/src/load/**` 用户改动原样保留、不回滚、内容不得改动) |
| 缺失接口候选 → 用户裁决/后续适配 | 候选被下游用户与适配计划消费;把「渲染端需改」误报为「数据端缺失」会诱导错误的数据端改动(D-13) |
| 探索范围 → Phase 5 / 后续增量 | 把 `HeroRendering` 渲染状态(D-18)或 `core.*`(D-12)混入本增量会扩大范围并污染阶段边界;反之把勇士移动契约/移动钩子误排除会漏掉本次主要失配点 |
@ -264,9 +323,10 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-04-02-01 | Tampering | 被查渲染端包 `packages-user/client-base` / `client-modules` | high | mitigate | Task 1 先记录「扫描前基线」(规划时既有、与勇士移动无关的用户改动 `client-base/src/load/**`);Task 1–3 均为只读;Task 3 门禁断言相对基线**无新增条目**(仅放行 `packages-user/client-base/src/load/` 下条目),出现任何其它条目即失败;不得回滚/暂存/提交用户既有改动 |
| T-04-02-01 | Tampering | 被查渲染端包 `packages-user/client-base` / `client-modules` | high | mitigate | Task 1 先记录「扫描前基线」(逐行 porcelain 条目 + `sha256` 摘要;规划时既有、与勇士移动无关的用户改动 `client-base/src/load/**`);Task 1–3 均为只读;Task 3 门禁断言「当前 porcelain 条目集合与基线记录逐条一致」且「基线摘要 `sha256`(status 条目 + 各基线条目文件内容的 sha256)未变」——基线目录内的新增/消失条目、状态码变化或基线文件内容被改动均失败;不得回滚/暂存/提交用户既有改动 |
| T-04-02-02 | Repudiation | 对账行(无锚点则不可追溯) | medium | mitigate | 每条 `#04-02-R-*` 强制含两侧 `file:line`;由 Task 1/3 的 `<verify>` 门禁机器判定(两侧锚点正则) |
| T-04-02-03 | Spoofing | 「缺失接口候选」段 | high | mitigate | 每条候选必须含 `渲染端需求` / `数据端现状` / `待确认` 三段且数据端现状带 `file:line`;节末固定结论行;门禁机器判定;明写「未经用户裁决不视为最终清单」(D-13) |
| T-04-02-03 | Spoofing | 「缺失接口候选」段 | high | mitigate | 候选**至少 1 条**且每条必须含 `渲染端需求` / `数据端现状` / `待确认` 三段、数据端现状带 `file:line`;节末固定结论行的 N 必须等于实际候选条数且 ≥ 1;门禁机器判定;明写「未经用户裁决不视为最终清单」(D-13) |
| T-04-02-07 | Elevation of Privilege | 执行前置关卡(AGENTS.md 要求的第一处 block 点) | high | mitigate | Task 0 为 `checkpoint:decision` + `gate="blocking-human"` 且本计划 `autonomous: false`;`<verify><fails_when>` 明示未收到用户「可以执行」四个字即必须停止、不派发执行子代理;`yolo` / `workflow.auto_advance` 等模式不得自动放行 |
| T-04-02-04 | Elevation of Privilege | 探索范围(`HeroRendering` 渲染状态 / `core.*` / 双布局 / legacy 移植) | medium | mitigate | D-12/D-18 为显式排除项且边界写明(排除 `IHeroRendering`/贴图别名/alpha 钩子,保留移动契约与移动钩子);文中排除说明与「处置」必须出现 `HeroRendering` 与 `core.*`;范围门禁禁止产生 04-03+ 计划文件 |
| T-04-02-05 | Information Disclosure | 探索文档引用的源码行与接口名 | low | accept | 记录只含接口名、文件行号与成员签名,不含凭据/密钥 |
| T-04-02-06 | Denial of Service | 只读子代理越权写入(D-09) | low | mitigate | Task 2 明确子代理仅回传事实表、不写文件、不贴大段源码;最终由主执行者只读门禁统一验证 |
@ -275,21 +335,23 @@ Output: `04-HERO-MOVER-INTERFACE.md`(本 run 唯一新增交付物;零生产
</threat_model>
<verification>
1. 结构门禁(Task 1):探索文档 8 个二级标题(含 `## 方法`)逐字存在;`## 方法` 含「扫描前基线」条目;至少 1 条 `#04-02-R-*` 双锚点对账行;文档含 `HeroRendering` 与 `core.*` 排除说明。
0. 执行前置关卡(Task 0,AGENTS.md 第一个 block 点):向用户汇报「要解决的问题 / 大致解决方案 / 范围边界」后**停下等待**用户明确回复「可以执行」四个字;未收到该回复前不得执行任何任务、不得派发执行子代理,任何触发方式与 `yolo`/`auto_advance` 模式均不得自动放行(人工核对:用户回复原文)。
1. 结构门禁(Task 1):探索文档 8 个二级标题(含 `## 方法`)逐字存在;`## 方法` 含「扫描前基线」条目(逐行原样记录 porcelain 条目)与 `基线摘要 sha256=<64hex>` 行,且记录条目与当前 `git status` 逐条一致;至少 1 条 `#04-02-R-*` 双锚点对账行;文档含 `HeroRendering` 与 `core.*` 排除说明。
2. CRLF 门禁(Task 1/2/3):文档无孤立 `LF`,全文 CRLF(dev.md「换行使用 CRLF 格式」)。
3. 两侧清单门禁(Task 2):数据端段 ≥ 8 个 `data-*` `file:line` 锚点;渲染端段 ≥ 12 个 `client-*` `file:line` 锚点;含 `@ts-expect-error` 标记清单与 `index.ts:line` barrel 导出核对;贴图/alpha 使用点只作 D-18 排除标记。
4. 对账完整性门禁(Task 3):`#04-02-R-*` ≥ 10 条且每条两侧锚点;三态词表齐备;每条 `### #04-02-G-*` 含 `渲染端需求` / `数据端现状` / `待确认`;`候选结论` 行存在;对账行与候选均不含 `IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`(D-18)。
5. 只读门禁(Task 3):`git status --porcelain -- packages-user/client-base packages-user/client-modules` 相对规划时基线**无新增条目**(仅既有 `packages-user/client-base/src/load/**` 条目被放行;不回滚它们)。
4. 对账完整性门禁(Task 3):`#04-02-R-*` ≥ 10 条且每条两侧锚点;三态词表齐备;`### #04-02-G-*` **≥ 1 条**且每条含 `渲染端需求` / `数据端现状` / `待确认`,`候选结论` 行的 N 等于实际候选条数且 ≥ 1;对账行、候选与「未能从阅读确定(未猜测)」节均不含 `IHeroRendering` / `hero.image` / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`(D-18)。
5. 只读门禁(Task 3):当前 `git status --porcelain -- packages-user/client-base packages-user/client-modules` 条目集合与记录在 `## 方法` 的扫描前基线**逐条一致**(不新增、不消失、状态码不变),且 `基线摘要 sha256`(status 条目 + 各基线条目文件内容的 sha256)未变 —— 基线目录内的新增/消失条目或基线文件内容被改动都会失败;既有 `packages-user/client-base/src/load/**` 改动原样保留(不回滚/暂存/提交)。
6. 范围门禁(Task 3):`.planning/phases/04-render-adaptation` 下计划文件仅 `04-01-PLAN.md` 与 `04-02-PLAN.md`(未规划 04-03+ 适配)。
7. 人工核对:「未能从阅读确定(未猜测)」的逐条理由;`## 处置` 对 D-11/D-12/D-13/D-17/D-18 的表述与范围两侧边界;缺失候选是否确实只记录未设计。
8. 提交:`docs(04-02)` 仅含探索文档与 `04-02-PLAN.md`(不含用户既有的 `client-base/src/load/**` 改动)。
</verification>
<success_criteria>
- `.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md` 按 8 段骨架(含 `## 方法`,其中含「扫描前基线」)产出,成员级覆盖数据端现行接口与渲染端现状,逐成员三态对账(匹配 / 渲染端需改 / 数据端缺失)每条两侧 `file:line` 可追溯(D-05/D-17)。
- 缺失接口候选独立成节、含「渲染端需求 / 数据端现状 / 待确认」,且只记录不设计不修复、未经用户裁决不视为最终清单(D-13)。
- `HeroRendering` **渲染状态**(`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`,D-18)与 `core.*`(D-12)被显式排除,未产生对账行或候选;勇士移动契约与移动钩子(`onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 等)在范围内照常登记;`getHeroStatusOn` 类裸函数(D-11)不属本增量。
- 被查两个渲染端包相对规划时基线零**新增**改动(用户既有 `packages-user/client-base/src/load/**` 改动未被回滚/暂存/提交);本 run 只新增探索文档一个交付文件,未规划、未实施任何适配工作(04-03+)。
- **执行前置关卡(AGENTS.md):** 本计划第一个 block 点是 Task 0 —— 向用户汇报「要解决的问题 / 大致解决方案 / 范围边界」后停下等待用户明确回复「可以执行」四个字;未收到前不执行任何任务、不派发执行子代理,任何触发方式(含子代理派发)与 `yolo`/`auto_advance` 模式均不得自动放行。
- `.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md` 按 8 段骨架(含 `## 方法`,其中含「扫描前基线」与 `基线摘要 sha256`)产出,成员级覆盖数据端现行接口与渲染端现状,逐成员三态对账(匹配 / 渲染端需改 / 数据端缺失)每条两侧 `file:line` 可追溯(D-05/D-17)。
- 缺失接口候选独立成节且**至少 1 条**,含「渲染端需求 / 数据端现状 / 待确认」,结论行 N 等于候选条数;只记录不设计不修复、未经用户裁决不视为最终清单(D-13)。
- `HeroRendering` **渲染状态**(`IHeroRendering` / `hero.image` 贴图别名 / `onSetImage` / `onSetAlpha` / `onSetFollowerAlpha`,D-18)与 `core.*`(D-12)被显式排除,未产生对账行或候选,且「未能从阅读确定(未猜测)」节不含这些字样;勇士移动契约与移动钩子(`onMoveHero` / `onJumpHero` / `onTurnHero` / `onStartMove` / `onEndMove` 等)在范围内照常登记;`getHeroStatusOn` 类裸函数(D-11)不属本增量。
- 被查两个渲染端包相对记录在 `## 方法` 的扫描前基线**零变化**(porcelain 条目集合逐条一致 + `基线摘要 sha256` 未变);用户既有 `packages-user/client-base/src/load/**` 改动未被回滚/暂存/提交、内容未被改动;本 run 只新增探索文档一个交付文件,未规划、未实施任何适配工作(04-03+)。
- 静态阅读无法定论的项进入「未能从阅读确定(未猜测)」而非写成缺失/错配;全文 CRLF。
- 用户可据此裁决缺失候选并规划后续勇士移动适配。
</success_criteria>

View File

@ -31,6 +31,7 @@
| New File | Role | Data Flow | Closest Analog | Match Quality |
|----------|------|-----------|----------------|---------------|
| `.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md` | audit / registry document | read-only inventory(分类登记:错配 / 数据端缺失 / 多余旧路径) | `.planning/phases/07-data-fixes/07-LOADSTATE-AUDIT.md`(只读审计骨架)+ `.planning/phases/06-unit-tests/06-TEST-FINDINGS.md`(字段定义表 + 分节登记) | exact(结构) |
| `.planning/phases/04-render-adaptation/04-HERO-MOVER-INTERFACE.md` | audit / interface-reconciliation ledger | read-only interface exploration(逐成员三态对账:匹配 / 渲染端需改 / 数据端缺失 + 缺失接口候选登记) | `.planning/phases/04-render-adaptation/04-RENDER-INTERFACE-AUDIT.md`(同阶段只读对账骨架、元信息块与「方法」段写法)+ `.planning/phases/02-pathfinding/02-INTERFACE-DRAFT.md`(「接口事实源」声明块)+ `.planning/phases/07-data-fixes/07-LOADSTATE-AUDIT.md`(8 段审计骨架) | exact(结构) |
### B. 扫描目标组(只读,不修改)