Merge pull request '清理文档' (#535) from opt/prune-docs into master
Project CI / AI game creator shell Rust smoke (push) Successful in 1m58s
Project CI / Backend tests (push) Successful in 3m53s
Project CI / Frontend tests (push) Successful in 2m4s
Project CI / Native shell tests (push) Successful in 5m52s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m57s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 9m11s
Project CI / AI game creator shell web tests (push) Successful in 1m43s
Project CI / Repository checks (push) Successful in 2m19s
Project CI / AI game creator shell Rust crates (push) Successful in 1m5s

Reviewed-on: #535
This commit was merged in pull request #535.
This commit is contained in:
2026-09-29 23:36:24 +08:00
6 changed files with 62 additions and 1087 deletions
+16
View File
@@ -27,9 +27,25 @@ docs/project-memory/
- `plans/` 只保存正在执行、具有明确剩余项和验收门禁的计划;完成或作废后立即删除,稳定结论融合进当前专题或共享记忆。
- `todos/` 只保存真实开放、有人接手即可执行的事项;每项应写明状态、下一决策点和关闭条件。已退役对象不保留未来 TODO。
- 分支、提交、测试轮次和阶段流水账不进入长期记忆;需要追溯时使用 Git 历史。
- `decision-log.md` 与 `pitfalls.md` 可以直接修改、合并和删除旧条目:被覆盖的决定、退役对象专属说明和重复记录不继续保留;仍有效的风险、兼容约束和未完成事项应保留或融合到当前专题。
- 自动验证检查实际代码、配置和行为,不要求决策记录或踩坑记录包含固定措辞,也不强制把同一规则复制到多个记忆文件。
- 若本目录与代码或最新 `docs/` 冲突,以代码和最新专题为准,并在同次变更中修正记忆。
- 禁止写入个人配置、API Key、Token、Cookie、会话记录、认证文件、本地私密路径、构建产物、日志、缓存和数据库 dump。
## 决策记录格式
以下格式按需使用,简单决策不必凑齐所有字段。
```md
## YYYY-MM-DD 决策标题
- 背景:为什么需要这个决策
- 决策:最终决定是什么
- 影响范围:涉及哪些模块/文档/流程
- 验证方式:如何确认决策仍有效
- 关联文档:相关 PRD、技术文档、提交或 Issue
```
## RAG 索引
本目录是本地 RAG 的高权重索引源,但检索结果只作为候选上下文。索引脚本位于 `scripts/rag/`;运行时依赖和 `.rag/` 数据默认不安装、不提交,启用前需先征得用户确认。
@@ -121,15 +121,15 @@
- 发布灰度改为**默认关闭**并修掉客户端“看得到点不动”:`is_game_distribution_publish_enabled_for_user` 现在要求 gate 行存在且 `enabled=true`(未登录、无行、`enabled=false` 一律 false),因此没配灰度时 `gameDistributionPublishEnabled=false`,AGC 不再渲染「发布到游戏广场」按钮、网页入口也不出现;AGC 侧新增 `announcePublishMessage`,把「已构建并打包试玩包」「先打开一个项目再发布」等提示通过 DirectProject 聊天容器的 `announce` 出口回话(普通项目不渲染工作台状态行,之前只写 workspaceStatus 才会表现为点击无反应)。后台「灰度发布配置」新增「可配置开关」列表:预设开关在未创建行时也可见并可一键配置(不再需要先猜 gate key)。
## 2026-09-23 口径更新:发行入口改为服务端派生
## 发行入口现行口径:服务端派生平台同源路径
- 管理员不再填写 `entryUrl`:审核通过时 `api-server` 读版本取 gameId,按部署模板 `GENARRATIVE_GAME_DISTRIBUTION_RELEASE_ENTRY_TEMPLATE`(生产形如 `https://{gameId}.games.<发行域名>/`,必须含 `{gameId}` 占位符)派生每游戏独立来源地址,再走原有 HTTPS / 无凭据 / 无 query / 无 fragment 校验;后台审核 DTO 与页面已删除该输入框。
- 上文「已完成证据」中描述「管理员填写 / 要求 HTTPS 发行入口」的条目是当时的交付事实,当前口径以主规范《平台入口与玩法链路》《本地开发验证与生产运维》与 `shared-memory/decision-log.md` 的 2026-09-23 条目为准。
- 非生产环境未配置模板时仍回落到本地发行网关回环地址(用于免 TLS 验证内嵌游玩);生产未配置模板、模板缺 `{gameId}`、gameId 非主机安全字符或派生结果非法时,审核通过直接失败。
- 管理员不再填写 `entryUrl`:审核通过时 `api-server` 读版本取 gameId,派生 `/games/{gameId}/` 写入公开投影。dev、release 与预览环境统一使用平台同源路径,不再配置发行域名、通配 TLS 或部署模板变量。
- 上文「已完成证据」中的管理员手填地址、每游戏独立来源及旧模板门禁属于当时的验证过程;现行合同以[平台入口与玩法链路](../../【玩法创作】平台入口与玩法链路-2026-05-15.md)、[本地开发验证与生产运维](../../【开发运维】本地开发验证与生产运维-2026-05-15.md)及决策记录的 2026-09-24 同源发行路径条目为准。
- 边缘将 `/games/{gameId}/` 及其资源路径转发到发行网关并清空 Cookie;网页通过 `sandbox="allow-scripts"` iframe 隔离游戏。历史绝对 HTTPS 入口继续按现行兼容规则读取,不据此恢复模板配置。
## 尚未完成
- 真实独立发行域名、通配 TLS 与 CDN 仍属部署侧:边缘模板与门禁已就绪,本地已用真实 nginx 验证按主机映射、Cookie 403 与命名空间隔离,但仍需在真实域名/证书下跑一次“审核通过 → 游玩 → 换版 → 下架”并确认 CDN TTL 不超过 60 秒窗口。
- 仍需在真实平台域名下按同源发行路径复验“审核通过 → 游玩 → 换版 → 下架”,并确认 CDN TTL 不超过 60 秒窗口;独立发行域名和通配 TLS 已不属于部署或验收要求。
- 版本回读、撤回与管理员安全下架已实现;主规范 HTTP 表中不再有待落地路由。容量与限额边界、发行包 PUT 重试已有真实栈证据;仍未做的是 CDN purge 失败行为、回滚演练与清理策略(不删除仍被公开版本引用的对象)的上线验收。
- 生产调用依赖已初始化的 editor generation runtime service identity;未初始化时 procedure 拒绝写入,不会退回 API 进程内存状态。
File diff suppressed because it is too large Load Diff
+25 -175
View File
@@ -1,5 +1,7 @@
# 踩坑与排障记录
这里只记录对当前开发仍有用的症状、根因、排查方法和风险边界。同一事实保留一个当前口径;退役对象的专属过程与单轮测试结果由 Git 历史追溯。遇到旧路径或版本时,以现行代码和专题文档为准。
## 2026-09-29 逐 delta 脱敏会吃掉段尾换行:DirectProject 汇报的 Markdown 表格整块失效
- **现象**:AGC 项目对话里 agent 汇报正文渲染错位——段落并进同一行、序号项挤成一段、Markdown 表格整块不渲染(表头 `| 素材 | 用途 | 路径 |`、`|---|---|---|` 与数据行以纯文本连在同一行里);同一段文本里的路径还会出现 `game<absolute-path>ame.js`、`assets<absolute-path>anvas-generated/...` 这种被切坏的占位符。重开项目读历史时同一段话渲染正常,很容易被当成渲染器偶发或模型写坏了 Markdown。
@@ -94,8 +96,6 @@
- **leader 卡死的兜底**:闸门只有 follower 的有界等待(60s),若提权子进程真的挂死(`Start-Process -Wait` 无超时),`leader_deadline`(5 分钟)之前该 key 一直被占住,之后新调用会接管并按新 leader 执行;被接管后旧 leader 迟到的结果按令牌丢弃,不会覆盖接管者。`clear_game_creator_acl_elevation_denials` 只清「被拒绝」记忆,不清理 running。
- **关联**:`src-tauri/src/acl_repair_gate.rs`、`src-tauri/src/config.rs`、issue #498。
> 策划历史条目边界:旧策划 V1/V2 已全部退役,当前入口仅使用 Design Agent。下文带日期的旧 Planning V2、Fast GDD、`plan.submit_gdd`、旧 IPC/模块记录仅用于追溯,不能作为恢复旧代码、身份门禁或专属测试的依据;共享问题需在现役调用上核查。现行合同见[策划 Agent 生产迁移与工作区浏览](../../technical/【技术方案】策划Agent生产迁移与工作区浏览-2026-09-10.md)。
## 2026-09-24 模型输出的围栏会粘在正文行里:聊天 Markdown 必须先归一化再解析
- **现象**:AGC 对话里代码块解析错位——引言行被当成代码渲染(`…实现细节(game.js):```js`),或者代码块收不住、把后面的正文一起吞进去(`… return centerOn(projection); }````)。文本本身「看起来没问题」,容易被当成渲染器坏了。
@@ -181,7 +181,7 @@
## macOS 只出 arm64 单架构,universal 必须失败关闭
`stage-node-runtime.mjs` 只把**构建宿主的 Node**打成便携运行时(官方发行版是单架构,没有 universal 发行版),而 2026-09-21 之前 `build-macos-ci.mjs` 构建的是 `universal-apple-darwin`:macOS Job #7~#13 因此在 `stageNodeRuntime` 直接抛「Node 运行时不支持发布目标:universal-apple-darwin」。期间出现过一版「按宿主架构放行」的过渡实现(`targetRuntime` 对 universal 返回宿主架构),它能骗过通用包自检(`check-macos-bundle.mjs` 按 `process.arch` 校验),但**Intel Mac 上这份 arm64 侧车不可执行**,等于把坏包发出去。当前决策:macOS 固定只构建 `aarch64-apple-darwin`,清单只登记 `darwin-aarch64`,`targetRuntime('universal-apple-darwin')` 保持失败关闭。恢复 Intel 的正确路径是先在 staging 支持按架构各带一份**同版本**运行时(另下载另一架构官方发行版)并让通用包自检按架构分别校验,再切回 universal 目标、把 `darwin-x86_64` 键登记回去;不得用「只带宿主架构」充数,也不得把 arm64 产物登记成 x86_64 键。
`stage-node-runtime.mjs` 只将构建宿主的单架构 Node 打成便携运行时。当前 macOS 固定构建 `aarch64-apple-darwin`,清单只登记 `darwin-aarch64`;`targetRuntime('universal-apple-darwin')` 必须失败关闭。若将来恢复 Intel 或 universal,须先在 staging 配齐同版本的双架构 Node、Codex 原生包、code-mode host 和 zsh,按运行切片选择并分别校验,Intel 真机验收后才能登记 `darwin-x86_64`;不能只合并主程序或复用构建宿主的单架构资源。
## release 冷备空间不足会把生产留在维护态
@@ -253,14 +253,6 @@ Copy Artifact 插件在**非 SYSTEM 认证**下按「认证用户」判权:只
AGC macOS 发布入口一度传入 `--no-sign`(目的是绕过没有 Apple 证书的代码签名),结果 Tauri 打印 `Warn Updater signing is skipped due to --no-sign flag.`,产物只有 `*​.app.tar.gz` 而没有 `.sig`,发布入口按设计在「缺少更新包签名」处失败关闭(2026-09-20 首次 Jenkins 实跑命中)。正确做法是不传 `--no-sign`,改为剥离 `APPLE_*` 凭据让 Tauri 跳过 Apple 签名——minisign 更新包签名与 Apple 代码签名这两个开关在 Tauri 里并不独立。Apple 签名状态要按 `codesign -dv` 实测记录,不能硬编码。
## 复用 workspace 的构建必须显式清理本次要写的产物
Jenkins workspace 跨构建保留:上一轮失败留下的同名 `陶泥儿_<version>_universal.dmg` 会让 `hdiutil create` 以「文件已经存在」失败,而上一轮遗留的 `*​.app.tar.gz.sig` 更危险——本轮即使没签出签名,验签门禁也会读到旧签名而误判通过。构建入口必须在构建前删除本次将写出的确切路径(更新包、签名、同版本 DMG 及其校验文件、`latest.json`、`release-notes.txt`),`hdiutil create` 同时用 `-ov`,让「归档里的产物来自本次构建」成为结构性事实而非假设。
## AGC macOS 单次构建耗时集中在主 crate 重复编译
AGC 主 crate(`genarrative_ai_game_creator_shell`)单架构 codegen 约 15–20 分钟,而每次 Tauri 构建都会重新生成前端 `dist`,`build.rs` 对 `dist` 目录的 `rerun-if-changed` 因此每次都判定变化,导致两个架构各重编一次主 crate。实测:`CARGO_BUILD_JOBS=4` 时首次 Jenkins 构建 78 分钟,提到 6 后为 41–43 分钟且成功;依赖 crate 走 sccache 与 target 缓存,首轮 0 命中属预期。剩余优化空间在「不必要地重建 dist」这一层,需单独设计(例如按内容摘要决定是否重跑前端构建),不要在发布入口里用假缓存换取速度。
## Godot C++ 扩展构建与对象生命周期
- 原生引导通过官方 `godot-cpp` 管理 Variant、String 和 Ref,不自行维护 ABI 存储。Godot 类型必须在扩展终止回调内释放,不能依赖 DLL 静态对象析构;桥节点可能已经退出,应按实例 ID 核验存活再回调。
@@ -290,10 +282,6 @@ AGC 主 crate(`genarrative_ai_game_creator_shell`)单架构 codegen 约 15
- **处理(现行口径)**:生成目录一律成对登记 `.prettierignore` + eslint `ignorePatterns`;改 `export_to` 时同步改这两处,并用 `cargo test --locked -p shared-contracts --features ts-bindings export_bindings --manifest-path server-rs/Cargo.toml` 后 `git diff` 为空来验证幂等。
- **易错点**:旧的 `apps/ai-game-creator-shell/src/contracts/generated/` 目录下的同名文件不会自动删除,换目录后必须显式删除旧文件,否则会出现「两个同名 union,改动只落在一个目录」的假绿。
## universal 主程序必须配套双架构原生依赖
AGC macOS 主程序可合并为 universal,但 Codex 原生包的 `codex-package.json`、code-mode host 和 zsh 仍有架构身份。两套包应各自保留上游布局与摘要,放入 `coding-agent/mac-native/darwin-arm64/`、`darwin-x64/`,由正在运行的主程序切片选择;不能只把主程序用 lipo 合并后复用最后一次构建的单架构资源。Tauri universal 两次 Cargo 构建共用 staging,每次都必须 stage 完整的两套资源。发布清单两个平台键同 URL/签名,只在 universal 产物上成立;Rosetta 隔离 smoke 不代替 Intel 真机验收。
## 生成草稿与异步展示边界必须按身份隔离
非模态生成浮层切换占位时按 draftId 分实例,卸载保留未提交/失败草稿,成功提交不再复活草稿;旧项目占位不存在时丢弃其保存回调。失败重试保留原请求输入和引用身份,引用失效不能静默过滤;修改已绑定输入须明确另起请求,不伪装成原请求重试。
@@ -497,8 +485,6 @@ AGC 的 Cocos 能力来自随客户端分发的 `agc-cocos-editor` 内置插件
- 同步 Tauri command 内的权限校验、会话目录扫描和锁等待会占用窗口线程。DirectProject 必须跳过专业 Agent 历史;开发入口仍需要的对话读取在 blocking worker 中执行,权限校验保留在同一后台闭包内。
- 排障测量完整 IPC 链路并同步采样原生窗口响应。某命令的调用端耗时可能包含前面的主线程队列等待,不能仅凭调用端耗时认定插件启动或上游请求本身缓慢。
> 当前口径:本文件保留可复用的排障经验;历史条目的旧路由、旧版本和已删除文档仅作根因背景,不得据此恢复退役入口。当前命令、路由和 schema 以代码与 `docs/README.md` 为准。
## 2026-09-12 画布滚轮要按 DOM 归属判定,portal 出去的浮层不能把滚轮让给画布
- **现象**:资源卡「快速编辑」里用 `@` 开出「选择素材」浮层后,在选择器列表上滚鼠标滚轮,列表自己在滚,背后的资源画布也一起平移 / 缩放(用户口语:「滚轮还是回滚到画布上」)。
@@ -521,14 +507,6 @@ AGC 的 Cocos 能力来自随客户端分发的 `agc-cocos-editor` 内置插件
- **排查顺序**:先看 debug 响应里 `output` 是否为空、是否同时有工具调用;不要当成模型拒调工具或策划提示词错误。
- **验证**:`platform-llm` 流式夹具覆盖「空 completed + 增量 item」能拿出可回放 `responses_output`,以及 completed 顶层 `output`。
## 2026-09-05 Planning V2 审批和续跑必须等过项目锁瞬时争用
- **现象**:策划 V2 在 GDD 审批提交修改意见后提示 `项目正在被其他写操作占用:...\\.agent\\project.lock`,聊天区再出现 `项目总控 Agent 执行失败,请稍后重试`。
- **原因**:V1 `decide_plan_gdd_at` / hydrate 已按完整或短窗口等待项目锁。V2 的审批、回合启动、策略落盘和 hydrate 直接 `acquire_project_write_lock`,与 GUI 重灌、刚结束的审批写盘或后台扫描撞车就立刻失败。修订后续跑走 `continue_planning_session_v2`,失败被前端写进总控错误位。这不是锁没释放,也不是 UAC。
- **处理**:一次性用户意图(审批、回合启动、策略落盘、失败投影、GDD 认领)走完整等待窗口;V2 hydrate 走短窗口。前端 V2 hydrate 对锁争用保持上一份状态,不把瞬时占用画进审批卡。
- **排查顺序**:先看错误是否点名 `project.lock` 且发生在提交修改意见或立刻续跑;不要当成总控 Runtime 或 Provider 失败。锁文件在失败后通常已被 Drop 删掉,现场缺文件不否定争用。
- **验证**:Rust 定向覆盖 V2 审批、修订续跑和 hydrate 等过短暂占用的项目锁。
## 2026-09-05 新建项目锁不要把继承 DACL 当成 UAC 事件
- **现象**:策划 V2 在 GDD 审批提交修改意见时弹出权限窗口,目标是 `Documents\Genarrative GameAgent\gameagent-*\.agent\project.lock`,随后 `AGC ACL 提权修复未成功(exit code Some(1))`。
@@ -537,19 +515,6 @@ AGC 的 Cocos 能力来自随客户端分发的 `agc-cocos-editor` 内置插件
- **排查顺序**:先看错误是否点名 `project.lock` 且含 `禁止继承` / `exit code Some(1)`;不要当成策划 V2 或 Provider 鉴权问题。含空格的 `Genarrative GameAgent` 项目根是复现条件,不是业务失败。
- **验证**:Windows 定向覆盖含空格项目根取锁、新锁已满足私有 DACL、Drop 删除,以及提权参数把带空格路径保留为一个 quoted token。
## 2026-09-04 Planning V2 不可变 GDD 创建后不能当没提交
- **现象**:`gdd.vN.json` 已 create-only 落盘,但 index / Markdown / conversation / session 任一步失败后,session 停在 `provider_failed` 且 `current_artifact_version` 仍指向旧版本。重试会用新 UUID/时间戳再写同一版本号,命中“已存在且内容不同”。
- **处理**:把该文件当作提交点。恢复时只认领 session 指针的下一个连续版本并补投影,不要删文件,也不要重建 GDD 身份。hydrate 和同一回合重试都必须走这条认领路径。
- **排查顺序**:先看 `.agent/planning-v2/gdd.vN.json` 是否已存在、再看 `session.json` 的 `currentArtifactVersion` 是否落后;不要为了重试去覆盖不可变文件。
- **验证**:孤儿文件重试后仍是同一 `gddId`/vN,hydrate 能看到当前产物。
## 2026-09-04 DeepSeek thinking 不能与 tool_choice=required 同时使用
- **现象**:DeepSeek V4(默认 thinking)对 `tool_choice=required` 或指定函数返回 HTTP 400:`Thinking mode does not support this tool_choice`。
- **处理**:策划 V2 协议工具固定 `tool_choice=auto`,由 Runtime 校验必须恰好调用 `plan_ask_question` 或 `plan_submit_gdd`。不要按模型名分支,也不要用 required 强行出稿。
- **验证**:请求体含 `tools` 且 `tool_choice=auto`;无工具调用时走既有非法输出重试。
## 2026-09-09 常用设置跨文件保存失败
主配置与 local overlay 的单文件原子写入不能保证整体成功;覆盖层写入失败会留下混合配置。保存前先序列化全部变更,多文件保存保留原内容,错误时逆序恢复并报告回滚失败;单文件保持原写入路径,成功后不回读、不触发外部诊断。此回滚仅处理可捕获错误,不承诺进程崩溃下的事务恢复。
@@ -622,21 +587,6 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- **处理**:项目 `.agent`、Agent DB 和公共 event 继续只写安全摘要;额外在应用私有配置目录的 `diagnostics/provider-reconciliation/<projectHash>/<requestHash>.json` 保存本次响应、tool calls 和校验错误,供本机人工排障。该文件不参与恢复/重试、不复制到项目、不进入 Git,单文件限制 1 MiB,写入失败不改变 reconciliation 语义。
- **排查顺序**:先读 Runtime 状态里的 `localDiagnostic` 相对引用,再在应用私有目录读取诊断,核对 requestId、requestSlot、tool name 和失败 pointer;不要为了取得原文而放宽 handoff 的安全门。
## 2026-08-27 阶段判定不能在持锁的 Provider builder 中再次获取项目锁
- **现象**:GDD 修订取证阶段新增后,重新启动策划时前两步表面成功,但父 Supervisor 在收到 `project-planning` 回执、生成下一轮工具计划时失败:`项目正在被其他写操作占用:$PROJECT_ROOT\\.agent\\project.lock`。
- **原因**:`provider_tool_plan` 在构建请求前已持有 `.agent/project.lock`;`plan_root_supervisor_stage_at` 又调用会自行取锁的 Acceptance Evidence 包装入口。同一进程的文件锁不可重入,持锁调用被误判为外部竞争,等待约 10 秒后失败。问题与 Provider、代理端口或 GDD 内容无关。
- **处理**:所有需要一致快照的状态读取保留在项目锁内;阶段判定提供明确的 `*_locked` 内部入口,外层入口仅供未持锁调用方取得一次锁。Provider builder 显式接收并校验当前锁后调用 locked 阶段判定,不引入可重入锁,也不移除 Acceptance Evidence 门禁。
- **排查顺序**:先看失败 Run 的事件顺序是否为 `delegate receipt ready → 生成工具计划 → 阶段判定项目锁失败`,再检查调用方是否已持有 Provider plan project lock;不要因为错误文案包含“其他写操作”就先扩大锁等待或放宽 Provider usage。
- **验证**:`cargo check`、`cargo fmt --check`、planning submit 68 passed、Provider request builder 17 passed;阶段测试同时覆盖未持锁包装入口和持锁 locked 入口。
## 2026-08-26 GDD 新版本提交后不能沿用“已有委派”工具面
- **现象**:`plan_root_supervisor_stage_at` 只按是否存在 delivery 判定 `Delegated`。用户修订产生的新 GDD 仍未完成当前根 Run 的 `file.read → agent.acceptance_update` 取证时,模型会看到 `agent.delegate`,可能重复派发同一条策划链。
- **原因**:自然语言 playbook 已规定“证据不足先取证、用户修改后才返工”,但阶段工具白名单没有把这条 durable 状态固化。
- **处理**:阶段判定复用现有 acceptance gate 的 GDD/session/delivery/graph identity 检查,增加无副作用的 `AwaitingAcceptanceEvidence` 阶段;`PLAN_PROVIDER_USAGE_DEFERRED` 保持 fail-closed,不通过放宽 Provider 使用量门禁解决。
- **排查顺序**:先看最新 `gdd.vN.json`、`session.latestSubmittedRef`、delivery 是否 `ClaimedByParent`,再看 Acceptance Graph 是否 `NeedsEvidence`;若仍可见 `agent.delegate`,优先检查 plan root 阶段快照,而不是修改 acceptance gate 或 Provider 门禁。
## 2026-08-15 把校验往链路前面挪,改的不是严格程度而是作用域
- 现象:CI 全量 5 条失败,看上去毫不相干(两条 Goal 续跑停在 `needs-reconciliation`、一条交接用例断言错误文案、一条恢复用例把不可读 state 的错误抛了出来、一条 Linux-only 用例错误码对不上),实际只有 3 个根因,且三者是**同一个形状**:新增或既有的检查被放在了链路更靠前的位置,于是它的语义作用域被悄悄放大或提前,而不是「变严」。
@@ -657,14 +607,6 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- 验证:改后 `cargo check --offline --all-targets` 通过、`cargo fmt --check` 通过、`project::manifest` 与 godot 相关定向测试 65 passed / 0 failed。判断「是不是本次合并引入」的通用手法:`git diff origin/master -- <file>` 为空即说明该文件就是 master 原样,问题不在合并。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/project/manifest.rs`;`rust-toolchain.toml`;master 提交 `578f8019f`。
## 2026-08-14 planning sidecar 必须区分“可读”与“可写”,canonical bytes 也不等于 typed 指纹
- 现象:如果为了保护 Runtime-owned 事实,直接把 `.agent/planning/**` 加进现有 private-control **读**门,planning 子 Agent 的 `file.read` / `file.list` 会一起失败;反过来若只依赖工具面约束,通用 `file.write`、`file.patch`、`file.delete`、`project.patchset` 或 checkpoint restore 仍可能覆盖 GDD/session。另一个常见误判是把“能反序列化且语义相同”的 JSON 当成已提交文件,导致尾换行、字段重排或重复键绕过不可变事实的字节身份。
- 原因:`.agent/planning/**` 是 Runtime 专用 durable sidecar,但 planning Agent 需要只读观察;`game/fast_gdd.md` 又是 Runtime renderer 的人读投影,二者都不能复用“读写一体”的旧 private path 判据。存储 bytes 与 typed fingerprint 是两个门:前者约束磁盘 canonical serialization(Rust struct 字段顺序、compact UTF-8、无 BOM/尾空白、无重复键),后者约束 domain-separated 业务 payload 的完整性;只过其中一门都不能视为 authoritative。
- 处理:保持现有 private-control **读**门不含 planning,新增只挡写 predicate;所有通用 mutation 与 restore 路径在推进 revision 前先拒绝 `.agent/planning/**` / `game/fast_gdd.md`,专用 writer 再校验 `project-planning + agent-delegate + standard + project-supervisor` 身份。GDD/index 等不可变文件走项目锁、同目录临时文件、`sync_all`、回读与 no-replace 发布;相同 canonical bytes 才是 replay,任何其它内容都是 identity conflict。session 只保留一个 `.session.json.previous`,primary 损坏时 fail closed,不得拿 previous 猜测新旧。
- 验证:先用 planning Agent 的只读 action 验证 sidecar 可列出/读取,再逐项证明 `file.write`、`file.patch`、`file.delete`、`project.patchset` 与 checkpoint restore 均拒绝;storage 测试应覆盖 duplicate key、BOM/尾空白、字段顺序、symlink/目录/硬链接、create-only replay/conflict、session 缺 primary 提升、primary 损坏和合法 successor。第 9.1 节 golden vector 当前为 3857 bytes / `sha256-serde-json-v2:a59856de7ef134cf2f49c4dedd2ba10ae4ab2340a9634d402eb792b6ee5458f0`。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_storage.rs`、`apps/ai-game-creator-shell/src-tauri/src/project/filesystem.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/file_ops.rs`、`apps/ai-game-creator-shell/src-tauri/src/patchset.rs`、`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md` 第 8.3~10.2 节。
## 2026-08-12 在 autonomous-game-build 下试图向用户提问,会让整条工作流永久瘫痪
- 现象:给自主构建链路加「问用户一句」的需求时,最自然的两个想法——让 DAG 节点自己问、或让父 Supervisor 代问——**都不成立**,而且第二个的失败方式是灾难性的。
@@ -721,7 +663,7 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- 身份与恢复:只允许完整 GUI / CLI 16 任务 DAG 的 `agent-ready-task-scheduler` 确定性直接 child、当前活跃根和正确 parent/binding;错误 source、delegated run、历史/终态根、非当前 root、跨 Agent/run 一律失败关闭。owner 再次 mutation 必须令旧凭证失效;相同身份恢复时可按当前磁盘事实确定性重验。
- 并发恢复补充:自主根任务 journal 写入后建立或重建 completion contract 时,初始 manifest reset 与 continuation reconciliation reset 不能重新使用 fail-fast 项目锁。异步 child finalization 可以合法插入两次取锁之间,使已入 journal 的新根被误记为 `completion-contract-failed`。这两条 reset 必须使用现有有界等待项目锁,超时仍失败关闭;只验证 scheduler 合同的测试应预占 child Runtime lane,不能真实启动后台 worker 后再手工改 manifest。确定性回归要显式持锁,分别证明初始合同与 continuation 合同等待释放后成功落盘。
- 验证:夹具必须从 `init_local_game_project_at` 开始,先断言无 `package.json` 且占位入口 smoke 失败,再证明产物不齐阻断、齐全后内部验证通过、无 smoke trace、再次 mutation 失效;另覆盖四个 owner 路径矩阵、错误身份、恢复、跨 run 凭证、`art-director` 有/无 Key、动态美术借凭证拒绝、`code-prototype` project.verify-only 阻断和试玩 executor 身份。
- 关联:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md`。
- 关联:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## UI 设计 State 的 strict JSON round-trip 不能混用两种浮点序列化表示(2026-08-19)
@@ -2054,38 +1996,6 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- 验证:浏览器创作 Tab 中每张开放态卡都应显示标题、描述和后台契约 `mudPointCost` 数量经前端格式化后的泥点消耗文案;旧契约缺字段时兜底显示 `10泥点数`;`npm test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx -t "creation start card renders reference-aligned banner and template metadata"` 应通过。
- 关联:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`src/index.css`、`src/components/custom-world-home/CustomWorldCreationHub.test.tsx`。
## 创作首屏开放态卡片不要再显示左上状态标签
- 现象:创作 Tab 的开放态玩法卡左上角会重复显示“可创建”或“可创作”,视觉上比其它状态更吵,还会和封面图抢注意力。
- 原因:卡片渲染层默认把 `badge` 当成所有状态都要展示的左上角标签,没有区分开放态与非开放态。
- 处理:开放态卡片不渲染左上标签,仅保留标题、描述和右下角消耗信息;`敬请期待`、`即将开放` 等非开放态标签继续保留。
- 验证:创作首屏 HTML 中不应包含 `可创建` / `可创作`,但仍应包含 `即将开放` 等非开放态状态。
- 关联:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 发现 / 创作 / 草稿页不要把根内容区再包成全局卡片壳
- 现象:发现页、创作页或草稿页根区一旦套回 `platform-page-stage`,页面边缘会立刻变得更厚,频道标签、列表和模板卡的横向空间都被挤窄,看起来像回到了旧全局卡片壳。
- 原因:`platform-page-stage` 本身是全局内容卡片壳,适合推荐页、我的页和其它页面,但这三页已经有自己的视觉结构;草稿页顶部筛选若继续用旧 `platform-tab`,还会和发现页频道标签不一致。
- 处理:这三页的根内容区只保留 `platform-remap-surface`,不要再加 `platform-page-stage`;草稿页顶部筛选复用发现页的 `platform-mobile-home-channel` 与 `platform-mobile-home-channel--active`。
- 验证:浏览器里这三页的根区应仍保留 `platform-remap-surface`,但不再出现 `platform-page-stage`;草稿页顶部筛选样式应和发现页频道标签一致。
- 关联:`src/components/custom-world-home/CustomWorldCreationHub.tsx`、`src/components/custom-world-home/CustomWorldWorkTabs.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/index.css`。
## 统一创作壳现在自己负责页面滚动和四条入口外壳
- 现象:统一创作页最初只包住拼图、抓大鹅和敲木鱼的工作台内容,跳一跳仍然保留独立工作台壳,页面级滚动职责也散落在平台入口 motion wrapper 里,导致移动端不同入口的可见外壳不一致。
- 原因:`UnifiedCreationPage` 只做了标题和隐藏契约,入口壳还在各自工作台里保留 `platform-remap-surface` / `overflow-y-auto`,`jump-hop` 也没进入统一 spec。
- 处理:把 `jump-hop` 纳入 `unifiedCreationSpec`,让 `UnifiedCreationPage` 自己承担页面级滚动与统一标题栏;`JumpHopCreationWorkspace`、`WoodenFishCreationWorkspace` 补 `unifiedChrome` / `showBackButton`,平台壳不再给这几条统一入口套额外滚动壳。
- 验证:`npm run test -- src/components/unified-creation/unifiedCreationSpecs.test.ts src/components/unified-creation/UnifiedCreationPage.test.tsx src/components/unified-creation/UnifiedGenerationPage.test.tsx src/components/unified-creation/workspaces/JumpHopCreationWorkspace.test.tsx src/components/unified-creation/workspaces/WoodenFishCreationWorkspace.test.tsx` 通过后,`/creation/puzzle`、`/creation/match3d`、`/creation/jump-hop`、`/creation/wooden-fish` 都应由同一套统一创作页外壳承载。
- 关联:`src/components/unified-creation/UnifiedCreationPage.tsx`、`src/components/unified-creation/unifiedCreationSpecs.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`。
## 统一创作编排层不要再让平台壳直挂旧工作台
- 现象:平台入口壳已经切到统一创作外壳,但源码里仍直接 lazy import 并渲染四个旧工作台分支,看起来还是四套入口编排。
- 原因:统一创作页只收口了可见外壳,入口层没有再抽一层统一创作编排组件,导致平台壳依旧要认识各玩法旧工作台。
- 处理:新增 `UnifiedCreationWorkspace`,由它内部按 `playId` 选择真实工作台;平台壳只依赖这一层,不再直接挂旧工作台分支。旧工作台已迁入 `src/components/unified-creation/workspaces/`,不再是入口编排事实源。
- 验证:`PlatformEntryFlowShellImpl.tsx` 中不应再出现四个旧工作台的入口渲染分支,创作 Tab 与 `/creation/<play>` 仍能正常进入对应工作台。
- 关联:`src/components/unified-creation/UnifiedCreationWorkspace.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## Jenkinsfile 开头不能带 UTF-8 BOM
- 现象:`Genarrative-Stdb-Module-Publish` 在 `Pipeline script from SCM` 读取 `jenkins/Jenkinsfile.production-stdb-module-publish` 后,流水线还未进入任何 stage 就失败,报 `java.lang.NoSuchMethodError: No such DSL method 'pipeline'`,堆栈位置是 `WorkflowScript.run(WorkflowScript:1)`。
@@ -2485,13 +2395,6 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- 验证:`cargo test -p module-auth projection --manifest-path server-rs/Cargo.toml`、`cargo test -p module-auth phone --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server phone_login_reuses_existing_user_for_same_phone_number --manifest-path server-rs/Cargo.toml`。
- 关联:`server-rs/crates/module-auth/src/lib.rs`、`server-rs/crates/spacetime-module/src/auth/procedures.rs`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 认证快照表和旧 procedure 已删除
- 现象:有些旧代码和生成 bindings 里还会残留 `get_auth_store_snapshot`、`upsert_auth_store_snapshot`、`import_auth_store_snapshot`、`import_auth_store_snapshot_json`、`export_auth_store_snapshot_from_tables`,或者把 `auth-store.json` 误当成认证恢复源。
- 原因:认证恢复已经彻底收口到 SpacetimeDB 正式表和 `module-auth` typed projection;本地文件持久化或 JSON 快照会和正式表投影打架,SpacetimeDB 不可用时还可能把旧快照回灌到用户表。
- 处理:先用 `npm run spacetime:generate` 刷新 bindings,确认 `server-rs/crates/spacetime-client/src/module_bindings.rs` 里已没有旧 snapshot table / procedure 导出;`module-auth` 只保留内存态和 projection view,不再写本地快照文件。
- 验证:`cargo check -p module-auth --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:spacetime-schema`、`npm run check:encoding`。
## 抓大鹅生成页只显示服务暂不可用先查 reason 和外部服务配置
- 现象:点击生成抓大鹅草稿后,页面只提示“服务暂不可用”,或者本地 `npm run dev:api-server` 看似启动但生成接口不可用。
@@ -3037,14 +2940,6 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- 验证:前端测试先点开模板确认面板,再 rerender 到另一 session,断言确认面板消失。
- 关联:`src/components/creative-agent/CreativeAgentWorkspace.tsx`、`src/components/creative-agent/CreativeAgentWorkspace.test.tsx`。
## 创作 Tab 语义迁移后,旧“新建作品”测试要改看智能创作首页
- 现象:把 `create` 从旧创作中心切到 `CreativeAgentHome` 后,旧测试仍尝试在创作页找“新建作品”类型卡,导致用例失败或定位不到元素。
- 原因:产品语义已经变成“创作 = 智能创作首页,草稿 = 旧作品架”,但测试夹具和 helper 还沿用旧入口。
- 处理:把这类测试改成验证智能创作首页、快捷胶囊、抽屉与草稿 Tab;同时给 `useRpgEntryLibraryDetail` 这类恢复路径补上 `setPlatformTabToDraft`。
- 验证:定向 `vitest`、`eslint`、`typecheck`、`check:encoding` 都通过。
- 关联:`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`src/components/rpg-entry/useRpgEntryAgentDraftRestore.test.tsx`、`src/components/rpg-entry/useRpgEntryLibraryDetail.ts`。
## server-rs 默认 cargo build 不能等同于构建 SpacetimeDB 模块
- 现象:在 `server-rs` 下无参数 `cargo build` 期望同时构建 `spacetime-module`,导致链接或构建范围误判。
@@ -3637,14 +3532,6 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- 验证:移动端视口检查视频 `rect` 应覆盖整个视口,`paused` 应最终变为 `false`,`currentTime` 应持续前进。
- 关联:`src/components/GenerationProgressHero.tsx`、`docs/【玩法创作】生成页圆环布局口径-2026-05-23.md`。
## 跳一跳结果页直达时不要把恢复面板当成空白页
- 现象:浏览器直接打开 `/creation/jump-hop/result`,如果没有 `sessionId`、`profileId`、`draftId` 或 `workId`,页面以前会看起来像空白,容易误判成结果页坏了。
- 原因:跳一跳结果页恢复原先只盯 `jumpHopSession.draft`,没有把“缺恢复信息”明确兜成可见恢复面板;直达结果页时也没有优先用 `profileId -> getWorkDetail` 补回完整作品。
- 处理:`PlatformEntryFlowShellImpl` 的跳一跳恢复逻辑改成先尝试 `profileId -> getWorkDetail`,再尝试 `sessionId -> getSession`;两者都没有时显示 `跳一跳草稿未恢复` 和 `返回创作`,不再留空白页。
- 验证:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "direct jump hop result route"`,并手测 `/creation/jump-hop/result` 与 `/creation/jump-hop/result?profileId=<id>` 两种情况。
- 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
2026-05-24 补充:`GenerationPageBackdrop` 不要通过 portal 挂到 `document.body`。body 级 fixed 背景会逃离生成页自己的 stacking context,即使业务内容有局部 `z-10`,真实浏览器里也可能把整页 UI 压住。背景视频应作为生成页根容器子节点保留 `fixed inset-0 z-0`,生成页内容保持 `relative z-10`;相关测试应同时断言背景容器低层级、生成页根容器高层级,以及视频节点仍在生成页 DOM 内部。视觉调整时还要记住:空心圆环的中心块要抽掉,时间卡与总进度标题都应缩小,不要让生成页再回到“纯色底 + 大字号说明卡”的状态。顶部返回和右上状态也不能沿用 `text-lg` / `sm:text-2xl` 这类展示级字号;当前步骤名、步骤状态和底部玩法信息标题要维持普通 UI 字号档位,优先保持 `text-xs` 到 `text-sm` 区间。
2026-05-24 补充:生成页“预计等待 / 已耗时”卡片本身已经有标签,传给 `GenerationProgressHero` 的值只能是纯时间,例如 `4 分钟`、`1 分 15 秒`,不要再拼接“预计还需”或“已耗时”;两张时间卡也要和当前步骤卡一样保持半透明。拼图总进度初始帧必须允许显示 `0%`,不要再用 `Math.max(1, nextProgress)` 之类的保护把启动态抬到 `1%`。
@@ -3735,14 +3622,6 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- 验证:`cargo test -p module-auth --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml work_author`、`npm run test -- scripts/rebind-orphan-work-owners.test.ts`。
- 关联:`server-rs/crates/api-server/src/work_author.rs`、`server-rs/crates/module-auth/src/domain.rs`、`scripts/rebind-orphan-work-owners.mjs`。
## 访客推荐页上下滑不要绑定登录态
- 现象:访客模式进入移动端推荐页后,推荐内容可展示和点击底部“下一个”,但在作品信息区域上下滑不会切换推荐作品,表现为推荐页不能上下滑动。
- 原因:推荐页滑动切换逻辑 `beginRecommendDrag(...)` 误把 `isAuthenticated` 作为启用条件;访客态虽然允许浏览和通过底部按钮切换,却无法触发同一套拖拽切换。
- 处理:推荐页拖拽只校验当前是否有作品、多作品可切换以及是否正在提交动画,不再要求登录;登录态相关操作仍由点赞、改造等按钮自身权限控制。
- 验证:`npx vitest run src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx` 覆盖访客态纵向滑动不弹登录且触发下一条推荐。
- 关联:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。
## Windows junction worktree 下 Vitest 定向路径失败先切真实路径
- 现象:在 Windows junction 或映射 worktree 中运行前端测试时,Vitest 可能把同一文件解析为另一盘符路径,误报文件不存在。
@@ -5433,12 +5312,6 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- 部分旧包补充:rollback 的规范图/背景图必须保存旧字节与旧 manifest entry,不能把这两项缺失隐式当成空内容;显式 `regenerate` 因此只在这两项可信可回滚时开放。历史主图集、私有回执、公开清单或 canonical 切片可以缺失,但八个严格路径与受管顶层 asset identity 必须逐项冻结其真实 `Present/Some` 或 `Missing/None` 状态,补偿也必须恢复相同存在性。不要因为旧美术包缺切片而阻断重生成,也不要把本轮新建的严格文件误记成旧文件。
- 对话扫描与 claim 补充:历史中出现 `User A / User B / Assistant B` 时,B 已回答不代表 A 已回答,扫描必须继续寻找 A。成功 Direct 回复在 Rust 返回前已经落盘,前端冗余 append 失败不能据此重跑;普通错误回复的显式落盘失败时,恢复 claim 要保持到 React fallback writer 的同一 messageId append 明确收敛。writer 成功或明确失败后才释放;失败路径要停止该消息的自动迟到重试,再由显式重新加载对话复用原 stable turn。终态后及时删除 claim,避免 Set 无界增长。
## GDD 历史审批回执误触发当前恢复提示(2026-08-27)
- 现象:修改 GDD 后新版本标题和内容已正确落盘,但审批卡一直显示“审批状态正在恢复”。
- 原因:`approval pending` 是当前 lineage 最新 GDD 的单例投影;恢复扫描却让每个历史 receipt 都拿它做 identity 比对。旧 receipt 与新 pending 不同并不表示损坏。
- 处理:历史 receipt 只修复自身投影;只有最新 GDD 的 receipt 才能校验、更新或清理当前 approval pending。不要在前端隐藏 `recoveryPending`,也不要取消最新版本的 identity fail-closed 检查。
## Native shell CI 不能在测试阶段重新解析 Cargo registry(2026-08-26)
- 现象:原生壳 job 的依赖预取成功后,AGC 检查仍在 `platform-llm` 测试阶段重新更新 registry index,并因 `symphonia` 下载的 TLS EOF 失败。
@@ -5648,23 +5521,12 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- 守卫三层:Rust `asset_category_mapping_covers_every_kind` 穷举断言(旧的 `"UI" → unclassified` 断言正是这条 bug 的反向加固,已删);`ui_editor/resource_bridge.rs` 的 `bridge_is_idempotent_and_installs_source_image` 走真实生产函数 → 真实写入 → 断言落盘 `category`;TS `parseGameCreationAppAssetKind` / `console.warn` 用例钉住"非 canonical 值收口 + 留痕原值"。
- 关联:`server-rs/crates/shared-contracts/src/game_creation_app/asset_kind.rs`、`packages/shared/src/contracts/gameCreationApp.ts`、`apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource_bridge.rs`、`apps/ai-game-creator-shell/tests/assetKind.test.ts`。
## AGC 资源搜索栏改成「临时叫出」的浮层,工具条带与它的下移逻辑一并撤掉(2026-09-11)
## AGC 资源筛选浮层不能遮住栏目操作
- 现象:资源搜索框与栏目标题栏在真机上仍然重合;分页态标题栏右端的「资源总览」(回到资源总览)按钮**点不动**。
- 成因(两层,同一处):常驻工具条带 `.game-resource-book-tools` 是 `position: absolute; top: 0; left/right: 0; z-index: 40` 的**横跨整行**盒子——只有搜索框、筛选条是不透明内容,其余透明区域照样接收点击;它正好盖在栏目标题栏(`z-index: 30`)那条带上,把「资源总览」按钮的命中区域整段吃掉。而管理区 `padding-top: var(--game-resource-book-tools-height)` 与画本场景按同一变量 `inset` 下移,两处都只为"给搜索框腾一条带"存在,带子本身仍与标题栏争同一条水平带。
- 处理(用户指定方案):搜索条不常驻。常态只在右下角缩放 Dock 末尾留一颗搜索按钮(`aria-label="搜索资源"`),`Ctrl/Cmd+F`(macOS `Cmd+F`)或点它把浮层叫出来;关闭复用美术画布的 `useImageCanvasFloatingOptionDismiss`(Esc / 点外部,Escape 在 document 上截断,不会连带清画布选中)。随后撤掉工具条带本身:管理区 `padding-top`、场景 `inset` 变量、`resourceBookToolsRef` 与 ResizeObserver 测高 effect、`resourceCanvasSceneSize()`、缩放与滚轮锚点里减掉工具条带的两处算式全部删除,`resourceBookSceneSize` 重新等于管理区盒子(场景盒子 = 可排布盒子,更自洽)。
- 几何口径(改这些数字前先读):搜索浮层 `position: absolute; right: 14px; bottom: 58px; width: min(320px, calc(100% - 28px))`——`58px` = Dock 的 `14px` + Dock 高度 `34px` + 10px 间隙,且**刻意不写 `top`**,所以它永远落在管理区底部、不可能压到标题栏那条带;状态提示与附件失败条的浮层容器 `.game-resource-book-notices` 用 `top: 58px`(= 标题栏 `min-height: 42px` + 16px)落在标题栏之下。
- 浮层必须钉住的两条:① 提示层容器整层 `pointer-events: none`、只有提示条自己 `pointer-events: auto`(横跨整行的透明盒子吃点击正是上面那条 bug);② 搜索条件不随浮层收起而清除(`searchText` 是画布状态,`type=search` 的原生 Esc 清空已被 `preventDefault` 挡掉),"当前有搜索"由 Dock 按钮的 `.is-active` 高亮表达。
- 门禁为什么抓不到:jsdom 不做布局命中判定,`getByLabelText` 也不校验可见性;可见性与重叠只能钉声明(`tests/appSurface/project-development.suite.ts` 的 `styleRuleBody` 口径),入口另用真实 DOM 用例覆盖——`tests/appSurface/harness.ts` 的 `openResourceSearch()` 先按产品入口打开浮层,浮层没打开时 `getByLabelText` 直接抛错,堵掉"元素在但点不到"的假绿。
- 关联:`apps/ai-game-creator-shell/src/styles.css`、`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`apps/ai-game-creator-shell/tests/appSurface/harness.ts`、`src/components/image-editor/useImageCanvasFloatingOptionDismiss.ts`。
- 后续(2026-09-13):搜索浮层与筛选浮层合并成唯一的 `.game-resource-filter-panel`(关键词 / 所在区域 / 自定义标签),Dock 只留放大镜按钮、`.game-resource-search` 已删除,关闭改用面板自己的「点外部 + Esc」;条目里的 `right: 14px` + `bottom: 58px`(= Dock 14px + 34px + 10px 间隙、刻意不写 `top`)与「条件不随浮层收起而清除」两条口径原样沿用,harness 的 `openResourceSearch()` 现名 `openResourceFilterPanel()`,见 `decision-log.md` 2026-09-13 一条。
## AGC 画布侧分类筛选条已按用户要求移除,共享筛选条组件保留(2026-09-11)
- 用户口径:「这个筛选功能有点鸡肋(悬浮文字这几个),给他去掉吧」——指资源画布顶部那排分类 chip(全部 / UI 交互 / 角色与对象 / 场景与环境 / 音频 / 文档 / 待归类)与同一排的画布标签 chip。
- 处理:只移除画布上的这一处 `PlatformResourceFilterBar` 用法,**共享组件本身保留**(`@` 素材面板 `ImageCanvasProjectAssetPickerDialog` 与参考图弹窗仍在用)。随它一起删掉只为它存在的画布筛选轴:`resourceCanvasCategoryFilters` / `showResourceCategoryFilter` / `resourceCanvasActiveTags` / `resourceTagLibrary`,以及 `visibleResources` 里两个此后恒真的条件——画布可见资源只由搜索框收窄。
- 不要顺手改分区:栏目大纲、栏目标题栏、`categoryOrder`、`projectResourcesByCategory` 与「资源总览」入口是另一回事,本次一行未动。将来若要恢复分类筛选,应重新设计入口,而不是把这排横跨画布顶部的 chip 行加回来。
- 关联:`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`packages/shared/src/components/PlatformResourceFilterBar.tsx`。
- **现象与原因**:横跨画布的透明浮层仍会接收点击,可能让栏目标题栏的「资源总览」看得见却点不动;jsdom 不做真实布局命中判定,单靠元素存在性测试抓不到重叠。
- **现行口径**:右下角 Dock 打开唯一的 `.game-resource-filter-panel`,提供关键词、所在区域和自定义标签筛选;面板贴 Dock 向上展开(`right: 14px; bottom: 58px`,不写 `top`),关闭时保留筛选条件。状态提示容器 `.game-resource-book-notices` 整层 `pointer-events: none`,仅提示条恢复点击,并位于标题栏下方。关闭面板用自身的点外部与 Esc 逻辑。
- **排查**:同时核对浮层的 CSS 位置和点击命中;测试入口使用 `openResourceFilterPanel()` 实际打开面板,再验证内部控件。
- **关联**:`apps/ai-game-creator-shell/src/styles.css`、`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`apps/ai-game-creator-shell/tests/appSurface/harness.ts`。
## AGC 项目对话的输入区必须留在对话框内部(2026-09-11)
@@ -5891,12 +5753,10 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- **排障注意**:Release GUI 没有 panic hook、也没有 stderr,panic 文案不会落盘;`diagnostics/application.log` 只会停在崩溃前最后一行(本次停在 `agent.codex_app_server.remote_control disabled` 之后),所以「应用日志没有异常」不能当作「没有 Rust panic」,要结合 WER 事件、minidump 的 fail-fast 参数与调用线程上下文判断。
- **关联**:`apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/mod.rs`、`apps/ai-game-creator-shell/src-tauri/src/main.rs`(`install_agent_runtime_async_runtime_with_deep_stack`)、`apps/ai-game-creator-shell/src-tauri/src/commands.rs`(`cancel_direct_codex_turn`)。
## 2026-09-24 Windows 本机跑 server-rs 全量 workspace:两类已知失败,其余全绿
## Windows 本机跑 server-rs 全量测试时先区分环境失败
- **命令与结果**(2026-09-24 实跑,镜像 CI):`cargo test --locked --workspace --exclude spacetime-module --no-fail-fast --manifest-path server-rs/Cargo.toml` → 2 个 target 失败,其余 crate 全绿:`api-server --bin api-server` **1124 passed / 11 failed / 8 ignored**,`server-manager-panel --lib` **5 passed / 1 failed**。
- **一、`api-server::wallet_refund_outbox::tests` 全部 11 条本机失败**:panic 是 `Io(Os { code: 5, kind: PermissionDenied })`,来自 outbox 用 `fs::hard_link` 在 `%TEMP%` 下做原子发布(`wallet_refund_outbox.rs` 201/449/468 行)。这是本机环境的既有基线——decision-log 多条验证记录早就写着「`wallet_refund_outbox` 本机环境失败,与基线一致」(当时是 3 条,模块长到 12 条测试后失败数同步变大)。不要为了让它变绿改断言或放宽发布语义;生产与 CI 在 Linux 上跑。
- **二、`server-manager-panel::fonts::tests::finds_existing_system_cjk_font` 失败**:`find_cjk_font_candidate()` 依赖 fontconfig 的 `fc-match`,Windows 本机没有该命令,因此断言「本机至少有一个 CJK 字体」必红。这是 Linux 专属用例,同样不要改成「找不到也算过」。
- **口径**:server-rs 的完整测试口径以 Linux(CI 的 `Server-rs tests` job)为准;本机跑全量时只把上面两类失败视为环境噪声,其余任何失败都要当真实信号处理。
- `wallet_refund_outbox` 用 `fs::hard_link` 在 `%TEMP%` 下原子发布;若 Windows 返回 `PermissionDenied`(OS error 5),先核对本机硬链接权限与 Linux CI 结果,不要为本机限制放宽正式发布语义。
- `server-manager-panel::fonts::tests::finds_existing_system_cjk_font` 依赖 fontconfig 的 `fc-match`,Windows 本机没有该命令时不能用它判断字体逻辑是否回归。完整 workspace 测试以 Linux CI 为准;其它失败仍需单独排查。
## 2026-09-16 AGC 壳跑过 `cargo test` 后前端 typecheck 必红:ts-rs 导出把 `generated/*.ts` 重写成另一种形状
@@ -6178,13 +6038,13 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
- **根因**:这个 harness 用桩 `systemctl` 驱动巡检脚本,Windows 上 `spawn systemctl` 直接 `ENOENT`,服务态检查不可能通过;它验证的是 Linux + systemd 的生产形态。
- **做法**:本机只跑 `npm run check:production-health-patrol-env`(本轮 OK)确认巡检变量口径;`check:production-health-patrol` 留给服务器/CI 复核,别据此判定巡检脚本本身坏了。
## 2026-09-28 线上 dev-mac 的更新包是「上一版 + 另一个渠道身份」:清单扫描选中了构建目录残留产物
## 按目录扫描更新产物时须核对包内版本与渠道身份
- **现象**:线上 `https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/dev-mac/latest.json`(`version=0.1.142`、`commit=c07c10c0c`)把更新包指向 `陶泥儿 Release.app.tar.gz`;下载解开看 `Contents/Info.plist`:`CFBundleShortVersionString=0.1.139`、`CFBundleIdentifier=world.genarrative.ai-game-creator.release`、`CFBundleName=陶泥儿 Release`。同一份清单的首装 DMG 却是 `陶泥儿开发版_0.1.142_aarch64.dmg`。也就是说签名是真的、对象也在,但**版本与渠道身份都是错的**。
- **根因**:`build-macos-ci.mjs` 以前只 `rmSync` 「本轮要写的确切文件名」,构建目录里上一轮(或其它渠道身份)留下的 `*.app.tar.gz` 不会被清;而 `generateUpdateManifest()` 是**扫描构建目录、按优先级挑产物**(同名优先级再按字典序),于是挑走了残留的 release 身份包。mac 构建机是复用 workspace 的,这类残留会长期存在。
- **判据**:只读核对要**打开产物看身份**,不能只看「地址存在 + 签名匹配」。`npm run check:agc-update-channel-manifests`(`AGC_UPDATE_VERIFY_DOWNLOAD=1`)现在会解出 mac 包的 `Info.plist`、以及 Windows 安装包的 PE `RT_VERSION`(`scripts/pe-version-info.mjs`),断言「产物内版本 == 清单版本」且「产物内产品名/identifier == 本渠道身份」,并额外断言**不同渠道的更新包不得字节相同**;本轮对线上取样得到三条 FAIL,正是这个缺陷。字节级佐证:`dev-mac/0.1.142/陶泥儿 Release.app.tar.gz` 与 `release-mac/0.1.139/陶泥儿 Release.app.tar.gz` 的 sha256 完全相同(`1dfc9deb79f7…`),说明 dev 分区里放的就是 release 那一次构建的产物。
- **处理(2026-09-28 已修)**:构建前按后缀清空 `macos/` 下的 `*.app.tar.gz`、`*.app.tar.gz.sig`、`*.dmg`、`*.dmg.sha256`;构建后读 `.app/Contents/Info.plist` 断言版本/identifier/产品名;生成清单后再断言 `release.artifact` 就是本轮那一个。守卫在 `apps/ai-game-creator-shell/scripts/macos-release-identity.mjs`,回归用例直接用线上那份 0.1.139 release 身份包(`node --test` 59 passed)。
- **教训**:凡是「按目录扫描挑产物」的发布步骤,都要么先清空同类产物、要么按本轮预期路径断言;只删「本轮要写的名字」等于把上一轮的坏包留在候选集里。现在共享入口 `generateUpdateManifest()` 也补了与平台无关的 `assertArtifactVersionMatches()`(文件名带 `_0.1.153_` 这类版本段时必须是本轮版本),所以即使将来某个流水线不再 `git clean -fdx`,旧安装包也会被拒绝而不是被发出去。还有一条更一般的:核对线上清单时,先看 decision-log 的现行口径(这里 macOS 已是 arm64 单架构),别拿过期里程碑文字当契约。
- **风险**:复用构建工作区里残留的其它版本或渠道更新包,会被扫描式清单生成器选中;对象存在且签名有效仍可能发错包。
- **现行口径**:macOS 构建前清理同类 `*.app.tar.gz`、签名与 DMG 产物,构建后检查 `Info.plist` 的版本、identifier、产品名,并断言清单引用的是本轮包。核对已发布清单时设置 `AGC_UPDATE_VERIFY_DOWNLOAD=1` 后运行 `npm run check:agc-update-channel-manifests`:检查 macOS `Info.plist`、Windows PE 版本资源与清单一致,以及渠道间包体隔离。文件名的版本段也须与本轮版本一致。
- **排查**:遇到「清单签名正常但客户端更新到错误版本」时,先打开包核对身份,再查扫描目录的残留;不要只看 URL 和签名。
- **上线边界**:构建侧修复不代表已发布对象已修正,线上包仍须按上述清单核验重新确认。
- **关联**:`apps/ai-game-creator-shell/scripts/build-macos-ci.mjs`、`apps/ai-game-creator-shell/scripts/macos-release-identity.mjs`、`apps/ai-game-creator-shell/scripts/build-release.mjs`。
## 2026-09-28 DirectProject 空历史注入被新版 app-server 拒绝:新项目第一条消息直接「执行通道中断」
@@ -6210,12 +6070,9 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
## 2026-09-29 Vite dev 冷启动会让 web E2E 的首个 goto 超时,别当成页面回归
- **现象**:`npm run dev` 刚起(Vite 冷缓存)时跑 `check:game-distribution-web-e2e`,三轮 API 断言(管理员登录、作者注册、真实游戏发布上传→送审→审核)全过,但 `page.goto('http://127.0.0.1:3000/games', { waitUntil: 'domcontentloaded' })` 以 `Timeout 30000ms exceeded` 失败——看起来像"网页打不开"。
- **实测**:冷启动时 `Invoke-WebRequest /games` 花了 **20,171 ms**(`/` 约 2,146 ms),随后连续两次 4,208 ms / 10,119 ms,预热完成后 **14 ms**。原因是 Vite dev 按需编译该路由的模块图,首个请求最贵。
- **处理(2026-09-29 当天补正:`fetch` 预热不够)**:第一版只在导航前 `fetch` 预热 `/games`、`/games/detail?id=…`、`/games/play?id=…` 并把首个 `goto` 超时提到 120s。但 `fetch` 只拿到 SPA 外壳(HTML 里没有路由模块),**并没有真的让 Vite 编译那些模块**,所以冷启动下断言阶段照样假失败:实测第一次卡在 `button.game-card:visible` 的 20 秒等待,第二次(目录已热)又推进到游玩页 `getByRole('button', { name: '开始游戏' })` 的 15 秒等待——每次都停在「该路由第一次被**浏览器**加载」的那一步。
现在改为**浏览器级预热**:起浏览器后用一次性 context 真的把三条路由走一遍(`page.goto(url, { waitUntil: 'domcontentloaded', timeout: 120_000 })` + 2 秒 settle,失败只告警),让 Vite 先把模块编译落缓存,再建断言用的 context;首个 `goto` 的 120s 超时保留。另外三个 web 脚本原本就用 `waitUntil: 'commit', timeout: 120_000` + 60s 等待,属同一类防护,别再去掉。
- **复验**:重启 `npm run dev`(Vite 冷缓存)后跑修复版 → **24/24 PASS**;同一次冷启动下修复前的脚本会在上面两处各失败一次;四条 web E2E(`web` / `publish` / `publish-recovery` / `a11y`)本次全部复跑通过。
- **判据**:遇到"某一步等元素超时 + 前面的 API 断言全过",且这一步正好是该路由第一次在浏览器里加载,先怀疑 Vite 按需编译。`curl`/`Invoke-WebRequest` 量到的 20s 级冷启动只能解释导航慢;**纯 HTTP 预热不算预热**,要预热就得用浏览器真的走一遍。
- **现象**:`npm run dev` 冷启动后,发行 web E2E 的 API 断言通过,浏览器首次进入 `/games`、详情或游玩页却在导航或元素等待处超时。
- **原因与处理**:Vite 会按需编译浏览器实际加载的路由模块;单纯 `fetch` 路由 URL 只拿到 SPA HTML,不能完成预热。用一次性浏览器 context 实际走过相关路由,再创建断言用的 context;冷导航保留足够的超时。
- **排查**:若失败恰在该路由首次浏览器加载处,先核对冷编译耗时;不要把 API 已通过、浏览器首次超时直接判为页面逻辑回归。
## 2026-09-29 客户端"本地导出上限"不等于"平台发布上限"
@@ -6225,11 +6082,10 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
## 2026-09-29 AGC 发布类 Job 变红或卡住,先看构建节点在线状态(不是构建脚本)
- **现象**:`Genarrative-Scheduled-Release-Trigger` 连续 FAILURE,日志尾部是 `Cancelling nested steps due to timeout` + `Genarrative-Agc-MacOS-Build 等待失败: Build of Genarrative-Agc-MacOS-Build was cancelled`(等满 2 hr);`Genarrative-Agc-MacOS-Build` 自己的日志是 ABORTED,且停在 `// node` 之前、只有 `Still waiting to schedule task` / `'genarrative-agc-macos-01' is offline`。两者是同一条根因的不同表现,别去翻构建脚本或产物验证逻辑。
- **现象**:定时发布 Job 因等待 macOS 构建超时而失败,子 Job 停在 `// node` 前,日志显示 `Still waiting to schedule task` 或节点 offline。此时先查节点调度与连接状态。
- **先查这个**:`GET https://jenkins.genarrative.world/jenkins/computer/api/json?tree=computer[displayName,offline,offlineCauseReason]`(注意实例挂在 **`/jenkins` 上下文路径**下,用根路径会 302/403)。`offlineCause` 为 `OfflineCause$ChannelTermination` 表示 agent 连接断开(机器关机/休眠/agent 进程退出/网络中断),此时 `GET /jenkins/computer/<节点名>/config.xml` 里的 `launcher` 决定能不能远程拉起。
- **不能远程拉起的形态**:`genarrative-agc-macos-01` 是 `JNLPLauncher`(inbound WebSocket,`remoteFS=/Users/suzmii/Library/Jenkins/agents/genarrative-agc-macos-local`),只能在那台 Mac 本机把 agent 起回来;Jenkins 侧没有可用入口。所以「macOS 渠道一直没有新版本」这类问题的第一问是:那台 Mac 是否开机、agent 是否在跑。(该节点 2026-09-24 19:54:51 +08:00 起因 `ChannelTermination` 离线,`dev-mac` 的 `latest.json` 因此一直停在 0.1.142。)
- **不能远程拉起的形态**:macOS 节点采用 `JNLPLauncher` inbound WebSocket,需要在 Mac 本机恢复 agent;Jenkins 控制器不能直接启动它。macOS 渠道迟迟没有新版本时,先确认机器和 agent 在线,再排查构建脚本。
- **离线期间不会产生半成品**:mac 构建在 `// node` 之前就被中止,Post Action 明确「未走到归档阶段时不会有任何产物,也不会写 OSS」;`Jenkinsfile.scheduled-release-trigger` 里给 mac 分支传 `SKIP_IF_SUPERSEDED=true`,节点回来后排队中的旧构建会自行让位。
- **同时在查的东西**:`dev-mac/0.1.142` 清单指向的是 `0.1.139` 的 release 身份包(构建目录里上一轮的 `<产品名>.app.tar.gz` 残留被扫描式产物选择器挑走)。修复已在 `build-macos-ci.mjs` + `macos-release-identity.mjs` 落地;`npm run check:agc-update-channel-manifests` 现在会自动报出 `bundle=0.1.139 manifest=0.1.142`、身份 release 以及「渠道隔离」下的 `dev-mac = release-mac`(同一 sha256),不需要人工比对。
## 2026-09-29 共享组件样式表加载顺序变了,同优先级的页面覆盖会静默失效
@@ -6242,11 +6098,5 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
## 2026-09-29 用夹具直接验证「已安装渠道包里的随包 Codex 能不能跑」(不用开 GUI)
- **做法**:仓库夹具支持把被测对象换成任意 exe,所以可以直接审问某个已安装的渠道包:
`npm run check:agc-direct-execution-fixture -- --agc-exe "<%LOCALAPPDATA%\<产品名>\genarrative-ai-game-creator-shell.exe>" --cases completed`
它只在临时目录里造项目、起 loopback Provider,跑的是 CLI/执行链路,不改被装的包、不碰 GUI。
- **判读口径**(把「包坏了」和「客户端逻辑旧了」分开):
- stderr 里出现 `Codex app-server JSON-RPC 失败:…` → 随包 sidecar **已经起来并回了错**,问题在请求体/客户端逻辑,不在随包依赖;`agent.codex_app_server.remote_control disabled reason=provider-proxy-auth` 是夹具本地模式的预期行,不是故障。
- 安装目录 `coding-agent/win-x64/manifest.json` 的 6 个 SHA-256 用来证明载荷本身没坏(Windows 侧还有 `codex-package.json` 声明 `codex-cli` 版本)。
- 只有在 sidecar 根本起不来时,才会看到缺组件/版本不匹配类报错。
- **实例**:2026-09-29 用这招查出本机已装 `陶泥儿开发版 0.1.154`(源码 `76cdb96c5`)会在第一条 direct 回合报 `items must not be empty`——那是 `7ec984d0d` 已修、`0.1.158`(`e1dacccec5`)才包含的缺陷;同一条 `completed` 用例在当前 master 的调试构建上通过,因此结论是「装着的版本旧」而不是「打包少东西」。
- **做法**:`npm run check:agc-direct-execution-fixture -- --agc-exe "<已安装渠道包的主程序绝对路径>" --cases completed`。夹具在临时项目和 loopback Provider 上跑 CLI 执行链路,可直接核对已安装包的随包 Codex。
- **判读**:若报 `Codex app-server JSON-RPC 失败`,sidecar 已启动并返回协议错误,应先检查请求体及已安装包的源码版本;若根本无法启动,再查安装目录 `coding-agent/win-x64/manifest.json` 的组件哈希、`codex-package.json` 的版本和缺失组件。夹具模式中的 `remote_control disabled reason=provider-proxy-auth` 是预期诊断行。
-98
View File
@@ -10,9 +10,6 @@ const nativeShellPlanPath =
'docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md';
const hostBridgeProtocolDocPath =
'docs/【前端架构】宿主壳能力统一协议-2026-06-17.md';
const developmentWorkflowDocPath =
'docs/project-memory/shared-memory/development-workflow.md';
const decisionLogDocPath = 'docs/project-memory/shared-memory/decision-log.md';
const rootPackageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
const mobileShellConfigCheckSource = fs.readFileSync(
'apps/mobile-shell/scripts/check-config.mjs',
@@ -3774,107 +3771,12 @@ function extractDocumentMethodTable(source) {
);
}
function assertNativeShellScaffoldScanWording(source, label) {
if (
source.includes('三端生产壳临时替身词扫描') ||
source.includes('三端壳生产源码') ||
source.includes('壳生产源码禁替身') ||
!source.includes('H5 HostBridge 真实调用链的临时替身词扫描')
) {
throw new Error(
`${label} must document production scaffold scanning for the H5 HostBridge call chain`,
);
}
}
function assertNativeShellCapabilityPlan() {
const planSource = fs.readFileSync(nativeShellPlanPath, 'utf8');
const hostBridgeProtocolDocSource = fs.readFileSync(
hostBridgeProtocolDocPath,
'utf8',
);
const developmentWorkflowDocSource = fs.readFileSync(
developmentWorkflowDocPath,
'utf8',
);
const decisionLogDocSource = fs.readFileSync(decisionLogDocPath, 'utf8');
if (
planSource.includes('permissions` 必须只包含 `core:default`') ||
planSource.includes(
'permissions=["core:default","allow-host-bridge-request"]',
)
) {
throw new Error(
'native shell plan must not document core:default as a desktop capability permission',
);
}
if (
!planSource.includes('主窗口 capability 只授予 `allow-host-bridge-request`')
) {
throw new Error(
'native shell plan must document the minimal desktop capability permission',
);
}
for (const staleShareWording of [
'深链、系统分享、即时本地通知',
'重复执行支付、登录、系统分享、文件导入导出',
'发布分享弹窗只有声明 `share.open` 时才显示“系统分享”',
'发布分享弹窗只有在宿主声明 `share.open` 时才提供“系统分享”动作',
]) {
if (
planSource.includes(staleShareWording) ||
hostBridgeProtocolDocSource.includes(staleShareWording) ||
decisionLogDocSource.includes(staleShareWording)
) {
throw new Error(
`native shell docs must describe share.open as a host-specific controlled share action: ${staleShareWording}`,
);
}
}
for (const requiredShareWording of [
'深链、受控分享动作、即时本地通知',
'重复 `id` 不得重复执行支付、登录、受控分享动作、文件导入导出',
'发布分享弹窗在 Expo 移动壳声明 `share.open` 时提供“系统分享”动作',
'发布分享弹窗在 Tauri 桌面壳中展示“复制分享文案 / 已复制 / 复制失败”',
'并按 `hostShell` 区分 Expo 系统分享面板和 Tauri 剪贴板复制表达',
]) {
if (
!planSource.includes(requiredShareWording) &&
!hostBridgeProtocolDocSource.includes(requiredShareWording) &&
!decisionLogDocSource.includes(requiredShareWording)
) {
throw new Error(
`native shell docs missing host-specific share.open wording: ${requiredShareWording}`,
);
}
}
for (const staleMobilePermissionText of [
'Android `permissions` 不手写显式权限',
'最终 Expo public config 只允许扫码能力由 `expo-camera` plugin 带入 `android.permission.CAMERA`',
'移动拍摄不请求麦克风权限',
]) {
if (
planSource.includes(staleMobilePermissionText) ||
hostBridgeProtocolDocSource.includes(staleMobilePermissionText)
) {
throw new Error(
`native shell docs must not keep stale mobile permission wording: ${staleMobilePermissionText}`,
);
}
}
assertNativeShellScaffoldScanWording(planSource, 'native shell plan');
assertNativeShellScaffoldScanWording(
hostBridgeProtocolDocSource,
'HostBridge protocol document',
);
assertNativeShellScaffoldScanWording(
developmentWorkflowDocSource,
'development workflow document',
);
assertNativeShellScaffoldScanWording(
decisionLogDocSource,
'decision log document',
);
assertShellLayerLayoutDocumented(planSource, 'native shell plan');
assertShellLayerLayoutDocumented(
hostBridgeProtocolDocSource,
-124
View File
@@ -2055,12 +2055,6 @@ const checks = [
reason:
'Pingora 试点文档必须记录 realpath canary 的 Nginx log_format 加载顺序踩坑。',
},
{
file: 'docs/project-memory/shared-memory/pitfalls.md',
includes: 'Pingora realpath canary include 要晚于 log_format',
reason:
'团队共享踩坑必须记录 realpath canary 独立 server 在 conf.d 中的加载顺序要求。',
},
{
file: 'scripts/jenkins-server-provision.sh',
includes: 'genarrative-health-patrol.timer',
@@ -3349,16 +3343,6 @@ const checks = [
includes: 'manifest.files` 视为闭集',
reason: '生产运维文档必须说明证据验真默认闭集归档口径。',
},
{
file: 'docs/project-memory/shared-memory/decision-log.md',
includes: 'manifest.files` 视为闭集',
reason: '团队共享决策必须记录 Pingora 证据验真闭集归档口径。',
},
{
file: 'docs/project-memory/shared-memory/pitfalls.md',
includes: 'manifest.files` 视为闭集',
reason: '团队共享踩坑必须记录 Pingora 证据目录不能夹带未登记条目。',
},
{
file: 'docs/technical/【开发运维】Pingora独立网关试点-2026-06-11.md',
includes: 'pingora-gateway-env-shadow-switch.mjs --apply',
@@ -3371,18 +3355,6 @@ const checks = [
reason:
'生产运维文档必须记录 rollback apply 前用随包脚本恢复 Pingora shadow env。',
},
{
file: 'docs/project-memory/shared-memory/decision-log.md',
includes: 'pingora-gateway-env-shadow-switch.mjs --apply',
reason:
'团队共享决策必须记录 rollback apply 前用随包脚本恢复 Pingora shadow env。',
},
{
file: 'docs/project-memory/shared-memory/pitfalls.md',
includes: 'pingora-gateway-env-shadow-switch.mjs --apply',
reason:
'团队共享踩坑必须记录 rollback apply 前用随包脚本恢复 Pingora shadow env。',
},
{
file: 'deploy/nginx/README.md',
includes: 'scripts/deploy/pingora-gateway-env-shadow-switch.mjs',
@@ -6772,39 +6744,6 @@ const checks = [
reason:
'Nginx README 必须说明 direct live 静态响应头摘要缺关键证据时会阻断证据包。',
},
{
file: 'docs/project-memory/shared-memory/decision-log.md',
includes: 'API / WSS 检查不落原始响应头',
reason: '团队共享决策必须记录 direct live 只归档静态白名单响应头。',
},
{
file: 'docs/project-memory/shared-memory/decision-log.md',
includes: 'directLiveStaticHeaders',
reason:
'团队共享决策必须记录切流 manifest 提升 direct live 静态响应头摘要。',
},
{
file: 'docs/project-memory/shared-memory/decision-log.md',
includes:
'静态头摘要缺少缓存头、校验头、Range `206 + Content-Range`、ETag 304 / Last-Modified 304 证据时整包记为 `CRITICAL`',
reason: '团队共享决策必须记录静态响应头摘要不完整会阻断证据包。',
},
{
file: 'docs/project-memory/shared-memory/pitfalls.md',
includes: '不要把 `direct-live.json` 当成只有状态码的摘要',
reason: '团队共享踩坑必须提醒 direct live JSON 需要可复盘静态响应头证据。',
},
{
file: 'docs/project-memory/shared-memory/pitfalls.md',
includes: 'manifest.summary.directLiveStaticHeaders',
reason: '团队共享踩坑必须提醒切流 manifest 需要可快速复盘静态响应头摘要。',
},
{
file: 'docs/project-memory/shared-memory/pitfalls.md',
includes:
'摘要缺少缓存头、校验头、Range `206 + Content-Range`、ETag 304 或 Last-Modified 304 证据,证据包会直接记为 `CRITICAL`',
reason: '团队共享踩坑必须提醒静态响应头摘要不完整会阻断证据包。',
},
{
file: 'scripts/ops/pingora-cutover-evidence-bundle.mjs',
includes: 'directLiveAccessLog',
@@ -7032,11 +6971,6 @@ const checks = [
excludes: '补齐 Brotli 取舍',
reason: 'Pingora 试点文档不能继续把 Brotli 取舍留成待办。',
},
{
file: 'docs/project-memory/shared-memory/pitfalls.md',
includes: 'Pingora Brotli 不能只看 Content-Encoding',
reason: '团队共享踩坑必须记录 Pingora Brotli 端到端解压风险。',
},
{
file: 'deploy/pingora/pingora-gateway.env.example',
includes: 'GENARRATIVE_PINGORA_GATEWAY_GZIP_LEVEL=5',
@@ -7137,17 +7071,6 @@ const checks = [
'--direct-pingora-access-log /var/log/genarrative/pingora-gateway.access.log',
reason: '生产运维文档必须展示 Pingora 直连 access log 落盘校验参数。',
},
{
file: 'docs/project-memory/shared-memory/decision-log.md',
includes: '直连日志证据补充',
reason: '团队共享决策必须记录 Pingora 直连 access log 证据门禁。',
},
{
file: 'docs/project-memory/shared-memory/pitfalls.md',
includes:
'--direct-pingora-access-log /var/log/genarrative/pingora-gateway.access.log',
reason: '团队共享踩坑必须记录 Pingora 直连 access log 参数。',
},
{
file: 'docs/【开发运维】本地开发验证与生产运维-2026-05-15.md',
includes:
@@ -7192,26 +7115,6 @@ const checks = [
includes: '不要继续用固定 `http://127.0.0.1/healthz` 与 `"ok":true`',
reason: 'Nginx README 必须防止回退 smoke 示例回到旧 healthz 口径。',
},
{
file: 'docs/project-memory/shared-memory/decision-log.md',
includes: '不再默认假定 `/healthz` 会返回 `"ok":true`',
reason: '团队共享决策必须记录回退 smoke 的真实 Nginx 入口要求。',
},
{
file: 'docs/project-memory/shared-memory/decision-log.md',
includes: '启用后复核应看到端口由 Pingora 占用而不是空闲',
reason: '团队共享决策必须记录启用后 --require-direct 不再检查端口空闲。',
},
{
file: 'docs/project-memory/shared-memory/pitfalls.md',
includes: '不要继续用固定 `/healthz` 与 `"ok":true`',
reason: '团队共享踩坑必须提醒回退 smoke 不能沿用旧 healthz 片段。',
},
{
file: 'docs/project-memory/shared-memory/pitfalls.md',
includes: '启用后 `--require-direct` 复核不再要求端口空闲',
reason: '团队共享踩坑必须提醒端口空闲只属于切流前门禁。',
},
{
file: 'deploy/nginx/README.md',
excludes:
@@ -7234,12 +7137,6 @@ const checks = [
excludes: 'rollback-nginx-smoke-expect-body="ok":true',
reason: 'Pingora 试点 runbook 不应再默认展示 ok:true 作为回退响应体片段。',
},
{
file: 'docs/project-memory/shared-memory/pitfalls.md',
excludes:
'--nginx-smoke-url http://127.0.0.1/healthz --nginx-smoke-host <域名>',
reason: '团队共享踩坑不应再把固定本机 healthz 当成可执行回退示例。',
},
{
file: 'deploy/pingora/pingora-gateway.env.example',
includes: 'GENARRATIVE_PINGORA_GATEWAY_TRUSTED_FRONT_PROXY_CONFIRMED',
@@ -7272,27 +7169,6 @@ const checks = [
reason:
'生产运维文档必须明确多实例 Pingora 与进程内保护的启动 / preflight 边界。',
},
{
file: 'docs/project-memory/shared-memory/decision-log.md',
includes: 'GENARRATIVE_PINGORA_GATEWAY_INSTANCE_COUNT>1',
reason: '团队共享决策必须记录 Pingora 多实例接流保护边界。',
},
{
file: 'docs/project-memory/shared-memory/decision-log.md',
includes: '禁止继续执行部署工作区根部脚本',
reason: '团队共享决策必须记录 API Deploy 不再执行 workspace 根部脚本。',
},
{
file: 'docs/project-memory/shared-memory/pitfalls.md',
includes: 'API deploy 脚本本身也不能继续用部署工作区根部',
reason:
'团队共享踩坑必须记录 workspace 根部 deploy 脚本会掩盖发布包布局问题。',
},
{
file: 'docs/project-memory/shared-memory/pitfalls.md',
includes: '备份脚本、健康巡检脚本和 env 示例目录同样不能从部署工作区兜底',
reason: '团队共享踩坑必须记录备份/巡检脚本也不能使用 workspace fallback。',
},
{
file: 'deploy/nginx/README.md',
includes: 'Pingora 影子网关已通过',