# dsh-study-buddy 重构计划(文档式笔记重构) > 版本基线:v1.0.0 | 状态:**执行中 · 待开工** | 读者:插件维护者 > 配套:[`需求分析.md`](需求分析.md)(要什么)、[`架构选型.md`](架构选型.md)(怎么做)、[`设计文档.md`](../设计文档.md)(现行 v0.9.1 设计)、 > [`技术文档.md`](../技术文档.md)(现行实现)、[`README.md`](../README.md)(文档地图与维护约定)、[`经验文档.md`](../经验文档.md)(方法论与踩坑) > > 本文件回答"**按什么顺序做、每批做完算完成的标准是什么、怎么不把库写坏**"。 > 需求层以 [`需求分析.md`](需求分析.md) 为准,接口契约以 [`架构选型.md`](架构选型.md) 为准;本文件只记执行顺序、批次门禁与回归策略。 --- ## 一、执行原则 | # | 原则 | 落点 | | :-- | :-- | :-- | | P1 | **主干先通,再加旁支** | 先打通"期望 → 规划 → 写入 → 微目录 → 总览"的最小闭环(阶段 1~4),再补 link/rename/lint 等旁支能力(阶段 5~6) | | P2 | **删旧与建新同批** | 删 `template.ts` 的那一批必须同时落地"无模板写入"能力,避免中间态既不能建卡也不能建块 | | P3 | **每批 `pnpm run check` 全绿才进下一批** | typecheck + vitest + esbuild 三道;批次内允许红,批次间不允许 | | P4 | **回归用例先红后绿** | 涉及数据破坏面(存档顺序、门禁零改动、路径越界、改名半写盘)的修复,先写复现用例再改实现(沿用现行 D13 纪律) | | P5 | **不碰用户真实 vault** | 全部开发与验证在 `tests/` 建的临时 vault;真实 vault 只在阶段 7 由用户自己跑 | | P6 | **文档与代码同批** | 每批结束时同步该类改动对应的文档与技能(沿用 [`docs/README.md`](../README.md) §二的"改代码要同步哪些文档"对照表) | | P7 | **风格保持** | 中文注释写"为什么"(含坑编号)、纯函数优先、白名单反例优先、断言用带引号的精确子串(需求 N5) | | P8 | **版本号是最后一刀** | 全部阶段完成后再改 `package.json` / `dsh.plugin.json` / 文档头部基线 | --- ## 二、四个待确认项(开工前定稿) | # | 问题 | 建议 | 影响范围 | | :-- | :-- | :-- | :-- | | C1 | frontmatter 是否保留"一句话定位"键(`简介`) | **保留**:检索权重里"简介 > 正文"这一档是召回质量的主要来源;无长度硬限,不再是约束。彻底删除会让召回塌一档,且 `note_search` 的命中行少一行可用信息 | `frontmatter.ts` / `search.ts` / `note.ts` / 全部写入路径 | | C2 | `legacy` 卡(有 ID、无 `来源章节`)是否允许在补齐 `来源章节` 后自动转为 `block` | **自动转**:`note_update(action=append/replace)` 写入 `来源章节` 即完成升级,不新增升级工具;未补齐的仍是存量 | `note.ts` / `note_update` / `overview` | | C3 | 笔记期望文件缺失时是否允许"一次临时回退到内建写法" | **不允许**:fail-loud 并给创建指引(需求 R28 方向)。允许回退会让"约束从期望文件来"这条重新变成"代码里也有默认模板" | `gate.ts` / `note_write` | | C4 | 索引读前 4KB 头失败时(frontmatter 超长/无换行巨行) | **回退读全文一次并标记该块**:宁可慢一次,不可索引缺字段 | `search.ts` / `vault.ts` | > 需求文档 [附录 C](需求分析.md) 的 R26~R30 已在 [`架构选型.md`](架构选型.md) 定稿(工具命名、存档目录、门禁行为、`来源章节` 语法、`domainFolders` 降级),本表是架构阶段新发现的 4 项。 --- ## 三、批次总览 ``` 阶段 0 前置:基线快照 + 真实 vault 备份 (0.5 天) 阶段 1 地基:契约层(frontmatter / notemodel / dirs / 门禁状态) (1.5 天) 阶段 2 门禁:planstore / gate / store / archive (1.5 天) 阶段 3 写入:note.ts / vault 扩展 / lint 瘦身 (1.5 天) 阶段 4 工具面:tools.ts + VaultStore 重写(18 个工具) (2 天) 阶段 5 旁支能力:links 改造 / overview / 存量升级路径 (1.5 天) 阶段 6 清理与收口:删旧模块 / 测试重整 / 技能与 persona 改写 (2 天) 阶段 7 文档与验收:全量验证 / 文档同步 / 版本号 (1.5 天) ``` 每批的"完成"= 该批验收门全过 + `pnpm run check` 全绿 + 对应文档/技能已同步。 --- ## 四、批次明细 ### 阶段 0 · 前置(不写业务代码) | 动作 | 交付 | 完成标准 | | :-- | :-- | :-- | | 记录基线 | `pnpm run check` 输出、测试项数(现行 265)、每请求固定开销实测字节数 | 三个数字落进本文件 §七 的"实测记录"表 | | 备份真实 vault | 用户侧 git 提交或整目录复制 | 用户确认可回滚 | | 冻结接口 | C1~C4 定稿写回 [`架构选型.md`](架构选型.md) | 四项状态从"待确认"改为"已确认" | **风险控制**:这一阶段不动 `src/`,任何失败都不影响现有功能。 --- ### 阶段 1 · 地基:契约层 | 模块 | 动作 | 要点 | | :-- | :-- | :-- | | `frontmatter.ts` | 扩展 | `CardMeta` 加 `sourceSection` / `order` / `summary`;`简介` 与 `定义` 双读;`模板` 键读时丢弃不报错;渲染顺序固定 | | `notemodel.ts` | 改名 | 由 `cardmodel.ts` 复制改名,能力不变(小节切分/块插入/块抹除/围栏语言/inlineText) | | `dirs.ts` | 新建 | 路径规范化、DirIndex 数据结构与增量维护、层级与目录名校验、嵌套深度提示、`笔记期望.md` 路径解析 | | `store.ts` | 新建 | `.study/session.json` 读写:期望签名、`activePlanId`;损坏可自愈(删文件即回空态) | **验收门** - `tests/frontmatter.spec.ts` 扩展:六键 + 三文档键往返、旧卡 `定义` → `简介` 双读、`模板` 键丢弃、含换行字段拒绝(保留现行 SEC-1 断言)。 - `tests/dirs.spec.ts` 新建:DirIndex 增删维护、路径越界拒绝、非法目录名清洗、深度提示、同名块消歧路径。 - `tests/store.spec.ts` 新建:`session.json` 往返、损坏报错或自愈(按设计)、并发写不半成品(原子写)。 - `pnpm run check` 全绿(此阶段旧代码仍在,测试不许红)。 --- ### 阶段 2 · 门禁:规划与存档 | 模块 | 动作 | 要点 | | :-- | :-- | :-- | | `planstore.ts` | 新建 | `plans/.json` 创建/读取/确认/消费/放弃/过期清理;`planId` 生成(沿用 `generateId` 格式) | | `gate.ts` | 新建 | 三连校验纯函数:期望签名一致 → 规划已确认未过期 → 路径在 `rootPath` 之下且 `title` 在 `items` 中未消费;返回结构化失败原因 + 修复文案 | | `archive.ts` | 新建 | 存档写入(`/<时间戳>.md`:存档自/原路径/存档时间/原因 + 原文)、列目录、读取、恢复 | **验收门** - `tests/planstore.spec.ts`:确认/未确认/过期/重复消费/放弃五种状态机;`planId` 不可预测且唯一。 - `tests/gate.spec.ts`(或并入 store):三连校验各自的成功与失败分支;失败文案包含修复步骤(断言精确子串)。 - `tests/archive.spec.ts`:**先红后绿**——先写"replace 中途失败不得留下已改正文"的复现用例,确认红,再实现;存档往返、多版本共存、恢复时当前正文转入存档。 - `pnpm run check` 全绿。 --- ### 阶段 3 · 写入:块渲染与落盘 | 模块 | 动作 | 要点 | | :-- | :-- | :-- | | `note.ts` | 由 `card.ts` 改造 | 保留 ID 生成、必填校验、渲染、`append`/`replace` 应用;**删除** `resolveTemplate` / 模板校验 / `DEFINITION_MAX` 硬拒绝;`备注` 校验改为"单行 + 非空";新增 `来源章节` 归一(§4.2 语法) | | `vault.ts` | 扩展 | 新增 `notePathFor(layout, dir, title)`;`uniqueCardPath` 改名 `uniqueNotePath`(保留三档后缀策略);`domainFolders` 降级为快捷方式(规划路径优先) | | `lint.ts` | 瘦身合并 | 删 `lintrules.ts` 的模板类规则,并入单文件;规则收敛为 `session-residue` / `code-language` / `external-resource` / `expect-rule`;**废弃 100 分制**,改 finding 清单 | | `template.ts` | **删除** | 连同 `TEMPLATE_TYPES` / 推断 / 校验;`templateHints` 配置键删除 | **验收门** - `tests/note.spec.ts`:渲染往返、ID 与文件名策略、`来源章节` 归一(含全角/半角、缺节号、无法解析)、`append`/`replace` 语义、含换行字段拒绝。 - `tests/vault.spec.ts` 扩展:`notePathFor` 路径越界、三档冲突后缀、`domainFolders` 快捷方式优先级低于规划路径。 - `tests/lint.spec.ts` 重写:4 条规则正例 + 白名单反例(沿用现行白名单:`L0/L1/L2`、`LOD`、`GAMES101 L15`、代码块与折叠块内);**断言不存在模板类规则 id**。 - **删除测试**:`tests/template.spec.ts` 整体删除(模板概念不存在)。 - `pnpm run check` 全绿。 --- ### 阶段 4 · 工具面:18 个工具的编排 | 动作 | 要点 | | :-- | :-- | | `search.ts` 改造 | `IndexedCard` 去 `body`、加 `sourceSection`/`order`/`summary`/`dirPath`;`indexNote` 只读头(≤4KB,失败回退全文,见 C4);`SearchHit` 加 `sourceSection`;`kind` 三态;snippet 现读 N 篇 | | `index.ts` 重写 | `VaultStore` 方法按工具一一对应;保留 `ensureIndex` / `resolveCard` / `assertWritable` / `noteSkips` / `titleHints` / `invalidate` 的既有纪律;新增 DirIndex 增量维护与门禁调用 | | `tools.ts` 重写 | 18 个工具定义;描述由注册表派生(规则清单、frontmatter 键表、状态枚举);`output.schema.type === 'string'` | **分批顺序(同一批内的实现顺序,每步可独立跑测试)** 1. `note_search` / `note_get` / `note_list`(只读三件套)—— 先让"看"通 2. `note_library` / `note_expect_get`(门禁开门) 3. `note_plan`(规划) 4. `note_write`(写入,接门禁) 5. `note_toc`(微目录) 6. `note_overview`(覆盖度) 7. `note_update`(更新 + 存档接线) 8. 其余(`note_link`/`unlink`/`rename`/`history`/`restore`/`note_lint`/`study_*` 接线与改写) **验收门** - `tests/tools.spec.ts` 重写:18 个名字集合、schema 契约、描述由注册表派生(断言含规则 id 与键名)、cwd 透传。 - `tests/apply.spec.ts` 更新:工具数 18、fail-loud、开场门禁接线、`rulesOff` 未知 id 校验(保留 N9)。 - `tests/search.spec.ts` 扩展:**读盘计数桩**——`note_search` 命中 N 篇只读 N 个文件(证明索引不常驻正文)、头读失败回退全文、snippet 只对 top-N。 - **端到端主路径**:`tests/store.spec.ts` 新增"空库 → 期望 → 规划 → 写入 → 微目录 → 总览"全链路用例;失败路径断言**文件不存在**(门禁零改动)。 - `pnpm run check` 全绿;冒烟 `buildToolDefs({}).length === 18`。 --- ### 阶段 5 · 旁支能力 | 模块 | 动作 | 要点 | | :-- | :-- | :-- | | `links.ts` | 由 `rename.ts` 改造 | 关联行归一/判重(沿用 D6 口径)、**wikilink 解析与改写**、断链检测、文件名计划;同名前缀消歧 | | `overview.ts` | 新建 | 按 `来源章节` 聚合:material → chapter → section;"未归类"清单;章节跳号提示;**不虚构章节** | | `history.ts` / `insight.ts` | **删除** | 折叠块模型与跨卡分析随模板/分值退场 | **验收门** - `tests/links.spec.ts`(由 `rename.spec.ts` 改造):wikilink 三种写法(`[[名]]` / `[[名|别名]]` / `[[名#锚点]]`)改写、相对路径消歧、断链检测排除"新标题包含旧标题"(保留 D9 反例)。 - `tests/overview.spec.ts` 新建:跨资料、同资料多章、章节号不规范(归一)、缺 `来源章节` 归入未归类、跳号提示。 - **存量升级路径用例**:`legacy` 卡补 `来源章节` → 变为 `block` 并被 `note_overview` 计入(C2);旧笔记只读、多根歧义口径不变。 - 删除 `tests/history.spec.ts` / `tests/insight.spec.ts`。 - `pnpm run check` 全绿。 --- ### 阶段 6 · 清理与收口 | 动作 | 要点 | | :-- | :-- | | 删净旧模块 | `template.ts` / `history.ts` / `insight.ts` / `card.ts` / `cardmodel.ts` / `rename.ts` / `state.ts` / `memory.ts` 的残留引用(`grep` 断言零命中) | | 配置收敛 | `presets/study/agent.cordis.yml`:删 `templateHints`/`mocDir`,加 `expectFile`/`planTtlHours`/`maxIndexHeaderBytes`;`domainFolders` 注释改为"快捷方式" | | 技能改写 | `card-format` → `note-format`(文档式块规范 + 期望文件读取口径);`study-loop` 归档章改为十步流程;`domain-adaptation` 去掉侧重表改为"引导用户写进期望";`incremental-update` 改为 append/replace + 存档语义 | | persona 改写 | 删三型模板段落;加"归档前先读期望 + 先规划确认";工具清单换成 18 个 | | 默认期望模板 | 新建 `presets/study/assets/笔记期望.md`(从范本特征提炼:受众/文风/详略/结构/公式与代码/图表/互引/命名/领域侧重/自检清单) | | 测试重整 | 用例总数、文件名、断言与新口径一致;`tests/skills.spec.ts` 更新为"技能与配置键表一致 + 规则清单一致(N11)" | **验收门** - `grep` 断言:`templateHints` / `card_create` / `三型` / `DEFINITION_MAX` 在 `src/`、`presets/`、`tests/` 中零命中(文档中作为历史记录保留的部分除外)。 - `tests/skills.spec.ts`:6 个技能的关键词断言与新口径一致;领域键表与 `agent.cordis.yml` 一致。 - `pnpm run check` 全绿。 --- ### 阶段 7 · 文档与验收 | 动作 | 要点 | | :-- | :-- | | 现行文档重写 | [`设计文档.md`](../设计文档.md) → v1.0(新决策表 D1~Dn,含本轮新增:索引非常驻正文、硬门禁、先存档后写正文);[`技术文档.md`](../技术文档.md) → 新模块表 / 工具表 / 配置表 / 测试布局 | | 使用者文档 | [`用户使用指南.md`](../用户使用指南.md):删卡片格式章,加文档式笔记、笔记期望、文件夹规划、微目录、覆盖度、存档与恢复、存量卡升级六节;[`README.md`](../../README.md) 特性与配置表同步 | | 经验沉淀 | [`经验文档.md`](../经验文档.md) 加本轮新坑(建议:门禁状态与磁盘真相的边界、索引头截断的误伤、wikilink 改名半写盘) | | 验证脚本 | [`check/prompt-verify-all-features.md`](../check/prompt-verify-all-features.md) 重写为 6 条主路径(期望读取/规划确认/块落盘/微目录/总览/历史存档) | | 文档地图 | [`README.md`](../README.md) 登记 `需求分析.md` / `架构选型.md` / `重构计划.md` 三份新文档与"改代码要同步哪些文档"对照表 | | 版本号 | `package.json` + `dsh.plugin.json` → 1.0.0(一致);各现行文档头部基线同步;`README.md` 更新记录加一条 | | 全量验收 | 见 §五 | --- ## 五、验收总纲 | 门 | 标准 | 出处 | | :-- | :-- | :-- | | 功能闭环 | 空库到可用:`note_library` → `note_expect_get` → `note_plan` → `note_write` → `note_toc` → `note_overview`,一次跑通 | 需求 F1~F5 | | 约束解除 | `src/` 中不存在三型模板、必填小节检查、字数硬拒绝、分型推断;改期望文件即改行为 | 需求 F4 | | 硬门禁 | 未读期望 / 未确认规划 / 越界路径 → 报错且磁盘零改动 | 需求 F2 | | 数据安全 | `replace` 先存档后写正文;任一步失败无半成品;改名失败可回滚 | 需求 N1 | | 导航正确 | 每主题目录有微目录;增删改名重跑即一致;手写段保留 | 需求 F3/F7 | | 覆盖度 | 按 `来源章节` 聚合,缺口显式,不虚构 | 需求 R15 | | 存量兼容 | `legacy` 卡可检索/读取/显式升级;旧笔记只读;多根与歧义口径不变 | 需求 F8 | | 性能 | 索引常驻不含正文(读盘计数桩断言);每请求固定开销 ≤ 现行基线 +10% | 需求 N2 | | 不退化 | 原子写、路径越界、跳过项分组回显、TTL 与强制重扫、`_autoPrefs` 语义(现存用例迁移后全绿) | 需求 N3 | | 工程质量 | `pnpm run check` 全绿;两 manifest 同版本;文档链接与版本基线守卫通过 | 需求 N5/N6 | | 真实库验证 | 由用户在真实 vault 上跑 6 条主路径 + 一轮真实归档 | 需求 §八 | --- ## 六、回归策略 ### 6.1 测试资产处置 | 文件 | 处置 | 说明 | | :-- | :-- | :-- | | `tests/card.spec.ts` | → `tests/note.spec.ts` | 去掉模板/定义硬限用例,加 `来源章节` 归一与 append/replace | | `tests/cardmodel.spec.ts` | → `tests/notemodel.spec.ts` | 仅改名 | | `tests/template.spec.ts` | **删除** | 模板概念退场 | | `tests/history.spec.ts` / `tests/insight.spec.ts` | **删除** | 折叠块与跨卡分析退场;核心不变量(先存档后写正文)迁到 `archive.spec.ts` | | `tests/rename.spec.ts` | → `tests/links.spec.ts` | 加 wikilink 改写与消歧 | | `tests/lint.spec.ts` | 重写 | 4 条规则 + 白名单反例 | | `tests/store.spec.ts` | 改造 + 扩充 | 保留 49 项里与安全/原子性相关的全部;新增闭环与门禁失败路径 | | `tests/skills.spec.ts` / `docs.spec.ts` / `preset.spec.ts` | 更新 | 关键词、键表、链接与版本基线守卫保留 | **总原则**:**安全类与原子性类用例一条不删**(原子写、路径越界、只读根、跳过项回显、回滚),只删"被删除功能的功能性用例"。 ### 6.2 每批的回归命令 ```bash pnpm run typecheck # 单批快速反馈 pnpm exec vitest run tests/<本批相关>.spec.ts pnpm run check # 批次收口(typecheck + vitest + esbuild) ``` ### 6.3 真实库验证(阶段 7,由用户执行) 1. 先 `git status` / 备份确认可回滚。 2. 在一个**新建的空 vault** 上跑 6 条主路径(避免污染真实库)。 3. 在真实 vault 的副本上跑:`note_library(check)` → `note_list` 顶层 → `note_search` 抽查 3 个旧概念 → `note_overview` 看存量卡是否被正确标为未归类。 4. 最后做一轮真实归档(读资料 → 规划确认 → 落块 → 微目录 → 总览)。 --- ## 七、实测记录(执行时回填) | 项 | 基线(v0.9.1) | 完成后(v1.0.0) | 门限 | | :-- | --: | --: | :-- | | 测试项数 | 265(实际仓库 268) | 252(删掉的是被删功能的用例,安全类一条未删) | — | | 测试文件数 | 18(实际仓库 19) | 20 | — | | 源码模块数 | 16 | 20 | — | | 工具数 | 12(预设实际可见 21) | **18** | — | | 每请求固定开销(工具 schema) | ~15.0 KB / 21 工具 | **14.4 KB / 18 工具**(实测 14704 字节) | ≤ +10% ✅ | | 构建产物 | 157.0 KB | 180.2 KB | 无硬门限 | | `pnpm run check` 测试段 | 1.6s | 1.4s | 无 | > 测量方法:`node -e "import('./lib/index.js').then(m => console.log(Buffer.byteLength(JSON.stringify(m.buildToolDefs({}).map(d => ({name:d.name,description:d.description,parameters:d.parameters}))))))"` ### 阶段 7 交付明细(文档与验收) | 动作 | 落点 | | :-- | :-- | | 用户指南 | `docs/用户使用指南.md` 全文重写为 v1.0:18 工具逐条、三条硬门禁的拒绝文案表、笔记期望热配置、微目录、覆盖度、历史存档与回退、排障表 | | 设计文档 | `docs/设计文档.md` 重写:转向理由、目录模型、门禁机制表、20 模块边界、D1~D23 决策 | | 技术文档 | `docs/技术文档.md` 重写:20 模块 API、18 工具契约、lint 规则表、配置键、`.study` 文件格式、错误语义、性能与缓存、20 文件测试布局、已知实现边界 | | 根 README | 特性/更新记录(v1.0.0 条目)/性能表(实测 14704 字节)/原理/主动权/格式样例/快速开始(含"复制期望模板"必做步骤)/配置表/18 工具一览/隐私/开发/结构/路线图 | | 验证脚本 | `docs/check/prompt-verify-all-features.md` 重写:重点改为**验证三条硬门禁真的挡住写入**(含逐条"被拒 + 文件不存在"核对)+ 收尾清理指引 | | 文档地图 | `docs/README.md` 重写:登记需求/架构/计划三份新文档、更新"改代码要同步哪些文档"对照表、维护检查清单加"旧概念零残留"与"lib/types 已重建" | | 经验文档 | `docs/经验文档.md` 补第二轮:3 件做对的事 + 6 个坑 + 4 条技巧(含"删除类操作不用绝对行号""删缓存字段前先 grep 全部读者") | | 版本 | `package.json` + `dsh.plugin.json` → **1.0.0**(一致);各现行文档头部基线同步 | | 收口修复 | 清掉 `mocDir` 死配置与 `mocPathFor`、修正误导性报错文案(`card_update` → `note_update`);补 `expectFile` 贯通(此前"配了不生效")+ 越界防御 | ### 最终验收证据(v1.0.0) | 硬线 | 证据 | | :-- | :-- | | ① 约束解除 | `src/` 与 `presets/` 中 `card_*` / `templateHints` / `mocDir` / 三型模板**零命中**(仅剩两处说明性注释);lint 规则恰为 4 条;`简介` 无长度上限 | | ② 硬门禁零改动 | `tests/noteflow.spec.ts` 三条门禁各自断言"报错 + 目标文件不存在";`tests/gate.spec.ts` 断言"存档失败时正文零改动" | | ③ 不退化 | 252 项全绿;原子写/路径越界/只读根/跳过项分组回显/TTL 强制重扫/`_autoPrefs` 均有用例 | | 性能 | 工具 schema **14704 字节 / 18 工具**(旧基线 ~15.0 KB / 21 工具);索引不含 `body` 有守门用例 | --- ## 八、风险与回退 | # | 风险 | 触发信号 | 回退动作 | | :-- | :-- | :-- | :-- | | 1 | 阶段 4 工具面重写期间功能不可用(中途态) | 中途需要真实使用 | 在分支上开发,`main` 保持 v0.9.1 可用;功能验收在阶段 7 后才切换 | | 2 | 索引"头读 4KB"在后端误伤 | `note_search` 命中率下降 / 字段缺失 | C4 的回退读全文兜底 + `maxIndexHeaderBytes` 可调 | | 3 | 工具面 18 个超出固定开销预算 | §七 实测 > +10% | 按 [`架构选型.md`](架构选型.md) §十一 预案合并(`unlink`→`link(action)`、`restore`→`history(action)`) | | 4 | 门禁摩擦导致用户绕过 | 用户抱怨"每次都要规划" | 规划仅在结构变化时要求(同目录续写走 `note_update`);`note_library(check)` 一次性回显期望与规划状态 | | 5 | 存量卡在新库被噪声化 | `note_list` 出现大量 legacy | 默认不列 legacy,单列计数一行;`note_search` 仍可检索 | | 6 | 阶段 6 删模块遗漏引用 | typecheck 报错 | 靠 `grep` 断言兜底(阶段 6 验收门) | | 7 | 真实 vault 被写坏 | 用户报告文件异常 | P5:开发期不碰真实库;写入全部原子写 + 先校验后提交;存档保证内容可回滚 | --- ## 九、进度看板(执行时更新) ### 计划偏离记录(执行中确认的取舍) | # | 计划原文 | 实况 | 判定与理由 | | :-- | :-- | :-- | :-- | | 1 | 阶段 4「`search.ts` 只读 frontmatter 头(≤4KB),失败回退全文」 | 实际 `readNoteSource` **读全文**(256KB 上限);4KB 头读落在 `dirs.readNoteHeader`(供 `note_list`) | **有意偏离**:检索要对**正文**做 token 化,只读头会让"正文里的关键词"搜不到。内存目标改由"索引不常驻正文"(A4)达成,读盘量与旧实现持平 | | 2 | 阶段 6「`state` + `memory` 合并为 `study`」 | 未合并,两文件保留 | **不做**:职责清晰、用例齐备,合并只少一个文件却增加一次回归面 | | 3 | 阶段 6「`grep card_create` 零命中」 | `tests/tools.spec.ts` 仍含 `card_create` 等字符串 | **保留**:那是"必须不存在"的**守门断言**,不是遗留引用;`src/` 与 `presets/` 已零命中 | | 4 | 阶段 4 工具数 16~18 | 取 18(上限) | 按需求方定稿"宁可多不要挤";实测 schema 开销 14.7 KB,未超预算 | | 5 | 阶段 3 计划「改名能力阶段 5 并入 `links.ts`」 | 阶段 6 才并入 | 时间点后移,结论不变(模块边界按架构选型 §2.1 收口) | | 6 | 计划未含"排序口径"一项 | 交付后 CI 暴露:裸 `localeCompare` 让顺序随宿主 locale 变化(本机 zh-CN 绿、runner 的 en-US 红),新增 `src/order.ts` 与 D24 | **计划外新增**:由 CI 而非计划发现;口径收成一处 + 源码守卫,见 [经验文档](../经验文档.md) §六.7 | | 阶段 | 状态 | 完成日期 | 备注 | | :-- | :-- | :-- | :-- | | 0 前置 | ✅ | 2026-10-24 | 分支 `refactor/doc-notes`;基线 268 项/19 文件;C1~C4 定稿写回架构选型 §1.1(A14/A15 为对应实现选型) | | 1 地基 | ✅ | 2026-10-24 | `frontmatter` 三文档键 + `notemodel` 改名 + `dirs`/`store` 新建;289 项/20 文件全绿 | | 2 门禁 | ✅ | 2026-10-24 | `planstore` / `gate` / `archive` 新建;含"先存档后写正文"的不变量闸门;306 项/21 文件全绿 | | 3 写入 | ✅ | 2026-10-24 | `note`/`links`/`moc`/`sourceSection` 新建;`template`/`insight` 删除;lint 瘦身为 4 条规则且废弃分值;271 项/19 文件全绿,产物 157→129KB | | 4 工具面 | ✅ | 2026-10-24 | 4a 索引层(A4 不常驻正文、三态类型);4b 18 个 `note_*` 工具 + 门禁接线;282 项/20 文件全绿;schema 开销 14.7KB ≤ 预算 | | 5 旁支 | ✅ | 2026-10-24 | `overview.ts` 纯模块(覆盖度聚合,不虚构章节)+ `tests/overview.spec.ts`(9 项) | | 6 收口 | ✅ | 2026-10-24 | 删 `card`/`updatelegacy`/`moc`/`history`/`rename` 与其用例;preset persona 与 6 个技能改写为文档式笔记口径;新增 `assets/笔记期望.md` 默认模板;`src/` 20 模块、preset 内 `card_*` 引用清零;251 项全绿 | | 7 文档与验收 | ✅(真实库验证待用户执行) | 2026-10-24 | 用户指南 / 设计文档 / 技术文档 / 根 README 全部 v1.0 重写;验证脚本重写(重点验证门禁);文档地图登记三份新文档;经验文档补第二轮(3 做对 + 6 坑 + 4 技巧);版本 1.0.0(两 manifest 一致)。**唯一待办**:真实 vault 验证(由用户按 `check/prompt-verify-all-features.md` 执行) | ### 阶段 1 交付明细 | 动作 | 落点 | | :-- | :-- | | 契约扩展 | `src/frontmatter.ts`:`来源章节` / `顺序` / `简介` 三键;`定义` 作别名双读;`模板` 只读不渲染(迁移期兼容,附 `@deprecated` 标记);键序固定 | | 模块改名 | `cardmodel.ts` → `notemodel.ts`(接口 `DocSection`,保留 `CardSection` 别名至阶段 6);`tests/cardmodel.spec.ts` → `tests/notemodel.spec.ts` | | 目录模型 | `src/dirs.ts`:路径归一/层级校验/微目录约定/期望路径/`DirIndex`/`listDir`(只读 4KB 头,失败回退全文 = C4)/`resolveNoteDir` 三档优先级 | | 门禁状态 | `src/store.ts`:`.study/session.json` 读写(损坏回空态不抛错)、`signatureOf` 三要素签名、`markExpectRead` / `clearExpectMark` | | 迁移期兼容 | `src/card.ts` 的 `renderCard` 在迁移期按旧键序补回 `模板` 行,保证存量卡字节不变(阶段 3 随模板模块删除) | ### 阶段 2 交付明细 | 动作 | 落点 | | :-- | :-- | | 规划存储 | `src/planstore.ts`:`plans/.json` 状态机(建/确认/消费/放弃/过期)、`pathInPlan` 范围判定、消费按标题记账且**写盘失败即视为失败**(不留"可重复写"的口子) | | 硬门禁 | `src/gate.ts`:三连校验(期望已读 → 规划已确认未过期 → 路径在规划根内),按序短路、每条失败都带修复步骤;`formatPlanProposal` 输出对话内提案(不落盘) | | 历史存档 | `src/archive.ts`:`.study/archive//<时间戳>.md`(毫秒级时间戳防撞名)、`archiveThenWrite` 把"先存档后写正文"收成唯一入口 | | 不变量闸门 | `tests/gate.spec.ts`:存档目录被占位文件挡住时,`archiveThenWrite` 抛错且**目标正文一个字未动** | ### 阶段 3 交付明细 | 动作 | 落点 | | :-- | :-- | | 删除模板 | `src/template.ts`、`tests/template.spec.ts`:三型模板/必填小节/分型推断整体退场(需求 R6);`config.templateHints` 键删除 | | 删除洞察 | `src/insight.ts`、`tests/insight.spec.ts`:跨卡一致性/质量趋势/可执行性评级随 100 分制一起退场(架构选型 A9) | | lint 重写 | `src/lint.ts`:4 条规则(`session-residue` / `code-language` / `external-resource` / `expect-rule`),报告改问题清单 + 严重级,**无总分**;`parseExpectRules` 让"检查什么"也走《笔记期望.md》热配置 | | 规则瘦身 | `src/lintrules.ts`:只留数据卫生判定;白名单反例(`L0/L1/L2`、`LOD`、讲义引用、代码块与折叠块内)一条不删 | | 笔记契约 | `src/note.ts`:校验(无字数上限、模板入参无效化)/渲染/`append`·`replace`·`definition` 三动作/wikilink 关联/`inverseKind` | | 来源章节 | `src/sourceSection.ts`:`《资料》第N章 标题 / N.N节` 的无损归一(全角数字、`第 2 章`、缺节号、无法识别时保留原样) | | 模块抽出 | `src/links.ts`(wikilink 关联增删/判重/列出)、`src/moc.ts`(MOC 组装,阶段 4 删除) | | 兼容层 | `src/card.ts` / `src/updatelegacy.ts`:受控过渡——`template` 入参被忽略、`定义` 映射为 `简介`,但**必填与换行校验仍 fail-loud**(约束可解除,写入正确性不能松) | | 产物 | `lib/index.js` 157.0 KB → 129.2 KB | ### 阶段 4a 交付明细(索引层) | 动作 | 落点 | | :-- | :-- | | 内存模型 | `search.ts`:`IndexedCard` **去掉 `body`**,只留元数据 + 四路倒排 token;`SearchHit.snippet` 改为可空,由调用方按需读盘填入 | | 读盘量 | `vault.ts` 新增 `readNoteSource`(读全文但设 256KB 上限并回传 `truncated`),既保住"正文关键词可搜",又不让巨型文件进内存 | | 文档类型 | `kind` 由 `card`/`note` 二元改为 `block`/`legacy`/`note` 三态(`kindOfNote`:有 ID 且有来源章节 = 块) | | 目录检索 | `SearchIndex.underDir` + `SearchOptions.dirPath`:为阶段 4b 的"按主题目录列块/检索"预置 | | 连带修正 | 批量体检改为逐篇按需读盘(低频重操作,可接受);改名预筛因索引无正文而收紧为"不预筛 + 只有真改动才写"——**正确性优先于省几次读盘** | | 守门用例 | `tests/search.spec.ts` 新增 A4 用例:`IndexedCard` 与命中结果都**不含 `body` 字段**,正文仍可被搜到 | ### 阶段 4b 交付明细(工具面与门禁接线) | 动作 | 落点 | | :-- | :-- | | 工具面 | `tools.ts` 重写为 18 个:`note_library` / `note_expect_get` / `note_list` / `note_get` / `note_search` / `note_overview` / `note_plan` / `note_write` / `note_update` / `note_toc` / `note_link` / `note_unlink` / `note_rename` / `note_history` / `note_restore` / `note_lint` / `study_progress` / `study_memory`;`card_*` 整族退场 | | 业务编排 | `index.ts` 的 `VaultStore` 新增同名方法;门禁、规划、存档、目录索引全部接线 | | 硬门禁 | `note_write` 三连校验:期望签名一致 → 规划已确认未过期 → 路径在规划根内且标题未消费;每条失败给修复步骤 | | 存档不变量 | `note_update(replace)` / `note_restore` 走 `archiveThenWrite`:**先存档、后写正文**;正文不留 `
` 历史块 | | 微目录 | `note_toc` 只重写 `note_toc:begin/end` 之间;`note_list` 的计数与"缺微目录"按**子树累加**(父级目录通常只放章节标题) | | 期望驱动 lint | `note_lint` 从《笔记期望.md》解析 `- [检查] …` 规则(`expect-rule`),"检查什么"也走热配置 | | 端到端用例 | `tests/noteflow.spec.ts`(14 项):三条门禁各自拒绝且**磁盘零改动**、期望改动后须重读、闭环(期望→规划→落块→微目录→覆盖度)、replace 存档与 restore 往返、wikilink 双向关联 | | 契约用例 | `tests/tools.spec.ts` 重写为 18 工具契约;`tests/apply.spec.ts` 更新为 18 个工具名 | | 性能实测 | 工具 schema 总字节 **14.7 KB / 18 工具**(旧基线 ~15.0 KB / 21 工具),未超 N2 预算(≤ +10%) | ### 阶段 5/6 交付明细(旁支与收口) | 动作 | 落点 | | :-- | :-- | | 覆盖度纯模块 | `src/overview.ts`:`summarizeOverview` / `formatOverview`;节号按**数值**排序(`2.10` 在 `2.2` 之后)、跳号只提示、**不虚构章节**;无法解析的条目进"未归类"而不是被丢掉 | | 模块删除 | `src/card.ts`、`src/updatelegacy.ts`、`src/moc.ts`、`src/history.ts` 与其用例;`note.ts` 成为笔记契约唯一来源 | | 旧方法删除 | `VaultStore.create/update/link/moc/history` 与 MOC 收集逻辑;工具面只剩 18 个 `note_*` / `study_*` | | 用例收敛 | `tests/store.spec.ts` 收敛为**存储层不变量**(多根检索/歧义/fail-loud/跳过项/TTL/进度与记忆),功能用例统一由 `noteflow.spec.ts` 的端到端承担 | | 残留清零 | `src/` 内 `card_create` / `templateHints` / `DEFINITION_MAX` 等旧概念引用为 0(`tests/tools.spec.ts` 里的断言是"必须不存在"的守门,保留) |