From a14718865ceca612eb70323ae9a289c81f0c630b Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Tue, 20 Oct 2026 14:37:34 +0800 Subject: [PATCH] =?UTF-8?q?docs(09-21):=20=E9=87=8D=E5=86=99=E4=B8=BA=20re?= =?UTF-8?q?vision=204=EF=BC=88=E6=95=B0=E6=8D=AE=E7=AB=AF=E7=A1=AC?= =?UTF-8?q?=E9=97=A8=E7=A6=81=20+=20=E9=A1=BA=E5=BA=8F=20+=20baseline?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../09-minimum-browser-runnable/09-21-PLAN.md | 414 ++++++++++-------- 1 file changed, 220 insertions(+), 194 deletions(-) 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 0860dc6..c08773f 100644 --- a/.planning/phases/09-minimum-browser-runnable/09-21-PLAN.md +++ b/.planning/phases/09-minimum-browser-runnable/09-21-PLAN.md @@ -3,7 +3,7 @@ phase: 09-minimum-browser-runnable plan: 21 type: execute wave: 18 -depends_on: [] +depends_on: [09-19, 09-20] files_modified: - script/vite-plugin-data-isolation.ts - script/vite-plugin-data-isolation.test.ts @@ -14,37 +14,38 @@ files_modified: - 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: 60000 - raw_tokens: 30000 + tokens: 36000 + raw_tokens: 36000 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` 终止,取代 `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 调整、D-99 保留 `vue-tsc --noEmit`、D-100 移除 babel 二次压缩并微调 `manualChunks`、**OI-11 的实测跨端引用链原文**、**数据端构建按设计终止属预期(OI-11)、门禁按渲染端 + content + LICENSE 分层**、全部拟新增命名(OI-1 / OI-2)、写操作边界(**不改 `src/**`**)、验证门禁方式与 `open_items`(OI-1..OI-11),然后必须**停下等待**;**回复「可以执行」即视为通过(本计划不因触发方式或 `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`)。" - - "**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/system` / `packages/audio` / `packages/legacy-*` / `src/client/**` / `node_modules/vue|@vue|ant-design-vue|@ant-design` 解析路径)即 `this.error(...)` **报错并终止**构建;数据端自身依赖(`@user/data-*`、`@motajs/common`、`@motajs/loader`、`@motajs/types`、`@motajs/animate`、`anon-tokyo` 等)**不得**误判。**`checkClientData`、`getFileName`、`getFileSize`、`ClientDataLevel` 一并删除**。" - - "**D-95 数据端产物:** 数据端构建改为 **Vite 库模式 + ESM**(`formats: ['es']`,不使用 iife),入口 `src/data.ts`,输出 `dist/data/data.js`;不生成 `data.iife.js` / `data.process.js`。**受 OI-11 约束:** 在用户修复 `src` 端跨端引用前,插件会按设计终止数据端构建,此产物**允许缺失**。" + - "**本计划的第一个 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` 去掉旧 ``。样式改由入口经 JS 打包引入属**用户侧 `src/**` 接线**(OI-5),本计划不改 `src/**`。" + - "**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/constant.ts`、`src/data/**`、`src/client/**`);不回退用户既有改动(`index.html`、`packages-user/client-modules/src/**`、`src/**` 等 WIP);不改数据端/渲染端源码、`public/**`、`vite.config.ts`、lockfile、`.planning/ROADMAP.md` / `STATE.md` / `09-CONTEXT.md` / 既有 PLAN 与 SUMMARY。" - - "**命名须先获批(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)。未获批前不得落码(测试文件按 AGENTS.md 免审批,但仍列入报备)。" - - "**验证可运行性(Nyquist)与预期阻断分层:** Task 1 以**真实 Vite `build()`** 驱动插件单测(渲染端 specifier / 渲染端解析路径 / `src/client` 渲染目录分别被拒;纯数据端通过并输出 ESM);Task 2 以 `minifyJSONC` / `buildContent` 单测 + 构建脚本结构断言 + **渲染端 + content + LICENSE 产物门禁(今天即可通过)** + `git status` 范围门禁兜底;`pnpm exec tsx script/build-game.ts` **必须实跑**:数据端因 OI-11 已知跨端引用被插件按设计终止时,构建脚本记录为**预期 pending** 并继续产出渲染端 + content + LICENSE,**不得**据此判定本计划失败;`dist/data/data.js` 作为**条件门禁**(存在则断言 ESM,缺失则记录 OI-11 pending)。**不得**为通过门禁而修改 `src/**` 或包源码。" + - "**写入边界(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 范围门禁**兜底;`pnpm exec tsx script/build-game.ts` **必须实跑**。**数据端 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` 仍照常断言。**不存在任何无条件通过承诺。**" artifacts: - path: script/vite-plugin-data-isolation.ts - provides: "新增 Vite 插件 `dataIsolationPlugin`:`enforce: 'pre'` + `resolveId` 拦渲染端 specifier,`moduleParsed` 按解析后绝对路径拦渲染端包(含传递依赖与 `src/client/**`),命中即 `this.error(...)` 终止数据端构建(命名待批准,OI-1)" + provides: "新增 Vite 插件 `dataIsolationPlugin`:`enforce: 'pre'` + `resolveId` 拦渲染端 specifier,`moduleParsed` 按解析后绝对路径拦渲染端包(含传递依赖与 `src/client/**`),命中即 `this.error(...)` 终止数据端构建;错误消息含 `[mota-data-isolation]` + 命中模块 + **命中 token(`matched: `)** + importers(命名待批准,OI-1)" - path: script/vite-plugin-data-isolation.test.ts - provides: "运行期单测:真实 `vite.build()`(`configFile:false`、临时目录 fixture、`write:false`)验证「渲染端 specifier 被拒」「渲染端解析路径被拒」「`src/client` 渲染目录被拒」「纯数据端通过且产物为 ESM」「数据端路径不误判」;每个 `it` 前一行注释(AGENTS.md)" + 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)" - path: script/build-resource.ts - provides: "改为 content 处理模块:`minifyJSONC`(保留 `// --- SYSTEM PREFIX END --- //` 标记 + `JSON.parse/stringify` 去注释)、`buildContent`(递归 `src/content` → `dist/content`:jsonc 压缩、fontmin glyph 子集压缩字体、其它素材原样拷贝;无字体二进制时跳过并告警)与结果/选项接口(命名待批准,OI-2)" + 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 原样拷贝 + 无字体时跳过;每个 `it` 前一行注释" + provides: "运行期单测:`minifyJSONC` 保留标记且压缩、去注释、非法 JSON 抛错、无标记纯 JSON 处理;`buildContent` 在临时目录完成 jsonc 压缩 + PNG 原样拷贝 + 无字体时跳过(`chars` 以 `string` 传入);每个 `it` 前一行注释" - path: script/build-game.ts - provides: "重写编排:渲染端构建到 `dist`(`copyPublicDir:false`、`manualChunks` 微调、移除 babel/zip/资源地图/资源分块/旧模板拷贝)→ 收集字体字符 → 数据端 ESM 库构建到 `dist/data`(注入 `dataIsolationPlugin`;OI-11 预期终止被分类记录后继续)→ `buildContent` 处理 `dist/content` → 复制 `LICENSE` → 输出体积与数据端状态;删除 `checkClientData` 等旧逻辑" + provides: "重写编排:`emptyDir(dist)` 一次 → **先**数据端 ESM 库构建到 `dist/data`(注入 `dataIsolationPlugin`;严格签名分类;失败不阻断后续诊断)→ **后**渲染端构建到 `dist`(`copyPublicDir:false`、`emptyOutDir:false`、`manualChunks` 微调、移除 babel/zip/资源地图/资源分块/旧模板拷贝;失败打印 BLOCKED 但不覆盖数据端)→ `getAllChars`(返回 `Promise`)→ `buildContent` 处理 `dist/content` → 复制 `LICENSE` → 输出体积与数据端状态;Windows 路径用单反斜杠正则归一化;删除 `checkClientData` 等旧逻辑" - path: script/build-packages.ts provides: "**删除**(D-93 / D-98)" - path: script/build-lib.ts @@ -53,82 +54,98 @@ must_haves: 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);不改其它章节" - path: .planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md - provides: "本计划执行摘要:范围、插件扫描口径与错误行为、content 压缩口径(含标记保留这一兼容性约束)、dist 产物清单与排除项、删除清单、`open_items` 最终处置、门禁结果、OI-11 预期阻断的完整错误与归因、渲染端 + content + LICENSE 产物结果" + provides: "本计划执行摘要:范围、插件扫描口径与错误行为(含命中 token)、content 压缩口径(含标记保留这一兼容性约束)、dist 产物清单与排除项、删除清单、`dev.md` 同步、`open_items` 最终处置、门禁结果(渲染端 BLOCKED 分支或成功分支)、数据端 ESM 硬门禁结果、渲染端两处 stale import 的当前状态、baseline 范围门禁结果" key_links: - "`dataIsolationPlugin`(`script/vite-plugin-data-isolation.ts`)↔ `buildData` 的 `plugins` 数组(`script/build-game.ts`)—— D-94 的唯一执行点;取代 `checkClientData` 体积启发式" - - "插件 `this.error` 错误文本(含 `mota-data-isolation` 与命中标识)↔ `buildGame` 的数据端 `.catch` 分类 —— 只有匹配 OI-11 已知链(`@motajs/render` 或 `src/client`)才记为预期 pending;其余数据端错误一律 `process.exit(1)`" - - "`buildData` 的 `build.lib.fileName: 'data'` + `formats: ['es']` ↔ `dist/data/data.js` —— D-95 产物契约(受 OI-11 约束,允许暂缺)" + - "插件 `this.error` 错误文本(含 `mota-data-isolation`、命中模块与**命中 token**)↔ `buildGame` 的数据端 `.catch` 分类 —— 归一化后(`String(reason)` 上把 Windows 反斜杠替换为 `/`,**源码单反斜杠正则**)同时匹配插件标识与已知渲染端 token(`@motajs/render` / `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 / `{}`" - - "`dist/content/**` ↔ `MotaDataLoader.resolveURL`(`packages-user/data-state/src/loader/loader.ts:74-82`,生产态为 `${BASE_URL}${url}`)与 `src/constant.ts` 的 `CORE_URL='content/core.jsonc'` / `CLIENT_CORE_URL='content/client.jsonc'` —— 目录落点契约" + - "`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)" assumptions: - - "**content 全部为「前缀注释 + 严格 JSON」且共 23 个:** 实测 `src/content/**/*.jsonc` 共 **23** 个文件,每个都**恰好包含一次** `// --- SYSTEM PREFIX END --- //`(含 `core.jsonc` / `client.jsonc` / `enemy.jsonc` / `item.jsonc` / `tile.jsonc` / `defaults/*` / `maps/*.jsonc` / `animation/.list.jsonc` / `audio/.list.jsonc` / `font/.list.jsonc` / `texture/.list.jsonc`);`PrefixedJSONCProcessor` 要求标记恰好出现一次且 `split` 后恰 2 段,故压缩必须保留该行。" + - "**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,OI-11):** `src/data.ts` → `src/data/entry.ts:2`(`import { CORE_URL, HERO_FACE } from '../constant'`)→ `src/constant.ts:2`(`import { MAIN_HEIGHT, MAIN_WIDTH } from './client/ui/constant'`)→ `src/client/ui/constant.ts:1`(`import { ElementLocator, Font } from '@motajs/render'`,`Font` 以 `new Font('Verdana', 16)` 值使用)。因此 D-94 隔离插件会在数据端构建时按设计 `this.error` 终止,`dist/data/data.js` 在用户打断该链路前**不可产出**。链路修复(拆分 `src/constant.ts`、内联常量、把渲染常量移出数据端依赖等)是**用户任务**,在本计划 `files_modified` 之外。" - - "**`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 文件被移动」而阻断。" + - "**数据端闭包已清理且无渲染端导入(实测 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` 导入;`audio` 系依赖仍由 `@motajs/audio` 使用(`codec-parser` / `ogg-opus-decoder`)。" - - "**`vite.config.ts` 提供 `base: './'` 与 `@motajs/*` / `@user/*` / `@dp/*` / `@/` 别名:** 数据端构建复用 `loadConfigFromFile` + `mergeConfig`,因此插件可在 `enforce: 'pre'` 阶段先拦未解析 specifier,再由 `moduleParsed` 按解析后路径兜底(即使别名解析先于用户插件执行,路径检查仍能拦截)。" - - "**构建顺序安全:** 渲染端构建先执行并 `emptyOutDir` 清空 `dist`;数据端构建 `emptyOutDir:false` 写入 `dist/data`;content 最后写入 `dist/content`;`LICENSE` 最后复制。数据端按预期失败时,`dist` 中渲染端产物不受影响。" + - "**`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` 不一致。" - "**行号可能漂移:** 本计划引用行号为规划日实测;门禁以「文件存在 + 结构/行为断言」为准,不硬编码行号。" 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 回归诊断,最终退出码仍为非零。" - "**不得删除 `public/**`:** D-97 的「整体删除」由用户执行;本计划只通过 `copyPublicDir: false` 阻止其进入 `dist`。" - - "**写入边界(D-56 + 用户裁定「你改 src,我做脚本」):** 只允许写 `files_modified`;**不得修改 `src/**` 的任何文件**(包括 `src/bootstrap.ts`、`src/constant.ts`、`src/data/**`、`src/client/**`);不得改 `vite.config.ts`、`packages/**`、`packages-user/**`、`public/**`、`script/dev.ts`、`script/declare.ts`、`script/types.ts`、`script/utils.ts`、lockfile;不得回退用户在工作树中的任何既有改动。" - - "**`.planning/**` 边界:** 不得改 `ROADMAP.md` / `STATE.md` / `09-CONTEXT.md` / 任何既有 PLAN 或 SUMMARY;只允许新增 `09-21-SUMMARY.md`(与 09-19 / 09-20 的既有做法一致)。" + - "**写入边界(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 按预期终止,只能记录归因;渲染端若因用户其它 WIP 失败,也只能记录归因;不得修 `src/**` 或包源码。" - - "**不得把预期阻断当作通过一切数据端错误的借口:** `.catch` 分类必须同时匹配插件错误标识与 OI-11 已知链(`@motajs/render` 或 `src/client`);任何不匹配的数据端错误必须 `process.exit(1)`,不得吞掉。" + - "**不得为通过门禁而修改范围外文件:** 数据端若出现严格签名,只能记录 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` 做负向检查,残留会被判负。" 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`)与 `IContentBuildStats`(`files` / `jsonc` / `fonts`);测试文件 `script/build-resource.test.ts`。备选:`processContent` / `compressJSONC`。**待批准**(dev.md 要求对象入参声明 interface)。" + - "**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(全量构建的已知阻断 = 指向 OI-11):** 唯一已测阻断是 OI-11 的数据端入口跨端引用(数据端构建按设计终止);渲染端依赖链已就位(`src/bootstrap.ts` → `./client/entry` / `./client/ui/load`,`src/client/ui/load.tsx` 存在),预期可构建。门禁分层:**渲染端 + `dist/content` + `LICENSE` 为必过**;`dist/data/data.js` 为条件门禁(存在则断言 ESM,缺失则记为 OI-11 pending)。若渲染端仍因用户其它 WIP 失败,记录完整错误与归因,不改范围外文件。**待确认**。" + - "**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.ts` → `src/data/entry.ts:2`(`import { CORE_URL, HERO_FACE } from '../constant'`)→ `src/constant.ts:2`(`import { MAIN_HEIGHT, MAIN_WIDTH } from './client/ui/constant'`)→ `src/client/ui/constant.ts:1`(`import { ElementLocator, Font } from '@motajs/render'`,`Font` 以 `new Font('Verdana', 16)` 值使用)。D-94 隔离插件会按设计终止数据端构建,`dist/data/data.js` 在用户打断该链路前**不可产出**。链路修复(拆分 `src/constant.ts` / 内联常量 / 移出渲染常量等)是**用户任务**,在本计划 `files_modified` 之外;本计划不改 `src/**`。Task 2 把「插件按预期终止」记为 expected pending blocker(**不是本计划失败**),并要求渲染端 + `dist/content` + `LICENSE` 仍产出并通过门禁。" + - "**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 仍硬断言)。" --- - 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/**` 相对路径读取,渲染端/数据端已分家。继续沿用旧脚本既无法跑通新数据,也无法保证「数据端独立包不得引用渲染端」。 **目标(D-93..D-100):** -1. **D-94** 用 Vite 插件扫描数据端模块图,命中渲染端模块即报错终止,**取代** `checkClientData`。 -2. **D-95** 数据端改为库模式 ESM,入口 `src/data.ts`,产物 `dist/data/data.js`。 +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)。 +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 插件维持现状。 **关键兼容性约束(本计划对 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 待确认)。 -**已知预期阻断(OI-11):** 数据端入口存在跨端引用链 `src/data.ts` → `src/data/entry.ts:2` → `src/constant.ts:2` → `src/client/ui/constant.ts:1`(`@motajs/render` 值使用)。D-94 插件会在数据端构建时**按设计终止**;这是**预期行为**(等待用户修复 `src`),不是本计划失败。本计划据此把门禁分层:渲染端 + `dist/content` + `LICENSE` 为必过;`dist/data/data.js` 为条件门禁。 +**当前实测状态(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`)= 预执行汇报关卡**:汇报要做什么 / 为什么 / 大致怎么做 / 影响范围(含插件扫描口径、content 压缩口径与标记保留理由、dist 产物清单、删除清单、`manualChunks` 调整、命名报备、验证方式、OI-11 预期阻断与门禁分层),停下等待「可以执行」。 -- **Task 1(`tracer`)= 分离插件端到端最小切片**:新增 Vite 插件并在**真实 `vite.build()`** 的临时 fixture 上验证——渲染端 specifier 被拒、渲染端解析路径(含传递依赖)/ `src/client` 渲染目录被拒、纯数据端通过且产物为 ESM、数据端路径不误判。这是本增量唯一的全新机制,先用运行期测试把「扫描 + 终止」跑通。 -- **Task 2 = 构建编排重写与收口**:重写 `build-resource.ts`(content 压缩/拷贝/字体)与 `build-game.ts`(渲染端 → 数据端 ESM(注入插件,OI-11 预期失败分类)→ content → LICENSE);删除旧脚本;调整 `package.json` 与 `index.html`;补 `build-resource.test.ts`;实跑全量构建并按分层门禁收口;写 `09-21-SUMMARY.md`。 +- **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 的源码改动;它依赖的是**已存在**的数据端加载器、`src/content` 数据与 `src/data.ts` 入口(均已就位)。因此可与既有计划解耦,独占 wave 18。 +**为何 `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`、写 SUMMARY),将由**派发子代理(`gsd-executor`)执行**,非内联执行;除 `files_modified` 所列路径外无任何写入,**`src/**` 零改动**。 +**修改方式声明(AGENTS.md):** 本计划执行含**文件写入与删除**(新增插件与测试、重写两个构建脚本、删除两个旧脚本、改 `package.json` / `index.html` / `dev.md` 构建表、写 SUMMARY),将由**派发子代理(`gsd-executor`)执行**,非内联执行;除 `files_modified` 所列路径外无任何写入,**`src/**` 零改动**。 + @C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md @C:/Users/13194/.config/opencode/gsd-core/templates/summary.md @@ -150,8 +167,10 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎 @vite.config.ts @src/data.ts @src/data/entry.ts -@src/constant.ts -@src/client/ui/constant.ts +@src/data/constant.ts +@src/client/constant.ts +@src/client/initialize.ts +@src/client/plugin/shop/shop.tsx @src/bootstrap.ts @packages-user/data-state/src/loader/jsoncProcessor.ts @packages-user/data-state/src/loader/loader.ts @@ -160,93 +179,100 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎 - Task 0: 预执行汇报关卡 —— 汇报「任务 11 · 构建重构(适配新引擎)」的要做什么 / 为什么 / 大致怎么做 / 影响范围(含 OI-11 预期阻断与门禁分层),等待用户回复「可以执行」 - `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` 含 ``;`src/content/**` 共 **23** 个 `.jsonc`(每个恰好一个 `// --- SYSTEM PREFIX END --- //`)与 `texture/*.png`;`src/content/font/` 无字体二进制;`src/data.ts` 存在并导出 `createCoreState`;`src/bootstrap.ts` 现导入 `./client/entry` 与 `./client/ui/load`(`src/client/ui/load.tsx` 存在);**OI-11 跨端引用链存在**(`src/data.ts` → `src/data/entry.ts` → `src/constant.ts` → `src/client/ui/constant.ts` → `@motajs/render`);`script/vite-plugin-data-isolation.ts` / `script/vite-plugin-data-isolation.test.ts` / `script/build-resource.test.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 快照) - .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) - - src/data.ts / src/data/entry.ts / src/client/entry.ts / src/constant.ts(`CORE_URL='content/core.jsonc'`、`CLIENT_CORE_URL='content/client.jsonc'`)/ src/client/ui/constant.ts(`@motajs/render` 值使用) + - 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`(用户 WIP 面) + - vite.config.ts(`base: './'`、`@motajs/*` / `@user/*` / `@dp/*` / `@/` 别名);`git status --porcelain -uall`(用户 WIP 面) - AGENTS.md(执行前须获批、命名须先反馈、修改方式须预先声明、禁改既有注释);dev.md(命名规则、对象类型须声明 interface、模块无副作用、非空判断约定、jsDoc 规范) - **停下并向用户汇报,等待用户回复「可以执行」后才继续;汇报本身只读。** + **停下并向用户汇报,等待用户回复「可以执行」后才继续;汇报本身只读(不修改仓库文件),但必须先捕获 baseline 快照。** ① **要解决的问题:** 旧构建脚本面向旧引擎与旧模板(依赖 `public/project`、数据端 iife、体积启发式、`main.js` 二次压缩、资源分块 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` 不改。 + ② **范围(D-93):** 重构 `script/build-game.ts` 与 `script/build-resource.ts`;删除 `script/build-packages.ts` 与 `script/build-lib.ts`;`script/dev.ts` 不改;同步 `dev.md` 构建命令表(W5)。 - ③ **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/system`、`packages/audio`、`packages/legacy-*`、**`src/client/**`**、`node_modules/{vue,@vue,ant-design-vue,@ant-design}`),命中即 `this.error(...)` **终止构建**;数据端自身依赖不误判。删除 `checkClientData` / `getFileName` / `getFileSize` / `ClientDataLevel`。 + ③ **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`。 - ④ **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`;注入分离插件。 + ④ **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`,因此**渲染端失败不会删除数据端产物**。 ⑤ **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,每个恰好一个标记。 - ⑥ **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。样式 import 由用户后续在 `src/**` 侧添加(OI-5),本计划不改 `src/**`。 + ⑥ **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。样式 import 由用户后续在 `src/**` 侧添加(OI-5),本计划不改 `src/**`。 - ⑦ **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 插件维持现状。 + ⑦ **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` 说明**。 - ⑧ **拟新增命名(须报备批准):** `script/vite-plugin-data-isolation.ts` + `dataIsolationPlugin` + 插件 `name: 'mota-data-isolation'` + 测试文件(OI-1);`buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats` + `script/build-resource.test.ts`(OI-2)。 + ⑧ **拟新增命名(须报备批准):** `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('')`)。** - ⑨ **待确认项:** 标记保留与 `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-11);遗留文件与依赖保留(OI-10);**数据端入口跨端引用待用户修复(OI-11)**。 + ⑨ **待确认项:** 标记保留与 `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)。 - ⑩ **OI-11 实测跨端引用链(原样告知,必须完整复述):** `src/data.ts` → `src/data/entry.ts:2`(`import { CORE_URL, HERO_FACE } from '../constant'`)→ `src/constant.ts:2`(`import { MAIN_HEIGHT, MAIN_WIDTH } from './client/ui/constant'`)→ `src/client/ui/constant.ts:1`(`import { ElementLocator, Font } from '@motajs/render'`,`Font` 以 `new Font('Verdana', 16)` 值使用)。因此 D-94 隔离插件会在数据端构建时**按设计** `this.error` 终止;在用户打断该链路前 **`dist/data/data.js` 不可产出**。链路修复(拆分 `src/constant.ts` / 内联常量 / 把渲染常量移出数据端依赖等)是**用户任务**,在本计划 `files_modified` 之外;**本计划不改 `src/**`**。 + ⑩ **当前数据端引用链(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`。 - ⑪ **门禁分层(必须原样告知):** 数据端构建因 OI-11 被插件按设计终止 = **预期 pending blocker,不是本计划失败**;Task 2 的构建脚本必须把「插件按预期终止」与「本计划脚本/插件错误」分开:只有错误文本同时匹配插件标识与 OI-11 已知链(`@motajs/render` 或 `src/client`)才记为预期并继续;其余数据端错误一律 `process.exit(1)`。**渲染端产物 + `dist/content` + `dist/LICENSE` 为必过门禁(今天即可通过)**;`dist/data/data.js` 为条件门禁(存在则断言 ESM,缺失则记录 OI-11 pending)。 + ⑪ **渲染端两处 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/**`**,须在汇报中说明修复属用户任务。 - ⑫ **产出文件:** 新增 `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`;新增 `09-21-SUMMARY.md`。**不修改 `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 在任何构建结果下都会尝试产出)。**不存在任何无条件通过承诺。** - ⑬ **不在本增量:** 不删 `public/**`;**不改 `src/**`(零改动)**;不改数据端/渲染端源码、`vite.config.ts`、`script/dev.ts`、`script/declare.ts`、`script/types.ts`、lockfile;不新增依赖;不改 `ROADMAP.md` / `STATE.md` / `09-CONTEXT.md` / 既有 PLAN 与 SUMMARY。 + ⑬ **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 被豁免。 - ⑭ **已知阻断(须一并告知并记入 SUMMARY):** 唯一已测阻断 = OI-11 数据端跨端引用;渲染端依赖链已就位(`src/bootstrap.ts` → `./client/entry` / `./client/ui/load`,后者存在),此前「`bootstrap.ts` 仍指向 `packages-user/client-modules/src/ui/load`」的判断**已过时**。若渲染端仍因用户其它 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/**`。** - ⑮ **验证方式:** Task 1 = `pnpm exec vitest run script/vite-plugin-data-isolation.test.ts`(真实 `vite.build()`:拒渲染端 specifier / 拒渲染端解析路径(传递依赖)/ 拒 `src/client` 渲染目录 / 通过纯数据端并输出 ESM / 不误判数据端路径)+ 结构断言;Task 2 = `pnpm exec vitest run script/build-resource.test.ts` + 构建脚本结构断言 + **渲染端 + content + LICENSE 产物门禁** + `git status` 范围门禁 + `pnpm exec tsx script/build-game.ts` 实跑(数据端 OI-11 预期终止 → 记为 pending 并继续;条件门禁判定 `dist/data/data.js`);**不设全仓 `check:type` / `build` / 全量 `test:ci` 门禁**。 + ⑮ **不在本增量:** 不删 `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。 - ⑯ **修改方式声明(AGENTS.md):** 本计划执行含文件写入与删除,由**派发子代理执行**(`gsd-executor`),非内联执行。 + ⑯ **已知阻断(须一并告知并记入 SUMMARY):** ① 数据端**当前无已知阻断**(历史 OI-11 链路已解决);② 渲染端可能因两处 stale import 失败(用户 WIP,用户修复);③ 若渲染端仍因用户其它 WIP 失败,记录完整错误与归因,**不得**修改范围外文件。 - ⑰ **关卡性质:** **无论触发方式或 `mode: yolo` / `workflow.auto_advance`,执行者 / 子代理必须停下等待用户回复;回复「可以执行」即视为通过。** 若用户就 OI-1..OI-11 给出指定,按指定执行。 + ⑰ **验证方式:** Task 1 = `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 = `pnpm exec vitest run script/build-resource.test.ts` + 构建脚本结构断言(含 **数据先于渲染的调用顺序**、**`dist` 只清空一次**、**两处 `emptyOutDir: false`**、`getAllChars` 返回 `Promise`、`dev.md` 构建表)+ 产物门禁(**数据端 ESM 硬断言** / 渲染端条件分支)+ **baseline 范围门禁** + `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 给出指定,按指定执行。 - 是否按本计划(09-21)执行「任务 11 · 构建重构」——新增数据端/渲染端分离 Vite 插件并注入数据端库模式 ESM 构建(`dist/data/data.js`)、重写 `build-resource` 把 `src/content`(23 个 jsonc)压缩拷贝到 `dist/content`(jsonc 保留系统标记、fontmin、不分块不 zip)、重写 `build-game` 去掉旧模板/babel/资源分块/源码 zip/资源地图/dist.zip 并保留 `LICENSE`、删除 `build-packages.ts` / `build-lib.ts` 与对应 npm script、`index.html` 去旧样式表 link、保留 `vue-tsc --noEmit`、微调 `manualChunks`;**不删 `public/**`、不改 `src/**`(含跨端引用链,交由用户修复)、不动用户其它并发 WIP**;**接受 OI-11 数据端预期阻断并按渲染端 + content + LICENSE 分层门禁**。 + 是否按本计划(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 硬失败、渲染端产物条件分支**。 - 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` 不匹配——属既有缺口,本计划只记录不改。**OI-11 是本次最重要的已知事实:** 数据端入口跨端引用会让 D-94 插件按设计终止数据端构建;用户已明确「你改 src,我做脚本」,故该链路由用户修复,本计划不改 `src/**`,并以「渲染端 + content + LICENSE 必过、`data.js` 条件门禁」保证今天的可验证性。工作树含用户并发改动(`index.html`、`packages-user/client-modules/src/**`、`src/**` 等),提交时须逐条核对暂存面、只暂存本计划 `files_modified`。行号为规划日实测、可能漂移,门禁以结构/行为断言为准。 + 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/**`**,故渲染端产物门禁是条件分支,但**绝不能拖累数据端硬门禁**。工作树含用户并发改动(`index.html`、`packages-user/client-modules/src/**`、`09-CONTEXT.md`、`pnpm-lock.yaml` 等),提交时须逐条核对暂存面、只暂存本计划 `files_modified`。行号为规划日实测、可能漂移,门禁以结构/行为断言为准。 - 回复「可以执行」即可(默认采用 OI-1..OI-11 的提议口径)。如需调整任一 `open_items`,请在回复中一并指明。 + 回复「可以执行」即可(默认采用 OI-1..OI-12 的提议口径)。如需调整任一 `open_items`,请在回复中一并指明。 - node -e "const fs=require('fs');const bad=[];const must=['script/build-game.ts','script/build-resource.ts','script/build-packages.ts','script/build-lib.ts','package.json','index.html','src/data.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)}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 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');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)}const walk=d=>fs.readdirSync(d,{withFileTypes:true}).flatMap(e=>e.isDirectory()?walk(d+'/'+e.name):[d+'/'+e.name]);const j=walk('src/content').filter(p=>p.endsWith('.jsonc'));if(j.length!==23)bad.push('expected 23 jsonc under src/content, got '+j.length);for(const p of j){const t=fs.readFileSync(p,'utf8');const n=t.split('// --- SYSTEM PREFIX END --- //').length-1;if(n!==1)bad.push('marker count != 1: '+p)}if(bad.length>0){console.error('PRESTATE FAILED :: '+bad.join(' ; '));process.exit(1)}const chain=['src/data.ts','src/data/entry.ts','src/constant.ts','src/client/ui/constant.ts'].every(p=>fs.existsSync(p));console.log('OK pre-execution state: legacy build scripts + scripts entries + legacy index.html + 23 jsonc (marker x1) present; new plugin/tests absent; OI-11 chain present='+chain)" - non-zero exit, 或 stderr 含 "PRESTATE FAILED"(旧构建脚本 / `package.json` scripts / `index.html` 旧 link / 加载器标记逻辑任一缺失;jsonc 数量不是 23 或某文件标记次数不是 1;新插件与测试文件已存在) - 用户在对话中明确回复「可以执行」(若就 OI-1..OI-11 给出非默认指定,须在汇报与后续任务中据其指定执行) + 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 给出非默认指定,须在汇报与后续任务中据其指定执行) - 用户回复中明确包含「可以执行」 - - 汇报内容涵盖:要解决的问题、范围(两个脚本重构 + 两个脚本删除 + npm script 调整)、D-94 插件扫描口径(specifier + 解析路径 + `src/client` + 报错终止 + 不误判数据端)与取代 `checkClientData`、D-95 库模式 ESM 与 `dist/data/data.js`、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)**、写操作边界(含不删 `public/**`、**`src/**` 零改动**)、验证方式、OI-1..OI-11 - - 汇报中已**原样完整复述 OI-11 的实测链**:`src/data.ts` → `src/data/entry.ts:2`(`import { CORE_URL, HERO_FACE } from '../constant'`)→ `src/constant.ts:2`(`import { MAIN_HEIGHT, MAIN_WIDTH } from './client/ui/constant'`)→ `src/client/ui/constant.ts:1`(`import { ElementLocator, Font } from '@motajs/render'`,`Font` 值使用),并说明 `dist/data/data.js` 在用户修复前不可产出 - - 汇报中已原样告知**门禁分层**:数据端被插件按设计终止 = 预期 pending(不是本计划失败);渲染端 + `dist/content` + `LICENSE` 必过;`dist/data/data.js` 为条件门禁 - - 汇报中已原样告知:**删除 `build-packages.ts` / `build-lib.ts` 与 `build:packages` / `build:lib` 属不可逆的仓库内删除(可经 git 恢复)**、**content 压缩保留标记是对 D-96 的兼容性补充** + - 汇报内容涵盖:要解决的问题、范围(两个脚本重构 + 两个脚本删除 + 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` 构建表将同步删除这两个命令** - 汇报中已原样告知**关卡性质**(必须停下等待;不因触发方式或 `yolo` / `auto_advance` 自动放行;回复「可以执行」即通过) - 用户明确回复「可以执行」;在此之前未执行任何任务、未修改或删除任何文件、未派发任何执行子代理 + 用户明确回复「可以执行」;baseline 快照已写入系统临时目录(含路径哈希与 `src` diff 清单);在此之前未执行任何任务、未修改或删除任何仓库文件、未派发任何执行子代理 - - Task 1: 数据端/渲染端分离 Vite 插件端到端最小切片 —— `dataIsolationPlugin` + 真实 `vite.build()` 运行期单测 + 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 目录)。 script/vite-plugin-data-isolation.ts, script/vite-plugin-data-isolation.test.ts @@ -261,151 +287,151 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎 (1) **新建 `script/vite-plugin-data-isolation.ts`(命名以 OI-1 获批结果为准):** - 自 `vite` 导入**类型** `Plugin`(`import { Plugin } from 'vite'`,按 dev.md 不使用 `import type`)。 - - 模块内声明两组模块级只读常量(不导出,避免额外命名面):**specifier 禁令**——`@user/client-` 前缀、`@user/entry-client`、`@motajs/render` / `@motajs/render-vue` / `@motajs/client` / `@motajs/client-base` / `@motajs/system` / `@motajs/audio` / `@motajs/legacy-*`、`vue` 与 `vue/*`、`@vue/*`、`ant-design-vue` 与子路径、`@ant-design/icons-vue` 与子路径;**解析路径禁令**——`[/\\]packages-user[\\/](client-modules|client-base|client-system|entry-client)[\\/]`、`[/\\]packages[\\/](render|render-vue|client|client-base|system|audio|legacy-[^\\/]+)[\\/]`、**`[/\\]src[\\/]client[\\/]`**、`[/\\]node_modules[\\/](vue|@vue|ant-design-vue|@ant-design)[\\/]`。 + - 模块内声明两组模块级只读常量(不导出,避免额外命名面),每个条目都是 `{ 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]`** 开头并包含**命中的模块标识**、**导入者**(`importer ?? ''`)与「data-side build must not depend on render-side modules」的明确说明,使构建立即终止且错误可定位。未命中返回 `null`(交回默认解析)。 - - `moduleParsed(id)`:对**解析后的绝对路径**做路径禁令匹配;命中即 `this.error(...)`(消息同样以 `[mota-data-isolation]` 开头,含该绝对路径与其 importers(可由 `this.getModuleInfo(id)?.importers` 提供,若为空则省略))。这一路覆盖经别名解析、传递依赖、`src/client/**` 与 `node_modules` 的真实落点——即使 Vite 别名解析先于用户插件执行,仍能拦截。 - - **不得**把数据端包(`@user/data-*`、`@motajs/common`、`@motajs/loader`、`@motajs/types`、`@motajs/animate`、`anon-tokyo`)列入禁令;**不得**对字符串做宽泛的 `client` 子串匹配(避免 `client.jsonc` 之类的误判)。 + - `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;模块顶层零副作用(只声明常量与函数)。 (2) **新建 `script/vite-plugin-data-isolation.test.ts`:** - 使用 `node:fs/promises` 的 `mkdtemp`(`os.tmpdir()`)、`mkdir`、`writeFile`、`rm` 与 `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 被拒**:entry 导入 `vue`;断言 `build()` rejects,错误消息含 `[mota-data-isolation]`、`vue` 与 `data-side build must not depend on render-side`;为排除「解析失败」干扰,可在 `resolve.alias` 把 `vue` 指到本地 fixture,但仍应先被 `resolveId` 拦截。 - 2. **渲染端解析路径被拒(传递依赖)**:临时目录内构造 `/packages-user/client-modules/fake.ts`,entry 相对导入它;断言 rejects 且消息含该路径片段。 - 3. **`src/client` 渲染目录被拒**:临时目录内构造 `/src/client/ui/fake.ts`,entry 相对导入它;断言 rejects 且消息含 `src/client` 路径片段(覆盖用户迁移后的真实渲染端目录形态)。 - 4. **纯数据端通过且产物为 ESM**:entry 导入 `/packages-user/data-state/fake.ts`(导出常量)并 re-export;断言 `build()` resolve,输出含 chunk 且其 `code` 含 `export` 语句(ESM 产物),不抛错。 - 5. **数据端路径不误判**:entry 相对导入 `/packages-user/data-common/fake.ts`;断言通过(证明禁令只覆盖渲染端路径)。 + 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` 负向断言(注释 / 白名单会误伤)。 - - entry `import 'vue'` → `build()` rejects;错误消息含 `[mota-data-isolation]`、`vue` 与 `data-side build must not depend on render-side`。 - - entry 相对导入 `/packages-user/client-modules/fake.ts` → rejects;错误消息含该路径(证明 `moduleParsed` 路径兜底生效,且覆盖传递依赖)。 - - entry 相对导入 `/src/client/ui/fake.ts` → rejects;错误消息含 `src/client`(证明迁移后的渲染端 UI 目录被覆盖)。 + - 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(不误判数据端)。 - 仅新增一个插件文件与一个测试文件;`Remove-Item` 两文件即可整体回退,未接线到构建脚本、未改任何既有文件。 + 仅新增一个插件文件与一个测试文件;删除这两文件即可整体回退,未接线到构建脚本、未改任何既有文件。 pnpm exec vitest run script/vite-plugin-data-isolation.test.ts - non-zero exit, 或任一断言失败(渲染端 specifier 未被拒 / 渲染端解析路径未被拒 / `src/client` 目录未被拒 / 纯数据端构建失败 / 数据端路径被误判 / 错误消息缺少插件标识、模块标识或导入者) - 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');for(const tok of ['@user/client-','@motajs/render','vue','ant-design-vue']){if(t.indexOf(tok)<0)bad.push('plugin forbidden set missing token: '+tok)}if(!/src\[\\\\\\/\]client/.test(t))bad.push('plugin lacks src/client resolved-path guard');for(const tok of ['@user/data-','@motajs/common']){if(t.indexOf(tok)>=0)bad.push('plugin must not forbid data-side 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('vite')<0)bad.push('test does not use real vite build');if(t.indexOf('dataIsolationPlugin')<0)bad.push('test does not import the plugin')}if(bad.length>0){console.error('PLUGIN GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK plugin structure: enforce pre + specifier + resolved-path (+ src/client) checks + abort + no data-side false positives')" - non-zero exit, 或 stderr 含 "PLUGIN GATE FAILED"(插件文件缺失 / 未导出 `dataIsolationPlugin` / 非 `enforce: 'pre'` / 缺 `moduleParsed` 路径检查 / 未 `this.error` 终止 / 缺错误标识 / 禁令缺少关键 token 或 `src/client` 守卫 / 误把数据端包列入禁令) + 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`) - - `script/vite-plugin-data-isolation.ts` 存在并按 OI-1 命名导出 `dataIsolationPlugin`;`enforce: 'pre'` + `resolveId` specifier 检查 + `moduleParsed` 解析路径检查(含 `src/client/**`)+ `this.error(...)` 终止;错误消息带 `[mota-data-isolation]` 标识 - - 禁令覆盖 D-94 所列渲染端面(`@user/client-*`、`@user/entry-client`、`@motajs/render*` / `client*` / `system` / `audio` / `legacy-*`、`vue`、`@vue/*`、`ant-design-vue`、`@ant-design/icons-vue` 及其解析路径);未把 `@user/data-*` / `@motajs/common` 等数据端依赖列入 - - 运行期单测用**真实 `vite.build()`** 证明五类行为(拒 specifier / 拒解析路径(传递依赖)/ 拒 `src/client` / 纯数据端通过且 ESM / 数据端不误判),且每个 `it` 前有一行注释 + - `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` 前有一行注释 - 未改任何既有文件;未接线到 `build-game.ts`(留给 Task 2) - 分离插件与运行期单测就位:真实 Vite 构建在渲染端依赖上被阻断、在纯数据端构建通过并产出 ESM;结构断言全绿;零既有文件改动 + 分离插件与运行期单测就位:真实 Vite 构建在渲染端依赖上被阻断且错误带 `matched:` token、在纯数据端构建通过并产出 ESM;结构断言全绿;零既有文件改动 - - Task 2: 构建编排重写与收口 —— `build-resource`(content 压缩/拷贝/字体)+ `build-game`(渲染端 → 数据端 ESM(OI-11 预期失败分类)→ content → LICENSE)+ 删除旧脚本 + npm script / index.html + 分层产物门禁 + SUMMARY - Task 1 已完成(`script/vite-plugin-data-isolation.ts` 导出 `dataIsolationPlugin` 并通过单测);Task 0 已闭环(OI-2..OI-11 已获批或按用户指定)。`src/content/**` 共 **23** 个 jsonc,每个恰好一个系统标记;`src/content/font/` 无字体二进制;`public/**` 仍存在(本计划不删,仅阻止其进入 dist);**OI-11 跨端引用链仍未修复时**,数据端构建会被插件按设计终止(预期,不是本计划失败)。 - 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, .planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md + 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 - .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(仅 7 行 `#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/constant.ts(`content/core.jsonc` / `content/client.jsonc`);src/data/entry.ts + src/client/ui/constant.ts(**OI-11 跨端引用链**) - - 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` 与现有风格一致) + - 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` 与现有风格一致) - (0) **前置确认:** 仅按 Task 0 获批口径执行;若 OI-2 命名被调整,以用户指定为准并在 SUMMARY 记录。**本任务不修改 `src/**`。** + (0) **前置确认:** 仅按 Task 0 获批口径执行;若 OI-2 命名被调整,以用户指定为准并写入 SUMMARY。**本任务不修改 `src/**`。** (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 子集文本)。 + - 声明并导出 `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]))`;**其它段数** = 标记异常,抛出带文件语义的错误。**严禁**丢掉标记行;**不得**改调用方后缀。 + - 导出 `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` 内镜像目录结构: - `*.jsonc` → `minifyJSONC` 后写入同名路径; - 字体扩展(`.ttf` / `.otf` / `.woff` / `.woff2`)→ 用 `Fontmin` 的 `glyph({ text: options.chars })` 插件压缩到 `outputDir` 对应子目录(保持文件名); - 其它文件(PNG / `.ts` 等)→ `fs-extra.copy` 原样拷贝; - `sourceDir/font` 下若**无**字体二进制:跳过 fontmin 并 `process.stdout.write` 告警(含目录),不抛错(OI-4)。 - - 按 dev.md 写 jsDoc;模块无顶层副作用;非空判断用 `isNil`(字面量)/ `!`(对象)。 + - 按 dev.md 写 jsDoc;模块无顶层副作用;非空判断字面量用 `isNil`、对象用 `!`。 - **注意:** `Fontmin` 的引入从 `build-game.ts` 移到本文件;`build-game.ts` 不再直接引用 fontmin。 (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` 与 `transformAsync`、`archiver` / `createWriteStream` / `compressing`、`RequiredData` / `RequiredIconsData` 及对 `public/project/{data,icons}.js` 的读取、`toCopy` 旧模板清单、源码压缩包、资源地图 JSON、资源分块拷贝、服务器 exe 拷贝、`dist.zip` 压缩与相关体积告警分支、`Fontmin` 直接引用。 - - **`buildClient(outDir)`**:复用 `loadConfigFromFile` + `mergeConfig`;注入 `legacy(...)`(目标浏览器列表与 `renderModernChunks:false` 维持现状,OI-7);`build.outDir = outDir`(即 `distDir`)、`copyPublicDir: false`、`minify: !DEBUG_BUILD`、`emptyOutDir: true`;`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'] }`(移除 iife 与 `name`)、`plugins: [dataIsolationPlugin()]`。构建成功后记录产物为 `dist/data/data.js`。 - - **`getAllChars(distDir)`**:递归读取渲染端产物中的 `.js`(**排除 `dist/data` 与 `dist/content`**)与 `src/content/**/*.jsonc` 的字符集合,返回 `Set`;供 fontmin 使用。此函数不依赖数据端产物,故数据端按预期失败时仍可执行。 - - **`buildGame()` 编排(新进度表 4 步:① 构建前准备 ② 构建渲染端 ③ 构建数据端 ④ 处理 content 与收尾):** - 1. `ensureDir(distDir)` + `emptyDir(distDir)`; - 2. `await buildClient(distDir)`; - 3. `const chars = await getAllChars(distDir)`; - 4. **数据端(关键:区分预期 OI-11 阻断与本计划错误):** `const message0 = ...` 不需要;直接调用 `buildData(resolve(distDir, 'data'), resolve(process.cwd(), 'src/data.ts'))`,用 Promise `.catch` 接收失败: - - **预期分支(a):** 先把错误文本中的反斜杠归一化为 `/`(`String(reason).replace(/\\\\/g, '/')`),若文本**同时**含 `mota-data-isolation` **与**(`@motajs/render` **或** `src/client`),则视为 **OI-11 预期阻断**:`process.stdout.write` 打印完整错误与该结论,返回 `'pending'`,**不退出**; - - **非预期分支(b/c):** 其它任何错误一律 `process.stderr.write(String(reason))` 并 `process.exit(1)`(不得吞掉)。 - 5. `const stats = await buildContent({ sourceDir: resolve(process.cwd(), 'src/content'), outputDir: resolve(distDir, 'content'), chars })`(`.catch` 失败即退出); - 6. `copy(resolve(process.cwd(), 'LICENSE'), resolve(distDir, 'LICENSE'))`(`.catch` 失败即退出); - 7. 输出构建信息:渲染端 dist 体积、content 统计(`files` / `jsonc` / `fonts`);**数据端状态行**——`'pending'` 时输出 `⚠️ 数据端构建:按预期终止(OI-11,待用户修复 src 端跨端引用)`,成功时输出 `✅ 数据端构建:dist/data/data.js(ESM)`;**不再**生成任何压缩包。 - - `logProgress` 的步骤表改为上述 4 项;最终汇总前 `ansi.clear` 并打印状态行(数据端 pending 的说明不得被进度勾选覆盖)。 + - **删除**:`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` 直接引用。**删除后不得把上述标识符留在注释或字符串里**(结构门禁做负向检查)。 + - **`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'] }`(移除 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` / `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 体积;**`dataFailed || contentFailed || licenseFailed` → `process.exit(1)`**;仅 `renderBlocked` 时退出码保持 0(用户 WIP,非本计划失败)。**不生成任何压缩包。** + - `logProgress` 步骤表同步改为上述 5 项;`ansi.clear` 后的状态行不得被进度勾选覆盖。 - 保留模块底部执行块(`buildGame()`);无需导出。 - (3) **删除旧脚本:** 删除 `script/build-packages.ts` 与 `script/build-lib.ts`(用 `Remove-Item` / `fs.rm`);确认全仓无其它引用(`Select-String` 检索 `build-packages` / `build-lib`,仅允许 `package.json` 旧 script 行在删除前命中)。 + (3) **删除旧脚本:** 删除 `script/build-packages.ts` 与 `script/build-lib.ts`(`Remove-Item` / `fs.rm`);确认全仓无其它引用(检索 `build-packages` / `build-lib`,仅允许 `package.json` 的 script 行在删除前命中)。 - (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`。 + (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 / `
` / `` / `