ekp/Docs/REPOSITORY_AUDIT_20260728.md
Kinneyzhang 83352c4571 feat!: add source-clean live buffer layout
Replace the physical ekp-region renderer with ekp-buffer text-property projection backed by shared semantic layout plans and stable live-edit transactions.

Optimize the C-backed resize hot path, and add deterministic performance, ERT, GUI, fuzz, release, and documentation coverage for task017 through task032.

BREAKING CHANGE: require ekp-buffer instead of ekp-region and rename module-owned ekp-region-* settings to ekp-buffer-*.
2026-07-30 01:07:25 +08:00

571 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# emacs-kp 仓库系统审计
> 审计日期2026-07-28
> 审计基线:`main@29cef97`(本地 tag `v1.0.0`
> 范围Elisp 核心、C 动态模块、buffer/region 集成、测试、性能、兼容性、文档、发布与扩展边界
> 性质:问题盘点与方向判断,不包含运行代码修复
## 后续状态
- `task002` 已在审计后的工作树中解决 P1-01DP key 现在包含行宽、
looseness 与全部六个运行时代价参数,并按结构相等比较。
- `task003` 已继续解决 P2-01auto-CWS 默认值现在同时进入段落哈希键
与最近段落快路径;`issue001` 的两个缓存缺陷均已闭环。
- `task004` 已解决 P1-02保存从逻辑副本序列化文件系统错误、编码
错误与用户中断均不再改变显示 buffer`issue002` 已闭环。
- `task005` 已解决 P1-03/P2-06copy filter 可组合并恢复integration
生命周期由 auto mode 与实际 justified span 共同决定;`issue003`
已闭环。
- `task006` 已解决 P1-04测试 fixture 完整隔离配置parshape 用例
经过公开分派路径CI、可复现乱序和逐例全新进程入口共同阻断顺序
假绿,`issue004` 已闭环。
- `task007` 已解决 P2-05GUI 矩阵共享结构化结果与报告断言边界,
任一失败行在 batch 中退出 1强制失败负控 2/2真实 GUI 七场景
7/7`issue007` 已闭环。
- `task008` 已解决 P2-02C 1.6 在提取前验证单段/批量完整 schema
非法调用统一 signal `ekp-c-invalid-input`,有效 int32 输入使用
int64 中间量;模块 signal 不再被 Elisp 静默吞掉。
- `task009` 已解决 P2-03默认构建改为 portablenative/debug/
sanitize 需显式选择;交互命令以 argv 和 `default-directory` 启动
make含空格路径实测通过。`issue005` 已闭环。
- `task010` 已解决 P2-07 的仓库内边界CI action 与 package-lint
固定到完整提交Windows 运行 Elisp 基线,`.phrase` 改为可追踪事实
来源release gate 与远端/产物 checklist 分责。未执行 push、tag
变更或发布。
- `task011` 已解决 P2-0449 份词典固定来源路径与 SHA-256更新器
可在 macOS/Linux 重建相同输出4 份含斜杠/替换语法的词典明确失败
关闭,不再静默降级。缺少准确许可证据的 Sanskrit 文件已移除。
- `task012` 已按 benchmark 修复 nil 缓存与长文本构造热点;
`task013` 已补齐交互保护命令、反馈与 session-local 文档;
`task014` 已集中行边空格与 render-marker 规则归属。
- 最终独立审查新增的 P1-05 已由 `task016` 解决most-recent fast
path 与 hash cache 共享完整 `ekp--para-key`,同一字符串对象原地
修改排版属性也会失效;`issue010` 已闭环。
- `task015` 已完成终局清理与门禁:删除九个死 accessor 和依赖私有
substring-filter API 的调用;最终独立代码审查 `APPROVE`、架构审查
`CLEAR`,全部仓库内 issue/task 均已闭环。
- `task017` 已将当前编辑器集成模块直接改名为 `ekp-buffer.el`
下文的 `ekp-region.el``ekp-region-*` 仍保留为审计基线的历史
证据,不代表当前加载入口。
- 下文保留 `main@29cef97` 的原始证据与判断,不把历史问题改写成从未发生。
## 结论先行
emacs-kp 的算法地基是可靠的Elisp 掌握文本与字体事实C 只镜像动态规划热路径;严格遍/紧急遍保证困难输入不丢内容buffer 层用可逆布局标记保存逻辑文本测试覆盖了核心算法、C/Elisp parity、编辑器协议与真实 GUI 像素贴合。
审计基线最需要处理的不是重写算法,而是四个已经实证的边界缺陷:
1. 算法 penalty 改变后,既有 DP 缓存仍按旧参数返回结果。
2. 保存失败会让原本已排版的 buffer 留在未排版状态。
3. EKP 直接覆盖已有的 `filter-buffer-substring-function`,可能破坏其他 mode 的复制语义。
4. 一个 C 绕过测试依赖前序测试泄漏的全局状态,整套测试为绿但单独运行失败。
**【Taste Score】** 🟡 Acceptable
设计方向正确,关键算法有真实测试;但缓存键、保存事务和编辑器协议组合这几个边界还没有单一 owner。继续堆 fallback、watcher 或 hook 会放大问题,下一步应先修正状态模型与边界契约。
**【Fatal Issues】**
- `ekp--dp-key` 没有覆盖所有影响 demerits 的参数,配置修改会静默失效。
- save/copy 集成不是可组合、可回滚的事务边界。
- 测试 fixture 没有恢复完整的全局配置,已经产生假绿。
**【Improvement Direction】**
- 先把“影响缓存结果的完整输入”变成显式键。
- 把保存、复制、isearch 视为编辑器协议边界,做可组合和失败恢复。
- 让每个测试独立运行,并让 GUI/本地 C 边界失败能可靠返回非零。
- C 模块继续只做 DP除非 benchmark 证明跨边界复制是主要瓶颈,不要把字体、分词或渲染语义下沉到 C。
## 1. 审计方法与验证基线
### 1.1 读取范围
- 核心:`ekp.el`、`ekp-utils.el`、`ekp-hyphen.el`
- 编辑器集成:`ekp-region.el`
- C 模块:`ekp_c/*.c`、`ekp_c/*.h`、`ekp_c/Makefile`
- 测试与演示:`tests/*.el`、`tests/run-tests.sh`
- 文档与发布README、DEVELOPER、CHANGELOG、CONTRIBUTING、CI、词典更新脚本、`.phrase`
- 历史决策:`.phrase/phases/phase-kp-overhaul-20260726/HANDOFF.md`、`.phrase/phases/phase-p1p2-20260726/NOTES.md`
### 1.2 新鲜验证
| 验证 | 结果 | 说明 |
|---|---|---|
| 默认 ERT 入口 | PASS94/94 | Emacs 30.2,加载 C 1.5 |
| C/Elisp fuzz | PASS300/300 | 字节级一致0 failure |
| 临时干净副本 byte-compile | PASS | `byte-compile-error-on-warn=t` |
| 临时干净副本 checkdoc | PASS | 四个发布 Elisp 文件无输出 |
| 临时干净副本 C release build | PASS | C11、`-Wall -Wextra -Wpedantic` 无警告 |
| 临时干净副本 ERT + fuzz | PASS | 新构建 C 模块94/94 + 300/300 |
| GUI 像素矩阵 | PASS7/7 | base、缩放、face remap、无 fringe、窄窗均无 overflow |
| 单测独立负控 | FAIL符合审计预期 | `ekp-test-parshape-bypasses-c` 单独加载 C 后失败,确认测试顺序依赖 |
GUI 数值矩阵的七种场景均满足 `widest == target``over=0`。干净截图的第二次抓取被 macOS 录屏权限提示覆盖;未批准系统权限,因此矩阵输出可作为数值证据,但本次不把截图当成无噪声视觉证据。
### 1.3 未验证项
- 未运行本地 `package-lint`;仓库 CI 会从 MELPA 动态安装它,但本机没有固定版本。
- 未运行 Windows 构建与 GUI 验证。
- 未验证远端 GitHub Actions 的运行结果;本地分支尚未推到 `origin/main`
- C 极值整数风险未在 macOS sanitizer 下动态复现:`make DEBUG=1` 能构建,但当前系统因 sanitizer runtime code-signing policy 拒绝加载该模块。
## 2. 当前架构
```text
字符串 API
ekp-pixel-justify / ekp-pixel-range-justify
├─ ekp-utils分箱、字体与像素测量
├─ ekp-hyphenLiang pattern 编译与断词
├─ ekp.elekp-para、前缀数组、缓存
├─ DP
│ ├─ Elisp 1D / 2D
│ └─ C 1D15 字段边界,批量按段落并行)
└─ rendererglue、soft break、soft hyphen、hidden payload
buffer API
ekp-region.el
├─ justify / unjustify
├─ auto mode宽度跟随、dirty 段落、lazy chunk
└─ 编辑器协议save、isearch、kill/yank、undo、modified state
```
### 2.1 扎实的部分
1. **Elisp 是语义事实源C 只做纯 DP。**
证据:`ekp.el:1733-1749` 构造 15 字段输入;`ekp_c/ekp.c:590-611` 暴露对应 API`ekp_c/README.md` 明确字体相关数据留在 Elisp。
判断:这是正确边界。不要重新把分词、字体测量或渲染下沉到 C。
2. **严格遍 → 紧急遍的失败模型一致。**
证据:`ekp.el:1173-1202` 与 `ekp_c/ekp_kp.c:501-537`fuzz 300/300。
判断:困难输入不丢内容的核心承诺有代码与性质测试双重保护。
3. **布局是可逆结构,不是破坏性格式化。**
证据:`ekp.el:2180-2191` 定义四类布局标记;`ekp-region.el:402-445` 做结构反转region 测试覆盖 roundtrip、save、isearch、kill、undo。
判断:模型有价值,但所有编辑器协议都必须显式适配,生命周期边界必须更硬。
4. **CI 覆盖面优于一般小型 Elisp 包。**
证据:`.github/workflows/ci.yml` 覆盖 Emacs 29.1/30.1/snapshot、C parity、fuzz、ASan/UBSan、macOS。
判断测试类型正确主要缺口是可复现性、Windows 和失败退出契约。
## 3. 按优先级排序的问题
优先级含义:
- P0数据丢失、安全或默认路径不可用立即阻断发布
- P1已实证的核心行为错误或测试可信度缺陷
- P2重要边界风险、兼容性/维护性缺陷或已知能力落差
- P3优化与体验机会应由基准或真实需求驱动
本次未发现 P0。
### P1-01 算法参数不在 DP 缓存键中,修改后静默复用旧结果
- **Evidence**
- `ekp--dp-key` 只包含 `line-pixel` 和非零 `ekp-looseness``ekp.el:1184-1190`。
- demerits 直接读取 `ekp-line-penalty`、`ekp-hyphen-penalty`、`ekp-adjacent-fitness-penalty`、`ekp-consecutive-hyphen-penalty` 等全局值:`ekp.el:1080-1125`。
- README 声称所有算法参数对两个引擎生效。
- 实测同一段落/宽度先用 hyphen penalty 0再改为 1000000缓存对象仍相同清缓存后断点改变cost 从 `100226389.0` 变为 `601214639.0`
- **Inference**
- 问题 owner 是 DP 缓存键,不是 C 参数同步。C 同步只在真正计算时运行,命中缓存时根本不会进入同步或 DP。
- **Impact**
- 用户通过 `setq` 或 Custom 调整排版质量参数时,界面可能看起来“设置无效”,直到显式 `ekp-clear-caches` 或段落键变化。
- **Direction**
- 把所有影响 DP 结果的参数收敛到一个不可变的 DP key不要再靠零散 watcher 猜测失效点。
- 回归测试必须在不清缓存的情况下修改每一类参数,并断言结果或 cost 与 fresh computation 一致。
- **Confidence**High
### P1-02 保存失败会留下未排版 buffer 和 stale save state
> 后续状态2026-07-28已由 `task004` 修复。保存不再执行
> unjustify/rejustify 事务,而是在 `write-region` annotation 边界写入
> 隐藏逻辑副本;成功、文件系统失败、编码失败和 `quit` 均有回归覆盖。
- **Evidence**
- `before-save-hook` 先把全部 justified spans 反转:`ekp-region.el:461-479`。
- 只有 `after-save-hook` 会重排并清空 `ekp-region--save-state``ekp-region.el:481-492`。
- 负路径实测:让 visited file 的目标目录消失后调用 `save-buffer`,得到 `still-justified=nil`、`save-state=t`。
- **Inference**
- 成功路径测试不足以证明保存事务;底层写入报错或保存被中断时,`after-save-hook` 不会提供 finally 语义。
- **Impact**
- README 的“保存逻辑文本,同时屏幕保持排版态”承诺在失败路径不成立;后续保存还可能携带陈旧 marker。
- **Direction**
- 先定义保存事务的 owner 和失败恢复路径,再实现;恢复必须无条件执行,写盘失败仍要回到原显示态。
- 增加写盘失败、编码失败、用户中断三类负路径测试。
- **Confidence**High
### P1-03 覆盖已有 copy filter破坏其他 mode/user 的复制语义
> 后续状态2026-07-28已由 `task005` 修复。测试覆盖 local/global
> prior filter、复制、DELETE kill、手动 unjustify 与 mode 关闭。
- **Evidence**
- 安装集成时直接 `setq-local filter-buffer-substring-function``ekp-region.el:378-389`。
- 移除时只删除 EKP 自己的当前值,没有保存或恢复前一个 filter`ekp-region.el:391-399`。
- 负路径实测:预设 filter 为复制内容加 `PRE:`justify 后该前缀消失。
- **Inference**
- `filter-buffer-substring-function` 是单槽协议EKP 必须显式组合或恢复前一个 owner直接覆盖不是可组合集成。
- **Impact**
- 在已有复制过滤逻辑的 major mode 中kill/copy 可能丢失 mode 定义的语义。
- **Direction**
- 记录前一个 buffer-local filter明确调用顺序并在最后一个 justified span 消失时恢复。
- 测试已有 filter、EKP filter、删除式 kill 三条真实 public path。
- **Confidence**High
### P1-04 测试顺序依赖制造假绿
> **Resolved by task006 (2026-07-28):** fixture 使用动态绑定恢复全部
> tunableparshape 用例绑定真实 `ekp-parshape` 并驱动公开 formatter。
> 108/108 通过可复现乱序,全部 108 个测试也逐个在全新 Emacs 进程
> 中通过。
- **Evidence**
- `ekp-test-parshape-bypasses-c` 的名称和 docstring 测 parshape代码却绑定 `ekp-first-line-indent``tests/ekp-tests.el:225-228`。
- C 1.5 已支持 first-line indent真正绕过 C 的条件是 `ekp-parshape` 非 nil`ekp.el:1667-1675`。
- `ekp-test-params-affect-c-module` 在结束时把全局 `ekp-use-c-module` 留为 nil`tests/ekp-tests.el:434-448`。
- 全套 94/94单独加载 C 后运行该测试则 1/1 FAIL。
- **Inference**
- 默认字母序恰好隐藏了错误测试;当前 fixture 只恢复部分 penalty没有恢复引擎和全部样式状态。
- **Impact**
- 测试套件可能继续掩盖其他全局状态泄漏,绿灯可信度下降。
- **Direction**
- 让 clean-state fixture 保存/恢复所有全局 tunable该测试绑定真实 `ekp-parshape`
- CI 增加 isolated 或 randomized-order lane至少覆盖依赖全局状态的测试。
- **Confidence**High
### P1-05 同一字符串对象的属性修改绕过完整段落 key
> 后续状态2026-07-28已由 `task016` 修复。most-recent slot
> 保存并比较完整 `ekp--para-key`,删除六个补偿性 style watcher
> CJK 与 Latin-with-space 原地属性修改回归、专项缓存矩阵及完整
> default/permuted/isolated ERT 均通过。
- **Evidence**
- `ekp--para-key` 包含过滤后的 text-property intervals。
- `ekp--get-para` 的 most-recent fast path 只比较对象 identity、语言、
width context 与 spacing signature。
- 独立审查实测warm `"文中"` 后在原对象加入 `ekp-no-break`,下一次
lookup 仍返回原 paragraph清缓存后的 fresh paragraph 不允许相同
break。
- **Inference**
- 这不是 hash collision而是第二套不完整 identity 规则绕开了唯一
完整 key。
- **Direction**
- 让 fast path 与 hash cache 共享 `ekp--para-key`,删除并行维护的
partial signature/watchers。
- 用 CJK 与 Latin-with-space 两类原地 property mutation 锁定 public
paragraph resolver。
- **Confidence**High
### P2-01 自动 CJK stretch 默认值不在段落缓存键中
> 后续状态2026-07-28已由 `task003` 修复。新增回归分别覆盖
> 段落哈希键、`ekp--last-para` 快路径及相同签名真实命中;修复没有
> 新增 watcher。
- **Evidence**
- `ekp-default-cws-stretch-pixel` 定义于 `ekp.el:94-97`,在 auto 参数计算中使用:`ekp.el:329-336`。
- auto 模式的 para key 只写入符号 `auto``ekp.el:740-746`。
- 实测把默认值从 2 改为 9 后,返回同一个 para缓存中的 `:cws-stretch` 仍为 2。
- **Inference**
- 这是 P1-01 同一类模型缺陷:缓存键没有完整表达决定结果的输入。
- **Direction**
- 将该值纳入 auto 参数 snapshot/key测试修改后无需手动清缓存。
- **Confidence**High
### P2-02 C API 的输入契约、错误语义与数值范围不闭合
> **Resolved by task008 (2026-07-28):** C 1.6 对 15 字段 shape、长度、
> 类型和 int32 范围做整体验证,非法输入使用专用 conditionDP 中间
> 行宽/前缀差/剩余空间改为 int64。六项边界红测 0/6 → 6/6公开
> dispatcher error 传播 0/1 → 1/1完整 ERT 116/116、fuzz 300/300。
- **Evidence**
- 文档说 bad argument 返回 nil直接传入长度错误的 vector 实测得到 `args-out-of-range`
- `ekp_c/ekp.c:176-237`、`323-374` 在多次 `vec_get`/`extract_integer` 后才统一检查 pending non-local exit。
- 输入先 clamp 到 `int32_t`,随后在 `ekp_c/ekp_kp.c:287-304` 继续做有符号加减。
- **Inference**
- 正常 `ekp.el` 调用会捕获 signal 并回落 Elisp因此常规文本不受影响但直接 C API 的文档契约不真实。
- `INT32_MAX` 附近的 width/protrusion/prefix 组合可能触发 C signed overflow。真实字体不接近此范围但公开 API 没有限制它。
- **Direction**
- 先校验 15 字段 arity、`n/n+1` 数组长度、排序位置和数值范围,再分配/提取。
- 统一选择“明确 signal”或“返回 nil”不要文档与实现各说一套。
- 内部度量改为 `int64_t` 或 checked/saturating arithmeticAPI 边缘再收窄。
- 增加 malformed vectors、极值、batch >1024 的 C 边界测试。
- **Confidence**Medium-High
### P2-03 C 构建入口对路径、CPU 和调试环境过度假设
> **Resolved by task009 (2026-07-28):** `PROFILE=portable` 成为默认,
> native/debug/sanitize 独立;交互构建不经过 shell。两项红测
> 0/2 → 2/2四 profile 均零警告,真实交互构建与含空格副本构建
> 均成功。
- **Evidence**
- `ekp-c-module-build` 拼接 `cd %s && make` 交给 shell路径没有 quote`ekp-utils.el:405-419`。
- 默认 Makefile 使用 `-march=native -flto``ekp_c/Makefile:33-36`。
- README 把普通 `make` 描述为通用入口。
- 当前 macOS 上 `make DEBUG=1` 构建成功,但加载时因 sanitizer runtime code-signing policy 失败。
- **Inference**
- 安装路径含空格或 shell 元字符时,交互 build 会失败,特殊路径还形成命令注入面。
- `-march=native` 适合本机 benchmark不适合分发预编译产物。
- **Direction**
- 用进程 `default-directory` + 参数向量调用 `make`,不要启动 shell 执行 `cd`
- portable 作为默认 profile`NATIVE=1`、`LTO=1`、`DEBUG=1` 显式选择。
- 文档分别说明 Linux sanitizer 与 macOS 调试模块的 runtime/code-signing 条件。
- **Confidence**High
### P2-04 词典覆盖与 parser 能力被文档高估
> 后续状态2026-07-28已由 `task011` 闭环。进一步核对发现除
> 2399 条三语言 replacement rules 外Esperanto 还有 4 条斜杠模式。
> 当前固定宽度 DP 无法表达“仅断点胜出时替换文字与宽度”,因此四份
> 词典统一 signal 而非产生错误断词49 项 manifest/upstream gate 与
> 双导出一致性均通过。
- **Evidence**
- 仓库实际有 50 个 `hyph_*.dic`,不是 README 所写的 70+。
- `ekp-hyphen--compile` 跳过所有含 `/` 的 alternative patterns`ekp-hyphen.el:114-153`。
- 仓库中 3 个词典含 2399 条非注释 alternative pattern主要来自 Hungarian也涉及 Catalan/Albanian。
- 50 个词典中有 20 个没有同名 `README_hyph_*.txt`README 却要求“见每个词典的 README”。
- `dictionaries/update.sh` 拉取移动中的 LibreOffice HEAD没有 pinned commit、manifest、checksum、错误退出或跨平台处理。
- **Inference**
- “任意 bundled dictionary 都完整工作”不成立。普通 Liang patterns 可用,但 replacement/alternative 语义被有意忽略,相关语言的断词质量会下降。
- 许可证不一定不合规,但仅凭当前仓库无法完整核对每个词典的来源版本与许可文件。
- **Direction**
- 先诚实声明 parser 支持的语法子集。
- 为 Hungarian/Catalan/Albanian 加 golden words再决定是否值得实现 replacement semantics。
- 更新脚本必须 pin upstream commit生成来源/许可/checksum manifest并在 macOS/Linux 都可运行。
- **Confidence**High
### P2-05 GUI 验证工具有结果,但不是可靠的发布门禁
> **Resolved by task007 (2026-07-28):** 单场景返回结构化结果,统一
> report 在 batch 任一失败时退出 1。README 明确工具加载边界;强制
> 失败/成功负控 2/2真实 GUI 矩阵 7/7干净全屏单窗口截图已复核。
- **Evidence**
- README 推荐 `M-x ekp-gui-verify`,命令实际位于 `tests/ekp-gui-verify.el`,普通 `(require 'ekp-region)` 不会定义它。
- `ekp-gui-verify-matrix` 在 noninteractive 下只打印 `FAIL` 文本,不会以非零状态退出:`tests/ekp-gui-verify.el:184-191`。
- 本次真实 GUI 矩阵 7/7 PASS。
- **Inference**
- 工具对人工诊断有价值,但当前无法直接成为 CI gate用户也可能按 README 执行一个尚未加载的命令。
- **Direction**
- 二选一:把单次诊断纳入发布包,或明确给出加载 tests 工具的命令。
- matrix 发现任何 FAIL 时必须 signal/exit 1表格只是报告不是判定。
- **Confidence**High
### P2-06 手动 unjustify 后集成 hook/filter 残留
> 后续状态2026-07-28已由 `task005` 修复。auto mode 外最后
> justified span 消失时,公共 unjustify 与外部删除路径都会卸载
> integrations内部 reflow/isearch 继续使用不改变生命周期的核心反转。
- **Evidence**
- `ekp-unjustify-region` 只反转文本属性:`ekp-region.el:402-445`。
- integrations 只在 minor mode 关闭路径移除:`ekp-region.el:928-938`。
- 实测全 buffer unjustify 后已经没有 justified span但 save/isearch hooks 和 local copy filter 仍存在。
- **Inference**
- 无 justified span 时多数 hook 会空跑,但 copy filter 仍占据协议槽;它与 P1-03 共享生命周期 owner。
- **Direction**
- 由“buffer 是否仍有 justified span / auto mode 是否开启”派生 integration 生命周期,不要让手动命令和 mode 各自维护一套真相。
- **Confidence**High
### P2-07 发布与文档治理没有闭环
> 后续状态2026-07-28仓库内治理已由 `task010` 闭环。静态 gate
> 检查 action SHA、Windows/ERT 路径、`.phrase`、package/changelog
> 与 C ABI 版本;远端 CI、tag 和 content-addressed artifact 由发布
> checklist 在实际发版时验证,本轮没有冒充已发布。
- **Evidence**
- 本地 `main``origin/main` ahead 31本地 `v1.0.0` 指向 HEAD远端没有该 tag。
- `CHANGELOG.md` 与 package header 已声明 1.0.0。
- `.phrase/``.gitignore` 忽略,但仓库协议又把它定义为需求/task/change/issue 的事实源。
- 审计前 README 的 C 版本、词典数量、测试数量、GUI 工具入口均与实现不一致。
- CI 使用 `purcell/setup-emacs@master`、运行时最新 MELPA `package-lint`、`macos-latest`,没有 Windows job。
- **Inference**
- 如果 GitHub `origin` 是正式发布面,则 1.0.0 尚未真正发布;如果不是,仓库缺少明确的发布状态说明。
- 被忽略的 `.phrase` 可以做本地过程数据,但不能同时作为可审计的版本化事实源。
- **Direction**
- 在 push 前增加 release gatecommit/tag/changelog/version/CI/checksum 一致。
- 明确 `.phrase` 是 tracked source of truth 还是 local workflow state只能选一个。
- pin CI action/工具版本,增加 Windows 构建 lane。
- **Confidence**High
### P3-01 tokenizer 与断词缓存还有局部性能债
> **Resolved by task012 (2026-07-28):** nil cache 使用显式 miss
> sentineltokenizer 与密集插入改为片段单次拼接。8,000 字符耗时
> 分别从 3.133 s 降到 1.100 s、从 0.945 s 降到 0.013 s完整 ERT
> 124/124、fuzz 300/300、warning-as-error 编译与 checkdoc 通过。
- **Evidence**
- `ekp-split-to-boxes` 在字符循环中反复 `concat``ekp-utils.el:253-316`。
- `ekp-hyphen-inserted` 每个断点重建字符串:`ekp-hyphen.el:216-224`。
- `ekp-hyphen--positions``(or (gethash ...) (puthash ...))`;缓存值为 nil 时无法区分 miss。对无 pattern 的 `qzxq` 连续查询两次,`ekp-hyphen--compute` 实际调用两次。
- **Inference**
- 极长 URL、长 token、重复 acronym 或大量零宽字符下会出现额外分配;正常段落不是当前最大瓶颈。
- **Direction**
- 先把 benchmark 加入这些退化输入,再做线性 builder 与显式 cache sentinel。
- **Confidence**High
### P3-02 核心函数与 `ekp-para` 承担了过多规则
> **Resolved by task014 (2026-07-28):** 既有 `ekp--dp-key` /
> `ekp--line-ideal-pixel` owner 保留;四处重复的行缘空格公式收敛到
> 一个内联纯规则renderer/region 的五项 marker 词汇表收敛到一个
> 常量。直接规则测试与既有 roundtrip/C parity 共同锁定边界,未拆
> 文件、未新增热循环分配。
- **Evidence**
- `ekp-para` 同时保存分箱、测宽、glue、禁则、悬挂、offset 和 DP cache`ekp.el:215-246`。
- `ekp--make-para`、1D/2D DP、renderer 都是长流程space/protrusion 公式还必须在 DP、C 重建和 renderer 三处保持一致。
- **Inference**
- 继续加 typography 选项时,最可能出现的错误不是算法本身,而是忘记更新 key、15 字段边界或渲染反变换。
- **Direction**
- 不拆新文件。先在 `ekp.el` 内把“cache signature”“line metrics”“render marker protocol”变成三个可直接测试的规则 owner。
- 只抽取能消除重复规则的纯函数;不要建立 helper ladder。
- **Confidence**High
### P3-03 交互入口与临时标记的产品语义不够清楚
> **Resolved by task013 (2026-07-28):** 四个保护命令均通过真实
> interactive/public formatter 路径测试minor mode 增加标准 EKP
> 菜单与 help命令反馈及双语文档明确属性仅在当前 buffer session
> 有效。未引入持久化格式或全局快捷键。
- **Evidence**
- minor mode keymap 只 remap `fill-paragraph``ekp-region.el:897-899`。
- no-break/verbatim 命令只写入普通 text property`ekp-region.el:568-595`;普通文件保存重开不会保留这些属性。
- **Inference**
- 用户难以发现 justify/unjustify/protect 操作,也可能误以为手工保护会持久化。
- **Direction**
- 先补 mode help、状态反馈和“仅当前 buffer session 有效”的文档;只有真实用户需要时再设计持久化语法映射。
- **Confidence**Medium
## 4. 优化与扩展路线
### 4.1 第一优先级:恢复行为可信度
1. 完整 DP cache signature。
2. 保存失败的无条件恢复。
3. copy filter 组合与 integrations 单一生命周期。
4. 测试 fixture 隔离、错误 parshape 测试修正、负路径测试。
这四项完成前,不建议扩大 typography 功能面。
### 4.2 第二优先级:硬化 C 与发布边界
1. 15 字段 schema preflight、`int64_t`/checked arithmetic。
2. portable/native/debug 三种构建 profile。
3. 可诊断的 C fallback用户路径仍回落但 debug/CI 能看到失败原因。
4. GUI matrix 非零退出、Windows job、固定 CI 依赖。
5. 词典来源/许可/checksum manifest。
### 4.3 性能方向:只做有基准支撑的工作
| 方向 | 当前判断 | 启动条件 |
|---|---|---|
| tokenizer 线性 builder | 小而明确 | 长 token benchmark 显示明显非线性 |
| nil-aware hyphen cache | 低成本 | 可直接修复并加计数测试 |
| C paragraph-handle API | 潜在高收益、高复杂度 | range search profile 证明 15 数组重复 marshal 是主要瓶颈 |
| 更多线程内并行 | 不建议 | 单段 DP 有前向依赖,当前按段落并行边界正确 |
| 把测宽/分词移入 C | 不建议 | 会拆散字体事实源并放大 parity 成本 |
### 4.4 可选产品扩展
1. **精确 width search 模式**
当前 ternary + local scan 明确不保证全局最优。可为小范围提供 exhaustive 选项,并保留当前快速默认。
2. **按段落选择断词语言**
当前 `ekp-latin-lang` 是全局单值。若真实文档有多语言段落,可增加 buffer-local resolver不要先做自动语言识别。
3. **可发布的诊断入口**
`ekp-diagnose` 与 GUI fit check 收敛成一个用户入口:快速检查当前 buffer需要完整矩阵时再加载 tests 工具。
4. **mode-native verbatim/no-break**
Org/Markdown 优先从语法/face 派生保护,不把瞬时 text property 当持久存储。其他 mode 通过一个 buffer-local predicate 接入。
5. **发布包与词典可选化**
词典占仓库约 6.1 MiB。若分发场景确有需要可提供 core + language packs没有安装/更新痛点前不要拆包。
### 4.5 明确不做
- 左缘悬挂与行中 glyph advance 压缩:当前 Emacs 显示模型不能可靠表达,已有文档说明。
- 把一个 coherent `ekp.el` 按“utils/common”标签拆成多个文件。
- 为了“以后也许需要”而新增协议层、adapter 或兼容 shim。
- 在没有 profile 数据前重写 C DP 或增加候选级并行。
## 5. 建议的验收门槛
### 核心行为
- 所有影响 DP 的参数修改后,不清缓存也与 fresh computation 一致。
- 保存成功、保存失败、用户中断后buffer 的显示态、逻辑文本、modified state 和 marker state 都正确。
- 已有 copy filter 与 EKP filter 组合后,两方语义都保留。
### 测试
- 每个 ERT 可单独运行。
- randomized order 通过。
- 现有 94 ERT、300 fuzz、C parity 全通过。
- GUI matrix 任一 FAIL 返回非零。
### C 边界
- malformed vector 不越界、不继续带 pending exit 调用 API。
- 数值上限有文档、有检查、有 sanitizer 覆盖。
- portable build 不含 `-march=native`native benchmark profile 单独启用。
### 发布
- package version、C ABI version、README、CHANGELOG、tag、远端 commit 对齐。
- CI action/tool 版本固定。
- 词典 manifest 能回答“来源 commit、文件 checksum、许可文件”。
## 6. Evidence / Inference / Unknown 总结
### Evidence
- 默认与 fresh-build 测试均 94/94fuzz 300/300GUI 数值矩阵 7/7。
- 四个 P1 问题均有代码路径与负控/独立复现。
- README/C ABI/test count/词典数量与仓库事实曾发生漂移。
- 本地 main/tag 与 origin 状态不一致。
### Inference
- C `int32_t` 极值会造成 signed overflow 风险,但常规字体输入不会接近该范围。
- alternative patterns 被忽略会降低相关语言质量;具体单词影响需要 golden corpus 定量。
- 交互可发现性不足与 text property 非持久化会造成用户困惑,尚无用户研究数据。
### Unknown
- Windows C 模块和 region UI 在当前版本是否完整通过。
- GitHub Actions 在尚未推送的 31 个本地提交上是否通过。
- 远端缺少 v1.0.0 是有意暂缓还是发布遗漏。
- 用户是否真正需要精确 width search、多语言 resolver 或 language packs。
## 7. 停止条件
本次任务在以下状态停止:
- 已覆盖代码、C、测试、GUI、文档、发布与扩展面
- 已把问题按证据和优先级写入文档;
- 已给出优化方向与“不做什么”;
- 未修改运行代码,也未推送/发布;
- 具体修复应从 P1-01 开始,每次只闭环一个原子问题。