@imdeadpool/guardex 7.0.41 → 7.0.43
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 +68 -13
- package/package.json +2 -1
- package/skills/gitguardex/SKILL.md +13 -0
- package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
- package/src/agents/cleanup-sessions.js +126 -0
- package/src/agents/detect.js +160 -0
- package/src/agents/finish.js +172 -0
- package/src/agents/inspect.js +189 -0
- package/src/agents/launch.js +240 -0
- package/src/agents/registry.js +133 -0
- package/src/agents/selection-panel.js +571 -0
- package/src/agents/sessions.js +151 -0
- package/src/agents/start.js +591 -0
- package/src/agents/status.js +143 -0
- package/src/agents/terminal.js +152 -0
- package/src/budget/index.js +343 -0
- package/src/ci-init/index.js +265 -0
- package/src/cli/args.js +305 -1
- package/src/cli/main.js +262 -132
- package/src/cockpit/action-runner.js +3 -0
- package/src/cockpit/actions.js +80 -0
- package/src/cockpit/control.js +1121 -0
- package/src/cockpit/index.js +426 -0
- package/src/cockpit/keybindings.js +224 -0
- package/src/cockpit/kitty-layout.js +549 -0
- package/src/cockpit/kitty-tree.js +144 -0
- package/src/cockpit/layout.js +224 -0
- package/src/cockpit/logs-reader.js +182 -0
- package/src/cockpit/menu.js +204 -0
- package/src/cockpit/pane-actions.js +597 -0
- package/src/cockpit/pane-menu.js +387 -0
- package/src/cockpit/projects-finder.js +178 -0
- package/src/cockpit/render.js +215 -0
- package/src/cockpit/settings-render.js +128 -0
- package/src/cockpit/settings.js +124 -0
- package/src/cockpit/shortcuts.js +24 -0
- package/src/cockpit/sidebar.js +311 -0
- package/src/cockpit/state.js +72 -0
- package/src/cockpit/theme.js +128 -0
- package/src/cockpit/welcome.js +266 -0
- package/src/context.js +76 -33
- package/src/doctor/index.js +3 -2
- package/src/finish/index.js +39 -2
- package/src/git/index.js +65 -0
- package/src/kitty/command.js +101 -0
- package/src/kitty/runtime.js +250 -0
- package/src/output/index.js +1 -1
- package/src/pr-review.js +241 -0
- package/src/scaffold/index.js +19 -0
- package/src/submodule/index.js +288 -0
- package/src/terminal/index.js +120 -0
- package/src/terminal/kitty.js +622 -0
- package/src/terminal/tmux.js +126 -0
- package/src/tmux/command.js +27 -0
- package/src/tmux/session.js +89 -0
- package/templates/AGENTS.multiagent-safety.md +27 -1
- package/templates/codex/skills/gitguardex/SKILL.md +2 -0
- package/templates/githooks/pre-commit +22 -1
- package/templates/github/workflows/README.md +87 -0
- package/templates/github/workflows/ci-full.yml +55 -0
- package/templates/github/workflows/ci.yml +56 -0
- package/templates/github/workflows/cr.yml +20 -1
- package/templates/scripts/agent-branch-finish.sh +544 -26
- package/templates/scripts/agent-branch-start.sh +89 -22
- package/templates/scripts/agent-preflight.sh +89 -0
- package/templates/scripts/agent-worktree-prune.sh +96 -5
- package/templates/scripts/codex-agent.sh +41 -6
- package/templates/scripts/openspec/init-plan-workspace.sh +43 -0
- package/templates/scripts/review-bot-watch.sh +31 -2
- package/templates/scripts/agent-session-state.js +0 -171
- package/templates/scripts/install-vscode-active-agents-extension.js +0 -135
- package/templates/vscode/guardex-active-agents/README.md +0 -34
- package/templates/vscode/guardex-active-agents/extension.js +0 -3782
- package/templates/vscode/guardex-active-agents/fileicons/gitguardex-fileicons.json +0 -54
- package/templates/vscode/guardex-active-agents/fileicons/icons/agent.svg +0 -5
- package/templates/vscode/guardex-active-agents/fileicons/icons/branch.svg +0 -7
- package/templates/vscode/guardex-active-agents/fileicons/icons/config.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/hook.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/openspec.svg +0 -5
- package/templates/vscode/guardex-active-agents/fileicons/icons/plan.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/spec.svg +0 -5
- package/templates/vscode/guardex-active-agents/icon.png +0 -0
- package/templates/vscode/guardex-active-agents/media/active-agents-hivemind.svg +0 -14
- package/templates/vscode/guardex-active-agents/package.json +0 -169
- package/templates/vscode/guardex-active-agents/session-schema.js +0 -1348
|
@@ -11,11 +11,22 @@ MERGE_MODE="auto"
|
|
|
11
11
|
GH_BIN="${GUARDEX_GH_BIN:-gh}"
|
|
12
12
|
NODE_BIN="${GUARDEX_NODE_BIN:-node}"
|
|
13
13
|
CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}"
|
|
14
|
-
CLEANUP_AFTER_MERGE_RAW="${GUARDEX_FINISH_CLEANUP:-
|
|
15
|
-
WAIT_FOR_MERGE_RAW="${GUARDEX_FINISH_WAIT_FOR_MERGE:-
|
|
14
|
+
CLEANUP_AFTER_MERGE_RAW="${GUARDEX_FINISH_CLEANUP:-true}"
|
|
15
|
+
WAIT_FOR_MERGE_RAW="${GUARDEX_FINISH_WAIT_FOR_MERGE:-true}"
|
|
16
16
|
WAIT_TIMEOUT_SECONDS_RAW="${GUARDEX_FINISH_WAIT_TIMEOUT_SECONDS:-1800}"
|
|
17
17
|
WAIT_POLL_SECONDS_RAW="${GUARDEX_FINISH_WAIT_POLL_SECONDS:-10}"
|
|
18
18
|
PARENT_GITLINK_AUTO_COMMIT_RAW="${GUARDEX_FINISH_PARENT_GITLINK_AUTO_COMMIT:-true}"
|
|
19
|
+
AUTO_RESOLVE_MODE_RAW="${GUARDEX_FINISH_AUTO_RESOLVE:-none}"
|
|
20
|
+
AUTO_RESOLVE_SAFE_GLOBS_DEFAULT='.omc/**:.omx/state/**:.dev-ports.json:apps/logs/**:.agents/settings.local.json:.codex/state/**:.claude/state/**'
|
|
21
|
+
AUTO_RESOLVE_SAFE_GLOBS_RAW="${GUARDEX_FINISH_AUTO_RESOLVE_SAFE_GLOBS-$AUTO_RESOLVE_SAFE_GLOBS_DEFAULT}"
|
|
22
|
+
PREFLIGHT_ENABLED_RAW="${GUARDEX_FINISH_PREFLIGHT:-true}"
|
|
23
|
+
PREFLIGHT_SCRIPT_RAW="${GUARDEX_FINISH_PREFLIGHT_SCRIPT:-scripts/agent-preflight.sh}"
|
|
24
|
+
AUTO_PROMOTE_DRAFT_RAW="${GUARDEX_FINISH_AUTO_PROMOTE:-true}"
|
|
25
|
+
# --skip-checks (or GUARDEX_FINISH_SKIP_CHECKS=1): append `--admin` to every
|
|
26
|
+
# `gh pr merge` invocation, bypassing required status checks. Requires admin
|
|
27
|
+
# permission on the target repo. Use when CI is wedged (e.g. runner billing,
|
|
28
|
+
# infrastructure outage) and the operator has accepted the risk.
|
|
29
|
+
SKIP_CHECKS_RAW="${GUARDEX_FINISH_SKIP_CHECKS:-false}"
|
|
19
30
|
|
|
20
31
|
run_guardex_cli() {
|
|
21
32
|
if [[ -n "$CLI_ENTRY" ]]; then
|
|
@@ -64,11 +75,84 @@ normalize_int() {
|
|
|
64
75
|
printf '%s' "$value"
|
|
65
76
|
}
|
|
66
77
|
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
# Resolve the pre-flight script path against the source worktree. The
|
|
79
|
+
# caller passes either the configured path (which may be relative) or
|
|
80
|
+
# an empty string; we return the absolute path if it exists and is
|
|
81
|
+
# executable, otherwise return empty.
|
|
82
|
+
resolve_preflight_script() {
|
|
83
|
+
local worktree="$1"
|
|
84
|
+
local configured="$2"
|
|
85
|
+
if [[ -z "$configured" ]]; then
|
|
86
|
+
configured="scripts/agent-preflight.sh"
|
|
87
|
+
fi
|
|
88
|
+
if [[ "$configured" = /* ]]; then
|
|
89
|
+
if [[ -x "$configured" ]]; then
|
|
90
|
+
printf '%s' "$configured"
|
|
91
|
+
fi
|
|
92
|
+
return 0
|
|
93
|
+
fi
|
|
94
|
+
local candidate="${worktree}/${configured}"
|
|
95
|
+
if [[ -x "$candidate" ]]; then
|
|
96
|
+
printf '%s' "$candidate"
|
|
97
|
+
fi
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# Run the pre-flight verification gate in the agent worktree before
|
|
101
|
+
# any push happens. Returns 0 on success or when no gate is
|
|
102
|
+
# configured; returns non-zero (and prints a hint) on failure, which
|
|
103
|
+
# the caller propagates so the push is refused.
|
|
104
|
+
run_preflight() {
|
|
105
|
+
local worktree="$1"
|
|
106
|
+
if [[ "$PREFLIGHT_ENABLED" -ne 1 ]]; then
|
|
107
|
+
return 0
|
|
108
|
+
fi
|
|
109
|
+
local script_path
|
|
110
|
+
script_path="$(resolve_preflight_script "$worktree" "$PREFLIGHT_SCRIPT_RAW")"
|
|
111
|
+
if [[ -z "$script_path" ]]; then
|
|
112
|
+
echo "[agent-branch-finish] No executable pre-flight script at ${PREFLIGHT_SCRIPT_RAW} (in ${worktree}); skipping pre-flight." >&2
|
|
113
|
+
return 0
|
|
114
|
+
fi
|
|
115
|
+
echo "[agent-branch-finish] Running pre-flight: ${script_path}" >&2
|
|
116
|
+
if ( cd "$worktree" && "$script_path" ); then
|
|
117
|
+
echo "[agent-branch-finish] Pre-flight passed." >&2
|
|
118
|
+
return 0
|
|
119
|
+
fi
|
|
120
|
+
echo "[agent-branch-finish] Pre-flight FAILED; refusing push. Override with --no-preflight if you really mean it." >&2
|
|
121
|
+
return 1
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# After a PR exists, if it is in draft and auto-promote is enabled,
|
|
125
|
+
# mark it ready-for-review. With the budget-friendly CI defaults
|
|
126
|
+
# (draft PRs skip CI), this is the moment when CI is allowed to fire.
|
|
127
|
+
maybe_auto_promote_pr() {
|
|
128
|
+
local pr_url="$1"
|
|
129
|
+
if [[ -z "$pr_url" ]] || [[ "$AUTO_PROMOTE_DRAFT" -ne 1 ]]; then
|
|
130
|
+
return 0
|
|
131
|
+
fi
|
|
132
|
+
if ! command -v "$GH_BIN" >/dev/null 2>&1; then
|
|
133
|
+
return 0
|
|
134
|
+
fi
|
|
135
|
+
local is_draft
|
|
136
|
+
is_draft="$("$GH_BIN" pr view "$pr_url" --json isDraft --jq '.isDraft' 2>/dev/null || true)"
|
|
137
|
+
if [[ "$is_draft" != "true" ]]; then
|
|
138
|
+
return 0
|
|
139
|
+
fi
|
|
140
|
+
echo "[agent-branch-finish] PR is draft; promoting to ready-for-review (pre-flight passed)." >&2
|
|
141
|
+
if "$GH_BIN" pr ready "$pr_url" >/dev/null 2>&1; then
|
|
142
|
+
echo "[agent-branch-finish] PR marked ready-for-review." >&2
|
|
143
|
+
else
|
|
144
|
+
echo "[agent-branch-finish] gh pr ready failed; PR left in draft. Promote manually if intended." >&2
|
|
145
|
+
fi
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
CLEANUP_AFTER_MERGE="$(normalize_bool "$CLEANUP_AFTER_MERGE_RAW" "1")"
|
|
149
|
+
WAIT_FOR_MERGE="$(normalize_bool "$WAIT_FOR_MERGE_RAW" "1")"
|
|
69
150
|
WAIT_TIMEOUT_SECONDS="$(normalize_int "$WAIT_TIMEOUT_SECONDS_RAW" "1800" "30")"
|
|
70
151
|
WAIT_POLL_SECONDS="$(normalize_int "$WAIT_POLL_SECONDS_RAW" "10" "0")"
|
|
71
152
|
PARENT_GITLINK_AUTO_COMMIT="$(normalize_bool "$PARENT_GITLINK_AUTO_COMMIT_RAW" "1")"
|
|
153
|
+
PREFLIGHT_ENABLED="$(normalize_bool "$PREFLIGHT_ENABLED_RAW" "1")"
|
|
154
|
+
AUTO_PROMOTE_DRAFT="$(normalize_bool "$AUTO_PROMOTE_DRAFT_RAW" "1")"
|
|
155
|
+
SKIP_CHECKS="$(normalize_bool "$SKIP_CHECKS_RAW" "0")"
|
|
72
156
|
|
|
73
157
|
while [[ $# -gt 0 ]]; do
|
|
74
158
|
case "$1" in
|
|
@@ -139,9 +223,54 @@ while [[ $# -gt 0 ]]; do
|
|
|
139
223
|
MERGE_MODE="direct"
|
|
140
224
|
shift
|
|
141
225
|
;;
|
|
226
|
+
--auto-resolve)
|
|
227
|
+
if [[ "${2:-}" =~ ^(none|safe|full)$ ]]; then
|
|
228
|
+
AUTO_RESOLVE_MODE_RAW="$2"
|
|
229
|
+
shift 2
|
|
230
|
+
else
|
|
231
|
+
AUTO_RESOLVE_MODE_RAW="safe"
|
|
232
|
+
shift
|
|
233
|
+
fi
|
|
234
|
+
;;
|
|
235
|
+
--auto-resolve=*)
|
|
236
|
+
AUTO_RESOLVE_MODE_RAW="${1#--auto-resolve=}"
|
|
237
|
+
shift
|
|
238
|
+
;;
|
|
239
|
+
--no-auto-resolve)
|
|
240
|
+
AUTO_RESOLVE_MODE_RAW="none"
|
|
241
|
+
shift
|
|
242
|
+
;;
|
|
243
|
+
--no-preflight)
|
|
244
|
+
PREFLIGHT_ENABLED_RAW="false"
|
|
245
|
+
shift
|
|
246
|
+
;;
|
|
247
|
+
--preflight)
|
|
248
|
+
PREFLIGHT_ENABLED_RAW="true"
|
|
249
|
+
shift
|
|
250
|
+
;;
|
|
251
|
+
--preflight-script)
|
|
252
|
+
PREFLIGHT_SCRIPT_RAW="${2:-}"
|
|
253
|
+
shift 2
|
|
254
|
+
;;
|
|
255
|
+
--skip-checks)
|
|
256
|
+
SKIP_CHECKS=1
|
|
257
|
+
shift
|
|
258
|
+
;;
|
|
259
|
+
--no-skip-checks)
|
|
260
|
+
SKIP_CHECKS=0
|
|
261
|
+
shift
|
|
262
|
+
;;
|
|
263
|
+
--no-auto-promote)
|
|
264
|
+
AUTO_PROMOTE_DRAFT_RAW="false"
|
|
265
|
+
shift
|
|
266
|
+
;;
|
|
267
|
+
--auto-promote)
|
|
268
|
+
AUTO_PROMOTE_DRAFT_RAW="true"
|
|
269
|
+
shift
|
|
270
|
+
;;
|
|
142
271
|
*)
|
|
143
272
|
echo "[agent-branch-finish] Unknown argument: $1" >&2
|
|
144
|
-
echo "Usage: $0 [--base <branch>] [--branch <branch>] [--no-push] [--cleanup|--no-cleanup] [--wait-for-merge|--no-wait-for-merge] [--wait-timeout-seconds <n>] [--wait-poll-seconds <n>] [--parent-gitlink-commit|--no-parent-gitlink-commit] [--keep-remote-branch|--delete-remote-branch] [--mode auto|direct|pr|--via-pr|--direct-only]" >&2
|
|
273
|
+
echo "Usage: $0 [--base <branch>] [--branch <branch>] [--no-push] [--cleanup|--no-cleanup] [--wait-for-merge|--no-wait-for-merge] [--wait-timeout-seconds <n>] [--wait-poll-seconds <n>] [--parent-gitlink-commit|--no-parent-gitlink-commit] [--keep-remote-branch|--delete-remote-branch] [--mode auto|direct|pr|--via-pr|--direct-only] [--auto-resolve[=none|safe|full]|--no-auto-resolve] [--no-preflight|--preflight] [--preflight-script <path>] [--no-auto-promote|--auto-promote]" >&2
|
|
145
274
|
exit 1
|
|
146
275
|
;;
|
|
147
276
|
esac
|
|
@@ -159,12 +288,162 @@ case "$MERGE_MODE" in
|
|
|
159
288
|
;;
|
|
160
289
|
esac
|
|
161
290
|
|
|
291
|
+
AUTO_RESOLVE_MODE="$(printf '%s' "$AUTO_RESOLVE_MODE_RAW" | tr '[:upper:]' '[:lower:]')"
|
|
292
|
+
case "$AUTO_RESOLVE_MODE" in
|
|
293
|
+
none|safe|full) ;;
|
|
294
|
+
*)
|
|
295
|
+
echo "[agent-branch-finish] Invalid --auto-resolve value: ${AUTO_RESOLVE_MODE_RAW} (expected none|safe|full)" >&2
|
|
296
|
+
exit 1
|
|
297
|
+
;;
|
|
298
|
+
esac
|
|
299
|
+
|
|
300
|
+
path_matches_auto_resolve_safe_glob() {
|
|
301
|
+
local path="$1"
|
|
302
|
+
if [[ -z "${AUTO_RESOLVE_SAFE_GLOBS_RAW:-}" ]]; then
|
|
303
|
+
return 1
|
|
304
|
+
fi
|
|
305
|
+
local -a globs=()
|
|
306
|
+
IFS=':' read -ra globs <<< "$AUTO_RESOLVE_SAFE_GLOBS_RAW"
|
|
307
|
+
local pattern rewritten
|
|
308
|
+
for pattern in "${globs[@]}"; do
|
|
309
|
+
[[ -z "$pattern" ]] && continue
|
|
310
|
+
rewritten="${pattern%/**}"
|
|
311
|
+
if [[ "$rewritten" != "$pattern" ]]; then
|
|
312
|
+
if [[ "$path" == "$rewritten"/* ]]; then
|
|
313
|
+
return 0
|
|
314
|
+
fi
|
|
315
|
+
else
|
|
316
|
+
# shellcheck disable=SC2053
|
|
317
|
+
if [[ "$path" == $pattern ]]; then
|
|
318
|
+
return 0
|
|
319
|
+
fi
|
|
320
|
+
fi
|
|
321
|
+
done
|
|
322
|
+
return 1
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
# Resolve a conflicting submodule pointer if and only if one side is a strict
|
|
326
|
+
# ancestor of the other (fast-forward direction). Writes the resolved SHA via
|
|
327
|
+
# git update-index and prints the chosen SHA on stdout. Returns 0 on success,
|
|
328
|
+
# 1 on uninitialized/divergent/unreachable cases.
|
|
329
|
+
try_resolve_submodule_pointer_conflict() {
|
|
330
|
+
local repo_root_arg="$1"
|
|
331
|
+
local source_worktree_arg="$2"
|
|
332
|
+
local conflict_path="$3"
|
|
333
|
+
|
|
334
|
+
# Confirm registered submodule path.
|
|
335
|
+
if [[ ! -f "$repo_root_arg/.gitmodules" ]]; then
|
|
336
|
+
return 1
|
|
337
|
+
fi
|
|
338
|
+
if ! git -C "$repo_root_arg" config -f .gitmodules --get-regexp '^submodule\..*\.path$' 2>/dev/null \
|
|
339
|
+
| awk '{print $2}' | grep -Fxq -- "$conflict_path"; then
|
|
340
|
+
return 1
|
|
341
|
+
fi
|
|
342
|
+
|
|
343
|
+
# Read the three stages from the index.
|
|
344
|
+
local stage_out
|
|
345
|
+
stage_out="$(git -C "$source_worktree_arg" ls-files -u -- "$conflict_path" 2>/dev/null || true)"
|
|
346
|
+
if [[ -z "$stage_out" ]]; then
|
|
347
|
+
return 1
|
|
348
|
+
fi
|
|
349
|
+
|
|
350
|
+
local base_sha="" ours_sha="" theirs_sha=""
|
|
351
|
+
local mode_field stage_sha stage_num path_field
|
|
352
|
+
while IFS=$'\t' read -r meta path_field; do
|
|
353
|
+
[[ -z "$meta" || -z "$path_field" ]] && continue
|
|
354
|
+
# meta format: "<mode> <sha> <stage>"
|
|
355
|
+
read -r mode_field stage_sha stage_num <<< "$meta"
|
|
356
|
+
[[ "$mode_field" != "160000" ]] && return 1
|
|
357
|
+
case "$stage_num" in
|
|
358
|
+
1) base_sha="$stage_sha" ;;
|
|
359
|
+
2) ours_sha="$stage_sha" ;;
|
|
360
|
+
3) theirs_sha="$stage_sha" ;;
|
|
361
|
+
esac
|
|
362
|
+
done <<< "$stage_out"
|
|
363
|
+
|
|
364
|
+
if [[ -z "$ours_sha" || -z "$theirs_sha" ]]; then
|
|
365
|
+
return 1
|
|
366
|
+
fi
|
|
367
|
+
|
|
368
|
+
# Pick a working clone for the submodule. Three sources, in order:
|
|
369
|
+
# 1) checked-out submodule worktree (cheap, no network)
|
|
370
|
+
# 2) cached internal clone at .git/modules/<name>
|
|
371
|
+
# 3) temp bare clone from the submodule URL (last resort; needs network)
|
|
372
|
+
local sub_query_dir=""
|
|
373
|
+
local sub_dir="$source_worktree_arg/$conflict_path"
|
|
374
|
+
if [[ -d "$sub_dir" ]] && git -C "$sub_dir" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
375
|
+
sub_query_dir="$sub_dir"
|
|
376
|
+
else
|
|
377
|
+
local repo_git_dir
|
|
378
|
+
repo_git_dir="$(git -C "$source_worktree_arg" rev-parse --git-common-dir 2>/dev/null || true)"
|
|
379
|
+
if [[ -n "$repo_git_dir" && -d "$repo_git_dir/modules/$conflict_path" ]]; then
|
|
380
|
+
sub_query_dir="$repo_git_dir/modules/$conflict_path"
|
|
381
|
+
fi
|
|
382
|
+
fi
|
|
383
|
+
|
|
384
|
+
local temp_sub_clone=""
|
|
385
|
+
cleanup_temp_sub_clone() {
|
|
386
|
+
[[ -n "$temp_sub_clone" && -d "$temp_sub_clone" ]] && rm -rf "$temp_sub_clone"
|
|
387
|
+
}
|
|
388
|
+
trap cleanup_temp_sub_clone RETURN
|
|
389
|
+
|
|
390
|
+
if [[ -z "$sub_query_dir" ]]; then
|
|
391
|
+
local sub_url
|
|
392
|
+
sub_url="$(git -C "$repo_root_arg" config -f .gitmodules --get "submodule.${conflict_path}.url" 2>/dev/null || true)"
|
|
393
|
+
if [[ -z "$sub_url" ]]; then
|
|
394
|
+
return 1
|
|
395
|
+
fi
|
|
396
|
+
temp_sub_clone="$(mktemp -d -t gx-submod-resolve-XXXXXX 2>/dev/null || true)"
|
|
397
|
+
if [[ -z "$temp_sub_clone" || ! -d "$temp_sub_clone" ]]; then
|
|
398
|
+
return 1
|
|
399
|
+
fi
|
|
400
|
+
if ! git clone --quiet --bare "$sub_url" "$temp_sub_clone" >/dev/null 2>&1; then
|
|
401
|
+
return 1
|
|
402
|
+
fi
|
|
403
|
+
sub_query_dir="$temp_sub_clone"
|
|
404
|
+
fi
|
|
405
|
+
|
|
406
|
+
if ! git -C "$sub_query_dir" cat-file -e "${ours_sha}^{commit}" 2>/dev/null \
|
|
407
|
+
|| ! git -C "$sub_query_dir" cat-file -e "${theirs_sha}^{commit}" 2>/dev/null; then
|
|
408
|
+
git -C "$sub_query_dir" fetch --quiet --all 2>/dev/null || true
|
|
409
|
+
fi
|
|
410
|
+
if ! git -C "$sub_query_dir" cat-file -e "${ours_sha}^{commit}" 2>/dev/null \
|
|
411
|
+
|| ! git -C "$sub_query_dir" cat-file -e "${theirs_sha}^{commit}" 2>/dev/null; then
|
|
412
|
+
return 1
|
|
413
|
+
fi
|
|
414
|
+
|
|
415
|
+
local chosen_sha=""
|
|
416
|
+
if [[ "$ours_sha" == "$theirs_sha" ]]; then
|
|
417
|
+
chosen_sha="$ours_sha"
|
|
418
|
+
elif git -C "$sub_query_dir" merge-base --is-ancestor "$ours_sha" "$theirs_sha" 2>/dev/null; then
|
|
419
|
+
chosen_sha="$theirs_sha"
|
|
420
|
+
elif git -C "$sub_query_dir" merge-base --is-ancestor "$theirs_sha" "$ours_sha" 2>/dev/null; then
|
|
421
|
+
chosen_sha="$ours_sha"
|
|
422
|
+
else
|
|
423
|
+
# Divergent histories; refuse.
|
|
424
|
+
return 1
|
|
425
|
+
fi
|
|
426
|
+
|
|
427
|
+
if ! git -C "$source_worktree_arg" update-index --cacheinfo "160000,${chosen_sha},${conflict_path}" >/dev/null 2>&1; then
|
|
428
|
+
return 1
|
|
429
|
+
fi
|
|
430
|
+
|
|
431
|
+
printf '%s' "$chosen_sha"
|
|
432
|
+
return 0
|
|
433
|
+
}
|
|
434
|
+
|
|
162
435
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
163
436
|
echo "[agent-branch-finish] Not inside a git repository." >&2
|
|
164
437
|
exit 1
|
|
165
438
|
fi
|
|
166
439
|
|
|
167
440
|
repo_root="$(git rev-parse --show-toplevel)"
|
|
441
|
+
finish_active_cwd="${GUARDEX_FINISH_ACTIVE_CWD:-$(pwd -P)}"
|
|
442
|
+
if [[ -d "$finish_active_cwd" ]]; then
|
|
443
|
+
finish_active_cwd="$(cd "$finish_active_cwd" && pwd -P)"
|
|
444
|
+
else
|
|
445
|
+
finish_active_cwd=""
|
|
446
|
+
fi
|
|
168
447
|
# The physical cwd may be a subdirectory inside the source worktree. Cleanup
|
|
169
448
|
# decisions need the enclosing worktree root, otherwise finishing from `src/`
|
|
170
449
|
# can delete the caller's cwd and turn a successful merge into a false shell
|
|
@@ -178,6 +457,36 @@ else
|
|
|
178
457
|
fi
|
|
179
458
|
repo_common_root="$(cd "$common_git_dir/.." && pwd -P)"
|
|
180
459
|
|
|
460
|
+
resolve_same_repo_worktree_for_cwd() {
|
|
461
|
+
local active_cwd="$1"
|
|
462
|
+
[[ -n "$active_cwd" && -d "$active_cwd" ]] || return 0
|
|
463
|
+
git -C "$active_cwd" rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 0
|
|
464
|
+
|
|
465
|
+
local active_worktree=""
|
|
466
|
+
active_worktree="$(git -C "$active_cwd" rev-parse --show-toplevel 2>/dev/null || true)"
|
|
467
|
+
[[ -n "$active_worktree" ]] || return 0
|
|
468
|
+
|
|
469
|
+
local active_common_raw=""
|
|
470
|
+
local active_common_dir=""
|
|
471
|
+
active_common_raw="$(git -C "$active_worktree" rev-parse --git-common-dir 2>/dev/null || true)"
|
|
472
|
+
[[ -n "$active_common_raw" ]] || return 0
|
|
473
|
+
if [[ "$active_common_raw" == /* ]]; then
|
|
474
|
+
active_common_dir="$active_common_raw"
|
|
475
|
+
else
|
|
476
|
+
active_common_dir="${active_worktree}/${active_common_raw}"
|
|
477
|
+
fi
|
|
478
|
+
active_common_dir="$(cd "$active_common_dir" 2>/dev/null && pwd -P)" || return 0
|
|
479
|
+
|
|
480
|
+
if [[ "$active_common_dir" == "$common_git_dir" ]]; then
|
|
481
|
+
cd "$active_worktree" 2>/dev/null && pwd -P
|
|
482
|
+
fi
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
active_cwd_worktree="$(resolve_same_repo_worktree_for_cwd "$finish_active_cwd")"
|
|
486
|
+
if [[ -n "$active_cwd_worktree" ]]; then
|
|
487
|
+
current_worktree="$active_cwd_worktree"
|
|
488
|
+
fi
|
|
489
|
+
|
|
181
490
|
if [[ -z "$SOURCE_BRANCH" ]]; then
|
|
182
491
|
SOURCE_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
|
183
492
|
fi
|
|
@@ -336,6 +645,22 @@ is_clean_worktree() {
|
|
|
336
645
|
&& git -C "$wt" diff --cached --quiet -- . ":(exclude).omx/state/agent-file-locks.json"
|
|
337
646
|
}
|
|
338
647
|
|
|
648
|
+
refresh_clean_base_worktree() {
|
|
649
|
+
local wt="$1"
|
|
650
|
+
[[ -z "$wt" || "$PUSH_ENABLED" -ne 1 ]] && return 0
|
|
651
|
+
|
|
652
|
+
if ! is_clean_worktree "$wt"; then
|
|
653
|
+
echo "[agent-branch-finish] Warning: local ${BASE_BRANCH} worktree is dirty; skipping 'git pull --ff-only origin ${BASE_BRANCH}' for ${wt}." >&2
|
|
654
|
+
return 0
|
|
655
|
+
fi
|
|
656
|
+
|
|
657
|
+
if GUARDEX_DISABLE_POST_MERGE_CLEANUP=1 GUARDEX_PRUNE_ACTIVE_CWD="$finish_active_cwd" git -C "$wt" pull --ff-only origin "$BASE_BRANCH" >/dev/null; then
|
|
658
|
+
echo "[agent-branch-finish] Refreshed local ${BASE_BRANCH} worktree with 'git pull --ff-only origin ${BASE_BRANCH}': ${wt}"
|
|
659
|
+
else
|
|
660
|
+
echo "[agent-branch-finish] Warning: failed to refresh local ${BASE_BRANCH} worktree with 'git pull --ff-only origin ${BASE_BRANCH}': ${wt}" >&2
|
|
661
|
+
fi
|
|
662
|
+
}
|
|
663
|
+
|
|
339
664
|
remove_stale_source_probe_worktrees "$SOURCE_BRANCH"
|
|
340
665
|
source_worktree="$(get_worktree_for_branch "$SOURCE_BRANCH")"
|
|
341
666
|
created_source_probe=0
|
|
@@ -346,6 +671,7 @@ merge_completed=0
|
|
|
346
671
|
merge_status="pr"
|
|
347
672
|
direct_push_error=""
|
|
348
673
|
pr_url=""
|
|
674
|
+
changed_submodule_push_done=0
|
|
349
675
|
|
|
350
676
|
cleanup() {
|
|
351
677
|
if [[ -n "$integration_worktree" && -d "$integration_worktree" ]]; then
|
|
@@ -432,20 +758,97 @@ if git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/${BASE_BRA
|
|
|
432
758
|
|
|
433
759
|
if ! git -C "$source_worktree" merge --no-commit --no-ff "origin/${BASE_BRANCH}" >/dev/null 2>&1; then
|
|
434
760
|
conflict_files="$(git -C "$source_worktree" diff --name-only --diff-filter=U || true)"
|
|
435
|
-
git -C "$source_worktree" merge --abort >/dev/null 2>&1 || true
|
|
436
761
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
762
|
+
if [[ "$AUTO_RESOLVE_MODE" != "none" && -n "$conflict_files" ]]; then
|
|
763
|
+
auto_resolve_unresolved=""
|
|
764
|
+
auto_resolve_resolved_state=""
|
|
765
|
+
auto_resolve_resolved_submodules=""
|
|
766
|
+
while IFS= read -r conflict_path; do
|
|
767
|
+
[[ -z "$conflict_path" ]] && continue
|
|
768
|
+
if path_matches_auto_resolve_safe_glob "$conflict_path"; then
|
|
769
|
+
if git -C "$source_worktree" checkout --theirs -- "$conflict_path" >/dev/null 2>&1 \
|
|
770
|
+
&& git -C "$source_worktree" add -- "$conflict_path" >/dev/null 2>&1; then
|
|
771
|
+
auto_resolve_resolved_state+="${conflict_path}"$'\n'
|
|
772
|
+
continue
|
|
773
|
+
fi
|
|
774
|
+
fi
|
|
775
|
+
if [[ "$AUTO_RESOLVE_MODE" == "full" ]]; then
|
|
776
|
+
if chosen_sha="$(try_resolve_submodule_pointer_conflict "$repo_root" "$source_worktree" "$conflict_path")"; then
|
|
777
|
+
auto_resolve_resolved_submodules+="${conflict_path}@${chosen_sha}"$'\n'
|
|
778
|
+
continue
|
|
779
|
+
fi
|
|
780
|
+
fi
|
|
781
|
+
auto_resolve_unresolved+="${conflict_path}"$'\n'
|
|
442
782
|
done <<< "$conflict_files"
|
|
783
|
+
|
|
784
|
+
if [[ -n "$auto_resolve_unresolved" ]]; then
|
|
785
|
+
git -C "$source_worktree" merge --abort >/dev/null 2>&1 || true
|
|
786
|
+
echo "[agent-branch-finish] --auto-resolve=${AUTO_RESOLVE_MODE}: some conflicts are outside the safe allowlist (or submodule histories diverge); aborting." >&2
|
|
787
|
+
echo "[agent-branch-finish] Unresolved conflicts:" >&2
|
|
788
|
+
while IFS= read -r unresolved_path; do
|
|
789
|
+
[[ -n "$unresolved_path" ]] && echo " - ${unresolved_path}" >&2
|
|
790
|
+
done <<< "$auto_resolve_unresolved"
|
|
791
|
+
echo "[agent-branch-finish] State-file allowlist (GUARDEX_FINISH_AUTO_RESOLVE_SAFE_GLOBS): ${AUTO_RESOLVE_SAFE_GLOBS_RAW}" >&2
|
|
792
|
+
if [[ "$AUTO_RESOLVE_MODE" != "full" ]]; then
|
|
793
|
+
echo "[agent-branch-finish] Submodule pointer auto-resolve requires --auto-resolve=full; not enabled for this run." >&2
|
|
794
|
+
fi
|
|
795
|
+
exit 1
|
|
796
|
+
fi
|
|
797
|
+
|
|
798
|
+
# Claim resolved paths so the pre-commit lock guard accepts the merge.
|
|
799
|
+
auto_resolve_claim_paths=()
|
|
800
|
+
while IFS= read -r resolved_path; do
|
|
801
|
+
[[ -n "$resolved_path" ]] && auto_resolve_claim_paths+=("$resolved_path")
|
|
802
|
+
done <<< "$auto_resolve_resolved_state"
|
|
803
|
+
while IFS= read -r resolved_entry; do
|
|
804
|
+
[[ -z "$resolved_entry" ]] && continue
|
|
805
|
+
auto_resolve_claim_paths+=("${resolved_entry%@*}")
|
|
806
|
+
done <<< "$auto_resolve_resolved_submodules"
|
|
807
|
+
if [[ "${#auto_resolve_claim_paths[@]}" -gt 0 ]]; then
|
|
808
|
+
run_guardex_cli locks claim --branch "$SOURCE_BRANCH" "${auto_resolve_claim_paths[@]}" >/dev/null 2>&1 || true
|
|
809
|
+
fi
|
|
810
|
+
|
|
811
|
+
auto_resolve_commit_msg="Merge origin/${BASE_BRANCH} into ${SOURCE_BRANCH} (gx --auto-resolve=${AUTO_RESOLVE_MODE}; state files -> base, submodule pointers fast-forwarded)"
|
|
812
|
+
if ! git -C "$source_worktree" commit -m "$auto_resolve_commit_msg" >/dev/null 2>&1; then
|
|
813
|
+
git -C "$source_worktree" merge --abort >/dev/null 2>&1 || true
|
|
814
|
+
echo "[agent-branch-finish] --auto-resolve=${AUTO_RESOLVE_MODE}: failed to commit resolved merge (pre-commit hook may have rejected it; verify file locks)." >&2
|
|
815
|
+
exit 1
|
|
816
|
+
fi
|
|
817
|
+
|
|
818
|
+
state_count=0
|
|
819
|
+
submod_count=0
|
|
820
|
+
[[ -n "$auto_resolve_resolved_state" ]] && state_count="$(printf '%s' "$auto_resolve_resolved_state" | grep -c '^[^[:space:]]')"
|
|
821
|
+
[[ -n "$auto_resolve_resolved_submodules" ]] && submod_count="$(printf '%s' "$auto_resolve_resolved_submodules" | grep -c '^[^[:space:]]')"
|
|
822
|
+
echo "[agent-branch-finish] --auto-resolve=${AUTO_RESOLVE_MODE}: resolved ${state_count} state-file conflict(s), ${submod_count} submodule pointer conflict(s)." >&2
|
|
823
|
+
if [[ -n "$auto_resolve_resolved_state" ]]; then
|
|
824
|
+
echo "[agent-branch-finish] State files (resolved to base):" >&2
|
|
825
|
+
while IFS= read -r resolved_path; do
|
|
826
|
+
[[ -n "$resolved_path" ]] && echo " - ${resolved_path}" >&2
|
|
827
|
+
done <<< "$auto_resolve_resolved_state"
|
|
828
|
+
fi
|
|
829
|
+
if [[ -n "$auto_resolve_resolved_submodules" ]]; then
|
|
830
|
+
echo "[agent-branch-finish] Submodule pointers (fast-forwarded):" >&2
|
|
831
|
+
while IFS= read -r resolved_entry; do
|
|
832
|
+
[[ -n "$resolved_entry" ]] && echo " - ${resolved_entry%@*} -> ${resolved_entry##*@}" >&2
|
|
833
|
+
done <<< "$auto_resolve_resolved_submodules"
|
|
834
|
+
fi
|
|
835
|
+
else
|
|
836
|
+
git -C "$source_worktree" merge --abort >/dev/null 2>&1 || true
|
|
837
|
+
|
|
838
|
+
echo "[agent-branch-finish] Preflight conflict detected between '${SOURCE_BRANCH}' and latest origin/${BASE_BRANCH}." >&2
|
|
839
|
+
if [[ -n "$conflict_files" ]]; then
|
|
840
|
+
echo "[agent-branch-finish] Conflicting files:" >&2
|
|
841
|
+
while IFS= read -r file; do
|
|
842
|
+
[[ -n "$file" ]] && echo " - ${file}" >&2
|
|
843
|
+
done <<< "$conflict_files"
|
|
844
|
+
fi
|
|
845
|
+
echo "[agent-branch-finish] Rebase/merge '${BASE_BRANCH}' into '${SOURCE_BRANCH}' and resolve conflicts before finishing." >&2
|
|
846
|
+
echo "[agent-branch-finish] Or rerun with --auto-resolve=safe (state files) or --auto-resolve=full (state files + fast-forward-able submodule pointers)." >&2
|
|
847
|
+
exit 1
|
|
443
848
|
fi
|
|
444
|
-
|
|
445
|
-
|
|
849
|
+
else
|
|
850
|
+
git -C "$source_worktree" merge --abort >/dev/null 2>&1 || true
|
|
446
851
|
fi
|
|
447
|
-
|
|
448
|
-
git -C "$source_worktree" merge --abort >/dev/null 2>&1 || true
|
|
449
852
|
fi
|
|
450
853
|
|
|
451
854
|
should_create_integration_helper=1
|
|
@@ -682,14 +1085,89 @@ maybe_auto_commit_parent_gitlink() {
|
|
|
682
1085
|
echo "[agent-branch-finish] Parent gitlink auto-committed '${subrepo_rel}' in ${super_root}."
|
|
683
1086
|
}
|
|
684
1087
|
|
|
1088
|
+
maybe_push_changed_submodule_branches() {
|
|
1089
|
+
local base_ref="${1:-}"
|
|
1090
|
+
local source_ref="${2:-}"
|
|
1091
|
+
local changed_path=""
|
|
1092
|
+
local gitlink_mode=""
|
|
1093
|
+
local gitlink_sha=""
|
|
1094
|
+
local submodule_dir=""
|
|
1095
|
+
local branch_name=""
|
|
1096
|
+
local candidate_branch=""
|
|
1097
|
+
local remote_name=""
|
|
1098
|
+
local push_output=""
|
|
1099
|
+
|
|
1100
|
+
if [[ "$PUSH_ENABLED" -ne 1 || "$changed_submodule_push_done" -eq 1 ]]; then
|
|
1101
|
+
return 0
|
|
1102
|
+
fi
|
|
1103
|
+
changed_submodule_push_done=1
|
|
1104
|
+
if [[ -z "$base_ref" || -z "$source_ref" ]]; then
|
|
1105
|
+
return 0
|
|
1106
|
+
fi
|
|
1107
|
+
|
|
1108
|
+
while IFS= read -r changed_path; do
|
|
1109
|
+
[[ -n "$changed_path" ]] || continue
|
|
1110
|
+
|
|
1111
|
+
gitlink_mode="$(git -C "$source_worktree" ls-tree "$source_ref" -- "$changed_path" | awk 'NR == 1 { print $1 }')"
|
|
1112
|
+
if [[ "$gitlink_mode" != "160000" ]]; then
|
|
1113
|
+
continue
|
|
1114
|
+
fi
|
|
1115
|
+
gitlink_sha="$(git -C "$source_worktree" ls-tree "$source_ref" -- "$changed_path" | awk 'NR == 1 { print $3 }')"
|
|
1116
|
+
if [[ -z "$gitlink_sha" ]]; then
|
|
1117
|
+
continue
|
|
1118
|
+
fi
|
|
1119
|
+
|
|
1120
|
+
submodule_dir="${source_worktree}/${changed_path}"
|
|
1121
|
+
if ! git -C "$submodule_dir" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
1122
|
+
echo "[agent-branch-finish] Warning: changed gitlink '${changed_path}' has no checked-out submodule at ${submodule_dir}; cannot auto-push submodule commit ${gitlink_sha}." >&2
|
|
1123
|
+
return 1
|
|
1124
|
+
fi
|
|
1125
|
+
if ! git -C "$submodule_dir" cat-file -e "${gitlink_sha}^{commit}" >/dev/null 2>&1; then
|
|
1126
|
+
echo "[agent-branch-finish] Warning: changed gitlink '${changed_path}' points at ${gitlink_sha}, but that commit is not present in ${submodule_dir}; cannot auto-push it." >&2
|
|
1127
|
+
return 1
|
|
1128
|
+
fi
|
|
1129
|
+
|
|
1130
|
+
branch_name="$(git -C "$submodule_dir" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
1131
|
+
if [[ -z "$branch_name" || "$branch_name" == "HEAD" ]] || ! git -C "$submodule_dir" merge-base --is-ancestor "$gitlink_sha" "$branch_name" >/dev/null 2>&1; then
|
|
1132
|
+
candidate_branch="$(git -C "$submodule_dir" for-each-ref --contains "$gitlink_sha" --format='%(refname:short)' refs/heads | head -n 1)"
|
|
1133
|
+
branch_name="$candidate_branch"
|
|
1134
|
+
fi
|
|
1135
|
+
if [[ -z "$branch_name" || "$branch_name" == "HEAD" ]]; then
|
|
1136
|
+
echo "[agent-branch-finish] Warning: changed gitlink '${changed_path}' points at ${gitlink_sha}, but no local submodule branch contains it; cannot choose a safe remote branch to push." >&2
|
|
1137
|
+
return 1
|
|
1138
|
+
fi
|
|
1139
|
+
|
|
1140
|
+
remote_name="$(git -C "$submodule_dir" config --get "branch.${branch_name}.remote" || true)"
|
|
1141
|
+
if [[ -z "$remote_name" ]]; then
|
|
1142
|
+
remote_name="origin"
|
|
1143
|
+
fi
|
|
1144
|
+
if ! git -C "$submodule_dir" remote get-url "$remote_name" >/dev/null 2>&1; then
|
|
1145
|
+
echo "[agent-branch-finish] Warning: changed gitlink '${changed_path}' branch '${branch_name}' has no usable remote '${remote_name}'; cannot auto-push submodule commit ${gitlink_sha}." >&2
|
|
1146
|
+
return 1
|
|
1147
|
+
fi
|
|
1148
|
+
|
|
1149
|
+
if push_output="$(git -C "$submodule_dir" push -u "$remote_name" "${branch_name}:${branch_name}" 2>&1)"; then
|
|
1150
|
+
echo "[agent-branch-finish] Pushed changed submodule '${changed_path}' branch '${branch_name}' to '${remote_name}' before parent finish."
|
|
1151
|
+
else
|
|
1152
|
+
echo "[agent-branch-finish] Changed submodule '${changed_path}' must be pushed before the parent branch can be finished." >&2
|
|
1153
|
+
[[ -n "$push_output" ]] && echo "$push_output" >&2
|
|
1154
|
+
return 1
|
|
1155
|
+
fi
|
|
1156
|
+
done < <(git -C "$source_worktree" diff --name-only "$base_ref" "$source_ref" -- 2>/dev/null || true)
|
|
1157
|
+
}
|
|
1158
|
+
|
|
685
1159
|
wait_for_pr_merge() {
|
|
686
1160
|
local deadline
|
|
687
1161
|
deadline=$(( $(date +%s) + WAIT_TIMEOUT_SECONDS ))
|
|
688
1162
|
local wait_notice_printed=0
|
|
689
1163
|
local merge_output=""
|
|
1164
|
+
local -a merge_flags=(--squash --delete-branch)
|
|
1165
|
+
if [[ "$SKIP_CHECKS" -eq 1 ]]; then
|
|
1166
|
+
merge_flags+=(--admin)
|
|
1167
|
+
fi
|
|
690
1168
|
|
|
691
1169
|
while true; do
|
|
692
|
-
if merge_output="$("$GH_BIN" pr merge "$SOURCE_BRANCH"
|
|
1170
|
+
if merge_output="$("$GH_BIN" pr merge "$SOURCE_BRANCH" "${merge_flags[@]}" 2>&1)"; then
|
|
693
1171
|
return 0
|
|
694
1172
|
fi
|
|
695
1173
|
if is_local_branch_delete_error "$merge_output"; then
|
|
@@ -752,6 +1230,7 @@ run_pr_flow() {
|
|
|
752
1230
|
return 0
|
|
753
1231
|
fi
|
|
754
1232
|
|
|
1233
|
+
maybe_push_changed_submodule_branches "$start_ref" "$SOURCE_BRANCH"
|
|
755
1234
|
git -C "$source_worktree" push -u origin "$SOURCE_BRANCH"
|
|
756
1235
|
|
|
757
1236
|
pr_title="$(git -C "$repo_root" log -1 --pretty=%s "$SOURCE_BRANCH" 2>/dev/null || true)"
|
|
@@ -760,16 +1239,45 @@ run_pr_flow() {
|
|
|
760
1239
|
fi
|
|
761
1240
|
pr_body="Automated by gx branch finish (PR flow)."
|
|
762
1241
|
|
|
763
|
-
"
|
|
1242
|
+
pr_create_output=""
|
|
1243
|
+
if pr_create_output="$("$GH_BIN" pr create \
|
|
764
1244
|
--base "$BASE_BRANCH" \
|
|
765
1245
|
--head "$SOURCE_BRANCH" \
|
|
766
1246
|
--title "$pr_title" \
|
|
767
|
-
--body "$pr_body"
|
|
1247
|
+
--body "$pr_body" 2>&1)"; then
|
|
1248
|
+
:
|
|
1249
|
+
else
|
|
1250
|
+
# Idempotent: a PR already opened for this head is fine — fall through
|
|
1251
|
+
# to `gh pr view` so we still capture the URL. Anything else is a real
|
|
1252
|
+
# failure and the user needs to see it.
|
|
1253
|
+
if ! grep -qiE 'already exists|a pull request for branch' <<<"$pr_create_output"; then
|
|
1254
|
+
echo "[agent-branch-finish] gh pr create failed:" >&2
|
|
1255
|
+
echo "${pr_create_output}" >&2
|
|
1256
|
+
fi
|
|
1257
|
+
fi
|
|
768
1258
|
|
|
769
1259
|
pr_url="$("$GH_BIN" pr view "$SOURCE_BRANCH" --json url --jq '.url' 2>/dev/null || true)"
|
|
770
1260
|
|
|
1261
|
+
if [[ -z "$pr_url" ]]; then
|
|
1262
|
+
echo "[agent-branch-finish] No PR found for '${SOURCE_BRANCH}' after gh pr create; cannot proceed with PR merge." >&2
|
|
1263
|
+
if [[ -n "$pr_create_output" ]]; then
|
|
1264
|
+
echo "[agent-branch-finish] Last gh pr create output:" >&2
|
|
1265
|
+
echo "${pr_create_output}" >&2
|
|
1266
|
+
fi
|
|
1267
|
+
return 1
|
|
1268
|
+
fi
|
|
1269
|
+
echo "[agent-branch-finish] PR URL: ${pr_url}" >&2
|
|
1270
|
+
|
|
1271
|
+
# Pre-flight already passed by the time we reach the PR; promote any
|
|
1272
|
+
# existing draft so the budget-friendly CI gate fires once.
|
|
1273
|
+
maybe_auto_promote_pr "$pr_url"
|
|
1274
|
+
|
|
771
1275
|
merge_output=""
|
|
772
|
-
|
|
1276
|
+
local -a merge_flags=(--squash --delete-branch)
|
|
1277
|
+
if [[ "$SKIP_CHECKS" -eq 1 ]]; then
|
|
1278
|
+
merge_flags+=(--admin)
|
|
1279
|
+
fi
|
|
1280
|
+
if merge_output="$("$GH_BIN" pr merge "$SOURCE_BRANCH" "${merge_flags[@]}" 2>&1)"; then
|
|
773
1281
|
return 0
|
|
774
1282
|
fi
|
|
775
1283
|
if is_local_branch_delete_error "$merge_output"; then
|
|
@@ -783,7 +1291,11 @@ run_pr_flow() {
|
|
|
783
1291
|
fi
|
|
784
1292
|
|
|
785
1293
|
auto_output=""
|
|
786
|
-
|
|
1294
|
+
local -a auto_flags=(--squash --delete-branch --auto)
|
|
1295
|
+
if [[ "$SKIP_CHECKS" -eq 1 ]]; then
|
|
1296
|
+
auto_flags+=(--admin)
|
|
1297
|
+
fi
|
|
1298
|
+
if auto_output="$("$GH_BIN" pr merge "$SOURCE_BRANCH" "${auto_flags[@]}" 2>&1)"; then
|
|
787
1299
|
echo "[agent-branch-finish] PR auto-merge enabled; waiting for required checks/reviews." >&2
|
|
788
1300
|
return 2
|
|
789
1301
|
fi
|
|
@@ -799,7 +1311,11 @@ run_pr_flow() {
|
|
|
799
1311
|
}
|
|
800
1312
|
|
|
801
1313
|
if [[ "$PUSH_ENABLED" -eq 1 ]]; then
|
|
1314
|
+
if ! run_preflight "$source_worktree"; then
|
|
1315
|
+
exit 1
|
|
1316
|
+
fi
|
|
802
1317
|
if [[ "$MERGE_MODE" != "pr" ]]; then
|
|
1318
|
+
maybe_push_changed_submodule_branches "$start_ref" "$SOURCE_BRANCH"
|
|
803
1319
|
if ! direct_push_output="$(git -C "$integration_worktree" push origin "HEAD:${BASE_BRANCH}" 2>&1)"; then
|
|
804
1320
|
direct_push_error="$direct_push_output"
|
|
805
1321
|
merge_completed=0
|
|
@@ -847,9 +1363,7 @@ fi
|
|
|
847
1363
|
run_guardex_cli locks release --branch "$SOURCE_BRANCH" >/dev/null 2>&1 || true
|
|
848
1364
|
|
|
849
1365
|
base_worktree="$(get_worktree_for_branch "$BASE_BRANCH")"
|
|
850
|
-
|
|
851
|
-
git -C "$base_worktree" pull --ff-only origin "$BASE_BRANCH" >/dev/null 2>&1 || true
|
|
852
|
-
fi
|
|
1366
|
+
refresh_clean_base_worktree "$base_worktree"
|
|
853
1367
|
maybe_auto_commit_parent_gitlink "$base_worktree"
|
|
854
1368
|
|
|
855
1369
|
# Pivot out of the agent worktree before prune calls that may remove it.
|
|
@@ -861,6 +1375,10 @@ pivot_to_repo_root_before_prune() {
|
|
|
861
1375
|
fi
|
|
862
1376
|
}
|
|
863
1377
|
|
|
1378
|
+
run_guardex_prune() {
|
|
1379
|
+
GUARDEX_PRUNE_ACTIVE_CWD="$finish_active_cwd" run_guardex_cli worktree prune "$@"
|
|
1380
|
+
}
|
|
1381
|
+
|
|
864
1382
|
if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
|
|
865
1383
|
if [[ "$source_worktree" == "$repo_root" ]]; then
|
|
866
1384
|
if is_clean_worktree "$source_worktree"; then
|
|
@@ -871,7 +1389,7 @@ if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
|
|
|
871
1389
|
git -C "$source_worktree" checkout --detach >/dev/null 2>&1 || true
|
|
872
1390
|
fi
|
|
873
1391
|
if [[ "$switched_to_base" -eq 1 && "$PUSH_ENABLED" -eq 1 ]] && git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/${BASE_BRANCH}"; then
|
|
874
|
-
|
|
1392
|
+
refresh_clean_base_worktree "$source_worktree"
|
|
875
1393
|
fi
|
|
876
1394
|
fi
|
|
877
1395
|
elif [[ "$source_worktree" == "$current_worktree" && "$source_worktree" == "${agent_worktree_root}"/* ]]; then
|
|
@@ -906,7 +1424,7 @@ if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
|
|
|
906
1424
|
fi
|
|
907
1425
|
|
|
908
1426
|
pivot_to_repo_root_before_prune
|
|
909
|
-
if !
|
|
1427
|
+
if ! run_guardex_prune "${prune_args[@]}"; then
|
|
910
1428
|
echo "[agent-branch-finish] Warning: automatic worktree prune failed." >&2
|
|
911
1429
|
echo "[agent-branch-finish] You can run manual cleanup: gx cleanup --base ${BASE_BRANCH}" >&2
|
|
912
1430
|
fi
|
|
@@ -920,7 +1438,7 @@ if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
|
|
|
920
1438
|
fi
|
|
921
1439
|
else
|
|
922
1440
|
pivot_to_repo_root_before_prune
|
|
923
|
-
if !
|
|
1441
|
+
if ! run_guardex_prune --base "$BASE_BRANCH"; then
|
|
924
1442
|
echo "[agent-branch-finish] Warning: temporary worktree prune failed." >&2
|
|
925
1443
|
fi
|
|
926
1444
|
|