实现第一版内存清洗核心
落实不可变数据契约、组件基类、原子修改执行器与顺序流水线。补充稳定性复查、审计记录、测试和当前机制文档。
This commit is contained in:
@@ -2,41 +2,53 @@
|
||||
|
||||
## 状态
|
||||
|
||||
草稿,待批准。本设计细化已冻结的 `0002`,不替代或修改其中的选择。
|
||||
已批准并冻结(2026-08-22)。
|
||||
|
||||
本设计批准后,才授权创建这里列出的 Python 包、测试和工程配置,并实现不含真实清洗规则的最小核心。
|
||||
在批准前,本仓库仍然没有可运行的清洗工具。
|
||||
`supersedes: 0002`(范围有限):本设计只替代 `0002` 中“所有组件必须提供公共 `check`”、
|
||||
`detect_only` / `suggestion` / `auto_fix` 三种处理状态,以及第一版同时建设检查流程的选择。
|
||||
`0002` 已确定的 Markdown 单一输入、项目显式组装、组件顺序执行、精确修改、单轮清洗、最终只读复查、
|
||||
文件适配层与项目 profile 边界继续有效。
|
||||
|
||||
本次批准授权创建这里列出的 Python 包、测试和工程配置,并实现不含真实清洗规则的最小核心。
|
||||
批准本设计不等于实现已经存在;在对应代码和测试实际落地前,本仓库仍然没有可运行的清洗工具。
|
||||
|
||||
## 1. 问题
|
||||
|
||||
`0002` 已经确定:项目显式组合组件,流水线只接收 Markdown,组件先检查再提出精确修改,清洗只执行一轮,
|
||||
最后进行只读复查。
|
||||
`0002` 已经确定总体组织方式,但把检查和清洗同时放进第一版:组件先返回三类问题,检查流程报告全部问题,
|
||||
清洗流程只应用其中的 `auto_fix`。
|
||||
|
||||
这些总体原则还不足以开始实现。目前尚未确定:
|
||||
真实材料中确实存在幻觉、截断、损坏表格和断链图片等问题,但第一版核心只有 Markdown,没有原文、图片、
|
||||
人工审核或回源能力。此时报告“确认异常但无法处理”的内容不能形成闭环;`suggestion` 也没有批准、拒绝、
|
||||
快照复核和安全应用协议。提前把这些能力放入核心,会引入暂时没有消费者的 `Issue`、`CheckResult` 和状态分支。
|
||||
|
||||
- 组件通过什么 Python 接口报告问题;
|
||||
第一版应先回答更小的问题:具体组件如何针对当前 Markdown 提出唯一、安全的精确修改,公共执行器如何保证
|
||||
这些修改没有过期、冲突或部分执行,流水线又如何确认一次清洗已经稳定。
|
||||
|
||||
尚需确定:
|
||||
|
||||
- 组件通过什么 Python 接口提出确定修改;
|
||||
- 中文文本的位置如何表示;
|
||||
- 候选修改怎样绑定当前 Markdown,避免旧位置误改新文本;
|
||||
- 修改怎样绑定当前 Markdown,避免旧位置误改新文本;
|
||||
- 多项修改如何保证全部成功或全部不执行;
|
||||
- 检查错误、清洗错误和最终未稳定如何返回;
|
||||
- 插入与替换在边界接触时如何判定冲突;
|
||||
- 组件错误、修改失败和最终未稳定如何返回;
|
||||
- 第一版源码、测试和依赖边界是什么。
|
||||
|
||||
如果这些问题留给实现时临时决定,组件很容易各自返回不同格式,或者直接生成整篇新 Markdown,最终无法统一
|
||||
验证和追踪修改。
|
||||
|
||||
## 2. 目标与非目标
|
||||
|
||||
目标:
|
||||
|
||||
- 建立只处理内存字符串的 Python 核心;
|
||||
- 确定快照、问题、候选修改、实际改动和运行结果的职责;
|
||||
- 让具体组件只负责定位问题和提出精确修改,不直接改写整篇 Markdown;
|
||||
- 让公共修改执行器统一验证范围、冲突、原子性和实际改动记录;
|
||||
- 实现组件独立调用、流水线检查、单轮清洗和最终只读复查;
|
||||
- 建立只处理内存字符串的 Python 自动清洗核心;
|
||||
- 让组件只提出能够立即自动执行的精确修改,不报告无法处理的疑似问题;
|
||||
- 确定快照、候选修改、文本编辑、实际改动、错误和运行结果的职责;
|
||||
- 让公共修改执行器统一验证范围、原文、冲突、原子性和实际改动记录;
|
||||
- 实现项目显式组装、组件顺序执行、单轮清洗和最终稳定性复查;
|
||||
- 使用测试专用组件验证组合机制,不把真实清洗语义混入架构实现。
|
||||
|
||||
非目标:
|
||||
|
||||
- 不实现独立文档检查、`detect_only`、人工建议或审核流程;
|
||||
- 不提供公共 `check`、`inspect`、预览或 dry-run 接口;
|
||||
- 不实现任何面向论文、GovDoc 或其他项目的真实清洗组件;
|
||||
- 不引入 Markdown parser、AST、HTML parser 或其他运行依赖;
|
||||
- 不读取或写入 Markdown 文件,不提供 CLI、批处理或服务接口;
|
||||
@@ -47,7 +59,7 @@
|
||||
|
||||
## 3. 名称与技术边界
|
||||
|
||||
- 项目展示名暂定为 `mdpolish`;
|
||||
- 项目展示名为 `mdpolish`;
|
||||
- Python 分发名和导入名使用小写 `mdpolish`;
|
||||
- 源码包位于 `src/mdpolish/`;
|
||||
- 第一版支持 Python 3.11 及以上版本;
|
||||
@@ -60,24 +72,31 @@
|
||||
|
||||
## 4. 方案比较与决定
|
||||
|
||||
### 4.1 组件直接返回整篇新 Markdown
|
||||
### 4.1 同时建设检查、建议和自动修改
|
||||
|
||||
接口最简单,但流水线无法确认组件实际改了哪里,也无法统一检查过期位置、范围冲突和部分失败。组件的检查逻辑
|
||||
还可能与修改逻辑逐渐分离。
|
||||
这种方案能够描述长期可能需要的文档体检和人工确认,但第一版没有外部材料、审核入口和建议应用协议。
|
||||
不可执行的问题不会参与清洗,候选建议又会在快照变化后过期。
|
||||
|
||||
第一版不采用。以后确有独立检查需求时,新增平行的 Inspector 设计,不在本轮预留三段状态。
|
||||
|
||||
### 4.2 组件直接返回整篇新 Markdown
|
||||
|
||||
接口简单,但流水线无法确认组件实际改了哪里,也无法统一检查过期位置、范围冲突和部分失败。
|
||||
组件还可能顺带改写没有被选中的内容。
|
||||
|
||||
不采用。
|
||||
|
||||
### 4.2 组件返回 AST,由流水线重新输出全文
|
||||
### 4.3 组件返回 AST,由流水线重新输出全文
|
||||
|
||||
适合格式化器和结构化编译,但会让没有被组件选中的 Markdown 也发生书写形式变化。第一版还需要先选择 parser、
|
||||
扩展方言和 renderer,超出了当前最小闭环。
|
||||
|
||||
不采用。
|
||||
|
||||
### 4.3 组件报告问题及精确文本修改
|
||||
### 4.4 组件只提出能够自动执行的精确修改
|
||||
|
||||
组件只读取当前快照,返回问题和可选候选修改;公共执行器验证并应用修改。这样可以保留原文、统一审计,
|
||||
也可以拒绝过期或重叠修改。
|
||||
组件读取当前快照,只返回前置条件严格、处理方式唯一的 `ProposedChange`。公共执行器统一验证并应用修改。
|
||||
如果某种输入存在歧义,组件直接忽略,不修改,也不在第一版中额外报告。
|
||||
|
||||
采用此方案。
|
||||
|
||||
@@ -90,24 +109,23 @@ Markdown 字符串
|
||||
DocumentSnapshot
|
||||
│
|
||||
▼
|
||||
Component._check_snapshot()
|
||||
Component._propose_changes()
|
||||
│
|
||||
├── Issue
|
||||
│ └── 可选 ProposedChange
|
||||
│ └── 一个或多个 TextEdit
|
||||
└── ProposedChange
|
||||
└── 一个或多个 TextEdit
|
||||
│
|
||||
▼
|
||||
公共修改执行器
|
||||
│
|
||||
├── 验证快照、范围、原文和冲突
|
||||
├── 原子应用当前组件的全部自动修改
|
||||
├── 原子应用当前组件的全部修改
|
||||
└── 生成 Change 和新 DocumentSnapshot
|
||||
│
|
||||
▼
|
||||
下一个 Component
|
||||
│
|
||||
▼
|
||||
最终快照只读复查
|
||||
最终快照重新提议但不应用
|
||||
│
|
||||
▼
|
||||
TransformResult
|
||||
@@ -115,12 +133,12 @@ TransformResult
|
||||
|
||||
核心分为四层:
|
||||
|
||||
- **数据模型:** 不可变地表达快照、范围、问题、修改、错误和结果;
|
||||
- **组件基类:** 提供统一的 `check` 和 `transform` 行为,只把问题定位留给具体组件;
|
||||
- **数据模型:** 不可变地表达快照、范围、候选修改、实际改动、错误和结果;
|
||||
- **组件基类:** 只定义组件身份、适用边界和 `_propose_changes()` 扩展点;
|
||||
- **修改执行器:** 是唯一能够把 `TextEdit` 应用到 Markdown 的位置;
|
||||
- **流水线:** 负责组件顺序、检查错误汇总、清洗失败停止和最终只读复查。
|
||||
- **流水线:** 负责组件顺序、快照刷新、错误、失败停止和最终稳定性复查。
|
||||
|
||||
文件读写、终端输出和未来配置不进入这四层。
|
||||
文件读写、终端输出、未来检查能力和配置不进入这四层。
|
||||
|
||||
## 6. 快照与位置
|
||||
|
||||
@@ -147,37 +165,25 @@ TransformResult
|
||||
Python 字符串下标是第一版唯一权威位置。行号和列号由快照与下标计算,只用于显示,不作为修改依据。
|
||||
这里的位置按 Unicode 码点工作,不按 UTF-8 字节或用户看到的字形数量工作。
|
||||
|
||||
## 7. 问题与候选修改
|
||||
## 7. 候选修改与文本编辑
|
||||
|
||||
### 7.1 处理能力
|
||||
### 7.1 `ProposedChange`
|
||||
|
||||
问题的处理能力固定为三种:
|
||||
一个候选修改表示组件确认能够自动执行的一次完整动作。它至少包含:
|
||||
|
||||
- `detect_only`:只报告,不包含候选修改;
|
||||
- `suggestion`:包含候选修改,但自动清洗不应用;
|
||||
- `auto_fix`:包含能够自动应用的候选修改。
|
||||
- 目标快照哈希;
|
||||
- 非空的修改理由;
|
||||
- 一个或多个 `TextEdit`。
|
||||
|
||||
`detect_only` 如果携带候选修改,或者 `auto_fix` 没有候选修改,都属于组件契约错误。
|
||||
同一候选修改中的编辑必须全部应用或全部不应用。组件必须以确定顺序返回候选修改;通常按首个编辑在原文中的
|
||||
位置从前到后排列。流水线根据组件执行位置、目标快照哈希和候选修改序号分配当前结果内的确定性引用,
|
||||
组件不生成随机 ID。
|
||||
|
||||
### 7.2 `Issue`
|
||||
第一版没有“不自动应用的候选修改”。不能确认唯一正确改法时,组件不返回 `ProposedChange`。
|
||||
|
||||
一条问题至少包含:
|
||||
### 7.2 `TextEdit`
|
||||
|
||||
- 产生问题的快照哈希;
|
||||
- 组件标识和组件版本;
|
||||
- 问题范围;文档级问题可以没有具体范围;
|
||||
- 简明说明和可复核证据;
|
||||
- 处理能力;
|
||||
- 可选的候选修改。
|
||||
|
||||
问题只描述某个快照中的事实。快照变化后,它可以继续作为历史记录,但不能直接用于修改新快照。
|
||||
|
||||
### 7.3 `ProposedChange` 与 `TextEdit`
|
||||
|
||||
一个候选修改表示解决一条问题所需的完整动作,可以包含一个或多个 `TextEdit`。同一候选修改中的编辑必须
|
||||
全部应用或全部不应用。
|
||||
|
||||
每个 `TextEdit` 至少包含:
|
||||
每个文本编辑至少包含:
|
||||
|
||||
- 目标快照哈希;
|
||||
- `TextSpan`;
|
||||
@@ -187,50 +193,65 @@ Python 字符串下标是第一版唯一权威位置。行号和列号由快照
|
||||
插入使用 `start == end` 和空 `expected_text`;删除使用空 `replacement`。`replacement` 与
|
||||
`expected_text` 完全相同的无效修改视为组件契约错误,不生成虚假的改动记录。
|
||||
|
||||
## 8. 组件接口
|
||||
## 8. 组件接口与契约
|
||||
|
||||
`Component` 使用抽象基类,而不是仅使用结构化 `Protocol`。公共基类负责保持独立调用与流水线调用的语义一致。
|
||||
`Component` 使用抽象基类。每个具体组件只实现以下扩展点:
|
||||
|
||||
每个具体组件只实现以下扩展点:
|
||||
|
||||
- 组件标识、组件版本和当前参数;
|
||||
- `_check_snapshot(snapshot)`:读取快照并返回问题,不产生副作用。
|
||||
- 稳定的组件标识;
|
||||
- 组件版本;
|
||||
- 当前参数;
|
||||
- 非空的适用边界说明;
|
||||
- `_propose_changes(snapshot)`:读取快照并返回能够自动执行的候选修改,不产生副作用。
|
||||
|
||||
组件标识使用稳定的小写字符串;同一语义不能因为改了 Python 类名就更换标识。组件版本使用
|
||||
`MAJOR.MINOR.PATCH` 形式。组件参数必须能表示为确定的只读基础数据,流水线将实际参数记录到结果中。
|
||||
|
||||
公共基类提供:
|
||||
适用边界至少说明组件处理的结构、严格前置条件和明确排除项。组件只对满足全部前置条件的内容提出修改;
|
||||
相似但有歧义的内容直接忽略。
|
||||
|
||||
- `check(markdown)`:建立快照,执行该组件检查并返回检查结果;
|
||||
- `transform(markdown)`:等价于只包含该组件的流水线清洗。
|
||||
所有组件必须满足以下不变量:
|
||||
|
||||
具体组件不得重写公共 `check`、`transform` 或修改执行器。类型声明使用 `final` 标记这些入口;代码评审和测试
|
||||
同时检查组件只实现规定扩展点。
|
||||
- 相同 Markdown、组件版本和参数产生相同顺序的候选修改;
|
||||
- 成功执行一次后再次执行,不产生新的实际改动;
|
||||
- 不读取文件、网络、环境变量、当前时间或随机数;
|
||||
- 不修改传入对象或外部状态;
|
||||
- 不直接生成或改写整篇 Markdown,只提交精确 `TextEdit`。
|
||||
|
||||
组件必须是确定性的:相同 Markdown、组件版本和参数必须产生相同问题及候选修改。组件不能读取文件、网络、
|
||||
环境变量、当前时间或随机数,也不能修改传入对象和外部状态。
|
||||
第一版组件没有公共 `check()` 或 `transform()`。单个组件通过只包含它的 `Pipeline` 独立运行:
|
||||
|
||||
```python
|
||||
result = Pipeline([component]).transform(markdown)
|
||||
```
|
||||
|
||||
这样避免 `component.py` 反向依赖 `pipeline.py`,也避免组件和流水线维护两套执行逻辑。
|
||||
|
||||
第一版流水线禁止出现两个相同组件标识的实例。需要用不同参数运行同一组件两次时,应由项目重新考虑组件边界,
|
||||
不能依靠重复 ID 制造含义不清的执行记录。
|
||||
|
||||
## 9. 公共修改执行器
|
||||
|
||||
流水线不会把多个组件在旧快照上产生的修改集中到最后再应用。每个组件都针对当前快照检查;该组件结束后,
|
||||
它的自动修改作为一个批次交给公共执行器。
|
||||
每个组件都针对当前快照提出修改;该组件结束后,它的全部候选修改作为一个批次交给公共执行器。
|
||||
|
||||
执行器按以下顺序验证当前组件的整个批次:
|
||||
|
||||
1. 问题和编辑的快照哈希都等于当前快照哈希;
|
||||
2. 所有范围合法;
|
||||
3. `markdown[start:end]` 与 `expected_text` 完全一致;
|
||||
4. 不存在重复编辑、范围重叠或同一位置的多个插入;
|
||||
5. 所有编辑都会实际改变内容。
|
||||
1. 候选修改和编辑的快照哈希都等于当前快照哈希;
|
||||
2. 每个候选修改理由非空并至少包含一个编辑;
|
||||
3. 所有范围合法;
|
||||
4. `markdown[start:end]` 与 `expected_text` 完全一致;
|
||||
5. 不存在重复编辑或下述范围冲突;
|
||||
6. 所有编辑都会实际改变内容。
|
||||
|
||||
相邻但不重叠的范围可以同时修改。验证全部通过后,执行器按位置从后向前应用编辑,避免前面的修改使后面的
|
||||
下标失效。任意一项验证失败,当前组件的整个批次都不应用。
|
||||
范围冲突使用以下保守规则:
|
||||
|
||||
这里的“当前组件整个批次”包括该组件本次检查产生的所有 `auto_fix` 候选修改。`suggestion` 和 `detect_only`
|
||||
问题永远不进入自动修改批次。
|
||||
- 两个非空范围真正重叠时冲突;相邻的 `[a, b)` 与 `[b, c)` 可以同时修改;
|
||||
- 两个插入位于同一位置时冲突,不同位置可以同时插入;
|
||||
- 插入点位于另一个非空范围内部,或等于该范围的起点、终点时,均视为冲突。
|
||||
|
||||
最后一条有意比半开区间的数学重叠更严格,避免相同起点的执行顺序和边界插入语义不明确。组件如果确实需要
|
||||
替换一段文字并在边界追加内容,应合并为一个 `TextEdit.replacement`。
|
||||
|
||||
验证全部通过后,执行器按位置从后向前应用编辑,避免前面的修改使后面的下标失效。任意一项验证失败,
|
||||
当前组件的整个批次都不应用。内部应用顺序不决定报告顺序;结果中的实际改动按原文位置从前到后排列。
|
||||
|
||||
### 9.1 `Change`
|
||||
|
||||
@@ -238,24 +259,40 @@ Python 字符串下标是第一版唯一权威位置。行号和列号由快照
|
||||
|
||||
- 组件标识和版本;
|
||||
- 组件在流水线中的执行位置;
|
||||
- 候选修改在本次组件结果中的引用和编辑序号;
|
||||
- 修改理由;
|
||||
- 修改前范围、`before` 和 `after`;
|
||||
- 修改前后的快照哈希;
|
||||
- 所属候选修改,使一次多位置动作可以整体追踪。
|
||||
- 修改前后的快照哈希。
|
||||
|
||||
修改记录描述实际发生的变化,不复制未执行的建议,也不把问题记录冒充改动记录。
|
||||
同一个 `ProposedChange` 产生的多条 `Change` 使用相同引用,使一次多位置动作可以整体追踪。同一组件批次中的
|
||||
所有 `Change` 共享该批次修改前后的快照哈希,不制造并不存在的中间公开快照。
|
||||
|
||||
## 10. 检查流程
|
||||
修改记录只描述实际发生的变化,不把未执行或验证失败的候选修改冒充实际改动。
|
||||
|
||||
`Pipeline.check(markdown)` 建立一个输入快照。所有组件按照项目给出的顺序检查同一个快照,Markdown 全程不变。
|
||||
## 10. 模块依赖方向
|
||||
|
||||
- 一个组件正常完成后,流水线按原顺序收集问题;
|
||||
- 一个组件抛出异常或返回违反契约的数据时,流水线记录结构化组件错误,然后继续检查后续组件;
|
||||
- 检查结果记录输入哈希、实际组件顺序、版本、参数、问题和错误;
|
||||
- 只要存在组件错误,检查结果就不能表示为完整成功,但已经获得的问题仍然保留。
|
||||
第一版保持以下单向依赖:
|
||||
|
||||
组件异常不能被静默忽略。错误至少记录组件身份、错误阶段、异常类型和安全的错误说明。是否保存 traceback
|
||||
留在内存实现中决定,不向未来报告格式作承诺。
|
||||
```text
|
||||
models.py
|
||||
▲ ▲
|
||||
│ │
|
||||
component.py edits.py
|
||||
▲ ▲
|
||||
\ /
|
||||
pipeline.py
|
||||
```
|
||||
|
||||
- `models.py` 只依赖 Python 标准库;
|
||||
- `component.py` 只依赖数据模型,不导入流水线或修改执行器;
|
||||
- `edits.py` 只依赖数据模型,不调用组件或流水线;
|
||||
- `pipeline.py` 可以依赖组件、修改执行器和数据模型;
|
||||
- `__init__.py` 只导出批准的公共对象,不实现第二套逻辑。
|
||||
|
||||
修改执行器不理解 arXiv、表格、HTML 或其他业务语义,也不决定组件顺序和最终状态。组件不应用编辑,
|
||||
不刷新快照,也不知道文件、CLI 或未来报告格式。
|
||||
|
||||
如果实现中发现这些边界导致机械性的循环依赖,可以拆分数据模型文件,但不能让底层模块反向导入流水线。
|
||||
|
||||
## 11. 清洗流程与运行状态
|
||||
|
||||
@@ -264,40 +301,57 @@ Python 字符串下标是第一版唯一权威位置。行号和列号由快照
|
||||
`Pipeline.transform(markdown)` 按以下步骤运行:
|
||||
|
||||
1. 建立输入快照;
|
||||
2. 按顺序让当前组件检查当前快照;
|
||||
3. 收集该组件的 `auto_fix` 候选修改;
|
||||
2. 按项目给出的顺序,让当前组件针对当前快照提出修改;
|
||||
3. 验证组件元数据和候选修改契约;
|
||||
4. 原子验证并应用当前组件的整个修改批次;
|
||||
5. 有修改时建立新快照,下一个组件只能读取新快照;
|
||||
6. 所有组件各执行一次后,对最终快照运行最终只读复查。
|
||||
6. 所有组件各执行一次后,对最终快照运行最终稳定性复查。
|
||||
|
||||
组件没有提出修改是正常情况,不产生空批次或虚假 `Change`。
|
||||
空组件列表也是合法输入:流水线对原输入建立快照后直接完成空的最终复查,返回零改动的 `success`。
|
||||
|
||||
清洗阶段如果组件异常、返回无效数据或修改批次冲突,流水线立即停止,不继续运行后面的组件,也不进行最终复查。
|
||||
之前组件已经完成的内存修改和 `Change` 保留在失败结果中,但不能作为成功输出。
|
||||
|
||||
### 11.2 最终只读复查
|
||||
### 11.2 最终稳定性复查
|
||||
|
||||
最终复查让所有已选组件按照原顺序检查最终快照,不应用任何修改。
|
||||
最终复查让所有已选组件按照原顺序针对最终快照重新提出修改,但不应用任何修改。
|
||||
|
||||
- 发现仍可由已选组件 `auto_fix` 的问题:状态为 `unstable`;
|
||||
- 只剩 `detect_only` 或 `suggestion` 问题:保留为未解决问题,不妨碍核心流水线成为 `success`;
|
||||
- 最终复查发生组件错误:状态为 `failed`;复查继续检查其余组件,以汇总只读阶段的错误和问题。
|
||||
复查阶段仍然验证组件元数据、候选修改、原文和整个组件批次的冲突;区别只是验证通过后不应用编辑。
|
||||
每条有效残留修改连同组件标识、版本、执行位置和确定性引用一起记录,使调用方知道由哪个组件提出。
|
||||
|
||||
是否因为未解决的 `detect_only` 或 `suggestion` 问题阻止下游使用,由使用项目决定,不写死在共用核心中。
|
||||
- 所有组件都不再提出修改:状态可以是 `success`;
|
||||
- 任一组件仍提出有效修改:状态为 `unstable`,并保留这些 `residual_proposals`;
|
||||
- 复查发生组件错误或返回无效数据:状态为 `failed`;复查继续调用其余组件,以汇总只读阶段的错误。
|
||||
|
||||
### 11.3 状态与文本字段
|
||||
如果最终复查同时出现错误和其他组件的有效残留修改,最终状态以 `failed` 为准,但已经获得的
|
||||
`residual_proposals` 仍然保留,不能因为另一个组件失败而丢失。
|
||||
|
||||
最终复查只回答“选中的自动清洗组件是否已经稳定”,不声称 Markdown 没有截断、幻觉、损坏表格或其他
|
||||
第一版不处理的问题。
|
||||
|
||||
### 11.3 状态与结果字段
|
||||
|
||||
清洗状态至少包括:
|
||||
|
||||
- `success`:单轮清洗和最终复查完整完成,没有仍可自动修复的问题;
|
||||
- `failed`:组件执行、数据契约或修改验证失败;
|
||||
- `unstable`:修改阶段没有错误,但最终复查仍发现已选组件可自动修复的问题。
|
||||
- `success`:单轮清洗和最终复查完整完成,选中组件不再提出修改;
|
||||
- `failed`:组件执行、组件契约或修改验证失败;
|
||||
- `unstable`:修改阶段没有错误,但最终复查仍产生有效候选修改。
|
||||
|
||||
为避免调用方忽略状态并误用半成品:
|
||||
|
||||
- `success` 只提供 `output_markdown`,`partial_markdown` 为空;
|
||||
- `failed` 和 `unstable` 不提供 `output_markdown`,只提供诊断用的 `partial_markdown`;
|
||||
- 三种状态都记录输入哈希、当前内容哈希、组件清单、实际改动、未解决问题和错误。
|
||||
- 三种状态都记录输入哈希、当前内容哈希、组件清单、实际改动和错误;
|
||||
- `residual_proposals` 记录最终复查已经验证有效的残留修改;它可以出现在 `unstable` 或最终复查阶段产生的
|
||||
`failed` 结果中,在 `success` 和清洗阶段直接失败的结果中为空。
|
||||
|
||||
错误至少记录组件标识和版本、发生阶段(`transform` 或 `final_review`)、异常或契约错误类型,以及不泄露
|
||||
额外原文的简明说明。组件异常、契约错误和修改验证错误都不能被静默忽略。
|
||||
|
||||
输入本身从不被原地覆盖。即使输出内容与输入完全相同,只要最终复查通过,也可以是零改动的 `success`。
|
||||
由于核心只返回内存结果、不写文件,第一版不再提供额外预览接口;调用方可以先审查 `TransformResult`,
|
||||
再由未来适配层决定是否保存成功输出。
|
||||
|
||||
## 12. 源码与测试结构
|
||||
|
||||
@@ -321,35 +375,41 @@ tests/
|
||||
```
|
||||
|
||||
- `models.py` 只放不可变的数据模型和状态枚举;
|
||||
- `component.py` 放组件基类和组件契约验证;
|
||||
- `component.py` 放组件基类和组件元数据契约;
|
||||
- `edits.py` 放纯文本修改验证与应用;
|
||||
- `pipeline.py` 放检查、单轮清洗和最终复查编排;
|
||||
- `pipeline.py` 放单轮清洗和最终稳定性复查编排;
|
||||
- `__init__.py` 只导出第一版公共对象;
|
||||
- `py.typed` 声明分发包提供类型信息;
|
||||
- 测试辅助组件只存在于 `tests/`,不发布成示例清洗能力。
|
||||
|
||||
如果实现中发现这些边界导致循环依赖,可以在不改变公共职责的前提下机械拆分模块;新增新的业务层、运行依赖
|
||||
或公共入口仍需要重新评审。
|
||||
新增新的业务层、运行依赖或公共入口仍需要重新评审。
|
||||
|
||||
## 13. 测试专用组件与验收
|
||||
|
||||
第一版不借真实文档验证,也不把测试字符串包装成正式清洗规则。测试中建立最小假组件,分别产生固定问题、
|
||||
建议修改、精确替换、组件异常和连锁影响。
|
||||
第一版不借真实文档验证,也不把测试字符串包装成正式清洗规则。测试中建立最小假组件,分别产生零修改、
|
||||
固定修改、多位置修改、组件异常、无效候选和连锁影响。
|
||||
|
||||
至少覆盖:
|
||||
|
||||
- 空 Markdown、中文、换行和 Unicode 组合字符;
|
||||
- 插入、删除、替换以及一次问题包含多个编辑;
|
||||
- 相邻范围可以应用,重叠范围、重复编辑和同点插入明确失败;
|
||||
- 哈希过期、范围越界、`expected_text` 不符和无效修改明确失败;
|
||||
- 当前组件批次全部成功或全部不应用;
|
||||
- 检查流程记录错误后继续其他组件;
|
||||
- 清洗流程遇错立即停止,并区分成功输出与部分文本;
|
||||
- 后一个组件制造前一个组件的新问题时,最终复查返回 `unstable`,且不自动开始第二轮;
|
||||
- `suggestion` 和 `detect_only` 不被自动应用;
|
||||
- 组件独立运行与单组件流水线结果一致;
|
||||
- 空组件列表返回原文不变、零改动的 `success`;
|
||||
- 插入、删除、替换以及一次候选修改包含多个编辑;
|
||||
- 相邻非空范围可以应用,重叠范围和重复编辑明确失败;
|
||||
- 不同位置插入可以应用,同点插入明确失败;
|
||||
- 插入位于非空范围内部、起点或终点时明确失败;
|
||||
- 哈希过期、范围越界、`expected_text` 不符、空候选、空理由和无效修改明确失败;
|
||||
- 当前组件批次全部成功或全部不应用,包括不同候选修改之间发生冲突;
|
||||
- 组件异常或契约错误使清洗立即停止,并区分成功输出与部分文本;
|
||||
- 后一个组件读取前一个组件修改后的新快照;
|
||||
- 后一个组件制造前一个组件的新问题时,最终复查返回 `unstable`,保留 `residual_proposals`,且不开始第二轮;
|
||||
- 最终复查发生错误时继续调用其余组件并最终返回 `failed`;
|
||||
- 最终复查同时出现错误和有效残留修改时,两者都保留,状态为 `failed`;
|
||||
- 错误记录能够区分 `transform` 和 `final_review` 阶段;
|
||||
- 同一个候选修改的多条 `Change` 共享引用、理由和批次前后哈希;
|
||||
- 单个组件通过单组件 `Pipeline` 正常运行;
|
||||
- 成功流水线再次运行不产生实际改动;
|
||||
- 输入字符串不被修改,相同输入、组件和参数产生相同结果。
|
||||
- 输入字符串不被修改,相同输入、组件和参数产生相同顺序的结果;
|
||||
- 重复组件标识、无效版本、不可表示的参数和空适用边界明确失败。
|
||||
|
||||
批准并实现后,基础验证至少包括:
|
||||
|
||||
@@ -361,28 +421,45 @@ pytest
|
||||
|
||||
README 届时记录实际可用命令。只有这些命令真实运行成功后,才能报告对应检查通过。
|
||||
|
||||
## 14. 风险与代价
|
||||
## 14. 未来检查能力如何扩展
|
||||
|
||||
- **没有公共 AST:** 结构复杂的组件以后可能需要重复解析;先保持解析细节为组件内部实现,等真实规则证明需要
|
||||
共享分析后再设计。
|
||||
第一版不为未来检查功能预留空枚举或可空候选修改。以后真实项目证明只读检查有独立消费者时,通过新 design
|
||||
增加平行接口,例如:
|
||||
|
||||
```text
|
||||
Inspector.inspect(DocumentSnapshot) -> Finding
|
||||
InspectionPipeline.inspect(markdown) -> InspectionResult
|
||||
```
|
||||
|
||||
未来检查能力可以复用 `DocumentSnapshot`、`TextSpan` 和组件身份规则,但不修改 `TextEdit`、
|
||||
`ProposedChange`、公共修改执行器、`Pipeline.transform()` 或 `TransformResult`。某项能力同时需要检查和清洗时,
|
||||
对应 Inspector 与 Component 可以在自身实现中复用定位函数,不需要让两个产品流程共享同一种结果模型。
|
||||
|
||||
人工建议以后还需要批准、拒绝、快照复核和应用协议,不能只增加一个 `suggestion` 枚举就视为完成。
|
||||
|
||||
## 15. 风险与代价
|
||||
|
||||
- **第一版不报告不可处理问题:** `success` 只表示选中组件执行稳定,不表示文档整体正确;README 和未来 API
|
||||
文档必须明确这一点。
|
||||
- **没有公共 AST:** 结构复杂的组件以后可能需要重复解析;等真实规则证明需要共享分析后再设计。
|
||||
- **Python 字符位置不是跨语言协议:** 第一版只承诺 Python API;以后输出机器可读跨语言格式时,需要单独定义
|
||||
坐标语义,不能直接假设 JavaScript UTF-16 或 UTF-8 byte offset 与之相同。
|
||||
- **最终复查增加检查成本:** 选中的组件最多检查两次,但换来对组合连锁影响的明确判断,第一版接受此代价。
|
||||
坐标语义,不能假设 JavaScript UTF-16 或 UTF-8 byte offset 与之相同。
|
||||
- **最终复查增加检查成本:** 选中的组件最多提出两次修改,但换来对组合连锁影响的明确判断,第一版接受此代价。
|
||||
- **组件级原子批次可能放弃部分正确修改:** 这是有意的保真选择;组件应修复自己的冲突,而不是让流水线猜测。
|
||||
- **失败结果仍含部分 Markdown:** 使用独立字段并让成功输出为空,降低误用风险;未来文件适配层不得默认写出
|
||||
`partial_markdown`。
|
||||
- **结果可能包含原文片段:** 第一版结果只存在内存,不建设日志和持久化;以后新增 reporter 时必须单独评审
|
||||
脱敏和保存边界。
|
||||
- **组件版本需要维护:** 组件语义、定位或修改行为变化时必须更新版本,不能只改代码而保留相同审计身份。
|
||||
- **组件版本需要维护:** 组件语义、适用边界、定位或修改行为变化时必须更新版本,不能只改代码而保留相同身份。
|
||||
|
||||
## 15. 批准后的实施边界
|
||||
## 16. 批准后的实施边界
|
||||
|
||||
批准本设计只授权:
|
||||
|
||||
1. 创建第 12 节列出的工程与测试文件;
|
||||
2. 实现第 5 至 11 节描述的内存核心;
|
||||
2. 实现第 5 至 11 节描述的内存自动清洗核心;
|
||||
3. 创建测试专用假组件并完成第 13 节验证;
|
||||
4. 根据真实实现更新 README 当前阶段和基础检查;
|
||||
5. 实现完成后新增 `research-wiki/explanation/` 文档,解释当前实际架构。
|
||||
|
||||
批准本设计不授权实现真实清洗规则,不授权读取真实数据,不授权文件覆盖、CLI、发布、提交或推送。
|
||||
批准本设计不授权实现真实清洗规则,不授权读取真实数据,不授权文件覆盖、独立检查能力、CLI、发布、提交或推送。
|
||||
|
||||
Reference in New Issue
Block a user