@sven1103/opencode-worktree-workflow 0.5.1 → 0.6.1
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/README.md +107 -3
- package/package.json +7 -2
- package/schemas/worktree-cleanup-apply.result.schema.json +185 -0
- package/schemas/worktree-cleanup-preview.result.schema.json +109 -0
- package/schemas/worktree-prepare.result.schema.json +64 -0
- package/skills/worktree-workflow/SKILL.md +62 -0
- package/src/cli.js +173 -0
- package/src/index.js +164 -15
package/README.md
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
# OpenCode Worktree Workflow
|
|
2
2
|
|
|
3
|
-
`@sven1103/opencode-worktree-workflow` is an
|
|
3
|
+
`@sven1103/opencode-worktree-workflow` is an npm package that provides OpenCode git worktree helpers for creating synced feature worktrees and cleaning up merged ones.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
To get the workflow running in a project:
|
|
8
|
+
|
|
9
|
+
1. Install the package once by following [Recommended setup](#recommended-setup).
|
|
10
|
+
2. Enable the plugin in your OpenCode config as shown in [Recommended setup](#recommended-setup).
|
|
11
|
+
3. If you want manual `/wt-new` and `/wt-clean` triggers, install the markdown files from [Install slash commands](#install-slash-commands).
|
|
12
|
+
4. If you want policy guidance for when to isolate work, install the skill from [Co-shipped skill](#co-shipped-skill).
|
|
13
|
+
5. If you need to understand how the local fallback works, see [CLI fallback](#cli-fallback).
|
|
14
|
+
|
|
15
|
+
## Recommended setup
|
|
16
|
+
|
|
17
|
+
Install the package once:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install -D @sven1103/opencode-worktree-workflow
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Enable the native OpenCode plugin in `opencode.json`:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"$schema": "https://opencode.ai/config.json",
|
|
28
|
+
"plugin": ["@sven1103/opencode-worktree-workflow"]
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This single package provides two access modes:
|
|
33
|
+
|
|
34
|
+
- native plugin tools inside OpenCode: `worktree_prepare`, `worktree_cleanup`
|
|
35
|
+
- local CLI fallback from the same installed package:
|
|
36
|
+
- `npx opencode-worktree-workflow wt-new "<title>" --json`
|
|
37
|
+
- `npx opencode-worktree-workflow wt-clean <args> --json`
|
|
38
|
+
|
|
39
|
+
In practice:
|
|
40
|
+
|
|
41
|
+
- if the plugin is loaded, use the native tools first
|
|
42
|
+
- if the native tools are unavailable, use the local CLI fallback from the same installed package
|
|
43
|
+
- if the package is not installed, no CLI fallback is available
|
|
4
44
|
|
|
5
45
|
## Install in an OpenCode project
|
|
6
46
|
|
|
@@ -28,7 +68,7 @@ Keeping the npm dependency in `package.json` makes the installation more durable
|
|
|
28
68
|
If you do not already install dependencies in your project, you can add the package directly with npm:
|
|
29
69
|
|
|
30
70
|
```sh
|
|
31
|
-
npm install @sven1103/opencode-worktree-workflow
|
|
71
|
+
npm install -D @sven1103/opencode-worktree-workflow
|
|
32
72
|
```
|
|
33
73
|
|
|
34
74
|
## Install slash commands
|
|
@@ -75,12 +115,76 @@ curl -fsSL "https://github.com/sven1103-agent/opencode-worktree-plugin/releases/
|
|
|
75
115
|
curl -fsSL "https://github.com/sven1103-agent/opencode-worktree-plugin/releases/download/${VERSION}/wt-clean.md" -o ".opencode/commands/wt-clean.md"
|
|
76
116
|
```
|
|
77
117
|
|
|
118
|
+
## Co-shipped skill
|
|
119
|
+
|
|
120
|
+
This repo also co-ships a `worktree-workflow` skill as a policy layer over the package capability.
|
|
121
|
+
|
|
122
|
+
- checked-in skill: `skills/worktree-workflow/SKILL.md`
|
|
123
|
+
- release asset: `SKILL.md`
|
|
124
|
+
|
|
125
|
+
The skill teaches when to use task-scoped worktrees, when repo root is still acceptable, and how to prefer the native tool path before falling back to the packaged CLI.
|
|
126
|
+
|
|
127
|
+
Project-local install (latest release):
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
mkdir -p .opencode/skills/worktree-workflow
|
|
131
|
+
curl -fsSL "https://github.com/sven1103-agent/opencode-worktree-plugin/releases/latest/download/SKILL.md" -o ".opencode/skills/worktree-workflow/SKILL.md"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
mkdir -p .opencode/skills/worktree-workflow
|
|
136
|
+
wget -qO ".opencode/skills/worktree-workflow/SKILL.md" "https://github.com/sven1103-agent/opencode-worktree-plugin/releases/latest/download/SKILL.md"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
If your setup uses installed skill files, copy the released `SKILL.md` into a `worktree-workflow/` skill folder in the appropriate location for that environment, or consume the checked-in file from this repo directly.
|
|
140
|
+
|
|
78
141
|
## What the plugin provides
|
|
79
142
|
|
|
80
143
|
- `worktree_prepare`: create a worktree and matching branch from the latest configured base-branch commit, or the default branch when no base branch is configured
|
|
81
144
|
- `worktree_cleanup`: preview all connected worktrees against the configured base branch, auto-clean safe ones, and optionally remove selected review items
|
|
82
145
|
|
|
83
|
-
This package
|
|
146
|
+
This package now ships the plugin capability, a CLI fallback surface, thin slash commands, and a co-shipped policy skill.
|
|
147
|
+
|
|
148
|
+
## Structured contract
|
|
149
|
+
|
|
150
|
+
The native tool results and CLI `--json` output now use a versioned structured contract with a `schema_version` field.
|
|
151
|
+
|
|
152
|
+
- current `schema_version`: `1.0.0`
|
|
153
|
+
- contract overview: `docs/contract.md`
|
|
154
|
+
- compatibility model: `docs/compatibility.md`
|
|
155
|
+
- checked-in schemas for transparency:
|
|
156
|
+
- `schemas/worktree-prepare.result.schema.json`
|
|
157
|
+
- `schemas/worktree-cleanup-preview.result.schema.json`
|
|
158
|
+
- `schemas/worktree-cleanup-apply.result.schema.json`
|
|
159
|
+
|
|
160
|
+
Human-readable output remains available through the result `message`, but callers should depend on the structured fields rather than parsing prose.
|
|
161
|
+
|
|
162
|
+
## CLI fallback
|
|
163
|
+
|
|
164
|
+
The npm package also exposes a local CLI so agents can fall back to the same installed package when the native plugin tools are unavailable.
|
|
165
|
+
|
|
166
|
+
Examples:
|
|
167
|
+
|
|
168
|
+
```sh
|
|
169
|
+
npx opencode-worktree-workflow wt-new "Improve checkout retry logic"
|
|
170
|
+
npx opencode-worktree-workflow wt-new "Improve checkout retry logic" --json
|
|
171
|
+
npx opencode-worktree-workflow wt-clean preview
|
|
172
|
+
npx opencode-worktree-workflow wt-clean apply feature/foo --json
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Defaults:
|
|
176
|
+
|
|
177
|
+
- human-readable output by default
|
|
178
|
+
- structured output with `--json`
|
|
179
|
+
- the CLI shares the same underlying implementation and result contract as the native tools
|
|
180
|
+
- the CLI fallback depends on the package already being installed in the project
|
|
181
|
+
|
|
182
|
+
## Compatibility model
|
|
183
|
+
|
|
184
|
+
The repo keeps config loading, argument normalization, and execution semantics centralized in the package implementation so existing installations continue to work across native tools, CLI fallback, and slash commands.
|
|
185
|
+
|
|
186
|
+
- compatibility overview: `docs/compatibility.md`
|
|
187
|
+
- existing `.opencode/worktree-workflow.json` setups remain the supported configuration path
|
|
84
188
|
|
|
85
189
|
## Optional project configuration
|
|
86
190
|
|
package/package.json
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sven1103/opencode-worktree-workflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "OpenCode plugin for creating and cleaning up git worktrees.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"opencode-worktree-workflow": "./src/cli.js"
|
|
9
|
+
},
|
|
7
10
|
"exports": {
|
|
8
11
|
".": "./src/index.js"
|
|
9
12
|
},
|
|
10
13
|
"files": [
|
|
11
|
-
"src"
|
|
14
|
+
"src",
|
|
15
|
+
"schemas",
|
|
16
|
+
"skills"
|
|
12
17
|
],
|
|
13
18
|
"keywords": [
|
|
14
19
|
"opencode",
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/sven1103-agent/opencode-worktree-plugin/schemas/worktree-cleanup-apply.result.schema.json",
|
|
4
|
+
"title": "worktree_cleanup apply result",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"schema_version",
|
|
9
|
+
"ok",
|
|
10
|
+
"mode",
|
|
11
|
+
"default_branch",
|
|
12
|
+
"base_branch",
|
|
13
|
+
"base_ref",
|
|
14
|
+
"requested_selectors",
|
|
15
|
+
"removed",
|
|
16
|
+
"failed"
|
|
17
|
+
],
|
|
18
|
+
"properties": {
|
|
19
|
+
"schema_version": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"const": "1.0.0"
|
|
22
|
+
},
|
|
23
|
+
"ok": {
|
|
24
|
+
"type": "boolean",
|
|
25
|
+
"const": true
|
|
26
|
+
},
|
|
27
|
+
"mode": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"const": "apply"
|
|
30
|
+
},
|
|
31
|
+
"default_branch": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"minLength": 1
|
|
34
|
+
},
|
|
35
|
+
"base_branch": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"minLength": 1
|
|
38
|
+
},
|
|
39
|
+
"base_ref": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"minLength": 1
|
|
42
|
+
},
|
|
43
|
+
"requested_selectors": {
|
|
44
|
+
"type": "array",
|
|
45
|
+
"items": {
|
|
46
|
+
"type": "string"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"removed": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"items": {
|
|
52
|
+
"$ref": "#/$defs/removedItem"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"failed": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"items": {
|
|
58
|
+
"$ref": "#/$defs/failedItem"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"message": {
|
|
62
|
+
"type": "string"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"$defs": {
|
|
66
|
+
"baseCleanupItem": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"required": [
|
|
69
|
+
"branch",
|
|
70
|
+
"worktree_path",
|
|
71
|
+
"head",
|
|
72
|
+
"status",
|
|
73
|
+
"reason",
|
|
74
|
+
"detached",
|
|
75
|
+
"selectable"
|
|
76
|
+
],
|
|
77
|
+
"properties": {
|
|
78
|
+
"branch": {
|
|
79
|
+
"type": ["string", "null"]
|
|
80
|
+
},
|
|
81
|
+
"worktree_path": {
|
|
82
|
+
"type": ["string", "null"]
|
|
83
|
+
},
|
|
84
|
+
"head": {
|
|
85
|
+
"type": ["string", "null"]
|
|
86
|
+
},
|
|
87
|
+
"status": {
|
|
88
|
+
"type": ["string", "null"],
|
|
89
|
+
"enum": ["safe", "review", "blocked", null]
|
|
90
|
+
},
|
|
91
|
+
"reason": {
|
|
92
|
+
"type": ["string", "null"]
|
|
93
|
+
},
|
|
94
|
+
"detached": {
|
|
95
|
+
"type": "boolean"
|
|
96
|
+
},
|
|
97
|
+
"selectable": {
|
|
98
|
+
"type": ["boolean", "null"]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"removedItem": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"additionalProperties": false,
|
|
105
|
+
"required": [
|
|
106
|
+
"branch",
|
|
107
|
+
"worktree_path",
|
|
108
|
+
"head",
|
|
109
|
+
"status",
|
|
110
|
+
"reason",
|
|
111
|
+
"detached",
|
|
112
|
+
"selectable",
|
|
113
|
+
"selected"
|
|
114
|
+
],
|
|
115
|
+
"properties": {
|
|
116
|
+
"branch": {
|
|
117
|
+
"type": ["string", "null"]
|
|
118
|
+
},
|
|
119
|
+
"worktree_path": {
|
|
120
|
+
"type": ["string", "null"]
|
|
121
|
+
},
|
|
122
|
+
"head": {
|
|
123
|
+
"type": ["string", "null"]
|
|
124
|
+
},
|
|
125
|
+
"status": {
|
|
126
|
+
"type": ["string", "null"],
|
|
127
|
+
"enum": ["safe", "review", "blocked", null]
|
|
128
|
+
},
|
|
129
|
+
"reason": {
|
|
130
|
+
"type": ["string", "null"]
|
|
131
|
+
},
|
|
132
|
+
"detached": {
|
|
133
|
+
"type": "boolean"
|
|
134
|
+
},
|
|
135
|
+
"selectable": {
|
|
136
|
+
"type": ["boolean", "null"]
|
|
137
|
+
},
|
|
138
|
+
"selected": {
|
|
139
|
+
"type": "boolean"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"failedItem": {
|
|
144
|
+
"type": "object",
|
|
145
|
+
"additionalProperties": false,
|
|
146
|
+
"required": [
|
|
147
|
+
"selector",
|
|
148
|
+
"branch",
|
|
149
|
+
"worktree_path",
|
|
150
|
+
"head",
|
|
151
|
+
"status",
|
|
152
|
+
"reason",
|
|
153
|
+
"detached",
|
|
154
|
+
"selectable"
|
|
155
|
+
],
|
|
156
|
+
"properties": {
|
|
157
|
+
"selector": {
|
|
158
|
+
"type": ["string", "null"]
|
|
159
|
+
},
|
|
160
|
+
"branch": {
|
|
161
|
+
"type": ["string", "null"]
|
|
162
|
+
},
|
|
163
|
+
"worktree_path": {
|
|
164
|
+
"type": ["string", "null"]
|
|
165
|
+
},
|
|
166
|
+
"head": {
|
|
167
|
+
"type": ["string", "null"]
|
|
168
|
+
},
|
|
169
|
+
"status": {
|
|
170
|
+
"type": ["string", "null"],
|
|
171
|
+
"enum": ["safe", "review", "blocked", null]
|
|
172
|
+
},
|
|
173
|
+
"reason": {
|
|
174
|
+
"type": ["string", "null"]
|
|
175
|
+
},
|
|
176
|
+
"detached": {
|
|
177
|
+
"type": "boolean"
|
|
178
|
+
},
|
|
179
|
+
"selectable": {
|
|
180
|
+
"type": ["boolean", "null"]
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/sven1103-agent/opencode-worktree-plugin/schemas/worktree-cleanup-preview.result.schema.json",
|
|
4
|
+
"title": "worktree_cleanup preview result",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"schema_version",
|
|
9
|
+
"ok",
|
|
10
|
+
"mode",
|
|
11
|
+
"default_branch",
|
|
12
|
+
"base_branch",
|
|
13
|
+
"base_ref",
|
|
14
|
+
"groups"
|
|
15
|
+
],
|
|
16
|
+
"properties": {
|
|
17
|
+
"schema_version": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"const": "1.0.0"
|
|
20
|
+
},
|
|
21
|
+
"ok": {
|
|
22
|
+
"type": "boolean",
|
|
23
|
+
"const": true
|
|
24
|
+
},
|
|
25
|
+
"mode": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"const": "preview"
|
|
28
|
+
},
|
|
29
|
+
"default_branch": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"minLength": 1
|
|
32
|
+
},
|
|
33
|
+
"base_branch": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"minLength": 1
|
|
36
|
+
},
|
|
37
|
+
"base_ref": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"minLength": 1
|
|
40
|
+
},
|
|
41
|
+
"groups": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"additionalProperties": false,
|
|
44
|
+
"required": ["safe", "review", "blocked"],
|
|
45
|
+
"properties": {
|
|
46
|
+
"safe": {
|
|
47
|
+
"type": "array",
|
|
48
|
+
"items": {
|
|
49
|
+
"$ref": "#/$defs/cleanupItem"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"review": {
|
|
53
|
+
"type": "array",
|
|
54
|
+
"items": {
|
|
55
|
+
"$ref": "#/$defs/cleanupItem"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"blocked": {
|
|
59
|
+
"type": "array",
|
|
60
|
+
"items": {
|
|
61
|
+
"$ref": "#/$defs/cleanupItem"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"message": {
|
|
67
|
+
"type": "string"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"$defs": {
|
|
71
|
+
"cleanupItem": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"additionalProperties": false,
|
|
74
|
+
"required": [
|
|
75
|
+
"branch",
|
|
76
|
+
"worktree_path",
|
|
77
|
+
"head",
|
|
78
|
+
"status",
|
|
79
|
+
"reason",
|
|
80
|
+
"detached",
|
|
81
|
+
"selectable"
|
|
82
|
+
],
|
|
83
|
+
"properties": {
|
|
84
|
+
"branch": {
|
|
85
|
+
"type": ["string", "null"]
|
|
86
|
+
},
|
|
87
|
+
"worktree_path": {
|
|
88
|
+
"type": ["string", "null"]
|
|
89
|
+
},
|
|
90
|
+
"head": {
|
|
91
|
+
"type": ["string", "null"]
|
|
92
|
+
},
|
|
93
|
+
"status": {
|
|
94
|
+
"type": ["string", "null"],
|
|
95
|
+
"enum": ["safe", "review", "blocked", null]
|
|
96
|
+
},
|
|
97
|
+
"reason": {
|
|
98
|
+
"type": ["string", "null"]
|
|
99
|
+
},
|
|
100
|
+
"detached": {
|
|
101
|
+
"type": "boolean"
|
|
102
|
+
},
|
|
103
|
+
"selectable": {
|
|
104
|
+
"type": ["boolean", "null"]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/sven1103-agent/opencode-worktree-plugin/schemas/worktree-prepare.result.schema.json",
|
|
4
|
+
"title": "worktree_prepare result",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"schema_version",
|
|
9
|
+
"ok",
|
|
10
|
+
"title",
|
|
11
|
+
"branch",
|
|
12
|
+
"worktree_path",
|
|
13
|
+
"default_branch",
|
|
14
|
+
"base_branch",
|
|
15
|
+
"base_ref",
|
|
16
|
+
"base_commit",
|
|
17
|
+
"created"
|
|
18
|
+
],
|
|
19
|
+
"properties": {
|
|
20
|
+
"schema_version": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"const": "1.0.0"
|
|
23
|
+
},
|
|
24
|
+
"ok": {
|
|
25
|
+
"type": "boolean",
|
|
26
|
+
"const": true
|
|
27
|
+
},
|
|
28
|
+
"title": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"minLength": 1
|
|
31
|
+
},
|
|
32
|
+
"branch": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"minLength": 1
|
|
35
|
+
},
|
|
36
|
+
"worktree_path": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"minLength": 1
|
|
39
|
+
},
|
|
40
|
+
"default_branch": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"minLength": 1
|
|
43
|
+
},
|
|
44
|
+
"base_branch": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"minLength": 1
|
|
47
|
+
},
|
|
48
|
+
"base_ref": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"minLength": 1
|
|
51
|
+
},
|
|
52
|
+
"base_commit": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"minLength": 1
|
|
55
|
+
},
|
|
56
|
+
"created": {
|
|
57
|
+
"type": "boolean",
|
|
58
|
+
"const": true
|
|
59
|
+
},
|
|
60
|
+
"message": {
|
|
61
|
+
"type": "string"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: worktree-workflow
|
|
3
|
+
description: Use this skill when you need to decide whether a task should move into a git worktree, when repo root is still safe, or when you need to choose between native worktree tools and the standard CLI fallback.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## When to use me
|
|
7
|
+
|
|
8
|
+
- Use this skill when work should be isolated from the current checkout.
|
|
9
|
+
- Use this skill when the task is substantial, risky, or likely to involve multiple collaborating agents.
|
|
10
|
+
- Use this skill when you need to decide whether repo root is still safe for a tiny edit.
|
|
11
|
+
- Use this skill when you need to choose the native worktree tools first and the packaged CLI fallback second.
|
|
12
|
+
|
|
13
|
+
## Goals
|
|
14
|
+
|
|
15
|
+
- Keep user work isolated from repo root when appropriate.
|
|
16
|
+
- Prefer task-scoped worktrees for non-trivial editable work.
|
|
17
|
+
- Support both native worktree tools and CLI fallback environments.
|
|
18
|
+
- Keep policy in the skill and execution semantics in the package.
|
|
19
|
+
|
|
20
|
+
## Root policy
|
|
21
|
+
|
|
22
|
+
- Treat repo root as shared space.
|
|
23
|
+
- Use repo root only for tiny, root-safe tasks.
|
|
24
|
+
- Treat a task as root-safe only when it is one focused change, touches at most one or two closely related files, does not need parallel delegation, does not imply a likely edit-test-fix loop, does not involve risky refactoring or migration work, and does not risk interfering with unrelated dirty root state.
|
|
25
|
+
|
|
26
|
+
## Task worktree policy
|
|
27
|
+
|
|
28
|
+
- Prefer one task-scoped worktree for non-trivial editable work.
|
|
29
|
+
- Treat a task worktree as belonging to a task or workstream, not to a single agent.
|
|
30
|
+
- Keep planning, implementation, and review for one linear task in the same task worktree unless the work splits.
|
|
31
|
+
- Create a separate divergent worktree only when concurrent branches of work may conflict or need independent experimentation.
|
|
32
|
+
|
|
33
|
+
## Capability ladder
|
|
34
|
+
|
|
35
|
+
- Use the native worktree tools as the primary path when the native worktree tools are available.
|
|
36
|
+
- Use the packaged CLI fallback path when the native tools are unavailable.
|
|
37
|
+
- Continue in repo root only for tiny, root-safe tasks when no worktree capability is available; otherwise stop and explain that isolation capability is unavailable.
|
|
38
|
+
|
|
39
|
+
## Creation behavior
|
|
40
|
+
|
|
41
|
+
- Use a short descriptive task title when creating a worktree.
|
|
42
|
+
- Treat the returned `worktree_path` as the active execution target for follow-up work.
|
|
43
|
+
- Use that worktree path as the working directory for later shell commands.
|
|
44
|
+
- Use paths inside that worktree for later file reads or edits.
|
|
45
|
+
|
|
46
|
+
## Cleanup behavior
|
|
47
|
+
|
|
48
|
+
- Keep cleanup preview-first by default because cleanup is preview-first unless deletion is clearly intended.
|
|
49
|
+
- Use cleanup apply only when deletion is clearly intended and controlled by the orchestrating runtime.
|
|
50
|
+
- Treat slash commands as manual human entry points, not as the canonical agent interface.
|
|
51
|
+
|
|
52
|
+
## Boundaries
|
|
53
|
+
|
|
54
|
+
- Do not encode runtime storage, session artifact, or orchestration file-layout details here.
|
|
55
|
+
- Do not duplicate package argument normalization or config parsing here.
|
|
56
|
+
- Rely on the shared package implementation for config loading, base-branch resolution, cleanup normalization, and structured result semantics.
|
|
57
|
+
|
|
58
|
+
## Examples
|
|
59
|
+
|
|
60
|
+
- Move a risky refactor into a task-scoped worktree before editing multiple files and running several test-fix loops.
|
|
61
|
+
- Stay in repo root for a tiny, root-safe doc fix that touches one related file and does not need delegation.
|
|
62
|
+
- Prefer the native worktree tools first, then switch to the packaged CLI fallback path if the native tools are unavailable.
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
|
|
8
|
+
import { WorktreeWorkflowPlugin } from "./index.js";
|
|
9
|
+
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
|
+
|
|
12
|
+
function shellEscape(value) {
|
|
13
|
+
return `'${String(value).replaceAll("'", `'"'"'`)}'`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function createShell(cwdBase) {
|
|
17
|
+
const shell = (strings, ...values) => {
|
|
18
|
+
const [firstValue] = values;
|
|
19
|
+
const raw = firstValue?.raw || strings.raw?.join("") || strings.join("");
|
|
20
|
+
let cwd = cwdBase;
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
cwd(nextCwd) {
|
|
24
|
+
cwd = nextCwd;
|
|
25
|
+
return this;
|
|
26
|
+
},
|
|
27
|
+
quiet() {
|
|
28
|
+
return this;
|
|
29
|
+
},
|
|
30
|
+
async nothrow() {
|
|
31
|
+
try {
|
|
32
|
+
const result = await execFileAsync("sh", ["-lc", raw], { cwd });
|
|
33
|
+
return {
|
|
34
|
+
text() {
|
|
35
|
+
return result.stdout;
|
|
36
|
+
},
|
|
37
|
+
stderr: Buffer.from(result.stderr),
|
|
38
|
+
exitCode: 0,
|
|
39
|
+
};
|
|
40
|
+
} catch (error) {
|
|
41
|
+
return {
|
|
42
|
+
text() {
|
|
43
|
+
return error.stdout || "";
|
|
44
|
+
},
|
|
45
|
+
stderr: Buffer.from(error.stderr || ""),
|
|
46
|
+
exitCode: typeof error.code === "number" ? error.code : 1,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
shell.escape = shellEscape;
|
|
54
|
+
return shell;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function printUsage() {
|
|
58
|
+
process.stdout.write(
|
|
59
|
+
[
|
|
60
|
+
"Usage:",
|
|
61
|
+
" opencode-worktree-workflow wt-new <title> [--json]",
|
|
62
|
+
" opencode-worktree-workflow wt-clean [preview|apply] [selectors...] [--json]",
|
|
63
|
+
"",
|
|
64
|
+
"Examples:",
|
|
65
|
+
" opencode-worktree-workflow wt-new \"Improve checkout retry logic\"",
|
|
66
|
+
" opencode-worktree-workflow wt-clean preview",
|
|
67
|
+
" opencode-worktree-workflow wt-clean apply feature/foo",
|
|
68
|
+
].join("\n") + "\n",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function printSubcommandUsage(command) {
|
|
73
|
+
if (command === "wt-new") {
|
|
74
|
+
process.stdout.write(
|
|
75
|
+
[
|
|
76
|
+
"Usage:",
|
|
77
|
+
" opencode-worktree-workflow wt-new <title> [--json]",
|
|
78
|
+
"",
|
|
79
|
+
"Create a synced worktree and branch from the configured base branch.",
|
|
80
|
+
].join("\n") + "\n",
|
|
81
|
+
);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (command === "wt-clean") {
|
|
86
|
+
process.stdout.write(
|
|
87
|
+
[
|
|
88
|
+
"Usage:",
|
|
89
|
+
" opencode-worktree-workflow wt-clean [preview|apply] [selectors...] [--json]",
|
|
90
|
+
"",
|
|
91
|
+
"Preview connected worktrees or remove safe and explicitly selected review worktrees.",
|
|
92
|
+
].join("\n") + "\n",
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function parseCliArgs(argv) {
|
|
98
|
+
const outputJson = argv.includes("--json");
|
|
99
|
+
const args = argv.filter((arg) => arg !== "--json");
|
|
100
|
+
return { outputJson, args };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function run(argv = process.argv.slice(2)) {
|
|
104
|
+
const { outputJson, args } = parseCliArgs(argv);
|
|
105
|
+
const [command, ...rest] = args;
|
|
106
|
+
|
|
107
|
+
if (!command || command === "--help" || command === "-h" || command === "help") {
|
|
108
|
+
printUsage();
|
|
109
|
+
process.exitCode = command ? 0 : 1;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if ((command === "wt-new" || command === "wt-clean") && rest.some((arg) => arg === "--help" || arg === "-h" || arg === "help")) {
|
|
114
|
+
printSubcommandUsage(command);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const plugin = await WorktreeWorkflowPlugin({
|
|
119
|
+
$: createShell(process.cwd()),
|
|
120
|
+
directory: process.cwd(),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
let result;
|
|
124
|
+
|
|
125
|
+
if (command === "wt-new") {
|
|
126
|
+
const title = rest.join(" ").trim();
|
|
127
|
+
|
|
128
|
+
if (!title) {
|
|
129
|
+
throw new Error("wt-new requires a descriptive title.");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
result = await plugin.tool.worktree_prepare.execute(
|
|
133
|
+
{ title },
|
|
134
|
+
{ metadata() {}, worktree: process.cwd() },
|
|
135
|
+
);
|
|
136
|
+
} else if (command === "wt-clean") {
|
|
137
|
+
const raw = rest.join(" ").trim();
|
|
138
|
+
result = await plugin.tool.worktree_cleanup.execute(
|
|
139
|
+
{ raw, selectors: [] },
|
|
140
|
+
{ metadata() {}, worktree: process.cwd() },
|
|
141
|
+
);
|
|
142
|
+
} else {
|
|
143
|
+
throw new Error(`Unknown command: ${command}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (outputJson) {
|
|
147
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
process.stdout.write(`${result.message || JSON.stringify(result, null, 2)}\n`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function isInvokedAsScript(argvPath = process.argv[1]) {
|
|
155
|
+
if (!argvPath) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
return fs.realpathSync(argvPath) === fileURLToPath(import.meta.url);
|
|
161
|
+
} catch {
|
|
162
|
+
return fileURLToPath(import.meta.url) === argvPath;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const invokedAsScript = isInvokedAsScript();
|
|
167
|
+
|
|
168
|
+
if (invokedAsScript) {
|
|
169
|
+
run().catch((error) => {
|
|
170
|
+
process.stderr.write(`${error.message || String(error)}\n`);
|
|
171
|
+
process.exitCode = 1;
|
|
172
|
+
});
|
|
173
|
+
}
|
package/src/index.js
CHANGED
|
@@ -13,6 +13,8 @@ const DEFAULTS = {
|
|
|
13
13
|
protectedBranches: [],
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
const RESULT_SCHEMA_VERSION = "1.0.0";
|
|
17
|
+
|
|
16
18
|
async function pathExists(targetPath) {
|
|
17
19
|
try {
|
|
18
20
|
await fs.access(targetPath);
|
|
@@ -22,6 +24,14 @@ async function pathExists(targetPath) {
|
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
function isMissingGitRepositoryError(message) {
|
|
28
|
+
return /not a git repository/i.test(message);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isMissingRemoteError(message, remote) {
|
|
32
|
+
return new RegExp(`No such remote:?\s+${remote}|does not appear to be a git repository|Could not read from remote repository`, "i").test(message);
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
async function readJsonFile(filePath) {
|
|
26
36
|
if (!(await pathExists(filePath))) {
|
|
27
37
|
return null;
|
|
@@ -163,6 +173,18 @@ function formatPreview(grouped, defaultBranch) {
|
|
|
163
173
|
].join("\n");
|
|
164
174
|
}
|
|
165
175
|
|
|
176
|
+
function formatPrepareSummary(result) {
|
|
177
|
+
return [
|
|
178
|
+
`Created worktree for "${result.title}".`,
|
|
179
|
+
`- branch: ${result.branch}`,
|
|
180
|
+
`- worktree: ${result.worktree_path}`,
|
|
181
|
+
`- default branch: ${result.default_branch}`,
|
|
182
|
+
`- base branch: ${result.base_branch}`,
|
|
183
|
+
`- base ref: ${result.base_ref}`,
|
|
184
|
+
`- base commit: ${result.base_commit}`,
|
|
185
|
+
].join("\n");
|
|
186
|
+
}
|
|
187
|
+
|
|
166
188
|
function formatCleanupSummary(defaultBranch, removed, failed, requestedSelectors) {
|
|
167
189
|
const lines = [`Cleaned worktrees relative to ${defaultBranch}:`];
|
|
168
190
|
|
|
@@ -194,6 +216,88 @@ function formatCleanupSummary(defaultBranch, removed, failed, requestedSelectors
|
|
|
194
216
|
return lines.join("\n");
|
|
195
217
|
}
|
|
196
218
|
|
|
219
|
+
function toStructuredCleanupItem(item) {
|
|
220
|
+
return {
|
|
221
|
+
branch: item.branch ?? null,
|
|
222
|
+
worktree_path: item.path ?? item.worktree_path ?? null,
|
|
223
|
+
head: item.head ?? null,
|
|
224
|
+
status: item.status ?? null,
|
|
225
|
+
reason: item.reason ?? null,
|
|
226
|
+
detached: Boolean(item.detached),
|
|
227
|
+
selectable: typeof item.selectable === "boolean" ? item.selectable : null,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function toStructuredCleanupFailure(item) {
|
|
232
|
+
return {
|
|
233
|
+
selector: item.selector ?? null,
|
|
234
|
+
branch: item.branch ?? null,
|
|
235
|
+
worktree_path: item.path ?? item.worktree_path ?? null,
|
|
236
|
+
head: item.head ?? null,
|
|
237
|
+
status: item.status ?? null,
|
|
238
|
+
reason: item.reason ?? null,
|
|
239
|
+
detached: Boolean(item.detached),
|
|
240
|
+
selectable: typeof item.selectable === "boolean" ? item.selectable : null,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function buildPrepareResult({ title, branch, worktreePath, defaultBranch, baseBranch, baseRef, baseCommit }) {
|
|
245
|
+
const result = {
|
|
246
|
+
schema_version: RESULT_SCHEMA_VERSION,
|
|
247
|
+
ok: true,
|
|
248
|
+
title,
|
|
249
|
+
branch,
|
|
250
|
+
worktree_path: worktreePath,
|
|
251
|
+
default_branch: defaultBranch,
|
|
252
|
+
base_branch: baseBranch,
|
|
253
|
+
base_ref: baseRef,
|
|
254
|
+
base_commit: baseCommit,
|
|
255
|
+
created: true,
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
...result,
|
|
260
|
+
message: formatPrepareSummary(result),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function buildCleanupPreviewResult({ defaultBranch, baseBranch, baseRef, grouped }) {
|
|
265
|
+
const structuredGroups = {
|
|
266
|
+
safe: grouped.safe.map(toStructuredCleanupItem),
|
|
267
|
+
review: grouped.review.map(toStructuredCleanupItem),
|
|
268
|
+
blocked: grouped.blocked.map(toStructuredCleanupItem),
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
schema_version: RESULT_SCHEMA_VERSION,
|
|
273
|
+
ok: true,
|
|
274
|
+
mode: "preview",
|
|
275
|
+
default_branch: defaultBranch,
|
|
276
|
+
base_branch: baseBranch,
|
|
277
|
+
base_ref: baseRef,
|
|
278
|
+
groups: structuredGroups,
|
|
279
|
+
message: formatPreview(grouped, baseBranch),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function buildCleanupApplyResult({ defaultBranch, baseBranch, baseRef, removed, failed, requestedSelectors }) {
|
|
284
|
+
return {
|
|
285
|
+
schema_version: RESULT_SCHEMA_VERSION,
|
|
286
|
+
ok: true,
|
|
287
|
+
mode: "apply",
|
|
288
|
+
default_branch: defaultBranch,
|
|
289
|
+
base_branch: baseBranch,
|
|
290
|
+
base_ref: baseRef,
|
|
291
|
+
requested_selectors: requestedSelectors,
|
|
292
|
+
removed: removed.map((item) => ({
|
|
293
|
+
...toStructuredCleanupItem(item),
|
|
294
|
+
selected: Boolean(item.selected),
|
|
295
|
+
})),
|
|
296
|
+
failed: failed.map(toStructuredCleanupFailure),
|
|
297
|
+
message: formatCleanupSummary(baseBranch, removed, failed, requestedSelectors),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
197
301
|
function splitCleanupToken(value) {
|
|
198
302
|
if (typeof value !== "string") {
|
|
199
303
|
return [];
|
|
@@ -275,8 +379,17 @@ function normalizeCleanupArgs(args, config) {
|
|
|
275
379
|
}
|
|
276
380
|
|
|
277
381
|
export const __internal = {
|
|
382
|
+
RESULT_SCHEMA_VERSION,
|
|
383
|
+
buildCleanupApplyResult,
|
|
384
|
+
buildCleanupPreviewResult,
|
|
385
|
+
buildPrepareResult,
|
|
386
|
+
classifyEntry,
|
|
387
|
+
isMissingGitRepositoryError,
|
|
388
|
+
isMissingRemoteError,
|
|
278
389
|
parseCleanupRawArguments,
|
|
279
390
|
normalizeCleanupArgs,
|
|
391
|
+
toStructuredCleanupFailure,
|
|
392
|
+
toStructuredCleanupItem,
|
|
280
393
|
};
|
|
281
394
|
|
|
282
395
|
function selectorMatches(item, selector) {
|
|
@@ -367,8 +480,18 @@ export const WorktreeWorkflowPlugin = async ({ $, directory }) => {
|
|
|
367
480
|
}
|
|
368
481
|
|
|
369
482
|
async function getRepoRoot() {
|
|
370
|
-
|
|
371
|
-
|
|
483
|
+
try {
|
|
484
|
+
const result = await git(["rev-parse", "--show-toplevel"]);
|
|
485
|
+
return result.stdout;
|
|
486
|
+
} catch (error) {
|
|
487
|
+
if (isMissingGitRepositoryError(error.message || "")) {
|
|
488
|
+
throw new Error(
|
|
489
|
+
"This command must run inside a git repository. Initialize a repository first or run it from an existing repo root.",
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
throw error;
|
|
494
|
+
}
|
|
372
495
|
}
|
|
373
496
|
|
|
374
497
|
async function loadWorkflowConfig(repoRoot) {
|
|
@@ -454,7 +577,17 @@ export const WorktreeWorkflowPlugin = async ({ $, directory }) => {
|
|
|
454
577
|
}
|
|
455
578
|
|
|
456
579
|
async function getBaseRef(repoRoot, remote, baseBranch) {
|
|
457
|
-
|
|
580
|
+
try {
|
|
581
|
+
await git(["fetch", "--prune", remote, baseBranch], { cwd: repoRoot });
|
|
582
|
+
} catch (error) {
|
|
583
|
+
if (isMissingRemoteError(error.message || "", remote)) {
|
|
584
|
+
throw new Error(
|
|
585
|
+
`Could not fetch base branch information from remote \"${remote}\". Configure the expected remote in .opencode/worktree-workflow.json or add that remote to this repository.`,
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
throw error;
|
|
590
|
+
}
|
|
458
591
|
|
|
459
592
|
const remoteRef = `refs/remotes/${remote}/${baseBranch}`;
|
|
460
593
|
const remoteExists = await git(["show-ref", "--verify", "--quiet", remoteRef], {
|
|
@@ -529,15 +662,15 @@ export const WorktreeWorkflowPlugin = async ({ $, directory }) => {
|
|
|
529
662
|
);
|
|
530
663
|
}
|
|
531
664
|
|
|
532
|
-
return
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
665
|
+
return buildPrepareResult({
|
|
666
|
+
title: args.title,
|
|
667
|
+
branch: branchName,
|
|
668
|
+
worktreePath,
|
|
669
|
+
defaultBranch,
|
|
670
|
+
baseBranch,
|
|
671
|
+
baseRef,
|
|
672
|
+
baseCommit,
|
|
673
|
+
});
|
|
541
674
|
},
|
|
542
675
|
}),
|
|
543
676
|
worktree_cleanup: tool({
|
|
@@ -593,7 +726,12 @@ export const WorktreeWorkflowPlugin = async ({ $, directory }) => {
|
|
|
593
726
|
}
|
|
594
727
|
|
|
595
728
|
if (normalizedArgs.mode !== "apply") {
|
|
596
|
-
return
|
|
729
|
+
return buildCleanupPreviewResult({
|
|
730
|
+
defaultBranch,
|
|
731
|
+
baseBranch,
|
|
732
|
+
baseRef,
|
|
733
|
+
grouped,
|
|
734
|
+
});
|
|
597
735
|
}
|
|
598
736
|
|
|
599
737
|
const requestedSelectors = [...new Set(normalizedArgs.selectors || [])];
|
|
@@ -622,7 +760,10 @@ export const WorktreeWorkflowPlugin = async ({ $, directory }) => {
|
|
|
622
760
|
continue;
|
|
623
761
|
}
|
|
624
762
|
|
|
625
|
-
selected.push(
|
|
763
|
+
selected.push({
|
|
764
|
+
...match,
|
|
765
|
+
selector,
|
|
766
|
+
});
|
|
626
767
|
}
|
|
627
768
|
|
|
628
769
|
const targets = [...grouped.safe];
|
|
@@ -631,6 +772,7 @@ export const WorktreeWorkflowPlugin = async ({ $, directory }) => {
|
|
|
631
772
|
if (!targets.some((target) => target.path === item.path)) {
|
|
632
773
|
targets.push({
|
|
633
774
|
...item,
|
|
775
|
+
selector: item.selector ?? null,
|
|
634
776
|
selected: true,
|
|
635
777
|
});
|
|
636
778
|
}
|
|
@@ -676,7 +818,14 @@ export const WorktreeWorkflowPlugin = async ({ $, directory }) => {
|
|
|
676
818
|
allowFailure: true,
|
|
677
819
|
});
|
|
678
820
|
|
|
679
|
-
return
|
|
821
|
+
return buildCleanupApplyResult({
|
|
822
|
+
defaultBranch,
|
|
823
|
+
baseBranch,
|
|
824
|
+
baseRef,
|
|
825
|
+
removed,
|
|
826
|
+
failed,
|
|
827
|
+
requestedSelectors,
|
|
828
|
+
});
|
|
680
829
|
},
|
|
681
830
|
}),
|
|
682
831
|
},
|