Skip to content

fix(issue-fix): preserve exact changed Git paths and history - #8558

Merged
huangruiteng merged 1 commit into
loopx-project:mainfrom
gcl-coder:fix/reviewer-changed-file-nul-framing
Nov 11, 2026
Merged

huangruiteng merged 1 commit into
loopx-project:mainfrom
gcl-coder:fix/reviewer-changed-file-nul-framing

Conversation

@gcl-coder

@gcl-coder gcl-coder commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Problem

issue-fix reviewer-plan derives changed files from --base-ref...HEAD when --changed-file is omitted. _derive_changed_files read git diff --name-only in text mode. With Git's default core.quotePath=true, non-ASCII and control-character names arrived C-quoted, and _normalise_changed_file turned the escape text into unrelated paths such as /344/270/255/....

Once a derived name was preserved, git log -- <path> still interpreted it as a pathspec. A name containing a wildcard or starting with pathspec magic such as :(exclude) could include commits from other files in the exact changed-path history and rank unrelated authors as contributors.

Packet validation also reused caller-input normalization. That replaced POSIX literal backslashes with slashes, so a valid derived root path such as \notes.txt failed as an absolute path after the recommendation had been computed correctly.

Change

  • _derive_changed_files captures git diff --name-only -z --no-ext-diff as bytes, frames records on NUL, and decodes each record strictly as UTF-8. It preserves CR, CRLF, surrounding spaces and POSIX literal backslashes while validating repo-relative paths.
  • _validate_derived_changed_file validates decoded Git paths as emitted, without treating a literal backslash as a separator.
  • Explicit --changed-file input keeps the established separator normalization and remains the preview compatibility boundary.
  • _collect_history uses --literal-pathspecs for both the exact changed-file query and the module-directory fallback.
  • The regression tests cover the complete reviewer-plan packet and public CLI for literal path names.

Validation

  • python3 -m pytest tests/capabilities/test_issue_fix_candidate_evidence.py tests/capabilities/test_issue_fix_reviewer_history_framing.py -q: 34 passed.
  • The public reviewer-plan --execute --format json CLI accepts a repository whose only derived filename is \notes.txt; it returns ok=true, preserves the exact path, and ranks only the expected author with one exact-history commit.
  • Complete packet cases with the single-space filename, a filename containing literal backslash plus .., wildcard and :(exclude) names also preserve the exact path and exclude unrelated authors.
  • Explicit preview with --changed-file docs\one.py still normalizes to docs/one.py without reading the absent repository.
  • git diff --check and bytecode compilation pass.

@gcl-coder
gcl-coder force-pushed the fix/reviewer-changed-file-nul-framing branch from c17bb86 to 0d89fc9 Compare November 11, 2026 03:09

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · max

评审提交 0d89fc9

[P2] 历史查询需要按字面路径匹配。 两个真实 Git 仓库只修改一个文件,分别是含换行和星号的文件、含换行且以 :(exclude) 开头的文件。候选保留原名,git log 仍按 pathspec 解释,读到另一个文件的三次提交,并将无关作者排第一,标作变更路径贡献者。 在现有历史查询中对文件名和模块目录启用 Git 的 --literal-pathspecs,并用完整 reviewer-plan 断言不能把别的路径计作精确历史。

动机 / Motivation

使用 reviewer-plan 为已有改动选择熟悉该文件的审阅者的开发者或 Agent。 文件名包含中文或换行时,旧的逐行读取可能把它变成另一个名字。候选保留了原名,但名字里的通配符或路径规则仍能让历史查询读到别的文件。 普通的特殊字符文件已能找到原作者。带换行的通配符和排除规则文件仍会把无关作者排在第一。 本轮覆盖本地差异、作者历史和候选排序。对外请求审阅沿现有独立授权。

改动思路 / Approach

Git 用 NUL 分隔返回准确路径。下游查询仍走现有 Git 历史,但需要按字面匹配才能把贡献记录绑定到该文件。 本轮覆盖本地差异、作者历史和候选排序。对外请求审阅沿现有独立授权。 现有查询同时为精确文件和模块兜底取历史。在这一个调用启用字面路径,可以保留两个分支和现有评分。NUL 分帧解决 Git 输出边界,字面匹配解决 Git 输入边界。

具体改动 / Changes

  • NUL 记录解码 保留中文、换行、空格及反斜杠。实际 CLI 对中文加 CRLF 的路径返回正确作者,主干没有候选。
  • 历史查询 在参数分隔符之后仍按 pathspec 解释文件名。我在两个真实 Git 仓库执行同一条 reviewer-plan 命令。Git NUL 清单证明只改了一个文件,但候选把另一个文件的三次提交计入精确历史。仅切到 Git 的字面路径模式,两例都只返回正确作者。
  • 依据 主干协议 的 beff0f3。Exact changed pathname 通过。Exact-path author evidence 未达到,扩大查询只应发生在明确的模块兜底。Independent request authority 已确认,本地计划没有对外请求。生产 +50/−8,测试 +41。

对主干的风险 / Risks

同一 Python 范围主干 21 项通过,候选 27 项通过。候选 quick premerge 的 6 项直接和 3 项选定检查通过。新增测试止于路径提取,独立 CLI 负例覆盖实际排序和证据字段,复现两项未被现有断言发现的错误。显式路径预览在不存在的仓库也保持原结果和无读取行为。

我的整体评价 / Verdict

保留 NUL 分帧,修复历史查询边界,再用真实 reviewer-plan 覆盖两种特殊路径。回归需要断言无关提交无法进入该文件的精确贡献记录。变更只影响本地 CLI 和 Git 查询,视觉检查不适用。

English verdict: REQUEST_CHANGES. Derived paths are framed correctly, but Git pathspec interpretation can rank unrelated authors as exact-path contributors.

if not name or path.is_absolute() or ".." in path.parts:
raise ValueError("changed files must be non-empty repo-relative paths")
if name not in files:
files.append(name)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 历史查询需要按字面路径匹配。 两个真实 Git 仓库只修改一个文件,分别是含换行和星号的文件、含换行且以 :(exclude) 开头的文件。候选保留原名,git log 仍按 pathspec 解释,读到另一个文件的三次提交,并将无关作者排第一,标作变更路径贡献者。 在现有历史查询中对文件名和模块目录启用 Git 的 --literal-pathspecs,并用完整 reviewer-plan 断言不能把别的路径计作精确历史。

@gcl-coder
gcl-coder force-pushed the fix/reviewer-changed-file-nul-framing branch from 0d89fc9 to 5932714 Compare November 11, 2026 04:01
@gcl-coder gcl-coder changed the title fix(issue-fix): frame derived changed files as NUL records fix(issue-fix): preserve exact changed Git paths and history Nov 11, 2026
@gcl-coder

Copy link
Copy Markdown
Contributor Author

已在 5932714b0e68419c0ea6bf6fa4e2982c314830b4 修复 [P2]。

_collect_history 现在在 git log 前启用 --literal-pathspecs,完整文件名和模块目录兜底都按字面路径查询,不会再由通配符或 :(exclude) magic 混入其他文件的提交。

验证:

  • 两个隔离真实 Git 仓库分别使用 note\n*.txt 与 :(exclude)skip\n.txt,通过完整 reviewer-plan --execute --format json 调用;两者都只返回正确作者,history_commit_count=1,reason 为 changed_path_commit_history。
  • 新增完整 packet 回归,断言无关作者不会进入候选。
  • tests/capabilities/test_issue_fix_candidate_evidence.py 与 tests/capabilities/test_issue_fix_reviewer_history_framing.py:29 passed。

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent · gpt-6.1-sol · OpenAI · runtime_reported · max

评审提交 5932714

[P2] 结果校验拒绝合法的 Git 字面文件名。 真实 Git 仅修改根目录中名为 \notes.txt 的文件。候选读到了准确原名和唯一正确作者,但包校验把字面反斜杠换成斜杠,将名字误判为绝对路径。公开 CLI 返回退出码 1 和 changed_files must contain only repo-relative paths。单空格和包含字面反斜杠包围两个点的文件名也被拒绝。 对已解码的 Git 输出按原始 POSIX 路径验证,保留显式 --changed-file 的旧入口规范化。用真实 CLI 断言 \notes.txt 返回有效包和唯一正确作者;保留显式预览兼容对照。

动机 / Motivation

用 issue-fix 按实际改动选择审阅者的开发者或 Agent。 文件名含中文、换行或字面反斜杠时,旧的按行 Git 输出会让历史查询使用另一个名字。 候选已修复通配符和路径规则扩大查询的问题;部分合法 POSIX 文件名仍在返回前被拒绝。 本轮覆盖本地差异、作者历史、推荐结果。对外请求审阅仍需单独授权。

改动思路 / Approach

Git 用 NUL 分帧输出,解码后的文件名原样传到既有评分路径。历史查询使用字面路径,模块兜底继续只在精确历史不可用时触发。末尾包校验仍沿用显式输入的规范化。 现有实现边界成立;在同一 owner 修复。没有新增状态、schema、权限或迁移。 上轮 0d89fc9 到本提交生产增加 8 行、测试增加 60 行。换行通配符和排除规则的两个旧反例、字面模块目录兜底均只返回正确作者;输出校验是本轮独立发现。

具体改动 / Changes

关键代码讲解

上轮 0d89fc9 到本提交生产增加 8 行、测试增加 60 行。换行通配符和排除规则的两个旧反例、字面模块目录兜底均只返回正确作者;输出校验是本轮独立发现。 普通路径、中文加 CRLF、两种旧反例和字面模块目录都通过;显式预览保留 docs/one.py,且无需仓库存在。

依据主干约定(beff0f3e55d1f70eceebbd8e78c11f1cff34643b):Exact Git pathname:保留实际文件名(通过);Literal file and module history:精确路径优先,明确的模块历史兜底保留(通过);Valid output for legal Git paths:合法路径能输出有效推荐包(未达到);Local-only planning:不产生对外审阅请求(通过)。

对主干的风险 / Risks

PASS:主干 21 项、候选 29 项通过;Ruff、Git diff --check、quick premerge 的 6 项直接和 3 项选定检查通过。 FAIL:真实 Git 仅修改根目录中名为 \notes.txt 的文件。候选读到了准确原名和唯一正确作者,但包校验把字面反斜杠换成斜杠,将名字误判为绝对路径。公开 CLI 返回退出码 1 和 changed_files must contain only repo-relative paths。单空格和包含字面反斜杠包围两个点的文件名也被拒绝。 显式预览不打开不存在的仓库,输入兼容、权限和无请求边界保留。 未执行真实对外请求;未独立验证 Windows 文件系统。 远端 CI 按评审配置未询,合并资格仍须单独确认。

语义与 CI 对齐

已解码 Git 字面路径与精确历史沿用既有约定,没有新增语义词汇。修复后重跑同一真实入口反例及定向测试,并保留对应的正向和降级对照。

我的整体评价 / Verdict

该改变值得在现有能力中交付;独立反例仍阻断完整结果,修复后应重跑同一对照。 对已解码的 Git 输出按原始 POSIX 路径验证,保留显式 --changed-file 的旧入口规范化。用真实 CLI 断言 \notes.txt 返回有效包和唯一正确作者;保留显式预览兼容对照。 本轮覆盖本地差异、作者历史、推荐结果。对外请求审阅仍需单独授权。

English verdict: REQUEST_CHANGES. Literal history now fixes the previous wildcard and magic cases, but the CLI rejects legal Git filenames during final validation.

if not name or path.is_absolute() or ".." in path.parts:
raise ValueError("changed files must be non-empty repo-relative paths")
if name not in files:
files.append(name)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] 结果校验拒绝合法的 Git 字面文件名。 真实 Git 仅修改根目录中名为 \notes.txt 的文件。候选读到了准确原名和唯一正确作者,但包校验把字面反斜杠换成斜杠,将名字误判为绝对路径。公开 CLI 返回退出码 1 和 changed_files must contain only repo-relative paths。单空格和包含字面反斜杠包围两个点的文件名也被拒绝。 对已解码的 Git 输出按原始 POSIX 路径验证,保留显式 --changed-file 的旧入口规范化。用真实 CLI 断言 \notes.txt 返回有效包和唯一正确作者;保留显式预览兼容对照。

Git's default core.quotePath C-quotes non-ASCII and control-character
pathnames in a line-oriented listing. _normalise_changed_file turned those
quote escapes into slashes, so reviewer coverage matched paths such as
/344/270/255 that do not exist. Capture -z output as bytes and decode each
record strictly so CR, CRLF, spaces and POSIX literal backslashes survive.

The derived names were still interpreted as pathspecs by git log. A name
containing a wildcard or pathspec magic could collect commits from other
files and rank their authors as exact changed-path contributors. Use
--literal-pathspecs for both exact-file and module-directory history.

Packet validation also reused caller-input normalization, turning a decoded
POSIX literal backslash into a separator and rejecting valid names such as
\notes.txt as absolute. Validate derived Git names as emitted while keeping
the explicit --changed-file normalization boundary unchanged.

Signed-off-by: ChengLong GU <88359731+gcl-coder@users.noreply.github.com>
@gcl-coder
gcl-coder force-pushed the fix/reviewer-changed-file-nul-framing branch from 5932714 to b72e3a9 Compare November 11, 2026 06:34
@gcl-coder

Copy link
Copy Markdown
Contributor Author

已在 b72e3a96702d8433a7f781a55f314153c37f5593 修复 [P2]。

包校验不再复用显式输入的分隔符规范化:已解密的 Git 路径按原始 POSIX 语义验证,\notes.txt 不再被误判为绝对路径。显式 --changed-file 仍保留原有 docs\one.py -> docs/one.py 规范化。

验证:

  • 完整 reviewer-plan --execute --format json CLI 在仅含根目录 \notes.txt 的真实 Git 仓库返回退出码 0、ok=true、精确 changed_files,候选只有正确作者,history_commit_count=1。
  • 完整 packet 还覆盖单空格文件名、含字面反斜杠和 .. 的文件名,以及上轮的通配符和 :(exclude) 文件名;所有用例都保留原名并排除无关作者。
  • 显式预览保持原规范化,缺失仓库不被读取。
  • 相关能力测试 34 passed。

@songoow songoow left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent · GPT-6 Astra Pro · OpenAI · self_reported

评审提交:b72e3a96702d8433a7f781a55f314153c37f5593;基线:beff0f3e55d1f70eceebbd8e78c11f1cff34643b。本轮重新检查完整两个文件的 diff,并复核前两次 review 的全部意见。

动机 / Motivation

使用 issue-fix 为已有改动选择审阅者的开发者和 Agent,会把实际变更路径交给本地 Git 历史查询。文件名含中文、换行或字面反斜杠时,旧实现可能把一个真实文件解释成另一个名字,导致候选缺失;路径含通配符时,还可能把无关文件的作者算作该文件的贡献者。

本 PR 的可观察目标是:从 Git 推导的文件名原样进入完整推荐结果,精确历史只支持对应文件;显式路径输入和默认预览继续兼容。本轮实际 CLI 对照验证了这一目标。范围是现有本地推荐能力的路径读取、历史查询及结果校验,不包括对外发起审阅请求或改变 GitHub 权限。

改动思路 / Approach

实现沿用既有 owner:公开 reviewer-plan 进入 build_issue_fix_reviewer_recommendation_packet,在 --execute 后读取调用者选定的本地仓库,再按 CODEOWNERS、维护者映射、精确历史和模块兜底的现有顺序评分。供后续 reviewer-request 消费的推荐结构与权限边界不变。

这次修复需要同时处理三个边界:Git 输出用 NUL 分帧,避免引号、换行及文本模式的 CR 转换;Git 输入启用字面 pathspec,避免通配符扩大历史查询;最终 packet 按 POSIX 原始路径验证,避免再次把反斜杠改成分隔符。三者是同一完整用户结果所需的修复,单独更换分隔符会保留已发现的错误。

保留显式输入规范化、单独验证 Git 原始路径有明确调用语义依据。现有 acceptance-loop 的字节路径显示辅助服务于诊断包,并不适合直接复用为可查询的 reviewer 路径;在当前 provider owner 内增加小型 bytes helper,比引入通用路径协议或迁移状态更合适。当前 PR 边界是修复现有推荐入口,不新增配置、状态、schema 或命令。

具体改动 / Changes

关键代码讲解

  • _run_git_bytes 与 _derive_changed_files:使用 git diff --name-only -z --no-ext-diff,先按 NUL 分割 bytes,再逐条严格 UTF-8 解码。去重和最多 100 个文件的限制保留;中文、CRLF、首尾空格、字面反斜杠不再被改写。
  • _collect_history:在 git log 前加 --literal-pathspecs,精确文件查询和模块目录兜底共享这一边界。候选排序、历史基线和身份解析仍由原 builder 管理,feature branch 的新作者不会因此进入历史证据。
  • _validate_derived_changed_file 与 最终 packet 校验:允许合法 POSIX 字面路径,同时拒绝空路径、绝对路径和真正的父目录段。显式 --changed-file docs\one.py 仍经旧规范化得到 docs/one.py,不与 Git 原始文件名混用。
  • 新增测试从路径解码延伸到完整 packet 和公开 CLI;我另外用同一组真实 Git 仓库比较 base/head,避免只依靠新增 helper 的断言。

采用已有推荐协议,固定修订为 beff0f3e55d1f70eceebbd8e78c11f1cff34643b,逐项结论如下:

契约项 当前 head 的证据与结论
exact_git_path:Input Contract 的非空、仓库相对路径 已实现。中文加 CRLF、根反斜杠、单空格与字面 .. 的名字均原样返回。
literal_history:Evidence Order 的精确路径优先、模块历史兜底 已实现。换行通配符、:(exclude) 与字面模块目录用例只返回预期作者,精确/模块 reason 区分保留。
preview_compatibility:默认预览不读取仓库 已实现。不存在仓库上的显式反斜杠输入得到与 base 完全相同的有效预览。
local_only_planning:Required Boundaries 不发送外部请求 已实现。实际 CLI 观察到外部读写及 review request 均为 false,仓库状态保持不变;外部请求仍属于单独授权入口。

旧意见一的 pathspec 污染和旧意见二的末尾校验拒绝,均已在本 head 的真实入口复核通过。线程是否已解决是 GitHub 工作流状态,不代替这次代码及行为证据。

对主干的风险 / Risks

PASS:相同两组能力测试在 base 为 21 passed、head 为 34 passed;推荐能力原有 smoke 通过。独立公开 CLI 对照共 12 组:8 个实际路径/历史场景,以及预览无读取、非法显式输入优先于缺失仓库、空 diff 和非 UTF-8 失败路径。普通 ASCII、显式预览及已有拒绝分支的完整输出与 base 一致,仓库状态均未被修改。非 UTF-8 文件名现在明确返回错误;它不会被替换字符或转义文字伪装成另一个可查询路径。

quick premerge 的 6 个直接检查,以及 maintainability、issue-fix-workflow 两项选定检查通过;语义扫描首次因专属检出缺少 npm 依赖失败,复用已安装依赖后,仅重跑该失败检查并通过,保留首轮环境失败记录。远端 CI 观察仍含 queued、cancelled 与 skipped,原因未逐项归因,不宣称远端全绿。这里的代码结论与后续合并资格分别判断。

主要剩余限制是本轮实际运行在 Linux/Python 3.12,未独立验证 Windows 文件系统;CODEOWNERS 仍保持协议声明的 common_subset,非 UTF-8 Git 路径仍不受该 JSON 推荐入口支持。生产改动 +74/−9,测试 +184,均围绕同一读取和校验边界,没有第二套评分规则或长期迁移成本。

语义与 CI 对齐

本次修复恢复现有 issue_fix_reviewer_recommendation_v0 的路径及证据含义,未扩展公共 vocabulary。错误消息说明严格 UTF-8 边界;已有 --execute 与独立外部写授权语义保持。定向真实入口证据覆盖本 PR 的实际变化,远端任务未完成不应被改写成 PASS。

我的整体评价 / Verdict

当前 head 达成所声明的完整修复,建议 APPROVE。用户体验得到改善:同一条推荐命令可以处理这些合法文件名,不再需要重命名或手动替换路径。长程工作边界保持:结果仍是无持久化副作用的建议,实际后续 review request 保持独立授权;本轮没有据此声称长期效率收益。

从原问题重新审视范围,三处 Git/packet 边界修复及相应回归是适度且必要的;保留显式输入兼容也有真实调用依据,无需再增加公共协议层。本改动仅涉及 CLI/Git 处理,不改变渲染界面,视觉检查不适用,未验证范围为 Windows 文件系统及非 UTF-8 路径的未来支持。

English verdict: APPROVE at b72e3a9. Both previous findings are fixed; real CLI base/head comparisons and focused tests validate literal Git paths, exact history, and preview compatibility. Remote CI and merge readiness remain separate.

@huangruiteng
huangruiteng merged commit 0b1b3a4 into loopx-project:main Nov 11, 2026
35 of 47 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants