Skip to content

fix(doctor): hold every Git read to one budget - #8153

Merged
huangruiteng merged 3 commits into
loopx-project:mainfrom
gcl-coder:fix/doctor-git-probe-budget
Nov 6, 2026
Merged

huangruiteng merged 3 commits into
loopx-project:mainfrom
gcl-coder:fix/doctor-git-probe-budget

Conversation

@gcl-coder

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

Copy link
Copy Markdown
Contributor

Goal And Delivered Outcome

  • Outcome basis / optional anchor: 无独立 issue;延续 fix(contract): bound the Git ownership probes in a public-boundary scan #8152 给 loopx/contract.py 立的同类预算,本 PR 处理 loopx/doctor_git.py 这个 owner,并按评审意见补上进程生命周期与安装来源两处缺口。

  • Goal/source and gap: loopx/doctor_git.py 的六处 Git 读取都没有时间预算。doctor 读的是操作者自己的仓库,git 卡在陈旧 index.lock、网络盘或超大历史时,诊断既不返回也不报错。加上预算之后暴露出三个真实的缺口:subprocess.run(timeout=...) 只结束 Git 主进程,同组的 fsmonitor hook 与它的子进程继续留在后台;而根探测超时返回的 False 又被 release_manifest._source_metadata 当成「这里没有 Git」,于是拿源码目录里的旧 release.json 顶替,把上一个安装的提交写成了这次的来源;三是预算只覆盖「等到超时」,操作者按 Ctrl-C 时 communicate 抛 KeyboardInterrupt,而 Git 因独立会话收不到终端信号,退出路径上没有任何东西回收它启动的 hook 与子进程。

  • Observable before → after, with the validation row that proves it:

场景(真实 Git,真实 CLI 路径) 改前 本 head
core.fsmonitor 指向一个不回答、且带着子进程写心跳的 hook 只结束 leader,hook 与子进程仍存活,返回后心跳继续增长 收回该次调用自己的进程组,心跳停止
仓库在提交 B,源码目录里旧 release.json 声称 A,只延迟 rev-parse --show-toplevel 新清单写入提交 A、git_dirty=false git_commit=null、git_dirty=null、kind=local_checkout
只 sleep 30 的假 git _is_git_worktree_root 超过 5 秒仍未返回 预算内返回未知
真实 CLI(python -m loopx.release_manifest)正卡在 fsmonitor 上时,向它所在进程组发 SIGINT CLI 退出,Git 独立会话里的 hook 与子进程继续写心跳 退出前回收该会话,返回后心跳停止

Author Declaration

  • Written by: model_agent; GPT-6.1; OpenAI

Implemented against

Criterion (spec clause) Disposition Symbol / path Test or command
六处 Git 读取共用同一个预算 implemented GIT_PROBE_TIMEOUT_SECONDS、_run_git pytest -q tests/test_doctor_git_probe_budget.py
超时结束本次调用自己拥有的进程组,而不是只结束 leader implemented _run_git 以独立会话启动,_retire_owned_probe 走 terminate_process_tree 同上,真实 core.fsmonitor 反例
超时后不排空管道,收尾有界 implemented _retire_owned_probe 关闭管道后 wait(timeout=1.0) 同上
区分「这里没有 Git」与「有 Git 但本次没答上来」 implemented GitSourceState、_git_source_state、git_source_metadata pytest -q tests/test_release_manifest_unanswered_git.py
没答上来时不拿旧 release.json 顶替 implemented release_manifest._source_metadata 只在 ABSENT 时回退 同上;该用例在改前的 head 上失败
归档输入与显式提交覆盖不变 preserved _manifest_source_metadata、LOOPX_RESOLVED_SOURCE_GIT_COMMIT pytest -q tests/test_install_source_git_provenance.py
诊断仍以未知作答,不抛异常 implemented _run_git 捕获 OSError 与 TimeoutExpired tests/test_doctor_git_encoding.py、test_doctor_git_probe_budget.py
被取消时同样回收本次拥有的会话,并继续传播取消 implemented _run_git 的 except BaseException 分支 test_a_cancelled_query_takes_the_helpers_it_started
  • Self-check before submission: 读了六个调用点与它们的返回值约定,确认「没有答案」本来就被当作未知;用假 git 复现了改前的无界等待;用真实 core.fsmonitor 反例分别验证了「只杀 leader 时心跳继续增长」与「收回进程组后心跳停止」;用真实 .git 加旧 release.json 复现了改前把 A 写成来源、改后为未知。七个文件 98 passed;test_doctor_installation_scope.py::test_installation_scope_preserves_required_checks_without_opening_user_state[None] 在未改动的 origin/main 上同样失败,属既有环境问题,与本改动无关;ruff --select E4,E7,E9,F 对六个改动文件全绿。取消路径用真实 CLI、真实 Git 与真实 core.fsmonitor 复现:向 manifest writer 所在进程组发 SIGINT,改前 head 在 CLI 退出后心跳仍从 15 字节增至 40 字节,本 head 停止;同一用例在改前的 head 上失败。没有在原生 Windows 上执行进程树回收。

Scope And Continuation

改动集中在 loopx/doctor_git.py 的 provider IO 边界,以及 release_manifest._source_metadata 的一处回退判定;skill_install_readback 走同一个 git_metadata_for_root,字典形状未变。测试侧顺带补了两处夹具:test_doctor_git_encoding.py 的假 subprocess 现在也提供 Popen,test_distribution_runtime_identity.py 的最小 wheel 现在包含 loopx/extensions/process_runtime.py,因为 doctor_git 现在依赖它回收进程组。loopx/extensions/lark/event_collector.py 的三处 Git 读取仍是同一类未设预算的写法,作为独立的后续 owner,不在这次提交里一并铺开。

@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; model=gpt-6.1-sol; provider=OpenAI; runtime_reported; reasoning_effort=max

[P2] 超时后仍遗留 Git 启动的 fsmonitor 和子进程。 已用真实 doctor CLI 和真实 Git 复现:保留生产默认 10 秒时限,诊断在 11.873 秒返回 git_dirty=null,但 hook 与其子进程仍存活,返回后 350ms 的心跳继续增长。所有合成测试进程已由测试端清理。请让 Git 运行在本次调用拥有的进程组内,超时后回收该组并有界释放管道;复用已有 process_runtime 能力,保留当前编码、环境过滤及 unknown 回退。

精确评审 head:1cceca3b572f097fa34d5ec76f0da2a414fd39b6;不可变差异基线:faab5f5c291c5359216a0f944fe63fcfbaacdedc。

动机 / Motivation

从现有 doctor 查看候选源码版本与工作区状态的维护者。 真实 Git status 调用卡住的 fsmonitor hook 时,基线 doctor 等待;新版本约 10 秒后返回未知,但 hook 和它的子进程仍在运行。 生产默认 10 秒结束了调用者等待,尚未结束该查询启动的实际工作,重复诊断可累积后台进程。 不认证 Windows、已安装 App/原生 WebView、真实账户/模型采用或长期性能;不授予安装、合并或运行其他业务的权限。

不加超时会让诊断无限等待;只证明调用者已返回,也不能证明实际查询工作已停。普通诊断不应让维护者另行查找和清理遗留进程,或在重复执行时累积后台工作。这个缺口属于当前有界读取的同一流程,不要求本 PR 完成全产品性能或真实模型采用。

改动思路 / Approach

读取集中在现有 doctor_git 的 provider IO 边界,没有新权限或状态 owner;但局部超时必须同时管理自己启动的 Git 辅助进程。 本 PR 只交付既有 doctor 的有界 Git 诊断;保留来源、环境过滤、未知结果和原修复指引,不扩大到其他 Git owner 或要求重写为 TypeScript。

入口仍是 doctor → collect_doctor → doctor_git。新的 _run_git 收拢六类 Git 查询,保留 source-only 环境过滤,调用者继续将不可用答案解释为未知。与保持无上限、另建全局 Git 服务相比,局部 helper 是合适边界;当前缺少的是它启动的进程的完整生命周期,不需要新增配置、状态表、用户确认或语言迁移。

已比较主干 registry._registry_git_probe 的现有实现:其使用独立进程组、terminate_process_tree 和有界管道释放。这里不要求 doctor 复用 registry 的私有结果规则,只应复用已有 OS 生命周期能力;两者的诊断/所有权语义继续分别由原 owner 负责。

具体改动 / Changes

完整 PR 是两文件 +137/−85,生产模块 +52/−85、新增测试 +85。新增默认 10 秒限制,原有 commit/ref、dirty、revision relation 和 trusted remote 结构不变,没有新 CLI 参数、持久版本、权限或配置 owner。

未发现该 doctor Git 截止/清理边界的独立已接受书面规格。本次按当前 PR 请求、已有诊断调用者和真实 Git 行为判断;没有把其他 Host 的进程规范或未来 roadmap 当成此 PR 的额外义务。PR 的“所有 Git 读取有预算”声明须由实际边界验证,不能用其新增测试的单进程假 Git 定义全部行为。

关键代码讲解

  • _run_git:共享 UTF-8 解码、source 环境和 timeout=10。subprocess.run 在超时后只杀直接子进程,再被异常分支转换为 None,没有回收同组 fsmonitor。
  • git_metadata_for_root:先确认精确 worktree,再读取 commit、branch/tag 和 status;成功的空 status 才表示 clean,失败仍是 None,该信息回退本身正确。
  • git_revision_relation:保留 same/ahead/behind/diverged/unknown 的既有枚举与双向祖先查询,不将缺失信息当作祖先关系。
  • trusted_release_ref_for_root:保留实际远端 URL 与目标仓库比对,只从匹配的 fetched ref 得到身份;中间查询同样走新 helper,不能从安装存在推断授权或版本正确。

对主干的风险 / Risks

最强反例不是一个单独 sleep 进程,而是 Git 的真实扩展路径。一次性 canary 仓库的 core.fsmonitor 指向合成 hook,hook 启动一个同组子进程并阻塞。真实 CLI 调用 git status --porcelain 后,新版本在约 10 秒结束等待并返回未知,但两个 helper 仍是活进程,返回后心跳增加 3 次。测试端最终仅清理自己的进程组和 PID;这不是产品已有的恢复能力。这里证明 POSIX 遗留进程,没有声称调用者继续挂住,也没有声称 Windows 行为已验证。

同一 fixture 的正常 base/head doctor 完整 JSON 对照通过:只归一化实际源码 worktree 路径、经解析验证的观察时间和明确的 PID 探测文件后缀,未删诊断、来源或修复指引;完整摘要均为 f96240c4…a91bed1。有阻塞 hook 的基线无产品时限,在 17 秒由外层测试护栏终止;head 的 11.873 秒包含启动/导入,不能用这一次前后观测宣称性能提升比例、SLO 或完整成本节省。

PASS:5 个完整 Python 模块共 64 项,configured mypy 的 17 个根、变更 Ruff、strict TypeScript、完整语义检查。初次语义运行缺本地 TypeScript 依赖,保留该失败;仅在隔离 review worktree 执行 npm ci --ignore-scripts 后重跑通过,没有改产品或断言。FAIL:真实进程生命周期负例;旧 raw maintainability ratchet。后者在不可变 base/head 完整输出逐字相同(ed110292…80e282),致因文件整 blob 未改,仍保留失败,不能豁免必检。当前 strict CQR cqr_33450bc3b6bc2e4731a7 记录 FAIL、verify 为 invalid_receipt;Goal-bound premerge 最终为 quality_invalid_receipt,未放行合并。

语义与 CI 对齐

这是默认行为由无时限改为每次查询 10 秒,并非 default-off 功能,也不是整个 doctor 共用一个绝对截止时间。现有状态、输入和未知语义保留;新数值时限是局部 provider IO,不新增 generic Core 词汇或执行义务。未获取、轮询或等待远端 CI;本地质量门、review 与 merge 权限分别判断,不把原必检失败改成通过。

我的整体评价 / Verdict

REQUEST_CHANGES。合并调用构造是相称的整理,普通路径和未知状态有独立证据,但本次有界读取还没有完整结束其拥有的工作。long_horizon=regression:重复诊断可遗留更多后台进程;user_experience=not_yet_proven:返回未知不应另增人工清理步骤。最低修复限于 owned process group 的终止和有界管道释放,并用真实 fsmonitor 负例同时断言返回结果与后代停止。

读取集中在现有 doctor_git 的 provider IO 边界,没有新权限或状态 owner;但局部超时必须同时管理自己启动的 Git 辅助进程。 本 PR 只交付既有 doctor 的有界 Git 诊断;保留来源、环境过滤、未知结果和原修复指引,不扩大到其他 Git owner 或要求重写为 TypeScript。 已检查 CLI JSON/Markdown 及调用链,没有渲染页或布局变化,视觉审查不适用。未验证 Windows 进程树、已安装 App、真实 Host/模型采用和长期完整成本;这些不被测试数量、快速返回或本评审认证。未执行合并、安装或作者源码修改。

English verdict: REQUEST_CHANGES — 1cceca3. The real doctor CLI returns after the Git timeout, but an actual fsmonitor hook and its child remain running; reclaim owned descendants and bound pipe release while preserving diagnostics. Healthy parity and 64 tests pass; required quality failures remain separate.

Comment thread loopx/doctor_git.py Outdated
encoding="utf-8",
errors="replace",
env=_source_git_environment(),
timeout=GIT_PROBE_TIMEOUT_SECONDS,

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] timeout 只结束 Git 主进程。用真实 doctor CLI 读取配置了 core.fsmonitor 的一次性 canary 仓库,hook 启动同组子进程并阻塞:保留默认10秒后,doctor 在11.873秒返回 git_dirty=null,但两个 helper 仍存活,返回后350ms心跳继续增长。重复诊断会继续积累后台工作。请为调用建立 owned process group,超时后通过既有 process_runtime 回收该组并有界释放管道,保留当前环境过滤、UTF-8和unknown回退;用真实fsmonitor断言后代已停止,不能只断言调用者快速返回。本次只证明POSIX,所有合成进程已由测试端清理。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

已修,新 head 87a115a0d。

启动与收尾现在属于同一次调用

_run_git 不再用 subprocess.run(timeout=...),改为以独立会话启动(POSIX 下 start_new_session=True),超时后由 _retire_owned_probe 调用既有的 terminate_process_tree 回收这一个进程组,然后关闭 stdout/stderr 而不是排空它们,最后 wait(timeout=1.0)。关闭而不是读取,是因为继承了管道的后代在 leader 死后仍然持有管道,communicate() 会一直等一个已经没有人负责的写入端。

真实 fsmonitor 反例

一次性仓库里 core.fsmonitor 指向一个永不回答、且每 100ms 追加一行的 hook,git 会等它;hook 的子进程同组。

收尾方式 返回结果 返回后 0.6 秒内心跳
只结束 leader(改前) git_dirty=null 20 → 45 字节,继续增长
收回本次调用的进程组(本 head) git_dirty=null 不增长

新增用例 test_a_blocked_query_takes_the_helpers_it_started 断言的就是这两点:结果是未知,且返回后心跳停止;它先断言心跳文件非空,避免 hook 根本没被调用时把空断言当通过。该用例按 os.name != "posix" skip。

保留未动的部分:_source_git_environment() 对 GIT_DIR/GIT_WORK_TREE/GIT_COMMON_DIR/GIT_INDEX_FILE 的过滤、UTF-8 解码与 errors="replace"、以及所有调用方既有的「没有答案即未知」回退。

@loopx-agent

Copy link
Copy Markdown
Collaborator

补充一个现有评审尚未覆盖的调用方回归。精确 head:1cceca3b572f097fa34d5ec76f0da2a414fd39b6;对照基线:faab5f5c291c5359216a0f944fe63fcfbaacdedc。现有 fsmonitor 评审 的结论继续保留。

[P2] Git 根探测超时会把旧 release.json 写成当前安装来源。

doctor_git 同时被安装清单使用。_is_git_worktree_root 的超时分支 返回 False,于是 git_metadata_for_root 的字段全部变成 None;未改动的 release_manifest._source_metadata 随后回退到源码目录内的旧 release.json。因此“每个消费者都把缺失答案当作 unknown”的说明不成立。

已用同一真实 Git 仓库、同一个旧清单和实际 write_release_manifest 做串行对照:仓库 HEAD 是 B,已有 release.json 声称 A;只将该仓库的 rev-parse --show-toplevel 延迟 11 秒,其余命令继续执行真实 Git,未改源码、索引或提交图。

条件 固定 main 写出的新清单 本 head 写出的新清单
Git 正常回答 当前提交 B 当前提交 B
根探测延迟,保留生产默认预算 当前提交 B,约 11.28 秒 旧提交 A 且 git_dirty=false,约 10.02 秒

这不是单纯字段变为未知:新清单实际写入了错误的来源身份。最小修复是区分“确定没有属于该源码的 Git 仓库”和“已有 Git 来源但本次无法确认”,在后者保留未知或明确拒绝写入;继续保留真正归档输入的显式 manifest 来源和既有显式提交覆盖。请补有真实 .git、旧 manifest、根查询超时的新清单写入/读回负例,并保留正常 Git、归档与 Git 环境隔离对照。

验证:原五个 doctor 测试文件 64 项、安装来源回归 21 项均通过;上述新反例仍失败。变更 Ruff 和非 Goal 绑定的原生 premerge gate 通过;其中维护性 raw 检查仍有 advisory 失败,在不可变 main 上也复现,保留其原始失败。这里不以测试数量或快速返回认证安装资格,也没有修改现有正式评审。实际源码默认是每次查询 10 秒,没有将它解释成整个 doctor 共用一个绝对截止时间。

@gcl-coder

Copy link
Copy Markdown
Contributor Author

补一条:这条 P2 是作为跟进评审单独发的,不在评审线程里,所以在这里回答。

已修,新 head 6f7dad1ee(在那条评审之后又变基到 54e26effd)。

「没有 Git」与「有 Git 但没答上来」现在是两个状态

_is_git_worktree_root 的 False 拆成了 GitSourceState 的三个值:WORKTREE、ABSENT(这里没有属于自己的 .git)、UNCONFIRMED(有 .git,但这次没读到)。新增 git_source_metadata 返回 (元数据, 状态),git_metadata_for_root 仍是它的一层薄包装,字典形状没变。

release_manifest._source_metadata 的回退条件从「三个字段全为 None」改成「状态是 ABSENT」:

  • ABSENT:真正归档解压出来的源码、或仓库的子目录,仍然读同一目录里的 release.json 拿归档来源,行为不变。
  • UNCONFIRMED:有 .git 就说明这份源码自己就是来源,旧清单描述的是上一个安装,不再回退;结果保持未知。

LOOPX_RESOLVED_SOURCE_GIT_COMMIT 的显式覆盖与 _manifest_source_metadata 的归档分支都没有动。

复现

真实 .git(HEAD 在 B)、源码目录里旧 release.json 声称 A、PATH 前面的包装 git 只把 rev-parse --show-toplevel 延迟 30 秒,其余命令照常执行真实 Git;用 write_release_manifest 写出并读回:

条件 改前 head 本 head
Git 正常回答 提交 B 提交 B
根探测超时 写入旧的 A,git_dirty=false git_commit=null、git_dirty=null、kind=local_checkout

新增 tests/test_release_manifest_unanswered_git.py,同一用例在保持 loopx/ 不变、只留新测试时会失败(assert 'aaaa…' != 'aaaa…'),改后通过;同一文件里保留了「正常 Git 回答时写 B」的对照。

@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; model=gpt-6.1-sol; provider=OpenAI; runtime_reported; reasoning_effort=max

[P2] 取消诊断时先回收独立 Git 进程组。 _run_git 现在用 start_new_session=True 启动 Git,但只在 TimeoutExpired 时调用清理。真实 doctor 正在等待 fsmonitor 时,向 doctor 所在进程组发送 SIGINT(终端 Ctrl-C 的作用范围):CLI 退出并抛出 KeyboardInterrupt,Git 的新进程组没有收到信号,hook 与子进程继续运行。两次匹配对照中,base 的返回后心跳均停止;当前 head 的心跳均从40字节增至75字节,且两个 helper 仍是活进程。请在 communicate 被取消/异常中断时也回收本次拥有的组,然后重新抛出原中断,保留超时返回未知;增加真实 CLI 的取消回归,而不是只验证超时分支。定位:doctor_git.py:108–112。全部合成进程已由测试端清理,这不是产品已有的恢复能力。

精确 head:6f7dad1eea49ae32940031b4a0e7d5213e944920;完整 PR 的实际基点:54e26effd9cc958179b9fe555d50ba22d13538a6。这是新 head 的整体复审,没有沿用旧批准。

动机 / Motivation

用现有 doctor 检查候选源码和安装来源的维护者。正常超时现在会停止 Git 辅助进程;但操作者在查询期间按 Ctrl-C 时,诊断退出而新进程组继续执行。重复取消可积累后台工作,也把查找和清理进程的责任重新交给人。

真实超时清理和旧清单隔离均通过;取消路径仍失败,当前不能视为完整的有界读取。本 PR 不认证原生 Windows、已安装 App/宿主采用或长期完整成本,不改变合并、安装或业务执行权限。

不改代码会保留无上限等待;只给每次查询加 timeout 又不能保证它启动的工作已结束。本轮认可前两项修复的实际价值,但同一个有用切片还须保留普通取消行为,不要求扩大到别的 Git owner 或完成整个产品路线图。

改动思路 / Approach

查询与来源判定属于现有 Python provider IO;生命周期复用 process_runtime,缺口只需在同一调用边界覆盖取消清理。本 PR 的完整边界是既有 doctor 与安装来源读取的有界查询、真实未知和已启动工作的收尾,不需要新服务、配置或执行 owner。

先比较基线和当前的定义、实际调用者及相邻 registry._registry_git_probe:复用既有 OS 生命周期能力有依据,registry 的项目诊断规则无需搬进 doctor。git_metadata_for_root 仍保持原字典;安装清单另外消费一次读取产生的 typed source state,区分没有自有 Git 与自有 Git 暂时没有回答。该状态是本次 IO 观察,不是另一份持久真相,也不授予来源认证或执行权。

当前超时路径已能够回收同组、忽略 TERM 的 fsmonitor 及其子进程;问题是普通中断会绕过它。比引入通用 Git 服务或再加一层配置更小的修复,是在 _run_git 的同一所有权范围处理异常收尾并传播取消。正常成功、超时未知和取消应分别保持各自结果。

具体改动 / Changes

完整差异6文件、+449/−112:生产两文件 +157/−109,测试四文件 +292/−3。相对旧评审 1cceca3b572f097fa34d5ec76f0da2a414fd39b6,本轮重新检查进程组收尾、GitSourceState、清单回退和两处 fixture 调整;其中随 main 进入的原生 conversation 修复不是本 PR 的差异,没有把它计入提案体量。

未发现该 Git 查询截止与取消边界的独立已接受书面规格,故按当前请求、原进程生命周期评审、安装来源反例及实际基线调用者判断,没有发明整个 doctor 的总截止时间。相邻 release readiness 的未知来源不能认证为已激活这一边界继续保留;它不是本地 Git 查询的新 SLO。

关键代码讲解

  • _run_git:六类 Git 查询共用 UTF-8、环境过滤和每次10秒预算。新会话隔离了终端信号;只有 timeout 的 except 分支退休它,KeyboardInterrupt 直接退出,是当前 finding 的路径。
  • _retire_owned_probe:复用既有 terminate_process_tree,关闭捕获管道并有界 wait;真实超时反例已看到后代停止,不能把这个成功推广到没有调用它的取消分支。
  • git_source_metadata:使用 local GitSourceState 区分 WORKTREE、ABSENT、UNCONFIRMED;兼容入口仍返回原 root/commit/ref/dirty 四字段,失败 status 不会变成 clean。
  • release_manifest._source_metadata:只有确定 ABSENT 才读归档清单;自有 .git 查询未回答时保持未知。实际模块 CLI 写入并重新读取 release.json,正常 Git、真正归档和显式提交覆盖保持原结果。

源码编码 fixture 改用完成的 FakeProcess 适配新 Popen 边界;物理 pip/wheel 身份 fixture 补入新依赖的 process_runtime。它们保持原断言,不把模拟 communicate 当作真实生命周期验收。新增真实 fsmonitor 和旧清单用例有独立可观察结果;本轮另外执行完整 CLI 取消与清单写回对照。

对主干的风险 / Risks

最强当前反例是正常取消。一次性仓库的真实 core.fsmonitor hook 启动同组心跳子进程,等待它确实写入后才发送 SIGINT。base 的 Git 与 doctor 同组,取消后无活 helper;新 head 的 Git 在独立组,在约1.2秒的完整观察窗口中 CLI 已退出,但仍有两个活 helper,返回后的400ms内心跳继续增长。复核使用显式关闭使用遥测的子进程环境,结果相同。测试端只清理自己创建的进程组;没有对用户运行中的进程动手。缺陷来自本 PR 的会话隔离及缺失中断收尾,不是仅凭静态推测新增要求。

相反,保留默认10秒的真实超时查询已返回,后代心跳停止;完整观测约10.93秒,包含返回后的400ms存活观察。基线在约17.25秒的完整观测中由测试外层护栏停止,不能称基线也有产品时限。清单的独立10次 CLI 观察保留旧 head 的失败:真实源码是 B、旧清单为 A,只延迟根查询,旧1cce在约10秒写入A;当前6f7在约10秒写入 commit/dirty 均未知及 local_checkout。不可变 main 仍等待后得到B,正常/归档/显式覆盖配对通过,移除延迟后当前版本重新写入B。这里不据单次耗时宣称性能比例或完整成本收益。

PASS:七个完整 Python 模块100项;CI 声明范围的 Ruff、六文件 Ruff、配置指定17根 mypy、diff advisory及完整 semantic vocabulary check、六路径公开边界扫描。真实正常 doctor 完整 JSON 在仅归一化已验证的源码路径、带时区观察钟和临时探针 PID 后一致,没有删字段或修复建议。首次对照保留了这些非确定值造成的不等结果;一条准备命令误写不存在的测试路径而未运行,随后只更正路径,没有改产品或断言。

语义与 CI 对齐

FAIL 并保留:当前 TS typecheck 的 conversation_binding.ts:1144 TS2345,以及 raw maintainability ratchet 的既有超大 interaction 函数。两条同命令都在不可变54e基线和当前head复现,完整输出分别逐字相同;相关生产整 blob、配置和依赖定义未变,且本 PR 的 Python 行为有独立证据,属于 pre_existing_unrelated。没有修这些相邻债,也没有把失败改成通过。精确 CQR cqr_1f6d87bf763046df1b91 为 FAIL、verify为invalid_receipt,保留取消失败及未豁免的验证失败。原生 quick premerge 的6个直接检查和3个选中检查通过,整体仍是 quality_invalid_receipt;没有放行合并。按当前 wait_for_ci=false 未获取、轮询或等待 CI。

这是默认从无时限改为每次查询10秒,不是 default-off 功能或整个 doctor 共用一个截止时间;PR说明与新增预算回归已披露。GitSourceState 是两个相邻 Python IO 消费者共享的局部分类,不是 Core 执行/配额词汇或新的持久协议。原环境过滤、类型化 revision relation、可信 remote 过滤与归档显式来源规则保留;没有把建议升级成执行义务。

我的整体评价 / Verdict

REQUEST_CHANGES。超时回收和旧清单隔离已经有真实反例转正,完整机制体量相称;但取消会使已启动工作失去收尾 owner,long_horizon=regression,user_experience=regression。请完成首段的取消清理与真实回归,再重新核验当前完整 head;不能因前两条已修或100项通过就继承批准。

已应用的同域整理是收拢 Git IO、复用 OS 生命周期及保留 typed unknown;无需另造状态、服务或要求 TypeScript 重写。兼容字典有现存 doctor/skill 消费者,新增 source-state pair 只供清单 owner,未引入版本 decoder 或持久迁移。

已检查 doctor 的 CLI JSON/Markdown、安装清单和 skill readback 消费者;差异不改变渲染页面或布局,视觉审查不适用。原生 Windows、真实安装及宿主/模型采用和长程成本未在本轮资格化。纯评审没有改作者源码、dismiss旧评审、安装或合并;旧失败和本轮新发现均保留。

English verdict: REQUEST_CHANGES — 6f7dad1. Real timeout cleanup and stale-manifest isolation pass, but terminal-group SIGINT exits doctor while its isolated Git helpers keep running; matched base controls stop them, reproduced twice. Retire owned work on interruption and propagate cancellation. 100 focused tests pass; unchanged baseline TS/ratchet failures and the failed exact-scope quality gate remain disclosed.

Comment thread loopx/doctor_git.py
return None
try:
stdout, stderr = process.communicate(timeout=GIT_PROBE_TIMEOUT_SECONDS)
except subprocess.TimeoutExpired:

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] 新会话需要在取消时收尾。这里仅捕获 TimeoutExpired;终端 Ctrl-C 向 doctor 所在进程组发 SIGINT 时,communicate 抛 KeyboardInterrupt,独立会话中的 Git/fsmonitor 收不到信号,也没有进入 _retire_owned_probe。真实 doctor+真实 fsmonitor 两次匹配对照:base取消后心跳停止,head在CLI退出后的400ms仍由40增至75字节且两个helper活着。请在中断时回收本次拥有的组并重新抛出原取消,保留超时unknown;增加真实CLI取消和后代停止回归。合成进程已由测试端清理。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

已修,新 head 28ded5091。

取消时由等待的人回收它启动的会话

_run_git 现在在 communicate 上区分两种「没等到答案」:

  • TimeoutExpired:回收本次调用自己的会话,返回 None,维持「未知」。
  • 其他中断(终端 Ctrl-C 的 KeyboardInterrupt、SystemExit 等):同样走 _retire_owned_probe 回收同一个会话,然后 raise 原样抛出。

独立会话让预算能收回 Git 的后代,代价就是这个会话收不到终端信号——那条路径上唯一的 owner 是正在 communicate 的这次调用,所以退出前必须在同一个所有权范围内收尾。超时语义没有变,Ctrl-C 仍是 Ctrl-C,不是被吞掉的错误。

真实 CLI 的取消回归

新增 test_a_cancelled_query_takes_the_helpers_it_started:以 start_new_session=True 启动真实的 manifest writer CLI(python -m loopx.release_manifest <release_root> --release-id … --source-root <repo>,即安装路径里读来源身份的那个入口),一次性仓库里 core.fsmonitor 指向真实 hook,hook 带自己的子进程持续写心跳。等心跳确实出现、确认 CLI 仍在等待后,向 CLI 所在进程组 发 SIGINT,也就是终端 Ctrl-C 的作用范围。

条件 改前 head 6f7dad1ee 本 head
CLI 等待 fsmonitor 时收到组 SIGINT CLI 退出,清单未写;hook 与子进程仍存活,退出后心跳 15 字节 → 40 字节 CLI 退出,清单未写;心跳停止

断言覆盖三件事:取消后 CLI 以非零退出、没有留下 release.json、以及返回后 0.6 秒内心跳不再增长。每次运行都会写 stop 文件并回收测试自己创建的进程组;用例按 os.name != "posix" skip,合成进程不出测试目录。

同文件与 tests/test_release_manifest_unanswered_git.py 等六个模块合计 98 passed;test_doctor_installation_scope.py::…[None] 在未改动的 origin/main 上同样失败,属既有环境问题。

`doctor` inspects a checkout the operator may not control, and none of its
six Git reads had a budget: `rev-parse` twice, the metadata runner behind
`git_metadata_for_root`, `merge-base --is-ancestor`, `remote`,
`remote get-url` and `rev-parse --verify`. A repository blocked on a stale
index lock, a network mount or a very large history therefore held the
diagnostic open with no output at all.

Reproduced with a fake `git` on PATH that only sleeps:
`_is_git_worktree_root` was still running after 5s.

All six reads now go through `_run_git`, bounded by
`GIT_PROBE_TIMEOUT_SECONDS`. The helper answers `None` when the budget runs
out, which is the answer every caller already treats as "unknown": root
detection fails closed, metadata fields stay `None`, revision relation stays
`UNKNOWN`, and the trusted release ref is refused. No new failure mode, and
the diagnostic still returns instead of raising.

The same class in `loopx/contract.py` was bounded in loopx-project#8152.

Signed-off-by: ChengLong GU <88359731+gcl-coder@users.noreply.github.com>
Two gaps the review found in the ten-second budget.

`subprocess.run(timeout=...)` retires the Git leader only. Git starts its
helpers -- fsmonitor hooks, pagers, credential helpers -- inside the group it
runs in, so a blocked `status` left the hook and its child behind and repeated
diagnostics accumulated them. The query now launches in a session of its own,
and an exhausted budget retires that group through the existing
`terminate_process_tree` before dropping the pipes instead of draining them: an
inherited pipe outlives the leader, so a drain would wait on work this call no
longer owns. Reproduced with a `core.fsmonitor` hook that never answers and
keeps a child writing heartbeats -- leader-only kill left it growing, the group
retirement stops it.

`_is_git_worktree_root` answered False both for "there is no Git here" and for
"there is Git here and it did not answer". `release_manifest._source_metadata`
stood an older `release.json` in for the second case, so a checkout at commit B
that carried a manifest claiming A wrote A back as its own source. The two
cases are separate states now, and only a checkout with no `.git` of its own
falls back to the recorded manifest; an unanswered read stays unknown.

Signed-off-by: ChengLong GU <88359731+gcl-coder@users.noreply.github.com>
The budget covered an exhausted wait, not an interrupted one. Git runs in a
session of its own so the budget can retire its helpers, which also means a
terminal Ctrl-C -- a SIGINT to the process group of the running `doctor` --
never reaches Git. `communicate` raised `KeyboardInterrupt` out of the only
place that owns that session, the diagnostic exited, and the fsmonitor hook
and its child kept running. Repeated cancellations accumulate the same
background work the timeout fix removed.

The wait now retires the owned session on any interrupted exit and re-raises
the original interruption, so an exhausted budget keeps answering "unknown"
while Ctrl-C stays Ctrl-C. Reproduced with the manifest writer CLI, real Git
and a real `core.fsmonitor` hook that keeps a child writing heartbeats:
interrupting the writer's group left the heartbeat growing on the previous
head and stops it now.

Signed-off-by: ChengLong GU <88359731+gcl-coder@users.noreply.github.com>
@gcl-coder
gcl-coder force-pushed the fix/doctor-git-probe-budget branch from 6f7dad1 to 28ded50 Compare November 6, 2026 02:45

@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; model=gpt-6.1-sol; provider=OpenAI; runtime_reported; reasoning_effort=max

APPROVE:当前完整 head 未发现阻塞性代码缺陷。 先前超时遗留进程、旧来源清单顶替以及 Ctrl-C 遗留新会话三项均已在真实入口复验。此为独立代码评审结论;原有 blocking reviews、严格质量门与合并授权仍须分别处理。

精确 head:28ded5091c3f1c0ba127d2d4e927e08d5f97ef2d;完整差异基点:927d0b389096a9ca8702c4da14a43836063ed29b。

动机 / Motivation

用现有 doctor 检查源码、或由安装流程写入来源清单的维护者。 Git 的 fsmonitor hook 阻塞时,基线会一直等待;当前版本按每次查询的预算返回未知,并在超时或 Ctrl-C 时结束自己启动的 Git 进程组。 真实超时与取消均已看到后代停止;根查询未回答时保留未知来源,不再用同目录的旧清单顶替,移除延迟后又能读回当前提交。 本轮不资格化原生 Windows、已安装 App 或宿主采用、长期性能和完整成本,也不授予安装或合并权限。

不修复会保留无界等待;只让父进程返回仍可能积累后台工作。来源读取又被安装清单消费,不能把“已有 Git 本次未答”当作“这里没有 Git”,否则速度更快却记录了错误身份。当前切片已同时覆盖这两个实际消费者,不需要完成整个性能或安装路线图才交付这项修复。

改动思路 / Approach

查询与来源判定留在现有 Python provider IO 边界,复用 process_runtime 的 OS 生命周期能力;没有新增全局服务、配置或执行 owner。 本 PR 的边界是既有 doctor 与清单来源读取的每次查询预算、未知结果和退出收尾;其他 Git owner 与整个 doctor 的总截止时间不在本切片内。

doctor 和 manifest writer 仍使用原有入口;新 helper 负责启动自己的 Git 会话,等待成功时返回原查询数据,超时时回收该会话并返回未知,取消时同样回收后继续传播原中断。清单 owner 只在确定没有自有 Git 时读取归档清单。这个分类由当前 IO 事实推导,不是人工同步的持久状态,也不授予运行、安装或来源认证权限。

已比较相邻 registry/contract 的 Git 读取与 process_runtime:共享的是 OS 生命周期,诊断与来源规则仍留在原 owner。将它们搬进一个全局 Git 服务或新增配置成本更大;当前收拢调用与保留字典兼容的整理已经足够。

具体改动 / Changes

完整六文件 +535/−112,生产两文件、fixture 四文件。相对上次评审 6f7dad1eea49ae32940031b4a0e7d5213e944920,新增取消收尾和真实 manifest CLI 回归;本轮仍重新检查整个基线到 head,而非只确认这一个分支。

本切片没有独立设计 RFC,采用已读的前次评审框架:https://github.com/loopx-project/loopx/pull/8153#pullrequestreview-5794986746,规格修订 6f7dad1eea49ae32940031b4a0e7d5213e944920。该框架无原生条款号,下列 RF 标识仅为本次逐项映射:RF-1 每次 Git 查询有界且失败未知,implemented;RF-2 取消时收回 owned group 并传播取消,implemented;RF-3 未回答的自有 Git 不得用旧清单顶替,implemented。归档输入与显式提交覆盖维持原合同。

关键代码讲解

  • _run_git:六类查询共享每次10秒预算、UTF-8 和既有 Git 环境过滤。TimeoutExpired 与其他中断分别保留“未知”及“继续取消”的结果,两个分支都回收同一个 owned session。
  • _retire_owned_probe:复用已有树终止能力,再关闭捕获管道并有界 wait;没有无限排空被后代持有的管道,也不触及无关进程组。
  • git_source_metadata:用 local GitSourceState 分辨 WORKTREE、ABSENT、UNCONFIRMED,旧 git_metadata_for_root 仍返回原四字段。状态来自一次实际读取,没有新持久协议或双重 owner。
  • release_manifest._source_metadata:只有 ABSENT 才走归档回退。源码 B 根查询暂未回答时写出 commit/dirty 未知;真正归档 A 和显式覆盖 C 的来源仍保持原样。

编码 fixture 适配 Popen,物理 pip/wheel fixture 补入实际新增依赖;没有删原断言。新增取消测试使用真实 writer CLI 和真实 fsmonitor,另外本轮独立通过实际 doctor CLI 复验,避免用 mocked communicate 证明生命周期。

对主干的风险 / Risks

真实 core.fsmonitor hook 及子进程写入正向心跳后才触发判断:当前超时完整观察约10.69秒,返回后400ms心跳900→900,无活 helper;组 SIGINT 后 CLI 非零退出,心跳40→40,无活 helper。基线超时路径在约17.14秒仍未自行结束,由外层测试护栏终止;基线取消正常停止。这里不是整个 doctor 的10秒 SLO,也没有从单次耗时推断性能比例或长程收益。所有合成组已由测试端回收。

另有10次真实 manifest CLI 写入/独立读回,覆盖正常 Git、归档、显式提交覆盖、延迟根查询与移除延迟后的恢复:当前延迟路径 commit/dirty 均未知、kind 为 local_checkout;恢复后重新得到真实提交。正常 doctor 完整 JSON 仅归一化已验证的源码路径、带时区观察钟和 PID 探针后相等,没有删诊断、修复指引或字段。

PASS:七个完整 Python 模块101项,CI 声明范围 Ruff、六文件 Ruff、配置指定17根 mypy、完整 semantic vocabulary 和 diff advisory、全部变更路径公开边界扫描。初次错误 smoke 路径、无 output 的 inventory --check 及默认 runtime-root 冲突均保留为未成功的准备尝试;更正命令/隔离 fixture 后验证通过,未改产品或放宽断言。

语义与 CI 对齐

FAIL 保留:TS 的 conversation_binding.ts:1144 TS2345、raw maintainability 的旧超大 interaction 函数。两项同命令在不可变 base/head 的完整输出分别逐字相同,致因整 blob、配置与依赖也未改;当前 Python 行为有独立通过证据,归因为 pre_existing_unrelated。没有将这些必检变绿或要求本 PR 修复相邻债。严格 CQR cqr_a3b977a878dead8a8b00 为 FAIL,verify 为 invalid_receipt;quick premerge 的6项直接检查和3项选中 smoke 通过,整体仍是 quality_invalid_receipt。未获取、轮询或等待远端 CI。

本改动默认从无时限改为每次查询10秒,已由 PR说明及预算/生命周期回归披露;不是 opt-in/default-off。局部来源枚举没有扩充 Core 配额或执行词汇,未将 guidance 变成机器义务。未知清理结果不被认证为已完成;原生 Windows 保持未验证。

我的整体评价 / Verdict

problem_context=goal_achieved 仅指这个 Git 读取/来源修复切片;long_horizon=improved、user_experience=improved 依据当前有界反例、真实取消和恢复结果,不代表整项父目标或全成本已验收。整体机制相称,兼容字典、真实归档、显式覆盖及可信远端过滤保留;同域整理已应用,无需再增加服务、协议版本或不相关 TypeScript 重写。

已检查 doctor 的 CLI JSON/Markdown、来源清单和 skill readback 调用链,差异不改变渲染页面或布局,视觉审查不适用。 原生 Windows、已安装 App/宿主采用和长期完整成本未验证。 严格质量门仍失败,既有评审状态与合并资格不由这个代码结论自动清除。本轮未改作者源码、dismiss评审或合并。

English verdict: APPROVE — 28ded50. Real doctor timeout and terminal-group cancellation now retire owned Git helpers; stale-manifest isolation and recovery pass, with healthy full-output parity and 101 tests. Unchanged baseline TS/ratchet failures and the strict quality/merge hold remain.

@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; model=gpt-6.1-sol; provider=OpenAI; runtime_reported; reasoning_effort=max

APPROVE:当前完整 head 未发现阻塞性代码缺陷。 先前超时遗留进程、旧来源清单顶替以及 Ctrl-C 遗留新会话三项均已在真实入口复验。此为独立代码评审结论;原有 blocking reviews、严格质量门与合并授权仍须分别处理。

精确 head:28ded5091c3f1c0ba127d2d4e927e08d5f97ef2d;完整差异基点:927d0b389096a9ca8702c4da14a43836063ed29b。

动机 / Motivation

用现有 doctor 检查源码、或由安装流程写入来源清单的维护者。 Git 的 fsmonitor hook 阻塞时,基线会一直等待;当前版本按每次查询的预算返回未知,并在超时或 Ctrl-C 时结束自己启动的 Git 进程组。 真实超时与取消均已看到后代停止;根查询未回答时保留未知来源,不再用同目录的旧清单顶替,移除延迟后又能读回当前提交。 本轮不资格化原生 Windows、已安装 App 或宿主采用、长期性能和完整成本,也不授予安装或合并权限。

不修复会保留无界等待;只让父进程返回仍可能积累后台工作。来源读取又被安装清单消费,不能把“已有 Git 本次未答”当作“这里没有 Git”,否则速度更快却记录了错误身份。当前切片已同时覆盖这两个实际消费者,不需要完成整个性能或安装路线图才交付这项修复。

改动思路 / Approach

查询与来源判定留在现有 Python provider IO 边界,复用 process_runtime 的 OS 生命周期能力;没有新增全局服务、配置或执行 owner。 本 PR 的边界是既有 doctor 与清单来源读取的每次查询预算、未知结果和退出收尾;其他 Git owner 与整个 doctor 的总截止时间不在本切片内。

doctor 和 manifest writer 仍使用原有入口;新 helper 负责启动自己的 Git 会话,等待成功时返回原查询数据,超时时回收该会话并返回未知,取消时同样回收后继续传播原中断。清单 owner 只在确定没有自有 Git 时读取归档清单。这个分类由当前 IO 事实推导,不是人工同步的持久状态,也不授予运行、安装或来源认证权限。

已比较相邻 registry/contract 的 Git 读取与 process_runtime:共享的是 OS 生命周期,诊断与来源规则仍留在原 owner。将它们搬进一个全局 Git 服务或新增配置成本更大;当前收拢调用与保留字典兼容的整理已经足够。

具体改动 / Changes

完整六文件 +535/−112,生产两文件、fixture 四文件。相对上次评审 6f7dad1eea49ae32940031b4a0e7d5213e944920,新增取消收尾和真实 manifest CLI 回归;本轮仍重新检查整个基线到 head,而非只确认这一个分支。

本切片没有独立设计 RFC,采用已读的前次评审框架:https://github.com/loopx-project/loopx/pull/8153#pullrequestreview-5794986746,规格修订 6f7dad1eea49ae32940031b4a0e7d5213e944920。该框架无原生条款号,下列 RF 标识仅为本次逐项映射:RF-1 每次 Git 查询有界且失败未知,implemented;RF-2 取消时收回 owned group 并传播取消,implemented;RF-3 未回答的自有 Git 不得用旧清单顶替,implemented。归档输入与显式提交覆盖维持原合同。

关键代码讲解

  • _run_git:六类查询共享每次10秒预算、UTF-8 和既有 Git 环境过滤。TimeoutExpired 与其他中断分别保留“未知”及“继续取消”的结果,两个分支都回收同一个 owned session。
  • _retire_owned_probe:复用已有树终止能力,再关闭捕获管道并有界 wait;没有无限排空被后代持有的管道,也不触及无关进程组。
  • git_source_metadata:用 local GitSourceState 分辨 WORKTREE、ABSENT、UNCONFIRMED,旧 git_metadata_for_root 仍返回原四字段。状态来自一次实际读取,没有新持久协议或双重 owner。
  • release_manifest._source_metadata:只有 ABSENT 才走归档回退。源码 B 根查询暂未回答时写出 commit/dirty 未知;真正归档 A 和显式覆盖 C 的来源仍保持原样。

编码 fixture 适配 Popen,物理 pip/wheel fixture 补入实际新增依赖;没有删原断言。新增取消测试使用真实 writer CLI 和真实 fsmonitor,另外本轮独立通过实际 doctor CLI 复验,避免用 mocked communicate 证明生命周期。

对主干的风险 / Risks

真实 core.fsmonitor hook 及子进程写入正向心跳后才触发判断:当前超时完整观察约10.69秒,返回后400ms心跳900→900,无活 helper;组 SIGINT 后 CLI 非零退出,心跳40→40,无活 helper。基线超时路径在约17.14秒仍未自行结束,由外层测试护栏终止;基线取消正常停止。这里不是整个 doctor 的10秒 SLO,也没有从单次耗时推断性能比例或长程收益。所有合成组已由测试端回收。

另有10次真实 manifest CLI 写入/独立读回,覆盖正常 Git、归档、显式提交覆盖、延迟根查询与移除延迟后的恢复:当前延迟路径 commit/dirty 均未知、kind 为 local_checkout;恢复后重新得到真实提交。正常 doctor 完整 JSON 仅归一化已验证的源码路径、带时区观察钟和 PID 探针后相等,没有删诊断、修复指引或字段。

PASS:七个完整 Python 模块101项,CI 声明范围 Ruff、六文件 Ruff、配置指定17根 mypy、完整 semantic vocabulary 和 diff advisory、全部变更路径公开边界扫描。初次错误 smoke 路径、无 output 的 inventory --check 及默认 runtime-root 冲突均保留为未成功的准备尝试;更正命令/隔离 fixture 后验证通过,未改产品或放宽断言。

语义与 CI 对齐

FAIL 保留:TS 的 conversation_binding.ts:1144 TS2345、raw maintainability 的旧超大 interaction 函数。两项同命令在不可变 base/head 的完整输出分别逐字相同,致因整 blob、配置与依赖也未改;当前 Python 行为有独立通过证据,归因为 pre_existing_unrelated。没有将这些必检变绿或要求本 PR 修复相邻债。严格 CQR cqr_a3b977a878dead8a8b00 为 FAIL,verify 为 invalid_receipt;quick premerge 的6项直接检查和3项选中 smoke 通过,整体仍是 quality_invalid_receipt。未获取、轮询或等待远端 CI。

本改动默认从无时限改为每次查询10秒,已由 PR说明及预算/生命周期回归披露;不是 opt-in/default-off。局部来源枚举没有扩充 Core 配额或执行词汇,未将 guidance 变成机器义务。未知清理结果不被认证为已完成;原生 Windows 保持未验证。

我的整体评价 / Verdict

problem_context=goal_achieved 仅指这个 Git 读取/来源修复切片;long_horizon=improved、user_experience=improved 依据当前有界反例、真实取消和恢复结果,不代表整项父目标或全成本已验收。整体机制相称,兼容字典、真实归档、显式覆盖及可信远端过滤保留;同域整理已应用,无需再增加服务、协议版本或不相关 TypeScript 重写。

已检查 doctor 的 CLI JSON/Markdown、来源清单和 skill readback 调用链,差异不改变渲染页面或布局,视觉审查不适用。 原生 Windows、已安装 App/宿主采用和长期完整成本未验证。 严格质量门仍失败,既有评审状态与合并资格不由这个代码结论自动清除。本轮未改作者源码、dismiss评审或合并。

English verdict: APPROVE — 28ded50. Real doctor timeout and terminal-group cancellation now retire owned Git helpers; stale-manifest isolation and recovery pass, with healthy full-output parity and 101 tests. Unchanged baseline TS/ratchet failures and the strict quality/merge hold remain.

@huangruiteng
huangruiteng merged commit f91e26d into loopx-project:main Nov 6, 2026
27 of 38 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.

3 participants