@omnicoreos/planka-mcp 0.2.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 +86 -0
- package/CONTRIBUTING.md +37 -0
- package/CREDITS.md +39 -0
- package/LICENSE +22 -0
- package/README.es.md +213 -0
- package/README.md +213 -0
- package/dist/client.d.ts +96 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +281 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +85 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +226 -0
- package/dist/errors.js.map +1 -0
- package/dist/identity.generated.d.ts +3 -0
- package/dist/identity.generated.d.ts.map +1 -0
- package/dist/identity.generated.js +4 -0
- package/dist/identity.generated.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/dist/operations/attachments.d.ts +34 -0
- package/dist/operations/attachments.d.ts.map +1 -0
- package/dist/operations/attachments.js +89 -0
- package/dist/operations/attachments.js.map +1 -0
- package/dist/operations/board-id.d.ts +9 -0
- package/dist/operations/board-id.d.ts.map +1 -0
- package/dist/operations/board-id.js +86 -0
- package/dist/operations/board-id.js.map +1 -0
- package/dist/operations/boards.d.ts +143 -0
- package/dist/operations/boards.d.ts.map +1 -0
- package/dist/operations/boards.js +391 -0
- package/dist/operations/boards.js.map +1 -0
- package/dist/operations/cards.d.ts +36 -0
- package/dist/operations/cards.d.ts.map +1 -0
- package/dist/operations/cards.js +81 -0
- package/dist/operations/cards.js.map +1 -0
- package/dist/operations/comments.d.ts +28 -0
- package/dist/operations/comments.d.ts.map +1 -0
- package/dist/operations/comments.js +53 -0
- package/dist/operations/comments.js.map +1 -0
- package/dist/operations/labels.d.ts +60 -0
- package/dist/operations/labels.d.ts.map +1 -0
- package/dist/operations/labels.js +146 -0
- package/dist/operations/labels.js.map +1 -0
- package/dist/operations/lists.d.ts +15 -0
- package/dist/operations/lists.d.ts.map +1 -0
- package/dist/operations/lists.js +40 -0
- package/dist/operations/lists.js.map +1 -0
- package/dist/operations/projects.d.ts +24 -0
- package/dist/operations/projects.d.ts.map +1 -0
- package/dist/operations/projects.js +61 -0
- package/dist/operations/projects.js.map +1 -0
- package/dist/operations/tasks.d.ts +30 -0
- package/dist/operations/tasks.d.ts.map +1 -0
- package/dist/operations/tasks.js +111 -0
- package/dist/operations/tasks.js.map +1 -0
- package/dist/schemas/entities.d.ts +389 -0
- package/dist/schemas/entities.d.ts.map +1 -0
- package/dist/schemas/entities.js +202 -0
- package/dist/schemas/entities.js.map +1 -0
- package/dist/schemas/requests.d.ts +539 -0
- package/dist/schemas/requests.d.ts.map +1 -0
- package/dist/schemas/requests.js +187 -0
- package/dist/schemas/requests.js.map +1 -0
- package/dist/schemas/responses.d.ts +2626 -0
- package/dist/schemas/responses.d.ts.map +1 -0
- package/dist/schemas/responses.js +75 -0
- package/dist/schemas/responses.js.map +1 -0
- package/dist/tools/attachments.d.ts +270 -0
- package/dist/tools/attachments.d.ts.map +1 -0
- package/dist/tools/attachments.js +247 -0
- package/dist/tools/attachments.js.map +1 -0
- package/dist/tools/cards.d.ts +401 -0
- package/dist/tools/cards.d.ts.map +1 -0
- package/dist/tools/cards.js +368 -0
- package/dist/tools/cards.js.map +1 -0
- package/dist/tools/comments.d.ts +217 -0
- package/dist/tools/comments.d.ts.map +1 -0
- package/dist/tools/comments.js +208 -0
- package/dist/tools/comments.js.map +1 -0
- package/dist/tools/index.d.ts +1293 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +62 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/labels.d.ts +214 -0
- package/dist/tools/labels.d.ts.map +1 -0
- package/dist/tools/labels.js +302 -0
- package/dist/tools/labels.js.map +1 -0
- package/dist/tools/lists.d.ts +117 -0
- package/dist/tools/lists.d.ts.map +1 -0
- package/dist/tools/lists.js +178 -0
- package/dist/tools/lists.js.map +1 -0
- package/dist/tools/navigation.d.ts +106 -0
- package/dist/tools/navigation.d.ts.map +1 -0
- package/dist/tools/navigation.js +151 -0
- package/dist/tools/navigation.js.map +1 -0
- package/dist/tools/queries.d.ts +380 -0
- package/dist/tools/queries.d.ts.map +1 -0
- package/dist/tools/queries.js +256 -0
- package/dist/tools/queries.js.map +1 -0
- package/dist/tools/tasks.d.ts +223 -0
- package/dist/tools/tasks.d.ts.map +1 -0
- package/dist/tools/tasks.js +169 -0
- package/dist/tools/tasks.js.map +1 -0
- package/docs/planka-2x-gotchas.md +192 -0
- package/docs/tools.md +659 -0
- package/docs/troubleshooting.md +138 -0
- package/package.json +71 -0
- package/project.identity.json +7 -0
- package/scripts/bootstrap-board.mjs +160 -0
- package/scripts/lib/planka-api.mjs +244 -0
- package/scripts/lib/workflow-template.mjs +26 -0
- package/scripts/setup.mjs +236 -0
- package/scripts/setup.sh +48 -0
- package/scripts/sync-identity.mjs +57 -0
- package/server.json +43 -0
- package/tests/smoke/planka-smoke.mjs +920 -0
- package/workflow/README.md +232 -0
- package/workflow/board-template.md +115 -0
- package/workflow/skills/planka-close-card/SKILL.md +81 -0
- package/workflow/skills/planka-orchestrator/SKILL.md +126 -0
- package/workflow/worktrees/README.md +104 -0
- package/workflow/worktrees/wt.conf.example +24 -0
- package/workflow/worktrees/wt.sh +345 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
die() {
|
|
6
|
+
echo "wt: $*" >&2
|
|
7
|
+
exit 1
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
info() {
|
|
11
|
+
echo " $*"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
INITIAL_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null || true)"
|
|
16
|
+
[[ -n "$INITIAL_ROOT" ]] || die "not inside a Git repository"
|
|
17
|
+
COMMON_DIR="$(git -C "$INITIAL_ROOT" rev-parse --path-format=absolute --git-common-dir)"
|
|
18
|
+
REPO_ROOT="$(dirname "$COMMON_DIR")"
|
|
19
|
+
|
|
20
|
+
CONFIG_FILE="$REPO_ROOT/.agent-worktrees.conf"
|
|
21
|
+
if [[ -f "$CONFIG_FILE" ]]; then
|
|
22
|
+
# This file is repository configuration and executes as shell syntax. Review it
|
|
23
|
+
# before trusting a cloned repository, just like any other executable script.
|
|
24
|
+
# shellcheck disable=SC1090
|
|
25
|
+
source "$CONFIG_FILE"
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
detect_base_branch() {
|
|
29
|
+
local remote current
|
|
30
|
+
remote="$(git -C "$REPO_ROOT" symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null || true)"
|
|
31
|
+
if [[ -n "$remote" ]]; then
|
|
32
|
+
echo "${remote#origin/}"
|
|
33
|
+
elif git -C "$REPO_ROOT" show-ref --verify --quiet refs/heads/main; then
|
|
34
|
+
echo "main"
|
|
35
|
+
elif git -C "$REPO_ROOT" show-ref --verify --quiet refs/heads/master; then
|
|
36
|
+
echo "master"
|
|
37
|
+
else
|
|
38
|
+
current="$(git -C "$REPO_ROOT" branch --show-current)"
|
|
39
|
+
[[ -n "$current" ]] || die "cannot determine a base branch"
|
|
40
|
+
echo "$current"
|
|
41
|
+
fi
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
WT_BASE_BRANCH="${WT_BASE_BRANCH:-$(detect_base_branch)}"
|
|
45
|
+
WT_ROOT="${WT_ROOT:-.worktrees}"
|
|
46
|
+
WT_BRANCH_PREFIX="${WT_BRANCH_PREFIX:-agent/}"
|
|
47
|
+
WT_MAX_WORKTREES="${WT_MAX_WORKTREES:-4}"
|
|
48
|
+
WT_PORT_START="${WT_PORT_START:-}"
|
|
49
|
+
WT_DEV_COMMAND="${WT_DEV_COMMAND:-}"
|
|
50
|
+
WT_INSTALL_COMMAND="${WT_INSTALL_COMMAND:-}"
|
|
51
|
+
WT_ENV_TEMPLATE="${WT_ENV_TEMPLATE:-}"
|
|
52
|
+
WT_ENV_TARGET="${WT_ENV_TARGET:-.env}"
|
|
53
|
+
|
|
54
|
+
[[ "$WT_ROOT" = /* ]] || WT_ROOT="$REPO_ROOT/$WT_ROOT"
|
|
55
|
+
[[ "$WT_MAX_WORKTREES" =~ ^[1-9][0-9]*$ ]] || die "WT_MAX_WORKTREES must be a positive integer"
|
|
56
|
+
if [[ -n "$WT_PORT_START" ]]; then
|
|
57
|
+
[[ "$WT_PORT_START" =~ ^[1-9][0-9]*$ ]] || die "WT_PORT_START must be a positive integer"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
validate_slug() {
|
|
61
|
+
local slug="$1"
|
|
62
|
+
[[ "$slug" =~ ^[a-z0-9][a-z0-9-]{1,40}$ ]] ||
|
|
63
|
+
die "invalid slug '$slug'; use 2-41 lowercase letters, numbers, or hyphens"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
wt_path() {
|
|
67
|
+
echo "$WT_ROOT/$1"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
meta_file() {
|
|
71
|
+
echo "$WT_ROOT/.metadata/$1"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
read_meta() {
|
|
75
|
+
local slug="$1" key="$2" file
|
|
76
|
+
file="$(meta_file "$slug")"
|
|
77
|
+
[[ -f "$file" ]] || return 1
|
|
78
|
+
sed -n "s/^${key}=//p" "$file" | head -n 1
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
existing_slugs() {
|
|
82
|
+
local path
|
|
83
|
+
[[ -d "$WT_ROOT" ]] || return 0
|
|
84
|
+
for path in "$WT_ROOT"/*; do
|
|
85
|
+
[[ -d "$path" ]] || continue
|
|
86
|
+
basename "$path"
|
|
87
|
+
done | sort
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
port_in_use() {
|
|
91
|
+
local port="$1"
|
|
92
|
+
[[ -n "$port" ]] || return 1
|
|
93
|
+
if command -v lsof >/dev/null 2>&1; then
|
|
94
|
+
lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1
|
|
95
|
+
elif command -v ss >/dev/null 2>&1; then
|
|
96
|
+
ss -ltn "sport = :$port" 2>/dev/null | grep -q LISTEN
|
|
97
|
+
else
|
|
98
|
+
return 1
|
|
99
|
+
fi
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
pick_free_port() {
|
|
103
|
+
[[ -n "$WT_PORT_START" ]] || return 0
|
|
104
|
+
local offset candidate slug assigned reserved
|
|
105
|
+
for ((offset = 0; offset < WT_MAX_WORKTREES; offset += 1)); do
|
|
106
|
+
candidate=$((WT_PORT_START + offset))
|
|
107
|
+
reserved=0
|
|
108
|
+
while IFS= read -r slug; do
|
|
109
|
+
[[ -n "$slug" ]] || continue
|
|
110
|
+
assigned="$(read_meta "$slug" PORT || true)"
|
|
111
|
+
[[ "$assigned" == "$candidate" ]] && reserved=1
|
|
112
|
+
done < <(existing_slugs)
|
|
113
|
+
if [[ "$reserved" -eq 0 ]] && ! port_in_use "$candidate"; then
|
|
114
|
+
echo "$candidate"
|
|
115
|
+
return 0
|
|
116
|
+
fi
|
|
117
|
+
done
|
|
118
|
+
return 1
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
discover_dev_command() {
|
|
122
|
+
if [[ -n "$WT_DEV_COMMAND" ]]; then
|
|
123
|
+
echo "$WT_DEV_COMMAND"
|
|
124
|
+
return 0
|
|
125
|
+
fi
|
|
126
|
+
[[ -f "$REPO_ROOT/package.json" ]] ||
|
|
127
|
+
die "set WT_DEV_COMMAND; no package.json was found"
|
|
128
|
+
node -e '
|
|
129
|
+
const p = require(process.argv[1]);
|
|
130
|
+
process.exit(p.scripts && p.scripts.dev ? 0 : 1);
|
|
131
|
+
' "$REPO_ROOT/package.json" || die "set WT_DEV_COMMAND; package.json has no dev script"
|
|
132
|
+
|
|
133
|
+
if [[ -f "$REPO_ROOT/pnpm-lock.yaml" ]]; then echo "pnpm run dev"
|
|
134
|
+
elif [[ -f "$REPO_ROOT/yarn.lock" ]]; then echo "yarn dev"
|
|
135
|
+
elif [[ -f "$REPO_ROOT/bun.lock" || -f "$REPO_ROOT/bun.lockb" ]]; then echo "bun run dev"
|
|
136
|
+
elif [[ -f "$REPO_ROOT/package-lock.json" ]]; then echo "npm run dev"
|
|
137
|
+
else die "set WT_DEV_COMMAND; no recognized package manager lockfile was found"
|
|
138
|
+
fi
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
cmd_new() {
|
|
142
|
+
local slug="${1:-}" path branch count port template target
|
|
143
|
+
validate_slug "$slug"
|
|
144
|
+
path="$(wt_path "$slug")"
|
|
145
|
+
branch="${WT_BRANCH_PREFIX}${slug}"
|
|
146
|
+
[[ ! -e "$path" ]] || die "worktree already exists: $path"
|
|
147
|
+
git -C "$REPO_ROOT" show-ref --verify --quiet "refs/heads/$branch" &&
|
|
148
|
+
die "branch already exists: $branch"
|
|
149
|
+
|
|
150
|
+
count="$(existing_slugs | sed '/^$/d' | wc -l | tr -d ' ')"
|
|
151
|
+
((count < WT_MAX_WORKTREES)) || die "maximum of $WT_MAX_WORKTREES live worktrees reached"
|
|
152
|
+
port="$(pick_free_port)" || die "no configured worktree port is free"
|
|
153
|
+
|
|
154
|
+
mkdir -p "$WT_ROOT"
|
|
155
|
+
git -C "$REPO_ROOT" worktree add -b "$branch" "$path" "$WT_BASE_BRANCH"
|
|
156
|
+
mkdir -p "$WT_ROOT/.metadata"
|
|
157
|
+
{
|
|
158
|
+
echo "SLUG=$slug"
|
|
159
|
+
echo "BRANCH=$branch"
|
|
160
|
+
echo "PORT=$port"
|
|
161
|
+
} > "$(meta_file "$slug")"
|
|
162
|
+
|
|
163
|
+
if [[ -n "$WT_ENV_TEMPLATE" ]]; then
|
|
164
|
+
template="$WT_ENV_TEMPLATE"
|
|
165
|
+
[[ "$template" = /* ]] || template="$REPO_ROOT/$template"
|
|
166
|
+
[[ -f "$template" ]] || die "WT_ENV_TEMPLATE does not exist: $template"
|
|
167
|
+
target="$path/$WT_ENV_TARGET"
|
|
168
|
+
mkdir -p "$(dirname "$target")"
|
|
169
|
+
cp "$template" "$target"
|
|
170
|
+
chmod 600 "$target"
|
|
171
|
+
info "copied reviewed environment template to $WT_ENV_TARGET"
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
if [[ -n "$WT_INSTALL_COMMAND" ]]; then
|
|
175
|
+
(cd "$path" && bash -lc "$WT_INSTALL_COMMAND")
|
|
176
|
+
info "dependency setup complete"
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
info "worktree: $path"
|
|
180
|
+
info "branch: $branch"
|
|
181
|
+
[[ -n "$port" ]] && info "port: $port"
|
|
182
|
+
return 0
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
cmd_up() {
|
|
186
|
+
local slug="${1:-}" path port command
|
|
187
|
+
validate_slug "$slug"
|
|
188
|
+
path="$(wt_path "$slug")"
|
|
189
|
+
[[ -d "$path" ]] || die "unknown worktree: $slug"
|
|
190
|
+
port="$(read_meta "$slug" PORT || true)"
|
|
191
|
+
[[ -z "$port" ]] || ! port_in_use "$port" || die "port $port is already in use"
|
|
192
|
+
command="$(discover_dev_command)"
|
|
193
|
+
if [[ "$command" == *"{port}"* && -z "$port" ]]; then
|
|
194
|
+
die "WT_DEV_COMMAND uses {port}, but WT_PORT_START is not configured"
|
|
195
|
+
fi
|
|
196
|
+
command="${command//\{port\}/$port}"
|
|
197
|
+
info "starting $slug${port:+ on port $port}"
|
|
198
|
+
cd "$path"
|
|
199
|
+
if [[ -n "$port" ]]; then
|
|
200
|
+
PORT="$port" exec bash -lc "$command"
|
|
201
|
+
else
|
|
202
|
+
exec bash -lc "$command"
|
|
203
|
+
fi
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
cmd_list() {
|
|
207
|
+
local slugs slug path branch port dev dirty ahead
|
|
208
|
+
slugs="$(existing_slugs)"
|
|
209
|
+
[[ -n "$slugs" ]] || {
|
|
210
|
+
echo "No live worktrees."
|
|
211
|
+
return 0
|
|
212
|
+
}
|
|
213
|
+
printf '%-24s %-32s %-8s %-10s %s\n' WORKTREE BRANCH PORT DEV CHANGES
|
|
214
|
+
while IFS= read -r slug; do
|
|
215
|
+
[[ -n "$slug" ]] || continue
|
|
216
|
+
path="$(wt_path "$slug")"
|
|
217
|
+
branch="$(read_meta "$slug" BRANCH || git -C "$path" branch --show-current)"
|
|
218
|
+
port="$(read_meta "$slug" PORT || true)"
|
|
219
|
+
if [[ -n "$port" ]] && port_in_use "$port"; then dev="running"; else dev="stopped"; fi
|
|
220
|
+
dirty="$(git -C "$path" status --porcelain | sed '/^$/d' | wc -l | tr -d ' ')"
|
|
221
|
+
ahead="$(git -C "$path" rev-list --count "$WT_BASE_BRANCH..HEAD")"
|
|
222
|
+
printf '%-24s %-32s %-8s %-10s dirty=%s ahead=%s\n' \
|
|
223
|
+
"$slug" "$branch" "${port:--}" "$dev" "$dirty" "$ahead"
|
|
224
|
+
done <<< "$slugs"
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
cmd_doctor() {
|
|
228
|
+
local slugs tmp slug path shared file
|
|
229
|
+
slugs="$(existing_slugs)"
|
|
230
|
+
[[ -n "$slugs" ]] || {
|
|
231
|
+
echo "No live worktrees."
|
|
232
|
+
return 0
|
|
233
|
+
}
|
|
234
|
+
tmp="$(mktemp)"
|
|
235
|
+
trap 'rm -f "$tmp"' RETURN
|
|
236
|
+
while IFS= read -r slug; do
|
|
237
|
+
[[ -n "$slug" ]] || continue
|
|
238
|
+
path="$(wt_path "$slug")"
|
|
239
|
+
{
|
|
240
|
+
git -C "$path" diff --name-only "$WT_BASE_BRANCH...HEAD"
|
|
241
|
+
git -C "$path" diff --name-only
|
|
242
|
+
git -C "$path" ls-files --others --exclude-standard
|
|
243
|
+
} | sort -u | while IFS= read -r file; do
|
|
244
|
+
[[ -n "$file" ]] && printf '%s\t%s\n' "$slug" "$file"
|
|
245
|
+
done
|
|
246
|
+
done <<< "$slugs" > "$tmp"
|
|
247
|
+
shared="$(cut -f2- "$tmp" | sort | uniq -d)"
|
|
248
|
+
if [[ -z "$shared" ]]; then
|
|
249
|
+
echo "No file is modified by more than one worktree."
|
|
250
|
+
return 0
|
|
251
|
+
fi
|
|
252
|
+
echo "Files modified by multiple worktrees:"
|
|
253
|
+
while IFS= read -r file; do
|
|
254
|
+
[[ -n "$file" ]] || continue
|
|
255
|
+
echo " $file"
|
|
256
|
+
awk -F '\t' -v target="$file" '$2 == target { print " - " $1 }' "$tmp"
|
|
257
|
+
done <<< "$shared"
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
cmd_kill() {
|
|
261
|
+
local slug="${1:-}" force="${2:-}" path port dirty ahead branch
|
|
262
|
+
validate_slug "$slug"
|
|
263
|
+
[[ -z "$force" || "$force" == "--force" ]] || die "unknown kill option: $force"
|
|
264
|
+
path="$(wt_path "$slug")"
|
|
265
|
+
[[ -d "$path" ]] || die "unknown worktree: $slug"
|
|
266
|
+
port="$(read_meta "$slug" PORT || true)"
|
|
267
|
+
[[ -z "$port" ]] || ! port_in_use "$port" || die "development server is still using port $port"
|
|
268
|
+
dirty="$(git -C "$path" status --porcelain | sed '/^$/d' | wc -l | tr -d ' ')"
|
|
269
|
+
ahead="$(git -C "$path" rev-list --count "$WT_BASE_BRANCH..HEAD")"
|
|
270
|
+
if [[ "$force" != "--force" && ( "$dirty" -gt 0 || "$ahead" -gt 0 ) ]]; then
|
|
271
|
+
die "$slug has uncommitted or unmerged work; merge it or repeat kill with --force"
|
|
272
|
+
fi
|
|
273
|
+
branch="$(git -C "$path" branch --show-current)"
|
|
274
|
+
if [[ "$force" == "--force" ]]; then
|
|
275
|
+
git -C "$REPO_ROOT" worktree remove --force "$path"
|
|
276
|
+
else
|
|
277
|
+
git -C "$REPO_ROOT" worktree remove "$path"
|
|
278
|
+
fi
|
|
279
|
+
if [[ -n "$branch" && "$branch" != "$WT_BASE_BRANCH" ]]; then
|
|
280
|
+
git -C "$REPO_ROOT" branch -d "$branch" >/dev/null 2>&1 ||
|
|
281
|
+
info "branch preserved because it is not merged: $branch"
|
|
282
|
+
fi
|
|
283
|
+
rm -f "$(meta_file "$slug")"
|
|
284
|
+
info "closed $slug"
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
cmd_merge() {
|
|
288
|
+
local slug="${1:-}" path port dirty ahead branch current lock
|
|
289
|
+
validate_slug "$slug"
|
|
290
|
+
path="$(wt_path "$slug")"
|
|
291
|
+
[[ -d "$path" ]] || die "unknown worktree: $slug"
|
|
292
|
+
mkdir -p "$WT_ROOT"
|
|
293
|
+
lock="$WT_ROOT/.merge.lock"
|
|
294
|
+
mkdir "$lock" 2>/dev/null || die "another merge is in progress"
|
|
295
|
+
MERGE_LOCK_DIR="$lock"
|
|
296
|
+
trap 'rmdir "$MERGE_LOCK_DIR" 2>/dev/null || true' EXIT INT TERM
|
|
297
|
+
|
|
298
|
+
port="$(read_meta "$slug" PORT || true)"
|
|
299
|
+
[[ -z "$port" ]] || ! port_in_use "$port" || die "development server is still using port $port"
|
|
300
|
+
dirty="$(git -C "$path" status --porcelain | sed '/^$/d' | wc -l | tr -d ' ')"
|
|
301
|
+
[[ "$dirty" -eq 0 ]] || die "worker tree has $dirty uncommitted change(s)"
|
|
302
|
+
ahead="$(git -C "$path" rev-list --count "$WT_BASE_BRANCH..HEAD")"
|
|
303
|
+
((ahead > 0)) || die "worker branch has no commits ahead of $WT_BASE_BRANCH"
|
|
304
|
+
dirty="$(git -C "$REPO_ROOT" status --porcelain | sed '/^$/d' | wc -l | tr -d ' ')"
|
|
305
|
+
[[ "$dirty" -eq 0 ]] || die "base tree has $dirty uncommitted change(s)"
|
|
306
|
+
current="$(git -C "$REPO_ROOT" branch --show-current)"
|
|
307
|
+
[[ "$current" == "$WT_BASE_BRANCH" ]] || die "base tree must have $WT_BASE_BRANCH checked out"
|
|
308
|
+
branch="$(git -C "$path" branch --show-current)"
|
|
309
|
+
|
|
310
|
+
if ! git -C "$path" merge "$WT_BASE_BRANCH" -m "merge: refresh $slug before integration"; then
|
|
311
|
+
git -C "$path" merge --abort >/dev/null 2>&1 || true
|
|
312
|
+
die "conflict while bringing $WT_BASE_BRANCH into $slug; resolve it in the worktree"
|
|
313
|
+
fi
|
|
314
|
+
git -C "$REPO_ROOT" merge --no-ff "$branch" -m "merge($slug): integrate worktree"
|
|
315
|
+
cmd_kill "$slug"
|
|
316
|
+
rmdir "$MERGE_LOCK_DIR" 2>/dev/null || true
|
|
317
|
+
trap - EXIT INT TERM
|
|
318
|
+
info "merged locally into $WT_BASE_BRANCH; nothing was pushed"
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
usage() {
|
|
322
|
+
cat <<'EOF'
|
|
323
|
+
wt.sh — conservative Git worktree helper
|
|
324
|
+
|
|
325
|
+
wt.sh new <slug> create a branch and worktree
|
|
326
|
+
wt.sh up <slug> start the configured/discovered dev command
|
|
327
|
+
wt.sh list show live worktrees and Git state
|
|
328
|
+
wt.sh doctor report overlapping changed files
|
|
329
|
+
wt.sh merge <slug> merge --no-ff locally and close the worktree
|
|
330
|
+
wt.sh kill <slug> [--force] close without merging
|
|
331
|
+
|
|
332
|
+
Configuration: .agent-worktrees.conf in the main repository root.
|
|
333
|
+
EOF
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
case "${1:-}" in
|
|
337
|
+
new) shift; cmd_new "$@" ;;
|
|
338
|
+
up) shift; cmd_up "$@" ;;
|
|
339
|
+
list) shift; cmd_list "$@" ;;
|
|
340
|
+
doctor) shift; cmd_doctor "$@" ;;
|
|
341
|
+
merge) shift; cmd_merge "$@" ;;
|
|
342
|
+
kill) shift; cmd_kill "$@" ;;
|
|
343
|
+
-h|--help|help) usage ;;
|
|
344
|
+
*) usage; exit 1 ;;
|
|
345
|
+
esac
|