@imdeadpool/guardex 7.0.41 → 7.1.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/README.md +94 -13
- package/package.json +3 -1
- package/skills/gitguardex/SKILL.md +13 -0
- package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
- package/skills/gx-act/SKILL.md +82 -0
- package/src/agents/cleanup-sessions.js +126 -0
- package/src/agents/finish.js +172 -0
- package/src/agents/inspect.js +202 -0
- package/src/agents/launch.js +249 -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 +146 -0
- package/src/agents/terminal.js +152 -0
- package/src/budget/index.js +344 -0
- package/src/ci-init/index.js +265 -0
- package/src/cli/args.js +357 -3
- package/src/cli/commands/agents.js +364 -0
- package/src/cli/commands/bootstrap.js +92 -0
- package/src/cli/commands/branch.js +127 -0
- package/src/cli/commands/claude.js +674 -0
- package/src/cli/commands/doctor.js +268 -0
- package/src/cli/commands/finish.js +26 -0
- package/src/cli/commands/mcp.js +122 -0
- package/src/cli/commands/misc.js +304 -0
- package/src/cli/commands/pr.js +439 -0
- package/src/cli/commands/prompt.js +92 -0
- package/src/cli/commands/release.js +305 -0
- package/src/cli/commands/report.js +244 -0
- package/src/cli/commands/review.js +32 -0
- package/src/cli/commands/setup.js +242 -0
- package/src/cli/commands/status.js +338 -0
- package/src/cli/commands/watch.js +234 -0
- package/src/cli/main.js +85 -3613
- package/src/cli/shared/repo-env.js +161 -0
- package/src/cli/shared/sandbox.js +417 -0
- package/src/cli/shared/scaffolding.js +535 -0
- package/src/cli/shared/toolchain-shims.js +420 -0
- 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/kitty-layout.js +549 -0
- package/src/cockpit/kitty-tree.js +144 -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 +304 -43
- package/src/core/runtime.js +6 -1
- package/src/doctor/index.js +45 -15
- package/src/finish/index.js +186 -7
- package/src/finish/preflight.js +177 -0
- package/src/finish/review-gate.js +182 -0
- package/src/git/index.js +511 -4
- package/src/hooks/index.js +0 -64
- package/src/kitty/command.js +101 -0
- package/src/kitty/runtime.js +250 -0
- package/src/mcp/collect.js +370 -0
- package/src/mcp/server.js +157 -0
- package/src/output/index.js +68 -2
- package/src/pr-review.js +264 -0
- package/src/pr.js +381 -0
- package/src/sandbox/index.js +13 -2
- package/src/scaffold/agent-worktree-prep.js +213 -0
- package/src/scaffold/index.js +127 -10
- package/src/speckit/index.js +226 -0
- package/src/submodule/index.js +288 -0
- package/src/terminal/index.js +45 -0
- package/src/terminal/kitty.js +622 -0
- package/src/terminal/tmux.js +125 -0
- package/src/tmux/command.js +27 -0
- package/src/tmux/session.js +89 -0
- package/src/toolchain/index.js +20 -0
- package/templates/AGENTS.monorepo-apps.md +26 -0
- package/templates/AGENTS.multiagent-safety.md +63 -323
- package/templates/AGENTS.multiagent-safety.min.md +11 -0
- package/templates/codex/skills/gitguardex/SKILL.md +2 -0
- package/templates/codex/skills/gx-act/SKILL.md +82 -0
- package/templates/githooks/pre-commit +44 -20
- 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 +519 -23
- package/templates/scripts/agent-branch-merge.sh +4 -1
- package/templates/scripts/agent-branch-start.sh +176 -24
- package/templates/scripts/agent-preflight.sh +115 -0
- package/templates/scripts/agent-worktree-prune.sh +96 -5
- package/templates/scripts/codex-agent.sh +41 -97
- 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,17 @@ 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/**:.codex/settings.local.json:.claude/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}"
|
|
19
25
|
|
|
20
26
|
run_guardex_cli() {
|
|
21
27
|
if [[ -n "$CLI_ENTRY" ]]; then
|
|
@@ -64,14 +70,90 @@ normalize_int() {
|
|
|
64
70
|
printf '%s' "$value"
|
|
65
71
|
}
|
|
66
72
|
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
# Resolve the pre-flight script path against the source worktree. The
|
|
74
|
+
# caller passes either the configured path (which may be relative) or
|
|
75
|
+
# an empty string; we return the absolute path if it exists and is
|
|
76
|
+
# executable, otherwise return empty.
|
|
77
|
+
resolve_preflight_script() {
|
|
78
|
+
local worktree="$1"
|
|
79
|
+
local configured="$2"
|
|
80
|
+
if [[ -z "$configured" ]]; then
|
|
81
|
+
configured="scripts/agent-preflight.sh"
|
|
82
|
+
fi
|
|
83
|
+
if [[ "$configured" = /* ]]; then
|
|
84
|
+
if [[ -x "$configured" ]]; then
|
|
85
|
+
printf '%s' "$configured"
|
|
86
|
+
fi
|
|
87
|
+
return 0
|
|
88
|
+
fi
|
|
89
|
+
local candidate="${worktree}/${configured}"
|
|
90
|
+
if [[ -x "$candidate" ]]; then
|
|
91
|
+
printf '%s' "$candidate"
|
|
92
|
+
fi
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Run the pre-flight verification gate in the agent worktree before
|
|
96
|
+
# any push happens. Returns 0 on success or when no gate is
|
|
97
|
+
# configured; returns non-zero (and prints a hint) on failure, which
|
|
98
|
+
# the caller propagates so the push is refused.
|
|
99
|
+
run_preflight() {
|
|
100
|
+
local worktree="$1"
|
|
101
|
+
if [[ "$PREFLIGHT_ENABLED" -ne 1 ]]; then
|
|
102
|
+
return 0
|
|
103
|
+
fi
|
|
104
|
+
local script_path
|
|
105
|
+
script_path="$(resolve_preflight_script "$worktree" "$PREFLIGHT_SCRIPT_RAW")"
|
|
106
|
+
if [[ -z "$script_path" ]]; then
|
|
107
|
+
echo "[agent-branch-finish] No executable pre-flight script at ${PREFLIGHT_SCRIPT_RAW} (in ${worktree}); skipping pre-flight." >&2
|
|
108
|
+
return 0
|
|
109
|
+
fi
|
|
110
|
+
echo "[agent-branch-finish] Running pre-flight: ${script_path}" >&2
|
|
111
|
+
if ( cd "$worktree" && "$script_path" ); then
|
|
112
|
+
echo "[agent-branch-finish] Pre-flight passed." >&2
|
|
113
|
+
return 0
|
|
114
|
+
fi
|
|
115
|
+
echo "[agent-branch-finish] Pre-flight FAILED; refusing push. Override with --no-preflight if you really mean it." >&2
|
|
116
|
+
return 1
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# After a PR exists, if it is in draft and auto-promote is enabled,
|
|
120
|
+
# mark it ready-for-review. With the budget-friendly CI defaults
|
|
121
|
+
# (draft PRs skip CI), this is the moment when CI is allowed to fire.
|
|
122
|
+
maybe_auto_promote_pr() {
|
|
123
|
+
local pr_url="$1"
|
|
124
|
+
if [[ -z "$pr_url" ]] || [[ "$AUTO_PROMOTE_DRAFT" -ne 1 ]]; then
|
|
125
|
+
return 0
|
|
126
|
+
fi
|
|
127
|
+
if ! command -v "$GH_BIN" >/dev/null 2>&1; then
|
|
128
|
+
return 0
|
|
129
|
+
fi
|
|
130
|
+
local is_draft
|
|
131
|
+
is_draft="$("$GH_BIN" pr view "$pr_url" --json isDraft --jq '.isDraft' 2>/dev/null || true)"
|
|
132
|
+
if [[ "$is_draft" != "true" ]]; then
|
|
133
|
+
return 0
|
|
134
|
+
fi
|
|
135
|
+
echo "[agent-branch-finish] PR is draft; promoting to ready-for-review (pre-flight passed)." >&2
|
|
136
|
+
if "$GH_BIN" pr ready "$pr_url" >/dev/null 2>&1; then
|
|
137
|
+
echo "[agent-branch-finish] PR marked ready-for-review." >&2
|
|
138
|
+
else
|
|
139
|
+
echo "[agent-branch-finish] gh pr ready failed; PR left in draft. Promote manually if intended." >&2
|
|
140
|
+
fi
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
CLEANUP_AFTER_MERGE="$(normalize_bool "$CLEANUP_AFTER_MERGE_RAW" "1")"
|
|
144
|
+
WAIT_FOR_MERGE="$(normalize_bool "$WAIT_FOR_MERGE_RAW" "1")"
|
|
69
145
|
WAIT_TIMEOUT_SECONDS="$(normalize_int "$WAIT_TIMEOUT_SECONDS_RAW" "1800" "30")"
|
|
70
146
|
WAIT_POLL_SECONDS="$(normalize_int "$WAIT_POLL_SECONDS_RAW" "10" "0")"
|
|
71
147
|
PARENT_GITLINK_AUTO_COMMIT="$(normalize_bool "$PARENT_GITLINK_AUTO_COMMIT_RAW" "1")"
|
|
148
|
+
PREFLIGHT_ENABLED="$(normalize_bool "$PREFLIGHT_ENABLED_RAW" "1")"
|
|
149
|
+
AUTO_PROMOTE_DRAFT="$(normalize_bool "$AUTO_PROMOTE_DRAFT_RAW" "1")"
|
|
72
150
|
|
|
73
151
|
while [[ $# -gt 0 ]]; do
|
|
74
152
|
case "$1" in
|
|
153
|
+
-h|--help)
|
|
154
|
+
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]"
|
|
155
|
+
exit 0
|
|
156
|
+
;;
|
|
75
157
|
--base)
|
|
76
158
|
BASE_BRANCH="${2:-}"
|
|
77
159
|
BASE_BRANCH_EXPLICIT=1
|
|
@@ -139,9 +221,46 @@ while [[ $# -gt 0 ]]; do
|
|
|
139
221
|
MERGE_MODE="direct"
|
|
140
222
|
shift
|
|
141
223
|
;;
|
|
224
|
+
--auto-resolve)
|
|
225
|
+
if [[ "${2:-}" =~ ^(none|safe|full)$ ]]; then
|
|
226
|
+
AUTO_RESOLVE_MODE_RAW="$2"
|
|
227
|
+
shift 2
|
|
228
|
+
else
|
|
229
|
+
AUTO_RESOLVE_MODE_RAW="safe"
|
|
230
|
+
shift
|
|
231
|
+
fi
|
|
232
|
+
;;
|
|
233
|
+
--auto-resolve=*)
|
|
234
|
+
AUTO_RESOLVE_MODE_RAW="${1#--auto-resolve=}"
|
|
235
|
+
shift
|
|
236
|
+
;;
|
|
237
|
+
--no-auto-resolve)
|
|
238
|
+
AUTO_RESOLVE_MODE_RAW="none"
|
|
239
|
+
shift
|
|
240
|
+
;;
|
|
241
|
+
--no-preflight)
|
|
242
|
+
PREFLIGHT_ENABLED_RAW="false"
|
|
243
|
+
shift
|
|
244
|
+
;;
|
|
245
|
+
--preflight)
|
|
246
|
+
PREFLIGHT_ENABLED_RAW="true"
|
|
247
|
+
shift
|
|
248
|
+
;;
|
|
249
|
+
--preflight-script)
|
|
250
|
+
PREFLIGHT_SCRIPT_RAW="${2:-}"
|
|
251
|
+
shift 2
|
|
252
|
+
;;
|
|
253
|
+
--no-auto-promote)
|
|
254
|
+
AUTO_PROMOTE_DRAFT_RAW="false"
|
|
255
|
+
shift
|
|
256
|
+
;;
|
|
257
|
+
--auto-promote)
|
|
258
|
+
AUTO_PROMOTE_DRAFT_RAW="true"
|
|
259
|
+
shift
|
|
260
|
+
;;
|
|
142
261
|
*)
|
|
143
262
|
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
|
|
263
|
+
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
264
|
exit 1
|
|
146
265
|
;;
|
|
147
266
|
esac
|
|
@@ -159,12 +278,162 @@ case "$MERGE_MODE" in
|
|
|
159
278
|
;;
|
|
160
279
|
esac
|
|
161
280
|
|
|
281
|
+
AUTO_RESOLVE_MODE="$(printf '%s' "$AUTO_RESOLVE_MODE_RAW" | tr '[:upper:]' '[:lower:]')"
|
|
282
|
+
case "$AUTO_RESOLVE_MODE" in
|
|
283
|
+
none|safe|full) ;;
|
|
284
|
+
*)
|
|
285
|
+
echo "[agent-branch-finish] Invalid --auto-resolve value: ${AUTO_RESOLVE_MODE_RAW} (expected none|safe|full)" >&2
|
|
286
|
+
exit 1
|
|
287
|
+
;;
|
|
288
|
+
esac
|
|
289
|
+
|
|
290
|
+
path_matches_auto_resolve_safe_glob() {
|
|
291
|
+
local path="$1"
|
|
292
|
+
if [[ -z "${AUTO_RESOLVE_SAFE_GLOBS_RAW:-}" ]]; then
|
|
293
|
+
return 1
|
|
294
|
+
fi
|
|
295
|
+
local -a globs=()
|
|
296
|
+
IFS=':' read -ra globs <<< "$AUTO_RESOLVE_SAFE_GLOBS_RAW"
|
|
297
|
+
local pattern rewritten
|
|
298
|
+
for pattern in "${globs[@]}"; do
|
|
299
|
+
[[ -z "$pattern" ]] && continue
|
|
300
|
+
rewritten="${pattern%/**}"
|
|
301
|
+
if [[ "$rewritten" != "$pattern" ]]; then
|
|
302
|
+
if [[ "$path" == "$rewritten"/* ]]; then
|
|
303
|
+
return 0
|
|
304
|
+
fi
|
|
305
|
+
else
|
|
306
|
+
# shellcheck disable=SC2053
|
|
307
|
+
if [[ "$path" == $pattern ]]; then
|
|
308
|
+
return 0
|
|
309
|
+
fi
|
|
310
|
+
fi
|
|
311
|
+
done
|
|
312
|
+
return 1
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
# Resolve a conflicting submodule pointer if and only if one side is a strict
|
|
316
|
+
# ancestor of the other (fast-forward direction). Writes the resolved SHA via
|
|
317
|
+
# git update-index and prints the chosen SHA on stdout. Returns 0 on success,
|
|
318
|
+
# 1 on uninitialized/divergent/unreachable cases.
|
|
319
|
+
try_resolve_submodule_pointer_conflict() {
|
|
320
|
+
local repo_root_arg="$1"
|
|
321
|
+
local source_worktree_arg="$2"
|
|
322
|
+
local conflict_path="$3"
|
|
323
|
+
|
|
324
|
+
# Confirm registered submodule path.
|
|
325
|
+
if [[ ! -f "$repo_root_arg/.gitmodules" ]]; then
|
|
326
|
+
return 1
|
|
327
|
+
fi
|
|
328
|
+
if ! git -C "$repo_root_arg" config -f .gitmodules --get-regexp '^submodule\..*\.path$' 2>/dev/null \
|
|
329
|
+
| awk '{print $2}' | grep -Fxq -- "$conflict_path"; then
|
|
330
|
+
return 1
|
|
331
|
+
fi
|
|
332
|
+
|
|
333
|
+
# Read the three stages from the index.
|
|
334
|
+
local stage_out
|
|
335
|
+
stage_out="$(git -C "$source_worktree_arg" ls-files -u -- "$conflict_path" 2>/dev/null || true)"
|
|
336
|
+
if [[ -z "$stage_out" ]]; then
|
|
337
|
+
return 1
|
|
338
|
+
fi
|
|
339
|
+
|
|
340
|
+
local base_sha="" ours_sha="" theirs_sha=""
|
|
341
|
+
local mode_field stage_sha stage_num path_field
|
|
342
|
+
while IFS=$'\t' read -r meta path_field; do
|
|
343
|
+
[[ -z "$meta" || -z "$path_field" ]] && continue
|
|
344
|
+
# meta format: "<mode> <sha> <stage>"
|
|
345
|
+
read -r mode_field stage_sha stage_num <<< "$meta"
|
|
346
|
+
[[ "$mode_field" != "160000" ]] && return 1
|
|
347
|
+
case "$stage_num" in
|
|
348
|
+
1) base_sha="$stage_sha" ;;
|
|
349
|
+
2) ours_sha="$stage_sha" ;;
|
|
350
|
+
3) theirs_sha="$stage_sha" ;;
|
|
351
|
+
esac
|
|
352
|
+
done <<< "$stage_out"
|
|
353
|
+
|
|
354
|
+
if [[ -z "$ours_sha" || -z "$theirs_sha" ]]; then
|
|
355
|
+
return 1
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
# Pick a working clone for the submodule. Three sources, in order:
|
|
359
|
+
# 1) checked-out submodule worktree (cheap, no network)
|
|
360
|
+
# 2) cached internal clone at .git/modules/<name>
|
|
361
|
+
# 3) temp bare clone from the submodule URL (last resort; needs network)
|
|
362
|
+
local sub_query_dir=""
|
|
363
|
+
local sub_dir="$source_worktree_arg/$conflict_path"
|
|
364
|
+
if [[ -d "$sub_dir" ]] && git -C "$sub_dir" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
365
|
+
sub_query_dir="$sub_dir"
|
|
366
|
+
else
|
|
367
|
+
local repo_git_dir
|
|
368
|
+
repo_git_dir="$(git -C "$source_worktree_arg" rev-parse --git-common-dir 2>/dev/null || true)"
|
|
369
|
+
if [[ -n "$repo_git_dir" && -d "$repo_git_dir/modules/$conflict_path" ]]; then
|
|
370
|
+
sub_query_dir="$repo_git_dir/modules/$conflict_path"
|
|
371
|
+
fi
|
|
372
|
+
fi
|
|
373
|
+
|
|
374
|
+
local temp_sub_clone=""
|
|
375
|
+
cleanup_temp_sub_clone() {
|
|
376
|
+
[[ -n "$temp_sub_clone" && -d "$temp_sub_clone" ]] && rm -rf "$temp_sub_clone"
|
|
377
|
+
}
|
|
378
|
+
trap cleanup_temp_sub_clone RETURN
|
|
379
|
+
|
|
380
|
+
if [[ -z "$sub_query_dir" ]]; then
|
|
381
|
+
local sub_url
|
|
382
|
+
sub_url="$(git -C "$repo_root_arg" config -f .gitmodules --get "submodule.${conflict_path}.url" 2>/dev/null || true)"
|
|
383
|
+
if [[ -z "$sub_url" ]]; then
|
|
384
|
+
return 1
|
|
385
|
+
fi
|
|
386
|
+
temp_sub_clone="$(mktemp -d -t gx-submod-resolve-XXXXXX 2>/dev/null || true)"
|
|
387
|
+
if [[ -z "$temp_sub_clone" || ! -d "$temp_sub_clone" ]]; then
|
|
388
|
+
return 1
|
|
389
|
+
fi
|
|
390
|
+
if ! git clone --quiet --bare "$sub_url" "$temp_sub_clone" >/dev/null 2>&1; then
|
|
391
|
+
return 1
|
|
392
|
+
fi
|
|
393
|
+
sub_query_dir="$temp_sub_clone"
|
|
394
|
+
fi
|
|
395
|
+
|
|
396
|
+
if ! git -C "$sub_query_dir" cat-file -e "${ours_sha}^{commit}" 2>/dev/null \
|
|
397
|
+
|| ! git -C "$sub_query_dir" cat-file -e "${theirs_sha}^{commit}" 2>/dev/null; then
|
|
398
|
+
git -C "$sub_query_dir" fetch --quiet --all 2>/dev/null || true
|
|
399
|
+
fi
|
|
400
|
+
if ! git -C "$sub_query_dir" cat-file -e "${ours_sha}^{commit}" 2>/dev/null \
|
|
401
|
+
|| ! git -C "$sub_query_dir" cat-file -e "${theirs_sha}^{commit}" 2>/dev/null; then
|
|
402
|
+
return 1
|
|
403
|
+
fi
|
|
404
|
+
|
|
405
|
+
local chosen_sha=""
|
|
406
|
+
if [[ "$ours_sha" == "$theirs_sha" ]]; then
|
|
407
|
+
chosen_sha="$ours_sha"
|
|
408
|
+
elif git -C "$sub_query_dir" merge-base --is-ancestor "$ours_sha" "$theirs_sha" 2>/dev/null; then
|
|
409
|
+
chosen_sha="$theirs_sha"
|
|
410
|
+
elif git -C "$sub_query_dir" merge-base --is-ancestor "$theirs_sha" "$ours_sha" 2>/dev/null; then
|
|
411
|
+
chosen_sha="$ours_sha"
|
|
412
|
+
else
|
|
413
|
+
# Divergent histories; refuse.
|
|
414
|
+
return 1
|
|
415
|
+
fi
|
|
416
|
+
|
|
417
|
+
if ! git -C "$source_worktree_arg" update-index --cacheinfo "160000,${chosen_sha},${conflict_path}" >/dev/null 2>&1; then
|
|
418
|
+
return 1
|
|
419
|
+
fi
|
|
420
|
+
|
|
421
|
+
printf '%s' "$chosen_sha"
|
|
422
|
+
return 0
|
|
423
|
+
}
|
|
424
|
+
|
|
162
425
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
163
426
|
echo "[agent-branch-finish] Not inside a git repository." >&2
|
|
164
427
|
exit 1
|
|
165
428
|
fi
|
|
166
429
|
|
|
167
430
|
repo_root="$(git rev-parse --show-toplevel)"
|
|
431
|
+
finish_active_cwd="${GUARDEX_FINISH_ACTIVE_CWD:-$(pwd -P)}"
|
|
432
|
+
if [[ -d "$finish_active_cwd" ]]; then
|
|
433
|
+
finish_active_cwd="$(cd "$finish_active_cwd" && pwd -P)"
|
|
434
|
+
else
|
|
435
|
+
finish_active_cwd=""
|
|
436
|
+
fi
|
|
168
437
|
# The physical cwd may be a subdirectory inside the source worktree. Cleanup
|
|
169
438
|
# decisions need the enclosing worktree root, otherwise finishing from `src/`
|
|
170
439
|
# can delete the caller's cwd and turn a successful merge into a false shell
|
|
@@ -178,6 +447,36 @@ else
|
|
|
178
447
|
fi
|
|
179
448
|
repo_common_root="$(cd "$common_git_dir/.." && pwd -P)"
|
|
180
449
|
|
|
450
|
+
resolve_same_repo_worktree_for_cwd() {
|
|
451
|
+
local active_cwd="$1"
|
|
452
|
+
[[ -n "$active_cwd" && -d "$active_cwd" ]] || return 0
|
|
453
|
+
git -C "$active_cwd" rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 0
|
|
454
|
+
|
|
455
|
+
local active_worktree=""
|
|
456
|
+
active_worktree="$(git -C "$active_cwd" rev-parse --show-toplevel 2>/dev/null || true)"
|
|
457
|
+
[[ -n "$active_worktree" ]] || return 0
|
|
458
|
+
|
|
459
|
+
local active_common_raw=""
|
|
460
|
+
local active_common_dir=""
|
|
461
|
+
active_common_raw="$(git -C "$active_worktree" rev-parse --git-common-dir 2>/dev/null || true)"
|
|
462
|
+
[[ -n "$active_common_raw" ]] || return 0
|
|
463
|
+
if [[ "$active_common_raw" == /* ]]; then
|
|
464
|
+
active_common_dir="$active_common_raw"
|
|
465
|
+
else
|
|
466
|
+
active_common_dir="${active_worktree}/${active_common_raw}"
|
|
467
|
+
fi
|
|
468
|
+
active_common_dir="$(cd "$active_common_dir" 2>/dev/null && pwd -P)" || return 0
|
|
469
|
+
|
|
470
|
+
if [[ "$active_common_dir" == "$common_git_dir" ]]; then
|
|
471
|
+
cd "$active_worktree" 2>/dev/null && pwd -P
|
|
472
|
+
fi
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
active_cwd_worktree="$(resolve_same_repo_worktree_for_cwd "$finish_active_cwd")"
|
|
476
|
+
if [[ -n "$active_cwd_worktree" ]]; then
|
|
477
|
+
current_worktree="$active_cwd_worktree"
|
|
478
|
+
fi
|
|
479
|
+
|
|
181
480
|
if [[ -z "$SOURCE_BRANCH" ]]; then
|
|
182
481
|
SOURCE_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
|
183
482
|
fi
|
|
@@ -336,6 +635,22 @@ is_clean_worktree() {
|
|
|
336
635
|
&& git -C "$wt" diff --cached --quiet -- . ":(exclude).omx/state/agent-file-locks.json"
|
|
337
636
|
}
|
|
338
637
|
|
|
638
|
+
refresh_clean_base_worktree() {
|
|
639
|
+
local wt="$1"
|
|
640
|
+
[[ -z "$wt" || "$PUSH_ENABLED" -ne 1 ]] && return 0
|
|
641
|
+
|
|
642
|
+
if ! is_clean_worktree "$wt"; then
|
|
643
|
+
echo "[agent-branch-finish] Warning: local ${BASE_BRANCH} worktree is dirty; skipping 'git pull --ff-only origin ${BASE_BRANCH}' for ${wt}." >&2
|
|
644
|
+
return 0
|
|
645
|
+
fi
|
|
646
|
+
|
|
647
|
+
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
|
|
648
|
+
echo "[agent-branch-finish] Refreshed local ${BASE_BRANCH} worktree with 'git pull --ff-only origin ${BASE_BRANCH}': ${wt}"
|
|
649
|
+
else
|
|
650
|
+
echo "[agent-branch-finish] Warning: failed to refresh local ${BASE_BRANCH} worktree with 'git pull --ff-only origin ${BASE_BRANCH}': ${wt}" >&2
|
|
651
|
+
fi
|
|
652
|
+
}
|
|
653
|
+
|
|
339
654
|
remove_stale_source_probe_worktrees "$SOURCE_BRANCH"
|
|
340
655
|
source_worktree="$(get_worktree_for_branch "$SOURCE_BRANCH")"
|
|
341
656
|
created_source_probe=0
|
|
@@ -346,6 +661,7 @@ merge_completed=0
|
|
|
346
661
|
merge_status="pr"
|
|
347
662
|
direct_push_error=""
|
|
348
663
|
pr_url=""
|
|
664
|
+
changed_submodule_push_done=0
|
|
349
665
|
|
|
350
666
|
cleanup() {
|
|
351
667
|
if [[ -n "$integration_worktree" && -d "$integration_worktree" ]]; then
|
|
@@ -432,20 +748,97 @@ if git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/${BASE_BRA
|
|
|
432
748
|
|
|
433
749
|
if ! git -C "$source_worktree" merge --no-commit --no-ff "origin/${BASE_BRANCH}" >/dev/null 2>&1; then
|
|
434
750
|
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
751
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
752
|
+
if [[ "$AUTO_RESOLVE_MODE" != "none" && -n "$conflict_files" ]]; then
|
|
753
|
+
auto_resolve_unresolved=""
|
|
754
|
+
auto_resolve_resolved_state=""
|
|
755
|
+
auto_resolve_resolved_submodules=""
|
|
756
|
+
while IFS= read -r conflict_path; do
|
|
757
|
+
[[ -z "$conflict_path" ]] && continue
|
|
758
|
+
if path_matches_auto_resolve_safe_glob "$conflict_path"; then
|
|
759
|
+
if git -C "$source_worktree" checkout --theirs -- "$conflict_path" >/dev/null 2>&1 \
|
|
760
|
+
&& git -C "$source_worktree" add -- "$conflict_path" >/dev/null 2>&1; then
|
|
761
|
+
auto_resolve_resolved_state+="${conflict_path}"$'\n'
|
|
762
|
+
continue
|
|
763
|
+
fi
|
|
764
|
+
fi
|
|
765
|
+
if [[ "$AUTO_RESOLVE_MODE" == "full" ]]; then
|
|
766
|
+
if chosen_sha="$(try_resolve_submodule_pointer_conflict "$repo_root" "$source_worktree" "$conflict_path")"; then
|
|
767
|
+
auto_resolve_resolved_submodules+="${conflict_path}@${chosen_sha}"$'\n'
|
|
768
|
+
continue
|
|
769
|
+
fi
|
|
770
|
+
fi
|
|
771
|
+
auto_resolve_unresolved+="${conflict_path}"$'\n'
|
|
442
772
|
done <<< "$conflict_files"
|
|
773
|
+
|
|
774
|
+
if [[ -n "$auto_resolve_unresolved" ]]; then
|
|
775
|
+
git -C "$source_worktree" merge --abort >/dev/null 2>&1 || true
|
|
776
|
+
echo "[agent-branch-finish] --auto-resolve=${AUTO_RESOLVE_MODE}: some conflicts are outside the safe allowlist (or submodule histories diverge); aborting." >&2
|
|
777
|
+
echo "[agent-branch-finish] Unresolved conflicts:" >&2
|
|
778
|
+
while IFS= read -r unresolved_path; do
|
|
779
|
+
[[ -n "$unresolved_path" ]] && echo " - ${unresolved_path}" >&2
|
|
780
|
+
done <<< "$auto_resolve_unresolved"
|
|
781
|
+
echo "[agent-branch-finish] State-file allowlist (GUARDEX_FINISH_AUTO_RESOLVE_SAFE_GLOBS): ${AUTO_RESOLVE_SAFE_GLOBS_RAW}" >&2
|
|
782
|
+
if [[ "$AUTO_RESOLVE_MODE" != "full" ]]; then
|
|
783
|
+
echo "[agent-branch-finish] Submodule pointer auto-resolve requires --auto-resolve=full; not enabled for this run." >&2
|
|
784
|
+
fi
|
|
785
|
+
exit 1
|
|
786
|
+
fi
|
|
787
|
+
|
|
788
|
+
# Claim resolved paths so the pre-commit lock guard accepts the merge.
|
|
789
|
+
auto_resolve_claim_paths=()
|
|
790
|
+
while IFS= read -r resolved_path; do
|
|
791
|
+
[[ -n "$resolved_path" ]] && auto_resolve_claim_paths+=("$resolved_path")
|
|
792
|
+
done <<< "$auto_resolve_resolved_state"
|
|
793
|
+
while IFS= read -r resolved_entry; do
|
|
794
|
+
[[ -z "$resolved_entry" ]] && continue
|
|
795
|
+
auto_resolve_claim_paths+=("${resolved_entry%@*}")
|
|
796
|
+
done <<< "$auto_resolve_resolved_submodules"
|
|
797
|
+
if [[ "${#auto_resolve_claim_paths[@]}" -gt 0 ]]; then
|
|
798
|
+
run_guardex_cli locks claim --branch "$SOURCE_BRANCH" "${auto_resolve_claim_paths[@]}" >/dev/null 2>&1 || true
|
|
799
|
+
fi
|
|
800
|
+
|
|
801
|
+
auto_resolve_commit_msg="Merge origin/${BASE_BRANCH} into ${SOURCE_BRANCH} (gx --auto-resolve=${AUTO_RESOLVE_MODE}; state files -> base, submodule pointers fast-forwarded)"
|
|
802
|
+
if ! git -C "$source_worktree" commit -m "$auto_resolve_commit_msg" >/dev/null 2>&1; then
|
|
803
|
+
git -C "$source_worktree" merge --abort >/dev/null 2>&1 || true
|
|
804
|
+
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
|
|
805
|
+
exit 1
|
|
806
|
+
fi
|
|
807
|
+
|
|
808
|
+
state_count=0
|
|
809
|
+
submod_count=0
|
|
810
|
+
[[ -n "$auto_resolve_resolved_state" ]] && state_count="$(printf '%s' "$auto_resolve_resolved_state" | grep -c '^[^[:space:]]')"
|
|
811
|
+
[[ -n "$auto_resolve_resolved_submodules" ]] && submod_count="$(printf '%s' "$auto_resolve_resolved_submodules" | grep -c '^[^[:space:]]')"
|
|
812
|
+
echo "[agent-branch-finish] --auto-resolve=${AUTO_RESOLVE_MODE}: resolved ${state_count} state-file conflict(s), ${submod_count} submodule pointer conflict(s)." >&2
|
|
813
|
+
if [[ -n "$auto_resolve_resolved_state" ]]; then
|
|
814
|
+
echo "[agent-branch-finish] State files (resolved to base):" >&2
|
|
815
|
+
while IFS= read -r resolved_path; do
|
|
816
|
+
[[ -n "$resolved_path" ]] && echo " - ${resolved_path}" >&2
|
|
817
|
+
done <<< "$auto_resolve_resolved_state"
|
|
818
|
+
fi
|
|
819
|
+
if [[ -n "$auto_resolve_resolved_submodules" ]]; then
|
|
820
|
+
echo "[agent-branch-finish] Submodule pointers (fast-forwarded):" >&2
|
|
821
|
+
while IFS= read -r resolved_entry; do
|
|
822
|
+
[[ -n "$resolved_entry" ]] && echo " - ${resolved_entry%@*} -> ${resolved_entry##*@}" >&2
|
|
823
|
+
done <<< "$auto_resolve_resolved_submodules"
|
|
824
|
+
fi
|
|
825
|
+
else
|
|
826
|
+
git -C "$source_worktree" merge --abort >/dev/null 2>&1 || true
|
|
827
|
+
|
|
828
|
+
echo "[agent-branch-finish] Preflight conflict detected between '${SOURCE_BRANCH}' and latest origin/${BASE_BRANCH}." >&2
|
|
829
|
+
if [[ -n "$conflict_files" ]]; then
|
|
830
|
+
echo "[agent-branch-finish] Conflicting files:" >&2
|
|
831
|
+
while IFS= read -r file; do
|
|
832
|
+
[[ -n "$file" ]] && echo " - ${file}" >&2
|
|
833
|
+
done <<< "$conflict_files"
|
|
834
|
+
fi
|
|
835
|
+
echo "[agent-branch-finish] Rebase/merge '${BASE_BRANCH}' into '${SOURCE_BRANCH}' and resolve conflicts before finishing." >&2
|
|
836
|
+
echo "[agent-branch-finish] Or rerun with --auto-resolve=safe (state files) or --auto-resolve=full (state files + fast-forward-able submodule pointers)." >&2
|
|
837
|
+
exit 1
|
|
443
838
|
fi
|
|
444
|
-
|
|
445
|
-
|
|
839
|
+
else
|
|
840
|
+
git -C "$source_worktree" merge --abort >/dev/null 2>&1 || true
|
|
446
841
|
fi
|
|
447
|
-
|
|
448
|
-
git -C "$source_worktree" merge --abort >/dev/null 2>&1 || true
|
|
449
842
|
fi
|
|
450
843
|
|
|
451
844
|
should_create_integration_helper=1
|
|
@@ -682,6 +1075,77 @@ maybe_auto_commit_parent_gitlink() {
|
|
|
682
1075
|
echo "[agent-branch-finish] Parent gitlink auto-committed '${subrepo_rel}' in ${super_root}."
|
|
683
1076
|
}
|
|
684
1077
|
|
|
1078
|
+
maybe_push_changed_submodule_branches() {
|
|
1079
|
+
local base_ref="${1:-}"
|
|
1080
|
+
local source_ref="${2:-}"
|
|
1081
|
+
local changed_path=""
|
|
1082
|
+
local gitlink_mode=""
|
|
1083
|
+
local gitlink_sha=""
|
|
1084
|
+
local submodule_dir=""
|
|
1085
|
+
local branch_name=""
|
|
1086
|
+
local candidate_branch=""
|
|
1087
|
+
local remote_name=""
|
|
1088
|
+
local push_output=""
|
|
1089
|
+
|
|
1090
|
+
if [[ "$PUSH_ENABLED" -ne 1 || "$changed_submodule_push_done" -eq 1 ]]; then
|
|
1091
|
+
return 0
|
|
1092
|
+
fi
|
|
1093
|
+
changed_submodule_push_done=1
|
|
1094
|
+
if [[ -z "$base_ref" || -z "$source_ref" ]]; then
|
|
1095
|
+
return 0
|
|
1096
|
+
fi
|
|
1097
|
+
|
|
1098
|
+
while IFS= read -r changed_path; do
|
|
1099
|
+
[[ -n "$changed_path" ]] || continue
|
|
1100
|
+
|
|
1101
|
+
gitlink_mode="$(git -C "$source_worktree" ls-tree "$source_ref" -- "$changed_path" | awk 'NR == 1 { print $1 }')"
|
|
1102
|
+
if [[ "$gitlink_mode" != "160000" ]]; then
|
|
1103
|
+
continue
|
|
1104
|
+
fi
|
|
1105
|
+
gitlink_sha="$(git -C "$source_worktree" ls-tree "$source_ref" -- "$changed_path" | awk 'NR == 1 { print $3 }')"
|
|
1106
|
+
if [[ -z "$gitlink_sha" ]]; then
|
|
1107
|
+
continue
|
|
1108
|
+
fi
|
|
1109
|
+
|
|
1110
|
+
submodule_dir="${source_worktree}/${changed_path}"
|
|
1111
|
+
if ! git -C "$submodule_dir" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
1112
|
+
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
|
|
1113
|
+
return 1
|
|
1114
|
+
fi
|
|
1115
|
+
if ! git -C "$submodule_dir" cat-file -e "${gitlink_sha}^{commit}" >/dev/null 2>&1; then
|
|
1116
|
+
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
|
|
1117
|
+
return 1
|
|
1118
|
+
fi
|
|
1119
|
+
|
|
1120
|
+
branch_name="$(git -C "$submodule_dir" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
1121
|
+
if [[ -z "$branch_name" || "$branch_name" == "HEAD" ]] || ! git -C "$submodule_dir" merge-base --is-ancestor "$gitlink_sha" "$branch_name" >/dev/null 2>&1; then
|
|
1122
|
+
candidate_branch="$(git -C "$submodule_dir" for-each-ref --contains "$gitlink_sha" --format='%(refname:short)' refs/heads | head -n 1)"
|
|
1123
|
+
branch_name="$candidate_branch"
|
|
1124
|
+
fi
|
|
1125
|
+
if [[ -z "$branch_name" || "$branch_name" == "HEAD" ]]; then
|
|
1126
|
+
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
|
|
1127
|
+
return 1
|
|
1128
|
+
fi
|
|
1129
|
+
|
|
1130
|
+
remote_name="$(git -C "$submodule_dir" config --get "branch.${branch_name}.remote" || true)"
|
|
1131
|
+
if [[ -z "$remote_name" ]]; then
|
|
1132
|
+
remote_name="origin"
|
|
1133
|
+
fi
|
|
1134
|
+
if ! git -C "$submodule_dir" remote get-url "$remote_name" >/dev/null 2>&1; then
|
|
1135
|
+
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
|
|
1136
|
+
return 1
|
|
1137
|
+
fi
|
|
1138
|
+
|
|
1139
|
+
if push_output="$(git -C "$submodule_dir" push -u "$remote_name" "${branch_name}:${branch_name}" 2>&1)"; then
|
|
1140
|
+
echo "[agent-branch-finish] Pushed changed submodule '${changed_path}' branch '${branch_name}' to '${remote_name}' before parent finish."
|
|
1141
|
+
else
|
|
1142
|
+
echo "[agent-branch-finish] Changed submodule '${changed_path}' must be pushed before the parent branch can be finished." >&2
|
|
1143
|
+
[[ -n "$push_output" ]] && echo "$push_output" >&2
|
|
1144
|
+
return 1
|
|
1145
|
+
fi
|
|
1146
|
+
done < <(git -C "$source_worktree" diff --name-only "$base_ref" "$source_ref" -- 2>/dev/null || true)
|
|
1147
|
+
}
|
|
1148
|
+
|
|
685
1149
|
wait_for_pr_merge() {
|
|
686
1150
|
local deadline
|
|
687
1151
|
deadline=$(( $(date +%s) + WAIT_TIMEOUT_SECONDS ))
|
|
@@ -752,6 +1216,7 @@ run_pr_flow() {
|
|
|
752
1216
|
return 0
|
|
753
1217
|
fi
|
|
754
1218
|
|
|
1219
|
+
maybe_push_changed_submodule_branches "$start_ref" "$SOURCE_BRANCH"
|
|
755
1220
|
git -C "$source_worktree" push -u origin "$SOURCE_BRANCH"
|
|
756
1221
|
|
|
757
1222
|
pr_title="$(git -C "$repo_root" log -1 --pretty=%s "$SOURCE_BRANCH" 2>/dev/null || true)"
|
|
@@ -760,14 +1225,39 @@ run_pr_flow() {
|
|
|
760
1225
|
fi
|
|
761
1226
|
pr_body="Automated by gx branch finish (PR flow)."
|
|
762
1227
|
|
|
763
|
-
"
|
|
1228
|
+
pr_create_output=""
|
|
1229
|
+
if pr_create_output="$("$GH_BIN" pr create \
|
|
764
1230
|
--base "$BASE_BRANCH" \
|
|
765
1231
|
--head "$SOURCE_BRANCH" \
|
|
766
1232
|
--title "$pr_title" \
|
|
767
|
-
--body "$pr_body"
|
|
1233
|
+
--body "$pr_body" 2>&1)"; then
|
|
1234
|
+
:
|
|
1235
|
+
else
|
|
1236
|
+
# Idempotent: a PR already opened for this head is fine — fall through
|
|
1237
|
+
# to `gh pr view` so we still capture the URL. Anything else is a real
|
|
1238
|
+
# failure and the user needs to see it.
|
|
1239
|
+
if ! grep -qiE 'already exists|a pull request for branch' <<<"$pr_create_output"; then
|
|
1240
|
+
echo "[agent-branch-finish] gh pr create failed:" >&2
|
|
1241
|
+
echo "${pr_create_output}" >&2
|
|
1242
|
+
fi
|
|
1243
|
+
fi
|
|
768
1244
|
|
|
769
1245
|
pr_url="$("$GH_BIN" pr view "$SOURCE_BRANCH" --json url --jq '.url' 2>/dev/null || true)"
|
|
770
1246
|
|
|
1247
|
+
if [[ -z "$pr_url" ]]; then
|
|
1248
|
+
echo "[agent-branch-finish] No PR found for '${SOURCE_BRANCH}' after gh pr create; cannot proceed with PR merge." >&2
|
|
1249
|
+
if [[ -n "$pr_create_output" ]]; then
|
|
1250
|
+
echo "[agent-branch-finish] Last gh pr create output:" >&2
|
|
1251
|
+
echo "${pr_create_output}" >&2
|
|
1252
|
+
fi
|
|
1253
|
+
return 1
|
|
1254
|
+
fi
|
|
1255
|
+
echo "[agent-branch-finish] PR URL: ${pr_url}" >&2
|
|
1256
|
+
|
|
1257
|
+
# Pre-flight already passed by the time we reach the PR; promote any
|
|
1258
|
+
# existing draft so the budget-friendly CI gate fires once.
|
|
1259
|
+
maybe_auto_promote_pr "$pr_url"
|
|
1260
|
+
|
|
771
1261
|
merge_output=""
|
|
772
1262
|
if merge_output="$("$GH_BIN" pr merge "$SOURCE_BRANCH" --squash --delete-branch 2>&1)"; then
|
|
773
1263
|
return 0
|
|
@@ -799,7 +1289,11 @@ run_pr_flow() {
|
|
|
799
1289
|
}
|
|
800
1290
|
|
|
801
1291
|
if [[ "$PUSH_ENABLED" -eq 1 ]]; then
|
|
1292
|
+
if ! run_preflight "$source_worktree"; then
|
|
1293
|
+
exit 1
|
|
1294
|
+
fi
|
|
802
1295
|
if [[ "$MERGE_MODE" != "pr" ]]; then
|
|
1296
|
+
maybe_push_changed_submodule_branches "$start_ref" "$SOURCE_BRANCH"
|
|
803
1297
|
if ! direct_push_output="$(git -C "$integration_worktree" push origin "HEAD:${BASE_BRANCH}" 2>&1)"; then
|
|
804
1298
|
direct_push_error="$direct_push_output"
|
|
805
1299
|
merge_completed=0
|
|
@@ -847,9 +1341,7 @@ fi
|
|
|
847
1341
|
run_guardex_cli locks release --branch "$SOURCE_BRANCH" >/dev/null 2>&1 || true
|
|
848
1342
|
|
|
849
1343
|
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
|
|
1344
|
+
refresh_clean_base_worktree "$base_worktree"
|
|
853
1345
|
maybe_auto_commit_parent_gitlink "$base_worktree"
|
|
854
1346
|
|
|
855
1347
|
# Pivot out of the agent worktree before prune calls that may remove it.
|
|
@@ -861,6 +1353,10 @@ pivot_to_repo_root_before_prune() {
|
|
|
861
1353
|
fi
|
|
862
1354
|
}
|
|
863
1355
|
|
|
1356
|
+
run_guardex_prune() {
|
|
1357
|
+
GUARDEX_PRUNE_ACTIVE_CWD="$finish_active_cwd" run_guardex_cli worktree prune "$@"
|
|
1358
|
+
}
|
|
1359
|
+
|
|
864
1360
|
if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
|
|
865
1361
|
if [[ "$source_worktree" == "$repo_root" ]]; then
|
|
866
1362
|
if is_clean_worktree "$source_worktree"; then
|
|
@@ -871,7 +1367,7 @@ if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
|
|
|
871
1367
|
git -C "$source_worktree" checkout --detach >/dev/null 2>&1 || true
|
|
872
1368
|
fi
|
|
873
1369
|
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
|
-
|
|
1370
|
+
refresh_clean_base_worktree "$source_worktree"
|
|
875
1371
|
fi
|
|
876
1372
|
fi
|
|
877
1373
|
elif [[ "$source_worktree" == "$current_worktree" && "$source_worktree" == "${agent_worktree_root}"/* ]]; then
|
|
@@ -906,7 +1402,7 @@ if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
|
|
|
906
1402
|
fi
|
|
907
1403
|
|
|
908
1404
|
pivot_to_repo_root_before_prune
|
|
909
|
-
if !
|
|
1405
|
+
if ! run_guardex_prune "${prune_args[@]}"; then
|
|
910
1406
|
echo "[agent-branch-finish] Warning: automatic worktree prune failed." >&2
|
|
911
1407
|
echo "[agent-branch-finish] You can run manual cleanup: gx cleanup --base ${BASE_BRANCH}" >&2
|
|
912
1408
|
fi
|
|
@@ -920,7 +1416,7 @@ if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
|
|
|
920
1416
|
fi
|
|
921
1417
|
else
|
|
922
1418
|
pivot_to_repo_root_before_prune
|
|
923
|
-
if !
|
|
1419
|
+
if ! run_guardex_prune --base "$BASE_BRANCH"; then
|
|
924
1420
|
echo "[agent-branch-finish] Warning: temporary worktree prune failed." >&2
|
|
925
1421
|
fi
|
|
926
1422
|
|