Repository navigation
fix(change-quality): preserve CR and CRLF in Git paths - #8645
Conversation
Read Git path output as bytes and split NUL-framed records before decoding. This avoids universal-newline translation rewriting legal CR and CRLF filenames, and stops replacement decoding from collapsing distinct undecodable paths. The repository root read uses the same lossless path boundary and removes only Git's terminal LF. Tests cover untracked CR and CRLF names and a checkout directory ending in LF. Signed-off-by: ChengLong GU <88359731+gcl-coder@users.noreply.github.com>
Signed-off-by: gcl-coder <gcl-coder@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · max
本次评审对应提交 260936bb2261dab6d1b74d0d6c34cbb19cb5049a。
动机 / Motivation
运行 Change Quality 审核工作树的开发者和 Agent。仓库中有三个文件名只差回车、换行或两者组合时,现有读取会把三者显示成同一路径。只修改回车文件后,原先的收据仍然被判有效。同一组真实 Git 操作在新版本列出九条精确路径,审核证据能逐条对应。只改回车文件的内容,原收据即被拒绝。独立验证使用 macOS 上的 Git 2.56 和临时仓库。Windows 原生入口与已安装版本尚未验证。
改动思路 / Approach
Git 的路径输出已经带边界,直接沿用现有二进制 probe 就能保留记录。本次改动仅调整路径读取并补相应反例,现有指纹、验证和收据流程继续承接。 普通 commit ID 仍走文本读取。路径类输出使用现有二进制 Git probe,工作树根去掉最后一个 LF,路径列表先按 NUL 分帧再解码。两者保留路径原样,后续的 scope、文件哈希和 evidence ref 能引用同一个名字。
具体改动 / Changes
关键代码讲解
_git_path_text只去掉rev-parse --show-toplevel输出的终止 LF。根目录末尾的 CR、LF 和空格仍属于路径。 scope.py_name_only逐条严格解码git diff -z和git ls-files -z的输出。无效 UTF-8 走既有 CLI 错误结构。 scope.pybuild_change_quality_scope沿用四个来源与现有指纹。untracked 哈希打开 Git 报告的原文件,新增测试用真实 Git 对象构造特殊名称,也覆盖根目录和路径证据。 scope.py
按 loopx/capabilities/change_quality/README.md 在 3a5899f00cab97294c20459ac8508d16dc317492 的 Protocol 审核。Protocol-1 的四类来源全部覆盖。Protocol-2 仍投影路径,未增加内容读取。Protocol-5 的 record 接受精确 evidence refs。Protocol-6 的 verify 接受同一范围的收据,并拒绝编辑后的旧收据。 协议原文
对主干的风险 / Risks
同样的合成输入在主干只列出七条路径,三个 untracked 变体都被读取为 LF 文件。旧收据在 CR 文件改动后仍显示 valid。head 列出九条路径并返回 stale_receipt。结尾 CRLF 的根目录在主干被截短,head 可完成记录和校验。普通及默认关闭的对照保留相同指纹和状态。对不可解码的提交路径,head 返回清楚的结构化错误。此项收紧适用于主动运行 scope 的调用者。
语义与 CI 对齐
PASS,标准 premerge 的 6 个直接检查与 7 个 canary,实测 CLI 在真实 Git 中完成 prepare、record、verify 和 stale 收据拒绝,Ruff 通过。组合 pytest 初跑 95 项通过,1 项既有超时夹具在并发检查期间未观察到心跳。使用相同夹具单独重跑时,主干和 head 均通过清理断言。未读取远端 CI。
我的整体评价 / Verdict
APPROVE。精确路径和文件内容重新绑定,长程审核可发现旧收据遗漏的文件修改,操作者能按原文件名复核。复用 Git probe 和 receipt owner 的改动范围足够,邻近检查未发现需要一起扩展的重构。现有状态词汇和协议形状仍适用。本次评审覆盖现有宿主与 CLI 行为。独立验证使用 macOS 上的 Git 2.56 和临时仓库。Windows 原生入口与已安装版本尚未验证。
English verdict: APPROVE. Head 260936b preserves exact Git path identity and rejects the stale receipt after a CR-only file edit; matched CLI and local premerge checks support approval.
Problem / 问题
change-qualityreadsgit diff --name-only -zandgit ls-files -zthrough Python text mode. Universal-newline decoding turns a legal\ror\r\ninside a filename into\n, so the scope names a different path and can fail while fingerprinting an untracked file. The same text read trims the repository root withstrip(), losing a checkout directory ending in LF, CR, or a space.Fix / 修复
Read Git path output as bytes, split NUL-framed records first, then decode each record strictly. The repository-root read removes only Git's single terminal LF. Undecodable changed paths now fail explicitly instead of collapsing through replacement decoding.
Scope paths keep their literal Git spelling through every source (committed, staged, unstaged, untracked) and through evidence refs; only classification projections such as repository context surface roots normalize separators.
Validation / 验证
python3 -m pytest -q tests/test_change_quality_scope_paths.py: 25 passedpython3 -m pytest -q tests/test_change_quality_scope_paths.py tests/test_change_quality_git_budget.py tests/test_runtime_subprocess_utf8.py tests/capabilities/test_machine_configuration_goal_defaults.py: 76 passedgit diff --check: passedtests/capabilities/test_change_quality.pyneeds the local TypeScript Effect runtime host permission in this checkout; it fails the same 18 tests at3a5899f00before this change, so those failures are environmental.Covered cases: CR, CRLF, and LF names staying distinct in one directory; all four Git sources keeping CR names; repository roots ending in LF, CR, and a space; a synthetic real Git tree keeping CR and CRLF names distinct; a non-UTF-8 committed path failing with
not valid UTF-8; evidence paths keeping CR, spaces, backslash, and non-ASCII names.