Files
my-pi/pi-worktree/README.md
T
云服务部-叶林立andDevin <158243242+devin-ai-integration[bot]@users.noreply.github.com> 97c2e75dfe feat(pi-worktree): 新增斜杠命令 git worktree 管理扩展
提供 /wt(别名 /worktree)一键拆分/合并:
- /wt new 建 worktree 后经 SessionManager.forkFrom + switchSession
  迁移会话 cwd,历史上下文保留;合并/切换同理
- /wt merge 先确认再 rebase 到来源分支、默认 --ff-only(可选
  --squash),来源改动用 stash 兜底,冲突可中止或保留后用
  /wt merge --continue / --abort 处理,永不 push
- 来源 checkout 的 .pi/worktree.json 声明环境复刻(copy/link/env/
  postCreate),确认框先展示计划再执行;postCreate 仅在项目已信任
  时以 argv 形式 execFile,失败可 /wt reopen 增量重跑
- 登记文件 <agentDir>/pi-worktree/state.json 原子写入并加锁
  串行化;会话切换后向模型注入 cwd 变更提示;未被 .gitignore
  覆盖的软链/环境文件写入 .git/info/exclude 防止误提交
- 仅注册用户显式触发的斜杠命令,不暴露模型可调用工具

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-19 17:45:41 -07:00

81 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# pi-worktree
本仓库自维护的 Pi 扩展:只用斜杠命令管理 git worktree,提供「一键拆分」(`/wt new`)与「一键合并」(`/wt merge`)。不注册任何模型可调用的工具。
所有 git 操作都通过 `node:child_process` 的 `execFile` 以 argv 形式调用(不经过 shell),普通命令 30 秒超时,`worktree add` / `remove` 为 120 秒。因为这些命令由用户显式触发,它们不经过 Bash 工具的权限策略。
## 命令
`/wt` 与别名 `/worktree` 等价;不带参数时弹出子命令菜单。
| 命令 | 说明 |
| --- | --- |
| `/wt new <slug> [--from <ref>]` | 在 `<root>/<仓库名>/<slug>` 创建 worktree,签出新分支 `<branchPrefix><slug>`(默认 `wt/<slug>`),确认后把当前会话切换进 worktree |
| `/wt merge [--squash] [--keep] [--continue] [--abort]` | 在当前 worktree 内执行:确认 → rebase 到来源分支 → 快进合并(或 squash)回来源分支 → 切回来源目录并清理 worktree 与分支。`--keep` 保留 worktree、分支和登记;rebase 冲突保留时可用 `--continue`/`--abort` 续跑或放弃 |
| `/wt switch [<slug>\|main]` | 切换到某个登记的 worktree;`main` 切回该 worktree 的来源 checkout。无参数时弹列表 |
| `/wt list` | 列出登记的 worktree:分支、路径、`(缺失)`、`(有未提交改动)`、`+N` 领先数,以及非本扩展管理的其它 git worktree |
| `/wt remove <slug> [--force]` | 删除指定 worktree 与分支;有未提交改动或未合并提交时要求 `--force` 并二次确认。不能删除当前所在的 worktree |
| `/wt prune` | 执行 `git worktree prune` 并清除目录已不存在的登记项 |
| `/wt reopen [<slug>]` | 重新读取来源目录的 `.pi/worktree.json` 并补跑环境初始化(已存在的文件/软链自动跳过);默认 slug 为当前所在 worktree |
## 流程与安全规则
- **拆分**:确认对话框显示新分支、基准 ref 和短 SHA、目标路径;创建并登记后才切换会话。会话切换失败不会回滚已创建的 worktree,可用 `/wt switch <slug>` 重试。
- **合并**:必须先校验来源 checkout 仍在登记的分支上;worktree 未提交改动先经确认后 `add -A` + commit;随后展示 diffstat 并要求确认——rebase 改写分支的动作只发生在确认之后。确认后若来源目录有未提交改动先 `stash push`,再 rebase 到来源分支并合并,无论成败都在 finally 中 `stash pop`(pop 失败时提示 stash 名称)。rebase 冲突时弹出选择:「中止 rebase,保持原状」或「保留冲突,稍后手动解决」;保留后在 worktree 中解决冲突并 `git add`,再运行 `/wt merge --continue` 续走合并流程,或用 `/wt merge --abort` 放弃。默认只允许快进合并,`--squash` 压缩为单个提交;**永远不会 push**。
- **删除**:拒绝删除当前所在的 worktree;有未提交改动或领先提交时必须 `--force` 并明确列出将丢弃的内容。
- **状态栏**:进入登记的 worktree 后 footer 显示 `🌳 <slug>`,离开后清除。
## 会话切换机制
Pi 不支持修改进程 cwd。`/wt new` / `merge` / `switch` 通过 `SessionManager.forkFrom`(或为空会话写入最小 session 文件)生成 header.cwd 指向目标目录的新会话文件,再调用 `ctx.switchSession` 让 Pi 以新 cwd 重建运行时;切换后的收尾工作在 `withSession` 回调里用新 `ctx` 完成。
切换成功后,扩展会先往新会话追加一条 `[pi-worktree] 工作目录已从 … 切换到 …` 的模型可见提示(`customType: pi-worktree`、`display: true`,空闲时直接写入 session 文件,不触发新轮次),告知此前旧 cwd 下的路径可能已不存在。
## 状态登记与锁
登记文件为 `<agentDir>/pi-worktree/state.json`(目录 `700`、文件 `600`、原子写入)。所有写操作经 `updateState` 在 `<path>.lock` 锁文件内「读取-修改-保存」,防止多个 Pi 进程互相覆盖登记;锁文件陈旧 30 秒会被接管,正常操作下最长等待 5 秒。读取(list、补全、findByCwd)不加锁。
## 环境复刻:`.pi/worktree.json`
在**来源 checkout**(读取来源是 `entry.sourcePath`,不是新 worktree)提交 `.pi/worktree.json` 后,`/wt new` 创建 worktree 时会按顺序执行 复制 → 软链 → 环境文件 → postCreate 四个阶段,确认对话框会预先列出完整计划:
```jsonc
{
"version": 1, // 必填
"copy": [".env.local", ".env.*.local", "config/local.*"],
"link": ["node_modules", ".gradle"],
"env": { "PORT": "{{port}}", "DB_NAME": "app_{{slug}}" },
"envFile": ".env.worktree", // 默认 .env.worktree
"portBase": 3000, // 默认 3000
"postCreate": [
{ "command": "npm", "args": ["ci"], "timeoutMs": 600000 }
]
}
```
规则:
- **复制**:只复制未被 git 跟踪的文件(已跟踪文件应通过提交共享);`*` / `?` 通配符只允许出现在路径最后一段,不支持 `**`。目录暂不复制。
- **软链**:目标必须被 gitignore 忽略,否则拒绝软链以避免改动写回来源目录。
- **环境文件**:`env` 渲染为 `KEY=value` 逐行写入 `envFile`(权限 `600`);模板变量:`{{slug}}`、`{{pathSlug}}`、`{{branch}}`、`{{path}}`、`{{sourcePath}}`、`{{sourceBranch}}`、`{{port}}`,其中 `port = portBase + fnv1a32(slug) % 1000`。
- **info/exclude 兜底**:未被 `.gitignore` 覆盖的软链(如 `node_modules/` 尾斜杠规则不匹配符号链接)和环境文件会写入 `.git/info/exclude`(仓库内所有 worktree 共享同一 common dir),确保 `/wt merge` 的 `commitAll` 不会把它们提交进来源分支。
- **postCreate**:仅当项目受 Pi 信任(`ctx.isProjectTrusted()`)时执行,未受信任的项目只列出步骤并跳过;步骤以 argv 形式调用 `execFile`(不经 shell),并向步骤注入 `PI_WT_PATH` / `PI_WT_SOURCE_PATH` / `PI_WT_BRANCH` / `PI_WT_SOURCE_BRANCH` / `PI_WT_SLUG` / `PI_WT_PORT` 环境变量;任一步失败即停止。
- **永不覆盖**:所有产物(复制、软链、环境文件)在目标已存在时跳过,因此 `/wt reopen` 可以安全地增量补跑;初始化结果记录在登记项的 `setup` 字段,`/wt list` 会标注 `(环境初始化失败:<步骤>)`。
## 状态与配置
- 登记文件:`<agentDir>/pi-worktree/state.json`(`getAgentDir()`,遵循 `PI_CODING_AGENT_DIR`),目录权限 `700`、文件权限 `600`,原子写入;文件损坏或版本未知时命令报错中止,不会被静默覆盖。**不**放在 `<agentDir>/my-pi/` 下,因为该目录每次升级会被整体替换。
- `settings.json` 的 `piWorktree` 键:
- `root`:worktree 根目录,默认 `~/.pi/worktrees`(只展开开头的 `~`,展开后必须是绝对路径)。worktree 路径为 `<root>/<主 worktree 目录名>/<slug>`。
- `branchPrefix`:新分支前缀,默认 `wt/`。
- slug 规则:`A-Za-z0-9._-` 与 `/` 分层,最长 64 字符,不能以 `-` 开头、不能含 `..`、不能以 `.lock` 结尾,保留名 `main` / `HEAD` 不可用。
## 测试
```bash
npm test # node --test test/*.test.ts
npm run check
```
测试在临时目录里创建真实 git 仓库;状态与会话测试使用隔离的 `PI_CODING_AGENT_DIR`。