From dd56f741bbdebe8cbb5fa349c60f4bb28090a84f Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Wed, 21 Oct 2026 13:18:54 +0800 Subject: [PATCH] =?UTF-8?q?docs(09-22):=20=E4=BF=AE=E8=AE=A2=E7=AC=AC=203?= =?UTF-8?q?=20=E8=BD=AE=E5=AE=A1=E6=9F=A5=EF=BC=88=E5=8F=8C=E8=A3=85?= =?UTF-8?q?=E9=A5=B0=E5=99=A8=E5=AF=B9=E7=85=A7=E6=9E=84=E5=BB=BA=20/=20js?= =?UTF-8?q?Doc=20=E5=9F=BA=E7=BA=BF=E9=97=A8=E7=A6=81=20/=20=E6=A0=B9?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E8=B7=B3=E8=BF=87=E8=BF=87=E6=BB=A4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../09-minimum-browser-runnable/09-22-PLAN.md | 120 ++++++++++-------- 1 file changed, 64 insertions(+), 56 deletions(-) diff --git a/.planning/phases/09-minimum-browser-runnable/09-22-PLAN.md b/.planning/phases/09-minimum-browser-runnable/09-22-PLAN.md index 4c32aa1..b48dce8 100644 --- a/.planning/phases/09-minimum-browser-runnable/09-22-PLAN.md +++ b/.planning/phases/09-minimum-browser-runnable/09-22-PLAN.md @@ -20,43 +20,44 @@ estimate: confidence: low must_haves: truths: - - "**本计划第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md / D-03)**:先汇报「任务 11 后续修复 —— 构建脚本的 content 过滤 + 修饰器删除」的要做什么 / 为什么 / 大致怎么做 / 影响范围,列明最小范围(`script/build-plugin.ts` 新增修饰器删除插件、`script/build-game.ts` 注入两处、`script/build-resource.ts` 增加 content 过滤、`script/build-plugin.test.ts` 扩展 + 新增 `script/build-resource.test.ts`)、逐文件夹的 content 过滤规则、修饰器删除口径(TS AST 精确切片、不删 import)、验证方式、已知边界与待批准命名(OI-1 / OI-2 / OI-3),然后停下等待「可以执行」。" + - "**本计划第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md / D-03)**:先汇报「任务 11 后续修复 —— 构建脚本的 content 过滤 + 修饰器删除」的要做什么 / 为什么 / 大致怎么做 / 影响范围,列明最小范围(`script/build-plugin.ts` 新增修饰器删除插件、`script/build-game.ts` 注入两处、`script/build-resource.ts` 增加 content 过滤、`script/build-plugin.test.ts` 扩展 + 新增 `script/build-resource.test.ts`)、逐文件夹的 content 过滤规则(含根目录显式跳过过滤)、修饰器删除口径(TS AST 精确切片、不删 import)、验证方式(含对照式双装饰器构建与加固后的 jsDoc 门禁)、已知边界与待批准命名(OI-1 / OI-2 / OI-3),然后停下等待「可以执行」。" - "**content 结构规则(用户裁定 1 / D-96):** `src/content` → `dist/content`;**根目录文件照常输出**(`.jsonc` 压缩、`item.ts` 原样);**`defaults/` 照常输出并压缩**(不排除、不按清单过滤);**其余子文件夹(`maps` / `texture` / `animation` / `audio` / `font`)只保留被各自 `.list.jsonc` 引用的文件**,未引用的剔除;**`.list.jsonc` 自身压缩输出**;无 `.list.jsonc` 的子文件夹全量输出。" + - "**根目录显式跳过清单过滤(INFO-5):** `buildContent` 调用 `processDirectory` 时必须传根标记(`isRoot` / `filter: false`),**根目录本身永不按清单取舍**——根目录全量输出不依赖「当前根目录没有 `.list.jsonc`」这一现状,也不依赖将来是否出现根 `.list.jsonc`;只有**子目录**才做 `.list.jsonc` 检测。" - "**清单匹配口径(用户裁定 1):** 解析 `.list.jsonc`(去注释后 `JSON.parse`)后递归收集**所有字符串值**,以「**完整文件名**」或「**去扩展名主文件名**」匹配保留(如 `terrains0.png` 命中完整名、`empty` 命中 `empty.jsonc`、`test` 命中 `test.anim`)。" - - "**jsonc 压缩(用户裁定 1 / D-96):** 去掉**所有** `//` 注释压成**纯 JSON**(含系统标记行),后缀**仍为 `.jsonc`**;**不分块、不 zip**;字体仍 fontmin 子集化(无字体二进制时跳过)。" + - "**jsonc 压缩(用户裁定 1 / D-96):** 去掉**所有整行** `//` 注释(`line.trim().startsWith('//')`,**含系统标记行** `// --- SYSTEM PREFIX END --- //`)压成**纯 JSON**,后缀**仍为 `.jsonc`**;**不得改动 `minifyJSONC` 的现有行为**——只按整行剥离 `//` 注释,不剥离行尾注释、不改解析/序列化方式;**不分块、不 zip**;字体仍 fontmin 子集化(无字体二进制时跳过)。" - "**修饰器删除(用户裁定 2):** 构建时删除**所有** `@shouldReplay(...)` / `@ignoreReplay(...)` 装饰器应用,**数据端构建与渲染端构建都删**(`buildData` 与 `buildClient` 各注入一次);**不得手动删 import**,交给 Vite/ESBuild 依赖分析。" - "**删除方式必须可靠(用户裁定 2):** 以 **TypeScript AST**(`ts.createSourceFile` + `ts.canHaveDecorators` / `ts.getDecorators`)定位装饰器节点,按 `getStart()` / `getEnd()` **逆序切片删除**;**字符串字面量、注释、以及非目标装饰器中出现的同名文本一律不得被删**;仅处理项目自身 `.ts` / `.tsx`(**跳过 `node_modules`**),不处理 `.vue`。" - "**构建范围与顺序不变(用户裁定 3 / 4 / D-93):** 只改构建脚本与其测试;**不改** `script/build-lib.ts`、`package.json`、`index.html`、`dev.md`、`src/**`、`public/**`、数据端/渲染端源码(`packages-user/**`、`packages/**`)、`vite.config.ts`、lockfile、`script/dev.ts`;**不改构建顺序**(准备 → 渲染端 → 数据端 → content → LICENSE);**不做特殊失败处理**(无 BLOCKED 分支、无 baseline / 范围门禁、无外部状态检查)。" - "**开发环境不受影响:** 修饰器删除插件 `apply: 'build'`(`pnpm dev` 仍保留装饰器,`script/dev.ts` 不改);插件 `enforce: 'pre'`,其 `transform` 早于 Vite 的 TS/JSX 转译。" - "**测试是必需产物:** 扩展 `script/build-plugin.test.ts`(新增 `describe`,不改动既有 `dataIsolationPlugin` 用例)并新增 `script/build-resource.test.ts`;每个 `it` 前一行注释(AGENTS.md);content 测试用临时 fixture 目录,不触碰 `src/content`。" - - "**验证可运行且范围诚实(Nyquist):** 修饰器删除用「纯函数文本断言 + **对照式**真实 `vite.build()`」双证——同一 fixture 在**不带插件**的对照构建里产物必须含 `mota_replay_probe` 与 `shouldReplay` 标记,在**带插件**的构建里必须不含(缺对照即成空断言);另含字符串/注释不误删、`node_modules` 跳过。content 过滤以 fixture 行为测试证明(`defaults` 全量、清单引用保留、未引用剔除、无清单子文件夹全量、`.list.jsonc` 压缩去注释、jsonc 去注释)。另加真实 `src/content` 的「清单覆盖率」检查、按函数切片的结构门禁、`build-resource.ts` 的「既有注释零删除」git diff 门禁,以及 `script/build-*.ts` 范围内的 `eslint`。结构门禁只断言**行为与导出形态**(AST API 标记、`enforce` / `apply`、导入绑定与注入位置),不硬编码提议命名。所有 `node -e` 门禁**只用单引号字面量**(PowerShell 5.1 下 `\"` 会提前闭合字符串,实测确定性 SyntaxError)。命令一律 `cmd /c \"pnpm exec ...\"`(PowerShell 5.1 下裸 `pnpm` 会因 `pnpm.ps1` 的 `PSSecurityException` 失败)。" - - "**命名须先获批(D-04):** OI-1(`removeReplayDecorators` / `replayDecoratorPlugin` / `name: 'mota-replay-decorator'`)、OI-2(`LIST_FILE_NAME` / `DEFAULTS_DIRECTORY` / `readListNames` / `collectListedNames` / `isListedFile`)、OI-3(`script/build-resource.test.ts` 文件名)。未获批前不得落码。**若用户在 Task 0 指定了与提议不同的命名,必须先按新命名修订本计划(门禁命令、测试用例与 SUMMARY 关键字同步)并重新获批后执行**——门禁本身按行为与导出形态断言,不会自动跟随改名。" + - "**验证可运行且范围诚实(Nyquist):** 修饰器删除用「纯函数文本断言 + **对照式双装饰器**真实 `vite.build()`」双证——同一 fixture 必须**导出一个类**、两个方法分别带 `@shouldReplay('...')` 与 `@ignoreReplay('...')`、各自返回**不同**的唯一字面量(`mota_replay_probe` / `mota_ignore_probe`),且 `shouldReplay` / `ignoreReplay` **工厂函数只在 fixture 内本地定义、不得导出**(否则实验组也会保留名字,对照失效)。**对照构建** `plugins: []` 产物必须 `toContain('shouldReplay')`、`toContain('ignoreReplay')`、`toContain('mota_replay_probe')`、`toContain('mota_ignore_probe')` 且两个方法名都在;**实验构建**(带插件)必须 `not.toContain('shouldReplay')`、`not.toContain('ignoreReplay')`、`toContain('mota_replay_probe')`、`toContain('mota_ignore_probe')` 且两个方法名都在(缺任一侧即成空断言);另含字符串/注释不误删、`node_modules` 跳过。content 过滤以 fixture 行为测试证明(`defaults` 全量、清单引用保留、未引用剔除、无清单子文件夹全量、**根目录即使出现 `.list.jsonc` 也全量**、`.list.jsonc` 压缩去注释、jsonc 去注释)。另加真实 `src/content` 的「清单覆盖率」检查、按函数切片的结构门禁、`build-resource.ts` **与 `build-plugin.ts`** 的「既有注释零删除」git 门禁(以 `git show HEAD:` 的注释行集合为基线逐行比对,**不依赖暂存状态**),以及 `script/build-*.ts` 范围内的 `eslint`。结构门禁只断言**行为与导出形态**(AST API 标记、`enforce` / `apply`、导入绑定与注入位置),不硬编码提议命名。所有 `node -e` 门禁**只用单引号字面量**(PowerShell 5.1 下 `\"` 会提前闭合字符串,实测确定性 SyntaxError)。命令一律 `cmd /c \"pnpm exec ...\"`(PowerShell 5.1 下裸 `pnpm` 会因 `pnpm.ps1` 的 `PSSecurityException` 失败)。" + - "**命名须先获批(D-04):** OI-1(`removeReplayDecorators` / `replayDecoratorPlugin` / `name: 'mota-replay-decorator'`,以及新增的模块级常量 `REPLAY_DECORATORS`(目标装饰器名集合)/ `SCRIPT_EXTENSIONS`(`.ts` / `.tsx` 后缀集合)/ `NODE_MODULES_SEGMENT`(`node_modules` 路径判定))、OI-2(`LIST_FILE_NAME` / `DEFAULTS_DIRECTORY` / `readListNames` / `collectListedNames` / `isListedFile` / `processDirectory` 新增的 `isRoot` 参数)、OI-3(`script/build-resource.test.ts` 文件名)。未获批前不得落码。**若用户在 Task 0 指定了与提议不同的命名,必须先按新命名修订本计划(门禁命令、测试用例与 SUMMARY 关键字同步)并重新获批后执行**——门禁本身按行为、值标记与导出形态断言,不会自动跟随改名。" artifacts: - path: script/build-plugin.ts - provides: "在既有 `dataIsolationPlugin` 之外新增:`removeReplayDecorators(code: string, fileName: string): string`(TS AST 定位并逆序切片删除 `@shouldReplay` / `@ignoreReplay` 装饰器)与 `replayDecoratorPlugin(): Plugin`(`name: 'mota-replay-decorator'`、`enforce: 'pre'`、`apply: 'build'`、`transform(code, id)` 仅处理项目内 `.ts` / `.tsx`,跳过 `node_modules`)(命名待批准,OI-1)" + provides: "在既有 `dataIsolationPlugin` 之外新增:模块级常量 `REPLAY_DECORATORS` / `SCRIPT_EXTENSIONS` / `NODE_MODULES_SEGMENT`(均为内部常量、不导出)、`removeReplayDecorators(code: string, fileName: string): string`(TS AST 定位并逆序切片删除 `@shouldReplay` / `@ignoreReplay` 装饰器)与 `replayDecoratorPlugin(): Plugin`(`name: 'mota-replay-decorator'`、`enforce: 'pre'`、`apply: 'build'`、`transform(code, id)` 仅处理项目内 `.ts` / `.tsx`,跳过 `node_modules`)(命名待批准,OI-1)" - path: script/build-plugin.test.ts - provides: "既有测试**扩展**:新增 `replayDecoratorPlugin` / `removeReplayDecorators` 的 `describe`——纯函数断言(删装饰器、保留字符串与注释中的同名文本、参数含引号/括号整段删除)+ **对照式**真实 `vite.build()`(同一 fixture:**不带插件**的对照产物必须含 `mota_replay_probe` 与 `shouldReplay` / `ignoreReplay` 标记;带插件产物必须不含这些标记且方法名仍存在);每个 `it` 前一行注释" + provides: "既有测试**扩展**:新增 `replayDecoratorPlugin` / `removeReplayDecorators` 的 `describe`——纯函数断言(删装饰器、保留字符串与注释中的同名文本、参数含引号/括号整段删除)+ **对照式双装饰器**真实 `vite.build()`(同一 fixture:**导出的类**上两个方法分别带 `@shouldReplay` / `@ignoreReplay` 并各自返回 `mota_replay_probe` / `mota_ignore_probe`,两个工厂函数在 fixture 内**本地定义且不导出**;**不带插件**的对照产物必须含 `shouldReplay` / `ignoreReplay` / 两个唯一字面量 / 两个方法名;带插件产物必须 **不含** `shouldReplay` 与 `ignoreReplay`,且两个唯一字面量与两个方法名**仍在**);每个 `it` 前一行注释" - path: script/build-game.ts provides: "注入:`buildClient` 的 `plugins` 增加 `replayDecoratorPlugin()`(在 `legacy(...)` 之前)、`buildData` 的 `plugins` 变为 `[dataIsolationPlugin(), replayDecoratorPlugin()]`;构建顺序、产物落点、失败处理、其余配置一律不变" - path: script/build-resource.ts - provides: "content 过滤:`LIST_FILE_NAME`(`.list.jsonc`)、`DEFAULTS_DIRECTORY`(`defaults`)、`readListNames`、`collectListedNames`、`isListedFile`;`processDirectory` 在有清单的目录内按清单保留文件、`defaults` 与无清单目录全量输出、`.list.jsonc` 始终压缩输出(命名待批准,OI-2)" + provides: "content 过滤:`LIST_FILE_NAME`(`.list.jsonc`)、`DEFAULTS_DIRECTORY`(`defaults`)、`readListNames`、`collectListedNames`、`isListedFile`;`processDirectory` 增加 `isRoot` 参数(`buildContent` 传根标记,**根目录永不按清单取舍**);有清单的目录内按清单保留文件、`defaults` 与无清单目录全量输出、`.list.jsonc` 始终压缩输出(命名待批准,OI-2)" - path: script/build-resource.test.ts - provides: "**新增**行为测试:临时 fixture 验证根文件、`defaults/` 全量、`maps/` 与 `texture/` 与 `font/` 的清单保留/剔除、**无清单子文件夹全量**、`.list.jsonc` 压缩且剥离 `//` 注释(fixture 清单原文带注释)、`item.ts` 原样拷贝;每个 `it` 前一行注释(文件名待批准,OI-3)" + provides: "**新增**行为测试:临时 fixture 验证根文件、**根目录即使存在 `.list.jsonc` 也全量输出**、`defaults/` 全量、`maps/` 与 `texture/` 与 `font/` 的清单保留/剔除、**无清单子文件夹全量**、`.list.jsonc` 压缩且剥离整行 `//` 注释(fixture 清单原文带注释)、`item.ts` 原样拷贝;每个 `it` 前一行注释(文件名待批准,OI-3)" - path: .planning/phases/09-minimum-browser-runnable/09-22-SUMMARY.md provides: "执行摘要:content 逐文件夹过滤规则与清单匹配口径、修饰器删除口径(TS AST + 双端注入 + 不删 import)、命名获批情形、门禁结果、`open_items` 处置、已知边界(未端到端跑全量 `build:game`;生产产物不再做录像安全检查;`index.html` 仍引用 `/styles.css`)" key_links: - "`replayDecoratorPlugin()`(`script/build-plugin.ts`)↔ `buildClient` 与 `buildData` 的 `plugins` 数组(`script/build-game.ts`)—— 用户裁定 2「双端都删」的唯一执行点" - - "`removeReplayDecorators` 的 AST 切片 ↔ `@shouldReplay('...')` 装饰器文本 —— 只删装饰器节点范围,字符串/注释中的同名文本不会被误删" + - "`removeReplayDecorators` 的 AST 切片 ↔ `@shouldReplay('...')` / `@ignoreReplay('...')` 装饰器文本 —— 只删装饰器节点范围,字符串/注释中的同名文本不会被误删" - "`replayDecoratorPlugin` 的 `apply: 'build'` ↔ `script/dev.ts` 未改 —— 开发环境仍保留装饰器" - - "`readListNames`(`minifyJSONC` + `JSON.parse` + `collectListedNames`)↔ `isListedFile` 的「完整文件名 / 去扩展名主文件名」双匹配 —— 用户裁定 1 的保留口径" - - "`processDirectory` 的 `.list.jsonc` 检测 ↔ `DEFAULTS_DIRECTORY` 豁免 —— `defaults/` 永远全量输出" + - "清单解析(`minifyJSONC` 剥离整行 `//` 注释 + `JSON.parse` + 递归收集字符串值)↔ 「完整文件名 / 去扩展名主文件名」双精确匹配 —— 用户裁定 1 的保留口径" + - "`processDirectory` 的 `.list.jsonc` 检测 ↔ `defaults` 豁免与根目录 `isRoot` 跳过 —— `defaults/` 与根目录永远全量输出" - "`.list.jsonc` 自身始终走 `minifyJSONC` 输出 ↔ 其余文件按清单取舍 —— 清单文件不会因未被自己引用而丢失" - "`dist/content/**` 落点 ↔ `MotaDataLoader.resolveURL` 的 `content/**` 与 `src/data/constant.ts` 的 `CORE_URL='content/core.jsonc'` —— 目录契约不变" assumptions: - "**当前清单已覆盖现有素材:** `texture/.list.jsonc` 引用了 `texture/` 下全部 48 个 PNG(含 `magictower.png`);`maps/.list.jsonc` 引用了全部 5 个地图 jsonc;`animation`(`[\"test\"]`)与 `font`(`[\"testFont.ttf\"]`)目录内无对应素材;`audio/.list.jsonc` 为 `{}`(解析后无字符串),`audio/` 当前为空。故过滤不会减少现有 `dist/content` 的实际素材(由 Task 2 的清单覆盖率门禁证明)。" - "**PowerShell 5.1 参数传递:** 所有 `node -e` 门禁只用单引号字面量,不出现 `\"`(双引号会提前闭合字符串)、不出现 `$` 与反引号(会被插值 / 转义);确需引号字符时用 `String.fromCharCode(39)` 构造。`cmd /c \"pnpm exec ...\"` 形式保留(裸 `pnpm` 会因 `pnpm.ps1` 的 `PSSecurityException` 失败)。" - - "**计数基线(已实测):** `src/content` 共 23 个 `.jsonc`(5 个根 + 8 个 `defaults` + 5 个地图 + 5 个 `.list.jsonc`)、`texture/` 共 48 个 PNG;`@shouldReplay` 应用共 69 处(20 个 `.ts` 文件,另有 1 处仅出现在 `packages-user/data-common/src/replay/func.ts` 的注释里,不算应用);`@ignoreReplay` 无实际应用。" - - "**`defaults/` 无 `.list.jsonc`:** 现为 8 个 `.jsonc`;按用户裁定 1 显式豁免,即使将来出现清单也全量输出。" + - "**计数基线(已实测):** `src/content` 共 23 个 `.jsonc`(5 个根 + 8 个 `defaults` + 5 个地图 + 5 个 `.list.jsonc`)、`texture/` 共 48 个 PNG;`@shouldReplay` = **69 次应用 / 20 个 `.ts` 文件**;`packages-user/data-common/src/replay/func.ts` 另有 **1 行注释含 2 处 `@shouldReplay` 文本**(第 5 行,非应用),其第 34 行注释含 `@ignoreReplay`(亦非应用);`@ignoreReplay` **无实际应用**。" + - "**`defaults/` 与根目录均无 `.list.jsonc`:** `defaults/` 现为 8 个 `.jsonc`,按用户裁定 1 显式豁免,即使将来出现清单也全量输出;根目录现为 5 个 `.jsonc` + `item.ts`,由 `isRoot` 显式跳过过滤,不依赖「根目录当前没有清单」这一现状。" - "**TypeScript AST 可用:** 仓库 `typescript@6.0.3` 为 devDependency;`ts.canHaveDecorators` / `ts.getDecorators` 可用,装饰器位于 `modifiers`;标准装饰器无需开启 `experimentalDecorators` 即可解析(`tsconfig.json` 未开启)。" - "**插件时序:** `enforce: 'pre'` 的 `transform` 早于 Vite 的 `vite:esbuild` TS 转译与 `@vitejs/plugin-vue-jsx`,因此删装饰器发生在任何编译之前;`apply: 'build'` 保证 `pnpm dev` 不受影响。" - "**import 由依赖分析处理:** 删除装饰器后 `shouldReplay` / `ignoreReplay` 的绑定变为未使用,由 Rollup/esbuild tree-shaking 移除;**不手动删 import**(用户裁定 2)。" @@ -66,7 +67,7 @@ must_haves: - "**行号会漂移:** `build-game.ts` 的门禁按**函数名切片**(`buildClient` < `buildData` < `buildGame` 的定义顺序)而非固定行号。" prohibitions: - "不得在用户回复通过 Task 0 关卡之前执行任何任务或派发执行子代理。" - - "**未经批准的命名一律不得使用:** `removeReplayDecorators` / `replayDecoratorPlugin` / `name: 'mota-replay-decorator'`(OI-1)、`LIST_FILE_NAME` / `DEFAULTS_DIRECTORY` / `readListNames` / `collectListedNames` / `isListedFile`(OI-2)、`script/build-resource.test.ts`(OI-3)。**若用户指定了其它命名,必须先用新命名修订本计划(含门禁命令与测试用例)并重新获批**,不得沿用旧名跑门禁、也不得只改代码不改计划。" + - "**未经批准的命名一律不得使用:** `removeReplayDecorators` / `replayDecoratorPlugin` / `name: 'mota-replay-decorator'` / 新增常量 `REPLAY_DECORATORS` / `SCRIPT_EXTENSIONS` / `NODE_MODULES_SEGMENT`(OI-1)、`LIST_FILE_NAME` / `DEFAULTS_DIRECTORY` / `readListNames` / `collectListedNames` / `isListedFile` / `isRoot` 参数(OI-2)、`script/build-resource.test.ts`(OI-3)。**若用户指定了其它命名,必须先用新命名修订本计划(含门禁命令与测试用例)并重新获批**,不得沿用旧名跑门禁、也不得只改代码不改计划。" - "**不得手动删除源码中的 import:** 不得改 `packages-user/**`、`packages/**`、`src/**` 里的任何 import 语句(用户裁定 2 / 3)。" - "**不得让 `defaults/` 参与清单过滤:** 不得排除、不得按清单取舍、不得跳过压缩。" - "**不得把清单匹配做成模糊匹配:** 只允许「完整文件名」或「去扩展名主文件名」两种精确匹配;不得用包含/前缀/正则通配去保留文件。" @@ -79,12 +80,15 @@ must_haves: - "**既有注释 / jsDoc 一律逐字保留(AGENTS.md):** `script/build-resource.ts` 中 `processDirectory` / `minifyJSONC` / `compressFont` / `buildContent` 与接口成员的 jsDoc 不得改写或删除;`script/build-plugin.ts` 中 `dataIsolationPlugin`、`SPECIFIER_BANS` / `PATH_BANS` / `findBan` 的注释同理。新逻辑的说明只能写在新函数 / 新常量上。" - "**不得在 `node -e` 门禁里用 `\\\"` 转义双引号:** PowerShell 5.1 会提前闭合字符串并抛 SyntaxError;内联脚本只允许单引号字面量(需引号字符时用 `String.fromCharCode(39)`),且不得出现 `$` 与反引号。" - "**不得删掉对照构建:** 不带插件的对照构建缺失时,`not.toContain` 就是空断言(编译产物本就不含 `@shouldReplay` 语法)。" - - "**不得用 `not.toContain('@shouldReplay')` 作为唯一证据:** 必须同时对「对照产物含标记」「实验产物不含标记 + 方法名/literal 仍在」两侧断言。" + - "**不得让对照 fixture 失效:** fixture 必须**导出一个类**、两个方法分别带 `@shouldReplay` / `@ignoreReplay` 并各自返回**不同**的唯一字面量(`mota_replay_probe` / `mota_ignore_probe`),`shouldReplay` / `ignoreReplay` 工厂函数**必须在 fixture 内本地定义且不得导出**——导出或把工厂放到别的模块,都会让实验组也保留名字,对照变成无效断言。" + - "**不得只断言删除效果:** 必须同时断言两侧——对照(`plugins: []`)含 `shouldReplay`、`ignoreReplay`、两个唯一字面量与两个方法名;实验组不含 `shouldReplay` / `ignoreReplay`,但两个唯一字面量与两个方法名**仍在**(证明只删装饰器、没删类与方法)。" + - "**不得把根目录纳入清单过滤:** `buildContent` 调用 `processDirectory` 时必须显式传根标记(`isRoot` / `filter: false`),根目录文件不得因将来出现根 `.list.jsonc` 而被过滤。" + - "**不得改动 `minifyJSONC` 的行为:** 只允许沿用现有的「整行 `//` 注释剥离」(含系统标记行);不得改为剥离行尾注释、不得改解析 / 序列化方式。" - "**不得新增依赖、不改 lockfile。**" - "**不得伪造门禁:** 不得手写 `dist` 产物冒充构建结果;不得用「全量 `build:game` 已通过」之类的表述掩盖未端到端运行的事实。" open_items: - - "**OI-1(修饰器删除插件命名):** 提议导出 `removeReplayDecorators(code: string, fileName: string): string` 与 `replayDecoratorPlugin(): Plugin`,插件 `name: 'mota-replay-decorator'`。备选:`stripReplayDecorators` / `removeReplayDecoratorPlugin`、`name: 'mota-replay-decorator-strip'`。**待批准**(D-04)。" - - "**OI-2(content 过滤内部命名):** 提议模块内常量 `LIST_FILE_NAME`(`.list.jsonc`)、`DEFAULTS_DIRECTORY`(`defaults`),内部函数 `readListNames(listFile: string): Promise>`、`collectListedNames(value: unknown, output: Set): void`、`isListedFile(name: string, names: Set): boolean`。备选:`LIST_NAME` / `LISTED_NAME_CACHE`、`loadListNames` / `pickStrings` / `keepFile`。**待批准**。既有 `buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats` 与 `IContentBuildCounter` **不改名**。" + - "**OI-1(修饰器删除插件命名):** 提议导出 `removeReplayDecorators(code: string, fileName: string): string` 与 `replayDecoratorPlugin(): Plugin`,插件 `name: 'mota-replay-decorator'`;并新增**不导出**的模块级常量 `REPLAY_DECORATORS`(目标装饰器名集合 `['shouldReplay', 'ignoreReplay']`)、`SCRIPT_EXTENSIONS`(`['.ts', '.tsx']`)、`NODE_MODULES_SEGMENT`(`node_modules` 路径判定正则)。备选:`stripReplayDecorators` / `removeReplayDecoratorPlugin`、`name: 'mota-replay-decorator-strip'`;常量备选 `DECORATOR_NAMES` / `TS_EXTENSIONS` / `NODE_MODULES_PATTERN`,或**全部改为函数内内联字面量、不新增模块级常量**。**待批准**(D-04)。门禁只断言值标记与导出形态,不绑定这些常量名。" + - "**OI-2(content 过滤内部命名):** 提议模块内常量 `LIST_FILE_NAME`(`.list.jsonc`)、`DEFAULTS_DIRECTORY`(`defaults`),内部函数 `readListNames(listFile: string): Promise>`、`collectListedNames(value: unknown, output: Set): void`、`isListedFile(name: string, names: Set): boolean`,以及 `processDirectory` 新增的 `isRoot: boolean = false` 参数(`buildContent` 传 `true`)。备选:`LIST_NAME` / `LISTED_NAME_CACHE`、`loadListNames` / `pickStrings` / `keepFile`、`isRootCall` / `filter`。**待批准**。既有 `buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats` 与 `IContentBuildCounter` **不改名**。" - "**OI-3(测试文件):** 扩展既有 `script/build-plugin.test.ts`;新增 `script/build-resource.test.ts`(与被测文件同名,测试文件按 AGENTS.md 由 AI 维护)。**报备**(如需改名请一并指明)。" - "**已知边界(不在本计划):** ① 未端到端运行全量 `build:game`(渲染端仍有用户 WIP 的 stale import),验证只到单测 + 结构门禁 + 真实 content 清单覆盖率;② 生产构建删掉录像安全检查装饰器属用户裁定 2 的预期结果,运行期不再产生 `@shouldReplay` 的收集信息;③ `index.html` 仍引用 `/styles.css`(09-21 已记录),本计划不改。" --- @@ -98,7 +102,7 @@ Phase 9 · 任务 11 · 第 22 个计划 —— **构建脚本的 content 过滤 **做法(最小):** - `script/build-plugin.ts`:在既有 `dataIsolationPlugin` 之外新增一个修饰器删除插件(TS AST 定位 + 逆序切片)与其纯函数。 - `script/build-game.ts`:`buildClient` 与 `buildData` 各注入该插件;其余配置、顺序、产物落点不变。 -- `script/build-resource.ts`:`processDirectory` 增加「有 `.list.jsonc` 的目录按清单取舍文件」的判断(`defaults/` 与无清单目录全量、`.list.jsonc` 自身始终压缩输出)。 +- `script/build-resource.ts`:`processDirectory` 增加「有 `.list.jsonc` 的目录按清单取舍文件」的判断(`defaults/` 与无清单目录全量、根目录 `isRoot` 跳过过滤、`.list.jsonc` 自身始终压缩输出)。 - 测试:扩展 `script/build-plugin.test.ts`;新增 `script/build-resource.test.ts`(fixture 行为测试)。 **约束:** 用户裁定 3 / 4 —— 只改构建脚本;不改 `build-lib.ts` / `package.json` / `index.html` / `dev.md` / `src/**` / `public/**` / 数据端与渲染端源码 / `vite.config.ts` / lockfile / `script/dev.ts`;不改构建顺序;不做特殊失败处理。 @@ -148,21 +152,21 @@ Phase 9 · 任务 11 · 第 22 个计划 —— **构建脚本的 content 过滤 ② **范围(最小):** `script/build-plugin.ts`(新增插件 + 纯函数)、`script/build-game.ts`(`buildClient` / `buildData` 各注入一次)、`script/build-resource.ts`(清单过滤)、`script/build-plugin.test.ts`(扩展)、`script/build-resource.test.ts`(新增)、`09-22-SUMMARY.md`。**其余文件一律不动**(含 `build-lib.ts` / `package.json` / `index.html` / `dev.md` / `src/**` / `public/**` / `packages-user/**` / `packages/**` / `vite.config.ts` / lockfile / `script/dev.ts`)。 - ③ **content 逐文件夹规则(用户裁定 1):** 根目录文件照常(`.jsonc` 压缩、`item.ts` 原样);`defaults/` 全量输出并压缩(显式豁免清单过滤);`maps` / `texture` / `animation` / `audio` / `font` 只保留被各自 `.list.jsonc` 引用的文件,未引用剔除;`.list.jsonc` 自身始终压缩输出;无清单的子文件夹全量输出。匹配口径 = 去注释 `JSON.parse` 后递归收集**所有字符串**,按「完整文件名」或「去扩展名主文件名」精确匹配(`empty` → `empty.jsonc`、`test` → `test.anim`、`terrains0.png` → 同名文件)。 + ③ **content 逐文件夹规则(用户裁定 1):** 根目录文件照常(`.jsonc` 压缩、`item.ts` 原样),且**根目录显式跳过清单过滤**(传根标记,不依赖「根目录当前没有 `.list.jsonc`」,将来出现也不过滤);`defaults/` 全量输出并压缩(显式豁免清单过滤);`maps` / `texture` / `animation` / `audio` / `font` 只保留被各自 `.list.jsonc` 引用的文件,未引用剔除;`.list.jsonc` 自身始终压缩输出;无清单的子文件夹全量输出。匹配口径 = 去注释 `JSON.parse` 后递归收集**所有字符串**,按「完整文件名」或「去扩展名主文件名」精确匹配(`empty` → `empty.jsonc`、`test` → `test.anim`、`terrains0.png` → 同名文件)。jsonc 压缩沿用 `minifyJSONC` 现有行为:只剥离**整行** `//` 注释(含系统标记行),不改其实现。 - ④ **修饰器删除口径(用户裁定 2):** 构建时删除**所有** `@shouldReplay(...)` / `@ignoreReplay(...)` 应用(当前 `@shouldReplay` 共 **69 处应用 / 20 个文件**,`@ignoreReplay` 无应用),**数据端与渲染端都删**(`buildData` + `buildClient` 各注入一次);用 **TypeScript AST**(`ts.createSourceFile` + `ts.canHaveDecorators` / `ts.getDecorators`)取装饰器节点范围后**逆序切片删除**,字符串 / 注释中的同名文本不删;仅处理项目自身 `.ts` / `.tsx`,跳过 `node_modules`,不处理 `.vue`;**不手动删 import**(交给依赖分析 tree-shaking)。插件 `apply: 'build'`,故 `pnpm dev` 行为不变。 + ④ **修饰器删除口径(用户裁定 2):** 构建时删除**所有** `@shouldReplay(...)` / `@ignoreReplay(...)` 应用(当前 `@shouldReplay` = **69 次应用 / 20 个 `.ts` 文件**;`packages-user/data-common/src/replay/func.ts` 另有 1 行注释含 2 处 `@shouldReplay` 文本,非应用;`@ignoreReplay` 无实际应用),**数据端与渲染端都删**(`buildData` + `buildClient` 各注入一次);用 **TypeScript AST**(`ts.createSourceFile` + `ts.canHaveDecorators` / `ts.getDecorators`)取装饰器节点范围后**逆序切片删除**,字符串 / 注释中的同名文本不删;仅处理项目自身 `.ts` / `.tsx`,跳过 `node_modules`,不处理 `.vue`;**不手动删 import**(交给依赖分析 tree-shaking)。插件 `apply: 'build'`,故 `pnpm dev` 行为不变。 - ⑤ **验证方式:** `script/build-plugin.test.ts` 扩展为「纯函数断言(删装饰器、字符串/注释不误删、带引号与括号的参数整段删除)+ **对照式**真实 `vite.build()`(同一 fixture 构建两次:不带插件的对照组产物必须含 `mota_replay_probe` 与 `shouldReplay` / `ignoreReplay` 标记,带插件的实验组必须不含这些标记且 `mota_replay_probe` 与方法名仍在)」;`script/build-resource.test.ts` 用临时 fixture 验证根文件、`defaults/` 全量、`maps` / `texture` / `font` 的保留与剔除、**无清单子文件夹全量**、`.list.jsonc` 压缩且剥离 `//` 注释、`item.ts` 原样;另加真实 `src/content` 的清单覆盖率检查(每个清单目录内非清单文件都必须被清单命中,防止过滤掉现有素材)、`build-game` 按函数切片的结构门禁(只断言导入绑定与注入位置,不绑定提议命名)、`build-resource.ts` 的「既有注释零删除」git diff 门禁,以及 `script/build-*.ts` 范围内的 `eslint`;命令一律 `cmd /c "pnpm exec ..."`,内联 `node -e` 只用单引号字面量。 + ⑤ **验证方式:** `script/build-plugin.test.ts` 扩展为「纯函数断言(删装饰器、字符串/注释不误删、带引号与括号的参数整段删除)+ **对照式双装饰器**真实 `vite.build()`(同一 fixture 构建两次:fixture **导出一个类**,两个方法分别带 `@shouldReplay` / `@ignoreReplay` 并各自返回不同唯一字面量 `mota_replay_probe` / `mota_ignore_probe`,`shouldReplay` / `ignoreReplay` 工厂在 fixture 内**本地定义、不导出**;**对照** `plugins: []` 产物必须含 `shouldReplay`、`ignoreReplay`、两个唯一字面量与两个方法名,**实验组**必须不含 `shouldReplay` / `ignoreReplay`,但两个唯一字面量与两个方法名**仍在**——两侧缺一即空断言)」;`script/build-resource.test.ts` 用临时 fixture 验证根文件、**根目录即使存在 `.list.jsonc` 也全量输出**、`defaults/` 全量、`maps` / `texture` / `font` 的保留与剔除、**无清单子文件夹全量**、`.list.jsonc` 压缩且剥离整行 `//` 注释、`item.ts` 原样;另加真实 `src/content` 的清单覆盖率检查(每个清单目录内非清单文件都必须被清单命中,防止过滤掉现有素材)、`build-game` 按函数切片的结构门禁(只断言导入绑定与注入位置,不绑定提议命名)、`build-resource.ts` **与 `build-plugin.ts`** 的「既有注释零删除」门禁(从 `git show HEAD:` 取注释行集合逐行比对,**不受暂存状态影响**,并校验基线注释行数下限防空洞),以及 `script/build-*.ts` 范围内的 `eslint`;命令一律 `cmd /c "pnpm exec ..."`,内联 `node -e` 只用单引号字面量。 ⑥ **已知边界(原样告知):** ① **不端到端运行全量 `build:game`**(渲染端仍有用户 WIP 的 stale import;09-21 已记录),验证只到单测 + 结构门禁 + 真实清单覆盖率;② 生产构建不再有录像安全检查装饰器,属预期结果;③ `index.html` 仍引用 `/styles.css`,本计划不产出、不改;④ `animation` / `audio` / `font` 目录当前无对应素材(清单为空或无文件),过滤不会影响现有产物。 - ⑦ **待批准命名(须用户确认):** OI-1(`removeReplayDecorators` / `replayDecoratorPlugin` / `name: 'mota-replay-decorator'`);OI-2(`LIST_FILE_NAME` / `DEFAULTS_DIRECTORY` / `readListNames` / `collectListedNames` / `isListedFile`);OI-3(新增测试文件 `script/build-resource.test.ts`、扩展 `script/build-plugin.test.ts`)。未获批前不落码。**若您给出其它命名,我会先修订本计划(含门禁命令与测试用例)再重新汇报**——门禁按行为与导出形态断言,不随改名自动跟随。 + ⑦ **待批准命名(须用户确认):** OI-1(`removeReplayDecorators` / `replayDecoratorPlugin` / `name: 'mota-replay-decorator'`,以及新增模块级常量 `REPLAY_DECORATORS` / `SCRIPT_EXTENSIONS` / `NODE_MODULES_SEGMENT`——若您不同意新增常量名,我改为函数内内联字面量);OI-2(`LIST_FILE_NAME` / `DEFAULTS_DIRECTORY` / `readListNames` / `collectListedNames` / `isListedFile`,以及 `processDirectory` 新增的 `isRoot` 参数);OI-3(新增测试文件 `script/build-resource.test.ts`、扩展 `script/build-plugin.test.ts`)。未获批前不落码。**若您给出其它命名,我会先修订本计划(含门禁命令与测试用例)再重新汇报**——门禁按行为、值标记与导出形态断言,不随改名自动跟随。 ⑧ **不在本增量:** 不改 `src/**`(含改动数据端源码或手删 import)、不改 `packages-user/**`、不改 `script/dev.ts`、不改 `package.json` / `dev.md`、不新增依赖、不改构建顺序与产物落点、不做特殊失败处理。 是否按本计划(09-22,最小范围)执行「任务 11 后续修复」——在 `script/build-plugin.ts` 新增基于 TypeScript AST 的 `@shouldReplay` / `@ignoreReplay` 删除插件并注入 `buildClient` 与 `buildData`(不手删 import)、在 `script/build-resource.ts` 增加「除 `defaults/` 外的素材子文件夹按各自 `.list.jsonc` 只保留被引用文件、`.list.jsonc` 自身压缩输出」的过滤、补两个测试文件与 SUMMARY;**仅改上述脚本与测试,其余文件一律不动**,并接受三项已知边界(不端到端跑全量 `build:game`、生产产物无录像安全检查、`/styles.css` 仍旧缺失)。 - AGENTS.md / D-03 / D-04 要求执行前与命名先获批;`.planning/config.json` 为 `mode: yolo`,故本关卡显式 `blocking-human` 且 `autonomous: false`。执行期含写操作(3 个脚本 + 2 个测试 + 1 个 SUMMARY),无删除。`texture/.list.jsonc` 已覆盖 `texture/` 下全部 48 个 PNG,`maps/.list.jsonc` 覆盖全部 5 个地图,故本次过滤不会减少现有素材——这一点由 Task 2 的清单覆盖率门禁证明。`@shouldReplay` 应用共 **69 处**(20 个 `.ts` 文件:`packages-user/data-base`、`data-system`、`src/data/plugin/shop`;另有 1 处仅出现在 `packages-user/data-common/src/replay/func.ts` 的注释中,不算应用),`@ignoreReplay` 当前无实际应用(仅定义与注释),插件需同时支持两者。 + AGENTS.md / D-03 / D-04 要求执行前与命名先获批;`.planning/config.json` 为 `mode: yolo`,故本关卡显式 `blocking-human` 且 `autonomous: false`。执行期含写操作(3 个脚本 + 2 个测试 + 1 个 SUMMARY),无删除。`texture/.list.jsonc` 已覆盖 `texture/` 下全部 48 个 PNG,`maps/.list.jsonc` 覆盖全部 5 个地图,故本次过滤不会减少现有素材——这一点由 Task 2 的清单覆盖率门禁证明。`@shouldReplay` = **69 次应用 / 20 个 `.ts` 文件**(`packages-user/data-base`、`data-system`、`src/data/plugin/shop`);`packages-user/data-common/src/replay/func.ts` 另有 **1 行注释含 2 处 `@shouldReplay` 文本**(非应用),同文件第 34 行注释含 `@ignoreReplay`;`@ignoreReplay` **当前无实际应用**(仅定义与注释),插件需同时支持两者。