- DiffView 聚焦 effect 依赖补上 before/after:stage 文本到达、 MergeView 重建后重新应用选区和滚动,跨组件点击不再停在顶部; - 非可编辑面板加 drawSelection,聚焦范围在浏览器中可见; - 移除 collapseUnchanged,评审始终可读完整未改动段落; - 滚动容器移到 .cm-mergeView 外层,编辑器高度自适应内容; - 新增 3 项 Vitest 覆盖折叠移除与聚焦时序,共 9 项通过; - README 与 explanation 同步当前检查数量。
6.2 KiB
本地 Markdown 清洗评审器如何保持只读和可追踪
1. 它解决什么问题
本地清洗实验已经保存最终 Markdown、逐条审计和 unified diff,但人工评审仍需要在多个文件之间切换,也看不到某个组件 执行前后的完整文本。当前评审器把一次已发布运行变成只读页面:主视图比较原文和最终成功输出,组件时间线则比较每个 组件实际收到的快照和它产生的新快照。
实现范围来自已批准的
0007-local-markdown-reviewer.md。精确 API、字段和运行行为以
reviewer/ 中的代码、类型和测试为准。
2. 同仓库怎样保持解耦
mdpolish 本地实验层
│
▼
manifest / result / cleaned / review-locator
│
▼
Python 产物适配器与共享重放 ──► 本地只读 API ──► React 页面
src/mdpolish/不导入reviewer/;reviewer/server/只从mdpolish导入无文件 I/O 的_artifact_replay.py,不导入组件、流水线或实验入口;- 浏览器只读取
/api/v1/,不解析磁盘 JSON,也不知道绝对路径; - Python wheel 不包含前端代码或 Node.js 依赖;
- 产物 schema 以后改变时,差异集中在服务端版本适配器,不扩散到页面组件。
前端与核心位于同一 Git 仓库,方便开发和评审,但仍是独立的 Node.js package。依赖版本只在
reviewer/package.json 和锁文件维护。
3. 运行定位文件保存什么
新实验会在运行目录根部原子保存 review-locator.json。它记录:
- 运行 ID 和发布时的绝对运行目录;
manifest.json的固定相对位置;- 每份文档的 ID、实际读取的绝对源路径和输入 SHA-256。
定位文件不复制原文,也不替代 manifest 或 result。评审器由用户显式指定当前运行目录;记录的旧运行目录只用于判断目录 是否被移动。服务只根据定位文件读取对应原文,并在每次展示前重新计算哈希。源文件不存在或内容变化时,页面明确报告 不可用,不按名称搜索替代文件。
绝对路径会暴露本机目录结构,所以定位文件与其他 artifact 一样使用 0600 权限并按本地敏感数据处理。历史运行没有该
文件时仍可查看清单和局部审计,但不能自动展示完整原文;评审器不会回写历史目录。
4. 服务为什么只读取一次运行
启动时必须传入一个具体运行目录。服务不会扫描 artifacts/,也没有让浏览器传入任意文件路径的 API。它先校验:
- manifest、result 和存在的 locator 都是支持的 schema、严格 UTF-8 JSON;
- 文档、组件、状态、路径和计数彼此一致;
- 所有 artifact 路径都留在所选运行目录;
- 原文和成功输出的字节哈希与审计一致;
- 成功文档确实同时具有
cleaned.md和changes.diff。
HTTP 只监听 127.0.0.1 的随机空闲端口,只接受 GET 和 HEAD。服务拒绝非本机 Host、跨域 Origin、路径穿越和
写请求,不提供删除、移动、重新清洗或 shell 执行能力。响应禁止缓存,不开放 CORS,也不向浏览器返回绝对源路径。
5. 组件阶段怎样准确重放
Change.span 使用 Python Unicode 码点位置,而 JavaScript 编辑器使用 UTF-16 code unit。共享 Python 重放模块直接按
原生码点范围逐组件处理,不让浏览器应用修改:
- 当前完整文本哈希必须等于组件批次的
before_sha256; - 每条范围内文本必须等于
before; - 同一批次不得有冲突范围,并按位置从后向前应用;
- 应用后完整文本哈希必须等于
after_sha256; - 全部组件结束后必须逐字等于
cleaned.md; - 服务端另外从已验证的组件前快照派生 UTF-16
editor_range,只供 CodeMirror 跳转。
组件没有修改时,阶段前后文本和哈希相同,但该组件仍显示在时间线中。包含中文、emoji、组合字符、BOM、CRLF 和无末尾 换行的合成测试用于保护跨语言坐标。任一重放校验失败时,页面拒绝显示组件阶段,不通过搜索或 diff 猜测位置。
中间快照只在服务内存中按需生成,不保存新的 Markdown 文件。failed 和 unstable 文档只显示错误、残留候选和已有的
局部审计,不重建一份看似正式的部分输出。
6. 页面当前能看什么
页面当前提供:
- 运行状态、文档状态、哈希和实际
Change数量; - 完整原文与最终成功 Markdown 的只读双栏源码比较;
- 8 个组件的实际顺序、版本和每份文档修改数量;
- 任一组件执行前后的完整文本比较;
- 修改理由、派生行列、
before/after和同候选修改关联; failed、unstable、路径失效、哈希变化和未知 schema 的独立错误状态。
Markdown 只作为文本交给 CodeMirror,不进入 innerHTML。第一版不渲染 Markdown、HTML 或图片,不加载 CDN、远程字体、
遥测和其他外部资源,也不提供编辑、审核或回写。
7. 当前验证结果和边界
2026-08-24 使用 Python 3.13.11 和当前用户 nvm 中的 Node.js 24.19.0 完成:
- Python Ruff、mypy 和 229 项 pytest 通过;
- reviewer ESLint、TypeScript、9 项 Vitest 和生产构建通过;
- 新运行
clindb-first-batch-reviewer-v1的 5 份论文全部success,共 155 条实际修改; - 5 份原文运行前后哈希不变;
- 本地 API 成功校验 5 份文档、8 个组件和 40 个组件阶段;
- 所有原文、成功输出和阶段前后文本的 SHA-256 与运行审计一致;
- 生产页面和全部本地构建资源可以通过只读服务读取,响应没有 CORS 并包含禁止缓存和内容类型保护头。
当前环境没有可用于自动视觉检查的本地浏览器,因此布局的真实浏览器视觉效果尚未验证。当前结果证明构建、服务、数据 重放和主要 React 状态可以运行,不等于已经完成跨浏览器、极端长度、渲染预览或生产部署验证。
实际启动与评审步骤见 review-local-cleaning-run.md。