@maxiaochao/pi-worktree 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-10-12 - v0.4.0
4
+
5
+ - Replace CLI start and prepare with the create-only command
6
+ - replace the CLI `start` and `prepare` commands with one idempotent `create` command that never launches Pi
7
+
8
+ ## 2026-10-09 - v0.3.2
9
+
10
+ - continue Agent tasks automatically after switching into a worktree
11
+
3
12
  ## 2026-10-09 - v0.3.1
4
13
 
5
14
  - show a persistent in-conversation confirmation after switching worktrees
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @maxiaochao/pi-worktree
2
2
 
3
- `pi-worktree` CLI、Pi Extension 与配套 skill。它既能启动独立 Pi,也能让用户或 Agent 在当前 Pi 界面中切换 worktree,并保留完整对话。
3
+ `pi-worktree` CLI、Pi Extension 与配套 skill。CLI 负责管理 worktree;Extension 让用户或 Agent 在当前 Pi 界面中切换,并保留完整对话。
4
4
 
5
5
  ![Worktree 在真实临时 Git 仓库中的运行截图](docs/screenshot.png)
6
6
 
@@ -13,14 +13,14 @@
13
13
  ![Worktree 隔离流程演示](docs/demo.gif)
14
14
 
15
15
  ```bash
16
- pi-worktree start feature -p "implement the feature"
17
- pi-worktree prepare --json feature
16
+ pi-worktree create feature
17
+ pi-worktree create --base main --json feature
18
18
  pi-worktree info feature
19
19
  pi-worktree out
20
20
  pi-worktree remove feature
21
21
  ```
22
22
 
23
- `start` 从当前分支创建或复用 `.worktrees/feature`,记录 base,并在该目录启动 Pi。`prepare` 复用同一创建逻辑,但只返回目标,不启动嵌套 Pi。
23
+ `create` 从当前分支(或显式 `--base`)创建或复用 `.worktrees/feature` 并记录 base,不启动 Pi。`--json` 提供相同操作的机器可读结果。
24
24
 
25
25
  在交互式 Pi 中,用户可以保留当前界面和完整对话:
26
26
 
@@ -61,8 +61,7 @@ pi update npm:@maxiaochao/pi-worktree # 更新独立子包
61
61
  ## 命令
62
62
 
63
63
  ```bash
64
- pi-worktree start <name> [pi args...]
65
- pi-worktree prepare [--base <ref>] [--json] <name>
64
+ pi-worktree create [--base <ref>] [--json] <name>
66
65
  pi-worktree list
67
66
  pi-worktree info <name>
68
67
  pi-worktree out
@@ -70,6 +69,6 @@ pi-worktree remove <name>
70
69
  pi-worktree prune
71
70
  ```
72
71
 
73
- `prepare --json` 是 Extension 和其他宿主使用的幂等机器接口,返回 `created`、`attached`、`reused` 或 `already-active`,以及绝对路径、分支和 dirty 状态。已有 worktree 会复用;已存在但未挂载的同名分支会挂载;stale 注册、非法名称和路径冲突会显式失败。
72
+ `create` 是幂等接口;`create --json` 供 Extension 和其他宿主使用,返回 `created`、`attached`、`reused` 或 `already-active`,以及绝对路径、分支和 dirty 状态。已有 worktree 会复用;已存在但未挂载的同名分支会挂载;stale 注册、非法名称和路径冲突会显式失败。
74
73
 
75
74
  Agent 用法见 `skills/pi-worktree/SKILL.md`。测试流程、夹具和结果见 [TESTING.md](TESTING.md)。
package/TESTING.md CHANGED
@@ -4,19 +4,19 @@
4
4
 
5
5
  1. `npm --prefix packages/worktree test`:在临时 Git 仓库中运行 CLI,并验证 Extension 注册、延迟命令桥接和 session 切换生命周期。
6
6
  2. `npm pack ./packages/worktree --dry-run`:确认 CLI、completion、skill 和安装脚本进入 tarball。
7
- 3. 手工验收发布包的 `pi-worktree start` 交互流程。
7
+ 3. 手工验收发布包的 `pi-worktree create` 创建与复用流程。
8
8
  4. 确认 README 示例、真实临时 Git 仓库输出 `docs/screenshot.png` 和 `docs/demo.gif` 进入 tarball。
9
9
 
10
10
  ## 用例数据
11
11
 
12
12
  - `--help` 输出命令说明。
13
- - `start` 从当前分支创建 `.worktrees/<name>` 并记录 base。
14
- - `prepare --json` 覆盖 created、reused、already-active、dirty 和非法名称安全失败。
13
+ - `create` 从当前分支创建 `.worktrees/<name>` 并记录 base。
14
+ - `create --json` 覆盖 created、reused、already-active、dirty 和非法名称安全失败。
15
15
  - `/worktree start` 把历史 fork 到 cwd 正确的新 session,再调用 `switchSession`。
16
- - `enter_worktree` 串行准备目标,在 `agent_settled` 后触发内部命令,并 fork 当前 session 后调用 `switchSession`。
16
+ - `enter_worktree` 串行创建或复用目标,在 `agent_settled` 后触发内部命令,并 fork 当前 session 后调用 `switchSession`。
17
17
  - Agent 轮被中止时不触发切换;用户取消 session replacement 时删除派生 session。
18
18
  - 自定义 session 目录在 fork 后保持不变。
19
- - 假 Pi 收到正确工作目录和参数。
19
+ - 已删除的 CLI `start` 和 `prepare` 会显式失败。
20
20
  - `list` 标记主 checkout 和子 worktree。
21
21
  - `info` 报告 clean、branch、base 和 ahead。
22
22
  - postinstall 把可执行 CLI 与 completion 复制到隔离 HOME,并保持执行权限。
package/bin/pi-worktree CHANGED
@@ -1,10 +1,8 @@
1
1
  #!/usr/bin/env bash
2
- # pi-worktree: create/reuse a git worktree, then launch pi inside it.
2
+ # pi-worktree: create, inspect, and clean up git worktrees.
3
3
  #
4
4
  # Usage:
5
- # pi-worktree start <name> [pi args...] # enter worktree <name> and start pi
6
- # pi-worktree start --base <ref> <name> [pi args...] # branch from <ref> instead of current branch
7
- # pi-worktree prepare [--base <ref>] [--json] <name> # create/reuse without launching pi
5
+ # pi-worktree create [--base <ref>] [--json] <name> # create/reuse without launching pi
8
6
  # pi-worktree -l | --list # list worktrees (main checkout marked "main checkout")
9
7
  # pi-worktree -i <name> # show base commit, ahead/behind, unique commits
10
8
  # pi-worktree -o | --out # cd back to the main checkout
@@ -229,13 +227,13 @@ cmd_remove() {
229
227
  if [[ "$ans" =~ ^[Yy]$ ]]; then
230
228
  git branch -D "$branch" && ok "deleted branch: $branch"
231
229
  else
232
- warn "branch '$branch' kept; 'pi-worktree start $branch' will reuse it as-is"
230
+ warn "branch '$branch' kept; 'pi-worktree create $branch' will reuse it as-is"
233
231
  fi
234
232
  fi
235
233
  fi
236
234
  }
237
235
 
238
- prepare_wt() {
236
+ create_wt() {
239
237
  local name="$1"
240
238
  require_repo
241
239
  validate_name "$name"
@@ -247,11 +245,11 @@ prepare_wt() {
247
245
  current_branch=$(git branch --show-current)
248
246
  [[ "$current_branch" == "$name" ]] \
249
247
  || die "already inside linked worktree '$current_branch'; return to the main checkout first"
250
- PREPARE_STATUS="already-active"
251
- PREPARE_DIR=$(pwd -P)
252
- PREPARE_BRANCH="$current_branch"
253
- PREPARE_DIRTY=false
254
- if [[ -n "$(git status --porcelain)" ]]; then PREPARE_DIRTY=true; fi
248
+ CREATE_STATUS="already-active"
249
+ CREATE_DIR=$(pwd -P)
250
+ CREATE_BRANCH="$current_branch"
251
+ CREATE_DIRTY=false
252
+ if [[ -n "$(git status --porcelain)" ]]; then CREATE_DIRTY=true; fi
255
253
  return 0
256
254
  fi
257
255
 
@@ -275,7 +273,7 @@ prepare_wt() {
275
273
  || die "$dir is not checked out on expected branch '$name'"
276
274
  ensure_gitignore
277
275
  ok "reusing existing worktree $dir"
278
- PREPARE_STATUS="reused"
276
+ CREATE_STATUS="reused"
279
277
  else
280
278
  local target_abs registered_path
281
279
  target_abs="$dir"
@@ -292,7 +290,7 @@ prepare_wt() {
292
290
  git worktree add "$dir" "$name"
293
291
  fi
294
292
  ok "checked out existing branch '$name'"
295
- PREPARE_STATUS="attached"
293
+ CREATE_STATUS="attached"
296
294
  # record a best-effort base for -i display
297
295
  local mb; mb=$(git merge-base main "$name" 2>/dev/null || true)
298
296
  [[ -n "$mb" ]] && git config "branch.$name.base" "$(git rev-parse --short "$mb")" \
@@ -307,71 +305,46 @@ prepare_wt() {
307
305
  git config "branch.$name.base" "$(git rev-parse --short "$base")"
308
306
  git config "branch.$name.baseRef" "$base"
309
307
  ok "created branch '$name' from $base @ $(git rev-parse --short "$base")"
310
- PREPARE_STATUS="created"
308
+ CREATE_STATUS="created"
311
309
  fi
312
310
  fi
313
311
 
314
- PREPARE_DIR=$(cd "$dir" && pwd -P)
315
- PREPARE_BRANCH=$(git -C "$dir" branch --show-current)
316
- PREPARE_DIRTY=false
317
- if [[ -n "$(git -C "$dir" status --porcelain)" ]]; then PREPARE_DIRTY=true; fi
312
+ CREATE_DIR=$(cd "$dir" && pwd -P)
313
+ CREATE_BRANCH=$(git -C "$dir" branch --show-current)
314
+ CREATE_DIRTY=false
315
+ if [[ -n "$(git -C "$dir" status --porcelain)" ]]; then CREATE_DIRTY=true; fi
318
316
  }
319
317
 
320
- print_prepare_json() {
321
- PI_WORKTREE_STATUS="$PREPARE_STATUS" \
322
- PI_WORKTREE_PATH="$PREPARE_DIR" \
323
- PI_WORKTREE_BRANCH="$PREPARE_BRANCH" \
324
- PI_WORKTREE_DIRTY="$PREPARE_DIRTY" \
318
+ print_create_json() {
319
+ PI_WORKTREE_STATUS="$CREATE_STATUS" \
320
+ PI_WORKTREE_PATH="$CREATE_DIR" \
321
+ PI_WORKTREE_BRANCH="$CREATE_BRANCH" \
322
+ PI_WORKTREE_DIRTY="$CREATE_DIRTY" \
325
323
  node -e 'process.stdout.write(JSON.stringify({status:process.env.PI_WORKTREE_STATUS,path:process.env.PI_WORKTREE_PATH,branch:process.env.PI_WORKTREE_BRANCH,dirty:process.env.PI_WORKTREE_DIRTY==="true"})+"\n")'
326
324
  }
327
325
 
328
- cmd_prepare() {
326
+ cmd_create() {
329
327
  BASE_OVERRIDE=""
330
328
  local json=0
331
329
  while [[ $# -gt 0 ]]; do
332
330
  case "$1" in
333
331
  --base)
334
- shift; [[ $# -ge 1 ]] || die "usage: pi-worktree prepare [--base <ref>] [--json] <name>"
332
+ shift; [[ $# -ge 1 ]] || die "usage: pi-worktree create [--base <ref>] [--json] <name>"
335
333
  BASE_OVERRIDE="$1"; shift ;;
336
334
  --json) json=1; QUIET=1; shift ;;
337
- --*) die "unknown prepare option: $1" ;;
335
+ --*) die "unknown create option: $1" ;;
338
336
  *) break ;;
339
337
  esac
340
338
  done
341
- [[ $# -eq 1 ]] || die "usage: pi-worktree prepare [--base <ref>] [--json] <name>"
342
- prepare_wt "$1"
339
+ [[ $# -eq 1 ]] || die "usage: pi-worktree create [--base <ref>] [--json] <name>"
340
+ create_wt "$1"
343
341
  if [[ "$json" == 1 ]]; then
344
- print_prepare_json
342
+ print_create_json
345
343
  else
346
- ok "$PREPARE_STATUS worktree: $PREPARE_DIR ($PREPARE_BRANCH)"
344
+ ok "$CREATE_STATUS worktree: $CREATE_DIR ($CREATE_BRANCH)"
347
345
  fi
348
346
  }
349
347
 
350
- cd_wt() {
351
- local name="$1"; shift
352
- require_repo
353
-
354
- # Already isolated? Preserve the original start behavior and launch Pi here.
355
- if in_linked_worktree; then
356
- ok "already in an isolated worktree ($(pwd)), launching pi here"
357
- exec pi "$@"
358
- fi
359
-
360
- # Dirty workspace: uncommitted changes do NOT follow into the new worktree.
361
- local dir; dir="$(main_checkout)/.worktrees/$name"
362
- if [[ ! -d "$dir" ]] && { ! git diff --quiet || ! git diff --cached --quiet || [[ -n "$(git ls-files --others --exclude-standard)" ]]; }; then
363
- warn "Uncommitted changes stay in THIS directory; they will NOT appear in the worktree:"
364
- git status --short | head -20
365
- read -rp "Continue? [y/N] " ans
366
- [[ "$ans" =~ ^[Yy]$ ]] || die "aborted"
367
- fi
368
-
369
- prepare_wt "$name"
370
-
371
- cd "$PREPARE_DIR"
372
- exec pi "$@"
373
- }
374
-
375
348
  case "${1:-}" in
376
349
  -l|--list|list|ls) shift; cmd_list "$@" ;;
377
350
  -i|--info|info) shift; cmd_info "$@" ;;
@@ -379,15 +352,7 @@ case "${1:-}" in
379
352
  -r|--remove|rm|remove) shift; cmd_remove "$@" ;;
380
353
  -p|--prune|prune) shift; subcmd_prune ;;
381
354
  -h|--help|help) sed -n '2,10p' "$0" | sed 's/^# \{0,1\}//' ;;
382
- prepare) shift; cmd_prepare "$@" ;;
383
- start) shift
384
- BASE_OVERRIDE=""
385
- if [[ "${1:-}" == "--base" ]]; then
386
- shift; [[ $# -ge 2 ]] || die "usage: pi-worktree start --base <ref> <name> [pi args...]"
387
- BASE_OVERRIDE="$1"; shift
388
- fi
389
- [[ $# -ge 1 ]] || die "usage: pi-worktree start <name> [pi args...]"
390
- name="$1"; shift; cd_wt "$name" "$@" ;;
391
- "") die "usage: pi-worktree start <name> [pi args...] (or list / info / out / remove / prune)" ;;
392
- *) die "unknown command: $1 (usage: pi-worktree start <name> [pi args...])" ;;
355
+ create) shift; cmd_create "$@" ;;
356
+ "") die "usage: pi-worktree create <name> (or list / info / out / remove / prune)" ;;
357
+ *) die "unknown command: $1 (usage: pi-worktree create <name>)" ;;
393
358
  esac
@@ -4,7 +4,7 @@ _pi_worktree() {
4
4
  cur="${COMP_WORDS[COMP_CWORD]}"
5
5
  first="${COMP_WORDS[1]}"
6
6
 
7
- local SUBS="start prepare list ls info out remove rm prune -l --list -i --info -o --out -r --remove -p --prune -h --help"
7
+ local SUBS="create list ls info out remove rm prune -l --list -i --info -o --out -r --remove -p --prune -h --help"
8
8
 
9
9
  # existing worktree names (only linked worktrees, i.e. under .worktrees/)
10
10
  local wts=""
@@ -17,13 +17,13 @@ _pi_worktree() {
17
17
  fi
18
18
 
19
19
  if (( COMP_CWORD == 1 )); then
20
- # first word: subcommands, flags, and existing worktree names (to enter)
20
+ # first word: subcommands, flags, and existing worktree names
21
21
  COMPREPLY=($(compgen -W "$SUBS $wts" -- "$cur"))
22
22
  return
23
23
  fi
24
24
 
25
25
  case "$first" in
26
- -i|--info|info|-r|--remove|remove|rm|start|prepare)
26
+ -i|--info|info|-r|--remove|remove|rm|create)
27
27
  # only the second word of info/remove completes worktree names;
28
28
  # later words offer nothing (info/remove take no further args)
29
29
  if (( COMP_CWORD == 2 )); then
@@ -33,7 +33,7 @@ _pi_worktree() {
33
33
  fi
34
34
  ;;
35
35
  *)
36
- # after a name: default (file) completion for pi args
36
+ # no command-specific completion
37
37
  COMPREPLY=()
38
38
  ;;
39
39
  esac
package/docs/demo.gif CHANGED
Binary file
Binary file
@@ -7,14 +7,14 @@ import { Type } from "typebox";
7
7
  const CLI_PATH = fileURLToPath(new URL("../bin/pi-worktree", import.meta.url));
8
8
  const PENDING_SWITCH_ARGUMENT = "__enter_worktree_pending__";
9
9
 
10
- export interface WorktreePrepareResult {
10
+ export interface WorktreeCreateResult {
11
11
  status: "created" | "attached" | "reused" | "already-active";
12
12
  path: string;
13
13
  branch: string;
14
14
  dirty: boolean;
15
15
  }
16
16
 
17
- export interface EnterWorktreeResult extends WorktreePrepareResult {
17
+ export interface EnterWorktreeResult extends WorktreeCreateResult {
18
18
  switch: "scheduled" | "already-active";
19
19
  }
20
20
 
@@ -49,41 +49,41 @@ function parseCommandArgs(input: string): { name: string; base?: string } {
49
49
  return { name, base };
50
50
  }
51
51
 
52
- function parsePrepareOutput(stdout: string): WorktreePrepareResult {
52
+ function parseCreateOutput(stdout: string): WorktreeCreateResult {
53
53
  const line = stdout.trim().split("\n").filter(Boolean).at(-1);
54
- if (!line) throw new Error("pi-worktree prepare 没有返回结果");
55
- const result = JSON.parse(line) as Partial<WorktreePrepareResult>;
54
+ if (!line) throw new Error("pi-worktree create 没有返回结果");
55
+ const result = JSON.parse(line) as Partial<WorktreeCreateResult>;
56
56
  if (
57
57
  !["created", "attached", "reused", "already-active"].includes(String(result.status))
58
58
  || typeof result.path !== "string"
59
59
  || typeof result.branch !== "string"
60
60
  || typeof result.dirty !== "boolean"
61
61
  ) {
62
- throw new Error("pi-worktree prepare 返回了无效结果");
62
+ throw new Error("pi-worktree create 返回了无效结果");
63
63
  }
64
- return result as WorktreePrepareResult;
64
+ return result as WorktreeCreateResult;
65
65
  }
66
66
 
67
- async function prepareWorktree(
67
+ async function createWorktree(
68
68
  pi: ExtensionAPI,
69
69
  cwd: string,
70
70
  name: string,
71
71
  base?: string,
72
72
  signal?: AbortSignal,
73
- ): Promise<WorktreePrepareResult> {
74
- const args = ["prepare", "--json"];
73
+ ): Promise<WorktreeCreateResult> {
74
+ const args = ["create", "--json"];
75
75
  if (base) args.push("--base", base);
76
76
  args.push(name);
77
77
  const result = await pi.exec(CLI_PATH, args, { cwd, signal });
78
78
  if (result.code !== 0) {
79
- throw new Error(result.stderr.trim() || result.stdout.trim() || `pi-worktree prepare 失败(退出码 ${result.code})`);
79
+ throw new Error(result.stderr.trim() || result.stdout.trim() || `pi-worktree create 失败(退出码 ${result.code})`);
80
80
  }
81
81
  if (signal?.aborted) throw new Error("worktree 创建已取消");
82
- return parsePrepareOutput(result.stdout);
82
+ return parseCreateOutput(result.stdout);
83
83
  }
84
84
 
85
85
  export default function worktreeExtension(pi: ExtensionAPI) {
86
- let pendingSwitch: WorktreePrepareResult | undefined;
86
+ let pendingSwitch: WorktreeCreateResult | undefined;
87
87
  let pendingDispatchScheduled = false;
88
88
  let transitionReserved = false;
89
89
 
@@ -108,7 +108,7 @@ export default function worktreeExtension(pi: ExtensionAPI) {
108
108
  description: "创建或复用 worktree,并在当前界面切换过去",
109
109
  handler: async (input, ctx) => {
110
110
  const isPendingSwitch = input.trim() === PENDING_SWITCH_ARGUMENT;
111
- let prepared: WorktreePrepareResult;
111
+ let created: WorktreeCreateResult;
112
112
  let targetSessionFile: string;
113
113
  try {
114
114
  if (!isPendingSwitch && transitionReserved) {
@@ -127,25 +127,25 @@ export default function worktreeExtension(pi: ExtensionAPI) {
127
127
 
128
128
  await ctx.waitForIdle();
129
129
  if (requestedSwitch) {
130
- prepared = requestedSwitch;
130
+ created = requestedSwitch;
131
131
  } else {
132
132
  const { name, base } = parseCommandArgs(input);
133
- prepared = await prepareWorktree(pi, ctx.cwd, name, base);
133
+ created = await createWorktree(pi, ctx.cwd, name, base);
134
134
  }
135
- if (resolve(ctx.cwd) === resolve(prepared.path)) {
135
+ if (resolve(ctx.cwd) === resolve(created.path)) {
136
136
  if (isPendingSwitch) {
137
137
  pendingSwitch = undefined;
138
138
  pendingDispatchScheduled = false;
139
139
  }
140
140
  transitionReserved = false;
141
- ctx.ui.notify(`已经位于 worktree:${prepared.path}`, "info");
141
+ ctx.ui.notify(`已经位于 worktree:${created.path}`, "info");
142
142
  return;
143
143
  }
144
144
 
145
145
  const sessionDir = ctx.sessionManager.usesDefaultSessionDir()
146
146
  ? undefined
147
147
  : ctx.sessionManager.getSessionDir();
148
- const target = SessionManager.forkFrom(sourceSessionFile, prepared.path, sessionDir);
148
+ const target = SessionManager.forkFrom(sourceSessionFile, created.path, sessionDir);
149
149
  targetSessionFile = target.getSessionFile() ?? "";
150
150
  if (!targetSessionFile) throw new Error("无法为 worktree 创建持久会话");
151
151
  } catch (error) {
@@ -162,13 +162,13 @@ export default function worktreeExtension(pi: ExtensionAPI) {
162
162
  try {
163
163
  switched = await ctx.switchSession(targetSessionFile, {
164
164
  withSession: async (next) => {
165
- const dirtyNote = prepared.dirty ? "(已有未提交改动)" : "";
165
+ const dirtyNote = created.dirty ? "(已有未提交改动)" : "";
166
166
  await next.sendMessage({
167
167
  customType: "worktree-switch",
168
- content: `✓ 已进入 worktree:${prepared.path}\nBranch: ${prepared.branch}${dirtyNote}`,
168
+ content: `✓ 已进入 worktree:${created.path}\nBranch: ${created.branch}${dirtyNote}\n\n在worktree继续任务`,
169
169
  display: true,
170
- details: prepared,
171
- }, { triggerTurn: false });
170
+ details: created,
171
+ }, { triggerTurn: true, deliverAs: "followUp" });
172
172
  },
173
173
  });
174
174
  } catch (error) {
@@ -214,28 +214,28 @@ export default function worktreeExtension(pi: ExtensionAPI) {
214
214
  }
215
215
  transitionReserved = true;
216
216
 
217
- let prepared: WorktreePrepareResult;
217
+ let created: WorktreeCreateResult;
218
218
  try {
219
- prepared = await prepareWorktree(pi, ctx.cwd, params.name, params.base, signal);
219
+ created = await createWorktree(pi, ctx.cwd, params.name, params.base, signal);
220
220
  if (signal.aborted) throw new Error("worktree 创建已取消");
221
221
  } catch (error) {
222
222
  transitionReserved = false;
223
223
  throw error;
224
224
  }
225
- const alreadyActive = resolve(ctx.cwd) === resolve(prepared.path);
225
+ const alreadyActive = resolve(ctx.cwd) === resolve(created.path);
226
226
  const result: EnterWorktreeResult = {
227
227
  switch: alreadyActive ? "already-active" : "scheduled",
228
- ...prepared,
228
+ ...created,
229
229
  };
230
230
  if (alreadyActive) transitionReserved = false;
231
- else pendingSwitch = prepared;
232
- const dirtyNote = prepared.dirty ? " 目标 worktree 已有未提交改动。" : "";
231
+ else pendingSwitch = created;
232
+ const dirtyNote = created.dirty ? " 目标 worktree 已有未提交改动。" : "";
233
233
  return {
234
234
  content: [{
235
235
  type: "text",
236
236
  text: alreadyActive
237
- ? `已经位于 worktree:${prepared.path}.${dirtyNote}`
238
- : `Worktree ${prepared.status}: ${prepared.path}.${dirtyNote} 当前 Agent run 结束后会自动切换到该目录;不要继续调用仓库工具。`,
237
+ ? `已经位于 worktree:${created.path}.${dirtyNote}`
238
+ : `Worktree ${created.status}: ${created.path}.${dirtyNote} 当前 Agent run 结束后会自动切换到该目录;不要继续调用仓库工具。`,
239
239
  }],
240
240
  details: result,
241
241
  structuredContent: result,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maxiaochao/pi-worktree",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Git worktree CLI, Pi extension, and skill for isolated coding tasks.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -15,9 +15,7 @@ Installed by the package postinstall hook to `~/.local/bin/pi-worktree`, with ba
15
15
  |---|---|
16
16
  | `enter_worktree({ name, base? })` | Agent entrypoint: create/reuse a worktree and automatically move this conversation there after the run settles. Call it alone and stop after it returns. |
17
17
  | `/worktree start <name> [--base <ref>]` | User entrypoint: fork the current persistent session into the worktree and switch the current Pi UI. |
18
- | `pi-worktree prepare [--base <ref>] [--json] <name>` | Create/reuse without launching Pi; `--json` is the machine interface. |
19
- | `pi-worktree start <name> [pi args...]` | Create or reuse `.worktrees/<name>`, then launch Pi inside it. Already inside a linked worktree, it just launches Pi there. |
20
- | `pi-worktree start --base <ref> <name> [pi args...]` | Same, but branch from `<ref>` instead of the current branch. |
18
+ | `pi-worktree create [--base <ref>] [--json] <name>` | Idempotently create/reuse without launching Pi; `--json` is the machine interface. |
21
19
  | `pi-worktree list` | All worktrees; the main checkout is marked `(main checkout)`, stale registrations are flagged. |
22
20
  | `pi-worktree info <name>` | Clean/dirty status, branch and HEAD, base commit, ahead/behind `main`, unique commits. |
23
21
  | `pi-worktree out` | Open a shell in the main checkout (for merging). |
@@ -27,7 +25,7 @@ Installed by the package postinstall hook to `~/.local/bin/pi-worktree`, with ba
27
25
  ## Isolating a task
28
26
 
29
27
  1. **Survey** — `pi-worktree list`, plus `pi-worktree info <name>` when the name you want is already taken. Reuse is free; a removed worktree is gone for good, because `remove` deletes its branch.
30
- 2. **Enter** — as an Agent, call `enter_worktree` once and stop; the Extension switches automatically after settlement. A user can instead run `/worktree start <name>`. Use the CLI `start` only when a separate Pi process is wanted.
28
+ 2. **Enter** — as an Agent, call `enter_worktree` once and stop; the Extension switches automatically after settlement. A user can instead run `/worktree start <name>`.
31
29
  3. **Work** — inside the worktree: commit there, and check `pi-worktree info <name>` before claiming progress. `ahead: N commits since base` is the evidence.
32
30
  4. **Land** — `pi-worktree out`, then `git merge <name>` in the main checkout. Done when the worktree's unique commits are in the target branch.
33
31
  5. **Clean up** — `pi-worktree remove <name>`. A merged branch goes without prompting; anything unmerged asks once.
@@ -36,6 +34,5 @@ Installed by the package postinstall hook to `~/.local/bin/pi-worktree`, with ba
36
34
 
37
35
  - Prefer `enter_worktree` when it is available. After the tool returns, do not read or modify repository files; the Extension waits for `agent_settled`, then forks the session into the target cwd and switches automatically.
38
36
  - `/worktree` is user-facing and cannot be invoked by the model. It requires a persisted session because it forks history into a session whose header owns the worktree cwd.
39
- - `pi-worktree start` **execs a new `pi`** in the worktree, and `out` execs a shell. Use this only for an intentionally separate process/session.
40
- - `prepare` is idempotent. It reports whether the target was created, attached, reused, or already active, and reports dirty reuse rather than discarding anything.
37
+ - `create` is idempotent and never launches Pi. It reports whether the target was created, attached, reused, or already active, and reports dirty reuse rather than discarding anything.
41
38
  - `remove` asks before discarding dirty files or unmerged commits, and a non-interactive stdin (EOF) aborts safely. Answer explicitly when the removal is intended: `printf 'y\ny\n' | pi-worktree remove <name>`.