Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions doc/architecture/SPEC/engine/steps/pr.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<!-- verified-against: 2026-09-26 -->

`LOC ~1390(6 个文件) · step 库(PR) · refactor-status: ok`
`LOC ~1988(6 个文件) · step 库(PR) · refactor-status: ok`

## 职责
受守卫的推送、只读的 PR 抓取/门禁、PR rebase、PR debug、受门禁的评审发布。
Expand All @@ -20,15 +20,19 @@
- `utils.py` —— 纯函数 `extract_signature`(及其正则)。

## Steps(12 个)
`ci.push`(script/push);`pr.fetch_diff`、`pr.gate_check`、`pr.checkout_branch`、
`ci.push`(script/push);`pr.fetch_diff`、`pr.gate_check`、
`pr.analyze_diff`、`pr.fetch_ci_failures`、`pr.group_failures`(deterministic/read);
`pr.checkout_branch`(deterministic/write_workspace);
`pr.harvest_debug_knowledge`(deterministic/knowledge);
`pr.rebase_onto_base`、`agent.debug_group`(agent/write_workspace);
`agent.verify_module`(validation/read);`pr.post_review`(script/push)。

`pr-debug` 的 report-only 路径不 checkout PR 分支。诊断组数受 playbook 参数和
settings 安全上限共同约束;执行组共享 checkout 时必须顺序修改。`agent.debug_group`
只有在观察到新 commit、干净的 tracked checkout、根因和验证证据时才算成功。
`pr-rebase` 的 report-only 路径在按 run 分键的私有 detached worktree 内本地
rebase,不修改配置 checkout 的分支或文件,不推送。该树是一次 run 的可变 scratch,
不是 `pr.fetch_diff` 使用的、按 head 分键的不可变评审快照。
`agent.verify_module` 的咨询结论是 `verified | problem | inconclusive`,未知文本
不能标成已验证。`ci.push` 对 PR debug/rebase 还需同一 HEAD 的 patch-gate 批准。
知识 intake 的 canonical 记录使用 schema v2、完整仓库名和稳定 event ID;缺少
Expand Down Expand Up @@ -64,7 +68,13 @@ settings 安全上限共同约束;执行组共享 checkout 时必须顺序修
`gh` 不可用时降级为 BLOCKED,**绝不崩溃**。
- `pr.checkout_branch` 把推导出的 `PushPolicy` **序列化后发布**(**B2**)——
在推送处 resume 时**绝不能**看到那个 deny-all 的默认值。
- `pr.checkout_branch` 的 report-only 模式只在私有树里 checkout,并经
`state_updates` 发布私有 `repo_path` 和配置 checkout 路径;resume 时所有后续
rebase/analyze/verify step 核对私有树身份、仓库归属及 detached HEAD,树若已被 reaper 清理就
BLOCKED,绝不回退到配置 checkout。
- `pr.rebase_onto_base`:由受治理 agent 解冲突,或 abort+升级(**工作区始终被还原**)。
判断 rebase 是否完成时通过 `git rev-parse --git-path` 定位每棵 worktree
自己的元数据;不能假设 `.git` 是目录。抓取 base 或生成 diff 失败时 BLOCKED。
- `pr.fetch_ci_failures` 经 profile 选定的 CI provider 富化日志,否则记一条
`capability_gap`(**E2**);`pr.group_failures` 按**归一化后**的签名分组。
- `pr.post_review` 是**双闸**的(**C5**)。
Expand All @@ -89,12 +99,14 @@ settings 安全上限共同约束;执行组共享 checkout 时必须顺序修
不含 agent 治理(那是 `agent_runtime`)。

## 依赖(允许)
`scopes`、`push`、`ci/*`、`adapters/base`(analyze)、`engine/step`、`.._common`、
`scopes`、`push`、`ci/*`、`adapters/base`(analyze)、`engine/step`、
`engine/worktrees`、`.._common`、
`..agent_runtime`。

## 测试
`test_pr_steps.py`(含钉 ref run 域隔离、head 移动检测、stale expected_head
BLOCK、worktree 分键/拒外来树)、`test_push_and_steps.py`、
BLOCK、worktree 分键/拒外来树、report-only rebase 的隔离与失效树拦截)、
`test_push_and_steps.py`、
`test_knowledge_harvest.py`(harvest step + executor crash-then-resume)、
`test_ci_and_repo_map.py`(注意:`test_ci_and_repo_map` monkeypatch 的是
`pr.debug._gh`,即 `pr.fetch_ci_failures` 绑定 `gh` 的那个子模块);
Expand Down
24 changes: 17 additions & 7 deletions doc/architecture/SPEC/engine/worktrees.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# engine/worktrees.py —— 规范

<!-- verified-against: 2026-08-28 -->
<!-- verified-against: 2026-09-26 -->

`LOC ~231 · PR-time worktree:身份、物化、持有 · refactor-status: ok`
`LOC ~294 · PR-time worktree:身份、物化、持有 · refactor-status: ok`

## 职责
PR 快照 worktree 的三条性质,每条都对应一个修掉的真实事故:
Expand All @@ -20,9 +20,14 @@ checkout 之间碰撞,force-remove 曾删掉一个活评审的树)、
`owned_by(repo, dest, sha, git)`;`materialize(repo, sha, dest, git,
timeout=300)`;`hold(dest, run_dir)`;`held_paths()`;`GitRunner` 类型别名
(git 以可调用注入 —— 本模块不含 subprocess 策略,可测)。
`mutable_dest_for(repo, pr, run_dir)` 和 `materialize_mutable(repo, sha, dest,
git, timeout=300)` 专供 report-only PR rebase:每个 run 独占一棵可变的 detached
worktree,允许本地 rebase,又不会改配置的 checkout 或复用评审的不可变快照。
`owned_by_repository(repo, dest, git)` 在可变树的 HEAD 改动后核对归属。

## 不变量
- **reaper 只删匹配 `_MANAGED_DEST`(`-hex8-prN-hex12` 后缀)的树**:
- **reaper 只删匹配 `_MANAGED_DEST`(`-hex8-prN-hex12` 后缀)或
`_MUTABLE_DEST`(`-hex8-prN-runhex12` 后缀)的树**:
worktrees 根是共享 scratch,长期存着其他工具的树 —— 一次"见什么删什么"
的扫描曾经毁掉过别人的工作,这个守卫因此存在。
- `materialize` 对 git 级失败**绝不抛**(调用方决定 block 还是降级),
Expand All @@ -33,6 +38,11 @@ timeout=300)`;`hold(dest, run_dir)`;`held_paths()`;`GitRunner` 类型别
恰是 head 门要防的"评错树"。
- 复用验证:`--git-common-dir` 必须解析回请求方仓库自己的 git dir **且**
HEAD == sha;force-remove 只对外来/撕裂的树成立,绝不对同身份的活树。
- 可变树按 run_dir 分键,复用时只核对仓库归属(rebase 会改变 HEAD),
拒绝 symlink、重定向到配置 checkout 的路径及外来仓库;调用方仅在 checkout
step 重试时可重置此树。后续 step 经 checkpoint 恢复路径,并在树被清理后
fail closed,绝不退回 live checkout。调用方还要确认 HEAD 保持 detached,
防止重置或 rebase 意外移动共享仓库里的命名分支。
- `hold()` 幂等(进程内 memo),经 `lifecycle.register_finalizer` 在每条
退出路径释放,进程死亡时内核兜底 —— 崩掉的 run 绝不永久钉住一棵树。
- **持有挂在"使用"而不是"创建"上**(`engine/steps/_common.py::repo_path`
Expand All @@ -42,17 +52,17 @@ timeout=300)`;`hold(dest, run_dir)`;`held_paths()`;`GitRunner` 类型别
## 边界 —— 不属于这里
无 subprocess 策略(git runner 注入);无 run 生命周期逻辑(持有原语之外
—— 那是 `engine/lifecycle.py`);何时物化/何时 block 是
`engine/steps/pr/fetch.py` 的决定。
`engine/steps/pr/fetch.py` 和 `engine/steps/pr/rebase.py` 的决定。

## 依赖(允许)
stdlib(hashlib/os/re/time/pathlib)+ `.lifecycle`
(fcntl 守卫、register_finalizer、require_file_locking)。

## 测试
`test_pr_steps.py`(materialize 创建与复用、同 basename 仓库分键、
拒绝外来树、fetch_diff 端到端钉住);`test_idempotency.py`
(reaper 只碰自己键出的树、放过被持有的树)。
拒绝外来树、fetch_diff 端到端钉住、report-only rebase 的隔离与 resume);
`test_idempotency.py`(reaper 只碰自己键出的不可变及可变树、放过被持有的树)。

## 重构备注
新模块(PR2)。键格式与 `_MANAGED_DEST` 必须同步演进 —— 改其一忘其二,
新模块(PR2)。键格式与 `_MANAGED_DEST` / `_MUTABLE_DEST` 必须同步演进 —— 改其一忘其二,
reaper 就会开始放过(或误删)新格式的树。
3 changes: 3 additions & 0 deletions doc/architecture/SPEC/task_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ expected_head_sha?, repo_path?)`;property `tier`、`read_only`、
- **C1**:**不存在可设置的 tier 字段**;`tier = KIND_TIER[kind]` —— 文本无法把它扩大。
- 只读 kind 的 `read_only` = `not post`,其余为 `report_only`;
`confirm_required = not read_only`。
- 这里的 `read_only` 是**任务权限/对外发布**的推导,不表示执行期间零文件写入:
`pr_rebase` 的 report-only 模式允许在按 run 隔离的 disposable worktree
本地 rebase,但不能修改配置 checkout 的分支/文件或推送 PR head。
- **快照绑定字段只收窄,绝不扩权**(C1 完整无损):`expected_head_sha`
(field_validator 强制 `FULL_SHA_RE`;设置后 run 只准评审这个 head,
否则以 stale 停下)与 `repo_path`(预约时冻结、由
Expand Down
Loading
Loading