diff --git a/.planning/phases/09-minimum-browser-runnable/09-21-PLAN.md b/.planning/phases/09-minimum-browser-runnable/09-21-PLAN.md index b3d3862..8ee0ab7 100644 --- a/.planning/phases/09-minimum-browser-runnable/09-21-PLAN.md +++ b/.planning/phases/09-minimum-browser-runnable/09-21-PLAN.md @@ -8,142 +8,90 @@ files_modified: - script/vite-plugin-data-isolation.ts - script/vite-plugin-data-isolation.test.ts - script/build-resource.ts - - script/build-resource.test.ts - script/build-game.ts - script/build-packages.ts - - script/build-lib.ts - - package.json - - index.html - - dev.md - .planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md autonomous: false requirements: [] estimate: - tokens: 36000 - raw_tokens: 36000 + tokens: 26000 + raw_tokens: 26000 tasks: 3 confidence: low must_haves: truths: - - "**本计划的第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md 硬要求,D-03 / D-93)**:Task 0 必须先汇报「任务 11 · 构建重构(适配新引擎)」的**要做什么 / 为什么 / 大致怎么做 / 影响范围**,必须列明:D-93 脚本重构与删除清单、D-94 隔离插件(specifier + 解析路径双路扫描 + `this.error` 终止 + 错误含命中 token,取代 `checkClientData`)、D-95 数据端库模式 ESM 与 `dist/data/data.js`、D-96 content 压缩(保留 `// --- SYSTEM PREFIX END --- //`)/ fontmin / 不分块 / 不 zip、D-97 dist 产物排除清单与 `LICENSE` 保留、`index.html` 去旧样式表 link、D-98 `package.json` script 调整 + `dev.md` 构建表同步、D-99 保留 `vue-tsc --noEmit`、D-100 移除 babel 二次压缩并微调 `manualChunks`、**新的构建顺序(数据端先于渲染端,二者互不阻塞)**、**数据端 ESM 为硬门禁(不放宽、不软通过)**、**当前数据端引用链(data-only,已清理)原文**、**渲染端两处 stale import 原文(`src/client/initialize.ts:14` / `src/client/plugin/shop/shop.tsx:9`,属用户 `src` WIP,用户将自行修复)**、**门禁分层(渲染端条件分支 + 数据端 ESM 必过)**、**baseline 快照与范围门禁口径**、全部拟新增命名(OI-1 / OI-2)、写操作边界(**不改 `src/**`**)、验证门禁方式与 `open_items`(OI-1..OI-12),然后必须**停下等待**;**回复「可以执行」即视为通过(本计划不因触发方式或 `mode: yolo` / `workflow.auto_advance` 自动放行)**;用户若对任一 `open_items` 给出指定,按指定执行。" - - "**D-93 / D-98 脚本重构与删除:** 重构 `script/build-game.ts` 与 `script/build-resource.ts`;**删除** `script/build-packages.ts` 与 `script/build-lib.ts`;从 `package.json` 的 `scripts` 中删除 `build:packages` / `build:lib`;`script/dev.ts` **不改**;`build:game` 保留(按 OI-6 调整其前置链,仍保留 `vue-tsc --noEmit`);**同步更新 `dev.md` 的构建命令表**,使其不再列出已删除的两个 script(W5)。" - - "**D-94 数据端独立包不得引用渲染端(取代 `checkClientData`):** 新增 Vite 插件在**数据端构建**时扫描模块图;命中渲染端模块(`@user/client-*`、`@user/entry-client`、`@motajs/render*`、`@motajs/client*`、`@motajs/system`、`@motajs/audio`、`@motajs/legacy-*`、`vue`、`vue/*`、`@vue/*`、`ant-design-vue`、`@ant-design/icons-vue`,以及对应的 `packages-user/client-*` / `packages/render*` / `packages/client*` / `packages/system` / `packages/audio` / `packages/legacy-*` / `src/client/**` / `node_modules/vue|@vue|ant-design-vue|@ant-design` 解析路径)即 `this.error(...)` **报错并终止**构建;**`moduleParsed` 的错误消息必须包含命中的禁用 token**(`matched: `,如 `@motajs/render` / `packages/render` / `packages/render-vue` / `packages/client` / `packages/system` / `packages/audio` / `packages/legacy-` / `packages-user/client-` / `src/client`),以便 `.catch` 分类器识别;数据端自身依赖(`@user/data-*`、`@motajs/common`、`@motajs/loader`、`@motajs/anon-tokyo`、`@motajs/types`、`@motajs/animate` 等)**不得**误判。**`checkClientData`、`getFileName`、`getFileSize`、`ClientDataLevel` 一并删除**。" - - "**D-95 数据端产物(与渲染端解耦):** 数据端构建改为 **Vite 库模式 + ESM**(`formats: ['es']`,不使用 iife),入口 `src/data.ts`,输出 `dist/data/data.js`;不生成 `data.iife.js` / `data.process.js`。**构建顺序改为:准备步骤清空 `dist` 一次 → 先构建数据端 → 再构建渲染端**;`buildClient` 与 `buildData` 均设 `emptyOutDir: false`(清空只在准备步骤做一次),因此**渲染端失败不会阻断数据端产物**。**当前实测数据端闭包无任何渲染端导入**(`src/data.ts` → `src/data/entry.ts` → `./constant` / `./initialize` / `./plugin` / `./event`,仅落到 `@user/data-*`、`@motajs/common`、`@motajs/loader`、`@motajs/anon-tokyo`),故 `dist/data/data.js` **必须产出且为 ESM**——这是**硬门禁**;`data.js` 缺失或非 ESM 一律判负(`bad.push` + 非零),**不存在 expected-pending 软通过、不存在 sentinel 豁免**。" - - "**D-96 content 处理:** 构建后 `src/content` → `dist/content`(路径去掉 `src` 前缀);`*.jsonc` 以 `JSON.parse + JSON.stringify` 压缩(去注释、**后缀仍为 `.jsonc`**);**压缩后必须保留且仅保留一行 `// --- SYSTEM PREFIX END --- //` 标记**,其后为压缩后的 JSON(理由:`PrefixedJSONCProcessor` 以该标记 `split` 且要求恰好 2 段,见 `packages-user/data-state/src/loader/jsoncProcessor.ts:14-18`;丢失标记会在运行期触发 error 66 并解析为 `{}`);字体用 fontmin(glyph 子集)压缩;**不分块**(不生成 `resource..h5data`)、**不 zip**。`build-resource` 改为从 `src/content` 读取素材。" - - "**D-97 旧模板内容与 dist 产物:** 不复制 `public/**`(`copyPublicDir: false`,`public` 整体删除由用户执行,本计划不删);不产出 `editor.html`、`_server`、`_docs`、`libs`、`logo.png`、`styles.css`、`main.js`、`script/template/启动服务.exe`;不打包 `source-code.zip`;不生成 `loadList.json`;不生成 `dist.zip`;**保留 `LICENSE`(LGPL v3)并复制到 `dist/LICENSE`**;`index.html` 去掉旧样式表 link。样式改由入口经 JS 打包引入属**用户侧 `src/**` 接线**(OI-5),本计划不改 `src/**`。" - - "**D-99 / D-100:** 构建仍前置 `vue-tsc --noEmit`(保留于 `build:game`);移除 `main.js` 二次压缩(babel)步骤与相关依赖引用;`manualChunks` 按实际依赖微调——`audio` chunk(`codec-parser` / `opus-decoder` / `ogg-opus-decoder` / `@wasm-audio-decoders/ogg-vorbis`)保留,`antdv` chunk 移除(全仓源码无 `ant-design-vue` / `@ant-design/icons-vue` 导入);`@vitejs/plugin-legacy` 维持现状(目标浏览器列表不变)。" - - "**写入边界(D-56 / AGENTS.md / 用户裁定「你改 src,我做脚本」):** 只允许写 `files_modified` 所列路径;**完全不修改 `src/**`**(含 `src/bootstrap.ts`、`src/data/**`、`src/client/**`,以及用户待修复的两处 stale import);不回退用户既有改动(`index.html`、`packages-user/client-modules/src/**`、`src/**` 等 WIP);不改数据端/渲染端源码、`public/**`、`vite.config.ts`、lockfile、`.planning/ROADMAP.md` / `STATE.md` / `09-CONTEXT.md` / 既有 PLAN 与 SUMMARY。`dev.md` 仅按 W5 更新构建命令表中的 `build:packages` / `build:lib` / `build:game` 三行。**范围门禁以 Task 0 的 baseline 快照为基线:任何相对 baseline 新增或被二次修改的 `src/**` / `09-CONTEXT.md` 路径一律判负,不得整体白名单放行 `src/`。**" - - "**命名须先获批(AGENTS.md / D-04):** 新增文件 `script/vite-plugin-data-isolation.ts`、导出 `dataIsolationPlugin`、插件 `name: 'mota-data-isolation'`、测试文件 `script/vite-plugin-data-isolation.test.ts`(OI-1);`script/build-resource.ts` 新导出 `buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats` 与测试文件 `script/build-resource.test.ts`(OI-2)。**类型一致性(W2):`IContentBuildOptions.chars` 为 `string`,`getAllChars` 相应返回 `Promise`(`[...set].join('')`),`Fontmin.glyph({ text })` 收到的是字符串。** 未获批前不得落码(测试文件按 AGENTS.md 免审批,但仍列入报备)。" - - "**验证可运行性(Nyquist)与门禁分层:** Task 1 以**真实 Vite `build()`** 驱动插件单测(渲染端 specifier(**未设 alias 的 `vue`**)被拒 / 渲染端解析路径(`packages-user/client-*`、`packages/render*`、`packages/client*`、`packages/system`、`packages/audio`、`packages/legacy-*`、`src/client`)被拒**且错误含命中 token** / 纯数据端通过并输出 ESM / 数据端路径不误判);Task 2 以 `minifyJSONC` / `buildContent` 单测 + 构建脚本结构断言(含**数据先于渲染的调用顺序**、**`dist` 只清空一次**、**两处 `emptyOutDir: false`**)+ 产物门禁 + **baseline 范围门禁**兜底;`cmd /c \"pnpm exec tsx script/build-game.ts\"` **必须实跑**(本仓 PowerShell 5.1 下裸 `pnpm` 会因 `pnpm.ps1` 的 `PSSecurityException` 失败,故一律经 `cmd /c` 调用),且**构建进程必须在有界时间内显式退出**(见 Task 2 的显式 `process.exit(...)` 要求)。**数据端 ESM 是独立硬门禁**:数据端先构建,`dist/data/data.js` 必须存在且含 `export`,否则 `bad.push` + 非零;**不因渲染端失败而跳过**。**渲染端产物门禁是条件分支**:`dist/index.html` 存在 → 断言不含 `styles.css` link;不存在 → 打印 BLOCKED 说明(渲染端因用户 `src/**` stale import 失败),而 `dist/data/data.js`、`dist/content/**`、`dist/LICENSE` 仍照常断言。**不存在任何无条件通过承诺。**" + - "**本计划第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md / D-03)**:先汇报「任务 11 · 构建重构(适配新引擎)」的要做什么 / 为什么 / 大致怎么做 / 影响范围,列明最小范围(只动 4 个 script 路径 + 删除 `build-packages.ts` + 可选插件测试)、确认的目标构建流程(准备 → 渲染端 → 数据端 → 隔离检测 → content → LICENSE)、**不改构建顺序**、**不做特殊失败处理(无 BLOCKED、无 baseline 范围门禁、无对外部文件/状态的检查)**、以及待批准命名(OI-1 / OI-2),然后停下等待「可以执行」。" + - "**渲染端(D-97 适配):** 以 `index.html` 为入口(Vite 默认,不显式设置 `rollupOptions.input`,`index.html` 本身不改),按 `vite.config.ts` 常规配置输出到 `dist`(常规 `index.html` + `assets/` / `chunks/`);`copyPublicDir: false`、`emptyOutDir: false`;不再拷贝旧模板产物(`editor.html` / `_server` / `_docs` / `libs` / `logo.png` / `styles.css` / `main.js` / `启动服务.exe`),不再做 `main.js` 的 babel 二次压缩。" + - "**数据端(D-94 / D-95):** 入口 `src/data.ts`,Vite 库模式 **ESM**(`formats: ['es']`);**入口产物固定为 `dist/data.js`(dist 根目录)**,数据端附属产物(chunk / asset)落在**数据端自己的子文件夹 `dist/data/`**,**不得与渲染端的 `assets/`、`chunks/` 混用**;`emptyOutDir: false`(不得清掉已产出的渲染端产物);注入 `dataIsolationPlugin()`。" + - "**隔离检测(D-94):** 新增 Vite 插件扫描数据端模块图(specifier + 解析后路径双路);命中渲染端模块即 `this.error(...)` 终止;错误消息含 `[mota-data-isolation]`、命中模块与 `matched: `;**取代**现有按体积的 `checkClientData` 启发式(`checkClientData` / `ClientDataLevel` / `getFileName` / `getFileSize` 一并删除)。" + - "**content(D-96):** `src/content` → `dist/content`;`*.jsonc` 压成**纯 JSON、去掉所有注释(含 `// --- SYSTEM PREFIX END --- //`)**,后缀仍 `.jsonc`;字体用 fontmin 压缩(无字体二进制时跳过并告警);**不分块、不 zip**;`build-resource` 从 `src/content` 读取素材。" + - "**最终 `dist` 根(D-97):** `index.html` + Vite 常规渲染产物 + `data.js` + `content/` + `LICENSE`;数据端附属在 `dist/data/`;**不产出** `source-code.zip` / `loadList.json` / `dist.zip` / 资源分块 `resource..h5data`。" + - "**写入边界:** 只写 `files_modified`;**不改** `script/build-lib.ts`、`package.json`、`index.html`、`dev.md`、`src/**`、`public/**`、`vite.config.ts`、lockfile、`packages*/**`、`script/dev.ts`;不回退用户既有 WIP。" + - "**命名须先获批(D-04):** `script/vite-plugin-data-isolation.ts` / `dataIsolationPlugin` / `name: 'mota-data-isolation'` / `script/vite-plugin-data-isolation.test.ts`(OI-1);`buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats`(OI-2)。未获批前不得落码。" + - "**验证可运行(Nyquist):** 插件单测以**真实 `vite.build()`** 证明「拒渲染端 import / 通过纯数据端」;两个脚本做结构断言(无 `checkClientData`、数据端 `formats: ['es']` 且入口 `data.js` 在根、附属产物在 `dist/data/`、jsonc 去注释、无 zip / 分块、无旧模板拷贝、数据子文件夹与渲染端分离);命令一律 `cmd /c \"pnpm exec ...\"`(PowerShell 5.1 下裸 `pnpm` 会因 `pnpm.ps1` 的 `PSSecurityException` 失败)。" artifacts: - path: script/vite-plugin-data-isolation.ts - provides: "新增 Vite 插件 `dataIsolationPlugin`:`enforce: 'pre'` + `resolveId` 拦渲染端 specifier,`moduleParsed` 按解析后绝对路径拦渲染端包(含传递依赖与 `src/client/**`),命中即 `this.error(...)` 终止数据端构建;错误消息含 `[mota-data-isolation]` + 命中模块 + **命中 token(`matched: `)** + importers(命名待批准,OI-1)" + provides: "新增插件 `dataIsolationPlugin`:`enforce: 'pre'` + `apply: 'build'`;`resolveId` 拦渲染端 specifier,`moduleParsed` 按解析后绝对路径兜底;命中即 `this.error(...)`,消息含 `[mota-data-isolation]` / 命中模块 / `matched: `(命名待批准,OI-1)" - path: script/vite-plugin-data-isolation.test.ts - provides: "运行期单测:真实 `vite.build()`(`configFile:false`、临时目录 fixture、`write:false`)验证「渲染端 specifier 被拒(未设 alias 的 `vue`)」「`packages-user/client-*` 解析路径被拒且错误含 token」「`src/client` 渲染目录被拒」「`packages/render` / `packages/render-vue` / `packages/system` / `packages/audio` / `packages/legacy-ui` 解析路径被拒且错误含各自 token」「纯数据端通过且产物为 ESM」「数据端路径不误判」;每个 `it` 前一行注释(AGENTS.md)" + provides: "运行期单测:真实 `vite.build()`(`configFile:false`、临时 fixture、`write:false`、lib ESM)验证拒渲染端 specifier(`vue`)、拒渲染端解析路径(`src/client` / `packages-user/client-*`)、通过纯数据端并产出 ESM;每个 `it` 前一行注释(AGENTS.md)" - path: script/build-resource.ts - provides: "改为 content 处理模块:`minifyJSONC`(保留 `// --- SYSTEM PREFIX END --- //` 标记 + `JSON.parse/stringify` 去注释)、`buildContent`(递归 `src/content` → `dist/content`:jsonc 压缩、fontmin glyph 子集压缩字体、其它素材原样拷贝;无字体二进制时跳过并告警)与结果/选项接口(`IContentBuildOptions.chars: string`;命名待批准,OI-2)" - - path: script/build-resource.test.ts - provides: "运行期单测:`minifyJSONC` 保留标记且压缩、去注释、非法 JSON 抛错、无标记纯 JSON 处理;`buildContent` 在临时目录完成 jsonc 压缩 + PNG 原样拷贝 + 无字体时跳过(`chars` 以 `string` 传入);每个 `it` 前一行注释" + provides: "重写为 content 处理模块:`minifyJSONC`(按行去掉 `//` 注释后 `JSON.parse` + `JSON.stringify`,输出纯 JSON)、`buildContent`(递归 `src/content` → `dist/content`:jsonc 压缩、fontmin glyph 子集、其它素材原样拷贝;无字体时跳过告警)与 `IContentBuildOptions`(`chars: string`)/ `IContentBuildStats`" - path: script/build-game.ts - provides: "重写编排:`emptyDir(dist)` 一次 → **先**数据端 ESM 库构建到 `dist/data`(注入 `dataIsolationPlugin`;严格签名分类;失败不阻断后续诊断)→ **后**渲染端构建到 `dist`(`copyPublicDir:false`、`emptyOutDir:false`、`manualChunks` 微调、移除 babel/zip/资源地图/资源分块/旧模板拷贝;失败打印 BLOCKED 但不覆盖数据端)→ `getAllChars`(返回 `Promise`)→ `buildContent` 处理 `dist/content` → 复制 `LICENSE` → 输出体积与数据端状态;Windows 路径用单反斜杠正则归一化;删除 `checkClientData` 等旧逻辑" + provides: "重写编排:清空/创建 `dist` 与 `_temp` 一次 → 渲染端(`index.html` 入口 → `dist`)→ 数据端(库模式 ESM,入口 `dist/data.js`、附属 `dist/data/`)→ `getAllChars` → `buildContent` → 复制 `LICENSE`;删除旧 pipeline(babel / zip / 分块 / 旧模板拷贝 / 资源地图 / `checkClientData`)" - path: script/build-packages.ts - provides: "**删除**(D-93 / D-98)" - - path: script/build-lib.ts - provides: "**删除**(D-93 / D-98)" - - path: package.json - provides: "删除 `build:packages` / `build:lib`;`build:game` 保留 `vue-tsc --noEmit`(按 OI-6 去掉 `declare.ts` 前置)" - - path: index.html - provides: "移除旧样式表 link(保留其余 head/body 内容与 `src/bootstrap.ts` module script)" - - path: dev.md - provides: "构建命令表同步:删除 `build:packages` / `build:lib` 两行,更新 `build:game` 说明(类型检查 + 渲染端游戏 + 数据端 ESM + content);不改其它章节" + provides: "**删除**(D-93 / D-98;`build-lib.ts` 不动)" - path: .planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md - provides: "本计划执行摘要:范围、插件扫描口径与错误行为(含命中 token)、content 压缩口径(含标记保留这一兼容性约束)、dist 产物清单与排除项、删除清单、`dev.md` 同步、`open_items` 最终处置、门禁结果(渲染端 BLOCKED 分支或成功分支)、数据端 ESM 硬门禁结果、渲染端两处 stale import 的当前状态、**D-97「样式经入口 JS 引入」不由本计划交付的显式声明(`src/styles.less` 全仓无 import 点;移除 link 后、用户接线前 `dist` 缺 `#game-draw` 居中样式,OI-5;不静默降级)**、baseline 范围门禁结果" + provides: "执行摘要:范围、插件扫描口径与错误行为、数据端产物落点(`dist/data.js` + `dist/data/`)、content 压缩口径(纯 JSON、无注释)、dist 产物与排除清单、删除清单、命名获批情形、门禁结果、`open_items` 处置" key_links: - - "`dataIsolationPlugin`(`script/vite-plugin-data-isolation.ts`)↔ `buildData` 的 `plugins` 数组(`script/build-game.ts`)—— D-94 的唯一执行点;取代 `checkClientData` 体积启发式" - - "插件 `this.error` 错误文本(含 `mota-data-isolation`、命中模块与**命中 token**)↔ `buildGame` 的数据端 `.catch` 分类 —— 归一化后(`String(reason)` 上把 Windows 反斜杠替换为 `/`,**源码单反斜杠正则**)同时匹配插件标识与已知渲染端 token(`@motajs/render` / `@motajs/client` / `@motajs/system` / `@motajs/audio` / `@motajs/legacy-` / `@user/client-` / `packages/render` / `packages/render-vue` / `packages/client` / `packages/system` / `packages/audio` / `packages/legacy-` / `packages-user/client-` / `src/client`)才标记为 **OI-11 回归诊断**;其它数据端错误标记为普通数据端失败;两条路径都使最终退出码非零(数据端 ESM 是硬门禁)" - - "`buildGame` 的调用顺序 `await buildData(` 先于 `await buildClient(` ↔ 数据端产物与渲染端解耦(D-95 / B1)—— 渲染端失败不会抹掉 `dist/data/data.js`;清空 `dist` 只在准备步骤执行一次,两次构建均 `emptyOutDir: false`" - - "`buildData` 的 `build.lib.fileName: 'data'` + `formats: ['es']` ↔ `dist/data/data.js` —— D-95 产物契约(硬门禁)" - - "`minifyJSONC` 的标记保留 ↔ `PrefixedJSONCProcessor.process`(`packages-user/data-state/src/loader/jsoncProcessor.ts:14-18`,`split(prefixSplit)` 要求恰好 2 段)—— 丢失标记会导致运行期 error 66 / `{}`" - - "`getAllChars` 返回 `Promise` ↔ `IContentBuildOptions.chars: string` ↔ `Fontmin.glyph({ text })` —— W2 类型一致性;不得让 `Set` 流入 `chars`" - - "`dist/content/**` ↔ `MotaDataLoader.resolveURL`(`packages-user/data-state/src/loader/loader.ts:74-82`,生产态为 `${BASE_URL}${url}`)与 `src/data/constant.ts` 的 `CORE_URL='content/core.jsonc'` / `src/client/constant.ts` 的 `CLIENT_CORE_URL='content/client.jsonc'` —— 目录落点契约" - - "`buildClient` 的 `copyPublicDir: false` ↔ D-97 —— 阻止旧 `public/**` 泄入 `dist`" - - "`package.json` 的 `build:game` ↔ `script/build-game.ts` / `vue-tsc --noEmit` —— D-98 / D-99" - - "`dev.md` 构建命令表 ↔ `package.json` scripts —— 删除 `build:packages` / `build:lib` 后文档不得再宣传这两个命令(W5)" - - "`index.html` 去 link ↔ 用户在 `src/**` 侧新增样式 import(OI-5,用户任务)—— D-97 样式链路由用户接线,本计划只负责移除 link" - - "Task 0 的 baseline 快照(`os.tmpdir()/gsd-09-21-baseline.json`,含 `git status --porcelain -uall` 路径哈希 + `git diff --name-only -- src`)↔ Task 2 的 baseline 范围门禁 —— 证明相对 baseline **零新增 / 零二次修改** `src/**` 与 `09-CONTEXT.md`(W4)" + - "`dataIsolationPlugin`(`script/vite-plugin-data-isolation.ts`)↔ `buildData` 的 `plugins` 数组(`script/build-game.ts`)—— D-94 的唯一执行点,取代 `checkClientData` 体积启发式" + - "`buildData` 的 `build.lib.fileName: 'data'` + `rollupOptions.output.entryFileNames: 'data.js'` ↔ `dist/data.js`(dist 根)—— D-95 入口产物契约" + - "`buildData` 的 `chunkFileNames` / `assetFileNames` 前缀 `data/` ↔ `dist/data/` —— 数据端附属产物与渲染端 `assets/`、`chunks/` 分离" + - "`buildClient` 不设 `rollupOptions.input` ↔ Vite 默认以 `index.html` 为入口 —— 渲染端输出常规 `dist/index.html` + `assets/` / `chunks/`" + - "`minifyJSONC` 的按行去注释 ↔ D-96「压成纯 JSON、去掉所有注释(含系统标记行)」;后缀仍 `.jsonc`" + - "`getAllChars` 返回 `Promise` ↔ `IContentBuildOptions.chars: string` ↔ `Fontmin.glyph({ text })` —— 类型一致,不得让 `Set` 流入" + - "`dist/content/**` ↔ `MotaDataLoader.resolveURL` 的 `content/**` 相对路径与 `src/data/constant.ts` 的 `CORE_URL='content/core.jsonc'` —— 目录落点契约" assumptions: - - "**content 全部为「前缀注释 + 严格 JSON」且共 23 个:** 实测 `src/content/**/*.jsonc` 共 **23** 个文件(`animation/.list.jsonc` / `audio/.list.jsonc` / `client.jsonc` / `core.jsonc` / 7 个 `defaults/*` / `enemy.jsonc` / `font/.list.jsonc` / `item.jsonc` / `maps/.list.jsonc` / 5 个 `maps/*.jsonc` / `texture/.list.jsonc` / `tile.jsonc`),每个都**恰好包含一次** `// --- SYSTEM PREFIX END --- //`;`PrefixedJSONCProcessor` 要求标记恰好出现一次且 `split` 后恰 2 段,故压缩必须保留该行。" - - "**生产态 content URL 以 `BASE_URL` 为根:** `MotaDataLoader.resolveURL` 在 `import.meta.env.DEV === false` 时返回 `${BASE_URL}${url}`;核心配置传 `content/core.jsonc` / `content/client.jsonc`,额外配置传 `content/${url}`,故 `dist/content/**` 是唯一正确落点。" - - "**字体目录当前无字体二进制:** `src/content/font/` 仅有 `.list.jsonc`(内容 `[\"testFont.ttf\"]`),`public/project/fonts/` 不存在;fontmin 无源可压,计划默认**跳过并告警**(OI-4),不得因缺字体阻断构建。" - - "**数据端闭包已清理且无渲染端导入(实测 2026-10-19,revision 4):** `src/data.ts` → `src/data/entry.ts`(`@user/data-state`、`./constant`、`@motajs/loader`、`./initialize`、`./plugin`、`./event`);`src/data/initialize.ts` / `src/data/enemy/**` / `src/data/hook/**` / `src/data/plugin/**` 等落到 `@user/data-common` / `@user/data-base` / `@user/data-system` / `@user/data-state` / `@motajs/common` / `@motajs/loader` / `@motajs/anon-tokyo`;**对 `src/data/**` 的检索未发现 `@motajs/render` / `@user/client-*` / `vue` / `ant-design-vue` 导入**。历史上的跨端引用链(`src/data/entry.ts` → `../constant` → `src/constant.ts` → `./client/ui/constant` → `@motajs/render`)已由用户的 `src` 重构**解决**:`src/constant.ts` 已删除,拆分为数据侧 `src/data/constant.ts`(仅 `@user/data-common`)与渲染侧 `src/client/constant.ts`。" - - "**渲染端仍有两处 stale import(用户 WIP,用户将修复 `src/**`):** `src/client/initialize.ts:14` 为 `} from '../constant';`(已删除文件,应为 `'./constant'`);`src/client/plugin/shop/shop.tsx:9` 为 `import { RENDER_WIDTH } from '@/constant';`(alias 指向已删除的 `src/constant.ts`,应为 `'@/client/constant'`)。因此**渲染端构建当前可能失败**;本计划不修 `src/**`,产物门禁必须接受条件分支。" - - "**`src/bootstrap.ts` 现指向工作树内的文件:** 实测 `src/bootstrap.ts:2` 为 `import { createClientCore } from './client/entry'`、`:4` 为 `import { LoadSceneUI } from './client/ui/load'`,且 `src/client/ui/load.tsx` 存在。渲染端构建不因「UI 文件被移动」而阻断;当前唯一已知的 `src` 侧阻断是上述两处 stale import。" - - "**插件系统已由用户接线:** `src/data/plugin/index.ts` / `src/data/plugin/list.ts`(`dataPlugins`)、`src/data/initialize.ts`(`MainDataPlugin`)、`src/client/plugin/**` 均存在(09-20 引擎侧 + 用户入口接线)。本计划只消费其存在性,不改其源码。" - - "**`src/content/item.ts` 是源码而非运行时素材:** `core.jsonc` 的 `itemEffectDir` 指向 `item.js`,但 `src/content` 中只有 `item.ts` 且当前无消费者(用户 WIP);本计划按 D-96 原样复制 content 目录,不改该数据/不改加载器(OI-8)。" - - "**`ant-design-vue` 已不再被源码引用:** 对 `packages/`、`packages-user/`、`src/` 的源码检索(排除 `node_modules`)未发现 `ant-design-vue` / `@ant-design/icons-vue` 导入(仅旧 `script/build-game.ts` 的 `manualChunks` 命中);`audio` 系依赖仍由 `@motajs/audio` 使用(`codec-parser` / `ogg-opus-decoder`)。`package.json` 的依赖条目仍保留、不删除(OI-10)。" - - "**`vite.config.ts` 提供 `base: './'` 与 `@motajs/*` / `@user/*` / `@dp/*` / `@/` 别名:** 数据端构建复用 `loadConfigFromFile` + `mergeConfig`,因此插件可在 `enforce: 'pre'` 阶段先拦未解析 specifier,再由 `moduleParsed` 按解析后路径兜底。**单测中不得给 `vue` 设 `resolve.alias`**(alias 解析可能先于 `enforce:'pre'` 插件),否则 specifier 守卫未被真正执行(W2)。" - - "**构建顺序与清空策略(revision 4):** 准备步骤 `ensureDir(dist)` + `emptyDir(dist)` **只执行一次**;随后**先数据端**(`dist/data`)、**后渲染端**(`dist`),两者均 `emptyOutDir: false`,故渲染端失败不会删除数据端产物;content 与 `LICENSE` 在两次构建之后执行,且**无论渲染端成败都会尝试产出**。数据端失败不阻断 content / `LICENSE`(用于诊断与门禁),但最终退出码非零。" - - "**`dev.md` 构建表已过期(W5):** `dev.md:28-37` 仍列出 `build:packages` / `build:lib`;本计划删除这两个 script 后必须同步该表(`build:game` 行保留并更新说明),否则文档与 `package.json` 不一致。" - - "**行号可能漂移:** 本计划引用行号为规划日实测;门禁以「文件存在 + 结构/行为断言」为准,不硬编码行号。" + - "**构建顺序维持现状:** 准备(清空/创建 `dist`、`_temp`)→ 渲染端 → 数据端;与现有 `build-game.ts` 的 client → data 顺序一致。数据端构建必须 `emptyOutDir: false`,否则会清掉渲染端产物。" + - "**`src/content` 共 23 个 `.jsonc`:** 均为「前缀注释(`// Type: ...` / `// --- SYSTEM PREFIX END --- //`)+ 严格 JSON」;注释只以整行 `//` 形式出现,故按行过滤可安全转成纯 JSON。" + - "**jsonc 输出为纯 JSON(无注释、无标记行):** 按本条执行;运行期 `PrefixedJSONCProcessor` 依赖标记行的适配不在本计划范围(不改 `src/**` / `packages-user/**`)。" + - "**字体:** `src/content/font/` 仅有 `.list.jsonc`(引用 `testFont.ttf`),无字体二进制;fontmin 无源可压时跳过并告警,不阻断构建。" + - "**渲染端可能构建失败:** `src/client/initialize.ts:14`(`'../constant'`)与 `src/client/plugin/shop/shop.tsx:9`(`'@/constant'`)仍指向已删除的 `src/constant.ts`,属用户 `src` WIP。按本条**不做特殊失败处理**:构建失败即失败,不由本计划的脚本层做 BLOCKED 分支;本计划的验证不实跑全量构建。" + - "**`build-lib.ts` 保持不动:** 仅删除 `build-packages.ts`;`package.json` 不改,故 `build:packages` script 会指向已删除文件(按本条接受)。" + - "**`manualChunks` 维持现状:** 当前 `antdv` + `audio` 两段不在本次适配范围,不改。" + - "**行号可能漂移:** 门禁以「文件存在 + 结构/行为断言」为准,不硬编码行号。" prohibitions: - - "不得在用户回复通过 Task 0 关卡之前执行本计划的任何任务、或派发任何执行子代理。" - - "**未经批准的命名一律不得使用:** `script/vite-plugin-data-isolation.ts` / `dataIsolationPlugin` / `mota-data-isolation` / `script/vite-plugin-data-isolation.test.ts`(OI-1)与 `buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats` / `script/build-resource.test.ts`(OI-2)必须先经 Task 0 报备并获批准;未获批前不得落码。" - - "**content 压缩必须保留标记行:** 不得只输出 `JSON.stringify` 结果而丢掉 `// --- SYSTEM PREFIX END --- //`;不得改后缀(仍为 `.jsonc`)。此为本计划对 D-96 的**兼容性补充**(OI-3),不是范围扩张。" - - "**构建顺序不得倒置:** 不得先构建渲染端再构建数据端;不得在 `buildClient` 或 `buildData` 内使用 `emptyOutDir: true`(会删除已产出的 `dist/data`);不得让渲染端失败以任何方式跳过后面的 content / `LICENSE` / 产物断言。" - - "**数据端门禁不得软化:** 不得为 `dist/data/data.js` 缺失写 expected-pending 软通过、不得写 sentinel 豁免、不得以「渲染端失败」为由跳过数据端 ESM 断言。严格签名只用于把数据端错误标记为 OI-11 回归诊断,最终退出码仍为非零。**`buildGame` 必须在每个分支显式 `process.exit(...)` 终止进程**(先 flush stdout;不得依赖事件循环自然退出——Vite/esbuild 的 handle 会让进程在渲染端构建失败后存活至超时)。" - - "**不得删除 `public/**`:** D-97 的「整体删除」由用户执行;本计划只通过 `copyPublicDir: false` 阻止其进入 `dist`。" - - "**写入边界(D-56 + 用户裁定「你改 src,我做脚本」):** 只允许写 `files_modified`;**不得修改 `src/**` 的任何文件**(包括 `src/bootstrap.ts`、`src/data/**`、`src/client/**`、两处 stale import);不得改 `vite.config.ts`、`packages/**`、`packages-user/**`、`public/**`、`script/dev.ts`、`script/declare.ts`、`script/types.ts`、`script/utils.ts`、lockfile;不得回退用户在工作树中的任何既有改动。`dev.md` 只允许改构建命令表三行。" - - "**`.planning/**` 边界:** 不得改 `ROADMAP.md` / `STATE.md` / `09-CONTEXT.md` / 任何既有 PLAN 或 SUMMARY;只允许新增 `09-21-SUMMARY.md`(与 09-19 / 09-20 的既有做法一致)。范围门禁以 baseline 快照为准,**不得整体白名单放行 `src/` 或 `09-CONTEXT.md`**。" - - "**不新增依赖:** 不得改 `package.json` 的 `dependencies` / `devDependencies`、不得改 lockfile;`vite-plugin-dts` 虽因 `build-lib.ts` 删除而失去使用点,也**不删除**(OI-10)。" - - "**不得改既有注释 / jsDoc(AGENTS.md):** 重写 `build-game.ts` / `build-resource.ts` 时保留/更新其内部注释属重写范围,但不得改动其它文件的既有注释;新增注释须遵循 dev.md。" - - "**不得以全仓门禁代替:** 不设 `pnpm check:type` / `pnpm build` / 全量 `pnpm test:ci` 作为通过条件(用户 WIP 存在历史类型错误)。" - - "**不得为通过门禁而修改范围外文件:** 数据端若出现严格签名,只能记录 OI-11 回归诊断;渲染端若因用户两处 stale import 或其它 WIP 失败,也只能记录完整错误与归因;不得修 `src/**` 或包源码。" - - "**不得伪造条件门禁:** 渲染端失败时不得手写 `dist/index.html` 以骗过产物门禁;不得把 BLOCKED 分支描述为「通过」;不得给出任何无条件通过承诺。注意:content 与 `LICENSE` 是构建脚本**正常产出**的,不是手写补的。" - - "**不得使用双反斜杠正则做路径归一化:** 源码必须是单反斜杠正则(`String(reason)` 上把 Windows 反斜杠替换为 `/`),不得写成四反斜杠(在 Windows 上会把两个反斜杠误当一次匹配,导致分类失效)。" - - "**不得把被删除的旧 pipeline 标识符写进注释或字符串:** Task 2 的结构门禁对 `transformAsync` / `archiver` / `compressing` / `checkClientData` / `ClientDataLevel` / `getFileName` / `getFileSize` / `generateResourceJSON` / `splitResource` / `source-code.zip` / `loadList.json` / `dist.zip` / `data.iife.js` / `_temp` 做负向检查,残留会被判负;并对**裸 `iife`** 做负向检查——`formats: ['iife']`、`data.iife.js`、甚至注释里解释「不用 iife」的说明都不得出现在 `script/build-game.ts` 中(要与该门禁口径一致,改写说明请放在本 PLAN / SUMMARY,不写进源码注释)。" + - "不得在用户回复通过 Task 0 关卡之前执行任何任务或派发执行子代理。" + - "**未经批准的命名一律不得使用:** `script/vite-plugin-data-isolation.ts` / `dataIsolationPlugin` / `mota-data-isolation` / `script/vite-plugin-data-isolation.test.ts`(OI-1)与 `buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats`(OI-2)。" + - "**构建顺序不得倒置:** 必须先渲染端、后数据端;`buildData` 必须 `emptyOutDir: false`(不得清掉已产出的渲染端产物)。" + - "**数据端附属产物不得混入渲染端目录:** 不得让 chunk / asset 落到 `assets/` 或 `chunks/`;入口必须是 `dist/data.js`(根),不得改成 `dist/data/data.js`。" + - "**jsonc 必须去净注释:** 不得保留 `// --- SYSTEM PREFIX END --- //` 或 `// Type: ...`;不得改后缀(仍 `.jsonc`)。" + - "**不得保留旧 pipeline:** 不得把 `checkClientData` / `ClientDataLevel` / `getFileName` / `getFileSize` / `generateResourceJSON` / `splitResource` / `transformAsync` / `archiver` / `compressing` / `source-code.zip` / `loadList.json` / `dist.zip` / 旧模板文件名写进源码(含注释与字符串)。" + - "**不得改范围外文件:** 不改 `script/build-lib.ts`、`package.json`、`index.html`、`dev.md`、`src/**`、`public/**`、`vite.config.ts`、lockfile、`packages*/**`、`script/dev.ts`;不改既有注释 / jsDoc(重写两个脚本自身的注释属重写范围)。" + - "**不得新增依赖、不改 lockfile。**" + - "**不得引入特殊失败处理:** 不写 BLOCKED 分支、不写 baseline / 范围门禁、不检查外部文件或仓库状态;构建失败即以非零退出并打印错误。" + - "**不得伪造门禁:** 不得手写 `dist` 产物或用手工文件冒充构建结果。" open_items: - - "**OI-1(插件文件与导出命名):** 提议新增 `script/vite-plugin-data-isolation.ts`,导出 `dataIsolationPlugin(): Plugin`,插件 `name: 'mota-data-isolation'`;测试文件 `script/vite-plugin-data-isolation.test.ts`。备选命名:`script/build-plugin-data-isolation.ts` / `forbidRenderDependencies` / `renderIsolationPlugin`。**待用户报备批准**(D-04;测试文件按 AGENTS.md 免审批,但仍一并报备)。" - - "**OI-2(content 模块命名与类型):** 提议 `script/build-resource.ts` 导出 `buildContent(options: IContentBuildOptions): Promise`、`minifyJSONC(text: string): string`,接口 `IContentBuildOptions`(`sourceDir` / `outputDir` / **`chars: string`**)与 `IContentBuildStats`(`files` / `jsonc` / `fonts`);测试文件 `script/build-resource.test.ts`。**W2 类型一致性:`getAllChars` 返回 `Promise`,`chars` 不做 `Set`。** 备选:`processContent` / `compressJSONC`。**待批准**(dev.md 要求对象入参声明 interface)。" - - "**OI-3(jsonc 标记保留与 `Type:` 注释丢失):** 提议压缩输出 = `// --- SYSTEM PREFIX END --- //` 一行 + 压缩 JSON;`// Type: ...` 等标记前注释**全部丢失**(含 `texture/.list.jsonc` 的类型指针)。D-96 说「去注释」,但加载器要求保留标记,故标记必须保留;`Type:` 指针丢失是否可接受**请用户确认**。" - - "**OI-4(字体来源):** `src/content/font/` 仅 `.list.jsonc`(引用 `testFont.ttf`),无字体二进制;提议「无字体文件时跳过 fontmin 并告警」。若用户要求压缩特定字体,请指定字体文件落点。" - - "**OI-5(样式引入落点 = 用户侧):** D-97 要求样式走 JS 打包,但 `src/styles.less` 当前无导入点;按用户裁定「你改 src,我做脚本」,**本计划不改 `src/**`**,样式 import 由用户后续在 `src/**` 侧添加。本计划只移除 `index.html` 的旧样式表 link;在用户接线前,`#game-draw` 居中样式可能缺失(记入 SUMMARY)。" - - "**OI-6(`build:game` 前置链):** 现 `build:game` = `tsx script/declare.ts && vue-tsc --noEmit && tsx script/build-game.ts`,而 `script/declare.ts` 读取 `public/project/*`(D-97 将删除 `public/**`)。提议改为 `vue-tsc --noEmit && tsx script/build-game.ts`(保留 D-99 的类型检查),**不删除** `script/declare.ts` 文件。**待确认**。" - - "**OI-7(`manualChunks` / legacy 插件):** 提议移除 `antdv` chunk、保留 `audio` chunk、`@vitejs/plugin-legacy` 维持现状(含目标浏览器列表与 `renderModernChunks:false`)。若用户希望移除 legacy 或调整目标,请在 Task 0 指明。" - - "**OI-8(`itemEffectDir` 缺文件):** `core.jsonc` 引用 `item.js`,`src/content` 仅有 `item.ts` 且无消费者。本计划原样复制 content,不生成 `item.js`、不改数据;**请确认该缺口由用户后续处理**。" - - "**OI-9(全量构建的可验证性 = 数据端硬门禁 + 渲染端条件分层):** 当前实测:**数据端闭包无渲染端导入**,`dist/data/data.js` **必须产出并断言 ESM(硬失败,无 pending 豁免)**;**渲染端仍有两处 stale import**(`src/client/initialize.ts:14` / `src/client/plugin/shop/shop.tsx:9`,用户 WIP),渲染端构建可能失败。门禁分层:数据端先构建且独立断言;渲染端成功 → 追加渲染产物断言;渲染端失败 → 打印 BLOCKED(不出 `ARTIFACT GATE FAILED`,数据端断言不受影响)。**待确认**。" - - "**OI-10(遗留依赖与文件):** `script/types.ts` 的 `RequiredData` / `RequiredIconsData` 及 `vite-plugin-dts`、`@babel/*`、`archiver`、`compressing` 等依赖在重构后可能失去使用点,但**不删除**(避免 lockfile/依赖面变更)。**请确认**保留。" - - "**OI-11(数据端跨端引用 · 历史问题已解决):** 历史上的链路 `src/data/entry.ts` → `../constant` → `src/constant.ts` → `./client/ui/constant` → `@motajs/render` **已由用户的 `src` 重构解决**:`src/constant.ts` 已删除,数据侧改为 `src/data/constant.ts`(仅 `@user/data-common`),渲染侧为 `src/client/constant.ts`。本计划保留严格签名分类作为回归诊断:若触发,说明 `src` 又被改回跨端引用,记为 **OI-11 回归**并**按数据端失败非零退出**(不再有 pending 放行)。**待用户确认链路稳定**。" - - "**OI-12(渲染端两处 stale import · 用户待修):** `src/client/initialize.ts:14`(`'../constant'` → 应为 `'./constant'`)与 `src/client/plugin/shop/shop.tsx:9`(`'@/constant'` → 应为 `'@/client/constant'`)指向已删除的 `src/constant.ts`。按用户裁定「你改 src,我做脚本」,**本计划不改 `src/**`**;请用户确认修复时点。修复前渲染端构建失败属预期,产物门禁走 BLOCKED 分支(数据端 ESM 仍硬断言)。" + - "**OI-1(插件命名):** 提议新增 `script/vite-plugin-data-isolation.ts`,导出 `dataIsolationPlugin(): Plugin`,插件 `name: 'mota-data-isolation'`;测试文件 `script/vite-plugin-data-isolation.test.ts`。备选:`script/build-plugin-data-isolation.ts` / `forbidRenderDependencies`。**待批准**(D-04;测试文件按 AGENTS.md 免审批,仍一并报备)。" + - "**OI-2(content 模块命名):** 提议 `script/build-resource.ts` 导出 `buildContent(options: IContentBuildOptions): Promise` 与 `minifyJSONC(text: string): string`;接口 `IContentBuildOptions`(`sourceDir` / `outputDir` / **`chars: string`**)、`IContentBuildStats`(`files` / `jsonc` / `fonts`)。备选:`processContent` / `compressJSONC`。**待批准**。" --- -Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎)**。 +Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎)**,最小范围,只动构建脚本。 -**要解决的问题:** 现有 `script/build-game.ts` / `script/build-resource.ts` 仍面向**旧引擎 + 旧模板**:构建产物依赖 `public/project`(旧 `data.js` / `icons.js` / `floors/` / `materials/` 等)、数据端产出 iife、按体积启发式 `checkClientData` 猜测「数据端是否引用了渲染端」、`main.js` 二次 babel 压缩、资源按 2MB 分块并 zip 成 `resource..h5data`、拷贝 `editor.html` / `_server` / `_docs` / `libs` / `logo.png` / `styles.css` / `main.js` / `启动服务.exe`、打包 `source-code.zip`、生成 `loadList.json`、再整体压成 `dist.zip`。新引擎的数据落在 `src/content`(jsonc + 贴图 + 字体目录),加载器按 `content/**` 相对路径读取,渲染端/数据端已分家。继续沿用旧脚本既无法跑通新数据,也无法保证「数据端独立包不得引用渲染端」。 +**要解决的问题:** 现有 `script/build-game.ts` / `script/build-resource.ts` 面向旧引擎与旧模板:数据端产出 iife、用体积启发式 `checkClientData` 猜「是否引用了渲染端」、`main.js` 做 babel 二次压缩、资源按 2MB 分块并 zip、拷贝 `editor.html` / `_server` / `_docs` / `libs` / `logo.png` / `styles.css` / `启动服务.exe`、打包 `source-code.zip`、生成 `loadList.json`、再整体压 `dist.zip`。新引擎的数据落在 `src/content`,渲染端与数据端分家。 -**目标(D-93..D-100):** -1. **D-94** 用 Vite 插件扫描数据端模块图,命中渲染端模块即报错终止(错误含命中 token),**取代** `checkClientData`。 -2. **D-95** 数据端改为库模式 ESM,入口 `src/data.ts`,产物 `dist/data/data.js`;**数据端先于渲染端构建,二者互不阻塞**。 -3. **D-96** `src/content` → `dist/content`:jsonc 压缩(**保留系统标记行**)、fontmin 压缩字体、**不分块 / 不 zip**;`build-resource` 改为从 `src/content` 读取。 -4. **D-97** dist 不再携带旧模板内容;保留 `LICENSE`;`index.html` 去掉旧样式表 link,样式由用户在 `src/**` 侧经 JS 引入(OI-5)。 -5. **D-93 / D-98** 删除 `build-packages.ts` / `build-lib.ts` 及对应 npm script;`build:game` 调整并保留 `vue-tsc --noEmit`(D-99);同步 `dev.md` 构建表(W5)。 -6. **D-100** 移除 `main.js` 二次压缩;`manualChunks` 按实际依赖微调(`antdv` 已无源码引用 → 移除;`audio` 保留);legacy 插件维持现状。 +**确认的目标构建流程(不得改序):** +1. 准备:清空 / 创建 `dist`、`_temp`。 +2. 渲染端:以 `index.html` 为入口(`index.html` 本身不改),按 Vite 常规配置输出到 `dist`(常规 `index.html` + `assets/` / `chunks/`)。 +3. 数据端:入口 `src/data.ts`,库模式 **ESM**;入口产物固定 **`dist/data.js`(dist 根)**;附属产物放**数据端自己的子文件夹**(如 `dist/data/`),**不得与渲染端 `assets/`、`chunks/` 混用**。 +4. 隔离检测:构建数据端时用 Vite 插件扫描模块图,命中渲染端即 `this.error` 终止;**取代** `checkClientData`。 +5. 内容:`src/content` → `dist/content`;jsonc 压成**纯 JSON、去掉所有注释(含系统标记行)**,后缀仍 `.jsonc`;字体 fontmin 压缩;**不分块、不 zip**。 +6. `LICENSE` 复制到 `dist`。 +7. 不产出旧模板内容与 `source-code.zip` / `loadList.json` / `dist.zip`;移除 `main.js` babel 步骤。 +8. 最终 `dist` 根:`index.html` + Vite 常规渲染产物 + `data.js` + `content/` + `LICENSE`;数据端附属在 `dist/data/`。 -**关键兼容性约束(本计划对 D-96 的实现补充):** 所有 `src/content/**/*.jsonc`(**实测 23 个,每个恰好一次标记**)都是「前缀注释 + 严格 JSON」,运行期由 `PrefixedJSONCProcessor` 以 `// --- SYSTEM PREFIX END --- //` **split** 并要求恰好 2 段(`packages-user/data-state/src/loader/jsoncProcessor.ts:14-18`)。因此「`JSON.parse + JSON.stringify` 去注释」**必须保留该标记行**;否则运行期触发 error 66、配置解析为 `{}`。`// Type: ...` 等标记前注释会按 D-96 丢失(OI-3 待确认)。 +**约束:** 不改构建顺序;只做上述适配;不做特殊失败处理(无 BLOCKED、无 baseline/范围门禁、无对外部文件或状态的检查);构建失败就失败。 -**当前实测状态(revision 4,2026-10-19):** -- **数据端已清理且无渲染端导入:** `src/data.ts` → `src/data/entry.ts` → `./constant` / `./initialize` / `./plugin` / `./event`,闭包落到 `@user/data-*`、`@motajs/common`、`@motajs/loader`、`@motajs/anon-tokyo`;`src/constant.ts` 已删除,对 `src/data/**` 的检索未发现 `@motajs/render` / `@user/client-*` / `vue`。因此 `dist/data/data.js` **必须产出且为 ESM**,这是**硬门禁**(缺失 / 非 ESM → `bad.push` + 非零,无软通过)。 -- **渲染端仍有两处 stale import(用户 WIP,用户将修复 `src/**`):** `src/client/initialize.ts:14`(`'../constant'`)、`src/client/plugin/shop/shop.tsx:9`(`'@/constant'`)。渲染端构建**当前可能失败**,因此渲染端产物门禁是**条件分支**(存在则断言、缺失则打印 BLOCKED),但**不影响数据端 ESM 的硬断言**。 - -**大致解决方案(本计划 = 09-21):** -- **Task 0(`checkpoint:decision@blocking-human`)= 预执行汇报关卡**:汇报要做什么 / 为什么 / 大致怎么做 / 影响范围(含插件扫描口径与命中 token、content 压缩口径与标记保留理由、dist 产物清单、删除清单、`manualChunks` 调整、`dev.md` 同步、命名报备、验证方式、当前数据端链路与两处渲染端 stale import、**数据先行的构建顺序与硬门禁**、baseline 范围门禁),并**捕获 baseline 快照**(写入系统临时目录,不写仓库),停下等待「可以执行」。 -- **Task 1(`tracer`)= 分离插件端到端最小切片**:新增 Vite 插件并在**真实 `vite.build()`** 的临时 fixture 上验证——未设 alias 的 `vue` 被拒、渲染端解析路径(含 `packages-user/client-*` / `packages/render*` / `packages/client*` / `packages/system` / `packages/audio` / `packages/legacy-*` / `src/client`)被拒**且错误含命中 token**、纯数据端通过且产物为 ESM、数据端路径不误判。这是本增量唯一的全新机制,先用运行期测试把「扫描 + 终止 + 可分类错误」跑通。 -- **Task 2 = 构建编排重写与收口**:重写 `build-resource.ts`(content 压缩/拷贝/字体,`chars: string`)与 `build-game.ts`(**清空一次 → 数据端 ESM(注入插件,严格签名分类)→ 渲染端(条件 BLOCKED)→ content → LICENSE**,Windows 路径用单反斜杠正则归一化);删除旧脚本;调整 `package.json`、`index.html` 与 `dev.md` 构建表;补 `build-resource.test.ts`;实跑全量构建并按硬数据门禁 + 条件渲染门禁 + baseline 范围门禁收口;写 `09-21-SUMMARY.md`。 - -**为何 `depends_on: [09-19, 09-20]`(W1):** 本计划消费 09-19 产出的 `src/content` 数据落点/素材与 09-20 的插件系统(`src/data/plugin/**` / `src/client/plugin/**` 由用户接线),二者的产物契约必须在场;本计划不修改其源码,只按契约构建与打包,因此 wave 取 `max(16, 17) + 1 = 18`。 - -**API coverage:** `detected:false`。**`No external API integration: 本计划为本地构建流程重构,不涉及任何外部 API。`** - -**修改方式声明(AGENTS.md):** 本计划执行含**文件写入与删除**(新增插件与测试、重写两个构建脚本、删除两个旧脚本、改 `package.json` / `index.html` / `dev.md` 构建表、写 SUMMARY),将由**派发子代理(`gsd-executor`)执行**,非内联执行;除 `files_modified` 所列路径外无任何写入,**`src/**` 零改动**。 +**修改方式声明(AGENTS.md):** 本计划执行含文件写入与删除,由**派发子代理(`gsd-executor`)执行**,非内联执行;除 `files_modified` 外无任何写入。 @@ -157,280 +105,198 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎 @.planning/STATE.md @.planning/phases/09-minimum-browser-runnable/09-CONTEXT.md @AGENTS.md -@dev.md @script/build-game.ts @script/build-resource.ts @script/build-packages.ts -@script/build-lib.ts -@package.json -@index.html @vite.config.ts +@index.html @src/data.ts -@src/data/entry.ts -@src/data/constant.ts -@src/client/constant.ts -@src/client/initialize.ts -@src/client/plugin/shop/shop.tsx -@src/bootstrap.ts +@src/content/core.jsonc @packages-user/data-state/src/loader/jsoncProcessor.ts -@packages-user/data-state/src/loader/loader.ts - Task 0: 预执行汇报关卡 —— 汇报「任务 11 · 构建重构(适配新引擎)」的要做什么 / 为什么 / 大致怎么做 / 影响范围(含数据先行顺序、数据端硬门禁、渲染端条件分支、baseline 范围门禁),等待用户回复「可以执行」 - `script/build-game.ts`(703 行)与 `script/build-resource.ts`(225 行)仍为旧引擎实现;`script/build-packages.ts` 与 `script/build-lib.ts` 存在;`package.json` scripts 含 `build:packages` / `build:lib` / `build:game`(`build:game` 含 `tsx script/declare.ts` 与 `vue-tsc --noEmit`);`index.html` 含 ``;`dev.md:28-37` 仍列出 `build:packages` / `build:lib`;`src/content/**` 共 **23** 个 `.jsonc`(每个恰好一个 `// --- SYSTEM PREFIX END --- //`)与 `texture/*.png`;`src/content/font/` 无字体二进制;`src/data.ts` 存在并导出 `createCoreState`;**数据端闭包已清理且无渲染端导入**:`src/constant.ts` 不存在,`src/data.ts` → `src/data/entry.ts`(`from './constant'` 等)→ `src/data/constant.ts`(仅 `@user/data-common`);**渲染端有两处 stale import**:`src/client/initialize.ts:14`(`'../constant'`)与 `src/client/plugin/shop/shop.tsx:9`(`'@/constant'`),属用户 `src` WIP;插件系统已接线(`src/data/plugin/list.ts` / `src/client/plugin/list.ts` / `src/data/initialize.ts`);`src/bootstrap.ts` 导入 `./client/entry` 与 `./client/ui/load`(后者存在);`script/vite-plugin-data-isolation.ts` / `script/vite-plugin-data-isolation.test.ts` / `script/build-resource.test.ts` **尚不存在**。 - (只读汇报,不修改仓库文件;仅在系统临时目录写入 baseline 快照) + Task 0: 预执行汇报关卡 —— 汇报最小范围、目标构建流程与待批准命名,等待「可以执行」 + `script/build-game.ts` / `script/build-resource.ts` 仍为旧引擎实现(含 `checkClientData`、babel、zip、资源分块、旧模板拷贝);`script/build-packages.ts` 与 `script/build-lib.ts` 均存在;`index.html` 含 `/styles.css` link 与 `/src/bootstrap.ts` module script;`vite.config.ts` 提供 `base: './'` 与 `@motajs/*` / `@user/*` / `@dp/*` / `@/` 别名;`src/content/**` 共 23 个 `.jsonc`、`src/content/font/` 无字体二进制;`script/vite-plugin-data-isolation.ts` 与测试文件尚不存在。 + (只读汇报,不修改任何文件) - - .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(**D-93 / D-94 / D-95 / D-96 / D-97 / D-98 / D-99 / D-100**;D-03 / D-04 / D-56) - - script/build-game.ts(全文:`buildClient:40` / `buildData:91` / `checkClientData:212` / `getAllChars:254` / `generateResourceJSON:310` / `buildGame:329`,含 babel、archiver、compressing、资源分块、旧模板拷贝、`source-code.zip`、`loadList.json`、`启动服务.exe`、`dist.zip`) - - script/build-resource.ts(全文:`splitResource:123` 从 `public/project/{animates,fonts,images,sounds,tilesets,autotiles,materials}` 读取并按 2MB 分块 zip 成 `resource..h5data`) - - script/build-packages.ts / script/build-lib.ts(待删除);script/types.ts / script/utils.ts(`formatSize`) - - package.json(只读:scripts、依赖面);index.html(只读:`` 与 `/src/bootstrap.ts` module script);dev.md(只读:构建命令表 `dev.md:28-37`) - - src/data.ts / src/data/entry.ts / src/data/constant.ts / src/client/constant.ts(`CORE_URL='content/core.jsonc'`、`CLIENT_CORE_URL='content/client.jsonc'`、`RENDER_WIDTH`/`RENDER_HEIGHT`) - - src/client/initialize.ts(只读:`:14` stale import)与 src/client/plugin/shop/shop.tsx(只读:`:9` stale import) - - src/data/plugin/list.ts / src/client/plugin/list.ts / src/data/initialize.ts(插件系统接线现状,只读) - - packages-user/data-state/src/loader/loader.ts:70-142(`resolveURL` 生产态 = `${BASE_URL}${url}`)与 packages-user/data-state/src/loader/jsoncProcessor.ts:4-28(标记 split 必须恰好 2 段) - - src/content/**(**23 个 jsonc**:`client.jsonc` / `core.jsonc` / `enemy.jsonc` / `item.jsonc` / `tile.jsonc` / `defaults/*` / `maps/*` / `texture/.list.jsonc` / `font/.list.jsonc` / `animation/.list.jsonc` / `audio/.list.jsonc`)与 src/styles.less - - vite.config.ts(`base: './'`、`@motajs/*` / `@user/*` / `@dp/*` / `@/` 别名);`git status --porcelain -uall`(用户 WIP 面) - - AGENTS.md(执行前须获批、命名须先反馈、修改方式须预先声明、禁改既有注释);dev.md(命名规则、对象类型须声明 interface、模块无副作用、非空判断约定、jsDoc 规范) + - .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(D-93 / D-94 / D-95 / D-96 / D-97 / D-98 / D-99 / D-100;D-03 / D-04) + - script/build-game.ts(`buildClient` / `buildData` / `checkClientData` / `getAllChars` / `generateResourceJSON` / `buildGame`) + - script/build-resource.ts(`splitResource` 分块 zip 逻辑);script/build-packages.ts(待删除) + - index.html(`` 与 `/src/bootstrap.ts`);vite.config.ts(别名 / `base`) + - src/data.ts;src/content/core.jsonc 与 src/content/font/.list.jsonc(注释 + 严格 JSON 形态) + - AGENTS.md(执行前须获批、命名须先反馈、修改方式须预先声明) - **停下并向用户汇报,等待用户回复「可以执行」后才继续;汇报本身只读(不修改仓库文件),但必须先捕获 baseline 快照。** + **停下并向用户汇报,等待回复「可以执行」后才继续;汇报本身只读。** - ① **要解决的问题:** 旧构建脚本面向旧引擎与旧模板(依赖 `public/project`、数据端 iife、体积启发式、`main.js` 二次压缩、资源分块 zip、旧模板拷贝、源码 zip、`loadList.json`、`dist.zip`),无法适配新引擎的 `src/content` 数据与双端分离。 + ① **要解决的问题:** 旧构建脚本面向旧引擎(iife 数据端、体积启发式、babel 二次压缩、资源分块 zip、旧模板拷贝、源码 zip、`loadList.json`、`dist.zip`),无法适配新引擎的 `src/content` 与双端分离。 - ② **范围(D-93):** 重构 `script/build-game.ts` 与 `script/build-resource.ts`;删除 `script/build-packages.ts` 与 `script/build-lib.ts`;`script/dev.ts` 不改;同步 `dev.md` 构建命令表(W5)。 + ② **范围(最小):** 新增 `script/vite-plugin-data-isolation.ts`(+ 可选 `script/vite-plugin-data-isolation.test.ts`);重写 `script/build-game.ts` 与 `script/build-resource.ts`;删除 `script/build-packages.ts`;写 `09-21-SUMMARY.md`。**`build-lib.ts` / `package.json` / `index.html` / `dev.md` / `src/**` / `public/**` / `vite.config.ts` / lockfile / `packages*/**` / `script/dev.ts` 全部不动。** - ③ **D-94 分离插件:** 新增 Vite 插件,在数据端构建时扫描**解析后的模块图**:`enforce: 'pre'` 的 `resolveId` 先拦渲染端 specifier(`@user/client-*`、`@user/entry-client`、`@motajs/render*`、`@motajs/client*`、`@motajs/system`、`@motajs/audio`、`@motajs/legacy-*`、`vue`、`vue/*`、`@vue/*`、`ant-design-vue`、`@ant-design/icons-vue`),`moduleParsed` 再按绝对路径兜底(`packages-user/client-*`、`packages/render*`、`packages/client*`、`packages/system`、`packages/audio`、`packages/legacy-*`、**`src/client/**`**、`node_modules/{vue,@vue,ant-design-vue,@ant-design}`),命中即 `this.error(...)` **终止构建**;**错误消息必须带命中模块与命中 token**(`matched: `,如 `packages/render` / `packages/render-vue` / `packages/client` / `packages/system` / `packages/audio` / `packages/legacy-` / `packages-user/client-` / `src/client` / `@motajs/render`),供数据端 `.catch` 分类;数据端自身依赖不误判。删除 `checkClientData` / `getFileName` / `getFileSize` / `ClientDataLevel`。 + ③ **目标构建流程(原样告知,不得改序):** 准备(清空/创建 `dist`、`_temp`)→ 渲染端(`index.html` 入口,常规输出到 `dist`)→ 数据端(`src/data.ts`,库模式 ESM,入口 `dist/data.js`,附属产物 `dist/data/`)→ 隔离检测(插件 `this.error`)→ content(`src/content` → `dist/content`,jsonc 纯 JSON 去净注释、后缀 `.jsonc`、fontmin、不分块不 zip)→ `LICENSE` 复制。最终 `dist` 根 = `index.html` + Vite 常规渲染产物 + `data.js` + `content/` + `LICENSE`。 - ④ **D-95 数据端(与渲染端解耦):** 库模式 + `formats: ['es']`,入口 `src/data.ts`,产物 `dist/data/data.js`;`build.lib.fileName: 'data'`(package.json `type: module` → es 扩展名 `.js`);`emptyOutDir: false`;`copyPublicDir: false`;注入分离插件。**构建顺序:准备步骤清空 `dist` 一次 → 先数据端 → 后渲染端**;`buildClient` 也设 `emptyOutDir: false`,因此**渲染端失败不会删除数据端产物**。 + ④ **隔离检测口径:** 插件 `enforce: 'pre'` + `apply: 'build'`;`resolveId` 拦 specifier(`@user/client-*`、`@user/entry-client`、`@motajs/render*`、`@motajs/client*`、`@motajs/system`、`@motajs/audio`、`@motajs/legacy-*`、`vue`、`@vue/*`、`ant-design-vue`、`@ant-design/icons-vue`);`moduleParsed` 按解析后路径兜底(`packages-user/client-*`、`packages/render*`、`packages/client*`、`packages/system`、`packages/audio`、`packages/legacy-*`、`src/client/**`、`node_modules/{vue,@vue,ant-design-vue,@ant-design}`);命中即 `this.error(...)`,错误含 `[mota-data-isolation]` / 命中模块 / `matched: `。`checkClientData` / `ClientDataLevel` / `getFileName` / `getFileSize` 一并删除。 - ⑤ **D-96 content:** `src/content` → `dist/content`;`.jsonc` = **保留一行系统标记 + `JSON.stringify(JSON.parse(body))`**(去注释、后缀仍 `.jsonc`;标记必须保留,否则 `PrefixedJSONCProcessor` 运行期 error 66);字体用 fontmin glyph 子集压缩;**不分块、不 zip**。`build-resource` 改为从 `src/content` 读取素材。实测 `src/content/**` 共 **23** 个 jsonc,每个恰好一个标记。 + ⑤ **content 口径:** jsonc 按行去掉 `//` 注释后 `JSON.parse` + `JSON.stringify`,输出纯 JSON(**含系统标记行在内的所有注释都去掉**),后缀仍 `.jsonc`;字体 fontmin glyph 子集(无字体二进制时跳过告警);不分块、不 zip。 - ⑥ **D-97 产物:** 不复制 `public/**`(`copyPublicDir: false`;整体删除由用户执行);dist 不携带旧模板内容(`editor.html` / `_server` / `_docs` / `libs` / `logo.png` / `styles.css` / `main.js` / `启动服务.exe`);不打包源码 zip;不生成资源地图 JSON;不生成 `dist.zip`;**复制 `LICENSE` 到 `dist/LICENSE`**;`index.html` 去掉旧样式表 link。**D-97 的「样式经入口 JS 引入」不由本计划交付:** `src/styles.less` 当前全仓无 import 点(仅 `index.html` 的旧 link 引用),按用户裁定「你改 src,我做脚本」本计划不改 `src/**`;因此移除 link 后、用户接线前 `dist` 缺 `#game-draw` 居中样式(OI-5),该缺口须**显式记入 SUMMARY**,不得描述为已交付。 + ⑥ **失败处理口径(原样告知):** **不做特殊失败处理** —— 无 BLOCKED 分支、无 baseline / 范围门禁、无对外部文件或仓库状态的检查;构建失败即打印错误并非零退出。本计划**不实跑全量构建**作为门禁(渲染端仍有两处用户 `src` WIP 的 stale import)。 - ⑦ **D-98 / D-99 / D-100:** 删除 `build:packages` / `build:lib`;`build:game` 调整(OI-6:移除读取已删 `public/project` 的 `declare.ts` 前置;**保留 `vue-tsc --noEmit`**);移除 babel 二次压缩;`manualChunks` 移除 `antdv`(源码已无引用)、保留 `audio`;legacy 插件维持现状;**`dev.md` 构建表删除 `build:packages` / `build:lib` 两行并更新 `build:game` 说明**。 + ⑦ **待批准命名(须用户确认):** OI-1(插件文件 / `dataIsolationPlugin` / `name: 'mota-data-isolation'` / 测试文件);OI-2(`buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats`)。未获批前不落码。 - ⑧ **拟新增命名(须报备批准):** `script/vite-plugin-data-isolation.ts` + `dataIsolationPlugin` + 插件 `name: 'mota-data-isolation'` + 测试文件(OI-1);`buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats` + `script/build-resource.test.ts`(OI-2)。**类型一致性(W2):`IContentBuildOptions.chars: string`;`getAllChars` 返回 `Promise`(`[...set].join('')`)。** + ⑧ **验证方式:** 插件单测以真实 `vite.build()` 证明「拒渲染端 import / 通过纯数据端」;两个脚本做结构断言(无 `checkClientData`、数据端 `formats: ['es']` 且入口 `data.js` 在根、附属产物 `dist/data/`、jsonc 去注释、无 zip / 分块、无旧模板拷贝、数据子文件夹与渲染端分离);命令一律 `cmd /c "pnpm exec ..."`。 - ⑨ **待确认项:** 标记保留与 `Type:` 注释丢失(OI-3);无字体二进制的处置(OI-4);样式 import 由用户在 `src/**` 侧完成(OI-5);`build:game` 前置链(OI-6);`manualChunks` / legacy(OI-7);`itemEffectDir` 缺 `item.js`(OI-8);全量构建的门禁分层(OI-9);遗留文件与依赖保留(OI-10);数据端历史跨端引用已解决(OI-11);渲染端两处 stale import 待用户修复(OI-12)。 - - ⑩ **当前数据端引用链(data-only,原样告知):** `src/data.ts`(`export { createCoreState } from './data/entry';`)→ `src/data/entry.ts`(`import { CORE_URL, HERO_FACE } from './constant';`,并导入 `./initialize` / `./plugin` / `./event`)→ `src/data/constant.ts`(仅 `import { FaceGroup } from '@user/data-common';`;`CORE_URL='content/core.jsonc'`、`HERO_FACE=FaceGroup.Dir8`)。**对 `src/data/**` 的检索未发现 `@motajs/render` / `@user/client-*` / `vue` 导入,数据端构建应能通过。** 历史上的跨端引用(`../constant` → `src/constant.ts` → `./client/ui/constant` → `@motajs/render`)**已由用户的 `src` 重构解决**:`src/constant.ts` 已删除,拆分为数据侧 `src/data/constant.ts` 与渲染侧 `src/client/constant.ts`。 - - ⑪ **渲染端两处 stale import(原样告知,属用户 `src` WIP,用户将修复):** ① `src/client/initialize.ts:14` 为 `} from '../constant';`(目标文件已删除,应为 `'./constant'`);② `src/client/plugin/shop/shop.tsx:9` 为 `import { RENDER_WIDTH } from '@/constant';`(`@/` 别名指向已删除的 `src/constant.ts`,应为 `'@/client/constant'`)。因此**渲染端构建当前可能失败**;本计划**不改 `src/**`**,须在汇报中说明修复属用户任务。 - - ⑫ **门禁分层(必须原样告知):** **数据端先行且独立:`dist/data/data.js` 必须产出且为 ESM,这是硬门禁**(缺失 / 非 ESM → `bad.push` + 非零);严格签名只用于把数据端错误标记为 **OI-11 回归诊断**,**不再有 pending 软通过或 sentinel 豁免**。**渲染端是条件分支**:`dist/index.html` 存在 → 断言不含 `styles.css` link;不存在(因用户 stale import)→ 打印 **BLOCKED** 说明,且 `dist/data/data.js`、`dist/content/**`、`dist/LICENSE` 仍照常断言(content 与 LICENSE 在任何构建结果下都会尝试产出)。**不存在任何无条件通过承诺。** - - ⑬ **baseline 与范围门禁(W4):** Task 0 必须先执行 baseline 捕获(`git status --porcelain -uall` + `git diff --name-only -- src` + 每个路径的 SHA-256),快照写入**系统临时目录**(`gsd-09-21-baseline.json`,不写仓库)。Task 2 的范围门禁据此判定:**任何相对 baseline 新增、或被二次修改的路径**(除 `files_modified` 白名单外)一律判负;`src/**` 与 `09-CONTEXT.md` 不再整体白名单放行,仅 baseline 中已存在且哈希未变的用户 WIP 被豁免。 - - ⑭ **产出文件:** 新增 `script/vite-plugin-data-isolation.ts`、`script/vite-plugin-data-isolation.test.ts`、`script/build-resource.test.ts`;重写 `script/build-resource.ts` / `script/build-game.ts`;删除 `script/build-packages.ts` / `script/build-lib.ts`;改 `package.json` / `index.html` / `dev.md`(仅构建命令表三行);新增 `09-21-SUMMARY.md`。**不修改 `src/**`。** - - ⑮ **不在本增量:** 不删 `public/**`;**不改 `src/**`(零改动,含两处 stale import)**;不改数据端/渲染端源码、`vite.config.ts`、`script/dev.ts`、`script/declare.ts`、`script/types.ts`、lockfile;不新增依赖;不改 `ROADMAP.md` / `STATE.md` / `09-CONTEXT.md` / 既有 PLAN 与 SUMMARY。 - - ⑯ **已知阻断(须一并告知并记入 SUMMARY):** ① 数据端**当前无已知阻断**(历史 OI-11 链路已解决);② 渲染端可能因两处 stale import 失败(用户 WIP,用户修复);③ 若渲染端仍因用户其它 WIP 失败,记录完整错误与归因,**不得**修改范围外文件;④ **样式缺口(D-97 未尽项)**:`src/styles.less` 全仓无 import 点,移除 `index.html` 的 `styles.css` link 后,用户接线前 `dist` 缺 `#game-draw` 居中样式(OI-5),本计划**不交付**「样式经入口 JS 引入」,须显式写明。 - - ⑰ **验证方式(PowerShell 5.1 口径:一律经 `cmd /c` 调用,裸 `pnpm` 会因 `pnpm.ps1` 的 `PSSecurityException` 失败):** Task 1 = `cmd /c "pnpm exec vitest run script/vite-plugin-data-isolation.test.ts"`(真实 `vite.build()`:未设 alias 的 `vue` 被拒 / 拒渲染端解析路径(含 token 断言:`packages-user/client-`、`packages/render`、`packages/render-vue`、`packages/system`、`packages/audio`、`packages/legacy-`、`src/client`)/ 通过纯数据端并输出 ESM / 不误判数据端路径)+ 结构断言;Task 2 = `cmd /c "pnpm exec vitest run script/build-resource.test.ts"` + 构建脚本结构断言(含 **数据先于渲染的调用顺序**、**`dist` 只清空一次**、**两处 `emptyOutDir: false`**、**显式 `process.exit(...)` 终止**、`getAllChars` 返回 `Promise`、`dev.md` 构建表)+ 产物门禁(**数据端 ESM 硬断言** / 渲染端条件分支)+ **baseline 范围门禁** + `cmd /c "pnpm exec tsx script/build-game.ts"` 实跑(必须在有界时间内退出);**不设全仓 `check:type` / `build` / 全量 `test:ci` 门禁**。 - - ⑱ **修改方式声明(AGENTS.md):** 本计划执行含文件写入与删除,由**派发子代理执行**(`gsd-executor`),非内联执行。 - - ⑲ **关卡性质:** **无论触发方式或 `mode: yolo` / `workflow.auto_advance`,执行者 / 子代理必须停下等待用户回复;回复「可以执行」即视为通过。** 若用户就 OI-1..OI-12 给出指定,按指定执行。 + ⑨ **不在本增量:** 不改 `src/**`(含两处 stale import);不改 `package.json`(故 `build:packages` 会指向已删除文件);不改 `dev.md` 构建表;不删 `public/**`;不新增依赖;不改 `manualChunks` / legacy 配置。 - 是否按本计划(09-21)执行「任务 11 · 构建重构」——新增数据端/渲染端分离 Vite 插件(错误含命中 token)并注入数据端库模式 ESM 构建(**数据端先构建、`dist/data/data.js` 为硬门禁**)、重写 `build-resource` 把 `src/content`(23 个 jsonc)压缩拷贝到 `dist/content`(jsonc 保留系统标记、fontmin、不分块不 zip)、重写 `build-game`(清空一次 → 数据端 → 渲染端条件 BLOCKED → content → LICENSE;去掉旧模板/babel/资源分块/源码 zip/资源地图/dist.zip)、删除 `build-packages.ts` / `build-lib.ts` 与对应 npm script、同步 `dev.md` 构建表、`index.html` 去旧样式表 link、保留 `vue-tsc --noEmit`、微调 `manualChunks`;**不删 `public/**`、不改 `src/**`(含用户待修复的两处渲染端 stale import)、不动用户其它并发 WIP**;**数据端 ESM 硬失败、渲染端产物条件分支**。 + 是否按本计划(09-21,最小范围)执行「任务 11 · 构建重构」——新增数据端隔离 Vite 插件并注入数据端库模式 ESM 构建(入口 `dist/data.js`、附属 `dist/data/`)、重写 `build-resource`(`src/content` → `dist/content`,jsonc 纯 JSON 去净注释、fontmin、不分块不 zip)、重写 `build-game`(准备 → 渲染端 → 数据端 → content → LICENSE;去掉 babel / zip / 分块 / 旧模板拷贝)、删除 `build-packages.ts`;**仅改上述脚本,其余文件一律不动**。 - AGENTS.md / D-03 / D-04 要求执行前与命名先获批;`.planning/config.json` 为 `mode: yolo`,故本关卡显式 `blocking-human` 且 `autonomous: false`。执行期含写操作与**删除两个脚本**。`src/content` 共 **23** 个 jsonc,均为「前缀注释 + 严格 JSON」且每个恰好一个标记,加载器 `PrefixedJSONCProcessor` 依赖 `// --- SYSTEM PREFIX END --- //` 的 split(必须恰好 2 段),故压缩保留标记是运行期兼容硬约束(去的是标记**之外**的注释)。`src/content/font` 无字体二进制、`src/content/item.ts` 与 `core.jsonc` 的 `itemEffectDir` 不匹配——属既有缺口,本计划只记录不改。**revision 4 的关键事实:** 用户的 `src` 重构已删除 `src/constant.ts`,数据端闭包(`src/data/**`)**不含任何渲染端导入**,因此 `dist/data/data.js` 必须产出并断言 ESM(硬门禁);渲染端仍有两处指向已删除文件的 stale import(`src/client/initialize.ts:14` / `src/client/plugin/shop/shop.tsx:9`),属用户 WIP,本计划**不改 `src/**`**,故渲染端产物门禁是条件分支,但**绝不能拖累数据端硬门禁**。**baseline 由 Task 0 在执行时捕获,不预置并发文件清单:** 规划日实测 `git status --porcelain -uall` 仅含 `.planning/phases/09-minimum-browser-runnable/09-CONTEXT.md`、`.planning/phases/10-docs-restructure/10-CONTEXT.md` 与 `10-13..10-20-PLAN.md`、`pnpm-lock.yaml` 的既有改动(`index.html` / `packages-user/**` / `src/**` 当前无改动);执行时以实际快照为准,提交时须逐条核对暂存面、只暂存本计划 `files_modified`。行号为规划日实测、可能漂移,门禁以结构/行为断言为准。 + AGENTS.md / D-03 / D-04 要求执行前与命名先获批;`.planning/config.json` 为 `mode: yolo`,故本关卡显式 `blocking-human` 且 `autonomous: false`。执行期含写操作与一个脚本删除。`src/content` 共 23 个 jsonc,均为「前缀注释 + 严格 JSON」,按行去注释即可安全转成纯 JSON。渲染端两处 stale import 仍存在(`src/client/initialize.ts:14` / `src/client/plugin/shop/shop.tsx:9`),属用户 WIP,本计划不修、也不据此设置构建门禁。 - 回复「可以执行」即可(默认采用 OI-1..OI-12 的提议口径)。如需调整任一 `open_items`,请在回复中一并指明。 + 回复「可以执行」即可;如需调整命名或流程细节,请在回复中一并指明。 - node -e "const fs=require('fs'),os=require('os'),path=require('path'),cp=require('child_process'),crypto=require('crypto');const bad=[];const must=['script/build-game.ts','script/build-resource.ts','script/build-packages.ts','script/build-lib.ts','package.json','index.html','dev.md','src/data.ts','src/data/entry.ts','src/data/constant.ts','src/client/constant.ts','src/client/initialize.ts','src/client/plugin/shop/shop.tsx','src/data/plugin/list.ts','src/client/plugin/list.ts','src/content/core.jsonc','src/content/client.jsonc','src/content/texture/.list.jsonc','src/content/font/.list.jsonc','packages-user/data-state/src/loader/jsoncProcessor.ts','packages-user/data-state/src/loader/loader.ts'];for(const p of must){if(!fs.existsSync(p))bad.push('missing ref: '+p)}if(fs.existsSync('src/constant.ts'))bad.push('src/constant.ts must not exist');const pkg=JSON.parse(fs.readFileSync('package.json','utf8'));for(const s of ['build:packages','build:lib','build:game']){if(!(s in pkg.scripts))bad.push('package.json missing script '+s)}const bg=fs.readFileSync('script/build-game.ts','utf8');for(const t of ['checkClientData','splitResource','source-code.zip','loadList.json']){if(bg.indexOf(t)<0)bad.push('build-game.ts missing legacy token: '+t)}const html=fs.readFileSync('index.html','utf8');if(html.indexOf('styles.css')<0)bad.push('index.html missing legacy stylesheet link');const dev=fs.readFileSync('dev.md','utf8');for(const s of ['build:packages','build:lib','build:game']){if(dev.indexOf(s)<0)bad.push('dev.md missing build table row: '+s)}const entry=fs.readFileSync('src/data/entry.ts','utf8');if(!/from\s+'\.\/constant'/.test(entry))bad.push('src/data/entry.ts must import ./constant');const dc=fs.readFileSync('src/data/constant.ts','utf8');if(dc.indexOf('@user/data-common')<0)bad.push('src/data/constant.ts must stay data-only (@user/data-common)');if(dc.indexOf('content/core.jsonc')<0)bad.push('src/data/constant.ts must keep CORE_URL=content/core.jsonc');const cc=fs.readFileSync('src/client/constant.ts','utf8');if(cc.indexOf('content/client.jsonc')<0)bad.push('src/client/constant.ts must keep CLIENT_CORE_URL=content/client.jsonc');const jp=fs.readFileSync('packages-user/data-state/src/loader/jsoncProcessor.ts','utf8');if(jp.indexOf('SYSTEM PREFIX END')<0&&jp.indexOf('prefixSplit')<0)bad.push('jsoncProcessor.ts missing prefix split logic');const walk=d=>fs.readdirSync(d,{withFileTypes:true}).flatMap(e=>e.isDirectory()?walk(path.join(d,e.name)):[path.join(d,e.name)]);const jsonc=walk('src/content').filter(p=>p.endsWith('.jsonc'));if(jsonc.length!==23)bad.push('expected 23 jsonc files, got '+jsonc.length);for(const p of jsonc){const n=fs.readFileSync(p,'utf8').split('// --- SYSTEM PREFIX END --- //').length-1;if(n!==1)bad.push('marker count '+n+' in '+p)}for(const p of ['script/vite-plugin-data-isolation.ts','script/vite-plugin-data-isolation.test.ts','script/build-resource.test.ts']){if(fs.existsSync(p))bad.push('post-state already exists: '+p)}let statusRaw='',srcRaw='';try{statusRaw=cp.execSync('git status --porcelain -uall',{encoding:'utf8'});srcRaw=cp.execSync('git diff --name-only -- src',{encoding:'utf8'})}catch(e){bad.push('baseline git capture failed: '+e.message)}const norm=p=>{if(p.charCodeAt(0)===34)p=p.slice(1);if(p.charCodeAt(p.length-1)===34)p=p.slice(0,-1);return p};const parse=raw=>raw.split(/\r?\n/).filter(Boolean).map(l=>{const p=l.slice(3).trim();const i=p.indexOf(' -> ');return norm(i>=0?p.slice(i+4):p)});const paths={};for(const p of parse(statusRaw)){paths[p]=fs.existsSync(p)?crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex'):null}const baseline={capturedAt:new Date().toISOString(),paths,srcDiff:srcRaw.split(/\r?\n/).filter(Boolean)};const bp=path.join(os.tmpdir(),'gsd-09-21-baseline.json');try{fs.writeFileSync(bp,JSON.stringify(baseline,null,2))}catch(e){bad.push('baseline write failed: '+e.message)}if(bad.length>0){console.error('PRESTATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK prestate :: baseline '+bp+' ('+Object.keys(paths).length+' pre-existing changes, srcDiff '+baseline.srcDiff.length+')')" - non-zero exit, 或 stderr 含 "PRESTATE FAILED":必需引用文件缺失 / `src/constant.ts` 仍存在 / `package.json` 缺 `build:packages`、`build:lib`、`build:game` / 旧 `build-game.ts` 缺 legacy token / `index.html` 缺旧样式表 link / **`dev.md` 缺三行构建表任一行** / `src/data/entry.ts` 不再从 `./constant` 导入 / `src/data/constant.ts` 不再仅依赖 `@user/data-common` 或丢了 `content/core.jsonc` / `src/client/constant.ts` 丢了 `content/client.jsonc` / 加载器标记逻辑缺失 / **jsonc 数量不是 23 或某文件标记次数不是 1** / 新插件与测试文件已存在 / **baseline git 捕获或写入临时文件失败** - 用户在对话中明确回复「可以执行」(若就 OI-1..OI-12 给出非默认指定,须在汇报与后续任务中据其指定执行) + node -e "const fs=require('fs');const bad=[];for(const p of ['script/build-game.ts','script/build-resource.ts','script/build-packages.ts','script/build-lib.ts','index.html','vite.config.ts','src/data.ts','src/content/core.jsonc','src/content/texture/.list.jsonc','src/content/font/.list.jsonc','packages-user/data-state/src/loader/jsoncProcessor.ts']){if(!fs.existsSync(p))bad.push('missing '+p)}const jc=fs.readdirSync('src/content',{recursive:true}).filter(f=>String(f).endsWith('.jsonc')).length;if(jc!==23)bad.push('expected 23 jsonc, got '+jc);if(fs.existsSync('script/vite-plugin-data-isolation.ts'))bad.push('plugin file must not exist yet');const bg=fs.readFileSync('script/build-game.ts','utf8');if(bg.indexOf('checkClientData')<0)bad.push('build-game.ts is not the legacy implementation');if(bad.length>0){console.error('PRESTATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK prestate: legacy scripts in place, 23 jsonc, plugin absent')" + non-zero exit,或 stderr 含 "PRESTATE FAILED":必需文件缺失 / jsonc 数量不是 23 / 插件文件已存在 / `build-game.ts` 已不是旧实现 + 用户明确回复「可以执行」(若就 OI-1 / OI-2 给出指定,按指定执行) - 用户回复中明确包含「可以执行」 - - 汇报内容涵盖:要解决的问题、范围(两个脚本重构 + 两个脚本删除 + npm script 调整 + `dev.md` 构建表同步)、D-94 插件扫描口径(specifier + 解析路径 + 命中 token + `src/client` + 报错终止 + 不误判数据端)与取代 `checkClientData`、D-95 库模式 ESM 与 `dist/data/data.js`、**数据端先于渲染端的构建顺序与两处 `emptyOutDir: false`**、D-96 content 压缩(**保留系统标记**、**23 个 jsonc**、fontmin、不分块不 zip)、D-97 产物清单与 `LICENSE` 保留、`index.html` 去 link、D-99 保留 `vue-tsc`、D-100 去 babel/微调 `manualChunks`、**全部拟新增命名(OI-1 / OI-2)与 `chars: string` / `Promise` 类型口径**、写操作边界(含不删 `public/**`、**`src/**` 零改动**)、验证方式、OI-1..OI-12 - - 汇报中已**原样完整复述当前数据端链路**:`src/data.ts` → `src/data/entry.ts`(`from './constant'` + `./initialize` / `./plugin` / `./event`)→ `src/data/constant.ts`(仅 `@user/data-common`),并说明历史跨端引用(`src/constant.ts` → `./client/ui/constant` → `@motajs/render`)**已由用户的 `src` 重构解决**,且 `src/data/**` 无渲染端导入 - - 汇报中已**原样复述两处渲染端 stale import**:`src/client/initialize.ts:14`(`'../constant'`)与 `src/client/plugin/shop/shop.tsx:9`(`'@/constant'`),并说明其属用户 `src` WIP、由用户修复,本计划不改 - - 汇报中已原样告知**门禁分层**:**数据端 ESM 硬门禁(缺失 / 非 ESM 即非零,无 pending 软通过)**;渲染端成功 → 追加渲染产物断言;渲染端因用户 stale import 失败 → **BLOCKED**(打印说明,不出 `ARTIFACT GATE FAILED`,不影响数据端断言);**无任何无条件通过承诺** - - 汇报中已原样告知 **baseline 范围门禁口径**:Task 0 捕获 `git status --porcelain -uall` + `git diff --name-only -- src` + 路径 SHA-256 到系统临时目录;Task 2 对任何相对 baseline 新增或被二次修改的路径判负(白名单仅 `files_modified`),**`src/**` / `09-CONTEXT.md` 不再整体白名单放行** - - 汇报中已原样告知:**删除 `build-packages.ts` / `build-lib.ts` 与 `build:packages` / `build:lib` 属不可逆的仓库内删除(可经 git 恢复)**、**content 压缩保留标记是对 D-96 的兼容性补充**、**`dev.md` 构建表将同步删除这两个命令** - - 汇报中已**显式说明 D-97 的未尽项**:「样式经入口 JS 引入」**不由本计划交付**(`src/styles.less` 全仓无 import 点;移除 link 后、用户于 `src/**` 接线前,`dist` 缺 `#game-draw` 居中样式,OI-5),并承诺写入 SUMMARY,不静默降级 - - 汇报中已说明**构建必须在每个分支显式 `process.exit(...)` 终止**(先 flush stdout),不得依赖事件循环自然退出(渲染端失败时 Vite/esbuild 残留 handle 会使进程挂到超时),且所有验证命令一律经 `cmd /c "pnpm exec ..."` 调用(PowerShell 5.1 下裸 `pnpm` 触发 `PSSecurityException`) - - 汇报中已原样告知**关卡性质**(必须停下等待;不因触发方式或 `yolo` / `auto_advance` 自动放行;回复「可以执行」即通过) + - 汇报涵盖:要解决的问题、最小范围与「其余文件一律不动」清单、目标构建流程 8 步与「不改构建顺序」、隔离检测口径(specifier + 解析路径 + `matched:` token + 取代 `checkClientData`)、数据端 ESM 入口 `dist/data.js` 与附属 `dist/data/`、content 纯 JSON 去净注释、最终 `dist` 清单与排除项、**不做特殊失败处理(无 BLOCKED / 无 baseline 门禁 / 无外部状态检查)**、OI-1 / OI-2 命名、验证方式 + - 已说明删除 `build-packages.ts` 而 `package.json` 不改(`build:packages` 将失效) + - 已说明本计划不改 `src/**`,渲染端两处 stale import 由用户处理,且不作为本计划门禁 - 用户明确回复「可以执行」;baseline 快照已写入系统临时目录(含路径哈希与 `src` diff 清单);在此之前未执行任何任务、未修改或删除任何仓库文件、未派发任何执行子代理 + 用户明确回复「可以执行」;在此之前未执行任何任务、未修改或删除任何仓库文件、未派发任何执行子代理 + - Task 1: 数据端/渲染端分离 Vite 插件端到端最小切片 —— `dataIsolationPlugin`(错误含命中 token)+ 真实 `vite.build()` 运行期单测 - Task 0 已闭环(用户回复「可以执行」;OI-1 已获批或按用户指定命名)。`vite` 与 `vitest` 可用;`script/vite-plugin-data-isolation.ts` 与 `script/vite-plugin-data-isolation.test.ts` 尚不存在;`packages-user/*` 与 `packages/*` 的渲染端/数据端包名如前置读取所列;`src/client/**` 已存在(用户迁移后的渲染端 UI 目录)。 + Task 1: 数据端隔离插件 `dataIsolationPlugin` + 真实 `vite.build()` 单测 + Task 0 已闭环(用户回复「可以执行」,OI-1 已获批或按用户指定)。`vite` 与 `vitest` 可用;`script/vite-plugin-data-isolation.ts` 与测试文件尚不存在。 script/vite-plugin-data-isolation.ts, script/vite-plugin-data-isolation.test.ts - - .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(**D-94**;D-04 / D-56) - - vite.config.ts(别名:`@motajs/*` → `packages/*/src`、`@user/*` → `packages-user/*/src`、`@dp/*`、`@/*`;`base: './'`) - - packages-user/client-modules/package.json / packages-user/client-base/package.json / packages-user/client-system/package.json(渲染端包面) - - packages-user/data-state/package.json / packages-user/data-common/package.json / packages-user/data-base/package.json / packages-user/data-system/package.json(数据端包面,用于避免误判) - - script/check-data-circular.test.ts(既有 `script/*.test.ts` 的 vitest 风格:显式 `import { describe, expect, it } from 'vitest'`;每个 `it` 前一行注释) - - vitest 配置位于 vite.config.ts 的 `test` 段(`testTimeout: 30000`) - - AGENTS.md(测试文件规则:每个 `it` 前一行注释;命名须先获批);dev.md(命名规范、jsDoc、模块无副作用、非空判断约定) + - .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(D-94;D-04) + - vite.config.ts(`@motajs/*` / `@user/*` / `@dp/*` / `@/` 别名,`base: './'`) + - script/check-data-circular.test.ts(既有 `script/*.test.ts` 风格:显式 `import { describe, expect, it } from 'vitest'`;每个 `it` 前一行注释) + - AGENTS.md(测试文件:每个 `it` 前一行注释);dev.md(命名、jsDoc、模块无副作用、非空判断约定) (1) **新建 `script/vite-plugin-data-isolation.ts`(命名以 OI-1 获批结果为准):** - - 自 `vite` 导入**类型** `Plugin`(`import { Plugin } from 'vite'`,按 dev.md 不使用 `import type`)。 - - 模块内声明两组模块级只读常量(不导出,避免额外命名面),每个条目都是 `{ test: RegExp, token: string }`: - - **specifier 禁令**——`@user/client-` 前缀(token `@user/client-*`)、`@user/entry-client`(token `@user/entry-client`)、`@motajs/render` / `@motajs/render-vue` 及其子路径(token `@motajs/render`)、`@motajs/client` / `@motajs/client-base` 及其子路径(token `@motajs/client`)、`@motajs/system`(token `@motajs/system`)、`@motajs/audio`(token `@motajs/audio`)、`@motajs/legacy-*`(token `@motajs/legacy-`)、`vue` 与 `vue/*`(token `vue`)、`@vue/*`(token `@vue/`)、`ant-design-vue` 与子路径(token `ant-design-vue`)、`@ant-design/icons-vue` 与子路径(token `@ant-design/icons-vue`)。 - - **解析路径禁令**——`packages-user/(client-modules|client-base|client-system|entry-client)/`(token `packages-user/client-`)、`packages/render/`(token `packages/render`)、`packages/render-vue/`(token `packages/render-vue`)、`packages/client` / `packages/client-base`(token `packages/client`)、`packages/system/`(token `packages/system`)、`packages/audio/`(token `packages/audio`)、`packages/legacy-*`(token `packages/legacy-`)、**`src/client/`**(token `src/client`)、`node_modules/(vue|@vue|ant-design-vue|@ant-design)/`(token `node_modules/<命中包>`)。 - - 正则统一同时接受 `/` 与 `\\` 两种分隔符(`[/\\]`),不做宽泛的 `client` 子串匹配(避免 `client.jsonc` 之类误判)。 - - 导出 `dataIsolationPlugin(): Plugin`,返回对象包含:`name: 'mota-data-isolation'`(OI-1)、`enforce: 'pre'`、`apply: 'build'`、`resolveId(source, importer)` 与 `moduleParsed(id)`。 - - `resolveId`:对 `source` 做 specifier 禁令匹配;命中即调用 `this.error(...)`,错误消息以 **`[mota-data-isolation]`** 开头并包含 **`matched: `**、命中的模块标识、**导入者**(`importer ?? ''`)与「data-side build must not depend on render-side modules」的明确说明。未命中返回 `null`(交回默认解析)。 - - `moduleParsed(id)`:对**解析后的绝对路径**做路径禁令匹配;命中即 `this.error(...)`,消息同样以 `[mota-data-isolation]` 开头,包含 **`matched: `**、该绝对路径与其 importers(`this.getModuleInfo(id)?.importers`,为空则省略)。这一路覆盖经别名解析、传递依赖、`src/client/**` 与 `node_modules` 的真实落点——即使 Vite 别名解析先于用户插件执行,仍能拦截;**命中 token 是 Task 2 `.catch` 分类器的契约**(W3)。 - - **不得**把数据端包(`@user/data-*`、`@motajs/common`、`@motajs/loader`、`@motajs/anon-tokyo`、`@motajs/types`、`@motajs/animate`)列入禁令;**不得**对字符串做宽泛的 `client` 子串匹配。 - - 按 dev.md 为导出函数与其参数写 jsDoc;模块顶层零副作用(只声明常量与函数)。 + - 自 `vite` 导入类型 `Plugin`(按 dev.md 不使用 `import type`)。 + - 模块内声明两组不导出的只读常量,每项 `{ test: RegExp, token: string }`: + - **specifier 禁令**:`@user/client-`(token `@user/client-`)、`@user/entry-client`、`@motajs/render`、`@motajs/client`、`@motajs/system`、`@motajs/audio`、`@motajs/legacy-`、`vue` 与 `vue/*`(token `vue`)、`@vue/`、`ant-design-vue`、`@ant-design/icons-vue`。 + - **解析路径禁令**:`packages-user/client-`、`packages/render`、`packages/render-vue`、`packages/client`、`packages/system`、`packages/audio`、`packages/legacy-`、`src/client`、`node_modules/(vue|@vue|ant-design-vue|@ant-design)`。 + - 正则同时接受 `/` 与 `\\`(`[/\\]`);**不做**宽泛的 `client` 子串匹配(避免误判 `client.jsonc`)。 + - 导出 `dataIsolationPlugin(): Plugin`,返回 `name: 'mota-data-isolation'`、`enforce: 'pre'`、`apply: 'build'`、`resolveId(source, importer)`、`moduleParsed(id)`。 + - `resolveId`:对 `source` 做 specifier 禁令匹配;命中即 `this.error(...)`,消息以 `[mota-data-isolation]` 开头并包含 `matched: `、命中模块与 `importer ?? ''`,说明「data-side build must not depend on render-side modules」;未命中返回 `null`。 + - `moduleParsed(id)`:对解析后绝对路径做路径禁令匹配;命中即 `this.error(...)`,消息同格式(含 `matched: ` 与 `this.getModuleInfo(id)?.importers`)。这一路覆盖别名解析、传递依赖、`src/client/**` 与 `node_modules` 的真实落点。 + - **不得**把数据端依赖(`@user/data-*`、`@motajs/common`、`@motajs/loader`、`@motajs/anon-tokyo`、`@motajs/types`、`@motajs/animate`)列入禁令。 + - 为导出函数与参数写 jsDoc;模块顶层零副作用。 (2) **新建 `script/vite-plugin-data-isolation.test.ts`:** - - 使用 `node:fs/promises` 的 `mkdtemp`(`os.tmpdir()`)、`mkdir`、`writeFile`、`rm` 与 `node:path`,每个用例独立临时目录,`afterEach` 清理。 + - 用 `node:fs/promises`(`mkdtemp` / `mkdir` / `writeFile` / `rm`)与 `node:os` / `node:path`;每个用例独立临时目录,`afterEach` 清理。 - 调用**真实** `vite.build()`:`configFile:false`、`root: `、`logLevel:'silent'`、`build: { write:false, minify:false, lib: { entry: , formats: ['es'], fileName: 'data' }, copyPublicDir:false }`、`plugins:[dataIsolationPlugin()]`。 - - 用例(每个 `it` 前一行注释说明测试内容): - 1. **渲染端 specifier 被拒(W2):** entry 写入 `import 'vue';`,**不设置任何 `resolve.alias`**;断言 `build()` rejects,错误消息含 `[mota-data-isolation]`、`matched: vue` 与 `data-side build must not depend on render-side`。 - 2. **`packages-user/client-*` 解析路径被拒:** 临时目录内构造 `/packages-user/client-modules/fake.ts`,entry 相对导入它;断言 rejects,消息含该路径片段 **且含 `matched: packages-user/client-`**(证明 `moduleParsed` 路径兜底 + classifier token)。 - 3. **`src/client` 渲染目录被拒:** 临时目录内构造 `/src/client/ui/fake.ts`,entry 相对导入它;断言 rejects,消息含 **`matched: src/client`**(覆盖用户迁移后的真实渲染端目录形态)。 - 4. **别名解析后的渲染端包被拒(classifier token 全覆盖,表驱动):** 依次构造 `/packages/render/src/x.ts`、`/packages/render-vue/src/x.ts`、`/packages/client-base/src/x.ts`、`/packages/system/src/x.ts`、`/packages/audio/src/x.ts`、`/packages/legacy-ui/src/x.ts` 并分别相对导入;每次断言 rejects 且消息含对应 `matched: packages/render` / `matched: packages/render-vue` / `matched: packages/client` / `matched: packages/system` / `matched: packages/audio` / `matched: packages/legacy-`。 - 5. **纯数据端通过且产物为 ESM:** entry 导入 `/packages-user/data-state/fake.ts`(导出常量)并 re-export;断言 `build()` resolve,输出含 chunk 且其 `code` 含 `export` 语句(ESM 产物),不抛错。 - 6. **数据端路径不误判:** entry 相对导入 `/packages-user/data-common/fake.ts`;断言通过(证明禁令只覆盖渲染端路径)。 - - 断言尽量稳定(不依赖 chunk 文件名/顺序),只依赖 rejects/resolve 与错误消息关键字。 - - **本测试是「数据端路径不误判」的唯一权威检查(W3):** 插件结构门禁**不再**对 `@user/data-` 做裸 `indexOf` 负向断言(注释 / 白名单会误伤)。 + - 用例(每个 `it` 前一行注释): + 1. **拒渲染端 specifier:** entry `import 'vue';`,**不设任何 `resolve.alias`**;断言 rejects,消息含 `[mota-data-isolation]`、`matched: vue` 与 `data-side build must not depend on render-side`。 + 2. **拒 `src/client` 渲染目录:** 构造 `/src/client/ui/fake.ts` 并相对导入;断言 rejects 且消息含 `matched: src/client`。 + 3. **拒 `packages-user/client-*` 解析路径:** 构造 `/packages-user/client-modules/fake.ts` 并相对导入;断言 rejects 且消息含 `matched: packages-user/client-`。 + 4. **纯数据端通过且产物为 ESM:** 构造 `/packages-user/data-common/fake.ts`(导出常量)并 re-export;断言 `build()` resolve,输出 chunk 的 `code` 含 `export`(ESM),不抛错。 + - 断言只依赖 rejects / resolve 与错误消息关键字,不依赖 chunk 文件名或顺序。 - - entry `import 'vue'` → `build()` rejects;错误消息含 `[mota-data-isolation]`、`matched: vue` 与 `data-side build must not depend on render-side`。 - - entry 相对导入 `/packages-user/client-modules/fake.ts` → rejects;消息含该路径与 `matched: packages-user/client-`。 - - entry 相对导入 `/src/client/ui/fake.ts` → rejects;消息含 `matched: src/client`。 - - entry 相对导入 `/packages/{render,render-vue,client-base,system,audio,legacy-ui}/src/x.ts` → 各自 rejects;消息含对应 `matched:` token(`packages/render` / `packages/render-vue` / `packages/client` / `packages/system` / `packages/audio` / `packages/legacy-`)。 - - entry 相对导入 `/packages-user/data-state/fake.ts` → resolve;输出 chunk `code` 含 `export`(ESM)。 - - entry 相对导入 `/packages-user/data-common/fake.ts` → resolve(不误判数据端)。 + - entry `import 'vue'` → rejects;消息含 `[mota-data-isolation]`、`matched: vue`、`data-side build must not depend on render-side`。 + - entry 导入 `/src/client/ui/fake.ts` → rejects;消息含 `matched: src/client`。 + - entry 导入 `/packages-user/client-modules/fake.ts` → rejects;消息含 `matched: packages-user/client-`。 + - entry 导入 `/packages-user/data-common/fake.ts` → resolve;输出 chunk `code` 含 `export`。 - 仅新增一个插件文件与一个测试文件;删除这两文件即可整体回退,未接线到构建脚本、未改任何既有文件。 + 仅新增一个插件文件与一个测试文件;删除它们即整体回退,未接线到构建脚本、未改既有文件。 cmd /c "pnpm exec vitest run script/vite-plugin-data-isolation.test.ts" - non-zero exit, 或任一断言失败(渲染端 specifier 未被拒 / 渲染端解析路径未被拒 / `src/client` 目录未被拒 / 渲染端别名包未被拒或缺对应 `matched:` token / 纯数据端构建失败 / 数据端路径被误判 / 错误消息缺少插件标识或导入者) - node -e "const fs=require('fs');const bad=[];const p='script/vite-plugin-data-isolation.ts';if(!fs.existsSync(p))bad.push('plugin file missing');else{const t=fs.readFileSync(p,'utf8');if(!/export\s+function\s+dataIsolationPlugin\s*\(/.test(t))bad.push('dataIsolationPlugin not exported');if(!/enforce\s*:\s*'pre'/.test(t))bad.push('plugin is not enforce:pre');if(t.indexOf('moduleParsed')<0)bad.push('plugin lacks moduleParsed resolved-path check');if(t.indexOf('this.error(')<0)bad.push('plugin does not abort via this.error');if(t.indexOf('mota-data-isolation')<0)bad.push('plugin error tag missing');if(t.indexOf('matched:')<0)bad.push('plugin error message lacks matched token');for(const tok of ['@user/client-','@user/entry-client','@motajs/render','@motajs/system','@motajs/audio','@motajs/legacy-','vue','@vue/','ant-design-vue','packages-user/client-','packages/render','packages/system','packages/audio','packages/legacy-','packages/client']){if(t.indexOf(tok)<0)bad.push('plugin forbidden set missing token: '+tok)}}const tp='script/vite-plugin-data-isolation.test.ts';if(!fs.existsSync(tp))bad.push('plugin test file missing');else{const t=fs.readFileSync(tp,'utf8');if(t.indexOf('build(')<0)bad.push('test does not call real vite build');if(t.indexOf('dataIsolationPlugin')<0)bad.push('test does not import the plugin');if(t.indexOf('matched:')<0)bad.push('test does not assert matched token');if(t.indexOf('src/client')<0)bad.push('test does not cover src/client')}if(bad.length>0){console.error('PLUGIN GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK plugin structure: enforce pre + specifier + resolved path + abort + matched token')" - non-zero exit, 或 stderr 含 "PLUGIN GATE FAILED"(插件文件缺失 / 未导出 `dataIsolationPlugin` / 非 `enforce: 'pre'` / 缺 `moduleParsed` 路径检查 / 缺 `this.error` 终止 / 缺错误标签 / 错误消息缺 `matched:` / 禁令缺少关键 token / 测试文件缺失或未用真实 `vite.build()` / 测试未断言 `matched:` 或未覆盖 `src/client`) + non-zero exit,或任一断言失败(渲染端 specifier 未被拒 / `src/client` 未被拒 / `packages-user/client-*` 未被拒 / 缺对应 `matched:` token / 纯数据端构建失败 / 错误消息缺少插件标识或导入者) + node -e "const fs=require('fs');const bad=[];const p='script/vite-plugin-data-isolation.ts';if(!fs.existsSync(p))bad.push('plugin file missing');else{const t=fs.readFileSync(p,'utf8');if(!/export\s+function\s+dataIsolationPlugin\s*\(/.test(t))bad.push('dataIsolationPlugin not exported');if(!/enforce\s*:\s*'pre'/.test(t))bad.push('plugin is not enforce:pre');if(t.indexOf('moduleParsed')<0)bad.push('moduleParsed path check missing');if(t.indexOf('this.error(')<0)bad.push('does not abort via this.error');if(t.indexOf('mota-data-isolation')<0)bad.push('error tag missing');if(t.indexOf('matched:')<0)bad.push('error message lacks matched token');for(const tok of ['@user/client-','@user/entry-client','@motajs/render','@motajs/client','@motajs/system','@motajs/audio','@motajs/legacy-','vue','@vue/','ant-design-vue','@ant-design/icons-vue','packages-user/client-','packages/render','packages/render-vue','packages/client','packages/system','packages/audio','packages/legacy-','src/client']){if(t.indexOf(tok)<0)bad.push('forbidden set missing: '+tok)}}const tp='script/vite-plugin-data-isolation.test.ts';if(!fs.existsSync(tp))bad.push('test file missing');else{const s=fs.readFileSync(tp,'utf8');if(s.indexOf('build(')<0)bad.push('test does not call real vite build');if(s.indexOf('dataIsolationPlugin')<0)bad.push('test does not import the plugin');if(s.indexOf('matched:')<0)bad.push('test does not assert matched token');if(s.indexOf('src/client')<0)bad.push('test does not cover src/client')}if(bad.length>0){console.error('PLUGIN GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK plugin: enforce pre + specifier + resolved path + abort + matched token')" + non-zero exit,或 stderr 含 "PLUGIN GATE FAILED"(插件文件缺失 / 未导出 `dataIsolationPlugin` / 非 `enforce: 'pre'` / 缺 `moduleParsed` / 缺 `this.error` / 缺错误标签或 `matched:` / 禁令缺关键 token / 测试文件缺失或未用真实 `vite.build()`) - - `script/vite-plugin-data-isolation.ts` 存在并按 OI-1 命名导出 `dataIsolationPlugin`;`enforce: 'pre'` + `resolveId` specifier 检查 + `moduleParsed` 解析路径检查(含 `src/client/**`)+ `this.error(...)` 终止;错误消息带 `[mota-data-isolation]` 标识与 `matched: ` - - 禁令覆盖 D-94 所列渲染端面(`@user/client-*`、`@user/entry-client`、`@motajs/render*` / `client*` / `system` / `audio` / `legacy-*`、`vue`、`@vue/*`、`ant-design-vue`、`@ant-design/icons-vue` 及其解析路径,含 `packages/render` / `packages/render-vue` / `packages/client*`);未把 `@user/data-*` / `@motajs/common` 等数据端依赖列入 - - 运行期单测用**真实 `vite.build()`** 证明六类行为(拒 specifier / 拒 `packages-user/client-*` / 拒 `src/client` / 拒渲染端别名包且 token 正确 / 纯数据端通过并 ESM / 数据端不误判),且每个 `it` 前有一行注释 + - 插件按 OI-1 命名导出 `dataIsolationPlugin`;`enforce: 'pre'` + `resolveId` + `moduleParsed` + `this.error`;错误消息含 `[mota-data-isolation]` 与 `matched: ` + - 禁令覆盖渲染端 specifier 与解析路径(含 `packages/render*` / `packages/client*` / `src/client`);未把数据端依赖列入 + - 单测以真实 `vite.build()` 证明拒 `vue`、拒 `src/client`、拒 `packages-user/client-*`、通过纯数据端并产出 ESM;每个 `it` 前一行注释 - 未改任何既有文件;未接线到 `build-game.ts`(留给 Task 2) - 分离插件与运行期单测就位:真实 Vite 构建在渲染端依赖上被阻断且错误带 `matched:` token、在纯数据端构建通过并产出 ESM;结构断言全绿;零既有文件改动 + 插件与运行期单测就位:渲染端依赖被阻断且错误带 `matched:` token,纯数据端构建通过并产出 ESM;结构断言全绿;零既有文件改动 + - Task 2: 构建编排重写与收口 —— `build-resource`(content 压缩/拷贝/字体)+ `build-game`(清空一次 → 数据端 ESM 硬门禁 → 渲染端条件 BLOCKED → content → LICENSE)+ 删除旧脚本 + npm script / index.html / dev.md 同步 + 产物/范围门禁 + SUMMARY - Task 1 已完成(`script/vite-plugin-data-isolation.ts` 导出 `dataIsolationPlugin` 并通过单测);Task 0 已闭环(OI-2..OI-12 已获批或按用户指定,且 baseline 快照已在系统临时目录)。`src/content/**` 共 **23** 个 jsonc,每个恰好一个系统标记;`src/content/font/` 无字体二进制;`public/**` 仍存在(本计划不删,仅阻止其进入 dist);**渲染端两处 stale import 仍未修复**(`src/client/initialize.ts:14` / `src/client/plugin/shop/shop.tsx:9`),渲染端构建可能失败,属用户 `src` WIP。 - script/build-resource.ts, script/build-resource.test.ts, script/build-game.ts, script/build-packages.ts, script/build-lib.ts, package.json, index.html, dev.md, .planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md + Task 2: 重写 `build-resource` 与 `build-game`、删除 `build-packages.ts`、写 SUMMARY + Task 1 已完成(`dataIsolationPlugin` 可用并通过单测);Task 0 已闭环(OI-2 已获批或按用户指定)。`src/content` 共 23 个 jsonc;`src/content/font/` 无字体二进制;`public/**` 仍存在(不删,仅通过 `copyPublicDir: false` 阻止进入 dist);`script/build-lib.ts` 保持原样。 + script/build-resource.ts, script/build-game.ts, script/build-packages.ts, .planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md - - .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(**D-93 / D-95 / D-96 / D-97 / D-98 / D-99 / D-100**;D-56) - - script/build-game.ts(旧编排:`logProgress` 步骤表、`buildClient`、`buildData`、`checkClientData`、`getAllChars`、`generateResourceJSON`、`buildGame` 的 toCopy / archiver / compressing / 服务器 exe / `dist.zip`) - - script/build-resource.ts(旧 `splitResource` / `CompressedUsage` / `LoadDataType` / `SplittedResource`) - - script/utils.ts(`formatSize`);script/build-packages.ts / script/build-lib.ts(待删除内容,确认无其它引用) - - package.json(scripts 面);index.html(样式 link 行);src/styles.less(仅 `#game-draw` 居中样式) - - src/content/**(**23 个 jsonc**:含 `texture/.list.jsonc` / `maps/.list.jsonc` / `defaults/*.jsonc` / `font/.list.jsonc`、`item.ts`、`texture/*.png`) - - packages-user/data-state/src/loader/jsoncProcessor.ts(标记 split);packages-user/data-state/src/loader/loader.ts:70-142(生产态路径);src/data/constant.ts(`content/core.jsonc`)/ src/client/constant.ts(`content/client.jsonc`);src/client/initialize.ts 与 src/client/plugin/shop/shop.tsx(两处 stale import) - - packages/audio/package.json 与 packages/audio/src/{source,decoder}.ts(`audio` chunk 依据);全仓源码中 `ant-design-vue` 无导入的检索结果 - - AGENTS.md(禁改既有注释、修改方式须预先声明);dev.md(命名、interface、jsDoc、非空判断、尽量不用 try-catch-finally——用 Promise `.catch` 与现有风格一致) + - .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(D-93 / D-95 / D-96 / D-97 / D-98 / D-99 / D-100) + - script/build-game.ts(旧编排:`buildClient` / `buildData` / `checkClientData` / `getAllChars` / `buildGame` 的 babel / archiver / compressing / 旧模板拷贝 / `dist.zip`) + - script/build-resource.ts(旧 `splitResource` 分块 zip);script/utils.ts(`formatSize`) + - index.html(入口 HTML,读而不改);src/content/**(23 个 jsonc 的注释形态);package.json(只读:`type: module` 决定 es 产物扩展名为 `.js`) + - AGENTS.md(禁改既有注释;修改方式须预先声明);dev.md(命名、interface、jsDoc、非空判断约定) - (0) **前置确认:** 仅按 Task 0 获批口径执行;若 OI-2 命名被调整,以用户指定为准并写入 SUMMARY。**本任务不修改 `src/**`。** + (0) **只按 Task 0 获批口径执行;`src/**`、`package.json`、`index.html`、`dev.md`、`vite.config.ts`、`script/build-lib.ts` 一律不动。** - (1) **重写 `script/build-resource.ts`(命名以 OI-2 为准):** 删除 `splitResource` / `CompressedUsage` / `LoadDataType` / `SplittedResource` / JSZip 与 `RequiredData` / `RequiredIconsData` 引用,改为 content 处理模块: - - 声明并导出 `IContentBuildOptions`:`readonly sourceDir: string`(`src/content`)、`readonly outputDir: string`(`dist/content`)、**`readonly chars: string`**(fontmin glyph 子集文本,**字符串**而非 `Set`)。 - - 声明并导出 `IContentBuildStats`:`readonly files: number`(写入文件数)、`readonly jsonc: number`(压缩的 jsonc 数)、`readonly fonts: number`(fontmin 处理的字体数)。 - - 导出 `minifyJSONC(text: string): string`:以系统标记 `'// --- SYSTEM PREFIX END --- //'` 定位;`split` 后:**1 段** = 无标记,对整体 `JSON.stringify(JSON.parse(text))`;**2 段** = 标记存在,输出 `标记 + '\n' + JSON.stringify(JSON.parse(parts[1]))`;**其它段数** = 标记异常,抛出带文件语义的错误。**严禁**丢掉标记行;**不得**改调用方后缀。 - - 导出 `buildContent(options: IContentBuildOptions): Promise`:递归遍历 `sourceDir`(`readdir` with `withFileTypes`)并在 `outputDir` 内镜像目录结构: + (1) **重写 `script/build-resource.ts`(命名以 OI-2 为准):** 删除 `splitResource` / `CompressedUsage` / `LoadDataType` / `ResourceInfo` / `SplittedResource` / `ResourceContent` / `ResourcePath` / JSZip 与 `./types` 引用,改为 content 处理模块: + - 导出 `IContentBuildOptions`:`readonly sourceDir: string`(`src/content`)、`readonly outputDir: string`(`dist/content`)、`readonly chars: string`(fontmin glyph 文本,**字符串**)。 + - 导出 `IContentBuildStats`:`readonly files: number`、`readonly jsonc: number`、`readonly fonts: number`。 + - 导出 `minifyJSONC(text: string): string`:按行过滤掉 `line.trim().startsWith('//')` 的行(去掉 `// Type: ...` 与 `// --- SYSTEM PREFIX END --- //` 等全部注释),`join('\n')` 后 `JSON.stringify(JSON.parse(body))` 返回**单行纯 JSON**;非法 JSON 直接抛错。**不得**保留标记行,**不得**改后缀。 + - 导出 `buildContent(options: IContentBuildOptions): Promise`:递归遍历 `sourceDir`(`readdir` + `withFileTypes`),在 `outputDir` 镜像目录结构: - `*.jsonc` → `minifyJSONC` 后写入同名路径; - - 字体扩展(`.ttf` / `.otf` / `.woff` / `.woff2`)→ 沿用旧写法(`script/build-game.ts:475-482`):`const fontmin = new Fontmin();` + **`Fontmin.glyph({ text: options.chars })`** 插件,`fontmin.src(...).dest(...).use(plugin).runAsync()` 压缩到 `outputDir` 对应子目录(保持文件名)。**结构门禁按 `Fontmin` 与 `glyph` 两个独立字面量检查(不要求连写成 `Fontmin.glyph`),但 `.glyph({ text: options.chars })` 必须收到 `options.chars` 字符串**; + - 字体(`.ttf` / `.otf` / `.woff` / `.woff2`)→ `new Fontmin()` + `Fontmin.glyph({ text: options.chars })`,`src(...).dest(...).use(plugin).runAsync()`,保持文件名; - 其它文件(PNG / `.ts` 等)→ `fs-extra.copy` 原样拷贝; - - `sourceDir/font` 下若**无**字体二进制:跳过 fontmin 并 `process.stdout.write` 告警(含目录),不抛错(OI-4)。 - - 按 dev.md 写 jsDoc;模块无顶层副作用;非空判断字面量用 `isNil`、对象用 `!`。 - - **注意:** `Fontmin` 的引入从 `build-game.ts` 移到本文件;`build-game.ts` 不再直接引用 fontmin。 + - 无字体二进制时跳过 fontmin 并 `process.stdout.write` 告警,不抛错。 + - 统计并返回 `files` / `jsonc` / `fonts`;按 dev.md 写 jsDoc;模块无顶层副作用;字面量非空判断用 `isNil`、对象用 `!`。 (2) **重写 `script/build-game.ts`:** - - **保留**:`DEBUG_BUILD` / `DEBUG_REPLAY`、`ansi`、`logProgress`(外部行为)、`buildClient` / `buildData` 名称、`getAllChars` 名称、`distDir`、错误处理与 `process.exit(1)` 风格、`formatSize` 报告。 - - **删除**:`tempDir` / `_temp` 相关、`RESOUCE_SIZE`、`checkClientData` / `ClientDataLevel` / `getFileName` / `getFileSize`、`generateResourceJSON` / `CompressedLoadList*`、`@babel/core` 与其变换函数、`archiver` / `createWriteStream` / `compressing`、`RequiredData` / `RequiredIconsData` 及对 `public/project/{data,icons}.js` 的读取、`toCopy` 旧模板清单、源码压缩包、资源地图 JSON、资源分块拷贝、服务器 exe 拷贝、`dist.zip` 压缩与相关体积告警分支、`Fontmin` 直接引用(移至 `build-resource.ts`)、**`formats: ['iife']` 与任何 `iife` 字样**。**删除后不得把上述标识符留在注释或字符串里**(结构门禁对以上 token **及裸 `iife`** 做负向检查;说明性文字放本 PLAN / SUMMARY,不写进源码)。 - - **`buildClient(outDir)`**:复用 `loadConfigFromFile` + `mergeConfig`;注入 `legacy(...)`(目标浏览器列表与 `renderModernChunks:false` 维持现状,OI-7);`build.outDir = outDir`(即 `distDir`)、`copyPublicDir: false`、`minify: !DEBUG_BUILD`、**`emptyOutDir: false`**;`rollupOptions.external` 维持 `['@wasm-audio-decoders/opus-ml']`;`output` 维持 `format:'es'` / `entryFileNames` / `chunkFileNames` / `assetFileNames`;`manualChunks` 仅保留 `audio`(移除 `antdv`)。 - - **`buildData(outDir, entry)`**:`build.outDir = outDir`(调用方传 `resolve(distDir, 'data')`)、`copyPublicDir:false`、**`emptyOutDir: false`**、`minify: !DEBUG_REPLAY`、`lib: { entry, fileName: 'data', formats: ['es'] }`(`formats` 仅 `['es']`,源码中不得残留任何 `iife` 字样或 `name`)、`plugins: [dataIsolationPlugin()]`。 - - **`getAllChars()`:返回 `Promise`**:递归读取渲染端产物中的 `.js`(**排除 `dist/data` 与 `dist/content`**,若 `dist` 下无 js 则跳过、不抛错)与 `src/content/**/*.jsonc` 的字符集合(`Set`),最终以 `[...chars].join('')` 返回**字符串**;供 fontmin 使用。此函数不依赖数据端产物。 - - **`buildGame()` 编排(新进度表 5 步:构建前准备 → 构建数据端 → 构建渲染端 → 处理 content → 收尾):** - 1. `await ensureDir(distDir)` + **`await emptyDir(distDir)`(整个流程只执行一次)**。 - 2. **先构建数据端(独立、硬门禁):** `await buildData(resolve(distDir, 'data'), resolve(process.cwd(), 'src/data.ts')).catch(reason => { ... })`;失败时把 `String(reason)` 中的 Windows 反斜杠归一化为 `/`(**源码单反斜杠正则**),若同时含 `mota-data-isolation` 与任一渲染端 token(`@motajs/render` / `@motajs/client` / `@motajs/system` / `@motajs/audio` / `@motajs/legacy-` / `@user/client-` / `packages/render` / `packages/render-vue` / `packages/client` / `packages/system` / `packages/audio` / `packages/legacy-` / `packages-user/client-` / `src/client`)→ 打印 `OI-11 回归诊断`;否则打印普通数据端失败;**两种情况都置 `dataFailed = true`**(不得吞掉、不得软通过、不得 `process.exit(0)`)。 - 3. **后构建渲染端(条件分支):** `await buildClient(distDir).catch(reason => { ... })`;失败时打印 `BLOCKED`(说明错误来自用户 `src/**`,含两处 stale import),置 `renderBlocked = true`,**不覆盖** `dist/data`、**不退出**。 - 4. `const chars = await getAllChars()`(`string`)→ `await buildContent({ sourceDir: resolve(process.cwd(), 'src/content'), outputDir: resolve(distDir, 'content'), chars })`(失败置 `contentFailed`)→ `await copy(resolve(process.cwd(), 'LICENSE'), resolve(distDir, 'LICENSE'))`(失败置 `licenseFailed`)。 - 5. 输出:渲染端状态(BUILT / BLOCKED)、数据端状态(`dist/data/data.js`(ESM)或 FAILED + 归因)、content 统计(`files` / `jsonc` / `fonts`)、dist 体积;随后**先 flush stdout**(`await new Promise(resolve => process.stdout.write('', resolve))`),再**无条件显式终止进程**:`process.exit(dataFailed || contentFailed || licenseFailed ? 1 : 0)`。**每个分支都必须到达这条显式退出**(数据端 / content / LICENSE 任一失败 → 1;仅 `renderBlocked` → 0),**不得依赖事件循环自然退出**——实测渲染端构建失败时 node/tsx 会因 Vite/esbuild 残留 handle 存活 7s+,构建会挂到超时。**不生成任何压缩包。** - - `logProgress` 步骤表同步改为上述 5 项;`ansi.clear` 后的状态行不得被进度勾选覆盖。 - - 保留模块底部执行块(`buildGame()`);无需导出。 + - **保留**:`DEBUG_BUILD` / `DEBUG_REPLAY`、`ansi`、`logProgress`(步骤表改为:`1. 构建前准备` / `2. 构建渲染端` / `3. 构建数据端` / `4. 处理 content` / `5. 收尾`)、`buildClient` / `buildData` / `getAllChars` 名称、`distDir`、`formatSize` 报告。 + - **删除**:`RESOUCE_SIZE`、`checkClientData` / `ClientDataLevel` / `getFileName` / `getFileSize`、`generateResourceJSON` / `CompressedLoadList*`、`@babel/core` 与其变换、`archiver` / `createWriteStream` / `compressing`、`RequiredData` / `RequiredIconsData` 与 `public/project/*` 读取、`toCopy` 旧模板清单、源码 zip、资源地图 JSON、资源分块拷贝、`启动服务.exe` 拷贝、`dist.zip`、直接引用 `Fontmin`(移至 `build-resource.ts`)。 + - **`buildClient(outDir)`**:复用 `loadConfigFromFile` + `mergeConfig`;注入 `legacy(...)`(目标浏览器列表与 `renderModernChunks:false` 维持现状);`build.outDir = outDir`(`dist`)、`copyPublicDir: false`、`minify: !DEBUG_BUILD`、`emptyOutDir: false`;**不设置 `rollupOptions.input`**(默认以 `index.html` 为入口);`rollupOptions.external` 维持 `['@wasm-audio-decoders/opus-ml']`;`output` 维持 `format:'es'` / `entryFileNames` / `chunkFileNames: 'chunks/...'` / `assetFileNames: 'assets/...'`;`manualChunks` **维持现状,不改**。 + - **`buildData(outDir, entry)`**:`build.outDir = outDir`(调用方传 `distDir`)、`copyPublicDir: false`、**`emptyOutDir: false`**、`minify: !DEBUG_REPLAY`、`lib: { entry, fileName: 'data', formats: ['es'] }`;`rollupOptions.output`:`entryFileNames: 'data.js'`、`chunkFileNames: 'data/[name].[hash].js'`、`assetFileNames: 'data/[name].[hash][extname]'` —— **入口落在 `dist/data.js`,附属产物落在 `dist/data/`**;`plugins: [dataIsolationPlugin()]`。 + - **`getAllChars()`:返回 `Promise`**:读取 `dist` 下渲染端 `.js`(**跳过 `dist/data` 与 `dist/content`**)与 `src/content/**/*.jsonc` 的字符集(`Set`),以 `[...chars].join('')` 返回字符串;无匹配文件时返回空字符串,不抛错。 + - **`buildGame()` 编排(顺序固定,不得倒置):** + 1. `await ensureDir(distDir)` + `await ensureDir(tempDir)` + `await emptyDir(distDir)` + `await emptyDir(tempDir)`(整个流程只做一次)。 + 2. **渲染端:** `await buildClient(distDir)`。 + 3. **数据端:** `await buildData(distDir, resolve(process.cwd(), 'src/data.ts'))`。 + 4. `const chars = await getAllChars()` → `await buildContent({ sourceDir: resolve(process.cwd(), 'src/content'), outputDir: resolve(distDir, 'content'), chars })`。 + 5. `await copy(resolve(process.cwd(), 'LICENSE'), resolve(distDir, 'LICENSE'))`;输出渲染端 / 数据端产物状态、content 统计与 `dist` 体积。 + - **失败处理保持简单:** 顶层执行块 `buildGame().catch(reason => { process.stderr.write(String(reason)); process.exit(1); })`;不写 BLOCKED 分支、不做重试或分类、不检查外部文件或仓库状态、不生成任何压缩包。 - (3) **删除旧脚本:** 删除 `script/build-packages.ts` 与 `script/build-lib.ts`(`Remove-Item` / `fs.rm`);确认全仓无其它引用(检索 `build-packages` / `build-lib`,仅允许 `package.json` 的 script 行在删除前命中)。 + (3) **删除 `script/build-packages.ts`**(`build-lib.ts` 保持不动)。 - (4) **`package.json`:** 删除 `build:packages` 与 `build:lib` 两个 script;按 OI-6 把 `build:game` 调整为 `vue-tsc --noEmit && tsx script/build-game.ts`(**保留 `vue-tsc --noEmit`**,移除 `tsx script/declare.ts` 前置;不删除 `script/declare.ts` 文件)。**不改** `dependencies` / `devDependencies`。 - - (5) **`index.html`:** 仅删除旧样式表 `` 行;保留 DOCTYPE / head 其它 meta / `
` / `` / `