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

12 KiB
Raw Blame History

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 / k≤n 界内)、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 已实测正确)。

五、快速接续命令

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-1dekp_c/ekp_kp.cdp_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