mirror of
https://github.com/motajs/template.git
synced 2026-10-27 19:11:46 +08:00
docs(10): 规划高级 I 七篇与读者修订循环
This commit is contained in:
parent
f53f611db0
commit
f654b39b31
@ -547,14 +547,14 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 →
|
||||
| 7. 数据端缺陷修复 | 15/16 | Complete(07-16 superseded) | - |
|
||||
| 8. 测试重构与接口对齐 | 11/11 | Complete | 2026-10-08 |
|
||||
| 9. 最小浏览器可运行版本 | 15/16 | In Progress| |
|
||||
| 10. 文档重构 | 0/12 | Planned | - |
|
||||
| 10. 文档重构 | 0/20 | Planned | - |
|
||||
|
||||
### Phase 10: 文档重构
|
||||
|
||||
**Goal:** 重构过老、结构不佳、新人难懂的项目文档,面向多背景读者(完全新手 / 不懂代码的作者 / 有基础的作者 / AI Agent)提供分层指南。本阶段采用「伞阶段 + 增量派发」:每次只规划执行一个已派发的具体小任务。**第一批已派发 = 中级教程(`docs/intermediate/`,24 篇中文 markdown)+ 在 VitePress 站点注册它们 + 旧内容清空(D-04 并入本批)**(D-01..D-44)。
|
||||
**Requirements**: (无专属需求 ID;以 `10-CONTEXT.md` 的 D-01..D-44 为需求基线,验收锚在 D-09:用户人工审阅 + 链接全部有效 + 构建通过)
|
||||
**Goal:** 重构过老、结构不佳、新人难懂的项目文档,面向多背景读者(完全新手 / 不懂代码的作者 / 有基础的作者 / AI Agent)提供分层指南。本阶段采用「伞阶段 + 增量派发」:每次只规划执行一个已派发的具体小任务。**第一批已派发 = 中级教程(`docs/intermediate/`,24 篇中文 markdown)+ 在 VitePress 站点注册它们 + 旧内容清空(D-04 并入本批)**(D-01..D-44)。**第二批已派发 = 高级教程 I(`docs/advanced/`,7 篇数据端文档)+ 站点注册 + 独立读者人格审阅 + 用户主导修订循环**(D-53..D-57)。
|
||||
**Requirements**: (无专属需求 ID;以 `10-CONTEXT.md` 的 D-01..D-57 为需求基线,验收锚在 D-09:用户人工审阅 + 链接全部有效 + 构建通过)
|
||||
**Depends on:** Phase 9
|
||||
**Plans:** 12 plans(第一批:中级教程 24 篇 + 站点注册 + D-09 复审修订)
|
||||
**Plans:** 20 plans(第一批:中级教程 24 篇 + 站点注册 + D-09 复审修订;第二批:高级 I 7 篇 + 注册 + 读者审阅 + 用户修订循环)
|
||||
|
||||
Plans:
|
||||
- [ ] 10-01-PLAN.md — Wave 0:旧内容清空 + 站点注册(删除 `docs/guide`、`docs/dev`、`docs/logger`、`docs/api` 与首页 `docs/index.md`;清理 `config.ts` 旧 nav/sidebar 键并解除 apiSidebar/api 依赖;`api.ts` 加缺失目录守卫;新增 nav 入口 + `sidebar['/intermediate/']` 4 组/24 条;报告并等待 checkpoint)
|
||||
@ -569,5 +569,15 @@ Plans:
|
||||
- [ ] 10-10-PLAN.md — 基础接口组引导式重写 finding 5(`hero-items`/`hero-equipment`/`hero-location`/`map-structure`/`map-tiles`/`flags`;6 篇)
|
||||
- [ ] 10-11-PLAN.md — 读者视角审阅(persona 子代理)→ 问题清单 → 迭代修正(≤3 轮)→ 复审清零
|
||||
- [ ] 10-12-PLAN.md — 最终验收:修订后重跑全批验收 + D-09 复审(重跑 10-06 Task 2 门禁)
|
||||
- [ ] 10-13-PLAN.md — 高级 I 第一篇:声明式 vs 命令式接口(地基;旧样板命令式 vs 新样板声明式 + 通用模板)
|
||||
- [ ] 10-14-PLAN.md — 高级 I 第二篇:插件系统(自定义插件、插件互调、依赖排序、生命周期、id 稳定性与迁移)
|
||||
- [ ] 10-15-PLAN.md — 高级 I 第三篇:可存档内容(`ISaveableContent`、三级压缩、注册、存档顺序 / 执行器、跨对象引用、structured-cloneable)
|
||||
- [ ] 10-16-PLAN.md — 高级 I 第四、五篇:录像一致性(`@shouldReplay` / `@ignoreReplay`)+ data-common 的 Store(内容层原始数据、运行时只读)
|
||||
- [ ] 10-17-PLAN.md — 高级 I 第六篇:钩子深入(多对象 / 身份校验 / 复用 / 注册移除 / 异步钩子)
|
||||
- [ ] 10-18-PLAN.md — 高级 I 导览 + 站点注册(`docs/advanced/index.md`、`config.ts` 的 nav / sidebar、六篇互链收口 + 全量构建)
|
||||
- [ ] 10-19-PLAN.md — 高级 I 独立读者人格审阅(不同于中级人格)→ 问题清单 → 迭代修正(≤3 轮)→ 复审清零
|
||||
- [ ] 10-20-PLAN.md — 高级 I 用户主导修订循环 → 用户确认 → 最终格式 / 构建 / SUMMARY 收尾
|
||||
|
||||
**说明:** 每篇中级教程遵循 `10-CONTEXT.md` D-21..D-44 的逐篇要点与阅读顺序(4/8/9/3)。**旧内容删除已并入本批(D-04,Wave 0 / 10-01)**:删除 `docs/guide/*`、`docs/dev/*`、`docs/logger/*`、`docs/api/*` 与首页 `docs/index.md`,并清理 `docs/.vitepress/config.ts` 的旧 `nav`/`sidebar` 键。文档改动为 `docs/intermediate/*.md`(新增)、`docs/.vitepress/config.ts` 与 `docs/.vitepress/api.ts`(缺失目录守卫,保留按包生成机制)。构建命令 `cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"`;`init.ts` 在 `docs/api/` 缺失时安全跳过。本机 PowerShell 禁止 `pnpm.ps1`,命令经 `cmd /c "..."` 执行。
|
||||
|
||||
**高级 I 说明:** 第二批(10-13..10-20)只写**数据端**内容,不含渲染端;七篇文件名与侧边栏显示名以各计划 Task 0 的用户确认为准(D-13)。指向高级 II / III 的内容一律使用代码 span 或 HTML 注释占位,不写真实链接(D-47)。每篇保留「怎么验证」小节,并按 D-48 六步 / D-49 改内置优先 / D-50 模板独立 / D-51 落点与副作用 / D-52 加粗边界执行。构建命令同上;10-18 负责站点注册与全量链接收口,10-19 / 10-20 分别执行独立读者人格审阅与用户主导修订循环,**用户明确确认后方可收尾**(D-46 第 2 阶段 / D-57)。
|
||||
|
||||
147
.planning/phases/10-docs-restructure/10-13-PLAN.md
Normal file
147
.planning/phases/10-docs-restructure/10-13-PLAN.md
Normal file
@ -0,0 +1,147 @@
|
||||
---
|
||||
phase: 10-docs-restructure
|
||||
plan: 13
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- docs/advanced/declarative-vs-imperative.md
|
||||
autonomous: false
|
||||
requirements: []
|
||||
user_setup: []
|
||||
estimate:
|
||||
tokens: 24000
|
||||
raw_tokens: 16000
|
||||
tasks: 3
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "`docs/advanced/declarative-vs-imperative.md` 存在,kebab-case、CRLF、单 H1"
|
||||
- "正文用旧样板命令式与新样板声明式并排对照,讲清「谁决定执行时机」,并给出对照表(执行时机 / 执行次数 / 副作用 / 存档与录像 / 组合方式)"
|
||||
- "声明式例子全部落在当前源码事实上:怪物特殊属性与光环、伤害计算器、道具效果、可存档对象、钩子;不把某个具体效果当作通用模板"
|
||||
- "含独立的「模板:声明式扩展的通用骨架」小节,与示例分开;示例用 class + new,并写明「不懂代码优先改内置」"
|
||||
- "含「怎么验证」小节;数据端 only(正文不出现 `src/client`);指向高级 II/III 的内容只用非断链代码 span 或 HTML 注释占位,不产生真实链接"
|
||||
artifacts:
|
||||
- "docs/advanced/declarative-vs-imperative.md"
|
||||
- ".planning/phases/10-docs-restructure/10-13-SUMMARY.md"
|
||||
key_links:
|
||||
- "回指中级教程的 `../intermediate/enemy-specials.md`、`combat-overview.md`、`enemy-calculator.md`、`data-vs-render.md`、`hooks.md`(这些页面已存在,链接可解析)"
|
||||
- "本篇是 10-14..10-17 的概念基础:插件、存档、录像、钩子都建立在「声明规则、引擎在合适时机执行」之上"
|
||||
- "不新增文件之外的任何配置;不改站点 `config.ts`、不改生产代码"
|
||||
---
|
||||
|
||||
<objective>
|
||||
新建 `docs/advanced/` 并写下高级 I 的第一篇:`declarative-vs-imperative.md`(声明式 vs 命令式接口)。本篇必须把「旧样板写一行即生效」与「新样板声明规则、引擎在合适时机执行」讲透,作为高级 I 其余六篇的共同基础。
|
||||
|
||||
Purpose: 高级教程后续的插件、存档、录像、钩子都要求读者先理解「执行时机由谁决定」。如果这一层没讲透,后面每篇都会变成死记接口(D-54:面向使用者,讲怎么用、怎么改、怎么自定义)。
|
||||
Output: 1 个新 markdown 文件。
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||
@C:/Users/13194/.config/opencode/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/phases/10-docs-restructure/10-CONTEXT.md
|
||||
@.planning/phases/10-docs-restructure/10-RESEARCH.md
|
||||
@.planning/phases/10-docs-restructure/10-PATTERNS.md
|
||||
@docs/intermediate/enemy-specials.md
|
||||
@docs/intermediate/combat-overview.md
|
||||
@docs/intermediate/enemy-calculator.md
|
||||
@docs/intermediate/data-vs-render.md
|
||||
@docs/intermediate/hooks.md
|
||||
|
||||
**写作规范(本计划全部适用):**
|
||||
- 受众是**使用者**,不是引擎编写者(D-54):每个机制都要回答「我能拿它做什么、我该改哪里、我怎么验证」。
|
||||
- 实操按 D-48 六步:目标 → 改哪里 → 复制什么(不含 `import`)→ 改什么 → 怎么验证 → 注意。系统/原理讲解可以保留,但实操必须能分步照抄。
|
||||
- 示例用 class 再 `new`,不写 plain object(D-49);主体讲「改内置」,末尾再讲「何时自己写」,并写明「如果你不怎么懂代码,建议直接改内置」。
|
||||
- 通用模板单独成节,用 `[模板]` / `[需改]` 标注,与具体示例分开(D-50)。
|
||||
- 落点优先写进已有文件,不建议读者新建文件;需要 `#region` 时说明用途;「副作用」要用通俗语言解释(D-51)。
|
||||
- `**加粗**` 不以标点开头或结尾(D-52);提交前按 flanking 规则自检。
|
||||
- 数据端 only:不写渲染端内容,正文不出现 `src/client`。
|
||||
- 指向高级 II / III 的内容只写代码 span(如 `高级教程 II · 自定义录像行为`)或 `<!-- TODO(advanced-II): ... -->`,不写真实链接(D-47)。
|
||||
|
||||
**源码事实(写作时逐条核对,勿凭记忆):**
|
||||
- 怪物特殊属性:`src/data/enemy/special.ts`(`ISpecial23Value`、`createSpecials`、`map.set`);数据里 `src/content/enemy.jsonc` 的 `special` 字段;光环转换器/视图在 `src/data/enemy/aura.ts`;注册在 `src/data/enemy/initialize.ts`。
|
||||
- 计算后怪物:`packages-user/data-system/src/combat/types.ts` 的 `IEnemyContext.buildup` 四阶段说明;中级导读见 `docs/intermediate/combat-overview.md`。
|
||||
- 伤害计算:`src/data/enemy/calculator.ts`(`MainDamageCalculator`、`calculate`、`getCriticalLimit`);接口 `IDamageCalculator` 在 `packages-user/data-system/src/combat/types.ts`;计算器会被临界/显示/战斗反复调用,因此必须无副作用。
|
||||
- 道具效果:`packages-user/data-common/src/store/types.ts` 的 `IItemEffect`(`useEffect(item, state)`);内容层实现 `src/content/item.ts`。
|
||||
- 可存档对象:`packages-user/data-common/src/save/types.ts` 的 `ISaveableContent`;注册与调用在 `packages-user/data-state/src/core.ts`。
|
||||
- 钩子:`packages/common/src/hook.ts` 的 `addHook` / `forEachHook`;中级用法见 `docs/intermediate/hooks.md`。
|
||||
- 旧样板命令式例子只作对比:用注释明确标注「旧样板示意」,不把旧接口名混进新样板示例;若无法核实某个旧接口名,就用行为描述,不臆造 API。
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking-human">
|
||||
<name>Task 0: 向用户汇报本计划并等待「可以执行」</name>
|
||||
<action>向用户汇报:**要做什么**(新建 `docs/advanced/` 并编写 `declarative-vs-imperative.md`,即「声明式 vs 命令式接口」)、**为什么**(这是高级 I 的地基,后续插件、存档、录像、钩子都依赖「执行时机由谁决定」这一层理解)、**大致怎么做**(先用旧样板命令式与新样板声明式并排对照,再用怪物特殊属性/光环、伤害计算器、道具效果、可存档对象、钩子五组事实说明「声明什么、引擎何时执行」,最后给通用模板、常见误区与「怎么验证」)、**影响范围**(仅新增 1 个 `docs/advanced/*.md`;不改 `config.ts`、不改生产代码、不新增外链)。同时请用户确认两个新名称:文件名 `declarative-vs-imperative.md`、页面标题「声明式 vs 命令式接口」(D-13:文档目录/文件名与显示名须先获批)。停下等待用户明确回复「可以执行」四个字;未收到前不得创建目录或写任何文件。</action>
|
||||
<what-built>本计划内容:高级 I 第一篇「声明式 vs 命令式接口」。</what-built>
|
||||
<how-to-verify>阅读上面的「要做什么 / 为什么 / 大致怎么做 / 影响范围」四段,并确认文件名与页面标题;如不同意请直接给出替代名称。</how-to-verify>
|
||||
<resume-signal>回复「可以执行」以继续;其它回复视为未批准,本计划停在此处。</resume-signal>
|
||||
<verify>
|
||||
<human-check>用户已回复「可以执行」,且未对文件名 / 页面标题提出替代。</human-check>
|
||||
</verify>
|
||||
<done>用户回复「可以执行」。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: 编写 declarative-vs-imperative.md 正文</name>
|
||||
<files>docs/advanced/declarative-vs-imperative.md</files>
|
||||
<action>(1) 创建 `docs/advanced/` 并写 `declarative-vs-imperative.md`,单 H1 为「声明式 vs 命令式接口」。(2) 正文按下列顺序组织:①两种写法的一句话区别——命令式是「你写一步、代码走到就执行一步」,声明式是「你声明规则/数据/对象,引擎在定义好的时机调用」;明确「声明式不是不写代码,而是把做什么与什么时候做分开」。②旧样板命令式:用一段明确标注「旧样板示意」的短例子(例如在某段事件脚本里直接循环改周围怪物的攻击,或直接改勇士血量),说明它的后果:只在执行到的那一瞬间生效、要重算只能重跑、每次状态改动都要自己保证记录与一致。旧接口名无法核实就不要写具体名字,用行为描述。③新样板声明式,至少覆盖五组事实:怪物特殊属性与光环(数据里声明 `special`,`special.ts` 定义属性,`aura.ts` 提供转换器/视图,引擎在构建计算后怪物时执行);伤害计算器(`MainDamageCalculator.calculate` 声明 f(enemy, hero),引擎为显示、临界、战斗反复调用,所以必须无副作用);道具效果(`src/content/item.ts` 的 `useEffect` 由引擎在使用/拾取时调用);可存档对象(声明 `saveState` / `loadState`,引擎在存档/读档时调用);钩子(声明钩子类并注册,引擎在事件发生时调用)。每组都要说清「你声明什么」「引擎在哪个时机执行」「为什么这比命令式更稳」。④对照表:行至少含你写什么、何时执行、执行次数、副作用、存档一致性、录像、组合方式、常见错误;列是旧样板命令式与新样板声明式。⑤为什么这样设计:时机集中、从模板重算避免叠加、按优先级/注册表组合、录像只记录玩家入口动作而派生结果重算、计算器被反复调用。⑥「副作用」用通俗话解释:除了返回结果之外还改了外部状态;计算器有副作用会导致临界/显示/战斗结果互相污染。⑦地图伤害按「计算后怪物」计算的说明(D-51):光环若给怪物新增领域类属性,该怪物确实会有领域效果;旧样板无法动态加特殊属性。⑧什么时候仍然用命令式:玩家直接产生的一次性动作(移动、使用道具、一次性脚本)仍调用命令式接口,但这些入口必须计入录像;持续生效的规则用声明式。⑨独立小节「模板:声明式扩展的通用骨架」:给一段 class 骨架,用 `[模板]` / `[需改]` 标出「实现策略接口、注册一次、由引擎调用」的通用形状,与前面的具体示例分开;写明示例是 class 再 `new`,不建议 plain object。⑩「改内置还是自己写」:优先改 `src/data/enemy/*`、`src/data/hook/*` 等已有实现;只有需要独立成组时才自写类;写明「如果你不怎么懂代码,建议直接改内置」。⑪常见误区:把声明式当成「不用写逻辑」、在计算器里改状态、在声明式规则里依赖调用顺序、把一次性动作也硬做成声明式。⑫「怎么验证」:给读者可操作的检查——改一处声明(例如给怪物加一条光环属性或改转换器参数)后不改编排代码,观察结果是否随声明变化;把计算器连续调用两次,确认第二次结果一致且没有额外状态变化;存档后改状态再读档,确认恢复。(3) 适当加 3–5 处链接,只链已存在的页面:`../intermediate/enemy-specials.md`、`../intermediate/combat-overview.md`、`../intermediate/enemy-calculator.md`、`../intermediate/data-vs-render.md`、`../intermediate/hooks.md`。高级 II/III 的内容用代码 span 或 HTML 注释,不写真实链接。(4) 全文 CRLF、单 H1、数据端 only、加粗符合 flanking。完成后运行 `node node_modules/prettier/bin/prettier.cjs --write "docs/advanced/declarative-vs-imperative.md"`。Commit:`docs(10-13): 编写声明式与命令式接口`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Stop'; $p='docs/advanced/declarative-vs-imperative.md'; if(-not(Test-Path -LiteralPath $p)){ Write-Error "MISSING $p" }; $raw=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $p)); if($raw -match "(?<!`r)`n"){ Write-Error "LF-ONLY $p" }; if(([regex]::Matches($raw,'(?m)^# ')).Count -ne 1){ Write-Error "H1-COUNT $p" }; foreach($needle in @('声明式','命令式','IDamageCalculator','enemyContext.buildup','ISaveableContent','addHook','副作用','模板:声明式扩展的通用骨架','怎么验证')){ if($raw -notmatch [regex]::Escape($needle)){ Write-Error "MISSING-CONTENT $needle" } }; if($raw -match 'src/client'){ Write-Error "RENDERER-CONTENT" }; if($raw -match '\*\*[,。!?;:、()《》「」『』【】,.!?;:\(\)\[\]{}"'']' -or $raw -match '[,。!?;:、()《》「」『』【】,.!?;:\(\)\[\]{}"'']\*\*'){ Write-Error "BOLD-FLANK" }; foreach($m in [regex]::Matches($raw,'\]\((\.{1,2}/[^)#]+\.md)(#[^)]*)?\)')){ $t=Join-Path (Split-Path -Parent $p) $m.Groups[1].Value; if(-not(Test-Path -LiteralPath $t)){ Write-Error ("DEAD-LINK " + $m.Groups[1].Value) } }; Write-Output 'STATIC-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "MISSING", "LF-ONLY", "H1-COUNT", "MISSING-CONTENT", "RENDERER-CONTENT", "BOLD-FLANK", or "DEAD-LINK"; or output lacks "STATIC-OK"</fails_when>
|
||||
</verify>
|
||||
<done>文件存在、CRLF、单 H1;五组声明式事实、对照表、副作用解释、地图伤害说明、独立模板节、改内置提示、常见误区与「怎么验证」齐全;无渲染端内容;无死链;加粗符合 flanking。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: 源码核对、格式与构建收口</name>
|
||||
<files>docs/advanced/declarative-vs-imperative.md</files>
|
||||
<action>逐条对照源码核对正文中的接口名、字段名、文件路径与行为描述:`src/data/enemy/special.ts`、`src/content/enemy.jsonc`、`src/data/enemy/aura.ts`、`src/data/enemy/initialize.ts`、`src/data/enemy/calculator.ts`、`packages-user/data-system/src/combat/types.ts`(`IEnemyContext.buildup`、`IDamageCalculator`)、`packages-user/data-common/src/store/types.ts`(`IItemEffect`)、`src/content/item.ts`、`packages-user/data-common/src/save/types.ts`、`packages-user/data-state/src/core.ts`、`packages/common/src/hook.ts`。发现不符就改正文,不凭记忆保留。确认「旧样板示意」没有被误写成可照抄的新接口;确认没有把某个具体效果(如某种光环)当通用模板;确认没有出现渲染端内容;确认高级 II/III 只有代码 span 或 HTML 注释。再跑一次 prettier `--write` 与构建门禁 `cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"`,确认 green、`build complete`、dead link = 0。Commit:`docs(10-13): 核对源码并收口声明式文档`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Continue'; $pf = cmd /c "node node_modules/prettier/bin/prettier.cjs --check docs/advanced/declarative-vs-imperative.md" 2>&1; $pfe=$LASTEXITCODE; $out = cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build" 2>&1; $ec=$LASTEXITCODE; $ErrorActionPreference='Stop'; if($pfe -ne 0){ $pf | Write-Output; Write-Error "PRETTIER-FAIL" }; if($ec -ne 0){ $out | Write-Output; Write-Error "BUILD-FAIL" }; $txt=($out | ForEach-Object { $_.ToString() }) -join "`n"; if($txt -notmatch 'build complete'){ Write-Error "NO-BUILD-COMPLETE" }; if($txt -match 'dead link'){ Write-Error "DEAD-LINK" }; Write-Output 'GATE-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "PRETTIER-FAIL", "BUILD-FAIL", "NO-BUILD-COMPLETE", or "DEAD-LINK"; or output lacks "GATE-OK"</fails_when>
|
||||
</verify>
|
||||
<done>正文逐条与源码一致;prettier 检查通过;构建 green、`build complete`、dead link = 0。</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| 构建期 markdown → 静态站点 | 正文由构建期静态渲染,无运行时输入 |
|
||||
| 站点 → 浏览器读者 | 站内链接是唯一可点击指向面 |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-10-37 | Tampering / XSS | `declarative-vs-imperative.md` | low | mitigate | 只写文本、代码块、表格;不引入原始 `<script>` 或内联 HTML |
|
||||
| T-10-38 | Spoofing | 站内链接 | low | mitigate | 只链已存在的 `docs/intermediate/*` 页面;link 检查断言目标文件存在 |
|
||||
| T-10-39 | Information Disclosure | 正文示例 | low | mitigate | 只用源码已公开的接口与路径;不写密钥、账号、内部凭据 |
|
||||
| T-10-SC | Tampering | npm/pip/cargo installs | low | accept | 本阶段不安装任何包,无供应链安装面 |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- 结构门禁:文件存在、kebab-case、CRLF、单 H1。
|
||||
- 内容门禁:D-53/D-54/D-48..D-52 要点齐全;五组声明式事实均有源码依据;模板与示例分离;含「怎么验证」。
|
||||
- 链接门禁:只链已存在的中间教程页面;高级 II/III 不产生真实链接。
|
||||
- 边界门禁:无渲染端内容;不改 `config.ts` 与生产代码;不新增外链。
|
||||
- 构建门禁:`cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"` 通过,`build complete`,dead link = 0。
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 高级 I 第一篇落位,构建通过。
|
||||
- 读者读完后能回答:旧样板为什么「写一行就生效」、新样板为什么「引擎在合适时机执行」、哪些场景仍该用命令式。
|
||||
- 没有把具体效果冒充通用模板,没有渲染端内容,没有死链。
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/10-docs-restructure/10-13-SUMMARY.md` when done
|
||||
</output>
|
||||
145
.planning/phases/10-docs-restructure/10-14-PLAN.md
Normal file
145
.planning/phases/10-docs-restructure/10-14-PLAN.md
Normal file
@ -0,0 +1,145 @@
|
||||
---
|
||||
phase: 10-docs-restructure
|
||||
plan: 14
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: [10-13]
|
||||
files_modified:
|
||||
- docs/advanced/plugin-system.md
|
||||
autonomous: false
|
||||
requirements: []
|
||||
user_setup: []
|
||||
estimate:
|
||||
tokens: 26000
|
||||
raw_tokens: 17000
|
||||
tasks: 3
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "`docs/advanced/plugin-system.md` 存在,kebab-case、CRLF、单 H1"
|
||||
- "讲清自定义插件:`IDataPluginDefine` 的 `id` / `dependencies` / `awake` / `initialize` / `afterLoad`,以及注册到 `src/data/plugin/list.ts` 的 `dataPlugins`"
|
||||
- "讲清插件间调用:依赖参数收到的已解析插件对象、`state.getDataPlugin(id)` 的返回形状与状态判断;`dependencies` 是 id 字符串数组,方法参数是插件对象数组"
|
||||
- "讲清依赖排序与失败语义:拓扑序、独立插件的登记顺序、循环依赖、缺失依赖、下游被阻断;不把排序细节写成猜测"
|
||||
- "讲清生命周期进阶点:`awake` 在主对象构造期同步执行、`initialize` 在加载前、`afterLoad` 在加载后;两个异步阶段可阻塞加载"
|
||||
- "讲清插件 id 稳定性与版本迁移:id 被依赖与存档标识引用,不能随意改名或复用;给出迁移时的最小做法"
|
||||
- "含独立的「模板:一个插件的最小骨架」小节与至少一个基于现有插件的示例;class + new;写明「不懂代码优先改内置」;含「怎么验证」"
|
||||
- "数据端 only(正文不出现 `src/client`);高级 II/III 只用非断链占位;构建 green、dead link = 0"
|
||||
artifacts:
|
||||
- "docs/advanced/plugin-system.md"
|
||||
- ".planning/phases/10-docs-restructure/10-14-SUMMARY.md"
|
||||
key_links:
|
||||
- "基于 10-13 的「声明规则、引擎在合适时机执行」模型解释三个生命周期时机"
|
||||
- "回指中级 `../intermediate/data-plugins.md`(基础用法)与 `../intermediate/data-state-overview.md`(state 入口)"
|
||||
- "为 10-15 的「可存档内容」提供插件注册位置:插件常在 `awake` 里调用 `state.addSaveableContent`"
|
||||
---
|
||||
|
||||
<objective>
|
||||
编写高级 I 第二篇:`plugin-system.md`(插件系统)。本篇把中级教程里只讲基础用法的插件系统展开到「能自己设计并维护一个插件」的程度:自定义插件、插件间调用、依赖与排序、三个生命周期的进阶点、插件 id 稳定性与版本迁移。
|
||||
|
||||
Purpose: D-53 把插件系统定为高级 I 的核心——后续内容都是基于插件系统的拓展。读者必须能独立写插件、让插件彼此协作,并知道为什么插件 id 不能随便改。
|
||||
Output: 1 个新 markdown 文件。
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||
@C:/Users/13194/.config/opencode/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/phases/10-docs-restructure/10-CONTEXT.md
|
||||
@.planning/phases/10-docs-restructure/10-RESEARCH.md
|
||||
@docs/advanced/declarative-vs-imperative.md
|
||||
@docs/intermediate/data-plugins.md
|
||||
@docs/intermediate/data-state-overview.md
|
||||
@packages-user/data-state/src/types.ts
|
||||
@packages-user/data-state/src/plugin.ts
|
||||
@packages-user/data-state/src/core.ts
|
||||
@src/data/plugin/list.ts
|
||||
@src/data/plugin/shop/plugin.ts
|
||||
@src/data/initialize.ts
|
||||
|
||||
**写作规范(同 10-13):** 受众是使用者(D-54);实操按 D-48 六步;示例 class + new(D-49);通用模板单独成节(D-50);落点优先已有文件、解释副作用、以目的先讲接口(D-51);加粗符合 flanking(D-52);数据端 only;高级 II/III 只用代码 span 或 HTML 注释(D-47)。
|
||||
|
||||
**源码事实(写作时逐条核对):**
|
||||
- `IDataPluginDefine`、`IDataPlugin`、`PluginStatus`、`ISaveableExecutor`、`ICoreState`:`packages-user/data-state/src/types.ts`。
|
||||
- 排序:`packages-user/data-state/src/plugin.ts` 的 `orderPlugins`;返回 `order` / `cyclic` / `missing`;循环依赖与缺失依赖会被阻断,依赖它们的下游也不会初始化。
|
||||
- 生命周期:`packages-user/data-state/src/core.ts`——构造器登记插件并立即 `awake`;`load()` 先 `initialize()`,再 `loader.start()`,最后 `afterLoad()`;三个阶段都按依赖顺序执行。
|
||||
- 注册列表:`src/data/plugin/list.ts` 的 `dataPlugins`。
|
||||
- 现有插件示例:`src/data/plugin/shop/plugin.ts`(`DataShopPlugin` 在 `awake` 里创建 system / manager,并注册 `@plugin/shop` 可存档内容);主插件 `src/data/initialize.ts` 的 `MainDataPlugin`(`awake` 做装配,`afterLoad` 读取加载后的核心配置创建勇士)。
|
||||
- 插件查找:`state.getDataPlugin(id)` 返回 `Readonly` 的插件包装对象或 `null`;包装对象含 `plugin` 与 `status`。
|
||||
- 不要在 `awake` 里做需要等待的异步工作;`initialize` / `afterLoad` 返回的 Promise 不兑现会阻塞依赖它的插件与加载。
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking-human">
|
||||
<name>Task 0: 向用户汇报本计划并等待「可以执行」</name>
|
||||
<action>向用户汇报:**要做什么**(编写 `docs/advanced/plugin-system.md`,把插件系统从中级的基础用法展开到能自行设计与维护插件)、**为什么**(D-53 把插件定为高级 I 的核心,后续存档、录像、钩子都挂在插件上)、**大致怎么做**(先讲最小插件与三个生命周期,再讲 `dependencies` 排序与失败语义、插件互调、注册与存档、id 稳定性与迁移,最后给独立模板、现有插件示例与「怎么验证」)、**影响范围**(仅新增 1 个 `docs/advanced/*.md`;不改 `config.ts`、不改生产代码、不新增外链)。同时请用户确认文件名 `plugin-system.md` 与页面标题「插件系统」(D-13)。停下等待用户明确回复「可以执行」四个字;未收到前不得写任何文件。</action>
|
||||
<what-built>本计划内容:高级 I 第二篇「插件系统」。</what-built>
|
||||
<how-to-verify>阅读上面的四段说明,并确认文件名与页面标题;如不同意请给出替代名称。</how-to-verify>
|
||||
<resume-signal>回复「可以执行」以继续;其它回复视为未批准,本计划停在此处。</resume-signal>
|
||||
<verify>
|
||||
<human-check>用户已回复「可以执行」,且未对文件名 / 页面标题提出替代。</human-check>
|
||||
</verify>
|
||||
<done>用户回复「可以执行」。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: 编写 plugin-system.md 正文</name>
|
||||
<files>docs/advanced/plugin-system.md</files>
|
||||
<action>(1) 写 `docs/advanced/plugin-system.md`,单 H1 为「插件系统」。(2) 正文顺序:①插件解决什么问题——把一组相关对象与生命周期收拢,随数据端启动加装;回指 10-13 的「声明规则、引擎在合适时机执行」。②最小插件:`IDataPluginDefine` 成员表(`id`、`dependencies`、`awake`、`initialize`、`afterLoad`),一段 class 骨架;说明 `dependencies` 是依赖插件的 id 字符串数组,三个方法收到的是解析后的插件对象数组;说明 register 在 `src/data/plugin/list.ts` 的 `dataPlugins`。③三个生命周期进阶:结合 `CoreState` 的构造与 `load()` 讲清——`awake` 在主对象构造期同步执行(不能 `await`,适合装配对象、注册可存档内容);`initialize` 在加载开始前、可返回 Promise 并阻塞加载;`afterLoad` 在主对象加载完毕后、适合依赖加载内容的初始化;三个阶段都按依赖顺序执行。④`dependencies` 与排序:拓扑序;无依赖关系的插件按登记顺序进入同一阶段;循环依赖与缺失依赖会被记录并阻断,依赖它们的下游插件也不会初始化;不要在运行时改依赖;依赖尽量少。⑤插件之间怎么调用:优先用方法参数里的依赖插件对象;没有声明依赖时用 `state.getDataPlugin(id)`,注意返回 `null` 与 `status`;给一段读取依赖插件并调用其方法的短示例。⑥插件 id 稳定性与版本迁移:id 被其他插件的 `dependencies` 引用,也常被用作可存档 id 的一部分(如 `@plugin/...`);改名会让依赖找不到、旧存档找不到对应可存档内容;不要复用旧 id 表示新语义;迁移时保留旧 id 或提供迁移路径,不要悄悄改名。⑦注册与目录:插件放在 `src/data/plugin/` 下的独立目录(`plugin.ts` / `types.ts` / `state.ts` / `manager.ts` / `system.ts` / `index.ts` 是现有形状);在 `list.ts` 里 `new` 后加入数组;说明这是少数必须新建文件/目录的场景,因为插件本身就是一个独立单元。⑧「模板:一个插件的最小骨架」独立小节:`[模板]` / `[需改]` 标注 id、dependencies、awake 里的对象装配与 `state.addSaveableContent`、initialize/afterLoad 的用途;不要把商店插件当模板。⑨「示例:读一个现有插件」:以 `DataShopPlugin` 为真实例子逐段说明 `awake` 做了什么、为什么注册 `@plugin/shop`;以 `MainDataPlugin` 说明 `afterLoad` 读取加载后配置的用法;再给一个「插件 B 依赖插件 A 并调用 A 的方法」的短示例,标 `[需改]`。⑩「改内置还是自己写」:如果只是给现有插件加行为,优先改现有插件;只有独立成组的功能才新建插件;写明「如果你不怎么懂代码,建议直接改内置」。⑪常见错误:在 `awake` 里 `await`、把 `dependencies` 写成对象、依赖循环、用 `state.getDataPlugin` 却不判空、把插件 id 当显示名随意改、在插件里直接改其他系统的内部字段。⑫「怎么验证」:把插件加进 `dataPlugins` 后启动,确认三个时机按预期发生(可用 `state.emitTip` 或控制台输出做可观察信号);用 `state.getDataPlugin` 取回插件并检查 `status`;存档后读档,确认插件注册的可存档内容恢复;把依赖写错一次,确认插件没有被初始化并在控制台看到错误,然后改回。 (3) 适当加 3–5 处链接,只链已存在页面:`../intermediate/data-plugins.md`、`../intermediate/data-state-overview.md`、`./declarative-vs-imperative.md`。高级 II/III 的内容(如排序算法内部、插件生命周期与加载器的深层关系)用代码 span 或 HTML 注释,不写真实链接。(4) CRLF、单 H1、数据端 only、加粗符合 flanking。完成后运行 prettier `--write`。Commit:`docs(10-14): 编写插件系统`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Stop'; $p='docs/advanced/plugin-system.md'; if(-not(Test-Path -LiteralPath $p)){ Write-Error "MISSING $p" }; $raw=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $p)); if($raw -match "(?<!`r)`n"){ Write-Error "LF-ONLY $p" }; if(([regex]::Matches($raw,'(?m)^# ')).Count -ne 1){ Write-Error "H1-COUNT $p" }; foreach($needle in @('IDataPluginDefine','dependencies','awake','initialize','afterLoad','orderPlugins','getDataPlugin','PluginStatus','版本迁移','模板:一个插件的最小骨架','怎么验证')){ if($raw -notmatch [regex]::Escape($needle)){ Write-Error "MISSING-CONTENT $needle" } }; if($raw -match 'src/client'){ Write-Error "RENDERER-CONTENT" }; if($raw -match '\*\*[,。!?;:、()《》「」『』【】,.!?;:\(\)\[\]{}"'']' -or $raw -match '[,。!?;:、()《》「」『』【】,.!?;:\(\)\[\]{}"'']\*\*'){ Write-Error "BOLD-FLANK" }; foreach($m in [regex]::Matches($raw,'\]\((\.{1,2}/[^)#]+\.md)(#[^)]*)?\)')){ $t=Join-Path (Split-Path -Parent $p) $m.Groups[1].Value; if(-not(Test-Path -LiteralPath $t)){ Write-Error ("DEAD-LINK " + $m.Groups[1].Value) } }; Write-Output 'STATIC-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "MISSING", "LF-ONLY", "H1-COUNT", "MISSING-CONTENT", "RENDERER-CONTENT", "BOLD-FLANK", or "DEAD-LINK"; or output lacks "STATIC-OK"</fails_when>
|
||||
</verify>
|
||||
<done>文件存在、CRLF、单 H1;三个生命周期、依赖排序与失败语义、插件互调、id 稳定性与迁移、独立模板、现有插件示例与「怎么验证」齐全;无渲染端内容;无死链。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: 源码核对、格式与构建收口</name>
|
||||
<files>docs/advanced/plugin-system.md</files>
|
||||
<action>逐条核对正文与源码:`packages-user/data-state/src/types.ts`(`IDataPluginDefine` 的三个方法签名、`PluginStatus` 四个状态、`getDataPlugin` 的返回类型)、`packages-user/data-state/src/plugin.ts`(排序返回的 `order` / `cyclic` / `missing` 与阻断逻辑)、`packages-user/data-state/src/core.ts`(构造器里 `awake` 的调用点、`load()` 的三段顺序、重复 id 的错误码)、`src/data/plugin/list.ts`、`src/data/plugin/shop/plugin.ts`、`src/data/initialize.ts`。确认没有把「三个方法收到的依赖参数」写成 id 数组;确认没有把「所有插件都按登记顺序」写成绝对结论(同阶段有依赖时先依赖后依赖者);确认没有把插件 id 的存档关系写反。确认没有渲染端内容、没有死链、没有把商店插件当通用模板。再跑 prettier `--write` 与构建门禁 `cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"`,确认 green、`build complete`、dead link = 0。Commit:`docs(10-14): 核对源码并收口插件文档`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Continue'; $pf = cmd /c "node node_modules/prettier/bin/prettier.cjs --check docs/advanced/plugin-system.md" 2>&1; $pfe=$LASTEXITCODE; $out = cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build" 2>&1; $ec=$LASTEXITCODE; $ErrorActionPreference='Stop'; if($pfe -ne 0){ $pf | Write-Output; Write-Error "PRETTIER-FAIL" }; if($ec -ne 0){ $out | Write-Output; Write-Error "BUILD-FAIL" }; $txt=($out | ForEach-Object { $_.ToString() }) -join "`n"; if($txt -notmatch 'build complete'){ Write-Error "NO-BUILD-COMPLETE" }; if($txt -match 'dead link'){ Write-Error "DEAD-LINK" }; Write-Output 'GATE-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "PRETTIER-FAIL", "BUILD-FAIL", "NO-BUILD-COMPLETE", or "DEAD-LINK"; or output lacks "GATE-OK"</fails_when>
|
||||
</verify>
|
||||
<done>插件签名、排序失败语义、互调与 id 关系逐条与源码一致;prettier 通过;构建 green、`build complete`、dead link = 0。</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| 构建期 markdown → 静态站点 | 正文由构建期静态渲染,无运行时输入 |
|
||||
| 站点 → 浏览器读者 | 站内链接是唯一可点击指向面 |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-10-40 | Tampering / XSS | `plugin-system.md` | low | mitigate | 只写文本、代码块、表格;不引入原始 `<script>` 或内联 HTML |
|
||||
| T-10-41 | Spoofing | 站内链接 | low | mitigate | 只链已存在页面;link 检查断言目标文件存在 |
|
||||
| T-10-42 | Information Disclosure | 正文示例 | low | mitigate | 只用源码已公开的接口与路径;不写密钥、账号 |
|
||||
| T-10-SC | Tampering | npm/pip/cargo installs | low | accept | 本阶段不安装任何包,无供应链安装面 |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- 结构门禁:文件存在、kebab-case、CRLF、单 H1。
|
||||
- 内容门禁:D-53/D-54/D-48..D-52 要点齐全;三个时机、排序失败语义、互调、id 稳定性与迁移均有源码依据。
|
||||
- 链接门禁:只链已存在页面;高级 II/III 不产生真实链接。
|
||||
- 边界门禁:无渲染端内容;不改 `config.ts` 与生产代码。
|
||||
- 构建门禁:`cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"` 通过,`build complete`,dead link = 0。
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 读者能照模板写出一个插件并注册,能解释三个时机与依赖排序的失败语义。
|
||||
- 读者知道插件 id 为什么必须稳定,以及迁移时的最小做法。
|
||||
- 构建通过,无死链、无渲染端内容。
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/10-docs-restructure/10-14-SUMMARY.md` when done
|
||||
</output>
|
||||
146
.planning/phases/10-docs-restructure/10-15-PLAN.md
Normal file
146
.planning/phases/10-docs-restructure/10-15-PLAN.md
Normal file
@ -0,0 +1,146 @@
|
||||
---
|
||||
phase: 10-docs-restructure
|
||||
plan: 15
|
||||
type: execute
|
||||
wave: 3
|
||||
depends_on: [10-14]
|
||||
files_modified:
|
||||
- docs/advanced/saveable-content.md
|
||||
autonomous: false
|
||||
requirements: []
|
||||
user_setup: []
|
||||
estimate:
|
||||
tokens: 28000
|
||||
raw_tokens: 18000
|
||||
tasks: 3
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "`docs/advanced/saveable-content.md` 存在,kebab-case、CRLF、单 H1"
|
||||
- "讲清存档一致性:哪些状态必须进存档、哪些派生/缓存不该进存档、读档后如何重建"
|
||||
- "讲清 `ISaveableContent` 的 `saveState` / `loadState`、`SaveCompression` 的 No/Low/High 三级及各自语义"
|
||||
- "讲清注册:`state.addSaveableContent(id, content)`、id 唯一性、重复 id 的后果、`getSaveableContent`"
|
||||
- "讲清存档顺序、`bindSaveableExecuter` 与 `afterLoad`:存档对象按注册顺序保存与读取;执行器在该对象 `loadState` 之后立即运行;插件 `afterLoad` 只在初次加载后运行,不能替代读档执行器"
|
||||
- "讲清跨对象引用:存档里存 id/键而不是活对象引用;读档时按顺序或延迟解析;给出可操作策略"
|
||||
- "讲清 structured-cloneable 约束:支持普通对象/数组/数字/字符串/布尔/null/Map/Set/TypedArray/ArrayBuffer 等;不支持函数、Symbol、DOM 节点;类实例会丢原型与方法;`saveState` 必须返回经 `structuredClone` 的对象"
|
||||
- "含独立的「模板:一个可存档对象的最小骨架」与一个插件内计数器的示例;class + new;写明「不懂代码优先改内置」;含「怎么验证」"
|
||||
- "数据端 only;高级 II/III 只用非断链占位;构建 green、dead link = 0"
|
||||
artifacts:
|
||||
- "docs/advanced/saveable-content.md"
|
||||
- ".planning/phases/10-docs-restructure/10-15-SUMMARY.md"
|
||||
key_links:
|
||||
- "接续 10-14 的插件:插件常在 `awake` 中注册自己的可存档内容"
|
||||
- "回指中级 `../intermediate/flags.md`(内置可存档变量)与 `../intermediate/data-plugins.md`(插件基础)"
|
||||
- "为 10-16 的「录像一致性」铺垫:读档期间录像被临时禁用,执行器不被包裹"
|
||||
---
|
||||
|
||||
<objective>
|
||||
编写高级 I 第三篇:`saveable-content.md`(可存档内容)。本篇把「存档一致性」讲成可操作的工程规范:声明 `ISaveableContent`、选择压缩级别、注册对象、处理存档顺序与跨对象引用、遵守 structured-cloneable 约束。
|
||||
|
||||
Purpose: 存档不一致是高级定制最常见、也最难排查的问题。读者需要知道「什么该存、存成什么形状、读档时谁先谁后」,而不是只会写一个 `saveState`。
|
||||
Output: 1 个新 markdown 文件。
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||
@C:/Users/13194/.config/opencode/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/phases/10-docs-restructure/10-CONTEXT.md
|
||||
@docs/advanced/declarative-vs-imperative.md
|
||||
@docs/advanced/plugin-system.md
|
||||
@docs/intermediate/flags.md
|
||||
@docs/intermediate/data-plugins.md
|
||||
@packages-user/data-common/src/save/types.ts
|
||||
@packages-user/data-state/src/core.ts
|
||||
@packages-user/data-state/src/types.ts
|
||||
@packages-user/data-base/src/flag/system.ts
|
||||
@src/data/plugin/shop/manager.ts
|
||||
@src/data/initialize.ts
|
||||
@packages-user/data-base/src/map/gameMap.ts
|
||||
|
||||
**写作规范(同 10-13):** 受众是使用者(D-54);实操按 D-48 六步;示例 class + new(D-49);通用模板单独成节(D-50);落点优先已有文件、解释副作用、以目的先讲接口(D-51);加粗符合 flanking(D-52);数据端 only;高级 II/III 只用代码 span 或 HTML 注释(D-47)。
|
||||
|
||||
**源码事实(写作时逐条核对):**
|
||||
- `ISaveableContent` 与 `SaveCompression`:`packages-user/data-common/src/save/types.ts`。`saveState(compression)` 返回 `T`,接口注释要求返回深拷贝(`structuredClone`);`loadState(state, compression)` 恢复。
|
||||
- 注册与调用:`packages-user/data-state/src/core.ts`——`addSaveableContent(id, content)`、`getSaveableContent(id)`、`bindSaveableExecuter(content | id, executor)`;`saveState` 按 `saveables` 的插入顺序遍历;`loadState` 同样按插入顺序,缺键告警 177,多键告警 178;每个对象 `loadState` 外包 `replaySystem.disable()` / `revert()`;执行器 `afterLoad(data, state)` 在该对象 `loadState` 之后立即调用,且不被录像禁用包裹。
|
||||
- 可存档 id:`src/data/initialize.ts` 注册 `@system/hero`、`@system/flags`、`@system/maps`、`@system/enemy`、`@system/replay`;插件示例 `@plugin/shop` 见 `src/data/plugin/shop/plugin.ts` 与 `manager.ts`。
|
||||
- `structuredClone` 的内置用法:`packages-user/data-base/src/flag/field.ts`(`toStructured` / `fromStructured`)、`hero/attribute.ts` 的 `saveState`、`enemy/enemy.ts` 的 `saveState`。
|
||||
- 压缩级别实例:`packages-user/data-base/src/map/gameMap.ts` 与 `mapLayer.ts`——No 存完整地图;Low 在脏时存完整地图、否则依赖参考数组;High 存行差分。说明「自定义对象可以三档同一实现,但必须三档都能被自己的 `loadState` 正确读回」。
|
||||
- 插件 `afterLoad` 与存档执行器 `afterLoad` 不是同一件事:前者在初次加载完成后运行一次;后者在每次读档、该对象恢复后运行。
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking-human">
|
||||
<name>Task 0: 向用户汇报本计划并等待「可以执行」</name>
|
||||
<action>向用户汇报:**要做什么**(编写 `docs/advanced/saveable-content.md`,系统讲可存档内容与存档一致性)、**为什么**(高级定制最容易在存档/读档上出错;插件、地图、战斗状态都需要可存档)、**大致怎么做**(先讲什么该存与 `ISaveableContent`,再讲三级压缩、注册、存档顺序与 `bindSaveableExecuter`、跨对象引用、structured-cloneable 约束,最后给独立模板、插件计数器示例与「怎么验证」)、**影响范围**(仅新增 1 个 `docs/advanced/*.md`;不改 `config.ts`、不改生产代码、不新增外链)。同时请用户确认文件名 `saveable-content.md` 与页面标题「可存档内容」(D-13)。停下等待用户明确回复「可以执行」四个字;未收到前不得写任何文件。</action>
|
||||
<what-built>本计划内容:高级 I 第三篇「可存档内容」。</what-built>
|
||||
<how-to-verify>阅读上面的四段说明,并确认文件名与页面标题;如不同意请给出替代名称。</how-to-verify>
|
||||
<resume-signal>回复「可以执行」以继续;其它回复视为未批准,本计划停在此处。</resume-signal>
|
||||
<verify>
|
||||
<human-check>用户已回复「可以执行」,且未对文件名 / 页面标题提出替代。</human-check>
|
||||
</verify>
|
||||
<done>用户回复「可以执行」。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: 编写 saveable-content.md 正文</name>
|
||||
<files>docs/advanced/saveable-content.md</files>
|
||||
<action>(1) 写 `docs/advanced/saveable-content.md`,单 H1 为「可存档内容」。(2) 正文顺序:①为什么需要可存档内容——存档一致性是什么:读档后游戏要继续按同一套规则运行,影响结果的状态必须恢复;派生的缓存、临时动画、可重建的索引不该存;漏存/多存分别会怎样。②`ISaveableContent`:`saveState(compression)` 返回存档数据、`loadState(state, compression)` 恢复;说明 `T` 是这个对象的存档形状;`saveState` 必须返回经 `structuredClone` 的对象,不能把活对象直接返回。③结构化克隆约束:支持普通对象、数组、数字、字符串、布尔、null、Map、Set、TypedArray、ArrayBuffer、Date 等;不支持函数、Symbol、DOM 节点、WeakMap/WeakSet;类实例会丢失原型与方法,读档后需要自己重建;嵌套对象会被逐层克隆;不要把活对象引用塞进存档数据。④注册:`state.addSaveableContent(id, content)`;id 在存档里唯一;重复 id 会告警并忽略后一个;`getSaveableContent(id)` 取回;插件通常在 `awake` 注册。⑤三级压缩:NoCompression(只提取必要数据,不压缩)、LowCompression(小幅度压缩,性能优先)、HighCompression(大幅度压缩,体积优先);三档都必须能被自己的 `loadState` 正确读回;自定义对象可以三档同一实现,但不能让某一档改变语义;以地图图层为例说明三档分别存什么。⑥存档顺序与执行器:`saveState` / `loadState` 按注册顺序遍历;`bindSaveableExecuter(content 或 id, executor)` 把执行器绑到某个可存档对象;执行器在该对象 `loadState` 之后立即运行,适合做「依赖其他已恢复对象」的收尾;一个对象只能绑一个执行器,一个执行器可以绑多个对象;执行器里的行为不会被录像禁用包裹,可能记录录像,要谨慎。⑦`afterLoad` 的两种含义:插件 `afterLoad(state, dependencies)` 只在初次加载完成后运行一次;存档执行器 `afterLoad(data, state)` 在每次读档、该对象恢复后运行;不要用插件 `afterLoad` 代替读档执行器。⑧跨对象引用:存档里存 id/键(如 floorId、uid、saveable id),不要存对象引用;读档时按注册顺序解析,必要时用执行器延后解析,或把「拥有者」注册在「被引用者」之后;给出三种策略及各自适用场景;说明为什么直接存对象引用会被 `structuredClone` 复制成新对象、丢掉身份。⑨「模板:一个可存档对象的最小骨架」独立小节:class 带一个 Map/Set 字段,`saveState` 用 `structuredClone` 返回普通对象/Map,`loadState` 清空并重建;`[模板]` / `[需改]` 标注。⑩「示例:插件里的计数器」:一个插件在 `awake` 创建计数器对象并 `state.addSaveableContent('@plugin/example-counter', ...)`;计数器有 `add` / `get`;`saveState` 返回 `{ values: structuredClone(this.values) }`;`loadState` 重建 Map;读档后计数恢复;示例标 `[需改]`。⑪「改内置还是自己写」:如果只是给现有系统补存档,优先改现有类的 `saveState` / `loadState`;只有独立成组时才新建可存档对象;写明「如果你不怎么懂代码,建议直接改内置」。⑫常见错误:返回 `this`、返回带方法的类实例、把缓存/索引也存进去、漏掉某个字段、重复 id、执行器里访问尚未恢复的对象、把插件 `afterLoad` 当读档钩子、在 `saveState` 里改状态。⑬「怎么验证」:改状态 → 存档 → 再改 → 读档,确认目标字段恢复且未存的缓存被重建;对 No/Low/High 三档各跑一次;用 `getSaveableContent` 确认注册;故意删掉存档里的一个键,确认控制台告警而不是静默通过;跨对象引用场景读档后确认引用仍指向正确对象。 (3) 适当加 3–5 处链接,只链已存在页面:`../intermediate/flags.md`、`../intermediate/data-plugins.md`、`./plugin-system.md`、`./declarative-vs-imperative.md`。高级 II/III 的内容(如存档格式演进、压缩算法细节)用代码 span 或 HTML 注释,不写真实链接。(4) CRLF、单 H1、数据端 only、加粗符合 flanking。完成后运行 prettier `--write`。Commit:`docs(10-15): 编写可存档内容`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Stop'; $p='docs/advanced/saveable-content.md'; if(-not(Test-Path -LiteralPath $p)){ Write-Error "MISSING $p" }; $raw=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $p)); if($raw -match "(?<!`r)`n"){ Write-Error "LF-ONLY $p" }; if(([regex]::Matches($raw,'(?m)^# ')).Count -ne 1){ Write-Error "H1-COUNT $p" }; foreach($needle in @('ISaveableContent','SaveCompression','NoCompression','LowCompression','HighCompression','addSaveableContent','bindSaveableExecuter','structuredClone','跨对象引用','模板:一个可存档对象的最小骨架','怎么验证')){ if($raw -notmatch [regex]::Escape($needle)){ Write-Error "MISSING-CONTENT $needle" } }; if($raw -match 'src/client'){ Write-Error "RENDERER-CONTENT" }; if($raw -match '\*\*[,。!?;:、()《》「」『』【】,.!?;:\(\)\[\]{}"'']' -or $raw -match '[,。!?;:、()《》「」『』【】,.!?;:\(\)\[\]{}"'']\*\*'){ Write-Error "BOLD-FLANK" }; foreach($m in [regex]::Matches($raw,'\]\((\.{1,2}/[^)#]+\.md)(#[^)]*)?\)')){ $t=Join-Path (Split-Path -Parent $p) $m.Groups[1].Value; if(-not(Test-Path -LiteralPath $t)){ Write-Error ("DEAD-LINK " + $m.Groups[1].Value) } }; Write-Output 'STATIC-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "MISSING", "LF-ONLY", "H1-COUNT", "MISSING-CONTENT", "RENDERER-CONTENT", "BOLD-FLANK", or "DEAD-LINK"; or output lacks "STATIC-OK"</fails_when>
|
||||
</verify>
|
||||
<done>文件存在、CRLF、单 H1;一致性、接口、三级压缩、注册、顺序与执行器、两种 afterLoad、跨对象引用、structuredClone 约束、独立模板、示例与「怎么验证」齐全;无渲染端内容;无死链。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: 源码核对、格式与构建收口</name>
|
||||
<files>docs/advanced/saveable-content.md</files>
|
||||
<action>逐条核对正文与源码:`packages-user/data-common/src/save/types.ts`(两个方法签名与压缩枚举)、`packages-user/data-state/src/core.ts`(`saveables` 的遍历顺序、缺键 177 / 多键 178、`replaySystem.disable` 与 `revert` 的包裹范围、执行器调用位置)、`packages-user/data-state/src/types.ts`(`ISaveableExecutor` 与 `bindSaveableExecuter` 的两种入参)、`src/data/initialize.ts`(五个内置 saveable id)、`src/data/plugin/shop/plugin.ts` 与 `manager.ts`(插件注册与 `saveState` / `loadState` 实现)、`packages-user/data-base/src/flag/field.ts` 与 `flag/system.ts`(`structuredClone` 的实际用法)、`packages-user/data-base/src/map/gameMap.ts` 与 `mapLayer.ts`(三级压缩的行为)。确认没有把「执行器在全部对象读完后统一运行」写错(源码是每个对象 `loadState` 后立即运行);确认没有把插件 `afterLoad` 与执行器 `afterLoad` 混为一谈;确认没有声称 `structuredClone` 支持函数或保留原型。确认无渲染端内容、无死链、无把具体计数器示例当通用模板。再跑 prettier `--write` 与构建门禁 `cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"`,确认 green、`build complete`、dead link = 0。Commit:`docs(10-15): 核对源码并收口可存档文档`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Continue'; $pf = cmd /c "node node_modules/prettier/bin/prettier.cjs --check docs/advanced/saveable-content.md" 2>&1; $pfe=$LASTEXITCODE; $out = cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build" 2>&1; $ec=$LASTEXITCODE; $ErrorActionPreference='Stop'; if($pfe -ne 0){ $pf | Write-Output; Write-Error "PRETTIER-FAIL" }; if($ec -ne 0){ $out | Write-Output; Write-Error "BUILD-FAIL" }; $txt=($out | ForEach-Object { $_.ToString() }) -join "`n"; if($txt -notmatch 'build complete'){ Write-Error "NO-BUILD-COMPLETE" }; if($txt -match 'dead link'){ Write-Error "DEAD-LINK" }; Write-Output 'GATE-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "PRETTIER-FAIL", "BUILD-FAIL", "NO-BUILD-COMPLETE", or "DEAD-LINK"; or output lacks "GATE-OK"</fails_when>
|
||||
</verify>
|
||||
<done>存档顺序、执行器时机、两种 afterLoad、structuredClone 支持范围与三级压缩均与源码一致;prettier 通过;构建 green、`build complete`、dead link = 0。</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| 构建期 markdown → 静态站点 | 正文由构建期静态渲染,无运行时输入 |
|
||||
| 站点 → 浏览器读者 | 站内链接是唯一可点击指向面 |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-10-43 | Tampering / XSS | `saveable-content.md` | low | mitigate | 只写文本、代码块、表格;不引入原始 `<script>` 或内联 HTML |
|
||||
| T-10-44 | Spoofing | 站内链接 | low | mitigate | 只链已存在页面;link 检查断言目标文件存在 |
|
||||
| T-10-45 | Information Disclosure | 正文示例 | low | mitigate | 只用源码已公开的接口与路径;不写密钥、账号;示例 id 用 `@plugin/example-counter` 这类明显占位名 |
|
||||
| T-10-SC | Tampering | npm/pip/cargo installs | low | accept | 本阶段不安装任何包,无供应链安装面 |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- 结构门禁:文件存在、kebab-case、CRLF、单 H1。
|
||||
- 内容门禁:D-53/D-54/D-48..D-52 要点齐全;三级压缩、执行器时机、跨对象引用与 structuredClone 约束均有源码依据。
|
||||
- 链接门禁:只链已存在页面;高级 II/III 不产生真实链接。
|
||||
- 边界门禁:无渲染端内容;不改 `config.ts` 与生产代码。
|
||||
- 构建门禁:`cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"` 通过,`build complete`,dead link = 0。
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 读者能写出一个可存档对象并正确注册,知道三档压缩各自要保证什么。
|
||||
- 读者能解释执行器与插件 `afterLoad` 的区别,能处理跨对象引用。
|
||||
- 构建通过,无死链、无渲染端内容。
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/10-docs-restructure/10-15-SUMMARY.md` when done
|
||||
</output>
|
||||
169
.planning/phases/10-docs-restructure/10-16-PLAN.md
Normal file
169
.planning/phases/10-docs-restructure/10-16-PLAN.md
Normal file
@ -0,0 +1,169 @@
|
||||
---
|
||||
phase: 10-docs-restructure
|
||||
plan: 16
|
||||
type: execute
|
||||
wave: 4
|
||||
depends_on: [10-15]
|
||||
files_modified:
|
||||
- docs/advanced/replay-consistency.md
|
||||
- docs/advanced/data-stores.md
|
||||
autonomous: false
|
||||
requirements: []
|
||||
user_setup: []
|
||||
estimate:
|
||||
tokens: 30000
|
||||
raw_tokens: 19000
|
||||
tasks: 4
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "`docs/advanced/replay-consistency.md` 与 `docs/advanced/data-stores.md` 存在,kebab-case、CRLF、单 H1"
|
||||
- "录像篇讲清 `@shouldReplay` / `@ignoreReplay` 该写在什么方法上:状态变更、玩家动作可达的入口 vs 延迟/被动路径;并说明 `@ignoreReplay` 是整段检查的忽略标记,不是按方法局部生效"
|
||||
- "录像篇讲清能保证什么、不能保证什么:只检测「区间内是否记录了新录像步」,不保证逻辑正确、参数正确、顺序正确;`@shouldReplay` 本身不记录录像"
|
||||
- "录像篇讲清检测机制与边界:收集区间由 `beginReplaySafetyCollection` / `endReplaySafetyCollection` 控制;读档期间 `loadState` 被 `disable` / `revert` 包裹,执行器不被包裹;自定义录像行为指向高级 II、深层原理指向高级 III(非断链)"
|
||||
- "Store 篇逐个讲清 `tileStore` / `itemStore` / `mapStore` / `eventStore` 存什么、键是什么、常用读取接口是什么"
|
||||
- "Store 篇明确它们是内容层原始数据,运行时一般只读;修改 `add*` 很可能绕过存档/录像/一致性,且不会更新已存在的运行时对象;给出正确注册位置(插件/加载期)与风险对照表"
|
||||
- "两篇都含「怎么验证」;数据端 only;高级 II/III 只用非断链占位;构建 green、dead link = 0"
|
||||
artifacts:
|
||||
- "docs/advanced/replay-consistency.md"
|
||||
- "docs/advanced/data-stores.md"
|
||||
- ".planning/phases/10-docs-restructure/10-16-SUMMARY.md"
|
||||
key_links:
|
||||
- "录像篇回指中级 `../intermediate/replay-commands.md` 与 `data-vs-render.md`;接续 10-15 的读档禁用录像"
|
||||
- "Store 篇回指中级 `../intermediate/data-state-overview.md`、`map-tiles.md`、`tile-store.md`"
|
||||
- "两篇共同服务 D-55:优先通过插件/声明式注册扩展,不要去改内容层原始数据"
|
||||
---
|
||||
|
||||
<objective>
|
||||
编写高级 I 第四、五篇:`replay-consistency.md`(录像一致性)与 `data-stores.md`(data-common 的 Store)。前者讲两个录像装饰器该写在哪、保证与不保证什么;后者讲四个 Store 各存什么、为什么运行时应当只读。
|
||||
|
||||
Purpose: 这两个主题都是「看起来能用、用错就很难查」的底层约束。读者需要能判断一个方法该不该标记、一个 Store 能不能改,而不是靠试。
|
||||
Output: 2 个新 markdown 文件。
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||
@C:/Users/13194/.config/opencode/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/phases/10-docs-restructure/10-CONTEXT.md
|
||||
@docs/advanced/declarative-vs-imperative.md
|
||||
@docs/advanced/saveable-content.md
|
||||
@docs/intermediate/replay-commands.md
|
||||
@docs/intermediate/data-state-overview.md
|
||||
@docs/intermediate/map-tiles.md
|
||||
@docs/intermediate/tile-store.md
|
||||
@packages-user/data-common/src/replay/func.ts
|
||||
@packages-user/data-common/src/replay/types.ts
|
||||
@packages-user/data-state/src/core.ts
|
||||
@packages-user/data-common/src/store/types.ts
|
||||
@packages-user/data-common/src/store/tileStore.ts
|
||||
@packages-user/data-common/src/store/itemStore.ts
|
||||
@packages-user/data-common/src/store/mapStore.ts
|
||||
@packages-user/data-common/src/store/eventStore.ts
|
||||
|
||||
**写作规范(同 10-13):** 受众是使用者(D-54);实操按 D-48 六步;示例 class + new(D-49);通用模板单独成节(D-50);落点优先已有文件、解释副作用、以目的先讲接口(D-51);加粗符合 flanking(D-52);数据端 only;高级 II/III 只用代码 span 或 HTML 注释(D-47)。
|
||||
|
||||
**录像源码事实(写作时逐条核对):**
|
||||
- `@shouldReplay` / `@ignoreReplay`:`packages-user/data-common/src/replay/func.ts`。`shouldReplay` 在收集区间内登记嵌套路径;`ignoreReplay` 把一个布尔标记置为 true,整个收集区间都会跳过检查。
|
||||
- 收集:`beginReplaySafetyCollection` 记录 `beforeLength`;`endReplaySafetyCollection` 在「被忽略 / 有新录像步 / 没有登记路径」任一条件成立时提前返回,否则告警并输出登记路径。
|
||||
- 内置 `@shouldReplay` 用法:`packages-user/data-base/src/flag/system.ts`、`flag/field.ts`、`hero/attribute.ts`、`hero/items.ts`、`hero/equipment.ts`、`hero/location.ts`、`map/mapLayer.ts`、`map/gameMap.ts`、`map/dynamicTile.ts`、`map/eventView.ts`。
|
||||
- 读档:`packages-user/data-state/src/core.ts` 的 `loadState` 对每个对象的 `loadState` 调 `replaySystem.disable()` / `revert()`;执行器不在禁用包裹内。
|
||||
- `ReplayCommandType` / `ReplayCode` / `IReplayCommand`:`packages-user/data-common/src/replay/types.ts`。
|
||||
- 重要:先核对 `beginReplaySafetyCollection` / `endReplaySafetyCollection` 在当前源码中的调用点。若只有测试调用,正文只描述「收集区间开启时」的检测口径与装饰器职责,不写「每步自动开启」的断言,也不把接线状态写成缺陷(D-14)。
|
||||
|
||||
**Store 源码事实(写作时逐条核对):**
|
||||
- `IDataCommon` 暴露四个 Store:`tileStore` / `itemStore` / `mapStore` / `eventStore`(`packages-user/data-common/src/types.ts`)。
|
||||
- `ITileStore`:以图块数字为键的 `ITileRawData`(`num` / `id` / `events` / `type` / `pass` / `eventPass`),另有 id 与数字的双向索引、默认事件映射;`getData` / `getEvent` / `getType` / `addTile` / `idToNumber` / `numberToId` / `num` / `id` / `identity`。
|
||||
- `IItemStore`:以道具图块数字为键的 `IItemRawData`(`category` / `name` / `text` / `effect` / `equip`);`getData` / `getCategory` / `addItem`;构造时接收 `tileStore` 做 id 换算。
|
||||
- `IMapStore`:以 `floorId` 为键的 `IMapRawData`(`width` / `map` / `layerAlias` / `events`);`getMap` / `addMap`。
|
||||
- `IGameEventStore`:以事件 id 为键的事件实例;`getEvent` / `addEvent`。
|
||||
- `enemyStore.ts` 里还有 `IEnemyStore`,但它不在 `IDataCommon` 上;如果正文要提,必须说明它是额外的原始数据表,不要写成 `state.enemyStore`。除非核对后有明确暴露点,否则本篇只讲 `IDataCommon` 的四个 Store。
|
||||
- Store 的 `add*` 是登记原始定义用的,不带 `@shouldReplay`;它们不是游戏状态,不在存档里。
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking-human">
|
||||
<name>Task 0: 向用户汇报本计划并等待「可以执行」</name>
|
||||
<action>向用户汇报:**要做什么**(编写两篇:`replay-consistency.md` 与 `data-stores.md`)、**为什么**(录像装饰器与内容层 Store 都是「用错很难查」的底层约束;高级 I 必须让读者能判断该不该标记、能不能改)、**大致怎么做**(录像篇讲装饰器落点、保证与不保证、检测边界,并先核对收集入口的调用点;Store 篇逐个讲四个 Store 存什么、为什么运行时只读、改 `add*` 的风险与正确注册位置;两篇都给「怎么验证」)、**影响范围**(仅新增 2 个 `docs/advanced/*.md`;不改 `config.ts`、不改生产代码、不新增外链)。同时请用户确认两个文件名 `replay-consistency.md` / `data-stores.md` 与页面标题「录像一致性」/「data-common 的 Store」(D-13)。停下等待用户明确回复「可以执行」四个字;未收到前不得写任何文件。</action>
|
||||
<what-built>本计划内容:高级 I 第四、五篇「录像一致性」与「data-common 的 Store」。</what-built>
|
||||
<how-to-verify>阅读上面的四段说明,并确认两个文件名与页面标题;如不同意请给出替代名称。</how-to-verify>
|
||||
<resume-signal>回复「可以执行」以继续;其它回复视为未批准,本计划停在此处。</resume-signal>
|
||||
<verify>
|
||||
<human-check>用户已回复「可以执行」,且未对文件名 / 页面标题提出替代。</human-check>
|
||||
</verify>
|
||||
<done>用户回复「可以执行」。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: 编写 replay-consistency.md</name>
|
||||
<files>docs/advanced/replay-consistency.md</files>
|
||||
<action>(1) 写 `docs/advanced/replay-consistency.md`,单 H1 为「录像一致性」。(2) 正文顺序:①录像一致性要解决什么——重放同一条录像要得到同样的数据端结果;核心原则仍是「玩家直接产生、会影响最终成绩的行为必须计入录像」;回指中级 `replay-commands.md`。②两个装饰器的职责:`@shouldReplay(message)` 把方法标记为「应该计入录像」,在检测区间内登记一条路径;`@ignoreReplay(reason)` 把整段检测标记为忽略;强调 `@ignoreReplay` 不是「只忽略这个方法」,而是当前收集区间内全局生效,所以不要用它掩盖告警。③该写在哪:写在会被玩家动作走到、并且真的会改变游戏状态的方法上(内置例子:`FlagSystem.setField` / `deleteField`、`HeroItems.addItem` / `useItem`、`HeroEquipment.equip` / `unequip`、`MapLayer.setBlock` 等);不要写在只读方法、纯计算/试算方法、会被反复调用做展示或临界搜索的方法上;`@ignoreReplay` 只用于确实不该计入录像的异步/被动路径,并说明理由。④能保证什么、不能保证什么:保证的是「这段区间里如果没有任何新录像步,就告警并列出登记过的路径」;不保证逻辑正确、参数正确、顺序正确、记录的指令与操作一一对应;`@shouldReplay` 本身不写录像,真正记录仍要调用 `record` 或走内置的录制入口。⑤检测机制与边界:`beginReplaySafetyCollection` 记录录像长度,`endReplaySafetyCollection` 比较长度并输出登记路径;读档期间 `CoreState.loadState` 对每个对象的 `loadState` 用 `disable` / `revert` 包裹,避免读档产生意外记录;执行器不在包裹内,确实可能记录。写作前先核对这两个入口在当前源码里的调用点;若没有生产调用,只描述「收集区间开启时」的口径,不臆造自动检测时序。⑥自定义录像行为(新增指令、主动/被动录像、沙盒)指向高级 II;录像格式与深层原理指向高级 III;都用代码 span 或 HTML 注释。⑦常见错误:给计算器/只读查询加 `@shouldReplay`、用 `@ignoreReplay` 压掉告警、以为加了装饰器就会自动记录、在异步回调里记录却没有处理顺序、在 `loadState` 里产生录像。⑧「怎么验证」:找一个玩家动作入口,故意不记录,观察控制台是否给出缺失记录告警与路径;补上记录后确认告警消失;再检查参数与顺序是否真的正确(装饰器不会替你验证这些);用录像回放确认结果一致。 (3) 适当加 3–5 处链接,只链已存在页面:`../intermediate/replay-commands.md`、`../intermediate/data-vs-render.md`、`./saveable-content.md`、`./declarative-vs-imperative.md`。不写指向高级 II/III 的真实链接。(4) CRLF、单 H1、数据端 only、加粗符合 flanking。完成后运行 prettier `--write`。Commit:`docs(10-16): 编写录像一致性`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Stop'; $p='docs/advanced/replay-consistency.md'; if(-not(Test-Path -LiteralPath $p)){ Write-Error "MISSING $p" }; $raw=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $p)); if($raw -match "(?<!`r)`n"){ Write-Error "LF-ONLY $p" }; if(([regex]::Matches($raw,'(?m)^# ')).Count -ne 1){ Write-Error "H1-COUNT $p" }; foreach($needle in @('@shouldReplay','@ignoreReplay','beginReplaySafetyCollection','endReplaySafetyCollection','能保证','不能保证','怎么验证')){ if($raw -notmatch [regex]::Escape($needle)){ Write-Error "MISSING-CONTENT $needle" } }; if($raw -match 'src/client'){ Write-Error "RENDERER-CONTENT" }; if($raw -match '\*\*[,。!?;:、()《》「」『』【】,.!?;:\(\)\[\]{}"'']' -or $raw -match '[,。!?;:、()《》「」『』【】,.!?;:\(\)\[\]{}"'']\*\*'){ Write-Error "BOLD-FLANK" }; foreach($m in [regex]::Matches($raw,'\]\((\.{1,2}/[^)#]+\.md)(#[^)]*)?\)')){ $t=Join-Path (Split-Path -Parent $p) $m.Groups[1].Value; if(-not(Test-Path -LiteralPath $t)){ Write-Error ("DEAD-LINK " + $m.Groups[1].Value) } }; Write-Output 'STATIC-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "MISSING", "LF-ONLY", "H1-COUNT", "MISSING-CONTENT", "RENDERER-CONTENT", "BOLD-FLANK", or "DEAD-LINK"; or output lacks "STATIC-OK"</fails_when>
|
||||
</verify>
|
||||
<done>录像篇存在、CRLF、单 H1;装饰器落点、保证与不保证、检测边界、常见错误与「怎么验证」齐全;无渲染端内容;无死链。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: 编写 data-stores.md</name>
|
||||
<files>docs/advanced/data-stores.md</files>
|
||||
<action>(1) 写 `docs/advanced/data-stores.md`,单 H1 为「data-common 的 Store」。(2) 正文顺序:①Store 是什么——数据端公共层(L0)里存放**内容层原始定义**的注册表,不是游戏运行时状态;它们由内容数据在加载/初始化时填入,运行时主要用来查询。②逐个讲四个 Store:`tileStore`(图块数字到 `ITileRawData`,含 id、默认事件、类型、通行性;读取接口 `getData` / `getEvent` / `getType` / `num` / `id` / `identity`);`itemStore`(道具图块数字到 `IItemRawData`,含分类、名称、描述、效果、装备数据;`getData` / `getCategory`);`mapStore`(`floorId` 到 `IMapRawData`,含宽、地图数组、图层别名、事件;`getMap`);`eventStore`(事件 id 到事件实例;`getEvent`)。用一张表收束:Store / 存什么 / 键 / 常用读取 / 写入接口。③Store 与运行时状态的区别:`tileStore` 的图块定义 vs 图层上的实际图块(`mapLayer.getBlock`);`itemStore` 的原始道具定义 vs 勇士背包里的道具实例;`mapStore` 的原始地图数据 vs `state.maps` 的运行时楼层;`eventStore` 的事件实例 vs 地图点上的事件视图。每对都给一句「改哪个会影响什么」。④运行时为什么只读:`add*` 只是登记原始定义,不带 `@shouldReplay`,也不进存档;在游戏进行中调用它,不会更新已经创建的对象,不会让录像重放得到同样结果,读档也不会恢复;因此会绕过存档/录像/一致性。⑤正确注册位置:内容层加载期、插件 `awake` / `initialize` / `afterLoad`(按是否需要加载内容选择),或专门的初始化函数;不要在每个玩家动作、每帧、事件回调里调用 `add*`。⑥什么时候可以读:查表、提示文本、UI、计算前查定义都可以;读取返回的原始对象也不要就地修改(`getEvent` 返回的是拷贝,其他多为直接引用)。⑦`enemyStore` 的说明:`data-common` 里还有 `EnemyStore` 类,但它不在 `IDataCommon` 上;除非核对到明确暴露点,不要把它写成 `state` 上的直接成员,只说明它是额外的原始数据表。⑧「怎么验证」:读一个图块/道具/地图定义,确认能查到;在游戏进行中调用 `add*` 改一个定义,检查已存在的对象是否变化、存档/读档后是否保留、录像重放是否一致——用这三个观察说明为什么不该在运行时改;把注册改到插件 `awake` 后重跑,确认行为稳定。 (3) 适当加 3–5 处链接,只链已存在页面:`../intermediate/data-state-overview.md`、`../intermediate/map-tiles.md`、`../intermediate/tile-store.md`、`./plugin-system.md`、`./saveable-content.md`。高级 II/III 的内容(如加载管线、内容层设计)用代码 span 或 HTML 注释,不写真实链接。(4) CRLF、单 H1、数据端 only、加粗符合 flanking。完成后运行 prettier `--write`。Commit:`docs(10-16): 编写 data-common 的 Store`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Stop'; $p='docs/advanced/data-stores.md'; if(-not(Test-Path -LiteralPath $p)){ Write-Error "MISSING $p" }; $raw=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $p)); if($raw -match "(?<!`r)`n"){ Write-Error "LF-ONLY $p" }; if(([regex]::Matches($raw,'(?m)^# ')).Count -ne 1){ Write-Error "H1-COUNT $p" }; foreach($needle in @('tileStore','itemStore','mapStore','eventStore','内容层','只读','addTile','addItem','addMap','addEvent','怎么验证')){ if($raw -notmatch [regex]::Escape($needle)){ Write-Error "MISSING-CONTENT $needle" } }; if($raw -match 'src/client'){ Write-Error "RENDERER-CONTENT" }; if($raw -match '\*\*[,。!?;:、()《》「」『』【】,.!?;:\(\)\[\]{}"'']' -or $raw -match '[,。!?;:、()《》「」『』【】,.!?;:\(\)\[\]{}"'']\*\*'){ Write-Error "BOLD-FLANK" }; foreach($m in [regex]::Matches($raw,'\]\((\.{1,2}/[^)#]+\.md)(#[^)]*)?\)')){ $t=Join-Path (Split-Path -Parent $p) $m.Groups[1].Value; if(-not(Test-Path -LiteralPath $t)){ Write-Error ("DEAD-LINK " + $m.Groups[1].Value) } }; Write-Output 'STATIC-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "MISSING", "LF-ONLY", "H1-COUNT", "MISSING-CONTENT", "RENDERER-CONTENT", "BOLD-FLANK", or "DEAD-LINK"; or output lacks "STATIC-OK"</fails_when>
|
||||
</verify>
|
||||
<done>Store 篇存在、CRLF、单 H1;四个 Store 的存储内容、键、读取接口、只读原因、正确注册位置与「怎么验证」齐全;`enemyStore` 未被误写成 `state.enemyStore`;无渲染端内容;无死链。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: 两篇源码核对、格式与构建收口</name>
|
||||
<files>docs/advanced/replay-consistency.md, docs/advanced/data-stores.md</files>
|
||||
<action>逐条核对:录像篇对照 `packages-user/data-common/src/replay/func.ts`(`shouldReplay` 的嵌套登记、`ignoreReplay` 的全局标记、`endReplaySafetyCollection` 的三个提前返回条件)与 `packages-user/data-state/src/core.ts`(`loadState` 的 `disable` / `revert` 包裹、执行器位置),并核对收集入口的实际调用点;若只有测试调用,按「收集区间开启时」的措辞改写任何「每步自动」的断言。Store 篇对照 `packages-user/data-common/src/store/types.ts`、`tileStore.ts`、`itemStore.ts`、`mapStore.ts`、`eventStore.ts`、`enemyStore.ts` 与 `packages-user/data-common/src/types.ts`(`IDataCommon` 的四个 Store 字段),逐字核对接口名、参数顺序与返回形状;确认它没有被写成 `state` 上的直接成员。两篇都确认无渲染端内容、无死链、无把具体示例当通用模板。再跑 prettier `--write` 与构建门禁 `cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"`,确认 green、`build complete`、dead link = 0。Commit:`docs(10-16): 核对源码并收口录像与 Store 文档`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Continue'; $pf = cmd /c "node node_modules/prettier/bin/prettier.cjs --check docs/advanced/replay-consistency.md docs/advanced/data-stores.md" 2>&1; $pfe=$LASTEXITCODE; $out = cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build" 2>&1; $ec=$LASTEXITCODE; $ErrorActionPreference='Stop'; if($pfe -ne 0){ $pf | Write-Output; Write-Error "PRETTIER-FAIL" }; if($ec -ne 0){ $out | Write-Output; Write-Error "BUILD-FAIL" }; $txt=($out | ForEach-Object { $_.ToString() }) -join "`n"; if($txt -notmatch 'build complete'){ Write-Error "NO-BUILD-COMPLETE" }; if($txt -match 'dead link'){ Write-Error "DEAD-LINK" }; if(([IO.File]::ReadAllText((Resolve-Path 'docs/advanced/data-stores.md'))) -match 'state\.enemyStore'){ Write-Error "ENEMY-STORE-EXPOSED" }; Write-Output 'GATE-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "PRETTIER-FAIL", "BUILD-FAIL", "NO-BUILD-COMPLETE", "DEAD-LINK", or "ENEMY-STORE-EXPOSED"; or output lacks "GATE-OK"</fails_when>
|
||||
</verify>
|
||||
<done>两篇接口名、参数顺序、返回形状与源码一致;录像篇未把未接线的检测写成既成事实;Store 篇未把 `enemyStore` 写成 `state` 入口;prettier 通过;构建 green、`build complete`、dead link = 0。</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| 构建期 markdown → 静态站点 | 正文由构建期静态渲染,无运行时输入 |
|
||||
| 站点 → 浏览器读者 | 站内链接是唯一可点击指向面 |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-10-46 | Tampering / XSS | 两篇 markdown 正文 | low | mitigate | 只写文本、代码块、表格;不引入原始 `<script>` 或内联 HTML |
|
||||
| T-10-47 | Spoofing | 站内链接 | low | mitigate | 只链已存在页面;link 检查断言目标文件存在 |
|
||||
| T-10-48 | Information Disclosure | 正文示例 | low | mitigate | 只用源码已公开的接口与路径;不写密钥、账号 |
|
||||
| T-10-SC | Tampering | npm/pip/cargo installs | low | accept | 本阶段不安装任何包,无供应链安装面 |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- 结构门禁:两文件存在、kebab-case、CRLF、单 H1。
|
||||
- 内容门禁:录像装饰器落点、保证/不保证、检测边界;四个 Store 的存储内容与只读风险均有源码依据。
|
||||
- 链接门禁:只链已存在页面;高级 II/III 不产生真实链接。
|
||||
- 边界门禁:无渲染端内容;不改 `config.ts` 与生产代码。
|
||||
- 构建门禁:`cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"` 通过,`build complete`,dead link = 0。
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 读者能给一个方法判断该不该加 `@shouldReplay` / `@ignoreReplay`,并知道装饰器不保证逻辑正确。
|
||||
- 读者能说出四个 Store 各存什么、为什么运行时不该改、该在什么时机注册。
|
||||
- 构建通过,无死链、无渲染端内容。
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/10-docs-restructure/10-16-SUMMARY.md` when done
|
||||
</output>
|
||||
149
.planning/phases/10-docs-restructure/10-17-PLAN.md
Normal file
149
.planning/phases/10-docs-restructure/10-17-PLAN.md
Normal file
@ -0,0 +1,149 @@
|
||||
---
|
||||
phase: 10-docs-restructure
|
||||
plan: 17
|
||||
type: execute
|
||||
wave: 3
|
||||
depends_on: [10-13]
|
||||
files_modified:
|
||||
- docs/advanced/hooks-in-depth.md
|
||||
autonomous: false
|
||||
requirements: []
|
||||
user_setup: []
|
||||
estimate:
|
||||
tokens: 26000
|
||||
raw_tokens: 17000
|
||||
tasks: 3
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "`docs/advanced/hooks-in-depth.md` 存在,kebab-case、CRLF、单 H1"
|
||||
- "讲清多对象场景:地图与图层、同一地图多个图层、同一类主对象多个实例;说明钩子挂在实例上而不是类上"
|
||||
- "讲清身份校验:如何在回调里判断触发者是不是你关心的 map / layer;对 `IMapLayerHooks` 这种不回传 layer 的钩子,给出「每层一个钩子实例并在构造时保存 layer」的可操作做法;对 `IGameMapHooks.onResizeLayer(layer, ...)` 这种回传 layer 的钩子,给出引用比较做法"
|
||||
- "讲清同一钩子被多个对象复用:哪些钩子能共享一个实例(回传身份参数),哪些必须每对象一个实例(不回传身份);不要把共享实例写成万能做法"
|
||||
- "讲清注册 / 移除:`addHook` 返回控制器、`removeHook` 需要原实例、`controller.unload()`;`awake` / `destroy`;`forEachHook` 的执行顺序不保证;同一实例重复注册会被多次调用且旧控制器仍留在内部列表"
|
||||
- "讲清异步钩子:哪些钩子返回 `Promise`、引擎用 `Promise.all` 并发等待、不要依赖异步钩子之间的执行顺序;同步钩子立即执行"
|
||||
- "含独立的「模板:一个带身份校验的钩子」与至少一个地图/图层身份校验示例;class + new;写明「不懂代码优先改内置」;含「怎么验证」"
|
||||
- "数据端 only;高级 II/III 只用非断链占位;构建 green、dead link = 0"
|
||||
artifacts:
|
||||
- "docs/advanced/hooks-in-depth.md"
|
||||
- ".planning/phases/10-docs-restructure/10-17-SUMMARY.md"
|
||||
key_links:
|
||||
- "回指中级 `../intermediate/hooks.md`(钩子基础与需求对照表)与 `../intermediate/map-structure.md`(图层结构)"
|
||||
- "接续 10-13 的声明式模型:钩子是「声明钩子类并注册,引擎在事件发生时调用」的最直接例子"
|
||||
- "为高级 II 的系统级内容(战斗上下文、渲染层钩子消费)留出非断链入口"
|
||||
---
|
||||
|
||||
<objective>
|
||||
编写高级 I 第六篇:`hooks-in-depth.md`(钩子深入)。中级只讲了「钩子是什么、需求对应哪个钩子」;本篇讲多对象场景下最容易出错的部分:身份校验、跨对象复用、注册与移除、异步钩子的时序。
|
||||
|
||||
Purpose: 钩子本身不难,难在「同一个钩子会被多个对象触发」。读者需要能判断「这次触发是不是我关心的那个 map / layer」,否则会把效果误加到别的楼层或图层上。
|
||||
Output: 1 个新 markdown 文件。
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||
@C:/Users/13194/.config/opencode/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/phases/10-docs-restructure/10-CONTEXT.md
|
||||
@docs/advanced/declarative-vs-imperative.md
|
||||
@docs/intermediate/hooks.md
|
||||
@docs/intermediate/map-structure.md
|
||||
@packages/common/src/hook.ts
|
||||
@packages/common/src/types.ts
|
||||
@packages-user/data-base/src/map/types.ts
|
||||
@packages-user/data-base/src/map/gameMap.ts
|
||||
@packages-user/data-base/src/map/mapLayer.ts
|
||||
@packages-user/data-common/src/common/mover.ts
|
||||
@packages-user/data-base/src/hero/types.ts
|
||||
@src/data/hook/initialize.ts
|
||||
|
||||
**写作规范(同 10-13):** 受众是使用者(D-54);实操按 D-48 六步;示例 class + new(D-49);通用模板单独成节(D-50);落点优先已有文件、解释副作用、以目的先讲接口(D-51);加粗符合 flanking(D-52);数据端 only;高级 II/III 只用代码 span 或 HTML 注释(D-47)。
|
||||
|
||||
**源码事实(写作时逐条核对):**
|
||||
- `Hookable`:`packages/common/src/hook.ts`。`addHook` 创建控制器、写入映射、把钩子加入列表、调用 `hook.awake`、再加入已加载列表并返回控制器;`removeHook` 按钩子对象查找并调用 `destroy`;`removeHookByController` 按控制器移除;`forEachHook` 只遍历已加载列表,文档注明「执行顺序不固定」。
|
||||
- `addHook` 不去重:同一钩子实例再次注册会再创建一个控制器并再次加入列表,旧控制器仍留在映射里;结果是同一实例被多次调用。正文把它写成「行为事实 / 应当避免」,不要写成引擎缺陷。
|
||||
- `awake` 在加入已加载列表之前调用;因此在 `awake` 内部由其他代码触发的事件不会回调到这个钩子。需要初始同步时,在 `awake` 里直接做。
|
||||
- `IMapLayerHooks`:`packages-user/data-base/src/map/types.ts`;方法包括 `onResize`、`onUpdateArea`、`onUpdateBlock`、`onOpenDoor`、`onCloseDoor`、`onCreateDynamic`、`onDeleteDynamic`、`onUpdateDynamicPosition`。这些方法**不接收 layer 参数**。
|
||||
- `IMapLayerHookController`:带 `layer` 与 `getMapData()`;控制器在 `MapLayer.createController` 里构造,能拿到所属图层。
|
||||
- `IGameMapHooks`:`onChangeBackground(tile)`、`onUpdateLayer(layerList)`、`onResizeLayer(layer, width, height)`;`onResizeLayer` 回传具体图层。`GameMap.addLayer` 会给每个图层注册一个 `StateMapLayerHook`,把图层的 `onResize` 转发到地图级钩子并带上 layer。
|
||||
- `IObjectMoverHooks`:`onMoveStart` / `onMoveEnd` / `onStepStart` / `onStepEnd` 接收 `tile` 与 `mover`;`onSetPos` / `onSetFaceDir` / `onSetMoveDir` 也接收 `tile`。移动器用 `Promise.all` 并发等待这些异步钩子。
|
||||
- `IMapLayerHooks` 的 `onOpenDoor` / `onCloseDoor` / `onDeleteDynamic` 也返回 `Promise`,`MapLayer` 用 `Promise.all` 等待。
|
||||
- `IHeroLocationHooks` 挂在 `state.hero.location`;`IHeroStateHooks` 挂在 `state.hero`;`IObjectMoverHooks` 挂在 `state.hero.location.mover`(见 `src/data/hook/initialize.ts`)。
|
||||
- 异步钩子清单以源码为准;不要凭中级教程的清单补充未核对的方法。
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking-human">
|
||||
<name>Task 0: 向用户汇报本计划并等待「可以执行」</name>
|
||||
<action>向用户汇报:**要做什么**(编写 `docs/advanced/hooks-in-depth.md`,讲多对象场景下的钩子身份校验、复用、注册移除与异步时序)、**为什么**(同一个钩子会被多个 map / layer 触发;不会判断触发者就会把效果加到错误对象上)、**大致怎么做**(先回顾钩子实例与注册机制,再讲地图/图层的身份来源、两种身份校验做法、共享 vs 每对象一个实例、注册移除与重复注册、异步钩子的并发等待,最后给独立模板、示例与「怎么验证」)、**影响范围**(仅新增 1 个 `docs/advanced/*.md`;不改 `config.ts`、不改生产代码、不新增外链)。同时请用户确认文件名 `hooks-in-depth.md` 与页面标题「钩子深入」(D-13)。停下等待用户明确回复「可以执行」四个字;未收到前不得写任何文件。</action>
|
||||
<what-built>本计划内容:高级 I 第六篇「钩子深入」。</what-built>
|
||||
<how-to-verify>阅读上面的四段说明,并确认文件名与页面标题;如不同意请给出替代名称。</how-to-verify>
|
||||
<resume-signal>回复「可以执行」以继续;其它回复视为未批准,本计划停在此处。</resume-signal>
|
||||
<verify>
|
||||
<human-check>用户已回复「可以执行」,且未对文件名 / 页面标题提出替代。</human-check>
|
||||
</verify>
|
||||
<done>用户回复「可以执行」。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: 编写 hooks-in-depth.md 正文</name>
|
||||
<files>docs/advanced/hooks-in-depth.md</files>
|
||||
<action>(1) 写 `docs/advanced/hooks-in-depth.md`,单 H1 为「钩子深入」。(2) 正文顺序:①回顾钩子实例:钩子对象由你自己 `new`,注册到某个主对象实例上;同一个类可以 new 出多个实例,钩子挂在实例上而不是类上;回指中级 `hooks.md`。②多对象场景:一张地图有多个图层;同一张地图的每个图层都是独立的 `MapLayer` 实例;同一类主对象可能有多个实例(例如多个图层、多个移动器)。说明「钩子被触发」只说明它所属的实例发生了事件,不说明是你想要的那个实例。③身份来源:`IMapLayerHooks` 的方法不接收 layer 参数,所以身份只能来自「这个钩子实例注册在哪个 layer 上」;`IGameMapHooks.onResizeLayer` 回传 layer,`onUpdateLayer` 回传 layerList;`IObjectMoverHooks` 回传 tile 与 mover;`IHeroLocationHooks` 的 `onSetFloor` 回传 map。用一张表列出「钩子族 / 身份参数 / 能否在回调里判断」。④两种身份校验做法:A. 每层一个钩子实例,构造时保存 `readonly layer: IMapLayer`,回调里用 `layer === this.layer` 或直接只在该实例上做事;B. 共享一个钩子实例,只在回传身份参数的钩子里用 `===` 比较(如 `onResizeLayer` 的 layer、`onSetFloor` 的 map、`IObjectMoverHooks` 的 tile)。明确:`IMapLayerHooks` 不能用 B,因为回调没有 layer 参数;不要靠 `alias` 字符串比较来代替对象身份(不同地图可能重名)。⑤同一钩子被多个对象复用:回传身份的钩子可以共享一个实例;不回传身份的钩子必须每对象一个实例,或在工厂里为每个对象创建并绑定身份;共享时不要在实例字段里保存「当前对象」,否则会被后一个对象覆盖。⑥注册与移除:`addHook` 返回控制器;`removeHook` 要传回**原来的钩子实例**;`controller.unload()` 可以按控制器移除;`awake` 在钩子加入已加载列表之前调用,`destroy` 在移除时调用;`forEachHook` 的执行顺序不保证,不要依赖注册顺序;同一实例重复 `addHook` 会被多次调用且旧控制器仍留在内部列表,必须避免。⑦异步钩子:列出返回 `Promise` 的钩子族(`IObjectMoverHooks` 的移动/步进、`IMapLayerHooks` 的开关门/删除动态图块、`IHeroStateHooks` 的切层前后、`ICombatFlowHooks` 的战前战后、`IReplaySandboxHooks` 的 `onStep`);说明引擎用 `Promise.all` 并发等待同一次触发的多个异步钩子,所以不要依赖它们之间的先后顺序;确实需要顺序时,用一个钩子内部串行,或改用脚本/优先级机制(指向高级 II)。⑧「模板:一个带身份校验的钩子」独立小节:class 构造时保存 `state` 与目标 `layer`,实现 `IMapLayerHooks` 的若干方法,回调开头用 `if (layer !== this.layer) return` 或只在该实例上处理;`[模板]` / `[需改]` 标注;说明如果目标钩子不回传身份,就靠「每对象一个实例」而不是回调内比较。⑨「示例:只处理指定图层」:给一个 `MapLayer` 目标,`new` 一个钩子实例并注册到该图层;改另一个图层的图块,确认不触发;改目标图层,确认触发;移除钩子后再改,确认不再触发。示例标 `[需改]`。⑩「示例:地图级钩子里筛图层」:在 `IGameMapHooks.onResizeLayer(layer, width, height)` 里比较 `layer === this.targetLayer`;说明地图级钩子会收到该地图所有图层的 resize。⑪「改内置还是自己写」:如果只是给现有系统加行为,优先改 `src/data/hook/*` 里已注册的钩子类;只有需要独立挂载/移除多个互不影响的钩子时才自己写类;写明「如果你不怎么懂代码,建议直接改内置」。⑫常见错误:把地图级钩子挂到图层上、用 alias 判断身份、共享实例却保存当前对象、重复注册同一实例、依赖异步钩子的顺序、在 `awake` 里等自己触发的事件、移除时 new 了一个新实例导致 `removeHook` 找不到。⑬「怎么验证」:注册到目标图层 → 改别的层确认不触发 → 改目标层确认触发 → 调用控制器卸载 → 再改确认不触发;对地图级钩子,改不同图层的尺寸,确认只有目标图层命中;对异步钩子,在回调里记录开始/结束顺序,确认它们并发而不是按注册顺序。 (3) 适当加 3–5 处链接,只链已存在页面:`../intermediate/hooks.md`、`../intermediate/map-structure.md`、`./declarative-vs-imperative.md`。高级 II/III 的内容(战斗上下文、渲染端如何消费图层钩子、优先级机制)用代码 span 或 HTML 注释,不写真实链接。(4) CRLF、单 H1、数据端 only、加粗符合 flanking。完成后运行 prettier `--write`。Commit:`docs(10-17): 编写钩子深入`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Stop'; $p='docs/advanced/hooks-in-depth.md'; if(-not(Test-Path -LiteralPath $p)){ Write-Error "MISSING $p" }; $raw=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $p)); if($raw -match "(?<!`r)`n"){ Write-Error "LF-ONLY $p" }; if(([regex]::Matches($raw,'(?m)^# ')).Count -ne 1){ Write-Error "H1-COUNT $p" }; foreach($needle in @('IMapLayerHooks','IGameMapHooks','onResizeLayer','addHook','removeHook','unload','Promise.all','身份','模板:一个带身份校验的钩子','怎么验证')){ if($raw -notmatch [regex]::Escape($needle)){ Write-Error "MISSING-CONTENT $needle" } }; if($raw -match 'src/client'){ Write-Error "RENDERER-CONTENT" }; if($raw -match '\*\*[,。!?;:、()《》「」『』【】,.!?;:\(\)\[\]{}"'']' -or $raw -match '[,。!?;:、()《》「」『』【】,.!?;:\(\)\[\]{}"'']\*\*'){ Write-Error "BOLD-FLANK" }; foreach($m in [regex]::Matches($raw,'\]\((\.{1,2}/[^)#]+\.md)(#[^)]*)?\)')){ $t=Join-Path (Split-Path -Parent $p) $m.Groups[1].Value; if(-not(Test-Path -LiteralPath $t)){ Write-Error ("DEAD-LINK " + $m.Groups[1].Value) } }; Write-Output 'STATIC-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "MISSING", "LF-ONLY", "H1-COUNT", "MISSING-CONTENT", "RENDERER-CONTENT", "BOLD-FLANK", or "DEAD-LINK"; or output lacks "STATIC-OK"</fails_when>
|
||||
</verify>
|
||||
<done>文件存在、CRLF、单 H1;多对象、身份校验两种做法、共享 vs 每对象实例、注册移除、重复注册、异步并发与「怎么验证」齐全;无渲染端内容;无死链。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: 源码核对、格式与构建收口</name>
|
||||
<files>docs/advanced/hooks-in-depth.md</files>
|
||||
<action>逐条核对正文与源码:`packages/common/src/hook.ts`(`addHook` 的调用顺序、不去重、`removeHook` 的查找方式、`forEachHook` 只遍历已加载列表)、`packages/common/src/types.ts`(`IHookBase` 的 `awake` / `destroy`、`forEachHook` 的顺序声明)、`packages-user/data-base/src/map/types.ts`(`IMapLayerHooks` 与 `IGameMapHooks` 的签名)、`packages-user/data-base/src/map/gameMap.ts`(`StateMapLayerHook` 如何把 `onResize` 转发到 `onResizeLayer` 并带上 layer)、`packages-user/data-base/src/map/mapLayer.ts`(`Promise.all` 等待异步钩子)、`packages-user/data-common/src/common/mover.ts`(`Promise.all` 等待移动钩子)、`packages-user/data-base/src/hero/types.ts`(`onSetFloor` 回传 map)、`src/data/hook/initialize.ts`(各钩子注册在哪个对象上)。确认没有把 `IMapLayerHooks` 写成会回传 layer;确认没有把 `forEachHook` 写成按注册顺序执行;确认没有把 `@ignoreReplay` / 录像内容混进来;确认无渲染端内容、无死链。再跑 prettier `--write` 与构建门禁 `cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"`,确认 green、`build complete`、dead link = 0。Commit:`docs(10-17): 核对源码并收口钩子文档`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Continue'; $pf = cmd /c "node node_modules/prettier/bin/prettier.cjs --check docs/advanced/hooks-in-depth.md" 2>&1; $pfe=$LASTEXITCODE; $out = cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build" 2>&1; $ec=$LASTEXITCODE; $ErrorActionPreference='Stop'; if($pfe -ne 0){ $pf | Write-Output; Write-Error "PRETTIER-FAIL" }; if($ec -ne 0){ $out | Write-Output; Write-Error "BUILD-FAIL" }; $txt=($out | ForEach-Object { $_.ToString() }) -join "`n"; if($txt -notmatch 'build complete'){ Write-Error "NO-BUILD-COMPLETE" }; if($txt -match 'dead link'){ Write-Error "DEAD-LINK" }; Write-Output 'GATE-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "PRETTIER-FAIL", "BUILD-FAIL", "NO-BUILD-COMPLETE", or "DEAD-LINK"; or output lacks "GATE-OK"</fails_when>
|
||||
</verify>
|
||||
<done>钩子签名、身份参数、注册顺序声明与异步等待方式逐条与源码一致;prettier 通过;构建 green、`build complete`、dead link = 0。</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| 构建期 markdown → 静态站点 | 正文由构建期静态渲染,无运行时输入 |
|
||||
| 站点 → 浏览器读者 | 站内链接是唯一可点击指向面 |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-10-49 | Tampering / XSS | `hooks-in-depth.md` | low | mitigate | 只写文本、代码块、表格;不引入原始 `<script>` 或内联 HTML |
|
||||
| T-10-50 | Spoofing | 站内链接 | low | mitigate | 只链已存在页面;link 检查断言目标文件存在 |
|
||||
| T-10-51 | Information Disclosure | 正文示例 | low | mitigate | 只用源码已公开的接口与路径;不写密钥、账号 |
|
||||
| T-10-SC | Tampering | npm/pip/cargo installs | low | accept | 本阶段不安装任何包,无供应链安装面 |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- 结构门禁:文件存在、kebab-case、CRLF、单 H1。
|
||||
- 内容门禁:多对象、身份校验、复用、注册移除、异步时序均有源码依据。
|
||||
- 链接门禁:只链已存在页面;高级 II/III 不产生真实链接。
|
||||
- 边界门禁:无渲染端内容;不改 `config.ts` 与生产代码。
|
||||
- 构建门禁:`cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"` 通过,`build complete`,dead link = 0。
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 读者能判断一个钩子会不会被非目标对象触发,并能用引用比较或每对象一个实例来防串。
|
||||
- 读者知道重复注册、共享实例保存当前对象、依赖异步顺序分别会出什么问题。
|
||||
- 构建通过,无死链、无渲染端内容。
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/10-docs-restructure/10-17-SUMMARY.md` when done
|
||||
</output>
|
||||
169
.planning/phases/10-docs-restructure/10-18-PLAN.md
Normal file
169
.planning/phases/10-docs-restructure/10-18-PLAN.md
Normal file
@ -0,0 +1,169 @@
|
||||
---
|
||||
phase: 10-docs-restructure
|
||||
plan: 18
|
||||
type: execute
|
||||
wave: 5
|
||||
depends_on: [10-13, 10-14, 10-15, 10-16, 10-17]
|
||||
files_modified:
|
||||
- docs/advanced/index.md
|
||||
- docs/advanced/declarative-vs-imperative.md
|
||||
- docs/advanced/plugin-system.md
|
||||
- docs/advanced/saveable-content.md
|
||||
- docs/advanced/replay-consistency.md
|
||||
- docs/advanced/data-stores.md
|
||||
- docs/advanced/hooks-in-depth.md
|
||||
- docs/.vitepress/config.ts
|
||||
autonomous: false
|
||||
requirements: []
|
||||
user_setup: []
|
||||
estimate:
|
||||
tokens: 22000
|
||||
raw_tokens: 15000
|
||||
tasks: 4
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "`docs/advanced/index.md` 存在,作为「高级 I 导览」:讲清 I / II / III 三层、阅读路径与前置、D-55 的「优先插件」原则"
|
||||
- "I / II / III 分层与 D-53 一致;高级 I 七篇(导览 + 六篇)全部在侧边栏可见;高级 II / III 只留分组,不产生指向不存在页面的真实链接"
|
||||
- "`docs/.vitepress/config.ts` 的 `nav` 含「高级教程」入口;`sidebar` 含 `'/advanced/'` 键,高级 I 组恰好挂 7 个链接(`/advanced/` + 六个文件名)"
|
||||
- "六篇正文之间、以及正文到 `index.md` 的链接全部可解析;高级 II / III 仍只用代码 span 或 HTML 注释"
|
||||
- "七篇均 kebab-case、CRLF、单 H1;prettier 检查通过;构建 green、`build complete`、dead link = 0"
|
||||
- "不改生产代码;除 `docs/.vitepress/config.ts` 外不改站点配置"
|
||||
artifacts:
|
||||
- "docs/advanced/index.md"
|
||||
- "docs/.vitepress/config.ts(仅 nav / sidebar)"
|
||||
- ".planning/phases/10-docs-restructure/10-18-SUMMARY.md"
|
||||
key_links:
|
||||
- "把 10-13..10-17 的六篇接入站点,使 `/advanced/` 成为可浏览的完整高级 I"
|
||||
- "index 的阅读路径是 D-53 分层与 D-55 原则的唯一入口"
|
||||
- "本计划完成后,10-19 的读者人格审阅对象是「站点上可见的七篇」"
|
||||
---
|
||||
|
||||
<objective>
|
||||
编写高级 I 导览并接入站点:新增 `docs/advanced/index.md`,在 `docs/.vitepress/config.ts` 增加 `/advanced/` 的 `nav` 与 `sidebar`(高级 I 组挂七篇;高级 II / III 留空分组),最后做一次全量链接、格式与构建收口。
|
||||
|
||||
Purpose: 六篇内容已经写好,但站点没有入口就等于不存在。导览还要承担 D-53 的分层说明与 D-55 的「优先插件」原则,否则读者不知道先读哪篇、也不知道为什么不该直接改内置。
|
||||
Output: `index.md`、`config.ts` 的注册,以及七篇之间的可解析链接。
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||
@C:/Users/13194/.config/opencode/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/phases/10-docs-restructure/10-CONTEXT.md
|
||||
@docs/advanced/declarative-vs-imperative.md
|
||||
@docs/advanced/plugin-system.md
|
||||
@docs/advanced/saveable-content.md
|
||||
@docs/advanced/replay-consistency.md
|
||||
@docs/advanced/data-stores.md
|
||||
@docs/advanced/hooks-in-depth.md
|
||||
@docs/.vitepress/config.ts
|
||||
@docs/intermediate/data-plugins.md
|
||||
@docs/intermediate/data-vs-render.md
|
||||
@docs/intermediate/src-tour.md
|
||||
@docs/intermediate/code-basics.md
|
||||
@docs/intermediate/hooks.md
|
||||
@docs/intermediate/data-state-overview.md
|
||||
|
||||
**写作规范(同 10-13):** 受众是使用者(D-54);实操按 D-48 六步;示例 class + new(D-49);通用模板单独成节(D-50);落点优先已有文件、解释副作用、以目的先讲接口(D-51);加粗符合 flanking(D-52);数据端 only;高级 II/III 只用代码 span 或 HTML 注释(D-47)。
|
||||
|
||||
**分层与原则(D-53 / D-55,逐字遵守):**
|
||||
- 高级 I:核心是插件系统,后续内容都是基于插件系统的拓展。
|
||||
- 高级 II:系统级实现(战斗上下文、光环/查询/优先级、伤害与地图伤害、渲染层与布局、加载与插件生命周期、自定义录像行为等)。
|
||||
- 高级 III:原理与架构(双端分离约束、数据端 L0–L3、编辑器识别协议、录像深层原理、性能与调试等)。
|
||||
- D-55:新增系统优先通过插件系统实现;只有不得不修改内置系统时才改内部。理由:版本迁移时 `src` 基本不会改,但内置包(`packages` / `packages-user`)有可能会改,改内置会增加升级维护成本。
|
||||
- 当前只写数据端内容;渲染端尚未完工,不写渲染端正文。
|
||||
|
||||
**配置现状:**
|
||||
- `docs/.vitepress/config.ts` 的 `nav` 当前只有中级教程一条;`sidebar` 只有 `'/intermediate/'` 键。
|
||||
- 侧边栏为手写数组;新增 `'/advanced/'` 键即可。链接写法用绝对路径、不带 `.md`(与 existing `'/intermediate/'` 写法一致)。
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking-human">
|
||||
<name>Task 0: 向用户汇报本计划并等待「可以执行」</name>
|
||||
<action>向用户汇报:**要做什么**(编写 `docs/advanced/index.md` 高级 I 导览;在 `docs/.vitepress/config.ts` 增加「高级教程」导航与 `/advanced/` 侧边栏;把七篇互相链接起来并做全量构建)、**为什么**(六篇内容需要站点入口;导览要讲清 I/II/III 分层、阅读路径与 D-55 的「优先插件」原则)、**大致怎么做**(先写导览,再改 `nav` / `sidebar`,最后做链接与构建收口;高级 II/III 只留空分组,不产生死链)、**影响范围**(新增 `index.md`;修改 `config.ts` 的 `nav` / `sidebar`;轻微编辑六篇的互链与返回导览链接;不改生产代码、不新增外链)。同时请用户确认三个新显示名:「高级教程」(导航)、「高级 I · 扩展基础」「高级 II · 系统实现」「高级 III · 原理与架构」(侧边栏分组),以及导览文件名 `index.md`、页面标题「高级 I 导览」(D-13)。停下等待用户明确回复「可以执行」四个字;未收到前不得改任何文件。</action>
|
||||
<what-built>本计划内容:高级 I 导览 + 站点注册 + 六篇互链收口。</what-built>
|
||||
<how-to-verify>阅读上面的四段说明,并确认三个分组显示名、导航名与导览标题;如不同意请给出替代名称。</how-to-verify>
|
||||
<resume-signal>回复「可以执行」以继续;其它回复视为未批准,本计划停在此处。</resume-signal>
|
||||
<verify>
|
||||
<human-check>用户已回复「可以执行」,且未对导航名 / 分组显示名 / 导览标题提出替代。</human-check>
|
||||
</verify>
|
||||
<done>用户回复「可以执行」。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: 编写 docs/advanced/index.md 导览</name>
|
||||
<files>docs/advanced/index.md</files>
|
||||
<action>(1) 写 `docs/advanced/index.md`,单 H1 为「高级 I 导览」。(2) 正文顺序:①高级教程是给谁的——有高度定制需求、需要理解扩展机制的数据端作者;不是引擎编写者;数据端 only。②「高级教程的三层」:用表格列 I / II / III 的定位与主题(按 D-53 原文归纳);说明当前可读的是 I,II / III 的内容待对应条件成熟后补充,但**不写真实链接**,用代码 span 或 HTML 注释。③「高级 I 包含什么」:一张表列七篇(导览本身 + 六篇),每篇一句话说明它能让你做什么,并给推荐阅读顺序:声明式 vs 命令式 → 插件系统 → 可存档内容 → 录像一致性 → data-common 的 Store → 钩子深入。④「阅读路径与前置」:前置是读完中级教程,尤其是 `data-vs-render`、`src-tour`、`code-basics`、`hooks`、`data-state-overview`、`data-plugins`、`replay-commands`;给两条路径——「做扩展系统」:声明式 → 插件 → 存档 → 录像;「改内容/机制」:声明式 → Store → 钩子;说明两条路径都建议先读声明式篇。⑤「优先用插件,少改内置」:按 D-55 讲清建议与理由(`src` 基本不变、内置包可能变、改内置增加升级维护成本);给一张表:需求类型 / 优先做法 / 什么情况下才改内置;明确「如果你不怎么懂代码,优先改内置已有实现,而不是自己造一套」。⑥「本层不覆盖什么」:渲染端内容、高级 II 的系统级实现、高级 III 的原理与架构、API 参考;只作边界说明,不展开。⑦「怎么验证你读懂了」:给一个可操作的小清单——能解释声明式与命令式的区别;能指出一个需求该做成插件还是改内置;能说出一个可存档对象必须满足的三个约束;能判断一个钩子会不会被非目标对象触发。⑧结尾给七篇的真实链接(此时六篇都已存在):`./declarative-vs-imperative.md`、`./plugin-system.md`、`./saveable-content.md`、`./replay-consistency.md`、`./data-stores.md`、`./hooks-in-depth.md`;再给 2–3 个中级前置页面的链接。(3) 不要把高级 II / III 写成真实链接;不要写「后续再讲」式元叙述;不要写渲染端正文;加粗符合 flanking。完成后运行 prettier `--write`。Commit:`docs(10-18): 编写高级 I 导览`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Stop'; $p='docs/advanced/index.md'; if(-not(Test-Path -LiteralPath $p)){ Write-Error "MISSING $p" }; $raw=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $p)); if($raw -match "(?<!`r)`n"){ Write-Error "LF-ONLY $p" }; if(([regex]::Matches($raw,'(?m)^# ')).Count -ne 1){ Write-Error "H1-COUNT $p" }; foreach($needle in @('高级 I','高级 II','高级 III','阅读路径','前置','优先','插件','怎么验证')){ if($raw -notmatch [regex]::Escape($needle)){ Write-Error "MISSING-CONTENT $needle" } }; foreach($target in @('./declarative-vs-imperative.md','./plugin-system.md','./saveable-content.md','./replay-consistency.md','./data-stores.md','./hooks-in-depth.md')){ if($raw -notmatch [regex]::Escape($target)){ Write-Error "MISSING-LINK $target" } }; if($raw -match 'src/client'){ Write-Error "RENDERER-CONTENT" }; if($raw -match '\*\*[,。!?;:、()《》「」『』【】,.!?;:\(\)\[\]{}"'']' -or $raw -match '[,。!?;:、()《》「」『』【】,.!?;:\(\)\[\]{}"'']\*\*'){ Write-Error "BOLD-FLANK" }; foreach($m in [regex]::Matches($raw,'\]\((\.{1,2}/[^)#]+\.md)(#[^)]*)?\)')){ $t=Join-Path (Split-Path -Parent $p) $m.Groups[1].Value; if(-not(Test-Path -LiteralPath $t)){ Write-Error ("DEAD-LINK " + $m.Groups[1].Value) } }; Write-Output 'STATIC-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "MISSING", "LF-ONLY", "H1-COUNT", "MISSING-CONTENT", "MISSING-LINK", "RENDERER-CONTENT", "BOLD-FLANK", or "DEAD-LINK"; or output lacks "STATIC-OK"</fails_when>
|
||||
</verify>
|
||||
<done>导览存在、CRLF、单 H1;三层分层、七篇表与阅读顺序、阅读路径与前置、D-55 原则与取舍表、边界、自检清单、六篇真实链接齐全;无渲染端内容;无死链。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: 在 config.ts 注册高级教程导航与侧边栏</name>
|
||||
<files>docs/.vitepress/config.ts</files>
|
||||
<action>(1) 在 `docs/.vitepress/config.ts` 的 `nav` 数组里,保留现有「中级教程」条目,新增 `{ text: '高级教程', link: '/advanced/' }`。(2) 在 `sidebar` 对象里新增 `'/advanced/'` 键,值是一个分组数组:第一组 `text: '高级 I · 扩展基础'`、`collapsed: false`,`items` 依次为 `{ text: '高级 I 导览', link: '/advanced/' }`、`{ text: '声明式 vs 命令式接口', link: '/advanced/declarative-vs-imperative' }`、`{ text: '插件系统', link: '/advanced/plugin-system' }`、`{ text: '可存档内容', link: '/advanced/saveable-content' }`、`{ text: '录像一致性', link: '/advanced/replay-consistency' }`、`{ text: 'data-common 的 Store', link: '/advanced/data-stores' }`、`{ text: '钩子深入', link: '/advanced/hooks-in-depth' }`;第二组 `text: '高级 II · 系统实现'`、`collapsed: true`、`items: []`;第三组 `text: '高级 III · 原理与架构'`、`collapsed: true`、`items: []`。不添加指向不存在页面的链接。(3) 保持现有 `/intermediate/` 键与格式不变;不要改 `nav` 里的中级条目;不要改 `outDir`、`base`、搜索、mermaid 等其他配置。(4) 用 `git diff -- docs/.vitepress/config.ts` 自查,确认只新增了导航条目与 `/advanced/` 侧边栏键。Commit:`docs(10-18): 注册高级教程导航与侧边栏`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Stop'; $cfg='docs/.vitepress/config.ts'; if(-not(Test-Path -LiteralPath $cfg)){ Write-Error "MISSING $cfg" }; $raw=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $cfg)); if($raw -notmatch "text:\s*'高级教程'"){ Write-Error "NO-NAV" }; if($raw -notmatch "'/advanced/':"){ Write-Error "NO-SIDEBAR-KEY" }; if($raw -notmatch "text:\s*'高级 I · 扩展基础'"){ Write-Error "NO-GROUP-I" }; if($raw -notmatch "text:\s*'高级 II · 系统实现'"){ Write-Error "NO-GROUP-II" }; if($raw -notmatch "text:\s*'高级 III · 原理与架构'"){ Write-Error "NO-GROUP-III" }; $targets=[regex]::Matches($raw,"link:\s*'/advanced/([^']*)'") | ForEach-Object { $_.Groups[1].Value } | Sort-Object -Unique; $want=@('','data-stores','declarative-vs-imperative','hooks-in-depth','plugin-system','replay-consistency','saveable-content') | Sort-Object -Unique; if(($targets -join ',') -ne ($want -join ',')){ Write-Error ("ADVANCED-LINKS " + ($targets -join ',')) }; Write-Output 'CONFIG-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "MISSING", "NO-NAV", "NO-SIDEBAR-KEY", "NO-GROUP-I", "NO-GROUP-II", "NO-GROUP-III", or "ADVANCED-LINKS"; or output lacks "CONFIG-OK"</fails_when>
|
||||
</verify>
|
||||
<done>`nav` 含高级教程;`sidebar['/advanced/']` 含 I/II/III 三组;高级 I 组恰好 7 个目标(导览 + 六篇);II/III 为空分组;其余配置未动。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: 六篇互链收口、格式与全量构建</name>
|
||||
<files>docs/advanced/declarative-vs-imperative.md, docs/advanced/plugin-system.md, docs/advanced/saveable-content.md, docs/advanced/replay-consistency.md, docs/advanced/data-stores.md, docs/advanced/hooks-in-depth.md</files>
|
||||
<action>(1) 逐篇检查六篇正文:把指向**已存在的高级 I 页面**的代码 span / 待补标记改成真实相对链接(例如 `./plugin-system.md`、`./saveable-content.md`、`./replay-consistency.md`、`./data-stores.md`、`./hooks-in-depth.md`、`./index.md`);不要动高级 II / III 的占位(它们仍无真实页面)。(2) 在每篇末尾加一行「返回[高级 I 导览](./index.md)」,保持链接数量适中(D-17)。(3) 确认没有页面链接到不存在的 `.md`;确认没有页面出现渲染端源码路径;确认没有页面把具体示例当通用模板;确认加粗符合 flanking。(4) 对七篇跑 prettier:`node node_modules/prettier/bin/prettier.cjs --write "docs/advanced/*.md"`。(5) 跑全量链接检查与构建门禁 `cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"`,确认七篇都被构建、`build complete`、dead link = 0。Commit:`docs(10-18): 收口高级 I 互链与构建`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Stop'; $names=@('index','declarative-vs-imperative','plugin-system','saveable-content','replay-consistency','data-stores','hooks-in-depth'); foreach($n in $names){ $p="docs/advanced/$n.md"; if(-not(Test-Path -LiteralPath $p)){ Write-Error "MISSING $p" }; $raw=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $p)); if($raw -match "(?<!`r)`n"){ Write-Error "LF-ONLY $p" }; if(([regex]::Matches($raw,'(?m)^# ')).Count -ne 1){ Write-Error "H1-COUNT $p" }; if($raw -match 'src/client'){ Write-Error "RENDERER-CONTENT $p" }; if($raw -match '\*\*[,。!?;:、()《》「」『』【】,.!?;:\(\)\[\]{}"'']' -or $raw -match '[,。!?;:、()《》「」『』【】,.!?;:\(\)\[\]{}"'']\*\*'){ Write-Error "BOLD-FLANK $p" }; foreach($m in [regex]::Matches($raw,'\]\((\.{1,2}/[^)#]+\.md)(#[^)]*)?\)')){ $t=Join-Path (Split-Path -Parent $p) $m.Groups[1].Value; if(-not(Test-Path -LiteralPath $t)){ Write-Error ("DEAD-LINK $p -> " + $m.Groups[1].Value) } } }; $cfg=[IO.File]::ReadAllText((Resolve-Path 'docs/.vitepress/config.ts')); $targets=[regex]::Matches($cfg,"link:\s*'/advanced/([^']*)'") | ForEach-Object { $_.Groups[1].Value } | Sort-Object -Unique; $want=@('','data-stores','declarative-vs-imperative','hooks-in-depth','plugin-system','replay-consistency','saveable-content') | Sort-Object -Unique; if(($targets -join ',') -ne ($want -join ',')){ Write-Error ("ADVANCED-LINKS " + ($targets -join ',')) }; $ErrorActionPreference='Continue'; $pf = cmd /c "node node_modules/prettier/bin/prettier.cjs --check docs/advanced/index.md docs/advanced/declarative-vs-imperative.md docs/advanced/plugin-system.md docs/advanced/saveable-content.md docs/advanced/replay-consistency.md docs/advanced/data-stores.md docs/advanced/hooks-in-depth.md" 2>&1; $pfe=$LASTEXITCODE; $out = cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build" 2>&1; $ec=$LASTEXITCODE; $ErrorActionPreference='Stop'; if($pfe -ne 0){ $pf | Write-Output; Write-Error "PRETTIER-FAIL" }; if($ec -ne 0){ $out | Write-Output; Write-Error "BUILD-FAIL" }; $txt=($out | ForEach-Object { $_.ToString() }) -join "`n"; if($txt -notmatch 'build complete'){ Write-Error "NO-BUILD-COMPLETE" }; if($txt -match 'dead link'){ Write-Error "DEAD-LINK" }; Write-Output 'FINAL-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "MISSING", "LF-ONLY", "H1-COUNT", "RENDERER-CONTENT", "BOLD-FLANK", "DEAD-LINK", "ADVANCED-LINKS", "PRETTIER-FAIL", "BUILD-FAIL", or "NO-BUILD-COMPLETE"; or output lacks "FINAL-OK"</fails_when>
|
||||
</verify>
|
||||
<done>七篇文件齐全、CRLF、单 H1;互链与返回导览链接可解析;无渲染端内容;prettier 检查通过;侧边栏 7 个目标正确;构建 green、`build complete`、dead link = 0。</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| 构建期 markdown → 静态站点 | 正文与侧边栏由构建期静态处理,无运行时输入 |
|
||||
| 站点 → 浏览器读者 | 站内链接是唯一可点击指向面 |
|
||||
| 配置层 → 构建产物 | `config.ts` 的 nav / sidebar 决定可浏览页面集合 |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-10-52 | Tampering / XSS | 七篇正文与配置 | low | mitigate | 只写文本、代码块、表格;不引入原始 `<script>` 或内联 HTML |
|
||||
| T-10-53 | Spoofing | 站内链接 | low | mitigate | 全量 link 检查断言每个相对 `.md` 目标存在;高级 II/III 不入侧边栏 |
|
||||
| T-10-54 | Information Disclosure | 正文 / 配置 | low | mitigate | 只引用仓库已公开路径;不写密钥、账号;侧边栏只挂已存在页面 |
|
||||
| T-10-SC | Tampering | npm/pip/cargo installs | low | accept | 本阶段不安装任何包,无供应链安装面 |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- 结构门禁:七篇存在、kebab-case、CRLF、单 H1。
|
||||
- 注册门禁:`nav` 含高级教程;`sidebar['/advanced/']` 含 I/II/III 三组;I 组恰好 7 个目标。
|
||||
- 链接门禁:七篇内所有相对 `.md` 链接目标存在;高级 II/III 无真实链接。
|
||||
- 格式门禁:prettier `--check` 对七篇通过。
|
||||
- 边界门禁:无渲染端内容;仅 `config.ts` 的 nav/sidebar 变更。
|
||||
- 构建门禁:`cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"` 通过,`build complete`,dead link = 0。
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 高级 I 七篇在站点上可见、可导航、可互相跳转。
|
||||
- 导览讲清 I/II/III、阅读路径、前置与 D-55 原则。
|
||||
- 构建通过,无死链,无渲染端内容,配置改动仅限 nav / sidebar。
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/10-docs-restructure/10-18-SUMMARY.md` when done
|
||||
</output>
|
||||
168
.planning/phases/10-docs-restructure/10-19-PLAN.md
Normal file
168
.planning/phases/10-docs-restructure/10-19-PLAN.md
Normal file
@ -0,0 +1,168 @@
|
||||
---
|
||||
phase: 10-docs-restructure
|
||||
plan: 19
|
||||
type: execute
|
||||
wave: 6
|
||||
depends_on: [10-18]
|
||||
files_modified:
|
||||
- .planning/phases/10-docs-restructure/10-19-READER-REVIEW.md
|
||||
- docs/advanced/index.md
|
||||
- docs/advanced/declarative-vs-imperative.md
|
||||
- docs/advanced/plugin-system.md
|
||||
- docs/advanced/saveable-content.md
|
||||
- docs/advanced/replay-consistency.md
|
||||
- docs/advanced/data-stores.md
|
||||
- docs/advanced/hooks-in-depth.md
|
||||
autonomous: false
|
||||
requirements: []
|
||||
user_setup: []
|
||||
estimate:
|
||||
tokens: 32000
|
||||
raw_tokens: 21000
|
||||
tasks: 4
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "读者人格**不是**中级教程用过的「不懂代码的游戏作者」,而是「有一定编程基础、已读完中级教程、想为引擎做深度定制、能读懂代码但不熟悉引擎内部架构的作者」;该人格在 Task 0 已经用户确认"
|
||||
- "`10-19-READER-REVIEW.md` 存在,逐字记录人格提示词、审阅范围(高级 I 七篇)、问题清单,并按**阻断性**与**非阻断**归类、定位到文件与章节"
|
||||
- "阻断性问题被迭代修正(上限 3 轮);每轮后构建 green、`build complete`、dead link = 0;达到上限仍有阻断问题时停下交用户,不无限循环"
|
||||
- "迭代结束再派发一个独立子代理(同一人格)复审,`10-19-READER-REVIEW.md` 逐条标注「已消除」或「仍存在」;阻断性问题为 0(或余项已交用户)"
|
||||
- "只改 `docs/advanced/*.md` 与新增的审阅记录;不改 `config.ts` 与生产代码;被改文件保持 kebab-case、CRLF、单 H1"
|
||||
- "非阻断问题只记录、不擅自扩大范围(D-14);不因读者提问删掉 D-53..D-57 要求的内容"
|
||||
artifacts:
|
||||
- ".planning/phases/10-docs-restructure/10-19-READER-REVIEW.md"
|
||||
- "docs/advanced/*.md(仅存在阻断性问题的篇目)"
|
||||
key_links:
|
||||
- "执行 D-57 第 1 阶段:独立的、不同于中级教程的读者人格 → 问题清单 → 迭代修正 → 复审"
|
||||
- "复审通过后,10-20 才进入用户主导的修订循环;本计划不替用户收尾"
|
||||
- "审阅对象是 10-18 已接入站点的七篇,而不是未注册的草稿"
|
||||
---
|
||||
|
||||
<objective>
|
||||
执行高级 I 的独立读者审阅:派发一个与中级教程**不同人格**的独立子代理(有一定编程基础、读完中级教程、想做深度定制、能读懂代码但不熟悉引擎内部架构的作者),通读七篇高级 I,产出问题清单;据此迭代修正阻断性问题,最后再派一个同人格独立子代理复审,直到阻断性问题为零或把余项交给用户。
|
||||
|
||||
Purpose: D-57 明确要求高级 I 必须走读者修订循环,且不能复用中级教程使用过的读者人格。高级 I 的读者会真的照文档写插件、写可存档对象,所以问题集中在「概念之间的连接」「照着做会不会卡住」「代码放哪」这些层面。
|
||||
Output: `10-19-READER-REVIEW.md`(人格、问题、迭代、复审)与针对阻断问题的文档修正。
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||
@C:/Users/13194/.config/opencode/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/phases/10-docs-restructure/10-CONTEXT.md
|
||||
@.planning/phases/10-docs-restructure/10-11-READER-REVIEW.md
|
||||
@.planning/phases/10-docs-restructure/10-18-PLAN.md
|
||||
@docs/advanced/index.md
|
||||
@docs/advanced/declarative-vs-imperative.md
|
||||
@docs/advanced/plugin-system.md
|
||||
@docs/advanced/saveable-content.md
|
||||
@docs/advanced/replay-consistency.md
|
||||
@docs/advanced/data-stores.md
|
||||
@docs/advanced/hooks-in-depth.md
|
||||
@docs/intermediate/hooks.md
|
||||
@docs/intermediate/data-plugins.md
|
||||
|
||||
**人格设定(D-57,不得复用中级教程人格):**
|
||||
> 假设你是一位有一定编程基础、已经读完本样板中级教程、想做深度定制、能读懂代码但不熟悉引擎内部架构的游戏作者。你正在阅读高级教程 I,想判断自己能不能照着文档完成扩展。
|
||||
|
||||
**与中级审阅的区别:**
|
||||
- 中级人格是「不懂代码的游戏作者」(见 `10-11-READER-REVIEW.md`),本计划**禁止**复用该人格或该提示词。
|
||||
- 高级 I 读者能读懂代码,所以问题不再是「变量是什么」,而是「这段代码该放哪」「两个系统怎么接」「照做之后为什么没有效果」「存档/录像/钩子之间怎么互相影响」。
|
||||
- 审阅对象是 `docs/advanced/` 的七篇,不是中级教程。
|
||||
|
||||
**流程约定:**
|
||||
- 迭代上限 **3 轮**;每轮后跑构建门禁;达到上限仍有阻断问题时停下向用户汇报。
|
||||
- 只改 `docs/advanced/*.md`;不触碰 `config.ts` 与生产代码。
|
||||
- 命令一律经 `cmd /c "..."` 执行(本机 PowerShell 禁止 `pnpm.ps1`)。
|
||||
- 修正须守住 D-53..D-57 与 D-48..D-52;不因读者提问删掉要求覆盖的内容。
|
||||
- 阻断性判定口径:读者据此**无法完成操作**,或会对**概念/系统边界产生实质误解**。
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking-human">
|
||||
<name>Task 0: 向用户汇报读者审阅计划并确认人格</name>
|
||||
<action>向用户汇报:**要做什么**(派发独立子代理,以「有一定编程基础、已读完中级教程、想做深度定制、能读懂代码但不熟悉引擎内部架构的作者」人格通读高级 I 七篇,产出问题清单,据此迭代修正并复审)、**为什么**(D-57 要求高级 I 走读者修订循环,且不能复用中级教程使用过的读者人格)、**大致怎么做**(Task 1 派发 persona 子代理并落清单;Task 2 按清单修阻断问题,最多 3 轮、每轮构建;Task 3 复跑同人格审阅确认阻断清零)、**影响范围**(可能修改 `docs/advanced/*.md` 中确有阻断问题的篇目 + 新增 `.planning` 下的审阅记录;不改站点配置与生产代码)。请用户逐字确认人格提示词:「假设你是一位有一定编程基础、已经读完本样板中级教程、想做深度定制、能读懂代码但不熟悉引擎内部架构的游戏作者。你正在阅读高级教程 I,想判断自己能不能照着文档完成扩展。」并等待用户明确回复「可以执行」四个字;未收到前不得派发子代理或改任何文件。</action>
|
||||
<what-built>本计划内容:高级 I 独立读者人格审阅 → 问题清单 → 迭代修正 → 复审确认。</what-built>
|
||||
<how-to-verify>阅读上面的四段说明;确认人格提示词与中级教程不同、审阅范围是高级 I 七篇、迭代上限 3 轮;如对人格有修改请直接给出。</how-to-verify>
|
||||
<resume-signal>回复「可以执行」以继续;其它回复视为未批准,本计划停在此处。</resume-signal>
|
||||
<verify>
|
||||
<human-check>用户已确认人格提示词并回复「可以执行」。</human-check>
|
||||
</verify>
|
||||
<done>用户确认人格并回复「可以执行」。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: 派发独立读者子代理并产出问题清单</name>
|
||||
<files>.planning/phases/10-docs-restructure/10-19-READER-REVIEW.md</files>
|
||||
<action>派发一个**独立子代理**(不与执行者共享上下文),其人格与任务设定逐字为:「假设你是一位有一定编程基础、已经读完本样板中级教程、想做深度定制、能读懂代码但不熟悉引擎内部架构的游戏作者。你正在阅读高级教程 I,想判断自己能不能照着文档完成扩展。」让子代理按 `docs/advanced/index.md` 的推荐顺序通读七篇(导览、声明式 vs 命令式、插件系统、可存档内容、录像一致性、data-common 的 Store、钩子深入),然后回答:「读完后你有哪些问题?哪些地方你照做会卡住、会做错,或者会把系统边界理解错?」子代理**只以读者身份提问**、不修改文件、不代入引擎作者视角。把原始提问与执行者的归类写进 `.planning/phases/10-docs-restructure/10-19-READER-REVIEW.md`:开头逐字记录人格提示词与审阅范围;每条问题附文件与章节定位;按**阻断性**与**非阻断**分组。阻断性口径:读者据此无法完成操作,或会对概念/系统边界产生实质误解。**本轮不改文档**。Commit:`docs(10-19): 记录高级 I 读者问题清单`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Stop'; $f='.planning/phases/10-docs-restructure/10-19-READER-REVIEW.md'; if(-not(Test-Path -LiteralPath $f)){ Write-Error "MISSING $f" }; $raw=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $f)); foreach($needle in @('有一定编程基础','读完本样板中级教程','深度定制','不熟悉引擎内部架构','阻断','非阻断','docs/advanced/index.md')){ if($raw -notmatch [regex]::Escape($needle)){ Write-Error "MISSING-CONTENT $needle" } }; if($raw -match '不懂代码的游戏作者'){ Write-Error "REUSED-INTERMEDIATE-PERSONA" }; Write-Output 'REVIEW-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "MISSING", "MISSING-CONTENT", or "REUSED-INTERMEDIATE-PERSONA"; or output lacks "REVIEW-OK"</fails_when>
|
||||
</verify>
|
||||
<done>审阅记录存在;逐字含高级 I 人格提示词与七篇范围;问题按阻断/非阻断归类并定位到文件章节;未复用中级人格。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: 按清单迭代修正阻断问题(上限 3 轮)</name>
|
||||
<files>docs/advanced/*.md(仅存在阻断性问题的篇目)</files>
|
||||
<action>逐个消除 `10-19-READER-REVIEW.md` 中的**阻断性问题**:对每个阻断问题,修改对应 `docs/advanced/*.md` 的相应段落,使一个「能读懂代码但不熟悉引擎内部架构」的读者也能照做——补「这段代码放哪」「和哪个系统对接」「为什么照做没效果」「存档/录像/钩子之间怎么互相影响」;必要时补最短可行示例或调整章节顺序。只改确有阻断问题的篇目;非阻断问题不修、仅保留在清单中供用户判断(D-14)。每轮修正后:(a) 跑构建门禁 `cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"`,须 green、`build complete`、dead link = 0;(b) 在 `10-19-READER-REVIEW.md` 追加该轮记录(本轮修了哪些问题、依据、构建结论)。迭代**最多 3 轮**:3 轮后仍有阻断问题时,停下把余项列给用户,不继续循环。守住 D-53..D-57 与 D-48..D-52,不因读者提问删掉要求覆盖的内容;不改 `config.ts` 与生产代码。每轮 Commit:`docs(10-19): 迭代修正高级 I 阻断问题(第N轮)`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Stop'; $names=@('index','declarative-vs-imperative','plugin-system','saveable-content','replay-consistency','data-stores','hooks-in-depth'); foreach($n in $names){ $p="docs/advanced/$n.md"; if(-not(Test-Path -LiteralPath $p)){ Write-Error "MISSING $p" }; $raw=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $p)); if($raw -match "(?<!`r)`n"){ Write-Error "LF-ONLY $p" }; if(([regex]::Matches($raw,'(?m)^# ')).Count -ne 1){ Write-Error "H1-COUNT $p" } }; $f='.planning/phases/10-docs-restructure/10-19-READER-REVIEW.md'; if(([IO.File]::ReadAllText((Resolve-Path -LiteralPath $f))) -notmatch '轮|迭代'){ Write-Error "NO-ROUND-LOG" }; $ErrorActionPreference='Continue'; $out = cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build" 2>&1; $ec=$LASTEXITCODE; $ErrorActionPreference='Stop'; if($ec -ne 0){ $out | Write-Output; Write-Error "BUILD-FAIL" }; $txt=($out | ForEach-Object { $_.ToString() }) -join "`n"; if($txt -notmatch 'build complete'){ Write-Error "NO-BUILD-COMPLETE" }; if($txt -match 'dead link'){ Write-Error "DEAD-LINK" }; Write-Output 'ITERATE-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "MISSING", "LF-ONLY", "H1-COUNT", "NO-ROUND-LOG", "BUILD-FAIL", "NO-BUILD-COMPLETE", or "DEAD-LINK"; or output lacks "ITERATE-OK"</fails_when>
|
||||
</verify>
|
||||
<done>阻断问题按最多 3 轮修正完毕(余项已交用户);每轮记录进 `10-19-READER-REVIEW.md`;构建 green、`build complete`、dead link = 0。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: 复跑同人格审阅确认阻断清零</name>
|
||||
<files>.planning/phases/10-docs-restructure/10-19-READER-REVIEW.md</files>
|
||||
<action>再次派发一个**独立子代理**(人格提示词逐字同 Task 1,不得换回中级教程人格),让它重新通读七篇并回答「现在还有哪些看不懂、照做会卡住、或会把系统边界理解错的地方?」。把复审结果追加进 `10-19-READER-REVIEW.md`:对照首轮清单,逐条标注「已消除」或「仍存在」;若仍有**阻断性**问题,回到 Task 2 再修(受 3 轮上限约束),否则记「阻断性问题 = 0,审阅通过」。最后跑一次构建门禁确认 green。本任务不引入新功能,只收敛理解性问题。Commit:`docs(10-19): 复审高级 I 读者视角(阻断问题清零)`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Stop'; $f='.planning/phases/10-docs-restructure/10-19-READER-REVIEW.md'; if(-not(Test-Path -LiteralPath $f)){ Write-Error "MISSING $f" }; $raw=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $f)); foreach($needle in @('已消除','仍存在','阻断性问题 = 0')){ if($raw -notmatch [regex]::Escape($needle)){ Write-Error "MISSING-CONTENT $needle" } }; if($raw -match '不懂代码的游戏作者'){ Write-Error "REUSED-INTERMEDIATE-PERSONA" }; $ErrorActionPreference='Continue'; $out = cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build" 2>&1; $ec=$LASTEXITCODE; $ErrorActionPreference='Stop'; if($ec -ne 0){ $out | Write-Output; Write-Error "BUILD-FAIL" }; $txt=($out | ForEach-Object { $_.ToString() }) -join "`n"; if($txt -notmatch 'build complete'){ Write-Error "NO-BUILD-COMPLETE" }; if($txt -match 'dead link'){ Write-Error "DEAD-LINK" }; Write-Output 'RECHECK-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "MISSING", "MISSING-CONTENT", "REUSED-INTERMEDIATE-PERSONA", "BUILD-FAIL", "NO-BUILD-COMPLETE", or "DEAD-LINK"; or output lacks "RECHECK-OK"</fails_when>
|
||||
</verify>
|
||||
<done>复审结果记录进 `10-19-READER-REVIEW.md`(逐条已消除/仍存在);阻断性问题 = 0(或余项已交用户);构建 green、`build complete`、dead link = 0。</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| 执行者 → 子代理 | 读者人格子代理以独立上下文阅读七篇并提问,不修改文件 |
|
||||
| 构建期 markdown → 静态站点 | 正文由构建期静态渲染,无运行时输入 |
|
||||
| 站点 → 浏览器读者 | 站内链接是唯一可点击指向面 |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-10-55 | Tampering / XSS | 被改高级 I 正文 | low | mitigate | 只写文本、代码块、表格;不引入原始 `<script>` 或内联 HTML |
|
||||
| T-10-56 | Spoofing | 审阅记录 / 链接 | low | mitigate | 记录逐字保留人格与来源;链接检查断言目标存在 |
|
||||
| T-10-57 | Information Disclosure | 审阅记录 / 正文 | low | mitigate | 只引用仓库已公开路径与接口;不写密钥、账号 |
|
||||
| T-10-SC | Tampering | npm/pip/cargo installs | low | accept | 本阶段不安装任何包,无供应链安装面 |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- 机制门禁:`10-19-READER-REVIEW.md` 含高级 I 人格提示词、七篇范围、首轮问题清单、每轮迭代记录、复审对照结果。
|
||||
- 人格门禁:记录中不得出现中级教程的「不懂代码的游戏作者」人格;提示词含「有一定编程基础」「读完中级教程」「深度定制」「不熟悉引擎内部架构」。
|
||||
- 迭代门禁:上限 3 轮;结束状态为阻断性问题 = 0(或余项已交用户)。
|
||||
- 构建门禁:每轮及收尾均 `cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"` 通过(green / `build complete` / dead link = 0)。
|
||||
- 结构门禁:七篇仍 kebab-case、CRLF、单 H1。
|
||||
- 越界门禁:`git status` 仅含 `docs/advanced/*.md` 的修改与新增的 `.planning` 审阅记录。
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 高级 I 的读者人格审阅按 D-57 落地:独立子代理 + 不同人格 + 问题清单 + 迭代 + 复审。
|
||||
- 阻断性问题清零(或余项已明确交用户);构建通过。
|
||||
- 没有复用中级人格,没有越界改动。
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/10-docs-restructure/10-19-SUMMARY.md` when done
|
||||
</output>
|
||||
177
.planning/phases/10-docs-restructure/10-20-PLAN.md
Normal file
177
.planning/phases/10-docs-restructure/10-20-PLAN.md
Normal file
@ -0,0 +1,177 @@
|
||||
---
|
||||
phase: 10-docs-restructure
|
||||
plan: 20
|
||||
type: execute
|
||||
wave: 7
|
||||
depends_on: [10-19]
|
||||
files_modified:
|
||||
- .planning/phases/10-docs-restructure/10-20-USER-REVIEW.md
|
||||
- docs/advanced/index.md
|
||||
- docs/advanced/declarative-vs-imperative.md
|
||||
- docs/advanced/plugin-system.md
|
||||
- docs/advanced/saveable-content.md
|
||||
- docs/advanced/replay-consistency.md
|
||||
- docs/advanced/data-stores.md
|
||||
- docs/advanced/hooks-in-depth.md
|
||||
autonomous: false
|
||||
requirements: []
|
||||
user_setup: []
|
||||
estimate:
|
||||
tokens: 28000
|
||||
raw_tokens: 18000
|
||||
tasks: 5
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "用户主导的修订循环被真正执行:用户以本领域视角逐条提出问题 → 派子代理修改 → 回报 → 用户继续提,直到用户明确确认满意"
|
||||
- "`10-20-USER-REVIEW.md` 记录每一轮用户意见、落点、处置与构建结论;用户最终确认(通过 / 满意)被明确记录"
|
||||
- "收尾由用户确认触发,不由 AI 自行收尾;用户未确认前不得写 SUMMARY 或宣称完成"
|
||||
- "最终七篇全部 kebab-case、CRLF、单 H1;prettier 检查通过;构建 green、`build complete`、dead link = 0"
|
||||
- "不改 `config.ts` 与生产代码;越界问题只记录、不实施(D-14)"
|
||||
- "修订中不删除 D-53..D-57 要求覆盖的内容,也不把高级 II / III 的占位改成死链"
|
||||
artifacts:
|
||||
- ".planning/phases/10-docs-restructure/10-20-USER-REVIEW.md"
|
||||
- ".planning/phases/10-docs-restructure/10-20-SUMMARY.md"
|
||||
key_links:
|
||||
- "执行 D-46 第 2 阶段 / D-57 的用户主导修订循环;这是高级 I 收尾的唯一合法出口"
|
||||
- "10-19 已消除独立读者人格的阻断问题;本计划处理用户以领域视角提出的问题"
|
||||
- "最终构建产物与审阅记录共同构成 D-09 的人工验收依据"
|
||||
---
|
||||
|
||||
<objective>
|
||||
执行高级 I 的用户主导修订循环并收尾:把构建好的站点交给用户,由用户以本领域视角逐条提出问题;AI 派子代理修改、回报;用户继续提,直到用户明确确认满意。只有用户确认后才做最终格式与构建收口并写 SUMMARY。
|
||||
|
||||
Purpose: D-46 / D-57 明确要求:子代理审阅之后,必须再经用户主导的修订循环,且必须获用户确认才可收尾,不得由 AI 自行收尾。本计划就是这条出口。
|
||||
Output: `10-20-USER-REVIEW.md`(用户意见与处置)、最终文档修订、`10-20-SUMMARY.md`。
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||
@C:/Users/13194/.config/opencode/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/phases/10-docs-restructure/10-CONTEXT.md
|
||||
@.planning/phases/10-docs-restructure/10-19-READER-REVIEW.md
|
||||
@.planning/phases/10-docs-restructure/10-11-READER-REVIEW.md
|
||||
@docs/advanced/index.md
|
||||
@docs/advanced/declarative-vs-imperative.md
|
||||
@docs/advanced/plugin-system.md
|
||||
@docs/advanced/saveable-content.md
|
||||
@docs/advanced/replay-consistency.md
|
||||
@docs/advanced/data-stores.md
|
||||
@docs/advanced/hooks-in-depth.md
|
||||
|
||||
**流程约定(D-46 第 2 阶段 / D-57):**
|
||||
- 用户主导:用户提问题,AI 不替用户判断「这不算问题」;用户说改就改,用户说不用改就不改。
|
||||
- 每轮:记录用户意见 → 派子代理逐条修改 → 回报 → 等用户继续提或确认。
|
||||
- **必须获用户明确确认(「通过」或「满意」)后才可收尾**;AI 不得自行收尾。
|
||||
- 用户指出的越界内容(超出高级 I 七篇范围)只记录、不实施(D-14)。
|
||||
- 修订不得删除 D-53..D-57 要求覆盖的内容;高级 II / III 仍只用代码 span 或 HTML 注释。
|
||||
- 命令一律经 `cmd /c "..."` 执行;只改 `docs/advanced/*.md` 与审阅记录,不改 `config.ts` 与生产代码。
|
||||
|
||||
**交付方式:**
|
||||
- 本地预览:`cmd /c "pnpm docs:preview"`(预览 `public/_docs`);或直接浏览构建产物。
|
||||
- 审阅对象:七篇高级 I;重点看内容是否正确、是否可照做、系统边界是否讲清、是否有死链。
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking-human">
|
||||
<name>Task 0: 向用户汇报用户修订循环计划并等待「可以执行」</name>
|
||||
<action>向用户汇报:**要做什么**(执行高级 I 的用户主导修订循环:用户逐条提问题 → AI 派子代理修改 → 回报 → 用户继续提,直到用户确认满意;确认后做最终格式与构建收口)、**为什么**(D-46 第 2 阶段 / D-57 要求必须经用户循环且获用户确认才可收尾,不得由 AI 自行收尾)、**大致怎么做**(Task 1 交付构建产物并收集用户意见;Task 2 逐条修改并记录;Task 3 回报并等待用户继续提或确认;用户确认后 Task 4 做最终 prettier / 构建 / SUMMARY)、**影响范围**(可能修改 `docs/advanced/*.md` + 新增 `.planning` 下的用户审阅记录与 SUMMARY;不改 `config.ts` 与生产代码)。停下等待用户明确回复「可以执行」四个字;未收到前不得交付审阅、不得派发修改子代理、不得写任何文件。</action>
|
||||
<what-built>本计划内容:高级 I 用户主导修订循环 + 用户确认后的收尾。</what-built>
|
||||
<how-to-verify>阅读上面的四段说明,确认「用户确认后才收尾」的流程;如对交付方式(预览命令 / 审阅范围)有要求请一并说明。</how-to-verify>
|
||||
<resume-signal>回复「可以执行」以继续;其它回复视为未批准,本计划停在此处。</resume-signal>
|
||||
<verify>
|
||||
<human-check>用户已回复「可以执行」。</human-check>
|
||||
</verify>
|
||||
<done>用户回复「可以执行」。</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking-human">
|
||||
<name>Task 1: 交付高级 I 构建产物,收集用户意见</name>
|
||||
<action>先跑 `cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"` 确认构建 green、`build complete`、dead link = 0,然后向用户交付:七篇的路径清单、`index.md` 的阅读顺序、本地预览方式 `cmd /c "pnpm docs:preview"`,以及 10-19 的读者审阅结论摘要(阻断问题已清零、非阻断残项列表)。请用户以本领域视角逐条提出:内容是否正确、是否有讲错或讲漏、照着做会不会卡住、系统边界(数据端 only、插件优先、存档/录像/钩子关系)是否讲清、有没有死链或排版问题。**不要替用户判断某条算不算问题**;用户提出的每一条都记录到 `.planning/phases/10-docs-restructure/10-20-USER-REVIEW.md`(意见、定位、状态=待处理)。若用户直接回复「通过」或「满意」,记录该结论并跳到 Task 3 的确认分支;否则本任务结束时带着用户意见清单进入 Task 2。Commit:`docs(10-20): 记录用户修订意见(第1轮)`。</action>
|
||||
<what-built>构建后的高级 I 七篇与预览方式;10-19 的读者审阅结论摘要。</what-built>
|
||||
<how-to-verify>本地 `cmd /c "pnpm docs:preview"` 预览,或直接阅读 `docs/advanced/*.md`;按「内容正确 / 可照做 / 边界清楚 / 链接与排版」四类逐条提问。</how-to-verify>
|
||||
<resume-signal>请逐条列出问题(可带文件名与章节);若已满意,请直接回复「通过」或「满意」。</resume-signal>
|
||||
<verify>
|
||||
<human-check>用户已给出至少一条具体意见,或明确回复「通过」/「满意」。</human-check>
|
||||
</verify>
|
||||
<done>用户意见已记录,或用户已明确确认通过。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: 按用户意见逐条派子代理修改并回报</name>
|
||||
<files>docs/advanced/*.md(按用户意见定位), .planning/phases/10-docs-restructure/10-20-USER-REVIEW.md</files>
|
||||
<action>读取 `10-20-USER-REVIEW.md` 中状态为「待处理」的用户意见,逐条派子代理修改对应 `docs/advanced/*.md`:每条意见都要落到具体文件与段落;用户说改哪里就改哪里,不擅自扩大范围;用户指出但属于高级 II/III 或渲染端的内容只记录、不实施(D-14)。每轮修改后:(a) 跑构建门禁 `cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"`,须 green、`build complete`、dead link = 0;(b) 在 `10-20-USER-REVIEW.md` 把该条状态改为「已处理」并写明处置与改动文件;(c) 向用户回报本轮改了哪些、怎么改的、构建结论。若用户又提出新意见,追加到记录并继续处理——这个循环由 Task 2 / Task 3 反复执行,直到用户确认。守住 D-53..D-57 与 D-48..D-52;不删除要求覆盖的内容;不把高级 II/III 占位改成死链;不改 `config.ts` 与生产代码。每轮 Commit:`docs(10-20): 按用户意见修订高级 I(第N轮)`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Stop'; $f='.planning/phases/10-docs-restructure/10-20-USER-REVIEW.md'; if(-not(Test-Path -LiteralPath $f)){ Write-Error "MISSING $f" }; $raw=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $f)); if($raw -notmatch '已处理|通过|满意'){ Write-Error "NO-DISPOSITION" }; if($raw -notmatch 'docs/advanced/'){ Write-Error "NO-TARGET" }; $names=@('index','declarative-vs-imperative','plugin-system','saveable-content','replay-consistency','data-stores','hooks-in-depth'); foreach($n in $names){ $p="docs/advanced/$n.md"; if(-not(Test-Path -LiteralPath $p)){ Write-Error "MISSING $p" }; $body=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $p)); if($body -match "(?<!`r)`n"){ Write-Error "LF-ONLY $p" }; if(([regex]::Matches($body,'(?m)^# ')).Count -ne 1){ Write-Error "H1-COUNT $p" } }; $ErrorActionPreference='Continue'; $out = cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build" 2>&1; $ec=$LASTEXITCODE; $ErrorActionPreference='Stop'; if($ec -ne 0){ $out | Write-Output; Write-Error "BUILD-FAIL" }; $txt=($out | ForEach-Object { $_.ToString() }) -join "`n"; if($txt -notmatch 'build complete'){ Write-Error "NO-BUILD-COMPLETE" }; if($txt -match 'dead link'){ Write-Error "DEAD-LINK" }; Write-Output 'FIX-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "MISSING", "NO-DISPOSITION", "NO-TARGET", "LF-ONLY", "H1-COUNT", "BUILD-FAIL", "NO-BUILD-COMPLETE", or "DEAD-LINK"; or output lacks "FIX-OK"</fails_when>
|
||||
</verify>
|
||||
<done>本轮用户意见逐条落到具体段落;记录中状态与改动文件清楚;构建 green、`build complete`、dead link = 0。</done>
|
||||
</task>
|
||||
|
||||
<task type="checkpoint:human-verify" gate="blocking-human">
|
||||
<name>Task 3: 回报修改结果,等待用户继续提或确认满意</name>
|
||||
<action>向用户回报本轮处置:逐条列出用户意见 → 改了哪个文件/段落 → 怎么改的 → 构建结论;附上更新后的预览方式。然后请用户继续审阅:如果还有问题,请继续逐条提出(执行者回到 Task 2 继续改,循环不限轮次,直到用户满意);如果已经满意,请明确回复「通过」或「满意」。**只有用户明确回复「通过」或「满意」才可进入 Task 4 收尾**;用户沉默、只回复「可以」、或回复其它内容都不算确认,不得自行收尾。把用户的确认原文记录到 `10-20-USER-REVIEW.md`。Commit:`docs(10-20): 记录用户确认结论`(仅在用户确认后提交)。</action>
|
||||
<what-built>按用户意见修订后的高级 I 七篇与逐条处置说明。</what-built>
|
||||
<how-to-verify>按上一轮的预览方式重新查看改动处;确认每条意见都已处理,且没有引入新的错误或死链。</how-to-verify>
|
||||
<resume-signal>若仍有问题,请逐条列出;若已满意,请回复「通过」或「满意」。</resume-signal>
|
||||
<verify>
|
||||
<human-check>用户已明确回复「通过」或「满意」,或提出了新的具体意见。</human-check>
|
||||
</verify>
|
||||
<done>用户确认满意(记录原文),或用户提出了新的具体意见(回到 Task 2)。</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 4: 用户确认后的最终格式、构建与 SUMMARY</name>
|
||||
<files>.planning/phases/10-docs-restructure/10-20-USER-REVIEW.md, .planning/phases/10-docs-restructure/10-20-SUMMARY.md, docs/advanced/*.md</files>
|
||||
<action>**前置:用户已在 Task 3 明确回复「通过」或「满意」;未确认则不得执行本任务。** (1) 最终格式:`node node_modules/prettier/bin/prettier.cjs --write "docs/advanced/*.md"`,再 `--check` 确认通过。(2) 最终构建:`cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"`,确认 green、`build complete`、dead link = 0。(3) 最终结构:七篇 kebab-case、CRLF、单 H1;`config.ts` 的 `/advanced/` 侧边栏仍为 7 个目标;无渲染端内容;高级 II/III 仍无真实链接。(4) 在 `10-20-USER-REVIEW.md` 记录最终结论:用户确认原文、最终构建结论、非阻断残项(只记录)。(5) 写 `10-20-SUMMARY.md`,含:七篇清单、用户循环轮次与处置摘要、10-19 读者审阅结论、最终构建 / prettier / 链接结论、未处理项(仅越界或非阻断)、以及「用户已确认收尾」的明确记录。Commit:`docs(10-20): 高级 I 用户修订循环收尾`。</action>
|
||||
<verify>
|
||||
<automated>$ErrorActionPreference='Stop'; $rev='.planning/phases/10-docs-restructure/10-20-USER-REVIEW.md'; $sum='.planning/phases/10-docs-restructure/10-20-SUMMARY.md'; if(-not(Test-Path -LiteralPath $rev)){ Write-Error "MISSING $rev" }; if(-not(Test-Path -LiteralPath $sum)){ Write-Error "MISSING $sum" }; $r=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $rev)); if($r -notmatch '通过|满意'){ Write-Error "NO-USER-CONFIRMATION" }; if($r -match '待处理'){ Write-Error "PENDING-USER-ITEMS" }; $s=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $sum)); foreach($needle in @('用户','确认','构建','七篇')){ if($s -notmatch [regex]::Escape($needle)){ Write-Error "SUMMARY-MISSING $needle" } }; $names=@('index','declarative-vs-imperative','plugin-system','saveable-content','replay-consistency','data-stores','hooks-in-depth'); foreach($n in $names){ $p="docs/advanced/$n.md"; if(-not(Test-Path -LiteralPath $p)){ Write-Error "MISSING $p" }; $body=[IO.File]::ReadAllText((Resolve-Path -LiteralPath $p)); if($body -match "(?<!`r)`n"){ Write-Error "LF-ONLY $p" }; if(([regex]::Matches($body,'(?m)^# ')).Count -ne 1){ Write-Error "H1-COUNT $p" }; if($body -match 'src/client'){ Write-Error "RENDERER-CONTENT $p" } }; $cfg=[IO.File]::ReadAllText((Resolve-Path 'docs/.vitepress/config.ts')); $targets=[regex]::Matches($cfg,"link:\s*'/advanced/([^']*)'") | ForEach-Object { $_.Groups[1].Value } | Sort-Object -Unique; $want=@('','data-stores','declarative-vs-imperative','hooks-in-depth','plugin-system','replay-consistency','saveable-content') | Sort-Object -Unique; if(($targets -join ',') -ne ($want -join ',')){ Write-Error ("ADVANCED-LINKS " + ($targets -join ',')) }; $ErrorActionPreference='Continue'; $pf = cmd /c "node node_modules/prettier/bin/prettier.cjs --check docs/advanced/index.md docs/advanced/declarative-vs-imperative.md docs/advanced/plugin-system.md docs/advanced/saveable-content.md docs/advanced/replay-consistency.md docs/advanced/data-stores.md docs/advanced/hooks-in-depth.md" 2>&1; $pfe=$LASTEXITCODE; $out = cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build" 2>&1; $ec=$LASTEXITCODE; $ErrorActionPreference='Stop'; if($pfe -ne 0){ $pf | Write-Output; Write-Error "PRETTIER-FAIL" }; if($ec -ne 0){ $out | Write-Output; Write-Error "BUILD-FAIL" }; $txt=($out | ForEach-Object { $_.ToString() }) -join "`n"; if($txt -notmatch 'build complete'){ Write-Error "NO-BUILD-COMPLETE" }; if($txt -match 'dead link'){ Write-Error "DEAD-LINK" }; Write-Output 'CLOSEOUT-OK'</automated>
|
||||
<fails_when>non-zero exit, or output contains "MISSING", "NO-USER-CONFIRMATION", "PENDING-USER-ITEMS", "SUMMARY-MISSING", "LF-ONLY", "H1-COUNT", "RENDERER-CONTENT", "ADVANCED-LINKS", "PRETTIER-FAIL", "BUILD-FAIL", "NO-BUILD-COMPLETE", or "DEAD-LINK"; or output lacks "CLOSEOUT-OK"</fails_when>
|
||||
</verify>
|
||||
<done>用户确认已记录且无待处理项;七篇结构正确、无渲染端内容;prettier 检查通过;构建 green、`build complete`、dead link = 0;SUMMARY 写明用户确认收尾。</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| 用户 → 执行者 | 用户意见是修订输入;执行者不得替用户判断某条不算问题 |
|
||||
| 执行者 → 子代理 | 修改子代理按用户意见改文档,不扩大范围 |
|
||||
| 构建期 markdown → 静态站点 | 正文由构建期静态渲染,无运行时输入 |
|
||||
| 站点 → 浏览器读者 | 站内链接是唯一可点击指向面 |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-10-58 | Tampering / XSS | 用户修订后的高级 I 正文 | low | mitigate | 只写文本、代码块、表格;不引入原始 `<script>` 或内联 HTML |
|
||||
| T-10-59 | Spoofing | 用户确认记录 | low | mitigate | 只把用户明确回复的「通过」/「满意」记为确认;沉默或其它回复不算 |
|
||||
| T-10-60 | Information Disclosure | 审阅记录 / 正文 | low | mitigate | 只引用仓库已公开路径与接口;不写密钥、账号 |
|
||||
| T-10-SC | Tampering | npm/pip/cargo installs | low | accept | 本阶段不安装任何包,无供应链安装面 |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- 机制门禁:`10-20-USER-REVIEW.md` 含每轮用户意见、定位、处置、构建结论与最终确认原文。
|
||||
- 确认门禁:用户明确回复「通过」或「满意」;记录中无「待处理」项。
|
||||
- 构建门禁:每轮及收尾均 `cmd /c "pnpm exec tsx docs/.vitepress/init.ts && pnpm docs:build"` 通过(green / `build complete` / dead link = 0)。
|
||||
- 格式门禁:prettier `--check` 对七篇通过。
|
||||
- 结构门禁:七篇 kebab-case、CRLF、单 H1;侧边栏 7 个目标正确。
|
||||
- 越界门禁:不改 `config.ts` 与生产代码;越界意见只记录、不实施。
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 用户主导修订循环真实执行,用户明确确认满意;AI 未自行收尾。
|
||||
- 最终七篇可构建、可预览、无死链、无渲染端内容。
|
||||
- 审阅记录与 SUMMARY 完整,能支撑 D-09 的人工验收。
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/10-docs-restructure/10-20-SUMMARY.md` when done
|
||||
</output>
|
||||
Loading…
Reference in New Issue
Block a user