@imdeadpool/guardex 7.0.14 → 7.0.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imdeadpool/guardex",
3
- "version": "7.0.14",
3
+ "version": "7.0.16",
4
4
  "description": "GitGuardex: hardened multi-agent git guardrails for parallel agent work.",
5
5
  "license": "MIT",
6
6
  "preferGlobal": true,
@@ -15,6 +15,7 @@
15
15
  "agent:codex": "bash ./scripts/codex-agent.sh",
16
16
  "agent:branch:start": "bash ./scripts/agent-branch-start.sh",
17
17
  "agent:branch:finish": "bash ./scripts/agent-branch-finish.sh",
18
+ "agent:branch:merge": "bash ./scripts/agent-branch-merge.sh",
18
19
  "agent:cleanup": "gx cleanup",
19
20
  "agent:hooks:install": "bash ./scripts/install-agent-git-hooks.sh",
20
21
  "agent:locks:claim": "python3 ./scripts/agent-file-locks.py claim",
@@ -60,7 +61,7 @@
60
61
  "bugs": {
61
62
  "url": "https://github.com/recodeee/gitguardex/issues"
62
63
  },
63
- "homepage": "https://guardextutorial.com",
64
+ "homepage": "https://github.com/recodeee/gitguardex-frontend",
64
65
  "funding": "https://github.com/sponsors/recodeecom",
65
66
  "publishConfig": {
66
67
  "access": "public"
@@ -144,24 +144,44 @@ else
144
144
  common_git_dir="$(cd "$repo_root/$common_git_dir_raw" && pwd -P)"
145
145
  fi
146
146
  repo_common_root="$(cd "$common_git_dir/.." && pwd -P)"
147
- agent_worktree_root="${repo_common_root}/.omx/agent-worktrees"
148
147
 
149
148
  if [[ -z "$SOURCE_BRANCH" ]]; then
150
149
  SOURCE_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
151
150
  fi
152
151
 
152
+ stored_worktree_root_rel="$(git -C "$repo_root" config --get "branch.${SOURCE_BRANCH}.guardexWorktreeRoot" || true)"
153
+ if [[ -z "$stored_worktree_root_rel" ]]; then
154
+ stored_worktree_root_rel=".omx/agent-worktrees"
155
+ fi
156
+ agent_worktree_root="${repo_common_root}/${stored_worktree_root_rel}"
157
+
153
158
  if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -z "$BASE_BRANCH" ]]; then
154
159
  echo "[agent-branch-finish] --base requires a non-empty branch name." >&2
155
160
  exit 1
156
161
  fi
157
162
 
158
163
  if [[ "$BASE_BRANCH_EXPLICIT" -eq 0 ]]; then
159
- configured_base="$(git -C "$repo_root" config --get multiagent.baseBranch || true)"
160
- if [[ -n "$configured_base" ]]; then
161
- BASE_BRANCH="$configured_base"
164
+ source_branch_base="$(git -C "$repo_root" config --get "branch.${SOURCE_BRANCH}.guardexBase" || true)"
165
+ if [[ -n "$source_branch_base" ]]; then
166
+ BASE_BRANCH="$source_branch_base"
167
+ else
168
+ configured_base="$(git -C "$repo_root" config --get multiagent.baseBranch || true)"
169
+ if [[ -n "$configured_base" ]]; then
170
+ BASE_BRANCH="$configured_base"
171
+ fi
162
172
  fi
163
173
  fi
164
174
 
175
+ if [[ -z "$BASE_BRANCH" ]]; then
176
+ for fallback_branch in dev main master; do
177
+ if git -C "$repo_root" show-ref --verify --quiet "refs/heads/${fallback_branch}" \
178
+ || git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/${fallback_branch}"; then
179
+ BASE_BRANCH="$fallback_branch"
180
+ break
181
+ fi
182
+ done
183
+ fi
184
+
165
185
  if [[ -z "$BASE_BRANCH" ]]; then
166
186
  BASE_BRANCH="dev"
167
187
  fi
@@ -268,8 +288,17 @@ if [[ "$should_require_sync" -eq 1 ]] && git -C "$repo_root" show-ref --verify -
268
288
  fi
269
289
  fi
270
290
 
271
- integration_worktree="${agent_worktree_root}/__integrate-${BASE_BRANCH//\//__}-$(date +%Y%m%d-%H%M%S)"
272
- integration_branch="__agent_integrate_${BASE_BRANCH//\//_}_$(date +%Y%m%d_%H%M%S)"
291
+ integration_stamp="$(date +%Y%m%d-%H%M%S)"
292
+ integration_worktree_base="${agent_worktree_root}/__integrate-${BASE_BRANCH//\//__}-${integration_stamp}"
293
+ integration_branch_base="__agent_integrate_${BASE_BRANCH//\//_}_$(date +%Y%m%d_%H%M%S)"
294
+ integration_worktree="$integration_worktree_base"
295
+ integration_branch="$integration_branch_base"
296
+ integration_suffix=1
297
+ while [[ -e "$integration_worktree" ]] || git -C "$repo_root" show-ref --verify --quiet "refs/heads/${integration_branch}"; do
298
+ integration_worktree="${integration_worktree_base}-${integration_suffix}"
299
+ integration_branch="${integration_branch_base}_${integration_suffix}"
300
+ integration_suffix=$((integration_suffix + 1))
301
+ done
273
302
  mkdir -p "$(dirname "$integration_worktree")"
274
303
 
275
304
  git -C "$repo_root" worktree add "$integration_worktree" "$start_ref" >/dev/null
@@ -0,0 +1,421 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ BASE_BRANCH=""
5
+ BASE_BRANCH_EXPLICIT=0
6
+ TARGET_BRANCH=""
7
+ TASK_NAME=""
8
+ AGENT_NAME="${GUARDEX_MERGE_AGENT_NAME:-codex}"
9
+ declare -a SOURCE_BRANCHES=()
10
+
11
+ usage() {
12
+ cat <<'EOF'
13
+ Usage: scripts/agent-branch-merge.sh --branch <agent/...> [--branch <agent/...> ...] [--into <agent/...>] [--task <task>] [--agent <agent>] [--base <branch>]
14
+
15
+ Examples:
16
+ bash scripts/agent-branch-merge.sh --branch agent/codex/ui-a --branch agent/codex/ui-b
17
+ bash scripts/agent-branch-merge.sh --into agent/codex/owner-lane --branch agent/codex/helper-a --branch agent/codex/helper-b
18
+ EOF
19
+ }
20
+
21
+ sanitize_slug() {
22
+ local raw="$1"
23
+ local fallback="${2:-merge-agent-branches}"
24
+ local slug
25
+ slug="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//; s/-{2,}/-/g')"
26
+ if [[ -z "$slug" ]]; then
27
+ slug="$fallback"
28
+ fi
29
+ printf '%s' "$slug"
30
+ }
31
+
32
+ resolve_base_branch() {
33
+ local repo="$1"
34
+ local explicit_target="$2"
35
+ local configured=""
36
+ local branch_base=""
37
+
38
+ if [[ -n "$explicit_target" ]]; then
39
+ branch_base="$(git -C "$repo" config --get "branch.${explicit_target}.guardexBase" || true)"
40
+ if [[ -n "$branch_base" ]]; then
41
+ printf '%s' "$branch_base"
42
+ return 0
43
+ fi
44
+ fi
45
+
46
+ configured="$(git -C "$repo" config --get multiagent.baseBranch || true)"
47
+ if [[ -n "$configured" ]]; then
48
+ printf '%s' "$configured"
49
+ return 0
50
+ fi
51
+
52
+ for fallback in dev main master; do
53
+ if git -C "$repo" show-ref --verify --quiet "refs/heads/${fallback}" \
54
+ || git -C "$repo" show-ref --verify --quiet "refs/remotes/origin/${fallback}"; then
55
+ printf '%s' "$fallback"
56
+ return 0
57
+ fi
58
+ done
59
+
60
+ printf '%s' "dev"
61
+ }
62
+
63
+ get_worktree_for_branch() {
64
+ local repo="$1"
65
+ local branch="$2"
66
+ git -C "$repo" worktree list --porcelain | awk -v target="refs/heads/${branch}" '
67
+ $1 == "worktree" { wt = $2 }
68
+ $1 == "branch" && $2 == target { print wt; exit }
69
+ '
70
+ }
71
+
72
+ is_clean_worktree() {
73
+ local wt="$1"
74
+ git -C "$wt" diff --quiet -- . ":(exclude).omx/state/agent-file-locks.json" \
75
+ && git -C "$wt" diff --cached --quiet -- . ":(exclude).omx/state/agent-file-locks.json" \
76
+ && [[ -z "$(git -C "$wt" ls-files --others --exclude-standard)" ]]
77
+ }
78
+
79
+ has_in_progress_git_op() {
80
+ local wt="$1"
81
+ local git_dir=""
82
+ git_dir="$(git -C "$wt" rev-parse --git-dir 2>/dev/null || true)"
83
+ if [[ -z "$git_dir" ]]; then
84
+ return 1
85
+ fi
86
+ if [[ "$git_dir" != /* ]]; then
87
+ git_dir="$(cd "$wt/$git_dir" 2>/dev/null && pwd -P || true)"
88
+ fi
89
+ if [[ -z "$git_dir" ]]; then
90
+ return 1
91
+ fi
92
+ [[ -f "${git_dir}/MERGE_HEAD" || -d "${git_dir}/rebase-merge" || -d "${git_dir}/rebase-apply" ]]
93
+ }
94
+
95
+ select_unique_worktree_path() {
96
+ local root="$1"
97
+ local name="$2"
98
+ local candidate="${root}/${name}"
99
+ local suffix=2
100
+ while [[ -e "$candidate" ]]; do
101
+ candidate="${root}/${name}-${suffix}"
102
+ suffix=$((suffix + 1))
103
+ done
104
+ printf '%s' "$candidate"
105
+ }
106
+
107
+ branch_exists() {
108
+ local repo="$1"
109
+ local branch="$2"
110
+ git -C "$repo" show-ref --verify --quiet "refs/heads/${branch}"
111
+ }
112
+
113
+ branch_is_agent_lane() {
114
+ local branch="$1"
115
+ [[ "$branch" == agent/* ]]
116
+ }
117
+
118
+ array_contains() {
119
+ local needle="$1"
120
+ shift || true
121
+ local item
122
+ for item in "$@"; do
123
+ if [[ "$item" == "$needle" ]]; then
124
+ return 0
125
+ fi
126
+ done
127
+ return 1
128
+ }
129
+
130
+ collect_branch_files() {
131
+ local repo="$1"
132
+ local base_ref="$2"
133
+ local branch="$3"
134
+ git -C "$repo" diff --name-only "${base_ref}...${branch}" -- . ":(exclude).omx/state/agent-file-locks.json" 2>/dev/null || true
135
+ }
136
+
137
+ while [[ $# -gt 0 ]]; do
138
+ case "$1" in
139
+ --base)
140
+ BASE_BRANCH="${2:-}"
141
+ BASE_BRANCH_EXPLICIT=1
142
+ shift 2
143
+ ;;
144
+ --into)
145
+ TARGET_BRANCH="${2:-}"
146
+ shift 2
147
+ ;;
148
+ --branch)
149
+ SOURCE_BRANCHES+=("${2:-}")
150
+ shift 2
151
+ ;;
152
+ --task)
153
+ TASK_NAME="${2:-}"
154
+ shift 2
155
+ ;;
156
+ --agent)
157
+ AGENT_NAME="${2:-codex}"
158
+ shift 2
159
+ ;;
160
+ -h|--help)
161
+ usage
162
+ exit 0
163
+ ;;
164
+ *)
165
+ echo "[agent-branch-merge] Unknown argument: $1" >&2
166
+ usage >&2
167
+ exit 1
168
+ ;;
169
+ esac
170
+ done
171
+
172
+ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
173
+ echo "[agent-branch-merge] Not inside a git repository." >&2
174
+ exit 1
175
+ fi
176
+
177
+ repo_root="$(git rev-parse --show-toplevel)"
178
+ common_git_dir_raw="$(git -C "$repo_root" rev-parse --git-common-dir)"
179
+ if [[ "$common_git_dir_raw" == /* ]]; then
180
+ common_git_dir="$common_git_dir_raw"
181
+ else
182
+ common_git_dir="$(cd "$repo_root/$common_git_dir_raw" && pwd -P)"
183
+ fi
184
+ repo_common_root="$(cd "$common_git_dir/.." && pwd -P)"
185
+ agent_worktree_root="${repo_common_root}/.omx/agent-worktrees"
186
+ mkdir -p "$agent_worktree_root"
187
+
188
+ if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -z "$BASE_BRANCH" ]]; then
189
+ echo "[agent-branch-merge] --base requires a branch value." >&2
190
+ exit 1
191
+ fi
192
+
193
+ if [[ -z "$TARGET_BRANCH" && "${#SOURCE_BRANCHES[@]}" -lt 1 ]]; then
194
+ echo "[agent-branch-merge] Provide at least one --branch <agent/...> source lane." >&2
195
+ exit 1
196
+ fi
197
+
198
+ if [[ -n "$TARGET_BRANCH" ]] && ! branch_is_agent_lane "$TARGET_BRANCH"; then
199
+ echo "[agent-branch-merge] --into must reference an agent/* branch: ${TARGET_BRANCH}" >&2
200
+ exit 1
201
+ fi
202
+
203
+ deduped_sources=()
204
+ for branch in "${SOURCE_BRANCHES[@]}"; do
205
+ if [[ -z "$branch" ]]; then
206
+ echo "[agent-branch-merge] --branch requires an agent/* branch value." >&2
207
+ exit 1
208
+ fi
209
+ if ! branch_is_agent_lane "$branch"; then
210
+ echo "[agent-branch-merge] Source branch must be agent/*: ${branch}" >&2
211
+ exit 1
212
+ fi
213
+ if ! branch_exists "$repo_root" "$branch"; then
214
+ echo "[agent-branch-merge] Local source branch not found: ${branch}" >&2
215
+ exit 1
216
+ fi
217
+ if ! array_contains "$branch" "${deduped_sources[@]}"; then
218
+ deduped_sources+=("$branch")
219
+ fi
220
+ done
221
+ SOURCE_BRANCHES=("${deduped_sources[@]}")
222
+
223
+ if [[ "${#SOURCE_BRANCHES[@]}" -eq 0 ]]; then
224
+ echo "[agent-branch-merge] No unique source branches were provided." >&2
225
+ exit 1
226
+ fi
227
+
228
+ if [[ "$BASE_BRANCH_EXPLICIT" -eq 0 ]]; then
229
+ BASE_BRANCH="$(resolve_base_branch "$repo_root" "$TARGET_BRANCH")"
230
+ fi
231
+
232
+ if [[ -z "$BASE_BRANCH" ]]; then
233
+ echo "[agent-branch-merge] Unable to resolve a base branch." >&2
234
+ exit 1
235
+ fi
236
+
237
+ start_ref="$BASE_BRANCH"
238
+ if git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/${BASE_BRANCH}"; then
239
+ git -C "$repo_root" fetch origin "$BASE_BRANCH" --quiet
240
+ start_ref="origin/${BASE_BRANCH}"
241
+ elif ! git -C "$repo_root" show-ref --verify --quiet "refs/heads/${BASE_BRANCH}"; then
242
+ echo "[agent-branch-merge] Base branch not found locally or on origin: ${BASE_BRANCH}" >&2
243
+ exit 1
244
+ fi
245
+
246
+ target_worktree=""
247
+ target_created=0
248
+
249
+ if [[ -z "$TARGET_BRANCH" ]]; then
250
+ if [[ -z "$TASK_NAME" ]]; then
251
+ first_hint="$(printf '%s' "${SOURCE_BRANCHES[0]}" | sed -E 's#^agent/[^/]+/##; s#^agent/##')"
252
+ source_count="${#SOURCE_BRANCHES[@]}"
253
+ if [[ "$source_count" -gt 1 ]]; then
254
+ TASK_NAME="$(sanitize_slug "merge-${first_hint}-and-$((source_count - 1))-more" "merge-agent-branches")"
255
+ else
256
+ TASK_NAME="$(sanitize_slug "merge-${first_hint}" "merge-agent-branches")"
257
+ fi
258
+ else
259
+ TASK_NAME="$(sanitize_slug "$TASK_NAME" "merge-agent-branches")"
260
+ fi
261
+
262
+ start_output=""
263
+ if ! start_output="$(
264
+ cd "$repo_root"
265
+ env GUARDEX_OPENSPEC_AUTO_INIT=1 bash "scripts/agent-branch-start.sh" "$TASK_NAME" "$AGENT_NAME" "$BASE_BRANCH" 2>&1
266
+ )"; then
267
+ printf '%s\n' "$start_output" >&2
268
+ exit 1
269
+ fi
270
+
271
+ printf '%s\n' "$start_output"
272
+ TARGET_BRANCH="$(printf '%s\n' "$start_output" | sed -n 's/^\[agent-branch-start\] Created branch: //p' | head -n 1)"
273
+ target_worktree="$(printf '%s\n' "$start_output" | sed -n 's/^\[agent-branch-start\] Worktree: //p' | head -n 1)"
274
+ if [[ -z "$TARGET_BRANCH" || -z "$target_worktree" ]]; then
275
+ echo "[agent-branch-merge] Unable to parse target branch/worktree from agent-branch-start output." >&2
276
+ exit 1
277
+ fi
278
+ target_created=1
279
+ else
280
+ if ! branch_exists "$repo_root" "$TARGET_BRANCH"; then
281
+ echo "[agent-branch-merge] Target branch not found: ${TARGET_BRANCH}" >&2
282
+ exit 1
283
+ fi
284
+
285
+ target_worktree="$(get_worktree_for_branch "$repo_root" "$TARGET_BRANCH")"
286
+ if [[ -z "$target_worktree" ]]; then
287
+ target_worktree="$(select_unique_worktree_path "$agent_worktree_root" "${TARGET_BRANCH//\//__}")"
288
+ git -C "$repo_root" worktree add "$target_worktree" "$TARGET_BRANCH" >/dev/null
289
+ target_created=1
290
+ echo "[agent-branch-merge] Attached worktree for target branch '${TARGET_BRANCH}': ${target_worktree}"
291
+ fi
292
+ fi
293
+
294
+ if [[ "$TARGET_BRANCH" == "$BASE_BRANCH" ]]; then
295
+ echo "[agent-branch-merge] Target branch must not equal the protected base branch '${BASE_BRANCH}'." >&2
296
+ exit 1
297
+ fi
298
+
299
+ if ! is_clean_worktree "$target_worktree"; then
300
+ if [[ "$target_created" -eq 1 ]]; then
301
+ echo "[agent-branch-merge] Target worktree has freshly generated scaffold changes; continuing inside the new integration lane."
302
+ else
303
+ echo "[agent-branch-merge] Target worktree is not clean: ${target_worktree}" >&2
304
+ echo "[agent-branch-merge] Commit, stash, or discard local changes before merging agent lanes." >&2
305
+ exit 1
306
+ fi
307
+ fi
308
+
309
+ if has_in_progress_git_op "$target_worktree"; then
310
+ echo "[agent-branch-merge] Target worktree has an in-progress merge/rebase: ${target_worktree}" >&2
311
+ echo "[agent-branch-merge] Resolve or abort that git operation before running the merge workflow." >&2
312
+ exit 1
313
+ fi
314
+
315
+ for source_branch in "${SOURCE_BRANCHES[@]}"; do
316
+ if [[ "$source_branch" == "$TARGET_BRANCH" ]]; then
317
+ echo "[agent-branch-merge] Source branch list includes the target branch: ${source_branch}" >&2
318
+ exit 1
319
+ fi
320
+ source_worktree="$(get_worktree_for_branch "$repo_root" "$source_branch")"
321
+ if [[ -n "$source_worktree" ]] && ! is_clean_worktree "$source_worktree"; then
322
+ echo "[agent-branch-merge] Source worktree is not clean for '${source_branch}': ${source_worktree}" >&2
323
+ echo "[agent-branch-merge] Commit or stash source-lane changes before integration." >&2
324
+ exit 1
325
+ fi
326
+ done
327
+
328
+ pending_branches=()
329
+ for source_branch in "${SOURCE_BRANCHES[@]}"; do
330
+ if git -C "$repo_root" merge-base --is-ancestor "$source_branch" "$TARGET_BRANCH" >/dev/null 2>&1; then
331
+ echo "[agent-branch-merge] Skipping '${source_branch}' because it is already integrated into '${TARGET_BRANCH}'."
332
+ continue
333
+ fi
334
+ pending_branches+=("$source_branch")
335
+ done
336
+
337
+ if [[ "${#pending_branches[@]}" -eq 0 ]]; then
338
+ echo "[agent-branch-merge] No pending source branches remain for target '${TARGET_BRANCH}'."
339
+ echo "[agent-branch-merge] Target worktree: ${target_worktree}"
340
+ exit 0
341
+ fi
342
+
343
+ declare -A file_to_branches=()
344
+ declare -a overlap_files=()
345
+ for source_branch in "${pending_branches[@]}"; do
346
+ while IFS= read -r changed_file; do
347
+ [[ -z "$changed_file" ]] && continue
348
+ existing="${file_to_branches[$changed_file]:-}"
349
+ if [[ -z "$existing" ]]; then
350
+ file_to_branches["$changed_file"]="$source_branch"
351
+ continue
352
+ fi
353
+ if [[ ",${existing}," == *",${source_branch},"* ]]; then
354
+ continue
355
+ fi
356
+ file_to_branches["$changed_file"]="${existing},${source_branch}"
357
+ if ! array_contains "$changed_file" "${overlap_files[@]}"; then
358
+ overlap_files+=("$changed_file")
359
+ fi
360
+ done < <(collect_branch_files "$repo_root" "$start_ref" "$source_branch")
361
+ done
362
+
363
+ echo "[agent-branch-merge] Target branch: ${TARGET_BRANCH}"
364
+ echo "[agent-branch-merge] Target worktree: ${target_worktree}"
365
+ echo "[agent-branch-merge] Base branch: ${BASE_BRANCH} (${start_ref})"
366
+ echo "[agent-branch-merge] Merge order: ${pending_branches[*]}"
367
+
368
+ if [[ "${#overlap_files[@]}" -gt 0 ]]; then
369
+ echo "[agent-branch-merge] Overlapping changed files detected across requested branches:"
370
+ for overlap_file in "${overlap_files[@]}"; do
371
+ branches_csv="${file_to_branches[$overlap_file]}"
372
+ branches_display="$(printf '%s' "$branches_csv" | sed 's/,/, /g')"
373
+ echo " - ${overlap_file} <- ${branches_display}"
374
+ done
375
+ else
376
+ echo "[agent-branch-merge] No overlapping changed files detected across requested branches."
377
+ fi
378
+
379
+ for index in "${!pending_branches[@]}"; do
380
+ source_branch="${pending_branches[$index]}"
381
+ echo "[agent-branch-merge] Merging '${source_branch}' into '${TARGET_BRANCH}'..."
382
+ if git -C "$target_worktree" merge --no-ff --no-edit "$source_branch"; then
383
+ echo "[agent-branch-merge] Merged '${source_branch}'."
384
+ continue
385
+ fi
386
+
387
+ conflict_files="$(git -C "$target_worktree" diff --name-only --diff-filter=U || true)"
388
+ echo "[agent-branch-merge] Merge conflict detected while merging '${source_branch}' into '${TARGET_BRANCH}'." >&2
389
+ echo "[agent-branch-merge] Target worktree: ${target_worktree}" >&2
390
+ if [[ -n "$conflict_files" ]]; then
391
+ echo "[agent-branch-merge] Conflicting files:" >&2
392
+ while IFS= read -r conflict_file; do
393
+ [[ -n "$conflict_file" ]] && echo " - ${conflict_file}" >&2
394
+ done <<< "$conflict_files"
395
+ fi
396
+ echo "[agent-branch-merge] Resolve or abort inside the integration worktree:" >&2
397
+ echo " cd \"${target_worktree}\"" >&2
398
+ echo " git status" >&2
399
+ echo " git add <resolved-files> && git commit" >&2
400
+ echo " # or: git merge --abort" >&2
401
+
402
+ remaining_branches=("${pending_branches[@]:$((index + 1))}")
403
+ if [[ "${#remaining_branches[@]}" -gt 0 ]]; then
404
+ echo "[agent-branch-merge] Remaining branches:" >&2
405
+ for remaining in "${remaining_branches[@]}"; do
406
+ echo " - ${remaining}" >&2
407
+ done
408
+ resume_cmd="gx merge --into ${TARGET_BRANCH} --base ${BASE_BRANCH}"
409
+ for remaining in "${remaining_branches[@]}"; do
410
+ resume_cmd="${resume_cmd} --branch ${remaining}"
411
+ done
412
+ echo "[agent-branch-merge] Resume after resolving with: ${resume_cmd}" >&2
413
+ fi
414
+ exit 1
415
+ done
416
+
417
+ echo "[agent-branch-merge] Merge sequence complete for '${TARGET_BRANCH}'."
418
+ if [[ "$target_created" -eq 1 ]]; then
419
+ echo "[agent-branch-merge] Review and verify in '${target_worktree}', then finish the integration branch when ready."
420
+ fi
421
+ echo "[agent-branch-merge] Next step: bash scripts/agent-branch-finish.sh --branch \"${TARGET_BRANCH}\" --base \"${BASE_BRANCH}\" --via-pr --wait-for-merge --cleanup"