mirror of
https://github.com/motajs/template.git
synced 2026-10-18 12:40:18 +08:00
docs(07-16): create post-refactor test alignment plan
This commit is contained in:
parent
4aaea68b65
commit
51f085c4f0
629
.planning/phases/07-data-fixes/07-16-PLAN.md
Normal file
629
.planning/phases/07-data-fixes/07-16-PLAN.md
Normal file
@ -0,0 +1,629 @@
|
||||
---
|
||||
phase: 07-data-fixes
|
||||
plan: 16
|
||||
type: execute
|
||||
wave: 16
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- packages-user/data-system/src/path/graph.test.ts
|
||||
- packages-user/data-system/src/path/system.test.ts
|
||||
- packages-user/data-system/src/path/performance.test.ts
|
||||
- packages-user/data-state/test/replayPlayback.test.ts
|
||||
- packages-user/data-base/src/hero/equipment.test.ts
|
||||
- packages-user/data-base/src/hero/saveLoad.test.ts
|
||||
- packages-user/data-base/src/hero/items.test.ts
|
||||
- packages-user/data-common/src/replay/system.test.ts
|
||||
- packages-user/data-base/src/hero/location.test.ts
|
||||
- packages-user/data-base/src/hero/state.test.ts
|
||||
- packages-user/data-state/test/saveablesRoundTrip.test.ts
|
||||
- packages-user/data-state/src/coreEventLayer.test.ts
|
||||
- packages-user/data-base/src/hero/mover.test.ts
|
||||
- packages-user/data-base/src/hero/follower.test.ts
|
||||
- packages-user/data-state/src/event/event.test.ts
|
||||
- packages-user/data-state/test/dataClosure.test.ts
|
||||
- packages-user/data-state/test/nodeTracer.test.ts
|
||||
- packages-user/data-state/test/fixtures/closed-loop.ts
|
||||
autonomous: false
|
||||
gap_closure: true
|
||||
requirements:
|
||||
- FIX-01
|
||||
estimate:
|
||||
tokens: 70000
|
||||
raw_tokens: 70000
|
||||
tasks: 6
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "**本计划只改测试面**:提交区间 `git diff BASE_SHA..HEAD --name-only` 的全集必须落在本计划 `files_modified` 的 18 条声明路径内;`packages-user/data-*/src/**.ts` 中**非测试文件**(即路径不匹配 `*.test.ts` 且不是本计划的 1 个测试夹具)在该区间内**零改动**。任何生产源码改动即视为越界,必须按 D-09 退出并修订计划"
|
||||
- "**17 行清单逐行有归属**:`07-VERIFICATION.md` 的 `### Post-Refactor Test Breakage` 表 1..17 行,每一行要么在本计划某个 Task 的声明范围内被对齐(Task 1 覆盖 1..4、Task 2 覆盖 5..8、Task 3 覆盖 9..12、Task 4 覆盖 13..17),要么在 Task 0 被用户明确裁定为「另立计划 / 退役 / 不改」。不得有任何一行既未被覆盖也未被裁定"
|
||||
- "**行 1..4(寻路家族)按 shipped 接口对齐**:`4aaea68` 后的 `IMapGraphBuilder`(`data-system/src/path/types.ts:45-79`)与 `IPathFinder`(`:121-152`)**已无 `useMapState` 成员**(`useMapState` 在 `4aaea68` 中被删除,索引器宽度改由 `MapGraphBuilder.useMapLayer` 经 `indexer.setWidth(layer.width)` 提供,`graph.ts:38-43`);`useDirGroup(group: number)` 已被 `useFaceHandler(face: IFaceHandler<number> | null)`(builder,`:65`)与 `useFaceHandler(face: IFaceHandler<number>)`(finder,`:144`)取代;builder 的 face 默认值由旧的 `InternalDirectionGroup.Dir4` 变为 `null`,未注入时 `build()` 告警 173 并返回 `null`(`graph.ts:81-84`)。对齐后 `graph.test.ts` / `system.test.ts` / `performance.test.ts` / `replayPlayback.test.ts` 的 `pnpm exec vitest run` 聚焦命令 0 失败"
|
||||
- "**行 5..8(录像家族)按 shipped 成员名与钩子语义对齐**:`ReplaySystem` 的成员名为 `array`(`data-common/src/replay/system.ts:34`),生产写入点是 `equipment.ts:195/212`、`items.ts:130`、`mover.ts:180-189/222` 的 `replay.array.add(...)`;`onRecordCommand` 的第二个实参是 `this.array.length - 1`(`system.ts:66`,0 基),故 `replay/system.test.ts` 的首步索引期望为 `0`。对齐后 4 个文件的聚焦命令 0 失败"
|
||||
- "**行 9..11(楼层/位置存档形状)按 shipped 形状对齐**:`HeroLocation.setFloor` 的入参由 `string | undefined` 变为 `IGameMap`(`location.ts:49-53`),`floorId` 改为从 `map.floorId` 派生,并新增 `location.map`(`location.ts:24`);`IHeroLocation.setFloor(map: IGameMap | null)`(`hero/types.ts:316`)、`IHeroLocationHooks.onSetFloor?(map: IGameMap | null)`(`:283`)、`IHeroChangeFloorInfo.target: IGameMap`(`:844`)为接口事实源。对齐后 `location.test.ts` / `state.test.ts` / `saveablesRoundTrip.test.ts` 的聚焦命令 0 失败(含 `floorId` 断言回到 `'F2'` / `'F1'`)"
|
||||
- "**行 12(初始化器)按其真实处置成文**:`CoreState.initMapState` **已从全仓删除**(`data-state/src/core.ts` 现有成员仅为 `addSaveableContent` / `getSaveableContent` / `bindSaveableExecuter` / `saveState` / `loadState` 与私有 `registerReplayCommands`;全仓对该名字的引用只剩 `coreEventLayer.test.ts:44` 与 `:137`)。该行的处置(重定向到 shipped 拥有者 `MapState.fromRaw`,或经用户批准退役该文件)必须逐字写入 Task 0 的 `<record>`,并按裁决执行"
|
||||
- "**行 13..17 在写任何断言之前先取得用户逐行裁定**(Task 0 Q4):规划期已测得 13/14 的首个可观测错误为 `TypeError: Cannot read properties of undefined (reading 'add')`(`mover.test.ts:48`、`follower.test.ts:67` 的录像桩仍是 `route`,而 `mover.ts:222` 调用 `replay.array.add`),15/16/17 的失败根因为夹具仍以 **floorId 字符串**调用 `setFloor`(`event.test.ts:76`、`closed-loop.ts:166` 与 `:234`),使 `location.map` 成为字符串、通行谓词读到 `map.eventLayer` 为 `undefined` 而拒绝移动。这三类都必须由用户裁定为「预期行为下的形状对齐(A)」还是「生产回归(B,超出本计划范围)」;未获裁定前**不得**修改这 5 个文件"
|
||||
- "**无一处断言被弱化**:18 个文件各自的 `expect(` 出现次数不低于 `BASE_SHA` 版本;每一次「期望值被改写」都必须以「文件 → 旧值 → 新值 → 授权的 Task 0 裁决条目」四列形式登记在 `07-16-SUMMARY.md`;不新增 `it.skip`/`describe.skip`/`test.skip`/`xit`/`xdescribe`/`it.todo`,仓库级 skipped 计数仍恰为 1(`data-base/src/hero/equipment.test.ts` 的码 147 用例,D-06,本计划不改动它的 skip 状态)"
|
||||
- "**注释只做三类落笔**:测试文件 `:1` 文件头覆盖说明的**追加**、覆盖描述发生变化的 `it` 前单行中文注释(dev.md:86)、以及**点名已删除接口**的测试文件注释改为点名 shipped 接口(例如把点位指向 `array` / `useFaceHandler` / `setFloor(map)`);除此三类之外的既有 jsDoc/注释一字不改,且这三类落笔必须先取得 Task 0 的注释范围裁决(记录项 R1)"
|
||||
- "**D-12/D-44 文件级三步门禁**对 18 个文件成立:`pnpm exec eslint --fix <文件>` 后 `pnpm exec eslint <文件>` 0 错误;`pnpm exec vue-tsc --noEmit` 输出按**改动文件的相对路径逐个过滤** 0 命中(不以整仓退出码判定,仓库存在既有渲染/legacy 诊断);`pnpm test:ci` 0 失败"
|
||||
- "**`pnpm test:ci` 收口**:`Test Files` 行不再出现非零 `failed`、`Tests` 汇总行不出现非零 `failed`、且该行仍含 `1 skipped`;总用例数与本计划执行前重录的当日基线**一致**——除非 Task 0 的裁决明确授权行 12 退役/重定向或 `location.test.ts` 的不可表达分支重塑造,此时用例数的变化必须逐条在 SUMMARY 登记(本计划不新增测试用例,D-10)"
|
||||
- "**提交纪律**:按接口家族原子提交(Task 1..4 各一次 `test(07-16): …`),一律 `git add <显式路径>`;本计划的声明路径集合 = `files_modified` 的 18 条 + 本计划的 `07-16-SUMMARY.md`;`.planning/ROADMAP.md` 与 `.planning/STATE.md` 的跟踪同步属 orchestrator 收尾步骤,本计划零编辑、零暂存、零提交"
|
||||
artifacts:
|
||||
- path: packages-user/data-system/src/path/graph.test.ts
|
||||
provides: "行 1:`createFixture` 去掉 `builder.useMapState(maps)`(`MapGraphBuilder` 已无该成员,宽度随 `useMapLayer(layer)` 生效),并按用例意图注入方向处理器——默认四正交用 `new modules.Dir4FaceHandler()`(对应旧默认 `InternalDirectionGroup.Dir4`),八方向用 `new modules.Dir8FaceHandler()`;`:346` 的 `builder.useFaceHandler(InternalDirectionGroup.Dir8)` 改为注入处理器对象"
|
||||
- path: packages-user/data-system/src/path/system.test.ts
|
||||
provides: "行 2:`createSystem` 去掉 `system.finder.useMapState(maps)`;因 finder 的 face 不再有默认值,夹具补注入方向处理器(`Dir4FaceHandler`,与旧默认一致),使 `finder.find` 不再走 `build()` 的告警 173 早退"
|
||||
- path: packages-user/data-system/src/path/performance.test.ts
|
||||
provides: "行 3:`system.finder.useMapState(maps)` 与 `builder.useMapState(maps)` 两处删除;`buildGraph` 内联 builder 补注入方向处理器,保持性能矩阵的图规模不变"
|
||||
- path: packages-user/data-state/test/replayPlayback.test.ts
|
||||
provides: "行 4:`createSmallMapScene` 的 `useMapState(state.maps)` 删除;`new DefaultPassPredicateImpl(state.maps)` 改为 `new DefaultPassPredicateImpl(state)`(`predicate.ts:8` 收 `IStateSystem`);finder 补注入方向处理器;`:124` 的 `setFloor('F1')` 改为传真实 `IGameMap`"
|
||||
- path: packages-user/data-base/src/hero/equipment.test.ts
|
||||
provides: "行 5:`ReplaySystemStub` 的成员 `route` 改为 `array`(`createReplaySystem` 的返回对象与 `:377/:385/:403/:405/:410` 的引用点同步),使 `equipment.ts:195/212` 的 `replay.array.add(...)` 命中桩;`:1` 文件头覆盖说明追加;顺带解除 07-15 CR-01 见证的阻塞(其红只因本行)"
|
||||
- path: packages-user/data-base/src/hero/saveLoad.test.ts
|
||||
provides: "行 6:`:82` 的 `replaySystem: { route, disable, revert }` 成员改名为 `array` 并同步局部常量名;`:284/:289/:511/:518/:543/:593/:599` 的 `setFloor(<string>)` 改为传 `IGameMap`,使 `floorId` 断言回到 `'F2'`"
|
||||
- path: packages-user/data-base/src/hero/items.test.ts
|
||||
provides: "行 7:`:62` 的录像桩成员 `route` 改为 `array`,使 `items.ts:130` 的 `replay.array.add(...)` 命中桩"
|
||||
- path: packages-user/data-common/src/replay/system.test.ts
|
||||
provides: "行 8:`appends a replay step and dispatches the record hook` 的 `onRecordCommand` 索引期望由 `1` 改为 `0`(生产传 `array.length - 1`,`system.ts:66`);`:1` 文件头覆盖说明追加"
|
||||
- path: packages-user/data-base/src/hero/location.test.ts
|
||||
provides: "行 9:`updates the floor and notifies the onSetFloor hook` 重塑为 `setFloor(<IGameMap>)` 形态,钩子断言从楼层 id 序列改为「收到的 map 对象 + `location.floorId` 由 `map.floorId` 派生」;`setFloor(undefined)` 这一分支在 shipped 实现下不可表达,其处置必须先在 Task 0 获裁定(记录项 R2)"
|
||||
- path: packages-user/data-base/src/hero/state.test.ts
|
||||
provides: "行 10:`changeFloor` 用例的 `target: 'F2'` 改为 `target: <IGameMap>`,`floorId` 断言(`'F2'`)与钩子顺序断言(`['before','floor','after']`)逐字保留"
|
||||
- path: packages-user/data-state/test/saveablesRoundTrip.test.ts
|
||||
provides: "行 11:`:127` 与 `:165` 的 `setFloor(<string>)` 改为传 `IGameMap`,`floorId` 断言(`'F1'`)与 `isMapActive` 断言逐字保留"
|
||||
- path: packages-user/data-state/src/coreEventLayer.test.ts
|
||||
provides: "行 12:按 Task 0 裁决处置——重定向到 shipped 拥有者 `MapState.fromRaw`(`data-base/src/map/mapState.ts:219-249`)并保持「event alias 图层被选为事件层、其余层序与 zIndex 不变」的原断言意图;或经用户批准退役该文件。不得保留对已删除成员 `initMapState` 的调用"
|
||||
- path: packages-user/data-base/src/hero/mover.test.ts
|
||||
provides: "行 13(待 Task 0 Q4 裁定):`:48` 的录像桩 `route` 改为 `array`,解除 6 条用例的 `onEnd` 悬挂(规划期实测首个可观测错误即 `reading 'add'`);不得改写这 6 条用例的既有断言"
|
||||
- path: packages-user/data-base/src/hero/follower.test.ts
|
||||
provides: "行 14(待 Task 0 Q4 裁定):`:67` 的录像桩 `route` 改为 `array`,解除 `gathers followers asynchronously and waits for the movement` 的 30s 悬挂;不得改写该用例的既有断言"
|
||||
- path: packages-user/data-state/src/event/event.test.ts
|
||||
provides: "行 15(待 Task 0 Q4 裁定):`:76` 的 `setFloor('F1')` 改为传真实 `IGameMap`,使 `awaits hero sequence and forward-step movement` 的勇士位移断言(`expected +0 to be 1`)在原期望值下成立"
|
||||
- path: packages-user/data-state/test/dataClosure.test.ts
|
||||
provides: "行 16(待 Task 0 Q4 裁定):本文件不含过期形状调用(`:272`/`:294` 已用 `replay.array`),其 2 条失败的修复来源是共享夹具 `closed-loop.ts`;若裁定为 A,则本文件**零改动**,只在 SUMMARY 说明「失败由共享夹具解释」"
|
||||
- path: packages-user/data-state/test/nodeTracer.test.ts
|
||||
provides: "行 17(待 Task 0 Q4 裁定):同上,本文件不含过期形状调用,其 1 条失败的修复来源是共享夹具 `closed-loop.ts`;裁定为 A 时本文件**零改动**"
|
||||
- path: packages-user/data-state/test/fixtures/closed-loop.ts
|
||||
provides: "行 16/17 与所有依赖该夹具的用例的共同根因:`:166` 与 `:234` 的 `setFloor('F1')` 改为传真实 `IGameMap`(`state.maps.fromRaw` 的返回值),使 `location.map` 不再是字符串、通行谓词可读到 `map.eventLayer`,夹具的其余装配与 `IClosedLoopFixture` 成员逐字保留"
|
||||
key_links:
|
||||
- "`MapGraphBuilder.useMapLayer`(`graph.ts:38-43`)在 `layer` 非空时执行 `this.indexer.setWidth(layer.width)` ↔ 被删除的 `useMapState` 原本承担的「提供图宽度」职责——这就是 `useMapState` 没有同名替代品的原因,测试侧只能删除该调用而不能改名为别的注入方法"
|
||||
- "`MapGraphBuilder.build` 的三处 173 早退(`graph.ts:73-84`:`layer` / `predicate` / `face` 任一为 `null`)↔ builder 的 `face` 默认值由 `InternalDirectionGroup.Dir4` 变为 `null`——凡调用 `build()` 或 `finder.find()` 的测试路径都必须显式注入方向处理器,否则断言会从「图内容不符」退化为空图"
|
||||
- "`mover.ts:216-222` 的瞬移录像写入 `replay.array.add(ReplayCode.Teleport, [step.x, step.y])`(`mover.ts` 在 `4aaea68` 新增)↔ `mover.test.ts:48`/`follower.test.ts:67`/`equipment.test.ts:86-99`/`saveLoad.test.ts:78-82`/`items.test.ts:62` 的录像桩成员名——桩缺 `array` 时在移动 Promise 链内抛 `TypeError`,`onEnd` 永不结算,表现为 30s 悬挂(规划期实测)"
|
||||
- "`equipment.ts:170-195` 的 `replay.disable()` / `replay.revert()` / `replay.array.add(...)` 三者 ↔ 录像桩 `{ array, disable, revert }` 的三成员形状——改名必须是整体形状对齐,不得只补 `array` 而留下 `route`(否则桩与断言描述不一致)"
|
||||
- "`system.ts:63-68` 的 `this.array.add(code, params)` 紧接 `hook.onRecordCommand?.(code, this.array.length - 1, params)` ↔ `replay/system.test.ts:94` 的期望元组——索引语义由「1 基」变为「刚追加那一步在数组中的 0 基下标」"
|
||||
- "`location.ts:49-53` 的 `setFloor(map: IGameMap)` ↔ `hero/types.ts:316` 的接口声明(允许 `null`)↔ `state.ts:117` 的 `this.location.setFloor(info.target)` ↔ `hero/types.ts:844` 的 `IHeroChangeFloorInfo.target: IGameMap`——测试侧每一处 `setFloor` 都必须提供 `IGameMap`,且 `location.map` 会被后续的通行判定读取(`mover.ts:203` 的 `map: this.tile.map` → `predicate.ts:33-35` 的 `if (!map) return false;` 与 `map.eventLayer`)"
|
||||
- "`data-state/src/core.ts` 已无 `initMapState` 成员(`:228-330` 仅剩 `registerReplayCommands`/`addSaveableContent`/`getSaveableContent`/`bindSaveableExecuter`/`saveState`/`loadState`)↔ 同名职责现由 `MapState.fromRaw`(`mapState.ts:219-249`,内含 `createMap` / `setLayerAlias` / `setEventLayer` / `setActiveStatus`)承担——行 12 无同主体替代,只有重定向或退役两条路"
|
||||
- "`data-common/src/types.ts` 在 `4aaea68` 中删除了 `IDataCommon.directionMapper` 成员 ↔ 测试夹具里残留的 `directionMapper: new DirectionMapper()` 只作为结构性假值存在(多处夹具已用 `as never` 收口)——本计划不要求清理它,除非某个焦点命令因它而红"
|
||||
assumptions:
|
||||
- "A1(D-10 例外口径):本计划**不新增任何测试用例**(D-10 的常规口径);每一次「期望值改写」都必须由 Task 0 的裁决授权并登记在案。若执行中发现某行无法在不新增用例的前提下表达 shipped 形状,按 D-09 退出并修订计划,不得自行新增用例"
|
||||
- "A2(测试面零符号新增):本计划不新增生产符号(无类/方法/字段/导出/告警码/接口成员)。测试面新增的标识符仅限:`dir4`/`dir8` 形态的局部处理器常量(若沿用既有命名则零新增)、以及为对齐桩成员名而改用的既有名字 `array`。任何新的公共/受保护/私有命名必须先按 AGENTS.md 取得用户批准(记录项 R3)"
|
||||
- "A3(基线不写死):本计划不把「17 文件 / 73 失败 / 667 通过 / 1 跳过」当作门禁数字——该组数字来自 `07-VERIFICATION.md` 的计划期记录(`19ad1ea`+),**未经本次规划运行复测**,且用户并行重构可能已改变它。执行前必须重录当日基线(`pnpm test:ci`),并以「失败集合 ⊆ 本清单 17 行 ∪ 用户并行改动」为对照口径;任何不在本清单内的失败一律归因于用户并行工作、不调查、不修复、不放宽断言,只登记观察"
|
||||
- "A4(规划期实测证据,仅限本次会话跑过的命令):`pnpm exec vitest run packages-user/data-base/src/hero/mover.test.ts packages-user/data-base/src/hero/follower.test.ts` 实测 `2 failed (2)` 文件 / `7 failed | 12 passed (19)` 用例 / `Errors 7`,每一条失败的首个可观测信号均为 `TypeError: Cannot read properties of undefined (reading 'add')`,且 6 条 mover 用例与 1 条 follower 用例的失败形态是 `Error: Test timed out in 30000ms.`;`pnpm exec vitest run packages-user/data-state/src/event/event.test.ts packages-user/data-state/test/dataClosure.test.ts packages-user/data-state/test/nodeTracer.test.ts` 实测 `3 failed (3)` 文件 / `4 failed | 17 passed (21)` 用例,失败用例名为 `awaits hero sequence and forward-step movement`、`executes a trigger event and applies its awaited mutation`、`preserves replay order, awaits completion, and stops on failure`、`replays movement, awaits event mutation, and ends normally`。这两组数据是本计划 Q4 的证据来源,执行时可用同样的命令复核"
|
||||
- "A5(行 12 的替代品已被覆盖):`MapState.fromRaw` 的图层装配语义已由 `packages-user/data-base/src/map/mapState.test.ts`(`generates and reads content on a map created by createMap`、`createMap` 注册与重复注册告警 121 等)与 `mapLifecycle.test.ts` 覆盖;因此行 12 若被裁定为「退役该文件」,其覆盖并非净损失——这一判断必须由用户在 Task 0 明确认领,不得由执行者自行认定"
|
||||
- "A6(`setFloor(null)` 无 shipped 支撑):`IHeroLocation.setFloor` 的接口签名允许 `null`(`hero/types.ts:316`),但 `HeroLocation.setFloor` 的实现签名是 `map: IGameMap` 且直接读取 `map.floorId`(`location.ts:49-53`),因此「把楼层清空为无」这一语义在当前实现下**没有可运行的表达路径**;`loadState` 仍是唯一能把 `floorId` 置回 `undefined` 的入口(`location.ts:74-79`)。这是否为待登记的生产缺口,由用户在 Task 0 裁定(记录项 R2),本计划不自行登记、不自行修改生产代码"
|
||||
- "A7(共享夹具与「零改动」文件):行 16/17 的失败可由 `closed-loop.ts` 的夹具对齐**完整解释**(规划期代码证据:`closed-loop.ts:166/234` 传字符串 → `location.map` 为字符串 → `mover.ts:203` 把它交给谓词 → `predicate.ts:33-35` 在 `map.eventLayer` 上取到 `undefined` → 拒绝移动)。因此 `dataClosure.test.ts` 与 `nodeTracer.test.ts` 在裁定为 A 时应保持**零改动**,并不得为了让它们「看起来参与修复」而改动其断言"
|
||||
- "A8(flag 假设,必须保留在册,不得视为已解决):本阶段无 SPEC,故 specless 探针回退运行;该探针对 `FIX-01` 只返回一行 `unclassified — review manually`(`coverage.unresolved = 1`),**未**给出可判定的边界/禁止项分类,也**不得**用任何回填(backstop)自动消化。本计划以「17 行清单逐行归属 + 18 个文件的聚焦/全量命令 + 文件级三步门禁」作为 `FIX-01` 在本轮的唯一可操作验证面,并明示「探针对 `FIX-01` 的未决分类状态保留在册」"
|
||||
- "A9(无用户环境准备):本计划无外部服务、无环境变量、无仪表盘配置、无需人工在仓外执行任何步骤,故 frontmatter 不含 `user_setup`"
|
||||
- "A10(行 3 的性能用例性质):`performance.test.ts` 断言的是图规模/耗时矩阵而非精确步序;对齐只许触碰注入面(删除 `useMapState`、补方向处理器),不得改动用例的规模参数与断言阈值——否则等于改测试意图"
|
||||
prohibitions:
|
||||
- "**不得修改任何生产源码**:`packages-user/data-*/src/**` 下除本计划 18 条声明路径之外的文件一律零改动;尤其不得触碰 `data-system/src/path/{graph,finder,system,types}.ts`、`data-common/src/{types.ts,replay/system.ts,common/faceManager.ts}`、`data-base/src/{hero/location.ts,hero/types.ts,hero/mover.ts,map/**}`、`data-state/src/{core.ts,hero/predicate.ts}`、`packages/common/src/logger.json`。若某行只有改生产才能转绿,按 D-09 退出并把该行升级给用户,不得就地修改"
|
||||
- "不得新增 `it.skip`/`describe.skip`/`test.skip`/`xit`/`xdescribe`/`it.todo`;不得弱化、删除或改写既有断言以换取转绿;期望值改写必须逐条由 Task 0 裁决授权并登记在 SUMMARY"
|
||||
- "未经 Task 0 的 Q4 裁定,不得修改 `data-base/src/hero/mover.test.ts`、`data-base/src/hero/follower.test.ts`、`data-state/src/event/event.test.ts`、`data-state/test/dataClosure.test.ts`、`data-state/test/nodeTracer.test.ts` 这 5 个文件(含不得先行改动其录像桩或夹具调用);也不得把它们的失败当作「顺手修掉」的对象"
|
||||
- "不得改动任何既有 jsDoc;测试文件内既有注释只允许三类落笔(`:1` 文件头覆盖说明的追加、覆盖描述变化的 `it` 前单行注释、点名已删除接口的注释改为点名 shipped 接口),且必须先取得 Task 0 的注释范围裁决(R1)"
|
||||
- "不得新增依赖、不得改 `package.json`/`pnpm-lock.yaml`;不得新建源码或测试文件(本计划零新增文件,只在 18 个既有文件内改动);不得使用 `try-catch-finally`(dev.md:121)"
|
||||
- "不得使用连续 `as` 断言(`as unknown as …`)、不得新增 `import type`、不得新增字符串特殊标识符、不得为对齐而引入 `any`"
|
||||
- "不得暂存或提交本计划**声明路径**之外的任何路径(声明路径 = `files_modified` 的 18 条 + 本计划的 `07-16-SUMMARY.md`);`.planning/ROADMAP.md` 与 `.planning/STATE.md` 由 orchestrator 在本计划收尾后同步,本计划零编辑/零暂存/零提交;工作树中既有的无关未暂存修改/删除与未跟踪文件同样不得被暂存或提交:提交一律 `git add <显式路径>`;禁止 `git add -A`/`git add -a`、`git stash`、`git clean`、`git reset --hard`"
|
||||
- "Task 0 的 `<record>` 为空时**不得**进行任何文件修改(未裁决即停);执行期若发现与裁决冲突,暂停并请用户澄清,不得自行改用任何【未选】分支或另辟他法(D-09)"
|
||||
---
|
||||
|
||||
<objective>
|
||||
闭合 `07-VERIFICATION.md` 的 `### Post-Refactor Test Breakage`(recorded 2026-09-17, user-requested repair)所载 **17 个测试文件的失效行**:让它们重新匹配用户已落地的接口/形状(寻路系统改选 `4aaea68`、`ReplaySystem.route` → `.array` 改名 `a2a8e6e`、`onRecordCommand` 索引改为 `array.length - 1` `34ba9e8`、`HeroLocation.setFloor` 改收 `IGameMap`),使 `pnpm test:ci` 回到 0 失败、跳过项仍恰为 1 条。
|
||||
|
||||
**本计划是 test-only 计划**:18 条声明路径全部是测试文件与 1 个测试夹具(`data-state/test/fixtures/closed-loop.ts`)。用户的定性是「应该主要都是接口变动,测试内容基本不变」;凡不能解释为接口/形状变动的行,一律在 Task 0 交用户逐行裁定,不得就地改写。
|
||||
|
||||
**规划期实测的失效清单(逐行来源 = `07-VERIFICATION.md` 的 17 行表)**
|
||||
|
||||
| # | 测试文件 | 规划期判定 |
|
||||
|---|-----------|-------------|
|
||||
| 1 | `data-system/src/path/graph.test.ts` | 接口:`builder.useMapState` 已删除;`useFaceHandler` 需要处理器对象 |
|
||||
| 2 | `data-system/src/path/system.test.ts` | 接口:`finder.useMapState` 已删除;finder 的 face 不再有默认值 |
|
||||
| 3 | `data-system/src/path/performance.test.ts` | 接口:同上(finder + 内联 builder 两处) |
|
||||
| 4 | `data-state/test/replayPlayback.test.ts` | 接口:`useMapState` + `DefaultPassPredicateImpl` 构造实参 + face 注入 + `setFloor` 形状 |
|
||||
| 5 | `data-base/src/hero/equipment.test.ts` | 接口:录像桩成员 `route` 已不存在(生产为 `array`) |
|
||||
| 6 | `data-base/src/hero/saveLoad.test.ts` | 接口:录像桩成员 + 7 处 `setFloor` 形状 |
|
||||
| 7 | `data-base/src/hero/items.test.ts` | 接口:录像桩成员 |
|
||||
| 8 | `data-common/src/replay/system.test.ts` | 契约:`onRecordCommand` 索引由 1 基改为 `length - 1` |
|
||||
| 9 | `data-base/src/hero/location.test.ts` | 接口:`setFloor` 收 `IGameMap`;`setFloor(undefined)` 分支不可表达 |
|
||||
| 10 | `data-base/src/hero/state.test.ts` | 接口:`IHeroChangeFloorInfo.target` 由 `string` 改为 `IGameMap` |
|
||||
| 11 | `data-state/test/saveablesRoundTrip.test.ts` | 接口:`setFloor` 形状(2 处) |
|
||||
| 12 | `data-state/src/coreEventLayer.test.ts` | **删除**:`CoreState.initMapState` 全仓已不存在(重定向或退役,需裁定) |
|
||||
| 13 | `data-base/src/hero/mover.test.ts` | 接口:录像桩成员 `route`(实测 `reading 'add'` → 6 条 30s 悬挂) |
|
||||
| 14 | `data-base/src/hero/follower.test.ts` | 接口:录像桩成员 `route`(实测同上 → 1 条 30s 悬挂) |
|
||||
| 15 | `data-state/src/event/event.test.ts` | 形状:夹具 `setFloor('F1')` 使 `location.map` 为字符串 → 移动被拒 |
|
||||
| 16 | `data-state/test/dataClosure.test.ts` | 形状:失败由共享夹具 `closed-loop.ts` 解释(本文件零改动) |
|
||||
| 17 | `data-state/test/nodeTracer.test.ts` | 形状:同上(本文件零改动) |
|
||||
|
||||
现状(shipped 事实源,逐条锚点,行号基于本计划所读版本):
|
||||
|
||||
1. **寻路家族(行 1..4)**:`4aaea68` 删除了 `useMapState`(builder 与 finder 两处),把 `useDirGroup(group: number)` 换成 `useFaceHandler(face: IFaceHandler<number> | null)`(builder,`path/types.ts:65`)/`useFaceHandler(face: IFaceHandler<number>)`(finder,`:144`),把 `PathfindingGraphBuilder` 改名为 `MapGraphBuilder`,并给 builder 暴露 `readonly indexer: ILocationHelper`(`:47`)。宽度来源从 `maps` 转到 `useMapLayer` 内的 `indexer.setWidth(layer.width)`(`graph.ts:38-43`);builder 的 `face` 默认值从 `InternalDirectionGroup.Dir4` 变为 `null`,缺失即走 `build()` 的三处 173 早退(`graph.ts:73-84`)。测试文件在 `4aaea68` 中只被机械改名(`graph.test.ts` 的类名两处、`performance.test.ts` 的 import 与类型两处、`graph.test.ts:346` 的 `useDirGroup` → `useFaceHandler`),调用面未对齐。
|
||||
2. **录像家族(行 5..8)**:生产成员名是 `array`(`replay/system.ts:34`),写入点见 `equipment.ts:195/212`、`items.ts:130`、`mover.ts:180-189/222`;`onRecordCommand` 的第二实参是 `this.array.length - 1`(`:66`)。测试桩仍是 `{ route: { add } }`(`equipment.test.ts:77`/`items.test.ts:62`/`mover.test.ts:48`/`follower.test.ts:67`/`saveLoad.test.ts:82`)。
|
||||
3. **楼层/位置形状(行 9..11)**:`HeroLocation.setFloor(map: IGameMap)` 直接取 `map.floorId`(`location.ts:49-53`),并新增 `location.map`(`:24`);接口侧 `setFloor(map: IGameMap | null)`(`hero/types.ts:316`)、`onSetFloor?(map: IGameMap | null)`(`:283`)、`IHeroChangeFloorInfo.target: IGameMap`(`:844`)。测试夹具仍有 16 处 `setFloor(<字符串>)` 调用点(清单见 `must_haves.truths` 的对应条目与各 Task 的 `<read_first>`)。
|
||||
4. **初始化器(行 12)**:`CoreState.initMapState` 已被整体删除;同名职责现落在 `MapState.fromRaw`(`mapState.ts:219-249`)。`coreEventLayer.test.ts` 是唯一引用点(`:44` 的类型声明、`:137` 的调用)。
|
||||
5. **行 13..17**:规划期实测(A4)证明 13/14 的首个可观测错误是录像桩缺 `array` 所致的 `TypeError`,15/16/17 的失败根因是 `setFloor` 传字符串导致通行判定恒假(代码链见 `key_links`)。两者都指向形状/接口漂移,但**必须由用户逐行认领**后才可落笔。
|
||||
|
||||
Purpose: 用户的「第二轮改选」在源码侧已落地,但 17 个测试文件仍停在旧接口上,使整套数据端测试无法作为回归网使用。本计划把这 17 行逐行对齐回 shipped 形状,让 `pnpm test:ci` 重新成为可信门禁,同时用「只改测试面 + 逐行归属 + 期望值改写台账 + 不得弱化断言」四条约束,杜绝「为了让测试变绿而偷偷改契约」这一本阶段已被记录过两次的风险(`07-VERIFICATION.md:337` 的 Common root cause)。
|
||||
|
||||
Output(本计划产出物 = frontmatter `files_modified` 的 18 个既有文件,**零新建文件、零新增依赖、零新增生产符号**):4 个寻路/回放测试文件(Task 1)、4 个录像相关测试文件(Task 2)、4 个楼层/初始化器相关文件(Task 3)、5 个行为存疑文件 + 1 个共享夹具(Task 4),以及 `.planning/phases/07-data-fixes/07-16-SUMMARY.md`(Task 5)。
|
||||
|
||||
**Artifacts this phase produces(本计划产出物,符号级清单)**
|
||||
|
||||
- **生产代码:零改动、零新增符号**(无新类、新方法、新字段、新导出、新告警码、新接口成员)。
|
||||
- **测试面标识符**:仅允许出现既有名字(`array`、`useFaceHandler`、`useMapLayer`、`setFloor`、`map`、`floorId`、`DefaultPassPredicateImpl`)与既有的处理器构造(`Dir4FaceHandler`/`Dir8FaceHandler`,`data-common/src/common/faceManager.ts:147/222`)或从夹具取得的处理器;任何**新的**公共/受保护/私有命名(含测试内的新局部常量与新用例标题)必须先取得用户批准(记录项 R3)。
|
||||
- **零新增用例**:不新增 `it`,不新增测试文件(D-10 常规口径)。
|
||||
- **产物**:`.planning/phases/07-data-fixes/07-16-SUMMARY.md`(Task 5),必须含:17 行 → 处置 → 提交的映射表、期望值改写台账(文件/旧值/新值/授权裁决)、18 个文件的 `expect(` 计数对照、Task 0 的记录项 R1/R2/R3 与裁决、行 12 的最终处置、行 13..17 的裁定与证据、以及「无生产改动」的区间证据。
|
||||
- **Phase 7 验证已失效**:`07-VERIFICATION.md`(`_Verified: 2026-09-17T10:42:49Z_`)既不覆盖 `4aaea68` 之后的代码,也不覆盖本批修复;本计划执行完毕后**必须重跑 `/gsd-verify-work`(或 `gsd-verify-work 7`)**重新出具结论。
|
||||
|
||||
**基线(规划期记录,未经本次规划运行的 `pnpm test:ci` 复测)**:`07-VERIFICATION.md` 记录为 **17 失败文件 / 73 失败用例 / 667 通过 / 1 跳过(741)**,唯一跳过项是 `data-base/src/hero/equipment.test.ts` 的码 147 用例(设计如此,D-06)。**执行前必须重录当日基线**并与本行对比;文件数或跳过项口径不符时先查明原因,任何不在 17 行清单内的失败一律归因于用户并行工作(A3)。本计划不写死通过数——因为本计划**不新增用例**,收口口径是「失败集合被清空 + 跳过数仍为 1 + 总用例数与当日基线一致(除 Task 0 授权的用例数变化)」。
|
||||
|
||||
Phase 7 裁决对应(逐条引用):D-01: 本计划是 Phase 7 第 16 个计划,承接 `07-VERIFICATION.md` 的 `### Post-Refactor Test Breakage`(17 行)。D-02: 按接口家族切分任务(寻路 / 录像 / 楼层与初始化器 / 行为存疑),逐家族汇报、逐家族门禁、逐家族提交。D-03..D-08 均不涉及(本计划不改 `combat`/`enemy`/`map` 生产代码,也不改 `#06-05-3`、`#06-07-1` 的既有处置)。D-09: 由 Task 0 关卡承载(本计划**尚未裁决**,`<record>` 为空,执行者必须停在此处)。D-10: 本计划**不新增用例**,只对齐既有用例(A1)。D-11: 测试文件按其自身规范(AGENTS.md:测试文件由 AI 维护,每个 `it` 前一行注释;dev.md:86 要求测试范围变化时同步注释)落笔,且三类注释落笔需先获 R1 裁决;**任何 jsDoc 一字不改**。D-12: 沿用 06-CONTEXT D-44 文件级三步门禁。D-13: 按接口家族原子提交。D-14: 这 17 行**不是**评审发现,没有 `WINDOWS.md` 账本条目——本计划**不新建**条目、不做 `fixed`/`waive`,只在 `07-16-SUMMARY.md` 登记「清单行号 → 处置 → 提交」的映射。
|
||||
|
||||
<assumption_delta_decision>
|
||||
- **noun**: N/A(本计划不改变任何核心身份模型的名词/概念,不做单数→复数或近义迁移)
|
||||
- **decision**: `no-change`
|
||||
- **rationale**: 假设-增量检测器在本阶段唯一命中的信号是字面量 `fallback`,它来自包名 `packages-user/data-fallback`(legacy 兼容层,用户已删除);该命中是**包名字符串**造成的假阳性,不是身份模型名词的迁移。本计划只改 18 个测试文件/测试夹具,不新增、不重命名任何领域概念(`array`、`setFloor`、`useFaceHandler`、`map`、`floorId` 全部是 shipped 源码中**已存在**的名字,本计划只是让测试改用它);故裁决为 `no-change`,不需要任何名词对齐或词汇表更新。
|
||||
- **note**: 该检测器由 orchestrator 运行(不是用户运行),故本条为流程性决策记录,不改变任何用户裁决。
|
||||
</assumption_delta_decision>
|
||||
|
||||
执行序:`depends_on: []`(Wave 16)。四个家族的改动文件互不重叠(寻路 4 个 / 录像 4 个 / 楼层 4 个 / 行为 5 个 + 夹具 1 个),但**不并波执行**:本计划 `autonomous: false`,Task 0 是 D-09 的逐计划裁决关卡,且每个家族各自需要「聚焦命令 → 文件级门禁 → 原子提交」,与 07-13/07-14/07-15 同一节奏。
|
||||
|
||||
**隔离要求(执行前必读)**:用户正在本仓库并行工作,且工作树的改动集合在规划与执行之间会继续变化,故**不以任何固定清单为准**。**通用规则(唯一判据)**:凡是**不在**本计划声明路径内的改动,一律不触碰、不暂存、不提交(声明路径 = `files_modified` 十八条 + 本计划的 `07-16-SUMMARY.md`;`.planning/ROADMAP.md`/`.planning/STATE.md` 的跟踪同步属 orchestrator 的收尾步骤,不计入本计划的改动面);每次提交前后用 `git status --short` 核对暂存区只含本计划当次声明的文件;用户自己的改动(例如 `packages-user/data-*/**` 的生产源码、`packages/common/src/logger.json`、`.planning/milestone.lock` 等)无论内容如何都属用户所有,本计划只读不写,且**不得**以「工作树中某文件有改动」为由判定本计划越界——越界判定一律只看 `git diff "$BASE_SHA".."$HEAD_SHA" -- <该路径>` 是否为空。计划期(2026-09-17 规划会话)最后一次观察到的无关改动为:`.planning/phases/07-data-fixes/07-VERIFICATION.md`(记录本批清单的修改)、`packages-user/data-state/src/core.ts` 与 `packages/common/src/dirtyTracker.ts`(均为修改),以及未跟踪的 `.planning/phases/07-data-fixes/07-16-PLAN.md`(即本计划自身)与 `.planning/milestone.lock`——**仅作参考**,执行时以 `git status --short` 为准。特别提醒:`data-state/src/core.ts` 在规划期已有用户未提交改动,故凡本计划需要引用 `CoreState` 的成员面(行 12 的 `initMapState` 是否仍不存在)时,**必须重新读取执行当日的文件内容**,不得沿用本计划记录的行锚点作判据。
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/book/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||
@C:/Users/book/.config/opencode/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/REQUIREMENTS.md
|
||||
@.planning/phases/07-data-fixes/07-CONTEXT.md
|
||||
@.planning/phases/07-data-fixes/07-VERIFICATION.md
|
||||
@.planning/phases/07-data-fixes/07-PATTERNS.md
|
||||
@.planning/phases/07-data-fixes/07-15-PLAN.md
|
||||
@.planning/phases/07-data-fixes/07-15-SUMMARY.md
|
||||
@.planning/codebase/TESTING.md
|
||||
@.planning/codebase/CONVENTIONS.md
|
||||
@AGENTS.md
|
||||
@dev.md
|
||||
|
||||
**执行起点提示**:本计划**尚未裁决**(Task 0 的 `<record>` 为空)。执行者**必须**在 Task 0 停下,向用户逐条汇报 Q1..Q4 与三个记录项(R1 注释范围、R2 `setFloor(null)` 缺口、R3 命名预审),取得裁决后由计划修订把裁决写进 `<record>`,再重新执行 Task 1..5。**不得**按任何「建议默认」自动推进。
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="checkpoint:decision" gate="blocking-human">
|
||||
<name>Task 0: 对齐口径裁决关卡(D-09)—— 寻路家族的替代接口(删 useMapState + 显式注入方向处理器 / 测试内建转发适配层 / 其他)、录像家族的改名方向(测试侧 route→array + 索引 0 基 / 改回生产成员名 / 其他)、楼层与初始化器家族的对齐(按 shipped 形状对齐 + 不可表达分支的处置 + 行 12 重定向或退役 / 其他)、行为存疑行 13..17 的逐行裁定(形状对齐 / 生产回归另立计划 / 先调查再报)</name>
|
||||
<files>(只读汇报,不修改任何文件)</files>
|
||||
<read_first>
|
||||
- packages-user/data-system/src/path/types.ts(`:45-79` `IMapGraphBuilder`:现有成员为 `indexer` / `useMapLayer` / `useCostFunction` / `usePassPredicate` / `useFaceHandler` / `build`,**无 `useMapState`**;`:121-152` `IPathFinder` 同形;`:97-101` `PathCostFunction`)
|
||||
- packages-user/data-system/src/path/graph.ts(`:21-56` `MapGraphBuilder` 的私有槽位与 `useMapLayer` 内的 `indexer.setWidth(layer.width)`;`:69-88` `build` 的三处 173 早退;`:106-133` `face.mapMovement()` 的邻域展开与 `(0,0)` 跳过)
|
||||
- packages-user/data-system/src/path/finder.ts(`:102` 私有 `graph`、`:108-116` `useMapLayer` 的 `layer.state !== this.state` 告警 182、`:122-128` `usePassPredicate`/`useFaceHandler` 的转发、`:200-214` `find` 的 `build()` → `InvalidInput` 回退)
|
||||
- packages-user/data-system/src/path/system.ts(`:25-27` 构造器建 `new PathFinder(state)`;`:37-48` `getPath` 经 `finder.find`)
|
||||
- packages-user/data-common/src/common/faceManager.ts(`:19-63` `IFaceHandler<T>` 契约;`:147-180` `Dir8FaceHandler`;`:222-255` `Dir4FaceHandler`;`mapMovement()` 含 `Unknown`(`{x:0,y:0}`),故 Dir4 出 4 条边、Dir8 出 8 条边)
|
||||
- packages-user/data-system/src/path/graph.test.ts(`:32-60` `TestModules` 与 `beforeAll` 动态 import;`:240-282` `createFixture`(`:275-279` 的 `PathfindingGraphBuilder` / `useMapState(maps)` / `useMapLayer(layer)`);`:285-302` 无谓词用例;`:304-332` 四正交用例;`:334-351` 八方向用例(`:346`);`:390-400` 附近 `useMapLayer(null)` 的守卫用例)
|
||||
- packages-user/data-system/src/path/system.test.ts(`:300-342` `createSystem`(`:339` `useMapState`、`:340` `useMapLayer`);`:344-354` `injectPredicate`)
|
||||
- packages-user/data-system/src/path/performance.test.ts(`:355-374` 夹具(`:361` `useMapState`、`:362` `useMapLayer`、`:366-372` `buildGraph` 内联 builder 的 `useMapState`))
|
||||
- packages-user/data-state/test/replayPlayback.test.ts(`:85-117` `createSmallMapScene`(`:109` `useMapState`、`:110` `useMapLayer`、`:111-113` `new DefaultPassPredicateImpl(state.maps)`、`:124` `setFloor('F1')`);`:361-377` 楼层激活后绑定事件层的瞬移用例(`:369` 再次 `useMapLayer`))
|
||||
- packages-user/data-state/src/hero/predicate.ts(`:5-8` `DefaultPassPredicateImpl` 构造器收 `IStateSystem`;`:25-35` `canPass` 读 `this.state.faceManager` 与 `map.eventLayer`)
|
||||
- packages-user/data-state/src/core.ts(`:194-200` 顶层装配:`useMover` / `useMapLayer(null)` / `usePassPredicate(new DefaultPassPredicateImpl(this))`,**无 `useFaceHandler` 注入**,`:116-121` 的 dir4/dir8 只注册进 `faceManager`)
|
||||
- packages-user/data-common/src/replay/system.ts(`:30-43` 构造器与成员 `array`;`:63-68` `record` 的 `array.add` + `onRecordCommand(code, array.length - 1, params)`)
|
||||
- packages-user/data-base/src/hero/equipment.test.ts(`:63-100` `ReplayRouteStub` / `ReplaySystemStub` / `createReplaySystem`;`:149` 桩用途注释;`:377/:385/:403/:405/:410` 引用点)
|
||||
- packages-user/data-base/src/hero/saveLoad.test.ts(`:76-84` 桩的形状;`:275-298` 楼层存档往返用例(`:284`/`:289`);`:505-600` 跟随者与勇士楼层的多处 `setFloor`)
|
||||
- packages-user/data-base/src/hero/items.test.ts(`:55-70` 桩形状与注释)
|
||||
- packages-user/data-base/src/hero/mover.test.ts(`:45-63` 桩与 `createTestTile`;`:137-258` 六条 step/teleport 用例的既有断言——裁决时用于判断「是否只改桩即可」)
|
||||
- packages-user/data-base/src/hero/follower.test.ts(`:54-93` 桩与 `createController`;`:114-200` 跟随者用例群)
|
||||
- packages-user/data-common/src/replay/system.test.ts(`:72-95` `appends a replay step and dispatches the record hook` 的期望元组 `[[5, 1, [1, true]]]`;`:162-179` 既有 disable/revert 用例)
|
||||
- packages-user/data-base/src/hero/location.ts(`:21-24` 字段 `floorId` / `map`;`:49-53` `setFloor(map: IGameMap)`;`:65-79` `saveState`/`loadState`)
|
||||
- packages-user/data-base/src/hero/types.ts(`:283` `onSetFloor?(map: IGameMap | null)`;`:302-318` `IHeroLocation` 的 `floorId`/`map`/`setFloor`;`:842-846` `IHeroChangeFloorInfo.target`)
|
||||
- packages-user/data-base/src/hero/location.test.ts(`:42-62` 夹具;`:95-113` `updates the floor and notifies the onSetFloor hook`——含 `setFloor('F2')` 与 `setFloor(undefined)` 两半)
|
||||
- packages-user/data-base/src/hero/state.test.ts(`:139-175` `changeFloor` 用例(`:161` `target: 'F2'`、`:173` `floorId` 断言))
|
||||
- packages-user/data-base/src/hero/state.ts(`:109-125` `changeFloor` → `this.location.setFloor(info.target)`)
|
||||
- packages-user/data-state/test/saveablesRoundTrip.test.ts(`:120-140` 夹具(`:127` `setFloor('F1')`);`:160-170`(`:165` `setFloor('F9')`);`:225-240` 的 `floorId`/`isMapActive` 断言)
|
||||
- packages-user/data-base/src/map/mapState.ts(`:210-260` `fromRaw` 的图层装配:`createMap` / `setLayerAlias` / 事件层识别 / `setEventLayer`;`:249-252` `createMap`)与 `:744-802` 的 `IMapState` 成员(`createMap`/`getMap`/`fromRaw`/`getActiveMap`/`isMapActive`/`setMapActiveStatus`)
|
||||
- packages-user/data-state/src/coreEventLayer.test.ts(`:36-46` `CoreInitializer` 接口(`:44` `initMapState`);`:73-124` `createFakeMapState` / `createFloor`;`:126-159` 用例(`:137` 调用点))
|
||||
- packages-user/data-base/src/map/mapState.test.ts(`createMap` 注册与重复注册告警 121、`generates and reads content on a map created by createMap`——行 12 若退役,用于说明等价覆盖已存在)
|
||||
- packages-user/data-state/src/event/event.test.ts(`:54-92` `createFixture`(`:76` `setFloor('F1')`);`:172-200` `awaits hero sequence and forward-step movement`)
|
||||
- packages-user/data-state/test/fixtures/closed-loop.ts(`:47-61` `IClosedLoopFixture`(含成员 `route: IReplayArray`);`:91-146` 两张地图的 `fromRaw`;`:163-172` `setMapActiveStatus` 与 `setFloor('F1')`;`:233-246` `reset` 内的第二次 `setFloor('F1')`)
|
||||
- packages-user/data-state/test/dataClosure.test.ts(`:240-250` 与 `:252-281` 两条失败用例;`:271-275` 已用 `replay.array`)
|
||||
- packages-user/data-state/test/nodeTracer.test.ts(整文件:`getReplayed()` 与 `location.x` 断言)
|
||||
- packages-user/data-base/src/hero/mover.ts(`:190-225` 通行判定把 `map: this.tile.map` 交给谓词,以及 `ObjectMoveType.Teleport` 的 `replay.array.add(ReplayCode.Teleport, [step.x, step.y])` 写入点)
|
||||
- .planning/phases/07-data-fixes/07-VERIFICATION.md(`### Post-Refactor Test Breakage` 的 17 行表与「用户定性」段)
|
||||
- .planning/phases/07-data-fixes/07-CONTEXT.md(D-01..D-14)
|
||||
</read_first>
|
||||
<decision>
|
||||
本计划**尚未裁决**。需用户逐条拍定后写入 `<record>`:
|
||||
|
||||
(Q1,行 1..4 寻路家族)测试如何对齐已落地的寻路接口:`useMapState` 已无任何替代成员,宽度改由 `useMapLayer(layer)` 内的 `indexer.setWidth(layer.width)` 提供;`useDirGroup(<number>)` 已被 `useFaceHandler(<IFaceHandler<number>>)` 取代;builder 的 face 默认值由旧 `InternalDirectionGroup.Dir4` 变为 `null`(缺失即 173 早退)。
|
||||
|
||||
(Q2,行 5..8 录像家族)测试侧把录像桩成员与钩子索引对齐到 shipped 名字与语义(`array`、`length - 1`),还是改回生产成员名。
|
||||
|
||||
(Q3,行 9..11 楼层/位置形状 + 行 12 初始化器)测试如何对齐 `setFloor(IGameMap)` 形状;`location.test.ts` 中「`setFloor(undefined)` 清空楼层」这一在 shipped 实现下不可表达的分支如何处置;`CoreState.initMapState` 已被整体删除后的行 12 是重定向到 `MapState.fromRaw` 还是退役该文件。
|
||||
|
||||
(Q4,行 13..17 行为存疑)逐行裁定:`mover.test.ts`(6 条 30s 悬挂)、`follower.test.ts`(1 条 30s 悬挂)、`event.test.ts`(`expected +0 to be 1`)、`dataClosure.test.ts`(2 条 `expected false to be true`)、`nodeTracer.test.ts`(`expected +0 to be 1`)各自是「预期行为下的形状对齐」还是「生产回归」。
|
||||
</decision>
|
||||
<context>
|
||||
**本计划尚未裁决。下述口径中凡改变「测试断言表达什么」者,其后果必须在汇报中讲清;任一分支被裁定后,Task 1..4 一律按 `<record>` 执行。**
|
||||
|
||||
**Q1 的事实基础(已读源码,非推测)**:`4aaea68`(`refactor: 寻路系统改造`)对 `path/{types,graph,finder,system}.ts` 做了一次整体改选:
|
||||
- `useMapState(maps: IMapState | null)` **被删除**(builder 与 finder 两处),**没有同名或近义替代品**——它原本承担的两件事被拆开:(a) 图宽度 → 改由 `MapGraphBuilder.useMapLayer` 在 `layer` 非空时执行 `this.indexer.setWidth(layer.width)`(`graph.ts:38-43`);(b) 索引器本身 → 变成 `IMapGraphBuilder.indexer: ILocationHelper` 的只读成员(`types.ts:47`),由构造器 `new MapLocIndexer()` 建立(`graph.ts:31-36`)。因此测试侧的 `useMapState(maps)` 只能**删除**——测试夹具里那个 `maps` 变量在删除后若不再被使用,其构造与局部变量也要一并按 lint 结果处理(不得留下未使用变量)。
|
||||
- `useDirGroup(group: number)` **被替换**为 `useFaceHandler(face: IFaceHandler<number> | null)`(builder)与 `useFaceHandler(face: IFaceHandler<number>)`(finder)。`4aaea68` 只对 `graph.test.ts:346` 做了一次机械改名(`useDirGroup(InternalDirectionGroup.Dir8)` → `useFaceHandler(InternalDirectionGroup.Dir8)`),**实参仍是数字**,与 `IFaceHandler<number>` 不符。可选处理器为 `data-common/src/common/faceManager.ts` 的 `Dir4FaceHandler`(`:222-255`,`mapMovement()` 含 `Unknown` 共 5 项,`graph.ts:108` 跳过 `(0,0)` 后得 4 条边)与 `Dir8FaceHandler`(`:147-180`,得 8 条边);`core.ts:116-121` 把它们注册进 `faceManager` 的 `FaceGroup.Dir4`/`FaceGroup.Dir8`。旧默认值是 `InternalDirectionGroup.Dir4`(`4aaea68^` 的 `finder.ts` 私有字段),故「保持原意图」的注入对象是 `Dir4FaceHandler`;显式要八方向的用例注入 `Dir8FaceHandler`。
|
||||
- builder 的 `face` 默认值由 `InternalDirectionGroup.Dir4` 变为 `null`:**凡调用 `build()` 的测试路径都必须显式注入**方向处理器,否则 `graph.ts:81-84` 告警 173 返回 `null`,断言会以「读 null 的属性」而不是「图内容不符」失败。`system.test.ts` 与 `performance.test.ts` 的 finder 路径同理(finder 的 `find` → `graph.build`)。
|
||||
- 附带:`data-common/src/types.ts` 在 `4aaea68` 中删除了 `IDataCommon.directionMapper`;多处夹具仍写 `directionMapper: new modules.DirectionMapper()`,但均以 `as never` 收口,**只要焦点命令不因它而红就不要动它**。
|
||||
|
||||
**Q2 的事实基础**:生产类的成员名是 `array`(`replay/system.ts:34`,由构造器 `new ReplayArray({...})` 赋值),写入口径见 `equipment.ts:195/212`、`items.ts:130`、`mover.ts:180-189/222`;`record()` 先 `this.array.add(code, params)` 再 `hook.onRecordCommand?.(code, this.array.length - 1, params)`(`:63-68`)。用户的 `34ba9e8` 已把钩子的第二实参从「1 基」改成 `length - 1`,`a2a8e6e` 把 `ReplaySystem.route` 改名为 `ReplaySystem.array` 并同步了 19 个文件,**漏掉了 5 个测试文件**(`equipment.test.ts`、`saveLoad.test.ts`、`items.test.ts`、`mover.test.ts`、`follower.test.ts`)。注意 `equipment.test.ts` 的桩还带一个自建接口名 `ReplayRouteStub`(`:64-73`)与一段用途注释(`:149`)——改名口径必须一并讲清(是否连接口名与注释一起改,见记录项 R1)。
|
||||
- A 分支的对外可观测后果:桩被改名为 `array` 后,`equipment.ts:195/212`、`items.ts:130`、`mover.ts:222` 的写入落到桩的数组上,断言可继续读同一数组(`equipment.test.ts:385/405/410`);`replay/system.test.ts:94` 的期望元组由 `[[5, 1, [1, true]]]` 变为 `[[5, 0, [1, true]]]`。附带效果:07-15 的 CR-01 见证(`diffs the final attributes when one compared item is currently equipped`)**其红只因本行**(`07-VERIFICATION.md:393` 已记录),改名后应即转绿——这是本计划的可观测收益之一。
|
||||
- B 分支(改回生产成员名 `route`)需要用户重开 `a2a8e6e` 的改名并同步 19 个生产文件,**超出本计划 test-only 范围**,选它等于本计划作废重规划。
|
||||
|
||||
**Q3 的事实基础**:
|
||||
- `HeroLocation.setFloor(map: IGameMap)`(`location.ts:49-53`)直接取 `map.floorId` 并把 `map` 存进新字段 `this.map`(`:24`),钩子回调改为收 `map`;接口声明 `setFloor(map: IGameMap | null)`(`hero/types.ts:316`)与 `onSetFloor?(map: IGameMap | null)`(`:283`)都允许 `null`,**但实现签名不允许且无空值分支**——即「把楼层清空为无」这一语义在当前实现下没有可运行路径;`loadState` 是唯一能把 `floorId` 置回 `undefined` 的入口(`location.ts:74-79`)。
|
||||
- `IHeroChangeFloorInfo.target` 由 `string` 变为 `IGameMap`(`hero/types.ts:844`),`HeroState.changeFloor` 直接 `this.location.setFloor(info.target)`(`state.ts:117`)。
|
||||
- 测试侧待对齐的 `setFloor(<字符串>)` 调用点共 16 处:`location.test.ts:107` 与 `:110`、`saveLoad.test.ts:284/289/511/518/543/593/599`、`event.test.ts:76`、`closed-loop.ts:166` 与 `:234`、`replayPlayback.test.ts:124`、`saveablesRoundTrip.test.ts:127` 与 `:165`;另有 `state.test.ts:161` 的 `target: 'F2'`。
|
||||
- 影响半径(决定「用真实 `IGameMap` 还是手搓形状」):`location.map` 会被移动路径读取——`mover.ts:203` 把 `map: this.tile.map` 交给谓词,`predicate.ts:33-35` 对 `!map` 直接拒绝并要求 `map.eventLayer`。因此在会移动的用例(`event.test.ts`、`closed-loop.ts`、`replayPlayback.test.ts`、`saveablesRoundTrip.test.ts`)里,**必须**是真实的 `IGameMap`;只在纯 `floorId` 读写的用例(`location.test.ts`、`saveLoad.test.ts:275-298`)里才可能用最小形状,而该选择也必须写进汇报。
|
||||
- 行 12 的处置不是改名:`CoreState.initMapState` **已从全仓删除**(`core.ts` 现有成员仅 `registerReplayCommands`/`addSaveableContent`/`getSaveableContent`/`bindSaveableExecuter`/`saveState`/`loadState`)。同名职责现落在 `MapState.fromRaw`(`mapState.ts:210-260`:`createMap` → 逐层 `setLayerAlias` → 识别并 `setEventLayer` → `setActiveStatus`),且 `mapState.test.ts` 已有同义覆盖。故行 12 只有两条路:(A1) 把该用例重定向到 `MapState.fromRaw`,保留「event alias 被选为事件层、层序与 zIndex 不变、矩阵内容逐字节一致」的原判据;(A2) 经用户批准退役该文件,并在 SUMMARY 明写「其被测 API 已被删除、等价覆盖已在 `mapState.test.ts`」。**不得**保留任何对已删除成员的调用,也不得用 `as never`/`Object.create` 之类手法把已删除的方法「假装」出来。
|
||||
|
||||
**Q4 的事实基础(规划期实测 + 代码链,逐行给出,供用户裁定)**:
|
||||
| 行 | 文件 | 规划期实测的首个可观测信号 | 代码链结论 |
|
||||
|----|------|------------------------------|-------------|
|
||||
| 13 | `mover.test.ts` | 6 条用例 `Error: Test timed out in 30000ms.`,同时捕获到 6 次 `TypeError: Cannot read properties of undefined (reading 'add')` | 桩在 `:48` 只有 `route`,而 `mover.ts:222` 在 Teleport 分支写 `replay.array.add(...)`;异常在移动 Promise 链内抛出 → `onEnd` 不结算 → 30s 悬挂 |
|
||||
| 14 | `follower.test.ts` | 1 条用例 30s 悬挂 + 1 次同一个 `TypeError` | 桩在 `:67` 同为 `route` 形状;跟随者移动器走同一条 `mover.ts` 记录路径 |
|
||||
| 15 | `event.test.ts` | `expected +0 to be 1`(`awaits hero sequence and forward-step movement`) | 夹具 `:76` 传字符串 → `location.map` 是字符串 → `mover.ts:203` 把它交给谓词 → `predicate.ts:33-35` 在 `map.eventLayer` 上取到 `undefined` → 拒绝移动 → 勇士兵 x 停在 0 |
|
||||
| 16 | `dataClosure.test.ts` | 2 条用例 `expected false to be true`(`fixture.eventCompleted()`) | 夹具 `closed-loop.ts:166/234` 同上;事件挂在 (1,0) 的 OnEnter 上,勇士进不去 → `getBlock(1,0)` 恒为 1 |
|
||||
| 17 | `nodeTracer.test.ts` | `expected +0 to be 1`(`sandbox.getReplayed()`) | 同一夹具同上;录像步 `Right` 被拒 → 回放计数为 0 |
|
||||
|
||||
三条分支的后果:(A) 认为这些用例原本断言的行为(勇士实际移动、事件按 OnEnter 触发并改写事件层、录像按序回放并结束)**仍是预期行为**,失效只是形状漂移的产物——那么 13/14 只需改桩成员名,15/16/17 只需改夹具/夹具调用的 `setFloor` 形态,且 `dataClosure.test.ts` 与 `nodeTracer.test.ts` 保持零改动(A7);(B) 认为其中某几行的行为已被有意改变(例如勇士移动的通行判定语义、事件触发时机)——那属于**生产回归**,超出本计划 test-only 范围,必须拆出另立计划并先修生产,**本计划在该行上停止**;(C) 先只做调查、把每行的最小化证据(去掉形状漂移后是否仍失败)提交给用户,再决定 A 或 B——默认口径,因为规划期的代码链结论虽强但**未经过「改一处、跑一次」的实测反证**。
|
||||
|
||||
观察(未确认、不登记,仅请用户在 Q3 裁决时知悉):`IHeroLocation.setFloor` 的接口允许 `null` 而实现不允许且无空值分支(`hero/types.ts:316` 对 `location.ts:49`),这是接口与实现的签名不一致。它**未被** `07-VERIFICATION.md` 登记,按 AGENTS.md「发现问题必须经用户确认后才可记录」与 D-09,本计划**只作为观察提出、不登记为缺陷、不修**。若用户希望一并处置,请在 Q3 中明确——但生产代码改动会超出本计划范围,届时应拆出另立计划。
|
||||
</context>
|
||||
<options>
|
||||
<option id="q1-delete-and-inject">
|
||||
<name>Q1 选项 A:【建议默认】删除全部 `useMapState(...)` 调用(无替代成员),把 `useDirGroup(<number>)` 改为 `useFaceHandler(<IFaceHandler<number>>)`,并在每条会走到 `build()`/`find()` 的测试路径上显式注入方向处理器(默认四正交用 `Dir4FaceHandler`,显式要八方向用 `Dir8FaceHandler`)</name>
|
||||
<pros>与 shipped 源码逐字一致(`path/types.ts:45-79` / `:121-152`、`graph.ts:38-43/69-88`);不新增任何生产符号;三层失败(缺成员、缺处理器、实参类型不符)一次对齐;`createFixture` 的既有断言(节点数、边数、方向集合、143/146 系列守卫)全部保持原意;`graph.ts:108` 跳过 `(0,0)` 的既有设计正好让 Dir4/Dir8 分别得到 4/8 条边,与既有用例期望一致</pros>
|
||||
<cons>测试夹具的注入面变多一行(每个会 build 的夹具补一个处理器构造);`performance.test.ts` 的 `buildGraph` 与 finder 两条路径都要补;删除 `useMapState(maps)` 后夹具里的 `maps` 变量可能变成未使用变量,需要按 lint 一并处置(保留在返回值里供断言使用,或删除其构造)</cons>
|
||||
</option>
|
||||
<option id="q1-adapter">
|
||||
<name>Q1 选项 B:在测试内建一个适配层(在桩对象上补一个把 `useMapState` 转发到 `useMapLayer` 的方法)以保留现有调用形状</name>
|
||||
<pros>改动面集中在夹具,调用点不动</pros>
|
||||
<cons>测试从此断言的是一个**不存在的接口**——生产类没有 `useMapState`,适配层只是在测「适配层自己」,会把真实接口变更掩盖成绿;与本轮「让测试重新匹配 shipped 接口」的目标相反;且需要新增测试内方法(需 AGENTS.md 命名预审),并可能与 lint 的未使用参数规则冲突</cons>
|
||||
</option>
|
||||
<option id="q2-rename">
|
||||
<name>Q2 选项 A:【建议默认】测试侧整体改名:5 个文件的录像桩成员 `route` → `array`(含引用点与桩的构造返回对象),`replay/system.test.ts` 的 `onRecordCommand` 首步索引期望 `1` → `0`</name>
|
||||
<pros>与 shipped 名字(`system.ts:34`)与 shipped 语义(`:66` 的 `array.length - 1`)逐字一致;一处改动同时解除 07-15 CR-01 见证的阻塞(`07-VERIFICATION.md:393`);`mover.test.ts`/`follower.test.ts` 的 7 条 30s 悬挂按实测根因随之一并解除;不新增生产符号、不改生产语义</pros>
|
||||
<cons>期望值确实被改写了两处(首步索引 1 → 0、桩成员名),因此必须由本裁决逐条授权并登记在 SUMMARY 的改写台账;`equipment.test.ts` 还带一个自建接口名与一段用途注释,其改名范围需在记录项 R1 中一并拍定</cons>
|
||||
</option>
|
||||
<option id="q2-revert">
|
||||
<name>Q2 选项 B:改回生产成员名 `route`(需要用户重开 `a2a8e6e` 的改名并同步 19 个生产文件)</name>
|
||||
<pros>测试文件完全不动</pros>
|
||||
<cons>**超出本计划 test-only 范围**,等于把已落地的生产改名回退;会让 `mover.ts`/`equipment.ts`/`items.ts` 与已同步的 19 个文件重新分叉;选它即本计划作废重规划</cons>
|
||||
</option>
|
||||
<option id="q3-align">
|
||||
<name>Q3 选项 A:【建议默认】按 shipped 形状对齐断言:所有 `setFloor(<字符串>)` 与 `IHeroChangeFloorInfo.target: 'F2'` 改为传真实 `IGameMap`(`MapState.createMap`/`fromRaw`/`getMap` 取得),`floorId` 与钩子顺序断言保持原期望值;`location.test.ts` 的 `setFloor(undefined)` 分支(不可表达)重塑为 map 形态并把该分支的消失登记在案;行 12 选 A1——把 `coreEventLayer.test.ts` 重定向到 shipped 拥有者 `MapState.fromRaw`</name>
|
||||
<pros>三类失败(`expected undefined to be 'F2'`/`'F1'`、事件不触发、图内容不符)一次对齐;会移动的用例使用真实地图,顺带修好「通行判定读到假 map」这条隐含依赖;`floorId` 与层序/zIndex 的原断言意图逐字保留;不新增生产符号、不新增用例</pros>
|
||||
<cons>夹具需要为不存在的楼层先建档(`createMap`)或改用现有档;`location.test.ts` 会**失去**「把楼层清空为无」这一半覆盖,且按 A6 该语义在当前实现下没有可运行路径——这一覆盖缺口必须由用户认领(记录项 R2);行 12 的重定向会把一个原本测 `CoreState` 私有装配的用例改测 `MapState.fromRaw`,其 `vi.mock('./enemy')`/`vi.mock('./legacy')` 与 `Object.create(CoreState.prototype)` 的骨架会变得无意义,需按 lint 与可读性一并清理(清理范围须在汇报中讲清)</cons>
|
||||
</option>
|
||||
<option id="q3-retire">
|
||||
<name>Q3 选项 B:行 9..11 按 A 对齐,但行 12 退役 `coreEventLayer.test.ts`(删除该文件),理由是其被测 API 已被删除且等价覆盖已在 `mapState.test.ts`</name>
|
||||
<pros>不制造「为已删除 API 保留的测试骨架」;覆盖并非净损失(`mapState.test.ts` 已覆盖 `createMap`/图层别名/事件层选择,`mapLifecycle.test.ts` 覆盖生命周期)</pros>
|
||||
<cons>删除测试文件属于减少覆盖,必须由用户显式批准;`files_modified` 需相应改为「该文件被删除」并在提交区间核对 `--diff-filter=D`;与 D-10「不轻易删减测试面」的精神接近边界,因此**不能**由执行者自行选择</cons>
|
||||
</option>
|
||||
<option id="q4-align">
|
||||
<name>Q4 选项 A:逐行认领为「预期行为下的形状对齐」——13/14 改桩成员名(`route` → `array`,不改任何断言),15 改 `event.test.ts` 夹具的 `setFloor` 形态(不改断言),16/17 只改共享夹具 `closed-loop.ts`(`dataClosure.test.ts`/`nodeTracer.test.ts` 保持零改动)</name>
|
||||
<pros>规划期实测与代码链都指向形状漂移:13/14 有 7 次 `TypeError: reading 'add'` 的实测证据,15/16/17 有「字符串 map 使通行判定恒假」的完整调用链;两种修法都不触碰断言,因而「无断言弱化」的门禁自然成立;`dataClosure.test.ts`/`nodeTracer.test.ts` 零改动也避免了「为了让文件看起来参与修复而改断言」的风险</pros>
|
||||
<cons>成立的前提是「这些用例原本断言的行为仍是预期行为」——这一点只能由用户认领;若其中某行其实是行为回归,按 A 落笔会把回归写进回归网(即把 bug 固化为期望)</cons>
|
||||
</option>
|
||||
<option id="q4-production">
|
||||
<name>Q4 选项 B:判定某几行为生产回归——该行**不在本计划落笔**,必须拆出另立计划先修生产代码,本计划在该行上停止并把处置写成「待另立计划」</name>
|
||||
<pros>不把回归固化进测试;责任面清晰(生产修复与 test-only 对齐分属两个计划)</pros>
|
||||
<cons>本计划将无法收口「`pnpm test:ci` 0 失败」(Task 5 的门禁会在这些行上持续红),必须明确写出停在哪一步、以及剩余红行由哪份新计划承接;用户需要额外一轮规划</cons>
|
||||
</option>
|
||||
<option id="q4-investigate">
|
||||
<name>Q4 选项 C:【建议默认】先调查再报:Task 4 第一步重跑聚焦命令采集每行的首个可观测信号,再按「只做形状对齐后该行是否转绿」逐行给出最小化证据,**在用户对每一行给出 A/B 之前不修改这 5 个文件与夹具**</name>
|
||||
<pros>与本阶段 D-09「方案失败即退出并修订计划」的节奏一致;规划期的代码链结论虽强,但「改一处、跑一次」的反证尚未做过,C 用最小代价把证据坐实;避免在用户尚未认领时就把期望固化</pros>
|
||||
<cons>本计划会停在 Task 4 等第二次裁决,`pnpm test:ci` 在裁决前保持红;需要用户在收到调查报告后再做一轮裁定</cons>
|
||||
</option>
|
||||
<option id="revise">
|
||||
<name>【兜底】修订方案后再执行</name>
|
||||
<pros>避免带错误契约落地</pros>
|
||||
<cons>按 D-09 退出本次修改、修订本 PLAN.md 后重新执行,不得自行另辟他法(例如自行改用适配层、自行删除测试文件、自行改生产成员名,或自行把某行判为生产回归)</cons>
|
||||
</option>
|
||||
</options>
|
||||
<record></record>
|
||||
<resume-signal>尚未裁决——执行者必须停在 Task 0,向用户逐条汇报 Q1..Q4 与 R1..R3,取得明确裁决后由计划修订把裁决(含每条的可观测后果)写入本 `<record>`,再重新执行 Task 1..5。不得按任何「建议默认」自动推进</resume-signal>
|
||||
<action>向用户汇报四组口径与三个记录项,逐条讲清取舍与**对外可观测后果**,并如实转述规划期证据:
|
||||
(1) **Q1(行 1..4 寻路家族)**:A 分支=删除 `useMapState`(无替代,宽度改由 `useMapLayer` 承担,`graph.ts:38-43`)+把 `useDirGroup(<number>)` 改为注入 `IFaceHandler<number>`(`Dir4FaceHandler` 对应旧默认四正交、`Dir8FaceHandler` 对应显式八方向,`faceManager.ts:147/222`)+在每条 build/find 路径补齐注入;B 分支=在测试内补一个转发适配层保留旧调用形状(等于测一个不存在的接口)。请说明:A 分支之后 `graph.test.ts` 的「节点数 1/边数 4/边数 8」三类既有期望都保持不变,而 B 分支会把已被删除的成员固化成「测试内的合同」。
|
||||
(2) **Q2(行 5..8 录像家族)**:A 分支=5 个测试文件的录像桩成员整体改名 `route` → `array`(含引用点)、`replay/system.test.ts` 的首步索引期望 `1` → `0`(`system.ts:66` 传 `array.length - 1`);可观测后果是「两处期望值被改写」,必须逐条授权并登记;附带收益是 07-15 的 CR-01 见证随之转绿(`07-VERIFICATION.md:393`)。B 分支=改回生产成员名,需重开 `a2a8e6e` 并同步 19 个生产文件,超出本计划范围。请同时给出 R1 的裁决:`equipment.test.ts` 的自建接口名 `ReplayRouteStub`(`:64-73`)与该桩的用途注释(`:149`)是「一并改名为 `array` 口径」还是「逐字保留」。
|
||||
(3) **Q3(行 9..11 + 行 12)**:A 分支=全部 `setFloor(<字符串>)` 与 `target: 'F2'` 改为传真实 `IGameMap`,`floorId`/钩子顺序/层序断言保持不变;其中会移动的用例必须用真实地图(`mover.ts:203` → `predicate.ts:33-35` 会读 `map.eventLayer`)。请明确讲清两点代价:其一,`location.test.ts` 的 `setFloor(undefined)` 分支在 shipped 实现下不可表达(实现签名非空,`location.ts:49`),A 分支会**失去**这一半覆盖,需 R2 认领;其二,行 12 的 `CoreState.initMapState` 已被整体删除,A1=把用例重定向到 `MapState.fromRaw`(`mapState.ts:210-260`)并保留层序/zIndex/矩阵断言,A2=退役该文件(等价覆盖已在 `mapState.test.ts`),请用户在 A1 与 A2 之间拍定,并知悉 A1 会清理该文件里已无意义的 `vi.mock('./enemy')`/`vi.mock('./legacy')` 与 `Object.create(CoreState.prototype)` 骨架(清理范围需在汇报中讲清)。
|
||||
(4) **Q4(行 13..17)**:逐行给出规划期实测信号与代码链结论(见 `<context>` 的 Q4 表),并请用户按「A 形状对齐 / B 生产回归另立计划 / C 先调查再报」逐行认领。特别说明 A 分支的落笔面:13/14 只改录像桩成员名、**不改任何断言**;15 只改夹具的 `setFloor` 形态;16/17 只改共享夹具 `closed-loop.ts`,两个测试文件本身**零改动**。
|
||||
(5) **记录项 R1(注释范围)**:AGENTS.md 规定测试文件由 AI 维护、只需保证每个 `it` 前有一行说明覆盖内容的注释,dev.md:86 要求测试范围变化时同步更新该注释;而 D-11 禁止擅自改动既有注释。请裁定本计划的注释落笔边界,建议为三类:(a) 测试文件 `:1` 文件头覆盖说明的**追加**(既有文字一字不改);(b) 覆盖描述发生变化的 `it` 前单行中文注释的同步;(c) **点名已删除接口**的既有注释(如桩的「route.add 记录」用途注释)改为点名 shipped 接口。三类之外的既有注释与任何 jsDoc 一字不改。
|
||||
(6) **记录项 R2(`setFloor(null)` 缺口)**:接口允许 `null`(`hero/types.ts:316`)而实现不允许且无空值分支(`location.ts:49`),且 `setFloor(undefined)` 的测试分支因此在 shipped 实现下不可表达。请裁定:仅作为「覆盖缺口」在 SUMMARY 登记(不改生产),还是另立计划处置生产签名不一致——后者超出本计划范围。
|
||||
(7) **记录项 R3(命名预审,AGENTS.md 要求)**:本计划**不新增任何公共/受保护/私有方法或字段**(生产代码零改动、测试面无新符号);测试内若确需新的局部常量名,必须先提请批准。请确认是否接受「只使用既有名字(`array`、`useFaceHandler`、`useMapLayer`、`setFloor`、`map`、`floorId`、`DefaultPassPredicateImpl`、`Dir4FaceHandler`、`Dir8FaceHandler`)与既有夹具变量名」这一约束,或另行指定命名。
|
||||
**不得在裁决前修改任何文件**;不得自行采纳任何建议默认值;不得把规划期「未确认、不登记」的观察写成缺陷。</action>
|
||||
<verify>
|
||||
<human-check>`<record>` 中 Q1..Q4 均有用户明确裁决(含 Q2 的两处期望值改写、Q3 的 `setFloor(undefined)` 覆盖缺口与行 12 的 A1/A2 选择、Q4 的逐行 A/B/C 认领均已被告知并确认),R1/R2/R3 三条记录项均已拍定,且与用户实际意图一致</human-check>
|
||||
<fails_when>`<record>` 仍为空(未裁决)—— 必须停在 Task 0、不得修改任何文件;或 Q4 的 5 行中有任一行未被逐行认领 —— 不得对该行落笔;或记录与用户实际意图不符 —— 必须暂停并请用户澄清,不得按「建议默认」或任何【未选】分支自行推进</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- Q1(寻路家族的替代接口口径)、Q2(录像家族的改名方向与索引语义)、Q3(楼层形状对齐口径 + `setFloor(undefined)` 分支撑处置 + 行 12 的 A1/A2 选择)、Q4(行 13..17 的逐行 A/B/C 认领)均已由用户给出并写入 `<record>`
|
||||
- 每条裁决都写明其对外可观测后果:Q2 的两处期望值改写(桩成员名、首步索引 1 → 0)、Q3 的覆盖缺口与行 12 的骨架清理范围、Q4 的落笔面(哪些文件改、哪些文件零改动)
|
||||
- R1(注释三类落笔边界)、R2(`setFloor(null)` 覆盖缺口的处置)、R3(命名约束)均已拍定
|
||||
- `<record>` 非空之前,工作树中本计划的 18 个声明路径**零改动**(可用 `git status --short` 核对这 18 个路径无新增改动)
|
||||
- 规划期「未确认、不登记」的观察(`setFloor` 接口签名与实现不一致)已如实转述给用户,且未在计划任何位置登记为新缺陷
|
||||
</acceptance_criteria>
|
||||
<done>Q1..Q4 的用户裁决(含每条的可观测后果)与 R1..R3 已完整写入 `<record>`;执行者据记录进入 Task 1,不再重复询问</done>
|
||||
</task>
|
||||
|
||||
<task type="tracer">
|
||||
<name>Task 1: 【tracer】寻路家族(行 1..4)—— 先在 `graph.test.ts` 打通「真实 MapState/GameMap → shipped MapGraphBuilder → build() 出图」这一条最薄端到端路径并验证转绿,再把同一替代口径铺到 `system.test.ts` / `performance.test.ts` / `replayPlayback.test.ts`</name>
|
||||
<files>packages-user/data-system/src/path/graph.test.ts, packages-user/data-system/src/path/system.test.ts, packages-user/data-system/src/path/performance.test.ts, packages-user/data-state/test/replayPlayback.test.ts</files>
|
||||
<read_first>
|
||||
- packages-user/data-system/src/path/types.ts(`:45-79` `IMapGraphBuilder` 的全部成员,确认 `useMapState` 不在其中;`:65` `useFaceHandler(face: IFaceHandler<number> | null)`;`:47` `readonly indexer: ILocationHelper`;`:121-152` `IPathFinder` 的同形成员与 `:144` `useFaceHandler(face: IFaceHandler<number>)`)
|
||||
- packages-user/data-system/src/path/graph.ts(`:21-56` 私有槽位与 `useMapLayer` 的 `indexer.setWidth(layer.width)`;`:69-88` `build` 的三处 173 早退与 `:85-88` 的 183 越界早退;`:99-135` BFS 与 `face.mapMovement()` 的邻接展开;`:106-108` 跳过 `(0,0)`)
|
||||
- packages-user/data-system/src/path/finder.ts(`:102` 私有 `graph`;`:108-116` `useMapLayer` 的 182 告警;`:122-128` 转发;`:200-214` `find`)
|
||||
- packages-user/data-common/src/common/faceManager.ts(`:19-64` `IFaceHandler<T>`;`:147-180` `Dir8FaceHandler`;`:222-255` `Dir4FaceHandler`)
|
||||
- packages-user/data-system/src/path/graph.test.ts(`:1` 文件头覆盖说明;`:32-60` `TestModules` 与 `beforeAll`;`:240-282` `createFixture`(`:266` `new modules.MapState(tileStore, commonState)`、`:275-279` 三行注入、`:281` 返回值);`:284-302` `includes only the start node when no predicate is injected`;`:304-332` 四正交;`:334-351` 八方向(`:346`);`:353-526` 其余用例(单向门、终端节点、损失、守卫),尤其 `:390-400` 附近的 `useMapLayer(null)` 用例)
|
||||
- packages-user/data-system/src/path/system.test.ts(`:1` 文件头覆盖说明;`:300-342` `createSystem`(`:327` `new modules.MapState(tileStore, commonState)`、`:336-340` 的 `useMover`/`useMapState`/`useMapLayer`);`:344-354` `injectPredicate`;`:356-599` 用例群)
|
||||
- packages-user/data-system/src/path/performance.test.ts(`:1` 文件头覆盖说明;`:14` 的 `MapGraphBuilder` 具名引入;`:355-374` 夹具(`:361` `useMapState`、`:362` `useMapLayer`、`:366-372` `buildGraph`);其余用例只读不改)
|
||||
- packages-user/data-state/test/replayPlayback.test.ts(`:1` 文件头覆盖说明;`:15` `DefaultPassPredicateImpl` 引入;`:85-117` `createSmallMapScene`(`:109-113` 三行注入、`:124` `setFloor('F1')`、`:127` `setFaceDir`);`:361-377` 楼层激活后绑定事件层的瞬移用例(`:369` 第二次 `useMapLayer`);`:144-153` `waitForEnded`)
|
||||
- packages-user/data-state/src/hero/predicate.ts(`:5-8` 构造器收 `IStateSystem`;`:25-35` `canPass` 读 `this.state.faceManager` 与 `map.eventLayer`)
|
||||
- packages-user/data-state/src/core.ts(`:110-121` dir4/dir8 的构造与 `faceManager.register`;`:194-200` 顶层装配的三行,作为「生产侧怎么注入」的对照——本任务**不修改**该文件)
|
||||
- packages-user/data-base/src/map/mapState.ts(`:249-252` `createMap`;`:210-260` `fromRaw`——用于确定夹具取 `IGameMap` 的既有写法)与 `packages-user/data-base/src/hero/location.ts`(`:49-53` `setFloor(map: IGameMap)`,供 `replayPlayback.test.ts:124` 对齐时对照)
|
||||
</read_first>
|
||||
<action>
|
||||
按 Task 0 `<record>` 的 Q1 裁决落地(**无分支、无「建议默认」**)。**先 tracer 后铺开**:第一步只改 `graph.test.ts` 并跑绿它,证明「shipped `MapGraphBuilder` + 真实 `MapState`/`GameMap` + 显式方向处理器」这条最薄链路端到端成立;第二步才把同一替代口径应用到其余三个文件。任一文件的既有断言都不得改写(Q1 只改注入面)。
|
||||
|
||||
(1) **`graph.test.ts`(tracer 切片)**:
|
||||
(a) `createFixture`(`:240-282`)删除 `builder.useMapState(maps)` 一行;保留 `builder.useMapLayer(layer)`(它现在同时承担旧 `useMapState` 的「提供图宽度」职责,`graph.ts:38-43`)。
|
||||
(b) 给 `createFixture` 补注入方向处理器,使同函数的两个既有分支都不再走 `build()` 的 173 早退(`graph.ts:81-84`):默认(`predicate === null` 与「注入谓词但未指名方向组」两类调用)注入四正交处理器 `new modules.Dir4FaceHandler()`——它与被删除前的默认值 `InternalDirectionGroup.Dir4` 语义一致(`Dir4FaceHandler.mapMovement()` 含 `Unknown`,被 `graph.ts:108` 跳过,净得 4 条边);若某条用例显式要八方向,则在该用例体内覆盖注入 `new modules.Dir8FaceHandler()`。实现方式二选一并在 SUMMARY 记录:给 `createFixture` 增一个可选参数(沿用既有参数风格),或在其返回值里暴露 builder 与处理器构造入口由用例自行注入;**不得**新增用例、不得新增文件。
|
||||
(c) `:346` 的 `builder.useFaceHandler(InternalDirectionGroup.Dir8)`(`4aaea68` 的机械改名残留,实参是数字)改为注入八方向处理器对象,使该用例的 `expect(center.edges).toHaveLength(8)` 在原期望值下成立;该用例前的中文单行注释按其覆盖描述同步(见 (5))。
|
||||
(d) `:290-302` 的「无谓词」用例与 `:390-400` 附近的 `useMapLayer(null)` 用例:前者依赖「未注入谓词时无可通行边」,仍按函数内谓词注入方式表达;后者断言的是 `build` 的越界/未绑定守卫(173/183),**断言逐字保留**——若其转绿需要注入处理器,按 (b) 的口径补齐注入而不是放宽断言。
|
||||
(e) 夹具返回值里的 `maps`(`:281`)若在删除 `useMapState` 后不再被任何用例使用,按 eslint 结果处置(从返回值中移除或保留给仍在使用它的用例);**不得**留下未使用变量。
|
||||
(f) 跑 `graph.test.ts` 的聚焦命令直到 0 失败;记录输出中的文件与用例计数(追加到 SUMMARY 用,不写进本计划的门禁数字)。
|
||||
(2) **`system.test.ts`(行 2)**:`createSystem`(`:300-342`)删除 `system.finder.useMapState(maps)`;在保留 `system.finder.useMapLayer(layer)` 与 `injectPredicate`(`:344-354`)的既有形状之上,为 finder 补注入方向处理器(与旧默认一致的四正交处理器)。若该夹具的 `maps` 随之不再被使用,按 (1e) 的同一口径处置。既有用例(最短步序、损失岔路、174 告警、Infinity、不可达、moveTo/teleportTo、策略与 173 守卫)的断言逐字保留。
|
||||
(3) **`performance.test.ts`(行 3)**:`:361` 的 `system.finder.useMapState(maps)` 与 `:368` 的 `builder.useMapState(maps)` 两处删除;finder 与 `buildGraph` 内联 builder 各补注入方向处理器(四正交,与旧默认一致),保持两条路径的图规模与既有规模参数(`initCommandLength` 之类不涉及此文件,但矩阵尺寸与阈值)逐字不变(A10)。`MapGraphBuilder` 的具名引入与 `ReturnType<MapGraphBuilder['build']>` 写法保留。
|
||||
(4) **`replayPlayback.test.ts`(行 4)**:`createSmallMapScene`(`:85-117`)三处对齐——`:109` 的 `state.pathfinding.finder.useMapState(state.maps)` 删除;`:110` 的 `useMapLayer(map.eventLayer)` 保留;`:111-113` 的 `new DefaultPassPredicateImpl(state.maps)` 改为 `new DefaultPassPredicateImpl(state)`(`predicate.ts:8` 收 `IStateSystem`)。另为该 finder 补注入方向处理器(`replayPlayback.test.ts` 已引入 `DefaultPassPredicateImpl`,方向处理器可用 `@user/data-common` 的 `Dir4FaceHandler` 具名引入,或在测试内以既有 `faceManager` 取用;**不得**新增 `import type`,imports 块内不得插注释/空行,dev.md:55)。`:124` 的 `setFloor('F1')` 按 Q3 裁决改成传真实 `IGameMap`(该场景的地图对象来自 `state.maps.fromRaw(...)` 的返回值 `map`,注意 `:104-106` 已对 `map`/`map.eventLayer` 做了非空校验,可直接使用)。`:361-377` 的瞬移用例(`:369` 的第二次 `useMapLayer`)为 07-08 的 `#06-07-1` 见证,**其断言与注释逐字保留**——它只依赖 `createSmallMapScene` 的夹具对齐与自身的 `useMapLayer` 绑定。
|
||||
(5) **注释落笔(严格限三类,且以 Task 0 的 R1 裁决为准)**:(a) 四个文件 `:1` 的文件头覆盖说明按需**追加**本轮覆盖字样(既有文字一字不改);(b) 覆盖描述发生变化的 `it` 前单行中文注释同步(dev.md:86;例如 `graph.test.ts` 八方向用例的注释若仍写旧方法名,改为描述「注入八方向处理器」);(c) 若某条注释点名了已删除的成员(`useMapState`/`useDirGroup`/`PathfindingGraphBuilder`),改为点名 shipped 名字。三类之外一律不动;任何 jsDoc 一字不改。
|
||||
(6) **禁止**:不得修改 `path/{graph,finder,system,types}.ts`、`data-common/src/{types.ts,common/faceManager.ts}`、`data-state/src/{core.ts,hero/predicate.ts}` 或任何其它生产文件;不得在测试内新增方法/类/导出;不得使用连续 `as`、不得新增 `import type`、不得使用 `any` 或 `@ts-expect-error` 来绕过 `useFaceHandler` 的实参类型;不得改动三个文件里与本次对齐无关的用例;不得为本任务新增用例(D-10)。
|
||||
(7) **提交**:本任务四个文件一次原子提交 `test(07-16): align pathfinding tests with the shipped graph builder interface`(D-13);提交一律按显式路径 `git add <四个文件>`。
|
||||
</action>
|
||||
<reversibility rating="reversible">四个文件均为测试文件,改动可由 `git revert` 完整回退,且不改变生产行为与对外契约</reversibility>
|
||||
<verify>
|
||||
<automated>pnpm exec vitest run packages-user/data-system/src/path/graph.test.ts packages-user/data-system/src/path/system.test.ts packages-user/data-system/src/path/performance.test.ts packages-user/data-state/test/replayPlayback.test.ts</automated>
|
||||
<fails_when>输出中出现 `Test Files` 行含非零 `failed`;或出现 `Tests` 汇总行含非零 `failed`;或任何一条用例名出现在 `FAIL` 段中(尤其 `includes only the start node when no predicate is injected`、`resolves four orthogonal neighbor edges for the center node`、`expands neighbor edges to eight when Dir8 group is injected`、`plays a teleport step after the event layer is bound on floor activation`);或输出中出现 `TypeError: builder.useMapState is not a function`、`TypeError: system.finder.useMapState is not a function`、`TypeError: state.pathfinding.finder.useMapState is not a function` 之一;或出现 `logger.warn` 码 173 对应的空图早退使某条用例以读取空值报错 —— 按 D-09 退出并修订计划,不得改用「在测试内补 useMapState 转发适配层」「用 `as never` 绕过 useFaceHandler 的实参类型」「放宽边数/节点数断言」等未选方案</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- 四个文件中不再出现对 `useMapState` 的调用;`useFaceHandler` 的实参是 `IFaceHandler<number>` 实例(`Dir4FaceHandler` 或 `Dir8FaceHandler`),不是 `InternalDirectionGroup` 的数值
|
||||
- 每条会走到 `build()`/`find()` 的测试路径都显式注入了方向处理器,无一条因 173 早退而以读取空值的形式报错
|
||||
- `graph.test.ts` 三类既有期望(起始节点用例 `nodes.size` 与 `edges` 长度、四正交用例的 4 条边与方向集合、八方向用例的 8 条边)逐字保留且成立
|
||||
- `replayPlayback.test.ts` 的 `#06-07-1` 瞬移用例(`plays a teleport step after the event layer is bound on floor activation`)断言与两条既有中文注释逐字保留且转绿;`DefaultPassPredicateImpl` 的构造实参为 `state`
|
||||
- `performance.test.ts` 的图规模参数与耗时断言阈值未被改动
|
||||
- 注释只出现 R1 授权的三类落笔;任何 jsDoc 一字未改;无新增用例、无新增文件、无生产文件改动
|
||||
- 本任务四个文件一次原子提交,暂存内容只含这四个文件
|
||||
</acceptance_criteria>
|
||||
<done>四个寻路/回放测试文件的聚焦命令 0 失败,且 `useMapState` 已从测试面消失;`useMapLayer` 单独承担图层与宽度注入、`useFaceHandler` 收到真实处理器对象(行 1..4 按 Q1 关闭)</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: 录像家族(行 5..8)—— 把 4 个测试文件的录像桩成员对齐到 shipped 的 `array`,并把 `onRecordCommand` 的首步索引期望由 1 基改为 `array.length - 1`(即 0)</name>
|
||||
<files>packages-user/data-base/src/hero/equipment.test.ts, packages-user/data-base/src/hero/saveLoad.test.ts, packages-user/data-base/src/hero/items.test.ts, packages-user/data-common/src/replay/system.test.ts</files>
|
||||
<read_first>
|
||||
- packages-user/data-common/src/replay/system.ts(`:30-43` 构造器把 `new ReplayArray({...})` 赋给成员 `array`;`:63-68` `record` 先 `array.add` 再 `onRecordCommand?.(code, this.array.length - 1, params)`;`:89-110` `disable`/`revert`/`saveState` 对 `array` 的转发——用于确认桩的最小成员面是 `{ array: { add }, disable, revert }`)
|
||||
- packages-user/data-base/src/hero/equipment.ts(`:165-215` `equip`/`unequip` 的录像写入点:`replay.disable()`、`replay.array.add(ReplayCode.Equip, [uid])`、`replay.array.add(ReplayCode.Unequip, [slot])`、`replay.revert()`)
|
||||
- packages-user/data-base/src/hero/items.ts(`:120-135` 的 `replay.array.add(ReplayCode.UseItem, [raw.num])`)
|
||||
- packages-user/data-base/src/hero/equipment.test.ts(`:1` 文件头覆盖说明;`:53-100` `TestEnv`/`ReplayRouteStub`/`ReplaySystemStub`/`createReplaySystem`;`:145-160` `createEnv`;`:360-415` 三条读桩的用例(`:377/:385/:403/:405/:410`);以及 07-15 新增的 `diffs the final attributes when one compared item is currently equipped`——其红只因本行,本任务的对齐应使它转绿)
|
||||
- packages-user/data-base/src/hero/saveLoad.test.ts(`:1` 文件头覆盖说明;`:70-90` 桩的形状(`:78` 局部常量与 `:82` 返回对象成员);该文件其余部分本任务不改,但需确认没有第二处读桩成员的地方)
|
||||
- packages-user/data-base/src/hero/items.test.ts(`:1` 文件头覆盖说明;`:55-70` 桩与使用它的用例;`:144` 附近 `routes equipment items into the equipment store` 的用例名与本任务无关,不要因此改它的名字)
|
||||
- packages-user/data-common/src/replay/system.test.ts(`:1` 文件头覆盖说明;`:21-37` 两个本地夹具;`:72-95` `appends a replay step and dispatches the record hook`(`:80-82` 钩子回调签名、`:88-94` 三条断言))
|
||||
- .planning/phases/07-data-fixes/07-15-SUMMARY.md(`Deviations` 第 1 条的根因与「一行补救」建议,用于确认本任务正是该阻塞的解除点)
|
||||
</read_first>
|
||||
<action>
|
||||
按 Task 0 `<record>` 的 Q2 裁决落地(**无分支**)。本任务只改测试面:桩成员改名与一处期望值改写,不触碰任何生产文件。
|
||||
|
||||
(1) **`equipment.test.ts`(行 5)**:把录像桩的成员名由 `route` 改为 `array`,与生产成员名一致——`ReplaySystemStub`(`:76-80`)的成员、`createReplaySystem`(`:83-100`)内的局部常量与 `return { ... }` 的键名、以及 `:377/:385/:403/:405/:410` 五处读 `env.replaySystem.route.commands` 的引用点同步改名;`TestEnv.replaySystem` 的类型保持 `ReplaySystemStub`。自建接口 `ReplayRouteStub`(`:64-73`)与该桩的用途注释(`:149`)按 R1 裁决处置:裁决为「一并改名」时把接口名与注释里的成员指称改为与 shipped 一致的 `array` 口径,裁决为「逐字保留」时两者**一字不动**(此时仅成员名与引用点改变)。`:1` 文件头覆盖说明按需追加本轮覆盖字样(既有文字不改)。**:arrow 注意**:该文件 07-15 新增的 CR-01 用例(`diffs the final attributes when one compared item is currently equipped`)与两条既有 `compareEquip` 用例、码 146 用例、D-06 保留的码 147 `it.skip` 用例全部**不得改动**——本任务的对齐应使 CR-01 用例转绿(此前其红只因桩缺 `array`)。
|
||||
(2) **`saveLoad.test.ts`(行 6)**:`:82` 的 `replaySystem: { route, disable: vi.fn(), revert: vi.fn() }` 把成员键名改为 `array`,并同步局部常量名(`:78`);`:284/:289/:511/:518/:543/:593/:599` 的 `setFloor(<字符串>)` 按 Q3 裁决改为传真实 `IGameMap`(该文件的夹具已具备图块/道具存储与位置对象,可在夹具内用 `MapState` 的 `createMap`/`fromRaw` 取得,或在用例前置就绪的 map 上取用;`:275-298` 的用例是纯楼层存档往返,断言 `floorId` 为 `'F2'`、朝向为 `Up`、坐标为 `(1,2)` 必须逐字保留)。`:511-520` 与 `:590-600` 的跟随者楼层用法同理。
|
||||
(3) **`items.test.ts`(行 7)**:`:62` 的桩成员 `route` 改为 `array`;桩的用途注释按 R1 裁决处置。既有断言一字不改。
|
||||
(4) **`replay/system.test.ts`(行 8)**:`:94` 的期望元组由 `[[5, 1, [1, true]]]` 改为 `[[5, 0, [1, true]]]`——生产 `record` 传的是 `this.array.length - 1`(`system.ts:66`),首步即 0;`:88-93` 对 `array.length` 与 `array.get(0)` 的两条断言保留。`:1` 文件头覆盖说明按需追加。**不得**把该断言弱化为只校验命令码或参数、不得改用 `toMatchObject` 绕过索引、不得为此新增用例——若要证明「索引随长度推进」而需要第二步,属新增断言,必须先取得用户批准(A1 禁止自行新增)。
|
||||
(5) **注释落笔**:严格限 Task 0 R1 授权的三类(`:1` 文件头覆盖说明追加、覆盖描述变化的 `it` 前单行注释、点名已删除成员/已改名成员的注释改为点名 shipped 名字);任何 jsDoc 一字不改。
|
||||
(6) **禁止**:不得修改 `data-common/src/replay/{system,array,types}.ts`、`data-base/src/hero/{equipment,items,saveLoad,location,types}.ts` 或任何其它生产文件;不得新增用例、不得新增文件、不得新增依赖;不得把桩改成 `as never` 的整体断言以绕过成员校验。
|
||||
(7) **提交**:本任务四个文件一次原子提交 `test(07-16): align replay stubs with the shipped array member and record hook index`(D-13),按显式路径 `git add`。
|
||||
</action>
|
||||
<reversibility rating="reversible">四个文件均为测试文件,改动可由 `git revert` 完整回退;其中唯一的期望值改写(首步索引 1 → 0)仍在 Task 0 的裁决范围内</reversibility>
|
||||
<verify>
|
||||
<automated>pnpm exec vitest run packages-user/data-base/src/hero/equipment.test.ts packages-user/data-base/src/hero/saveLoad.test.ts packages-user/data-base/src/hero/items.test.ts packages-user/data-common/src/replay/system.test.ts</automated>
|
||||
<fails_when>输出中出现 `Test Files` 行含非零 `failed`;或出现 `Tests` 汇总行含非零 `failed`;或出现 `TypeError: Cannot read properties of undefined (reading 'add')`;或该次运行仍含 30s 悬挂的用例(输出出现 `Test timed out in 30000ms`);或 `replaces` 段显示 `appends a replay step and dispatches the record hook` 的期望元组仍为索引 1;或该文件的用例计数低于 `BASE_SHA` 版本(说明有用例被删)—— 按 D-09 退出并修订计划,不得改用「改回生产成员名 `route`」「把桩整体断言为 `as never` 以跳过成员校验」「弱化索引断言」等未选方案</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- 四个文件中录像桩的成员名为 `array`,且 `equipment.ts`/`items.ts` 的写入点在桩上命中(不再抛 `reading 'add'`)
|
||||
- `replay/system.test.ts` 的 `appends a replay step and dispatches the record hook` 期望元组为 `[[5, 0, [1, true]]]`,且该改写已登记进 SUMMARY 的期望值改写台账
|
||||
- `equipment.test.ts` 的 D-06 码 147 `it.skip` 用例保持 skip(注释与用例名逐字保留),仓库级 skipped 计数仍恰为 1
|
||||
- `equipment.test.ts` 的 CR-01 用例(07-15 新增)在本任务后转绿,且其断言逐字未改
|
||||
- `saveLoad.test.ts` 与 `items.test.ts` 的既有断言逐字保留;`saveLoad.test.ts` 的 `setFloor` 调用点全部传 `IGameMap`
|
||||
- 注释只出现 R1 授权的三类落笔;任何 jsDoc 一字未改;无新增用例、无新增文件、无生产文件改动
|
||||
- 本任务四个文件一次原子提交,暂存内容只含这四个文件
|
||||
</acceptance_criteria>
|
||||
<done>录像家族的 4 个测试文件聚焦命令 0 失败;桩成员与 shipped 的 `array` 一致、`onRecordCommand` 索引语义与生产 `array.length - 1` 一致,且 07-15 CR-01 见证的阻塞随之解除(行 5..8 按 Q2 关闭)</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: 楼层/位置形状与初始化器(行 9..12)—— `setFloor` 对齐到 `IGameMap`(含 `changeFloor` 的 `target`)、`location.test.ts` 不可表达分支按裁决处置、`coreEventLayer.test.ts` 按裁决重定向到 `MapState.fromRaw` 或退役</name>
|
||||
<files>packages-user/data-base/src/hero/location.test.ts, packages-user/data-base/src/hero/state.test.ts, packages-user/data-state/test/saveablesRoundTrip.test.ts, packages-user/data-state/src/coreEventLayer.test.ts</files>
|
||||
<read_first>
|
||||
- packages-user/data-base/src/hero/location.ts(`:21-24` 字段 `floorId`/`map`;`:49-53` `setFloor(map: IGameMap)`;`:65-72` `saveState` 含 `floorId`;`:74-79` `loadState` 直接写 `floorId` 且**不恢复 `map`**)
|
||||
- packages-user/data-base/src/hero/types.ts(`:283` `onSetFloor?(map: IGameMap | null)`;`:302-318` `IHeroLocation` 的 `floorId`/`map`/`setFloor(map: IGameMap | null)`;`:842-846` `IHeroChangeFloorInfo` 的 `target: IGameMap` 与 `x`/`y`/`face`)
|
||||
- packages-user/data-base/src/hero/state.ts(`:105-125` `changeFloor` 的钩子顺序与 `this.location.setFloor(info.target)`、位置与朝向更新)
|
||||
- packages-user/data-base/src/hero/location.test.ts(`:12-40` 全局桩;`:42-62` `createState`/`createFaceHandler`/`createLocation`;`:64-74` 初始态用例;`:76-93` `setPos` 用例;`:95-113` `setFloor` 用例(`:98` 的 `floors: (string | undefined)[]`、`:107`、`:110`、`:112` 的期望序列);`:115-122` 朝向用例)
|
||||
- packages-user/data-base/src/hero/state.test.ts(`:139-175` `changes floor with the documented hook order`(`:160-165` 的 `target: 'F2'` 入参、`:167` 钩子顺序、`:168-172` 位置断言、`:173` `floorId` 断言);`:60-120` 该文件的既有夹具(用于确认取图对象的方式))
|
||||
- packages-user/data-state/test/saveablesRoundTrip.test.ts(`:1` 文件头覆盖说明;`:115-175` 夹具与 `:127`/`:165` 两处 `setFloor`;`:225-240` 的 `floorId`/`isMapActive` 断言;`:280-295` 的存档内容断言)
|
||||
- packages-user/data-base/src/map/mapState.ts(`:210-260` `fromRaw` 的图层装配:`createMap` → 逐层 `setLayerAlias` → 识别并 `setEventLayer` → `setActiveStatus`;`:249-252` `createMap(id, width, height)`)与 `packages-user/data-base/src/map/types.ts`(`:744-802` `IMapState` 的 `maps`/`createMap`/`getMap`/`fromRaw`/`getActiveMap`/`isMapActive`/`setMapActiveStatus`)
|
||||
- packages-user/data-state/src/coreEventLayer.test.ts(整文件:`:1` 文件头覆盖说明、`:3-46` 四个 `Fake*` 接口与 `CoreInitializer`(`:44` `initMapState`)、`:48-62` 两处 `vi.mock`、`:64-71` `beforeAll`、`:73-124` `createFakeMapState`/`createFloor`、`:126-189` 用例(`:128-159` 的层序/zIndex/事件层断言与矩阵断言))
|
||||
- packages-user/data-base/src/map/mapState.test.ts(`createMap` 的注册与重复注册告警 121、`generates and reads content on a map created by createMap`——作为行 12 重定向后的等价覆盖参照)
|
||||
- packages-user/data-base/src/hero/mover.ts(`:190-225` 把 `map: this.tile.map` 交给谓词——同一段是 Q4 行 15..17 的根因链,本任务只在对齐 `replayPlayback.test.ts`/夹具时参考,**不改该文件**)
|
||||
- packages-user/data-state/src/event/event.test.ts(`:54-92` `createFixture`:本任务若按 Q3 裁决对齐此处,仅改 `:76` 的 `setFloor` 形态)
|
||||
</read_first>
|
||||
<action>
|
||||
按 Task 0 `<record>` 的 Q3 裁决落地(**无分支**)。
|
||||
|
||||
(1) **`location.test.ts`(行 9)**:把 `updates the floor and notifies the onSetFloor hook` 重塑为 shipped 的 map 形态。要点:
|
||||
(a) 用一个真实的 `IGameMap`(经 `MapState` 的 `createMap`/`fromRaw` 取得)替换 `:107` 的字符串入参;断言 `location.floorId` 等于 `map.floorId`(仍为 `'F2'`),并断言新增的 `location.map` 指向同一对象。
|
||||
(b) 钩子回调签名由 `floorId => …` 改为收 map 对象(`onSetFloor?(map: IGameMap | null)`);`:112` 的期望序列由字符串序列改为「同一批地图对象序列」或等价的逐项断言(不得只断言长度)。
|
||||
(c) `:110` 的 `setFloor(undefined)` 分支在当前实现下**没有可运行路径**(实现签名非空且直接读 `map.floorId`,`location.ts:49`),按 R2 裁决处置:若裁决为「仅登记覆盖缺口」,则把这一半重塑为经 `loadState` 表达的最接近语义(`location.saveState()` → 改 `floorId` → `loadState`,`location.ts:74-79` 是唯一能把 `floorId` 置回 `undefined` 的入口)并逐字写明「`setFloor(undefined)` 的旧覆盖已随实现签名收紧而消失」;若裁决为「另立计划处置生产签名」,则本半**不落笔**并在 SUMMARY 登记为待另立计划。无论哪条,`location.test.ts:1` 的文件头覆盖说明必须按需追加被覆盖的行为面变化。
|
||||
(d) `createState`(`:42-48`)若因引入 `MapState` 而需要更完整的 `IDataCommon` 假值,按 `MapState` 构造器与 `GameMap` 实际读取的成员补齐;补法必须让焦点命令转绿且不引入 `any`/连续 `as`(既有 `as never` 的收口风格可沿用)。
|
||||
(2) **`state.test.ts`(行 10)**:`:160-165` 的 `await hero.changeFloor({ target: 'F2', x: 5, y: 6, face: FaceDirection.Up })` 改为 `target: <IGameMap>`(经该文件既有夹具可取的 `MapState` 创建/取得,floorId 为 `'F2'`);`:167` 的钩子顺序 `['before','floor','after']`、`:168-172` 的位置/朝向断言、`:173` 的 `floorId === 'F2'`、`:174` 的 `moveDirection` 断言**逐字保留**。
|
||||
(3) **`saveablesRoundTrip.test.ts`(行 11)**:`:127` 与 `:165` 的 `setFloor(<字符串>)` 改为传真实 `IGameMap`(该文件已用 `state.maps.fromRaw(...)`/`createMap(...)` 建档,直接取返回值;`:165` 的 `'F9'` 需先确保该楼层的图对象存在,若原本依赖「设一个尚未创建的楼层 id」这一语义,须改成先建档再设楼层,并在 SUMMARY 登记该夹具前置的变化)。`:230` 的 `floorId === 'F1'`、`:236` 的 `isMapActive('F1')`、`:289` 的存档内容断言**逐字保留**。
|
||||
(4) **`coreEventLayer.test.ts`(行 12)**:按裁决执行且仅执行其一——
|
||||
(a) **A1(重定向)**:把该用例的被测主体由已删除的 `CoreState.initMapState` 改为 shipped 拥有者 `MapState.fromRaw`(`mapState.ts:210-260`),保留原判据的语义:楼层集合等于入参顺序、每层的图层别名序列为 `['bg','bg2','event','fg','fg2']`、zIndex 序列为 `[0,10,20,30,40]`、事件层就是 event alias 那一层、各层矩阵与输入逐字节一致。随之该文件的 `CoreInitializer` 类型(`:44`)、`Object.create(CoreState.prototype)`(`:130-132`)、以及 `vi.mock('./enemy')`/`vi.mock('./legacy')`(`:48-62`,注意 `./legacy` 已不是真实模块)都会失去意义——按最小可运行原则清理(能删则删、能简则简),清理范围必须在 SUMMARY 逐条列出;`FakeMapState`/`FakeLayer`/`FakeGameMap` 若被真实 `MapState`/`GameMap` 取代则一并移除。`:1` 文件头覆盖说明按新的被测主体更新(追加改写)。
|
||||
(b) **A2(退役)**:删除该文件,并在 SUMMARY 明写「其被测 API `CoreState.initMapState` 已被整体删除;等价覆盖已在 `mapState.test.ts` 的 `createMap`/图层别名/事件层选择用例与 `mapLifecycle.test.ts` 的生命周期用例」;此时 `files_modified` 的该路径在提交区间应表现为删除(`--diff-filter=D` 命中),且**必须**由用户裁决授权(prohibitions 已禁止执行者自选)。
|
||||
(5) **禁止**:不得修改 `hero/{location,state,types}.ts`、`map/**`、`data-state/src/core.ts` 或任何其它生产文件;不得用 `as never`/`Object.create` 把已删除的 `initMapState` 假装出来以让旧用例继续通过;不得新增用例、不得新增文件;不得放宽 `floorId`/层序/zIndex/矩阵任一断言。
|
||||
(6) **提交**:本任务的改动一次原子提交 `test(07-16): align floor and initializer tests with the shipped map shapes`(D-13),按显式路径 `git add`(若 A2 退役,提交内容为文件删除)。
|
||||
</action>
|
||||
<reversibility rating="reversible">三个测试文件的改动可由 `git revert` 完整回退;行 12 若选 A1 亦为测试文件重写,若选 A2 则文件删除可由 `git revert` 恢复</reversibility>
|
||||
<verify>
|
||||
<automated>pnpm exec vitest run packages-user/data-base/src/hero/location.test.ts packages-user/data-base/src/hero/state.test.ts packages-user/data-state/test/saveablesRoundTrip.test.ts packages-user/data-state/src/coreEventLayer.test.ts</automated>
|
||||
<fails_when>输出中出现 `Test Files` 行含非零 `failed`;或出现 `Tests` 汇总行含非零 `failed`;或出现 `AssertionError: expected undefined to be 'F2'`、`expected undefined to be 'F1'`、`TypeError: initializer.initMapState is not a function` 之一;或行 12 的裁决为 A1 时该用例仍引用已删除的成员(编译期错误或运行期 `TypeError`);或行 12 的裁决为 A2 时提交区间内的删除未被授权(`<record>` 未写明 A2);或某条既有断言的期望值被改动却未登记在 SUMMARY 的改写台账 —— 按 D-09 退出并修订计划,不得改用「保留 `initMapState` 的调用并补一个测试内空实现」「用 `as never` 把字符串 map 蒙过去」「放宽 `floorId` 断言」等未选方案</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- 三个楼层相关测试文件的每一处 `setFloor` 与 `changeFloor({ target })` 都传入真实 `IGameMap`;`floorId`、钩子顺序、位置/朝向、`isMapActive` 与存档内容断言逐字保留且成立
|
||||
- `location.test.ts:96` 用例的钩子断言改为收 map 对象且逐项成立;`setFloor(undefined)` 这一半按 R2 裁决处置并在 `:1` 文件头覆盖说明与 SUMMARY 中如实登记
|
||||
- 行 12 的最终形态与 `<record>` 的 A1/A2 裁决一致:A1 时不再出现 `initMapState` 的任何引用、被测主体为 `MapState.fromRaw` 且原判据语义保留;A2 时该文件已被删除且删除被裁决授权
|
||||
- 注释只出现 R1 授权的三类落笔;任何 jsDoc 一字未改;无新增用例、无新增文件、无生产文件改动
|
||||
- 本任务改动一次原子提交,暂存内容只含本任务声明的文件(A2 时为该文件的删除)
|
||||
</acceptance_criteria>
|
||||
<done>楼层/位置相关与初始化器的 4 个文件聚焦命令 0 失败(A2 时以「退役文件 + 等价覆盖已存在」的形式收口),`floorId` 相关断言回到 `'F2'` / `'F1'`(行 9..12 按 Q3 关闭)</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 4: 行为存疑家族(行 13..17)—— 按 Task 0 的 Q4 逐行裁定执行:A 形状对齐(13/14 改桩成员名、15 改夹具 `setFloor` 形态、16/17 只改共享夹具 `closed-loop.ts`)/ B 判为生产回归则该行不落笔并升级/ C 先只采集证据再报</name>
|
||||
<files>packages-user/data-base/src/hero/mover.test.ts, packages-user/data-base/src/hero/follower.test.ts, packages-user/data-state/src/event/event.test.ts, packages-user/data-state/test/dataClosure.test.ts, packages-user/data-state/test/nodeTracer.test.ts, packages-user/data-state/test/fixtures/closed-loop.ts</files>
|
||||
<read_first>
|
||||
- packages-user/data-base/src/hero/mover.ts(`:186-225`:通行判定把 `map: this.tile.map` 交给谓词(`:203`),以及 `ObjectMoveType.Teleport` 分支的 `replay.array.add(ReplayCode.Teleport, [step.x, step.y])`——行 13 的首个可观测根因)
|
||||
- packages-user/data-base/src/hero/mover.test.ts(`:1` 文件头覆盖说明;`:45-63` `createState` 的录像桩(`:48`)与 `createTestTile`;`:70-113` `FakeTopImpl`;`:137-242` 六条 step/teleport 用例的全部既有断言;`:244-257` 码 144 用例)
|
||||
- packages-user/data-base/src/hero/follower.test.ts(`:1` 文件头覆盖说明;`:54-93` `createState` 的桩(`:67`)与 `createController`;`:95-112` `FakeTopImpl`;`:114-369` 跟随者用例群,其中 `gathers followers asynchronously and waits for the movement` 是规划期实测悬挂的那一条)
|
||||
- packages-user/data-state/src/event/event.test.ts(`:1` 文件头覆盖说明;`:54-92` `createFixture`(`:72` `createMap`、`:76` `setFloor('F1')`、`:79-90` `IBlockEventEnv`);`:172-200` `awaits hero sequence and forward-step movement` 的全部断言)
|
||||
- packages-user/data-state/test/fixtures/closed-loop.ts(`:47-61` `IClosedLoopFixture`;`:91-146` 两张地图的 `fromRaw`;`:163-172` `setMapActiveStatus` 与 `setFloor('F1')`、`replay.record(ReplayCode.Right)`;`:233-250` `reset` 内的第二次 `setFloor('F1')` 与沙箱创建)
|
||||
- packages-user/data-state/test/dataClosure.test.ts(`:252-281` 两条失败用例;确认 `:271-275` 与 `:294` 已使用 `replay.array`,故本文件不含过期形状)
|
||||
- packages-user/data-state/test/nodeTracer.test.ts(整文件:四条断言与 `waitForEnded`)
|
||||
- packages-user/data-state/src/hero/predicate.ts(`:25-35` `canPass`:`!map` 拒绝、`map.eventLayer` 为 `undefined` 时拒绝——行 15..17 的共同根因)
|
||||
- packages-user/data-base/src/hero/location.ts(`:49-53` `setFloor(map: IGameMap)`:行 15..17 的形态入口)
|
||||
</read_first>
|
||||
<action>
|
||||
**本任务的第一动作是复现并记录证据,第二动作才按 Task 0 `<record>` 的 Q4 逐行裁定分派。**
|
||||
|
||||
(1) **证据采集(必做,先于任何文件改动)**:以与规划期相同的命令重跑本家族涉及的 5 个测试文件,逐行记录每个失败用例的「用例全名 + 首个可观测信号 + 该信号对应的抛出点」。规划期已记录(A4):13/14 的首个信号是 `TypeError: Cannot read properties of undefined (reading 'add')` 且表现为 `Test timed out in 30000ms`(`mover.test.ts` 6 条、`follower.test.ts` 1 条);15/16/17 的信号分别是 `awaits hero sequence and forward-step movement` 的 `expected +0 to be 1`、`executes a trigger event and applies its awaited mutation` 与 `preserves replay order, awaits completion, and stops on failure` 的 `expected false to be true`、`replays movement, awaits event mutation, and ends normally` 的 `expected +0 to be 1`。执行期若实测与上述不符(例如新增/消失的失败用例),**以执行期实测为准**,并把它作为与 Q4 裁决是否仍然适用的判据。
|
||||
(2) **按行分派(严格照 `<record>`,不得自行改派)**:
|
||||
(a) **裁决为 A 的行**:仅做形状对齐,一句话概括每行的落笔面——行 13:`mover.test.ts:48` 的录像桩成员 `route` 改为 `array`,六条用例的断言一字不改;行 14:`follower.test.ts:67` 同一改名,该用例断言一字不改;行 15:`event.test.ts:76` 的 `setFloor(<字符串>)` 改为传真实 `IGameMap`,其余夹具与断言一字不改;行 16 与行 17:**只改共享夹具 `closed-loop.ts`** 的 `:166` 与 `:234` 两处 `setFloor(<字符串>)` 为传真实 `IGameMap`(该文件已在 `:91-146` 用 `state.maps.fromRaw(...)` 建好 `map`/`secondMap`,直接取用;`:163-164` 的 `setMapActiveStatus` 与 `:170-172` 的录像录制保持原样),`dataClosure.test.ts` 与 `nodeTracer.test.ts` **零改动**(A7),并在 SUMMARY 明写「其失败由共享夹具完整解释」。
|
||||
(b) **裁决为 B 的行**:**不修改任何断言与该行相关文件**;在 `07-16-SUMMARY.md` 把它登记为「生产回归,待另立计划」,写明现象、实测信号、代码链与建议的排查方向;并**停止本任务**,不得进入该行的 Task 5 收口(Task 5 的门禁要求全量 0 失败,B 分支下必然不满足,故按 D-09 退出并向用户汇报剩余红行归属)。
|
||||
(c) **裁决为 C 的行(若 Q4 整体选 C)**:不修改这 5 个文件与夹具的任何一行;把 (1) 的证据表写入 SUMMARY 的「待裁定」段,并说明「按 C 未落笔」;随后进入 Task 5,但 Task 5 的全量门禁只能以「红行集合恰等于 Q4 中 C/B 认领的行」为口径收口,且必须在 SUMMARY 与返回消息中明写仍需用户二次裁定。
|
||||
(3) **禁止**:不得改写这 5 个文件与夹具中除「形状对齐」外的任何一行;不得新增用例、不得新增 `it.skip`、不得放宽断言(尤其 `getReplayed()`、`location.x/y`、`eventCompleted()` 这类计数/布尔断言);不得修改 `mover.ts`、`predicate.ts`、`location.ts`、`core.ts` 或任何其它生产文件;不得把某些行的失败「顺手用 `setFloor` 之外的改法」掩盖过去(例如给谓词塞假 `map.eventLayer`、跳过通行判定、直接设置勇士坐标)。
|
||||
(4) **提交**:A 分支下按文件家族一次原子提交 `test(07-16): align behavioural fixtures with the shipped map and replay shapes`(D-13,按显式路径 `git add`);C/B 分支下**不产生提交**(本任务零改动)。
|
||||
</action>
|
||||
<reversibility rating="reversible">A 分支下改动的 6 个文件均为测试文件/测试夹具,可由 `git revert` 完整回退;C/B 分支下零改动</reversibility>
|
||||
<verify>
|
||||
<automated>pnpm exec vitest run packages-user/data-base/src/hero/mover.test.ts packages-user/data-base/src/hero/follower.test.ts packages-user/data-state/src/event/event.test.ts packages-user/data-state/test/dataClosure.test.ts packages-user/data-state/test/nodeTracer.test.ts</automated>
|
||||
<fails_when>在 Q4 裁决为 A 的行上,输出仍出现 `Tests` 汇总行含非零 `failed`,或出现 `TypeError: Cannot read properties of undefined (reading 'add')`、`Error: Test timed out in 30000ms`、`expected +0 to be 1`、`expected false to be true` 中任一属于该行的信号;或在 Q4 裁决为 B、C 的行上,`git status --short` 显示本任务声明的 6 个文件中有任何被改动(C/B 下本任务必须零改动);或本任务在未获 Q4 逐行认领的先行改动 —— 按 D-09 退出并修订计划,不得改用「放宽 `getReplayed()`/`eventCompleted()` 断言」「直接设置勇士坐标绕过移动」「给谓词补假事件层」「把某行判为生产回归」等未获授权的做法</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- 证据表(每行的用例全名 + 首个可观测信号 + 抛出点 + 与规划期 A4 记录的一致性)已采集并写入 `07-16-SUMMARY.md`
|
||||
- 行 13、14、15、16、17 各自的处置与 `<record>` 的逐行裁决一致;A 的行只做了形状对齐且断言未改;B 的行未落笔且已登记「待另立计划」;C 的行未落笔且已登记「待用户二次裁定」
|
||||
- 若行 16/17 裁决为 A:`dataClosure.test.ts` 与 `nodeTracer.test.ts` 在本任务提交区间内**零改动**,并已在 SUMMARY 明写失败来源为共享夹具
|
||||
- 本任务未修改任何生产文件;未新增用例;未新增 `it.skip`;未放宽任何断言
|
||||
- A 分支下本任务一次原子提交且暂存内容只含本任务声明的 6 个文件;C/B 分支下本任务零提交
|
||||
</acceptance_criteria>
|
||||
<done>行 13..17 按 Q4 逐行裁定收口:A 的行在既有断言不变的前提下转绿;B 的行被升级为待另立计划;C 的行留下可直接支撑用户二次裁定的最小化证据 —— 三者都不以「改写断言」为手段</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 5: D-12/D-44 文件级三步门禁 + 全量套件与基线核对 + 期望值改写台账 + 17 行清单映射登记(含「无生产改动」的区间证据与 Phase 7 验证失效说明)</name>
|
||||
<files>.planning/phases/07-data-fixes/07-16-SUMMARY.md</files>
|
||||
<read_first>
|
||||
- .planning/phases/07-data-fixes/07-16-PLAN.md(Task 0 `<record>` 的 Q1..Q4 与 R1..R3 裁决,用于逐条对照;以及全部 A1..A10 假设与 prohibitions)
|
||||
- .planning/phases/07-data-fixes/07-VERIFICATION.md(`### Post-Refactor Test Breakage` 的 17 行表、「用户定性」段与 `## Recommended Fix Plans` 的 07-16 段)
|
||||
- .planning/phases/07-data-fixes/07-15-SUMMARY.md(门禁/基线/SUMMARY 的既有写法参照;其 `Deviations` 第 1 条的 CR-01 阻塞应在本次解除,需在 SUMMARY 复核并更新其状态)
|
||||
- .planning/phases/07-data-fixes/07-15-PLAN.md(Task 5 的门禁与提交写法参照)
|
||||
- .planning/codebase/TESTING.md(`pnpm test:ci` 口径、`logger.catch` 断言写法、无覆盖率工具)
|
||||
- .planning/WINDOWS.md(确认这 17 行**没有**账本条目;本计划不新建、不 `fixed`/`waive`)
|
||||
- .planning/ROADMAP.md(Phase 7 段的 Plans 列表与 Wave 分组——**只读参照**:确认既有计划行与 Wave 段的现有形态,供 SUMMARY 写明「跟踪同步待 orchestrator 定点追加」的对照;本计划**不编辑**该文件)
|
||||
</read_first>
|
||||
<action>
|
||||
(1) **门禁(D-12 / D-44,文件级三步)**:对 `files_modified` 的 18 个文件中**本计划实际改动过的**文件(其中被裁决为 B/C 的行所涉文件与在 A7 下保持零改动的文件不在内)执行——(a) `pnpm exec eslint --fix <文件>` 后 `pnpm exec eslint <文件>` **0 错误**(CRLF 与 Prettier 由 `--fix` 处理,不手工调换行);(b) **文件级类型门禁按 06-RESEARCH Pitfall 4 的判定方式**:`pnpm exec vue-tsc --noEmit` 输出按**改动文件的相对路径逐个**过滤,**无命中**(**不得**用「整仓退出码非 0」判定,仓库存在既有渲染/legacy 诊断,不属本计划);(c) `pnpm test:ci` **无失败**、跳过项仍**恰为 1 条**(`data-base/src/hero/equipment.test.ts` 的码 147 用例,D-06),失败集合按 (2) 的口径核对。
|
||||
(2) **基线与增量核对(A3)**:执行前先重录当日基线(`pnpm test:ci`,记录文件数/通过数/失败数/跳过数四项),并与 `07-VERIFICATION.md` 的计划期参照(**未经本次规划运行复测**:17 失败文件 / 73 失败用例 / 667 通过 / 1 跳过,共 741)对比。收口口径三条:失败集合必须为空(除非 Q4 中有行被认领为 B/C,此时失败集合必须**恰等于**那些行);跳过数必须仍为 1;总用例数必须与当日基线**一致**——若不一致,逐条说明是哪一行、哪种裁决造成的变化(例如行 12 退役或重定向、`location.test.ts` 的不可表达分支重塑),并明写该变化已被授权。**不得**通过删改用例或放宽断言来对齐数字。
|
||||
(3) **期望值改写台账(本计划的核心反弱化证据)**:在 SUMMARY 中以「文件 → 旧期望值 → 新期望值 → 授权的 `<record>` 裁决条目 → 提交」五列登记**每一处**被改写的期望值(至少应含 `replay/system.test.ts` 的首步索引 1 → 0;其余逐条按实际落笔补全)。同时给出**每文件 `expect(` 出现次数对照**:以 `BASE_SHA` 版本与当前版本各计一次,逐文件列出「基线次数 → 当前次数」,要求当前次数**不低于**基线(仅允许相等或增加);若某文件当前次数低于基线,必须逐条指明是哪个裁决授权删除/合并了哪条断言,否则视为弱化并按 D-09 退出。
|
||||
(4) **「无生产改动」的区间证据(必做)**:用显式路径区间对比本计划起点 sha..HEAD——`git diff "$BASE_SHA".."$HEAD_SHA" --stat -- <本计划 18 条声明路径>` 与不带路径的 `git diff "$BASE_SHA".."$HEAD_SHA" --stat` 各跑一次,断言区间内出现的文件**全部**落在 18 条声明路径内(外加本计划的 `07-16-SUMMARY.md`);另对 `packages-user/data-*/src`、`packages/common/src/logger.json` 两个范围各跑一次 `git diff "$BASE_SHA".."$HEAD_SHA" --name-only --`,**逐一核对其中只出现本计划声明的测试文件路径**。任何生产源码出现在区间内即按 D-09 退出汇报。同时核对:未新建源码/测试文件(行 12 若裁决为 A2,其删除是唯一允许的文件增删)、未改 `package.json`/`pnpm-lock.yaml`、未新增或修改告警码。
|
||||
(5) **注释门槛核对(必做)**:对本计划改动过的文件逐行确认——除 R1 授权的三类落笔(`:1` 文件头覆盖说明的**追加**、覆盖描述变化的 `it` 前单行注释、点名已删除/已改名接口的注释改为点名 shipped 名字)之外,**没有任何** jsDoc/注释行被修改或删除;核对结论写入 SUMMARY 的对照表。
|
||||
(6) **17 行清单映射登记(D-14)**:在 SUMMARY 中以「清单行号 → 测试文件 → 处置(对齐 / 重定向 / 退役 / 待另立计划 / 待二次裁定)→ Q1..Q4 裁决分支 → 提交」六列逐行登记 17 行,**不得有任何一行留空**。同时明写:`WINDOWS.md` 未新建条目、未 `fixed`/`waive`(这 17 行本来就没有账本 id);CR-01 见证(07-15)在本计划后是否转绿及其依据;行 13..17 的证据表与(若适用)待裁定清单;A8 的 `FIX-01` 探针未决假设保留在册。
|
||||
(7) **Phase 7 验证失效与跟踪同步(orchestrator 负责,本计划不落笔)**:SUMMARY 明写 `07-VERIFICATION.md`(`_Verified: 2026-09-17T10:42:49Z_`)已被本批改动与更早的 `4aaea68` 失效,本计划执行完毕后**必须重跑 `/gsd-verify-work`(或 `gsd-verify-work 7`)**重新出具验证结论。`.planning/ROADMAP.md` 的 Phase 7 段(Plans 计数 + `07-16-PLAN.md` 计划行 + Wave 16 段)与 `.planning/STATE.md` 的同步是**本计划之外的 orchestrator 收尾步骤**(先例:07-10/07-11/07-15 的 SUMMARY 均明写「`ROADMAP.md` / `STATE.md` 未由本计划更新」);故本计划**不编辑、不暂存、不提交**这两个路径,SUMMARY 只承担「跟踪同步待 orchestrator 以定点追加方式完成、既有计划行与说明段不得被重排/删除/改写」的声明。
|
||||
</action>
|
||||
<verify>
|
||||
<automated>pnpm test:ci</automated>
|
||||
<fails_when>`Test Files` 行含非零 `failed`;或 `Tests` 汇总行含非零 `failed`(除 Q4 中被认领为 B/C 的行所对应的用例以外);或 `Tests` 汇总行不含 `1 skipped`;或总用例数与执行前重录的当日基线不一致且未在 SUMMARY 逐条说明授权来源;或 `vue-tsc --noEmit` 输出中含本计划改动过的任一文件路径;或 `git diff "$BASE_SHA".."$HEAD_SHA" --stat` 中出现 18 条声明路径与 `07-16-SUMMARY.md` 之外的文件(尤其生产源码);或某个提交的暂存内容含声明路径之外的文件;或本计划提交区间内 `.planning/ROADMAP.md`、`.planning/STATE.md` 出现改动;或改动过的文件里存在被修改/删除的既有 jsDoc/注释行(R1 三类之外);或某文件的 `expect(` 次数低于 `BASE_SHA` 版本且无裁决授权记载 —— 按 D-09 退出并修订计划,不得通过删改用例、放宽断言或撤回已提交改动来对齐门禁</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- 改动过的文件 `eslint` 0 错误、`vue-tsc --noEmit` 按文件路径过滤 0 命中、`pnpm test:ci` 无失败且跳过项恰为 1 条(失败集合为空,或恰等于 Q4 中认领为 B/C 的行)
|
||||
- 基线四项已重录并与计划期参照对比;总用例数不一致时已逐条说明授权来源;未通过删改用例或放宽断言对齐数字
|
||||
- 期望值改写台账(五列)与每文件 `expect(` 次数对照表完整;无未授权的断言删除或合并
|
||||
- 「无生产改动」的两次区间核对(带路径与不带路径)结果一致,区间内文件全部落在 18 条声明路径 + `07-16-SUMMARY.md` 之内;行 12 若裁决为 A2,其删除已在区间内体现且被授权
|
||||
- 注释门槛核对通过:除 R1 授权的三类落笔外,无任何 jsDoc/既有注释改动
|
||||
- SUMMARY 含 17 行六列映射表、行 13..17 证据表、`WINDOWS.md` 未新建/未 `fixed`/未 `waive` 的明写、CR-01 见证状态的复核、A8 探针未决假设,以及「Phase 7 验证失效 + 重跑 `/gsd-verify-work`」与「`.planning/ROADMAP.md`/`.planning/STATE.md` 跟踪同步属 orchestrator 收尾」的明写
|
||||
- 本任务改动一次原子提交(仅 `07-16-SUMMARY.md`),且本计划提交区间内 `.planning/ROADMAP.md`/`.planning/STATE.md` 为空
|
||||
</acceptance_criteria>
|
||||
<done>D-44 三步门禁全绿、基线增量与收口口径一致、期望值改写与 `expect(` 计数台账完整、17 行清单逐行有归属、无生产改动有区间证据,Phase 7 验证失效与重跑要求写入 `07-16-SUMMARY.md`;`.planning/ROADMAP.md`/`.planning/STATE.md` 的跟踪同步作为 orchestrator 的收尾步骤在 SUMMARY 中明写,本计划零触碰</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| 已落地的生产接口 ↔ 测试面的断言 | 测试是回归网的唯一事实源;当测试红而生产被改造时,「对齐测试」与「放松测试」在 diff 上可能长得一样——本计划的边界就是「只对齐注入面与形状,不改断言的期望值(除 Task 0 逐条授权者)」 |
|
||||
| 测试文件 ↔ 生产源码文件 | 本计划只允许写测试面;生产文件(`path/**`、`replay/**`、`hero/{location,state,mover,types}.ts`、`map/**`、`core.ts`、`predicate.ts`、`logger.json`)是只读输入。一旦为转绿而改生产,就跨越了「修复」与「改契约」的边界 |
|
||||
| 「接口变动」与「行为回归」的判定 | 行 13..17 的现象(悬挂、计数为 0、布尔为 false)既能由形状漂移解释,也可能由真实行为回归造成;把回归误判为漂移会把缺陷固化成期望值 |
|
||||
| 共享测试夹具 ↔ 多个测试文件 | `data-state/test/fixtures/closed-loop.ts` 被 `dataClosure.test.ts`/`nodeTracer.test.ts` 共用;改夹具会同时改变多个文件的被测前提,容易掩盖「某个文件本身也已漂移」 |
|
||||
| 包管理器 → 仓库 | 本计划无安装动作(`package.json`/`pnpm-lock.yaml` 不变) |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-7-54 | Tampering | 测试面被静默弱化以换取转绿(删除/合并/放宽 `expect`、把断言改成 `toMatchObject`、去掉索引或计数核对) | high | mitigate | (a) 每文件 `expect(` 次数对照表(`BASE_SHA` 版本 vs 当前,要求不低于基线,Task 5 (3));(b) 期望值改写五列台账,每一处都必须指向 Task 0 的授权裁决条目;(c) 仓库级 skipped 计数恒等式 `== 1`(D-06 的码 147 用例)作为「新增 skip 换绿」的直接证据;(d) Task 1..4 的 `<fails_when>` 各自点名禁止的替代做法(适配层、`as never`、放宽边数/索引/计数断言、直接设置坐标绕过移动) |
|
||||
| T-7-55 | Tampering / Elevation of Privilege | 以「测试修复」为名改动生产源码(`path/**`、`replay/**`、`hero/location.ts`、`core.ts`、`predicate.ts`、`logger.json`) | high | mitigate | 「无生产改动」的区间证据(Task 5 (4)):带路径与不带路径两次 `git diff "$BASE_SHA".."$HEAD_SHA" --stat` 的并集必须落在 18 条声明路径 + `07-16-SUMMARY.md` 之内;对 `packages-user/data-*/src` 与 `packages/common/src/logger.json` 各单独跑一次 `--name-only`,逐一核对只出现声明的测试文件;Task 1..4 的禁止条款逐一点名不得触碰的生产文件;提交一律按显式路径 `git add`,禁止 `git add -A`/`git add -a`/`git stash`/`git clean`/`git reset --hard` |
|
||||
| T-7-56 | Repudiation | 把真实的行为回归(行 13..17)当作「接口漂移」对齐掉,从而把缺陷固化成期望值 | high | mitigate | (a) Task 0 的 Q4 强制**逐行**认领,且默认口径为 C(先调查再报、不落笔);(b) Task 4 的第一动作是复现并记录每行的「用例全名 + 首个可观测信号 + 抛出点」,并与规划期实测(A4:7 次 `reading 'add'`、4 条断言漂移的具名用例)逐条对齐,实测不符时以实测为准并重新判定裁决是否仍适用;(c) B 分支被显式写为「该行不落笔 + 升级为待另立计划 + 停止本轮收口」,不允许就地改写;(d) `dataClosure.test.ts`/`nodeTracer.test.ts` 在 A7 下保持零改动,杜绝「为了看起来参与修复而改断言」 |
|
||||
| T-7-57 | Tampering | 行 12 用 `as never`/`Object.create` 把已删除的 `CoreState.initMapState` 假装出来,使旧用例表面转绿 | medium | mitigate | Task 3 (4) 明确禁止该做法;只允许 A1(重定向到 `MapState.fromRaw` 并保留层序/zIndex/矩阵判据)或 A2(经用户批准退役并在 SUMMARY 明写等价覆盖已在 `mapState.test.ts`/`mapLifecycle.test.ts`);`<fails_when>` 点名 `TypeError: initializer.initMapState is not a function` 与「未获授权的删除」两种信号 |
|
||||
| T-7-58 | Repudiation | 共享夹具被改动后,两个测试文件「看起来通过」但其自身可能也已漂移 | medium | mitigate | A7 要求把「失败由共享夹具完整解释」写进 SUMMARY;行 16/17 的文件在 A 分支下零改动并以此声明;Task 5 (6) 的六列映射表要求每一行都写明处置,不允许「行号留空」 |
|
||||
| T-7-59 | Repudiation | 门禁用「整仓退出码」判定,掩盖文件级诊断;或把与用户并行改动相关的失败混入本计划结论 | low | mitigate | Task 5 (1b) 要求 `vue-tsc --noEmit` 按改动文件相对路径逐个过滤、不得用整仓退出码;A3 规定「不在 17 行清单内的失败一律归因于用户并行工作,只登记观察」;`fails_when` 以「输出中含本计划改动文件的路径」为信号 |
|
||||
| T-7-SC | Tampering | npm/pnpm 依赖安装 | high | mitigate | 本计划不新增依赖、不改 `package.json`/`pnpm-lock.yaml`;出现安装需求即暂停并要求用户确认包合法性 |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
1. 四组聚焦命令全绿(或按 Q4 裁决得到明确的 B/C 归属):`pnpm exec vitest run packages-user/data-system/src/path/graph.test.ts packages-user/data-system/src/path/system.test.ts packages-user/data-system/src/path/performance.test.ts packages-user/data-state/test/replayPlayback.test.ts`;`pnpm exec vitest run packages-user/data-base/src/hero/equipment.test.ts packages-user/data-base/src/hero/saveLoad.test.ts packages-user/data-base/src/hero/items.test.ts packages-user/data-common/src/replay/system.test.ts`;`pnpm exec vitest run packages-user/data-base/src/hero/location.test.ts packages-user/data-base/src/hero/state.test.ts packages-user/data-state/test/saveablesRoundTrip.test.ts packages-user/data-state/src/coreEventLayer.test.ts`;`pnpm exec vitest run packages-user/data-base/src/hero/mover.test.ts packages-user/data-base/src/hero/follower.test.ts packages-user/data-state/src/event/event.test.ts packages-user/data-state/test/dataClosure.test.ts packages-user/data-state/test/nodeTracer.test.ts`;以及 Task 5 的 `pnpm test:ci`。
|
||||
2. `pnpm test:ci` **无失败**、跳过项**恰为 1 条**(D-06);总用例数与执行前重录的当日基线一致——不一致必须逐条指明授权来源(行 12 的重定向/退役、`location.test.ts` 不可表达分支的重塑);基线偏离先查明原因或按 D-09 退出。
|
||||
3. `pnpm exec eslint` 对本计划改动过的文件 0 错误;`pnpm exec vue-tsc --noEmit` 按改动文件相对路径过滤 0 命中(不得用退出码判定)。
|
||||
4. Task 0 的 `<record>` 中 Q1..Q4 与 R1..R3 均有用户裁决,代码/用例与裁决逐条一致(SUMMARY 的六列映射表);未采纳任何【未选】分支;Q4 的 5 行逐行有明确归属。
|
||||
5. 只改动 `files_modified` 的 18 条声明路径(本计划实际改动其中一部分)与本计划产出物 `07-16-SUMMARY.md`;**生产源码零改动**(区间证据两轮);未新建源码/测试文件(行 12 若裁决 A2,其删除是唯一允许的增删)、未新增依赖、未改 `package.json`/`pnpm-lock.yaml`、未新增或修改告警码;`.planning/ROADMAP.md`/`.planning/STATE.md` 在本计划提交区间内为空;工作树中属于用户的既有改动未被触碰、未被暂存、未被提交。
|
||||
6. 反弱化证据齐备:每文件 `expect(` 次数对照表(当前不低于基线,例外逐条授权)、期望值改写五列台账、仓库级 skipped 计数恒等式。
|
||||
7. 除 R1 授权的三类注释落笔外,本计划无任何 jsDoc/既有注释改动(用户长期规则 + D-11)。
|
||||
8. 无新增 `it.skip`/`describe.skip`/`test.skip`/`xit`/`xdescribe`/`it.todo`;既有断言未被弱化;D-06 的码 147 用例仍保留 skip;未新增用例(D-10)。
|
||||
9. `07-16-SUMMARY.md` 含 17 行六列映射表、行 13..17 证据表、`WINDOWS.md` 未新建/未 `fixed`/未 `waive` 的明写、CR-01 见证状态复核、A8 的 `FIX-01` 探针未决假设,以及「Phase 7 验证失效与重跑 `/gsd-verify-work`」「`.planning/ROADMAP.md`/`.planning/STATE.md` 的跟踪同步为 orchestrator 收尾」的明写。
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 行 1..4(寻路家族)按 Q1 对齐:`useMapState` 已从测试面消失,`useMapLayer` 单独承担图层与宽度注入,`useFaceHandler` 收到真实 `IFaceHandler<number>`;四个文件的聚焦命令无失败,且既有边数/节点数/方向集合断言一字未改。
|
||||
- 行 5..8(录像家族)按 Q2 对齐:4 个测试文件的录像桩成员为 `array`,`onRecordCommand` 的首步索引期望为 `0`(`array.length - 1`),且两处期望值改写已登记;07-15 的 CR-01 见证阻塞随之解除。
|
||||
- 行 9..11(楼层/位置形状)按 Q3 对齐:所有 `setFloor`/`changeFloor({ target })` 传入真实 `IGameMap`,`floorId`/钩子顺序/位置朝向/`isMapActive`/存档内容断言一字未改;`location.test.ts` 的不可表达分支按 R2 裁定处置并登记。
|
||||
- 行 12(初始化器)按 Q3 的 A1/A2 之一收口:不接受任何对已删除成员 `initMapState` 的残留引用,也不接受用类型逃逸把它「假装」出来。
|
||||
- 行 13..17 按 Q4 逐行收口:A 的行在既有断言不变的前提下转绿(16/17 只改共享夹具、两个测试文件零改动);B 的行被升级为待另立计划且不落笔;C 的行留下可直接支撑二次裁定的最小化证据。
|
||||
- 反弱化证据齐备(`expect(` 次数对照、期望值改写台账、skipped 计数恒等式),生产源码零改动的区间证据成立;D-44 三步门禁全绿;四个家族各按 D-13 原子提交且未牵连任何无关改动。
|
||||
- Phase 7 验证失效与重跑要求已记录(须重跑 `/gsd-verify-work`),17 行清单映射登记在 `07-16-SUMMARY.md`;`.planning/ROADMAP.md`/`.planning/STATE.md` 的跟踪同步交由 orchestrator 收尾(本计划零编辑、零暂存、零提交)。
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/07-data-fixes/07-16-SUMMARY.md` when done
|
||||
</output>
|
||||
Loading…
Reference in New Issue
Block a user