@signalridge/pi-worktree 0.49.3
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 +13 -0
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/package.json +65 -0
- package/src/command.ts +725 -0
- package/src/git.ts +1250 -0
- package/src/index.ts +1 -0
- package/src/safe-remove.ts +393 -0
- package/src/session.ts +95 -0
- package/src/settings.ts +285 -0
- package/src/worktree.ts +34 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Refuse removal whenever ignored local data is present, so cleanup never deletes unreviewed files; remove ignored data manually before retrying.
|
|
8
|
+
- Quarantine worktrees before deletion, verify the observed tree, and retain late-created data instead of racing recursive Git removal.
|
|
9
|
+
- Verify the tree snapshot across quarantine and before each deletion, retaining the quarantine when files or metadata change during removal.
|
|
10
|
+
- Revalidate the quarantined tree after the final Git inventory so ignored data created between the command check and quarantine is restored rather than deleted.
|
|
11
|
+
- Use Git's worktree-aware move for quarantine, so metadata removal targets the quarantined path and inverse Git moves restore failed removals safely.
|
|
12
|
+
- Move each quarantined entry to a private tombstone and recheck its identity before unlinking, retaining replacements detected during final deletion.
|
|
13
|
+
- Deregister Git worktree metadata with an exclusive non-directory reservation and isolated metadata prune after moving the real quarantine tree to a tombstone, so Git cannot recursively delete late-created or ignored data.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 narumiruna
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# 🌳 pi-worktree — Safe Git Worktree Management for Pi
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@signalridge/pi-worktree) [](https://pi.dev) [](./LICENSE)
|
|
4
|
+
|
|
5
|
+
`@signalridge/pi-worktree` adds one interactive `/worktree` command for common Git worktree operations and Pi workspace switching.
|
|
6
|
+
|
|
7
|
+
Pi cannot change its parent process working directory with `cd`. This extension performs the safe equivalent: it prepares a Pi session whose cwd is the selected worktree and switches to that session, preserving the current conversation when it has already been persisted.
|
|
8
|
+
|
|
9
|
+
This package is the full interactive worktree manager. It is distinct from the signalridge `pi-worktree-guard` extension, which only guards unsafe worktree-related operations and does not create, remove, prune, or switch worktrees. `pi-worktree` is publishable and stable, but it is not automatically enabled by the signalridge dotfiles configuration: the user's AGENTS constitution forbids worktrees.
|
|
10
|
+
|
|
11
|
+
## ✨ Features
|
|
12
|
+
|
|
13
|
+
- Shows compact main, linked, current, detached, locked, and prunable state in worktree selectors.
|
|
14
|
+
- Creates a new branch worktree or attaches an existing unoccupied local branch.
|
|
15
|
+
- Rejects occupied targets and unresolvable symbolic-link ancestors before Git can create a branch.
|
|
16
|
+
- Suggests `~/.worktrees/<main-worktree-name>/<branch>` by default and lets the user configure the root interactively.
|
|
17
|
+
- Optionally switches Pi into a newly created worktree while continuing the current conversation.
|
|
18
|
+
- Switches among existing registered worktrees through Pi's public session replacement API.
|
|
19
|
+
- Removes unlocked, non-current linked worktrees and preserves their branches.
|
|
20
|
+
- Refuses removal when tracked, untracked, manually index-flagged, submodule, or current unreachable detached-commit data may be lost.
|
|
21
|
+
- Refuses removal when ignored local data such as `node_modules/` is present; remove it manually before retrying.
|
|
22
|
+
- Names recovery-only administrative commits in the destructive confirmation instead of making ordinary rebase/reset history block cleanup forever.
|
|
23
|
+
- Always previews stale metadata before pruning it and revalidates the preview after confirmation.
|
|
24
|
+
- Runs Git through argv-based subprocess calls, without interpolating user input into shell commands.
|
|
25
|
+
|
|
26
|
+
## 📦 Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pi install npm:@signalridge/pi-worktree
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Try without installing permanently:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pi -e npm:@signalridge/pi-worktree
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Try this package locally from the repository root:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
just try worktree
|
|
42
|
+
# or: pi -e ./packages/pi-worktree
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 💬 Usage
|
|
46
|
+
|
|
47
|
+
Run the command without arguments:
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
/worktree
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Choose one action:
|
|
54
|
+
|
|
55
|
+
- **Add worktree** — enter a branch, optional start point, and optional path; confirm creation and optionally switch.
|
|
56
|
+
- **Switch worktree** — select another existing worktree and continue this Pi conversation there.
|
|
57
|
+
- **Remove worktree** — remove a linked worktree without deleting its branch; local ignored data must be removed first.
|
|
58
|
+
- **Prune stale metadata** — inspect Git's dry-run output, then optionally run the matching prune.
|
|
59
|
+
- **Configure worktree root** — set a machine-local default root or submit a blank value to restore `~/.worktrees`.
|
|
60
|
+
|
|
61
|
+
The standard root menu shows the registered count, current path, effective worktree root, its source,
|
|
62
|
+
and any settings warning. Escape closes it. `/worktree` intentionally does not accept text
|
|
63
|
+
subcommands or expose argument autocomplete. Every change is initiated and confirmed through TUI or
|
|
64
|
+
RPC dialogs; print and JSON modes reject the command observably. Operation-specific branch/path
|
|
65
|
+
inputs, worktree identity selectors, preflight previews, and destructive confirmations remain
|
|
66
|
+
extension-owned because they carry Git safety and commit-aware revalidation.
|
|
67
|
+
|
|
68
|
+
## 🌿 Add defaults
|
|
69
|
+
|
|
70
|
+
For a new branch, the current symbolic branch is the default start point. If Pi is running from detached HEAD, the command requires an explicit commit-ish. Git must resolve the start point to exactly one commit.
|
|
71
|
+
|
|
72
|
+
The default root is `~/.worktrees`, where `~` is Node's platform home directory. Suggestions use the registered main worktree's directory name, not the current linked-worktree cwd:
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
main worktree: /home/user/workspace/project
|
|
76
|
+
branch: feat/login
|
|
77
|
+
root: /home/user/.worktrees
|
|
78
|
+
suggested: /home/user/.worktrees/project/feat-login
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
On Windows, the equivalent default is such as `C:\Users\Alice\.worktrees`. Branch `/` characters become `-`. The extension does not add hashes or collision suffixes: if two normalized paths collide or the target already exists, Add stops before Git mutation.
|
|
82
|
+
|
|
83
|
+
Leave the path input blank to accept the suggestion. A custom absolute path is used directly; a custom relative path is resolved from the current Pi cwd. The target itself must not exist, and its nearest existing ancestor must resolve without a broken or looping symbolic link. Existing registered worktrees are never moved when this default changes.
|
|
84
|
+
|
|
85
|
+
The MVP does not expose `--force`, `-B`, `--detach`, `--orphan`, or lock options.
|
|
86
|
+
|
|
87
|
+
## ⚙️ Worktree root settings
|
|
88
|
+
|
|
89
|
+
The machine-local user settings file is:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
<getAgentDir()>/pi-worktree.json
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
For a default Pi installation this is typically `~/.pi/agent/pi-worktree.json`. Configure it through **Configure worktree root** or edit it manually:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"worktreeRoot": "~/worktrees"
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`worktreeRoot` accepts `~`, a home-prefixed path such as `~/worktrees`, or a native-platform absolute path. It does not expand `$VAR`, `%VAR%`, or other shell syntax. Empty, relative, NUL-containing, non-string, and invalid paths are rejected. There is no project override or extension-specific environment variable.
|
|
104
|
+
|
|
105
|
+
A missing `worktreeRoot` uses `~/.worktrees`; the settings file is created only by a successful interactive change. Submitting a blank value in the interactive action removes the override. Within one Pi process, queued saves run in invocation order, reread the latest valid document immediately before merging `worktreeRoot`, and preserve concurrent unknown-field edits. Settings reload on every `session_start`, including `/reload` and workspace replacement; a successful interactive save applies immediately to the next Add flow.
|
|
106
|
+
|
|
107
|
+
Malformed or invalid settings are warned about but never overwritten, including an invalid edit made while a settings action is open. An initial failure uses `~/.worktrees`; a later failure retains the last valid effective root. Interactive configuration remains blocked until the invalid file is fixed manually. Failed publication leaves the prior file and effective runtime root unchanged, and the save queue remains usable after rejection.
|
|
108
|
+
|
|
109
|
+
## 🔀 Pi workspace switching
|
|
110
|
+
|
|
111
|
+
Switching uses Pi's public `SessionManager` and `ctx.switchSession()` APIs:
|
|
112
|
+
|
|
113
|
+
1. The command waits for Pi to become fully idle so the current assistant/tool results are persisted.
|
|
114
|
+
2. A linear persisted session is forked into the target worktree. If `/tree` currently points at an older branch, the documented session entries for that active branch are written to the target instead, so switching cannot jump to a newer serialized leaf.
|
|
115
|
+
3. Pi tears down the old cwd-bound runtime and creates the target runtime.
|
|
116
|
+
4. The extension reports success only through the fresh replacement-session context.
|
|
117
|
+
|
|
118
|
+
If the current session is completely empty, the extension creates a valid empty Pi session for the target. If the current session is ephemeral (`--no-session`), the extension copies its active conversation branch into a persisted target session so the workspace switch does not lose context.
|
|
119
|
+
|
|
120
|
+
A successfully created Git worktree is never rolled back merely because Pi session switching fails. Re-run `/worktree` and choose **Switch worktree** after resolving the reported Pi/session issue.
|
|
121
|
+
|
|
122
|
+
## 🛡️ Safety boundaries
|
|
123
|
+
|
|
124
|
+
- The main worktree and current worktree cannot be removed.
|
|
125
|
+
- Locked or stale worktrees cannot be removed through this extension.
|
|
126
|
+
- Dirty, untracked, initialized-submodule, and intentional `assume-unchanged`/`skip-worktree` index state causes removal to fail closed. Sparse-checkout-managed `skip-worktree` entries outside the active sparsity rules are allowed when Git's rule checker can confirm them; clear other intentional index flags before removing the worktree.
|
|
127
|
+
- Ignored files and directories block removal and are listed in the refusal; remove them manually before retrying.
|
|
128
|
+
- A detached HEAD must be reachable from a local branch, tag, or remote ref before removal or prune.
|
|
129
|
+
- Removal and prune inspect reflogs, pseudorefs, per-worktree refs, and `FETCH_HEAD`. Historical commits reachable only through this administrative recovery state are listed by full OID in the destructive confirmation; approval removes those recovery pointers, so Git may later garbage-collect the commits. Create a branch or tag instead when any listed commit should survive.
|
|
130
|
+
- Staged-only administrative index state, a missing attached branch ref, or an unreachable current detached HEAD still blocks prune without an override.
|
|
131
|
+
- Removal never deletes a branch and never uses `--force`.
|
|
132
|
+
- Safe removal invokes argv-based `git worktree move <path> <quarantine>` before validation, moves the real tree to a private tombstone, reserves the registered path with an exclusive non-directory entry, and uses an isolated `git worktree prune --expire now` for metadata only. Failed pre-removal recovery uses the inverse Git move and retains an unsafe quarantine; production runtime never invokes a shell, `rm`, or `rm -rf`.
|
|
133
|
+
- Prune always runs `git worktree prune --dry-run --verbose` before confirmation, inspects candidates omitted from porcelain, rechecks the exact preview and recovery-risk set after confirmation, and uses Git's default expiry. Remove likewise rechecks worktree identity, filesystem path identity, inventory immediately before deletion, administrative path, and the approved recovery-risk set before mutation.
|
|
134
|
+
- The extension does not expose commit, push, rebase, repair, user-requested move, lock, or unlock worktree actions.
|
|
135
|
+
|
|
136
|
+
Use Git directly when you intentionally need force removal, branch deletion, custom prune expiry, detach/orphan creation, move, repair, lock, or unlock behavior.
|
|
137
|
+
|
|
138
|
+
## Requirements and limits
|
|
139
|
+
|
|
140
|
+
- Git must be installed and the current Pi cwd must be inside a non-bare Git worktree.
|
|
141
|
+
- The command requires a UI-capable Pi mode; print and JSON modes cannot drive its dialogs.
|
|
142
|
+
- Project trust and cwd-bound extension/resource loading during a switch remain owned by Pi.
|
|
143
|
+
- The extension registers no LLM tool, background watcher, project settings, or statusline item.
|
|
144
|
+
|
|
145
|
+
## 📁 Package layout
|
|
146
|
+
|
|
147
|
+
```text
|
|
148
|
+
packages/pi-worktree/
|
|
149
|
+
├── src/
|
|
150
|
+
│ ├── index.ts
|
|
151
|
+
│ ├── command.ts
|
|
152
|
+
│ ├── git.ts
|
|
153
|
+
│ ├── session.ts
|
|
154
|
+
│ ├── settings.ts
|
|
155
|
+
│ └── worktree.ts
|
|
156
|
+
├── test/
|
|
157
|
+
│ ├── command.test.ts
|
|
158
|
+
│ ├── git.integration.test.ts
|
|
159
|
+
│ ├── git.test.ts
|
|
160
|
+
│ ├── remove-ignored-command.test.ts
|
|
161
|
+
│ ├── session.test.ts
|
|
162
|
+
│ └── settings.test.ts
|
|
163
|
+
├── package.json
|
|
164
|
+
├── README.md
|
|
165
|
+
├── LICENSE
|
|
166
|
+
└── tsconfig.json
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 🏷️ Keywords
|
|
170
|
+
|
|
171
|
+
`pi-package`, `pi-extension`, `git`, `worktree`, `workspace`, `session`
|
|
172
|
+
|
|
173
|
+
## 📄 License
|
|
174
|
+
|
|
175
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@signalridge/pi-worktree",
|
|
3
|
+
"version": "0.49.3",
|
|
4
|
+
"description": "Pi extension for safe interactive Git worktree management and workspace switching.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"private": false,
|
|
8
|
+
"keywords": [
|
|
9
|
+
"pi-package",
|
|
10
|
+
"pi-extension",
|
|
11
|
+
"pi",
|
|
12
|
+
"git",
|
|
13
|
+
"worktree",
|
|
14
|
+
"workspace"
|
|
15
|
+
],
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"CHANGELOG.md"
|
|
21
|
+
],
|
|
22
|
+
"pi": {
|
|
23
|
+
"extensions": [
|
|
24
|
+
"./src/index.ts"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"piExtension": {
|
|
28
|
+
"lifecycle": "stable"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"check": "biome check --vcs-use-ignore-file=false src test package.json README.md tsconfig.json && npm run typecheck && npm run test",
|
|
32
|
+
"format": "biome check --write --vcs-use-ignore-file=false src test package.json README.md tsconfig.json",
|
|
33
|
+
"typecheck": "tsc --noEmit",
|
|
34
|
+
"test": "vitest run test --config vitest.config.ts",
|
|
35
|
+
"lint": "biome check ."
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@biomejs/biome": "2.5.7",
|
|
42
|
+
"@earendil-works/pi-coding-agent": "0.84.1",
|
|
43
|
+
"@earendil-works/pi-tui": "0.84.1",
|
|
44
|
+
"@types/node": "26.1.2",
|
|
45
|
+
"@types/proper-lockfile": "^4.1.4",
|
|
46
|
+
"typescript": "7.0.2",
|
|
47
|
+
"vitest": "4.0.18"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "https://github.com/signalridge/pi-extensions",
|
|
52
|
+
"directory": "packages/pi-worktree"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://github.com/signalridge/pi-extensions/tree/main/packages/pi-worktree",
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/signalridge/pi-extensions/issues"
|
|
57
|
+
},
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@narumitw/pi-tui-kit": "^0.49.1",
|
|
63
|
+
"proper-lockfile": "^4.1.2"
|
|
64
|
+
}
|
|
65
|
+
}
|