Skip to content

Commit a2afc82

Browse files
committed
refactor: replace enter command with checkout workflow
Remove the /enter slash command and rename its remaining script adapter to checkout.py. Refresh the target branch before creating a new managed worktree while preserving retained branches as explicit resumptions. Update guard guidance, documentation, and focused lifecycle coverage.
1 parent b4b00f2 commit a2afc82

26 files changed

Lines changed: 182 additions & 101 deletions

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ devloop/ # ← 仓库根(marketplace)
3131
├── .opencode/marketplace.json # opencode marketplace 索引(占位,按协议补)
3232
3333
├── devloop/ # plugin: 开发者日常工作流(第一个真实 plugin,Claude + Codex)
34-
│ │ # git / MR / lint / test / cwd-aware enter / Board / 硬拦截
34+
│ │ # git / MR / lint / test / cwd-aware context / Board / 硬拦截
3535
│ │ # Claude 坐到原生事件上:CwdChanged / PostCompact / FileChanged / monitors;Codex 用 hooks.codex.json 的事件子集
3636
│ ├── .claude-plugin/plugin.json # Claude manifest
3737
│ ├── .codex-plugin/plugin.json # Codex manifest(hooks 指向 hooks.codex.json)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ enter repo → develop on branch → validate affected Components → commit / p
1414

1515
The domain spine is **PR/MR → Repo → Component**: every PR/MR belongs to one repo, and each repo may contain multiple components with independent build/lint/test toolchains. A workspace is an optional context that aggregates multiple repos; plain single-repo use is fully supported.
1616

17-
The branch is the development axis. For concurrent sessions, a worktree is a special form of branch that provides an isolated checkout; `/enter <repo> --worktree <tag>` manages its creation, reuse, dependency preparation, and cleanup. devloop then projects the current change onto affected components and records validation at the same granularity.
17+
The branch is the development axis. For concurrent sessions, a worktree is a special form of branch that provides an isolated checkout; devloop's managed-worktree helper handles creation, reuse, dependency preparation, and cleanup. devloop then projects the current change onto affected components and records validation at the same granularity.
1818

1919
## The problem
2020

README.zh-CN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
领域主链是 **PR/MR → Repo → Component**:每条 PR/MR 始终属于一个 repo,一个 repo 可以包含多个拥有独立 build/lint/test 工具链的 component。Workspace 是聚合多个 repo 的可选上下文,单仓库形态同样完整支持。
1616

17-
Branch 是开发主轴;面向多 session 并发时,worktree 是 branch 的一种特殊形态,用来提供隔离 checkout。`/enter <repo> --worktree <tag>` 统一管理它的创建、复用、依赖准备和清理。随后,devloop 将本次改动投影到受影响的 component,并以同一粒度记录验证结果。
17+
Branch 是开发主轴;面向多 session 并发时,worktree 是 branch 的一种特殊形态,用来提供隔离 checkout。devloop 的 managed-worktree 脚本统一管理它的创建、复用、依赖准备和清理。随后,devloop 将本次改动投影到受影响的 component,并以同一粒度记录验证结果。
1818

1919
## 要解决什么
2020

devloop/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "devloop",
3-
"version": "0.2.3",
3+
"version": "0.2.4",
44
"description": "Dev-loop workflow, native-first rebuild: git/PR (GitHub + GitLab) + cwd-aware enter + Board-managed context delivery + lint/test gates. Built on native Claude Code events (CwdChanged / PostCompact / FileChanged / monitors).",
55
"author": {
66
"name": "qiankunli",

devloop/.codex-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "devloop",
3-
"version": "0.2.3",
3+
"version": "0.2.4",
44
"description": "Dev-loop workflow: git/PR (GitHub + GitLab) + Board-managed workspace/repo context delivery + lint/test gates + execution-level hard intercepts for Claude Code and Codex.",
55
"author": {
66
"name": "qiankunli",

devloop/AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ devloop/
8787
1. **领域归属沿 `PR/MR → Repo → Component`**:PR/MR 与 branch 生命周期归 Repo,验证范围与验证结果归 Component;Workspace 只聚合上下文。不得重新引入“一个 repo 只有一个代码目录”的假设,具体选择与身份语义见 [`CONCEPTS.md`](./CONCEPTS.md)
8888
2. **入口驱动领域,依赖不反向**`hooks/scripts → domain/lib``domain/` 持有业务事实和合法变化,`lib/` 只提供 Git、forge、配置、通知等技术能力。Git、forge、配置分别经统一 seam,入口不得散调外部协议或复制领域判断。
8989
3. **状态源提供事实,Board 决定组织和投递,guard 读取 live truth**:AGENTS.md 是文字知识源,`.devloop/` 是结构化运行态;Repo、Branch、WorkingTree 与 Session 状态按归属和写入者隔离,验证戳按 Component 记录。Board 只维护 per-session 投递游标,不复制业务事实,也不参与硬门禁判定。详见 [`docs/board.md`](./docs/board.md)[`CONCEPTS.md`](./CONCEPTS.md)
90-
4. **生命周期动作走唯一入口,合法例外才软提示**:commit/push/PR 走 `commit_flow`/smart 脚本;worktree 形态的创建、复用和清理走 `enter.py`。保护分支、失活分支、guest session 等无合法编辑路径的情况硬拦截,有合法例外的 in-flight PR/MR 只注入提示。具体流程见 [`docs/loop.md`](./docs/loop.md)[`docs/lifecycle-hooks.md`](./docs/lifecycle-hooks.md)
90+
4. **生命周期动作走唯一入口,合法例外才软提示**:commit/push/PR 走 `commit_flow`/smart 脚本;checkout 选择及 worktree 形态的创建、复用和清理走 `checkout.py`。保护分支、失活分支、guest session 等无合法编辑路径的情况硬拦截,有合法例外的 in-flight PR/MR 只注入提示。具体流程见 [`docs/loop.md`](./docs/loop.md)[`docs/lifecycle-hooks.md`](./docs/lifecycle-hooks.md)
9191

9292
---
9393

devloop/CONCEPTS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ devloop 内多个 skill / 脚本共用的术语。架构理念见 [`AGENTS.md`](
1313
拿站位去答归属,就是让「没有具体目标该选谁」的启发式去回答「这个文件属于谁」——结论是「改 README → 跑 server 的 lint」,而「为什么是 server 不是 cli」没有任何理由。这正是过去单值 `repo_code_dir` 在多代码目录仓上选错目录的根因。「本轮落在哪些 component」由 `select_components` 按**本次改动**算(产出 `WorkSet`),**不挂在 `Repo` 上当单值属性**——解析结果只带 `target_path` 作 explicit 信号。component 有**两个身份**,别混:`path`(绝对路径)是「这次在哪跑 make」的执行事实;`id`(仓相对路径)是**持久化身份**,跨 checkout / worktree 稳定,落 `.devloop` 的一切 key 都用它(见〈验证状态〉)。两者都在**出生点**由唯一构造入口 `Component.at(path, git_root)` 一次算清——`id` 必填、无默认值:给默认值则生产路径漏传就是静默的空 key(多个 component 撞进同一个戳),让消费方自己算又要各自再传一次 `git_root`(传错 root 算出的 key 就是错的)。绑在出生点,这两类错都不可表达。component 还**拥有自己的工具链动作**(`has_target` / `lint_target` / `test_command`):一个 component「能不能 / 该跑哪个 lint/test 命令」是它自己的事实,checks / gate rules 直接问 component,不再各自拿 `str` 路径去重解析 Makefile 或 Go module。
1414

1515
依赖环境同样属于 component。worktree 创建时预热、lint/test 启动前兜底;验证进程必须使用当前 checkout 的依赖视图,不能向上借主 checkout 的 `node_modules`,也不能沿用指向别处源码的 Python editable environment。跨 checkout 共享的是包管理器缓存,不是整个环境目录。详细模型见 [`docs/worktree-env.md`](./docs/worktree-env.md)
16-
- **`repo_code_dir`** / **default component**:repo 级**默认** component——没有更具体目标路径时用(按名字 `/enter` 一个仓、cwd 就是仓根)。探测规则 `server/` > `backend/` > `repo_dir``repo_layout.find_repo_code_dir` / `default_component`)。这是**选择**启发式(「没有具体目标该选谁」),与上面的**身份**判据(「这个目录是不是 component」)是两个问题,别拿它回答身份——`discover_components` 曾用它补根 component,而 `server/` 存在时它返回 `server/`(早被枚举收过),于是补根永远补不进、根的 go.mod 从 catalog 里消失。Go / TS 单 component 仓通常就是 `repo_dir`。子项目 `AGENTS.md` 一定在默认 component 下面。
16+
- **`repo_code_dir`** / **default component**:repo 级**默认** component——没有更具体目标路径时用(显式按名字选仓、cwd 就是仓根)。探测规则 `server/` > `backend/` > `repo_dir``repo_layout.find_repo_code_dir` / `default_component`)。这是**选择**启发式(「没有具体目标该选谁」),与上面的**身份**判据(「这个目录是不是 component」)是两个问题,别拿它回答身份——`discover_components` 曾用它补根 component,而 `server/` 存在时它返回 `server/`(早被枚举收过),于是补根永远补不进、根的 go.mod 从 catalog 里消失。Go / TS 单 component 仓通常就是 `repo_dir`。子项目 `AGENTS.md` 一定在默认 component 下面。
1717

1818
## 保护分支
1919

@@ -55,7 +55,7 @@ devloop 循环(`enter → 提需求 → 开发 → commit/PR → 人工 merge
5555
聚合工作区下多个 CLI session(claude / codex …)并发操作同一 workspace 是常态;每个 checkout 同一时刻只属于一个 session:
5656

5757
- **owner**:第一个对该 checkout 做**变更动作**的 session——Edit/Write、切分支 / commit 等会碰可变面(working tree / index / 分支位置)的操作,任一建立占有;持有 `<git_root>/.devloop/owner.lock`(pid 存活 + ts-TTL 判活)。占有点:edit guard 首笔编辑 / checkout guard / posttool git 变更。**enter / 只读不占有**:enter 只是选中上下文,多 session 并读不互斥——判据与下面 gitignored 豁免同源:是否污染 owner 的 diff。
58-
- **guest**:其它并发 session。guest 的两条破坏路径被硬拦——切分支(`checkout_owner_guard`)与直接 Edit/Write(`edit_owner_guard`),统一引导去 worktree(`/enter <repo> --worktree <tag>`
58+
- **guest**:其它并发 session。guest 的两条破坏路径被硬拦——切分支(`checkout_owner_guard`)与直接 Edit/Write(`edit_owner_guard`),统一引导走 managed-worktree 脚本创建隔离 checkout
5959
- **gitignored 文件豁免**:guest 写 gitignored 路径(eval 输出、运行日志…)放行——不进 owner 的 status/diff,无混入风险;放行不转移占有权。
6060
- **释放(两层都有)**:session 正常结束由 SessionEnd hook(`sessionend_release`)立即清掉本 session 的全部运行态(owner 锁含 worktree checkout + active 绑定);崩溃 / hook 没跑到时退化到 pid 死亡判活,ts-TTL 仅在 pid 不可探测时兜底。
6161
- 刻意共享 checkout 的逃逸口:人工删 `owner.lock`
@@ -112,7 +112,7 @@ schema / TTL / cap 数值在 `domain/context/base.py`,不在文档复述。
112112

113113
## 脚本的 repo 解析
114114

115-
commit_flow / run_fixlint / run_tests 与 cwd 解耦(session cwd 在聚合工作区常驻 workspace 根):repo 按"显式参数(`--repo` 名/路径)→ cwd 所在仓库 → 本 session 绑定的最近活跃仓(`active/<sid>.json`,见〈Session 运行态〉)"解析,解析来源自述在输出/PLAN 里。本 session 无绑定即拒绝兜底,报错附其它 session 的活跃仓做候选提示。名字走与 `/enter` 相同的模糊匹配(`domain/repo.py`
115+
commit_flow / run_fixlint / run_tests 与 cwd 解耦(session cwd 在聚合工作区常驻 workspace 根):repo 按"显式参数(`--repo` 名/路径)→ cwd 所在仓库 → 本 session 绑定的最近活跃仓(`active/<sid>.json`,见〈Session 运行态〉)"解析,解析来源自述在输出/PLAN 里。本 session 无绑定即拒绝兜底,报错附其它 session 的活跃仓做候选提示。名字统一走 `domain/repo.py` 的模糊匹配
116116

117117
## 占位符 `<PLUGIN_ROOT>`
118118

devloop/README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,29 @@ devloop 通过两个控制杠杆落实这条生命周期:**Board 上下文投
1212

1313
## 它做什么
1414

15-
- **PR/MR 生命周期入口**`/enter` 选择 repo/branch;多 session 并发时,`--worktree <tag>` branch 以隔离 checkout 形态展开,并统一处理基线、依赖准备和安全清理。`/gcam``/gcamp``/gcampr` 依次覆盖 commit、push 与创建/复用 PR/MR;已有 PR/MR 的冲突处理走可恢复的 `smart_rebase.sh start/continue/finish`,以 rebase 前保存的远端 SHA 做精确 `force-with-lease`。新工作从目标分支建立干净基线,最终 merge 始终留给人。
15+
- **PR/MR 生命周期入口**`cd` 选择 repo,原生 `CwdChanged` 自动加载上下文;多 session 并发时,managed-worktree 脚本将 branch 以隔离 checkout 形态展开,并统一处理基线、依赖准备和安全清理。`/gcam``/gcamp``/gcampr` 依次覆盖 commit、push 与创建/复用 PR/MR;已有 PR/MR 的冲突处理走可恢复的 `smart_rebase.sh start/continue/finish`,以 rebase 前保存的远端 SHA 做精确 `force-with-lease`。新工作从目标分支建立干净基线,最终 merge 始终留给人。
1616
- **component 感知**(多代码目录仓):一个 git 仓可能有多个自带工具链、可独立 lint/test 的目录——`server/` + `cli/``packages/*``cmd/*`。devloop 按**本次改动**决定跑哪些:改了 `cli/**` 就只跑 `cli` 的 lint/test,不静默回落仓根或 `server/`;改动跨多个 component 就都跑;clean tree 从仓根发起时枚举**全部** component(绝不替你猜一个)。验证戳也按 component 记——「A 过 B 挂」不会被记成整仓已验。术语见 [`CONCEPTS.md`](./CONCEPTS.md)
1717
- **Board 与 PR/MR 感知**:workspace/repo 的 `.devloop/` 保存当前 branch、工作区、近期 PR/MR、验证和 session 归属等结构化运行态;hook 与 monitor 持续刷新事实,Board 按相关性选择紧凑内容并决定 session/turn 投递。Claude 可用原生 status line 常驻展示工作上下文与健康状态;Codex 在 tmux 中使用三行 sidecar。状态与投递游标都自动排除在 git 提交之外。
1818
- **硬拦截**(PreToolUse deny):保护分支 commit/push、`git add -A`、直接 `git worktree add`、过期分支(PR 已 merged/closed)改文件、别的 session 占用的 checkout 上切分支或改文件(引导 worktree)、工作区根跑子项目命令、裸 `pytest`、uv 项目 `pip install`、编辑 `requirements.txt``lifecycle.pre_commit` 含 lint 时 lint 过期的裸 `git commit` gate。
19-
- **自动进项目**`cd` 进子项目时(`CwdChanged`)自动刷新上下文、浮现 AGENTS.md References,无需手动 `/enter`
19+
- **自动进项目**`cd` 进子项目时(`CwdChanged`)自动刷新上下文、浮现 AGENTS.md References,无需额外命令
2020
- **生命周期 hook**`pre_commit / post_commit / pre_mr / post_mr` 四相位可挂 hook,挂哪相位由 config 决定;两类——**inline 门禁**(失败挡 commit/MR)与 **signal hook**(advisory、后台跑、不挡)。当前内置三个:`lint``test`(门禁),`review`(signal——后台跑 [ocr](https://github.com/alibaba/open-code-review) 审全量改动、结果回流会话、有开放 MR 时发评论)。机制见 [`docs/lifecycle-hooks.md`](./docs/lifecycle-hooks.md);code-review 细节见 [`docs/code-review.md`](./docs/code-review.md)
2121
- **跨 CLI 适配**:Claude 使用完整 native event;Codex 缺少的 `CwdChanged / FileChanged / SessionEnd``PostToolUse`、TTL 与下一轮刷新路径降级补足。
2222

2323
## Slash 命令
2424

2525
| 命令 | 作用 |
2626
|------|------|
27-
| `/enter <name|path> [--worktree <tag>]` | 按名/路径跳进子项目;worktree 会自动准备依赖环境(context 自动加载) |
2827
| `/gcam "<msg>"` | 只 commit |
2928
| `/gcamp "<msg>"` | commit + push |
3029
| `/gcampr "<msg>" [--branch <name>]` | commit + push + 建/复用 PR/MR |
3130
| `/devloop:setup-board` | 为 Claude Code 安装原生 Board status line |
3231

3332
lint / test 无独立 slash 命令:正常由 gcam*`pre_commit` gate 自动触发;手动跑走 fix-lint / run-test skill(自然语言"修下 lint"/"跑下测试")。两条路径**共用同一套 component 选择**,所以 gate 替你跑的和你手动跑的永远是同一批 component;每次执行会自述本轮选了哪些 component、为什么(`changed files under: cli` / `clean tree, all components: …`),选错一眼可见,不用等错的测试跑完再猜。
3433

34+
进入子项目直接 `cd`。需要并发隔离 checkout 时,agent 使用
35+
`python3 "<PLUGIN_ROOT>/scripts/checkout.py" <repo> --worktree <tag>`,该脚本只为受控
36+
worktree 生命周期保留,不作为 slash command 暴露。
37+
3538
保护 / 过期分支上,gcam*`--branch <name>`,脚本会从 `origin/<target>` 切新分支(不给会拒绝并提示)。
3639

3740
gcam* 与 fix-lint / run-test 都不依赖 cwd:默认解析 cwd 所在仓库,在 workspace 根则兜底到最近活跃子项目;用 `--repo <name|path>` 显式指定,无需 `cd` 前缀。

devloop/commands/enter.md

Lines changed: 0 additions & 29 deletions
This file was deleted.

devloop/docs/branch-state.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ devloop 关于分支的所有事实,过去挤在一个 `branch.json` + 一次 `R
4646

4747
`remotes` 由 monitor 每 tick `git ls-remote`(便宜,只拿 SHA,不 fetch 对象)写入 `remote_branches.json`,带 `fetched_at`。注入展示 behind 时强制带它:`behind N (vs main, as of 18:07)`;若 monitor 的真 tip 与本地 `origin/<base>` 镜像不一致,显示 `⚠ trunk moved since fetch … — fetch to recount`**ahead/behind 是关系不是状态**,读时按 `local` vs `base_branch()` 的镜像现算,从不持久化(存它就是上次"behind 0 撒谎"的来源)。
4848

49-
`/enter`(刻意的"去看这个仓")是唯一付得起网络的点:进仓时若 `remote_branches` 陈旧(`REMOTE_VIEW_STALE_SEC`)就重拉 tips;若本地 trunk 镜像确实落后真 tip,再做**一次**有界 `git fetch`(只在真落后时付,clean enter 零成本)
49+
`CwdChanged` auto-enter(刻意的"去看这个仓"是唯一付得起网络的点进仓时若 `remote_branches` 陈旧`REMOTE_VIEW_STALE_SEC`就重拉 tips若本地 trunk 镜像确实落后真 tip再做**一次**有界 `git fetch`只在真落后时付clean enter 零成本
5050

5151
### 3.4 `fork_from`:git 不持久记录的事实,sticky
5252

0 commit comments

Comments
 (0)