18 KiB
0010:第一版跨项目库交付
状态
已于 2026-08-27 经用户明确批准。本文自批准起冻结;后续若改变这里的 backend 契约、版本身份或发布渠道, 应新增 design,不得回写本文。
supersedes: 0009(范围有限):本文只替代 0009 中没有被真实 optional dependency 验证覆盖的
SPELLCHECKER 大小写适配、发布前验收和版本身份部分。0009 已批准的规则模型、候选比较、失败关闭、
Markdown 块边界、无默认规则、无网络和函数式 Modifier 契约继续有效。
本文批准后只授权实现和验证,不自动授权提交、创建 tag、push、创建 GitHub Release、上传产物、修改其他仓库 或读取真实材料。
1. 问题与可观察现象
提交 11349e0 已经实现通用 mapped_line_join(),仓库基础检查在 Python 3.13.11 下得到:
168 passed, 2 skipped
两个 skip 分别对应没有安装的 pyspellchecker 和 wordfreq。这意味着核心契约和合成 backend 通过了测试,
但最关键的真实词典适配器没有进入同一次验收。
2026-08-27 在临时干净 venv 中从 wheel 安装全部 extras 后,实际版本为:
| 包 | 实际版本 |
|---|---|
mdpolish |
0.2.0 |
pyspellchecker |
0.9.0 |
pyphen |
0.18.1 |
wordfreq |
3.1.1 |
wordfreq、Pyphen 和显式 case_sensitive=False 的 pyspellchecker 都能把合成输入
an exam-\nple 稳定处理成 an example。但是,当前 LexicalLineJoinRule.case_sensitive 默认是 True,
实现会把它直接传给:
SpellChecker(language="en", case_sensitive=True)
真实上游立即拒绝:
ValueError: case_sensitive can only be True when not using a language dictionary.
pyspellchecker 的源码和官方 quickstart 都说明,大小写敏感模式只适用于不加载内置 language dictionary 的实例:
- https://github.com/barrust/pyspellchecker/blob/master/spellchecker/spellchecker.py
- https://github.com/barrust/pyspellchecker/blob/master/docs/source/quickstart.rst
这不是词典无命中,而是构造阶段的能力冲突。当前实现把它包装成泛化的“无法加载语言”错误,调用方无法知道 应该怎样改配置。
另一个交付问题是包版本仍为 0.2.0。这个版本已经用于 0008 后的函数式核心;如果新旧内容继续生成同名 wheel,
其他项目、pip 缓存和问题报告都无法可靠区分实际安装的是哪一份代码。
因此,当前提交适合受控试接,不适合直接作为一个带稳定版本身份的跨项目交付。
2. 目标与非目标
2.1 目标
- 对
SPELLCHECKER不支持的大小写组合给出构造期、可操作、不会泄露正文的错误; - 保留
case_sensitive=True的公共默认值,不静默开启忽略大小写; - 用真实安装的
pyspellchecker、Pyphen 和wordfreq验证 wheel,而不是把 skip 当成通过; - 为这次新增公共能力分配唯一包版本
0.3.0; - 将修复后的
mapped_line_join修改器版本更新为2.0.1,使审计记录可以区分修复前后; - 在 README 提供其他项目可以直接复制的安装、导入和自动英文断词示例;
- 将 GitHub tag 和 GitHub Release 确定为计划内的正式发布渠道,明确不使用 PyPI;
- 明确第一版固定版本方式和消费者责任;
- 保持核心安装零第三方运行依赖,extras 仍然只由调用方显式安装和启用。
2.2 非目标
- 不为
pyspellchecker的内置语言词典自行实现大小写敏感查询; - 不读取、复制或改写
pyspellchecker的内部压缩词典资源; - 不把
case_sensitive默认值改成False; - 不引入 Hunspell、Enchant、wordninja、在线词典、模型或 OCR 版面接口;
- 不新增默认规则、默认流水线、项目 profile、配置文件或 CLI;
- 不声称词典规则已经在真实业务语料上达到生产准确率;
- 不在本轮定义标注指标、接受阈值或真实材料实验;
- 不发布到 PyPI 或其他 Python 包索引,不提交 wheel 到 Git;
- 不因批准本文就自动创建 GitHub Release 或上传产物;
- 不修改其他 modifier、
_text_ranges.py、真实数据或其他仓库。
3. SPELLCHECKER 大小写适配
3.1 候选方案
| 方案 | 优点 | 代价与问题 | 选择 |
|---|---|---|---|
把 LexicalLineJoinRule.case_sensitive 默认改成 False |
默认示例可以直接运行 | 违反“不默认忽略大小写”,并静默改变公共语义 | 否决 |
忽略调用方的 True,内部总用大小写不敏感词典 |
代码最少 | 参数记录与实际行为不一致,属于静默降级 | 否决 |
| 读取上游包内 JSON 频率表,自己实现大小写敏感词典 | 理论上可以支持 True |
绑定上游内部资源路径和格式,扩大维护面;第一版没有证据需要它 | 本轮否决 |
SPELLCHECKER 明确要求 case_sensitive=False |
行为诚实、改动小、调用方必须主动选择 | 该 backend 暂不提供大小写敏感语言词典 | 采用 |
3.2 决定
case_sensitive 继续是所有规则共有的显式开关,默认仍为 True。新增 backend 组合验证:
backend == SPELLCHECKER and case_sensitive is True
在 mapped_line_join() 构造阶段立即抛出 ModifierContractError。错误消息必须说明:
pyspellchecker的内置 language dictionary 不支持大小写敏感模式;- 调用方若接受大小写不敏感匹配,需要显式设置
case_sensitive=False; - 不建议切换 backend,也不执行自动回退。
错误只包含 backend、rule_id 和配置字段,不包含输入正文。因为错误发生在构造修改器时,也不应等到
Pipeline.transform() 才暴露。
WORDFREQ 保留现有 case_sensitive=True/False 行为。不能因为一个 backend 的限制,把共同字段的默认值改成
另一个含义。
3.3 为什么第一版不复制词典
pyspellchecker 官方允许用 language=None, case_sensitive=True 加载调用方自己的词典,但当前规则只接受语言代码,
设计也禁止读取任意路径。为了绕过限制而解析依赖包内部的 resources/<language>.json.gz,会新增一套未获保证的
资源格式契约。
第一版更重要的是不误导调用方。明确拒绝不支持的组合,比复制上游实现、静默忽略参数或假装拥有大小写敏感语言词典 更符合失败关闭原则。如果未来项目确实需要该能力,应以真实样本和新 design 决定是增加版本化自带词典、扩展 backend, 还是接受自定义词典输入;不能回写本文。
4. 版本身份
4.1 包版本
实现本文时把 pyproject.toml 和 README 中的包版本从 0.2.0 更新为 0.3.0。
选择 0.3.0 而不是 1.0.0,原因是:
- 新能力是向现有函数式核心增加公共规则和 optional backend,属于
0.x阶段的次版本变化; - 公共规则还没有经过多个调用项目验证;
- Markdown 块扫描仍是保守词法子集;
- 没有真实语料准确率、默认 profile、CLI 或生产写入协议。
0.3.0 表示“可以被其他项目固定版本试用”,不表示“自动词典规则已经适合任意文档生产启用”。
4.2 修改器版本
mapped_line_join() 生成的 Modifier.version 从 2.0.0 更新为 2.0.1。
规则 dataclass 和序列化参数格式不改变,因此不升到 3.0.0。补丁版本用于表示:
- 真实
SPELLCHECKERbackend 的能力验证变得准确; - 不支持的组合从上游泛化异常变成稳定的构造期契约错误;
- 已支持组合的候选选择语义不改变。
4.3 版本记录
词典规则继续在 Modifier.parameters 中记录 backend 包版本、语言、候选形式和阈值。调用项目还必须固定
mdpolish 包版本;只记录 Modifier.version 不能替代安装依赖锁定。
5. 第一版交付渠道
5.1 采用范围
项目计划通过 GitHub 发布版本,不发布到 PyPI 或其他 Python 包索引。不可移动的 Git tag 是源码身份,GitHub Release 是面向使用方的正式发布记录。第一版候选 tag 和 Release 名称为:
v0.3.0
tag 必须指向通过本文全部验收的唯一提交,创建后不得移动;GitHub Release 必须绑定这个 tag,不能指向分支头。 其他项目可以通过 GitHub Git 地址和 tag 固定依赖,也可以安装该 Release 附带的 wheel。Python Packaging 规范允许 集成方使用 direct reference,但它不是包索引发布物:
- https://packaging.python.org/en/latest/specifications/version-specifiers/#direct-references
- https://packaging.python.org/en/latest/specifications/dependency-specifiers/
README 应给出 GitHub tag direct reference 和 Release wheel 两种安装形式,但不把仓库凭据或某个调用项目配置写入库代码。 仓库及 Release 是公开还是私有,由 GitHub 仓库权限决定;本文不授权改变仓库可见性。
5.2 wheel 的角色
wheel 在发布前首先是验证产物,不提交到 Git,也不在仓库内建立 artifact 目录。验收必须从最终源码构建 wheel,并在 干净环境从这个 wheel 安装,而不是依赖当前仓库的 editable install。
实际创建 GitHub Release 时,只能上传通过第 7 节验收的同一个 wheel,并同时提供 SHA-256 校验值。若 tag 后重新构建, 必须重新执行 wheel 元数据和消费者 smoke test,不能把不同构建物当作已经验收的产物。GitHub 自动生成的源码归档与 Release wheel 共同保存在 GitHub;本仓库不另存一份二进制副本。
PyPI、GitHub Packages、内部 Python 包索引和其他 artifact 仓库均不在计划内。将来若要增加其他发布渠道,必须用新的 design 改变本文,而不能只改发布脚本或 README。
5.3 tag 和 push 的确认门
本文批准后可以完成版本修改、测试、构建和提交前候选检查,但不能自动提交、创建或推送 v0.3.0,也不能创建
GitHub Release 或上传 wheel。
只有在用户看到最终 diff、真实测试输出和 wheel 元数据后,才能明确授权:
- 提交交付改动;
提交完成并向用户报告提交哈希和 tag 的准确目标后,才能再明确授权:
- 创建
v0.3.0tag; - push 提交和 tag;
- 创建绑定
v0.3.0的 GitHub Release; - 上传已验收的 wheel 和 SHA-256 校验值。
这些动作不因“设计已批准”而自动获得授权。
6. 公共调用契约与 README
6.1 支持的导入路径
保持 0009 的决定,不扩大聚合导出:
from mdpolish.modifiers import mapped_line_join
from mdpolish.modifiers.mapped_line_join import (
LexicalCandidateForm,
LexicalLineJoinRule,
LexiconBackend,
)
本轮不把全部枚举和 dataclass 重新导出到 mdpolish.modifiers 或包根。减少顶层公共表面积比缩短一行导入更重要。
6.2 README 示例
README 保留旧三元组示例,并新增一个最小自动英文断词示例。示例必须明确写出:
- 通过 GitHub tag direct reference 或 GitHub Release wheel 安装
lexicalextra; backend=LexiconBackend.SPELLCHECKER;case_sensitive=False;JOINED与源HYPHENATED共同参与候选;- 分数阈值只是示例配置,不是库推荐的通用生产阈值;
- 检查
RunStatus后才能消费输出。
README 还要明确区分:
| 说法 | 当前是否成立 |
|---|---|
| wheel 可以安装,公共 API 可以运行 | 是,验收通过后成立 |
| 自动词典不需要逐词维护映射 | 是 |
| 库自带默认英文清洗规则 | 否 |
| 某个阈值适合所有项目 | 否 |
| 已在真实业务文档证明生产准确率 | 否 |
6.3 调用项目责任
调用项目必须:
- 固定
mdpolish版本或不可移动 tag; - 把规则、顺序、backend、语言、阈值和例外作为项目配置评审;
- 检查
success、failed、unstable,不能只读取可能为空的输出; - 自己负责文件读写、覆盖策略、批处理、日志和回滚;
- 在自己的语料上验证误合并,不能把库的合成测试当作领域准确率。
7. 验证矩阵
7.1 核心环境
不安装 lexical 或 frequency extras,运行根 README 的全部基础检查,确认:
- 精确、正则、链式和 Markdown 失败关闭测试通过;
- 导入模块不会加载词典;
- 请求缺失 backend 时给出稳定错误;
- optional backend 测试可以明确 skip,但 skip 不能计入真实 backend 验收。
7.2 extras 环境
另建干净环境,安装最终 wheel 的 lexical 和 frequency extras,并另行安装仓库测试工具,运行同一测试集。
该环境的发布门要求:
pyspellchecker、Pyphen 和wordfreq真实 adapter 测试全部执行,不得 skip;SPELLCHECKER + case_sensitive=True在构造期得到预期契约错误;SPELLCHECKER + case_sensitive=False的唯一拼接候选可以合并;- Pyphen 合法和非法断点分别影响
JOINED; WORDFREQ唯一胜者、margin 不足和歧义路径都符合0009;- 包版本和 backend 版本进入审计参数;
- 全套测试没有因安装 extras 而改变精确/正则规则结果。
测试不能只断言“构造成功”。至少一个真实 backend 用例必须通过 Pipeline.transform() 检查最终状态和完整输出。
7.3 wheel 消费者 smoke test
从最终提交构建 wheel 后,在不位于仓库源码目录的临时环境验证:
- 只安装核心 wheel,运行精确和正则示例;
- 安装
lexicalextra,运行SPELLCHECKER + Pyphen示例; - 安装
frequencyextra,运行WORDFREQ示例; - 检查
mdpolish、修改器和 backend 版本记录; - 检查 wheel 不包含 tests、Wiki、真实数据、项目规则或临时产物;
- 生成并记录待上传 wheel 的 SHA-256 校验值;
- 确认执行期间不访问网络、调用模型或读取任意文档路径。
依赖安装本身可以访问配置的包索引;“运行期间无网络”指安装完成后的库行为,不把安装包与执行清洗混为一谈。
7.4 Python 支持范围
pyproject.toml 当前声明 Python 3.11 及以上。第一版交付前至少验证:
- 最低支持版本 Python 3.11;
- 当前开发版本 Python 3.13。
如果本地缺少其中一个解释器,不能把单版本结果描述成完整支持矩阵;应在可复现 CI 或受控环境补齐后再创建 tag。 本文不新增 tox、nox、CI provider 或容器配置。若现有环境无法完成双版本验证,报告阻塞而不是降低声明。
8. 消费项目试用边界
通过第 7 节只证明“库可以被安装并按契约运行”,不证明“某组词典参数适合目标项目”。
第一个调用项目应先做只读或影子试用:保存提议和审计信息,由人复核后再决定是否应用。至少观察:
- 正确合并与错误合并;
- 本应合并但被保留的候选;
JOINED、HYPHENATED、SPACED的选择分布;- 按段落、标题、列表和引用拆分的行为;
- 词典歧义、结构
UNKNOWN、代码和表格排除; - 项目专名和自然连字符是否需要
KeepLineJoinRule。
具体样本范围、标注方法、指标、接受阈值、输出目录和真实数据权限必须在调用项目或新的实验 design 中确认。
本文不授权读取 /home/lihaoze/gov_test_data,也不授权修改任何调用项目。
9. 风险与代价
SPELLCHECKER能力不对称: 使用内置语言词典时必须显式忽略大小写;需要精确大小写的项目应使用其他 backend 或等待新的词典设计。- 真实 extras 增加验收成本:
wordfreqwheel 和传递依赖较大,但不能为了节省安装时间继续跳过发布关键路径。 - GitHub 可用性与权限: direct reference 需要 Git 和相应仓库权限;Release wheel 也受仓库可见性和 GitHub 可用性约束。
- Release 产物一致性: tag、Release 和 wheel 来自不同操作步骤,必须用提交哈希、版本元数据和 SHA-256 防止 上传错误构建物。
0.3.0仍是预览契约: 其他项目必须固定版本,不能跟随分支头自动升级。- 双 Python 版本可能受环境限制: 缺少最低版本验证时,tag 会被阻塞。
- 合成测试不代表领域准确率: 即使全部 backend 测试通过,词典仍会漏掉专名、新词并误判自然连字符。
- 保守失败关闭降低覆盖率: 这是第一版为了正文保真接受的代价,不用放宽块扫描来追求漂亮数字。
10. 实施与验收范围
本文获批后授权:
- 修改
src/mdpolish/modifiers/mapped_line_join.py,增加SPELLCHECKER组合验证并把修改器版本更新到2.0.1; - 扩展
tests/test_mapped_line_join.py,覆盖真实 backend、明确错误和版本记录; - 把
pyproject.toml包版本更新到0.3.0,不改变已批准 extras 的依赖集合; - 更新根 README 的当前版本、自动词典示例、交付边界和实际验证结果;
- 在临时目录构建和安装 wheel,运行第 7 节验证;
- 只读检查
AGENTS.md与CLAUDE.md镜像、Git diff、工作区状态和最终提交候选范围。
本文获批后仍不授权:
- 修改已冻结的
0009; - 修改
_text_ranges.py、其他 modifier、其他 Wiki 文档或调用项目; - 读取或复制真实材料;
- 新增默认规则、CLI、profile、artifact 目录、CI 配置或第三方 backend;
- 提交、创建
v0.3.0tag、push、创建 GitHub Release、上传 wheel 或创建 PR。
实施完成的最终报告必须分别给出核心环境、extras 环境、Python 版本和 wheel smoke test 的真实输出。任何必需环境未验证时, 明确写“未验证”或报告阻塞,不能用合成 adapter 测试替代真实 backend 结果。