ekp/.phrase/phases/phase-kp-overhaul-20260726/HANDOFF.md
Kinneyzhang fd1210e557 fix: close repository audit findings
Complete task002 through task016 across cache correctness, editor lifecycle, C boundaries, build and release governance, dictionary provenance, performance, interactive workflows, tests, documentation, and final cleanup.
2026-07-28 22:43:10 +08:00

191 lines
12 KiB
Markdown
Raw Permalink 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.

# KP 算法系统性优化 — 进度交接文档
> **2026-07-28 superseded note:** 本文的“未完成事项”保留为 2026-07-26
> 历史快照;后续审计、整改和验证状态以
> `task_repository_audit_20260728.md` 及 `.phrase/docs/ISSUES.md`
> 为准。
>
> 阶段:phase-kp-overhaul-20260726
> 状态:**已完成并提交**——分支 `kp-overhaul`(基于 main@11437cb),
> 合并到 main:`git checkout main && git merge kp-overhaul`
> 审查状态:自查 + 300 例随机性质测试已完成(0 失败);多智能体 workflow 审查仍可选(见"未完成事项")
> 本文档面向下一次会话/贡献者,保证无缝衔接。
## 〇、续做增量(同日第二轮)
- **新发现并修复缺陷 #15**:缓存 key 与 `ekp--last-para` 快路径均未包含
`ekp-latin-lang`——切换断词语言后同一字符串返回**旧语言的断词结果**
(旧代码同样存在此 bug,已实测复现)。修复:key 与快路径都纳入语言;
新增回归测试 `ekp-test-para-cache-tracks-language`。测试总数 35 → **36**,
全部通过;byte-compile 仍零警告。
- **300 例随机性质测试通过(0 失败)**:随机中西混排/CJK 标点/ZWSP/双空格/
超长词 × 随机宽度 1300px,断言 ① C 与 elisp 输出逐字节一致 ② 内容零丢失
③ 代价有限 ④ 不报错。脚本已固化为 tests/ekp-fuzz.el(确定性 LCG 种子 42,可复现;
需 C 模块,单独运行:emacs -Q --batch -L . -l tests/ekp-fuzz.el)。
## 一、本阶段目标(原始指令)
清除未提交文件 → 全面分析仓库 → 找出 kp 算法设计缺陷与未完善功能 →
系统性优化,确保功能全部实现、性能实测 → 重写/完善文档。
## 二、提交内容
以下改动已在 `kp-overhaul` 分支提交(refactor! 单提交,含本文档):
```
M ekp.el # 核心重写:DP、缓存、参数、渲染、C 桥接
M ekp-utils.el # 字体检测 batch 回退、全角/组合字符修复、删 Rust 死代码
M ekp-hyphen.el # 仅 docstring 修正
M ekp_c/ekp_kp.c # 两遍紧急策略、badness 封顶、空格数组、参数化 penalties
M ekp_c/ekp.c # API v1.1:break-with-arrays 11 参、set-penalties 4-6 参
M ekp_c/ekp_module.h # 版本 1.1、结构体新字段
M tests/ekp-tests.el # 全新 ERT 套件(36 个测试)
A tests/ekp-bench.el # 基准脚本
A tests/ekp-demo.el # 交互式 demo(从旧 tests 迁移)
A tests/run-tests.sh # 一键跑测试
A tests/ekp-fuzz.el # 300 例随机性质测试(需 C 模块,单独运行)
M readme.md / readme_zh.md / DEVELOPER.md / DEVELOPER_ZH.md / ekp_c/README.md
```
`ekp_c/ekp.dylib` 已用新源码重新编译(版本 1.1,gitignore 忽略编译产物)。
会话开始时已按指令 `git clean -fd` 清除了全部 Syncthing sync-conflict 垃圾文件。
**建议提交信息**(Conventional Commits,单提交或按 fix/perf/test/docs 拆分):
```
refactor!: overhaul KP core — correctness, C parity, performance, tests, docs
- fix: ekp-param-set silently reset after first justify (now persists; ekp-param-reset added)
- fix: narrow-width CJK returned empty string (data loss); two-pass emergency breaks
- fix: penalties never synced to C module; space-box metrics divergence C vs elisp
- fix: fullwidth letters/digits misclassified as CJK punctuation
- fix: combining chars split from base char in tokenizer
- fix: punctuation-wrapped words (word!/(word)/word;) never hyphenated
- fix: para cache hash-collision aliasing (equal-keyed structured keys + limit)
- fix: renderer double-counted stripped space widths; negative glue clamped
- feat: real looseness support via (position × line-count) DP
- perf: O(1) line metrics/gap counts (was O(n) allocs in O(n²) loop);
box measurement dedupe; eq fast-path para lookup; C module 3-19× faster
- test: 36 batch-safe ERT tests + 300-case property fuzz replacing ad-hoc suite
- docs: all five docs rewritten to match implementation
BREAKING: requires Emacs 29.1+; C module must be rebuilt (v1.1, arity changes);
ekp-threshold-factor / ekp-flagged-penalty / ekp-forced-break-penalty removed;
Rust module stubs removed.
```
## 三、已完成工作(按类别)
### 1. 实测确认并修复的正确性缺陷(elisp)
| # | 缺陷 | 修复 |
|---|------|------|
| 1 | `ekp-param-set` 一次性失效:第二次排版起用户参数被静默重置 | 显式参数持久化(`ekp--params-explicit`),新增 `ekp-param-reset`;auto 模式按字符串派生 |
| 2 | 超窄宽度 CJK 整段返回空串(数据丢失);超长不可断词产生负宽 glue | 两遍 DP:严格遍 + 紧急单盒断行遍(仅在段尾不可达时);glue 钳制 ≥0 |
| 3 | `flagged-positions` 死代码(从未填充)、`ekp-threshold-factor` 剪枝语义可疑 | 连同 `ekp-flagged-penalty`/`ekp-forced-break-penalty` 一并删除 |
| 4 | para 缓存用 sxhash 整数 key,碰撞会串段 | `equal` 结构化 key(内容+属性区间+字体+参数或 `auto`)+ `ekp-para-cache-limit`(256) |
| 5 | 全角字母/数字()被当标点附着到前字 | `ekp-cjk-fw-punct-p` 排除 FF10-19/FF21-3A/FF41-5A |
| 6 | 组合字符(café NFD)被当空格拆成独立 box | 零宽附着类(Mn/Mc/Me、ZWJ/ZWNJ、变体选择符)并入前文;ZWSP 仍作断点 |
| 7 | `word!`、`(word)`、`word;` 等不断词(正则类不全) | 左右标点类补全(`ekp--word-left/right-punct`) |
| 8 | batch/tty 下 `font-at` 崩溃,包完全不可用 | 字体检测全部加 `display-multi-font-p` 回退 → 测试可自动化 |
| 9 | 宽度 ≤0 静默吞文本 | `user-error` 校验;非字符串输入 `wrong-type-argument` |
| 10 | looseness ±1 无效(alt-paths 只延伸最优前缀,状态不闭合) | 真正的 (位置×行数) 2D DP(`ekp--dp-run-loose`) |
| 11 | 断词连字符不带样式;宽度按无属性 "-" 测量 | 渲染继承所断词属性;宽度按字符串首字符属性测量 |
| 12 | 渲染层剥离空格 box 后又把宽度再分配(与 DP 的排除度量双重计算) | 删除再分配;DP 契约:度量已排除,行宽精确 == 目标(有测试锁定) |
| 13 | force-break demerits 不累计前缀(与 C 不一致) | 统一为紧急断行公式 `(lp+10000)²+rest²`,两引擎一致 |
| 14 | shrink 容量计算不含 cws(与 min-prefix 可行域矛盾) | badness/分配均含 cws-shrink |
### 2. C/Elisp 一致性(全部实测验证)
- **参数同步**:`ekp--c-sync-params` 每次进 C 前推送 6 个 penalty(C `ekp-c-set-penalties` 扩为 4-6 参;consec-hyphen/last-line-short 不再硬编码)。
- **badness 封顶**:C 侧超 10000 曾变 `EKP_INFINITY`(断点被丢),现与 elisp 一致封顶 10000。
- **空格 box 度量**:新增 `lead-spaces`/`trail-spaces` 数组(n+1)传给 C;`ekp-c-break-with-arrays` 9→11 参,batch 向量 9→11 元素。
- **两遍紧急策略**:C 与 elisp 完全相同(严格遍 → 不可达时紧急遍)。
- **版本门禁**:模块版本 1.1;`ekp-c-module-load` 拒绝旧模块并回落 elisp(`ekp-c-module-required-version`)。
- **looseness ≠ 0 时自动绕过 C**(`ekp--c-available-p`)。
- **验证结果**:6 个测试文件 × 5 宽度 = 30/30 输出逐字节一致;penalty 极值下同样一致。
### 3. 性能(实测,batch Emacs 30.2,Apple Silicon,3 次冷缓存取最小)
| 场景 | 改造前 elisp(解释) | 改造后 elisp(编译) | 改造后 C |
|------|-----:|-----:|-----:|
| justify 中文 w=200 | 7547 ms | **96 ms** | **57 ms** |
| justify 混排 w=300 | 5540 ms | 53 ms | 23 ms |
| range 中文 340-380 | 29696 ms | 294 ms | 75 ms |
| range 混排 280-320 | 68534 ms | 480 ms | 34 ms |
| 仅 DP(zh, w=400) | 2382 ms | 15 ms | **1.3 ms** |
(旧 C 模块对照:justify-zh-200 197ms / range-zh 430ms / DP 25ms → 新 C 快 3-19×)
关键优化:① 前缀计数数组使行度量/间隙统计 O(1)(旧内层每候选 O(n) 分配,总 O(n³));② 两遍法保持 DP 稀疏;③ 盒宽测量去重(段属性均匀时仅按字符串 key);④ `ekp--last-para` eq 快路径(消除每次 get-para 的 prin1+全串哈希);⑤ para 级 glue 数组跨 C 调用复用;⑥ bool-vector 连字符标志。
基线/复现脚本:`tests/ekp-bench.el`(改造前基线数字已录入 DEVELOPER*.md §9)。
### 4. 测试(tests/ekp-tests.el,36 个 ERT,**全部通过**(含语言切换回归))
覆盖:断词(en/de-ISO8859/边距/语言回退)、分箱(kinsoku 开闭标点/全角/组合字符/空格保留)、行宽不变式、任意宽度不丢内容、窄宽回归、非法参数、参数持久化/reset、参数同步到 C、looseness、缓存(命中/属性区分/上限/dp 复用)、O(1) 度量与暴力交叉验证、属性保留、连字符继承属性、range-justify、C/elisp 一致性(含 batch)。C 模块未编译时相关测试自动 skip。
运行:`tests/run-tests.sh /Applications/Emacs.app/Contents/MacOS/Emacs`
### 5. 文档(全部重写,与实现逐条对齐)
readme.md / readme_zh.md(用户指南 + 真实性能表 + 已知限制)、
DEVELOPER.md / DEVELOPER_ZH.md(五阶段管线、数据结构、demerits 公式与
TeX 差异、两遍策略、C 集成、基准方法学)、ekp_c/README.md(修正了
"wavefront 并行"“zero copy" 等与实现不符的旧说法;明确 `ekp-c-break-lines`
为实验路径)。**Package-Requires 已改为 Emacs 29.1**(string-pixel-width
/ object-intervals 实际要求;旧标注 27.1 不真实)。
### 6. 死代码清理
ekp-utils.el 的 Rust 模块支持(ekp_rust 目录不存在)已删除;
process 回调的 eval 式 lambda 改为词法闭包;byte-compile 零警告。
## 四、未完成事项(下次会话优先处理)
1. **多智能体对抗审查(可选)**:因会话限额(21:50 Asia/Shanghai 重置)
未能以 workflow 形式执行;已用两项替代手段覆盖主要风险:
① 针对脚本 prompt 中列出的重点自查项逐项人工核查——eq 快路径过期
(发现并修复了语言维度的真实 bug,见"续做增量")、C 空格数组索引
(lead/trail 均 n+1 元素,i<n / kn 界内)、rest²/penalty² 均以 double
计算无 int32 溢出;② 300 例随机性质测试 0 失败如仍需 workflow 审查,
脚本已存盘:
`~/.claude/projects/-Users-geekinney-IPARA-3-RESOURCES-emacs-config-github-emacs-kp-ekp-c/ffca1e3d-9bda-4b18-814c-e95d7a8222c5/workflows/scripts/ekp-final-review-wf_03fd954b-f7a.js`
(resumeFromRunId: `wf_03fd954b-f7a`),或直接 `/code-review`
2. **GUI 真实字体视觉验证**:batch 下全部验证通过;真实字体渲染建议用户在
图形 Emacs 里执行 `tests/ekp-demo.el` 中的注释示例
( `(ekp-demo-justify "zh" "en_US" "Cascadia Next SC" 666)`)。
3. **合并**:改动已提交到 `kp-overhaul` 分支;确认后合并到 main
(`git checkout main && git merge kp-overhaul`),如需推送再 `git push`
4. (可选后续)`ekp-justify-region` 之类的交互命令词典编码显式处理
(目前依赖 Emacs 自动检测,de_DE ISO-8859 已实测正确)。
## 五、快速接续命令
```bash
EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
REPO=~/IPARA/3-RESOURCES/emacs/config/github/emacs-kp
# 全量测试(36 个,约 25s,C 模块存在时含一致性测试)
$REPO/tests/run-tests.sh $EMACS
# 重建 C 模块(改 ekp_c/ 后必须;版本门禁 1.1)
cd $REPO/ekp_c && make clean && make
# 基准(elisp / C 两引擎)
$EMACS -Q --batch -L $REPO --eval '(setq ekp-use-c-module nil)' -l $REPO/tests/ekp-bench.el
$EMACS -Q --batch -L $REPO --eval '(progn (require (quote ekp)) (ekp-c-module-load))' -l $REPO/tests/ekp-bench.el
```
## 六、关键设计契约(改动任何一侧都要维护)
1. **两引擎逐字节一致**:改 demerits/度量公式必须同时改 `ekp--dp-run-1d`
`ekp_c/ekp_kp.c` `dp_process_position`,并跑一致性测试
2. **DP 与渲染的空格契约**:DP 度量排除行首(i>0)/行尾空格串
(lead/trail-spaces 数组),渲染剥离同一批 box 且**不再**补偿宽度。
3. **紧急断行只在第二遍**:存在合法排版时结果必须是纯 K-P 最优。
4. **`ekp--last-para` 失效点**:任何影响 para 内容的全局状态变化
(参数 apply/reset、clear-caches)都必须置 nil。
5. C 模块 API 变化必须递增 `EKP_VERSION_MINOR` 并同步
`ekp-c-module-required-version`