@imdeadpool/guardex 5.0.2 → 5.0.3
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/CONTRIBUTING.md +1 -0
- package/README.md +129 -311
- package/bin/multiagent-safety.js +506 -22
- package/package.json +2 -2
- package/templates/AGENTS.multiagent-safety.md +5 -3
- package/templates/scripts/agent-branch-finish.sh +141 -8
- package/templates/scripts/agent-branch-start.sh +40 -6
- package/templates/scripts/codex-agent.sh +21 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imdeadpool/guardex",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.3",
|
|
4
4
|
"description": "GuardeX: the Guardian T-Rex for your repo, with hardened multi-agent git guardrails.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"preferGlobal": true,
|
|
@@ -15,7 +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:cleanup": "
|
|
18
|
+
"agent:cleanup": "gx cleanup",
|
|
19
19
|
"agent:hooks:install": "bash ./scripts/install-agent-git-hooks.sh",
|
|
20
20
|
"agent:locks:claim": "python3 ./scripts/agent-file-locks.py claim",
|
|
21
21
|
"agent:locks:allow-delete": "python3 ./scripts/agent-file-locks.py allow-delete",
|
|
@@ -12,11 +12,13 @@
|
|
|
12
12
|
- For git isolation, each agent must start on a dedicated branch via `scripts/agent-branch-start.sh "<task-or-plan>" "<agent-name>"`.
|
|
13
13
|
- Treat the base branch (`main` or the user's current local base branch) as read-only while the agent branch is active.
|
|
14
14
|
- Agent completion defaults to `scripts/codex-agent.sh`, which auto-finishes the branch (auto-commit changed files, push/create PR, attempt merge, and pull the local base branch after merge).
|
|
15
|
-
- Auto-finish
|
|
16
|
-
- Use
|
|
17
|
-
- If codex-agent auto-finish cannot complete, immediately run `scripts/agent-branch-finish.sh --branch "<agent-branch>" --via-pr` and keep the branch open until checks/review pass.
|
|
15
|
+
- Auto-finish now waits for required checks/merge and then cleans merged sandbox branch/worktree by default.
|
|
16
|
+
- Use `--no-cleanup` only when you explicitly need to keep a merged sandbox for audit/debug follow-up.
|
|
17
|
+
- If codex-agent auto-finish cannot complete, immediately run `scripts/agent-branch-finish.sh --branch "<agent-branch>" --via-pr --wait-for-merge` and keep the branch open until checks/review pass.
|
|
18
18
|
- If merge/rebase conflicts block auto-finish, run a conflict-resolution review pass in that sandbox branch, then rerun `agent-branch-finish.sh --via-pr` until merged.
|
|
19
|
+
- Completion is not valid until these are true: commit exists on the agent branch, branch is pushed to `origin`, and PR/merge status is produced by `agent-branch-finish.sh` or `codex-agent`.
|
|
19
20
|
- Per-message loop is mandatory: for every new user message/task, start a fresh agent branch/worktree, claim ownership locks, implement and verify, finish via PR/merge cleanup, then repeat for the next message/task.
|
|
21
|
+
- If the change publishes or bumps a version, the same change must also update release notes/changelog entries.
|
|
20
22
|
|
|
21
23
|
1. Explicit ownership before edits
|
|
22
24
|
|
|
@@ -10,6 +10,9 @@ DELETE_REMOTE_BRANCH_EXPLICIT=0
|
|
|
10
10
|
MERGE_MODE="auto"
|
|
11
11
|
GH_BIN="${MUSAFETY_GH_BIN:-gh}"
|
|
12
12
|
CLEANUP_AFTER_MERGE_RAW="${MUSAFETY_FINISH_CLEANUP:-false}"
|
|
13
|
+
WAIT_FOR_MERGE_RAW="${MUSAFETY_FINISH_WAIT_FOR_MERGE:-false}"
|
|
14
|
+
WAIT_TIMEOUT_SECONDS_RAW="${MUSAFETY_FINISH_WAIT_TIMEOUT_SECONDS:-1800}"
|
|
15
|
+
WAIT_POLL_SECONDS_RAW="${MUSAFETY_FINISH_WAIT_POLL_SECONDS:-10}"
|
|
13
16
|
|
|
14
17
|
normalize_bool() {
|
|
15
18
|
local raw="${1:-}"
|
|
@@ -24,7 +27,27 @@ normalize_bool() {
|
|
|
24
27
|
esac
|
|
25
28
|
}
|
|
26
29
|
|
|
30
|
+
normalize_int() {
|
|
31
|
+
local raw="${1:-}"
|
|
32
|
+
local fallback="${2:-0}"
|
|
33
|
+
local min_value="${3:-0}"
|
|
34
|
+
local value="$raw"
|
|
35
|
+
|
|
36
|
+
if [[ -z "$value" || ! "$value" =~ ^[0-9]+$ ]]; then
|
|
37
|
+
value="$fallback"
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if (( value < min_value )); then
|
|
41
|
+
value="$min_value"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
printf '%s' "$value"
|
|
45
|
+
}
|
|
46
|
+
|
|
27
47
|
CLEANUP_AFTER_MERGE="$(normalize_bool "$CLEANUP_AFTER_MERGE_RAW" "0")"
|
|
48
|
+
WAIT_FOR_MERGE="$(normalize_bool "$WAIT_FOR_MERGE_RAW" "0")"
|
|
49
|
+
WAIT_TIMEOUT_SECONDS="$(normalize_int "$WAIT_TIMEOUT_SECONDS_RAW" "1800" "30")"
|
|
50
|
+
WAIT_POLL_SECONDS="$(normalize_int "$WAIT_POLL_SECONDS_RAW" "10" "0")"
|
|
28
51
|
|
|
29
52
|
while [[ $# -gt 0 ]]; do
|
|
30
53
|
case "$1" in
|
|
@@ -59,6 +82,22 @@ while [[ $# -gt 0 ]]; do
|
|
|
59
82
|
CLEANUP_AFTER_MERGE=0
|
|
60
83
|
shift
|
|
61
84
|
;;
|
|
85
|
+
--wait-for-merge)
|
|
86
|
+
WAIT_FOR_MERGE=1
|
|
87
|
+
shift
|
|
88
|
+
;;
|
|
89
|
+
--no-wait-for-merge)
|
|
90
|
+
WAIT_FOR_MERGE=0
|
|
91
|
+
shift
|
|
92
|
+
;;
|
|
93
|
+
--wait-timeout-seconds)
|
|
94
|
+
WAIT_TIMEOUT_SECONDS="$(normalize_int "${2:-}" "1800" "30")"
|
|
95
|
+
shift 2
|
|
96
|
+
;;
|
|
97
|
+
--wait-poll-seconds)
|
|
98
|
+
WAIT_POLL_SECONDS="$(normalize_int "${2:-}" "10" "0")"
|
|
99
|
+
shift 2
|
|
100
|
+
;;
|
|
62
101
|
--mode)
|
|
63
102
|
MERGE_MODE="${2:-auto}"
|
|
64
103
|
shift 2
|
|
@@ -73,7 +112,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
73
112
|
;;
|
|
74
113
|
*)
|
|
75
114
|
echo "[agent-branch-finish] Unknown argument: $1" >&2
|
|
76
|
-
echo "Usage: $0 [--base <branch>] [--branch <branch>] [--no-push] [--cleanup|--no-cleanup] [--keep-remote-branch|--delete-remote-branch] [--mode auto|direct|pr|--via-pr|--direct-only]" >&2
|
|
115
|
+
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>] [--keep-remote-branch|--delete-remote-branch] [--mode auto|direct|pr|--via-pr|--direct-only]" >&2
|
|
77
116
|
exit 1
|
|
78
117
|
;;
|
|
79
118
|
esac
|
|
@@ -98,6 +137,14 @@ fi
|
|
|
98
137
|
|
|
99
138
|
repo_root="$(git rev-parse --show-toplevel)"
|
|
100
139
|
current_worktree="$(pwd -P)"
|
|
140
|
+
common_git_dir_raw="$(git -C "$repo_root" rev-parse --git-common-dir)"
|
|
141
|
+
if [[ "$common_git_dir_raw" == /* ]]; then
|
|
142
|
+
common_git_dir="$common_git_dir_raw"
|
|
143
|
+
else
|
|
144
|
+
common_git_dir="$(cd "$repo_root/$common_git_dir_raw" && pwd -P)"
|
|
145
|
+
fi
|
|
146
|
+
repo_common_root="$(cd "$common_git_dir/.." && pwd -P)"
|
|
147
|
+
agent_worktree_root="${repo_common_root}/.omx/agent-worktrees"
|
|
101
148
|
|
|
102
149
|
if [[ -z "$SOURCE_BRANCH" ]]; then
|
|
103
150
|
SOURCE_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
|
@@ -171,7 +218,7 @@ created_source_probe=0
|
|
|
171
218
|
source_probe_path=""
|
|
172
219
|
|
|
173
220
|
if [[ -z "$source_worktree" ]]; then
|
|
174
|
-
source_probe_path="${
|
|
221
|
+
source_probe_path="${agent_worktree_root}/__source-probe-${SOURCE_BRANCH//\//__}-$(date +%Y%m%d-%H%M%S)"
|
|
175
222
|
mkdir -p "$(dirname "$source_probe_path")"
|
|
176
223
|
git -C "$repo_root" worktree add "$source_probe_path" "$SOURCE_BRANCH" >/dev/null
|
|
177
224
|
source_worktree="$source_probe_path"
|
|
@@ -229,7 +276,7 @@ if [[ "$should_require_sync" -eq 1 ]] && git -C "$repo_root" show-ref --verify -
|
|
|
229
276
|
fi
|
|
230
277
|
fi
|
|
231
278
|
|
|
232
|
-
integration_worktree="${
|
|
279
|
+
integration_worktree="${agent_worktree_root}/__integrate-${BASE_BRANCH//\//__}-$(date +%Y%m%d-%H%M%S)"
|
|
233
280
|
integration_branch="__agent_integrate_${BASE_BRANCH//\//_}_$(date +%Y%m%d_%H%M%S)"
|
|
234
281
|
mkdir -p "$(dirname "$integration_worktree")"
|
|
235
282
|
|
|
@@ -289,6 +336,78 @@ is_local_branch_delete_error() {
|
|
|
289
336
|
return 1
|
|
290
337
|
}
|
|
291
338
|
|
|
339
|
+
read_pr_state() {
|
|
340
|
+
local state_line
|
|
341
|
+
state_line="$("$GH_BIN" pr view "$SOURCE_BRANCH" --json state,mergedAt,url --jq '[.state, (.mergedAt // ""), (.url // "")] | @tsv' 2>/dev/null || true)"
|
|
342
|
+
if [[ -z "$state_line" ]]; then
|
|
343
|
+
return 1
|
|
344
|
+
fi
|
|
345
|
+
|
|
346
|
+
local parsed_state=""
|
|
347
|
+
local parsed_merged_at=""
|
|
348
|
+
local parsed_url=""
|
|
349
|
+
IFS=$'\t' read -r parsed_state parsed_merged_at parsed_url <<< "$state_line"
|
|
350
|
+
PR_STATE="$parsed_state"
|
|
351
|
+
PR_MERGED_AT="$parsed_merged_at"
|
|
352
|
+
if [[ -n "$parsed_url" ]]; then
|
|
353
|
+
pr_url="$parsed_url"
|
|
354
|
+
fi
|
|
355
|
+
return 0
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
wait_for_pr_merge() {
|
|
359
|
+
local deadline
|
|
360
|
+
deadline=$(( $(date +%s) + WAIT_TIMEOUT_SECONDS ))
|
|
361
|
+
local wait_notice_printed=0
|
|
362
|
+
local merge_output=""
|
|
363
|
+
|
|
364
|
+
while true; do
|
|
365
|
+
if merge_output="$("$GH_BIN" pr merge "$SOURCE_BRANCH" --squash --delete-branch 2>&1)"; then
|
|
366
|
+
return 0
|
|
367
|
+
fi
|
|
368
|
+
if is_local_branch_delete_error "$merge_output"; then
|
|
369
|
+
echo "[agent-branch-finish] PR merged but gh could not delete the local branch (active worktree); continuing local cleanup." >&2
|
|
370
|
+
return 0
|
|
371
|
+
fi
|
|
372
|
+
|
|
373
|
+
PR_STATE=""
|
|
374
|
+
PR_MERGED_AT=""
|
|
375
|
+
if read_pr_state; then
|
|
376
|
+
if [[ "$PR_STATE" == "MERGED" || -n "$PR_MERGED_AT" ]]; then
|
|
377
|
+
return 0
|
|
378
|
+
fi
|
|
379
|
+
if [[ "$PR_STATE" == "CLOSED" ]]; then
|
|
380
|
+
echo "[agent-branch-finish] PR closed without merge; cannot continue auto-finish." >&2
|
|
381
|
+
if [[ -n "$pr_url" ]]; then
|
|
382
|
+
echo "[agent-branch-finish] PR: ${pr_url}" >&2
|
|
383
|
+
fi
|
|
384
|
+
if [[ -n "$merge_output" ]]; then
|
|
385
|
+
echo "$merge_output" >&2
|
|
386
|
+
fi
|
|
387
|
+
return 1
|
|
388
|
+
fi
|
|
389
|
+
fi
|
|
390
|
+
|
|
391
|
+
if [[ "$wait_notice_printed" -eq 0 ]]; then
|
|
392
|
+
echo "[agent-branch-finish] Waiting for required checks/reviews, then retrying merge automatically (timeout ${WAIT_TIMEOUT_SECONDS}s)." >&2
|
|
393
|
+
if [[ -n "$pr_url" ]]; then
|
|
394
|
+
echo "[agent-branch-finish] PR: ${pr_url}" >&2
|
|
395
|
+
fi
|
|
396
|
+
wait_notice_printed=1
|
|
397
|
+
fi
|
|
398
|
+
|
|
399
|
+
if (( $(date +%s) >= deadline )); then
|
|
400
|
+
echo "[agent-branch-finish] Timed out waiting for PR merge after ${WAIT_TIMEOUT_SECONDS}s." >&2
|
|
401
|
+
if [[ -n "$merge_output" ]]; then
|
|
402
|
+
echo "$merge_output" >&2
|
|
403
|
+
fi
|
|
404
|
+
return 2
|
|
405
|
+
fi
|
|
406
|
+
|
|
407
|
+
sleep "$WAIT_POLL_SECONDS"
|
|
408
|
+
done
|
|
409
|
+
}
|
|
410
|
+
|
|
292
411
|
run_pr_flow() {
|
|
293
412
|
if ! command -v "$GH_BIN" >/dev/null 2>&1; then
|
|
294
413
|
echo "[agent-branch-finish] PR fallback requested but GitHub CLI not found: ${GH_BIN}" >&2
|
|
@@ -320,6 +439,11 @@ run_pr_flow() {
|
|
|
320
439
|
return 0
|
|
321
440
|
fi
|
|
322
441
|
|
|
442
|
+
if [[ "$WAIT_FOR_MERGE" -eq 1 ]]; then
|
|
443
|
+
wait_for_pr_merge
|
|
444
|
+
return $?
|
|
445
|
+
fi
|
|
446
|
+
|
|
323
447
|
auto_output=""
|
|
324
448
|
if auto_output="$("$GH_BIN" pr merge "$SOURCE_BRANCH" --squash --delete-branch --auto 2>&1)"; then
|
|
325
449
|
echo "[agent-branch-finish] PR auto-merge enabled; waiting for required checks/reviews." >&2
|
|
@@ -365,6 +489,10 @@ if [[ "$PUSH_ENABLED" -eq 1 ]]; then
|
|
|
365
489
|
if [[ -n "$pr_url" ]]; then
|
|
366
490
|
echo "[agent-branch-finish] PR: ${pr_url}" >&2
|
|
367
491
|
fi
|
|
492
|
+
if [[ "$WAIT_FOR_MERGE" -eq 1 ]]; then
|
|
493
|
+
echo "[agent-branch-finish] Merge did not complete within wait window; keeping branch open." >&2
|
|
494
|
+
exit 1
|
|
495
|
+
fi
|
|
368
496
|
echo "[agent-branch-finish] Merge pending review/check policy. Branch cleanup skipped for now." >&2
|
|
369
497
|
exit 0
|
|
370
498
|
fi
|
|
@@ -390,16 +518,21 @@ fi
|
|
|
390
518
|
if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
|
|
391
519
|
if [[ "$source_worktree" == "$repo_root" ]]; then
|
|
392
520
|
if is_clean_worktree "$source_worktree"; then
|
|
393
|
-
|
|
394
|
-
if
|
|
521
|
+
switched_to_base=0
|
|
522
|
+
if git -C "$source_worktree" checkout "$BASE_BRANCH" >/dev/null 2>&1; then
|
|
523
|
+
switched_to_base=1
|
|
524
|
+
else
|
|
525
|
+
git -C "$source_worktree" checkout --detach >/dev/null 2>&1 || true
|
|
526
|
+
fi
|
|
527
|
+
if [[ "$switched_to_base" -eq 1 && "$PUSH_ENABLED" -eq 1 ]] && git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/${BASE_BRANCH}"; then
|
|
395
528
|
git -C "$source_worktree" pull --ff-only origin "$BASE_BRANCH" >/dev/null 2>&1 || true
|
|
396
529
|
fi
|
|
397
530
|
fi
|
|
398
|
-
elif [[ "$source_worktree" == "$current_worktree" && "$source_worktree" == "${
|
|
531
|
+
elif [[ "$source_worktree" == "$current_worktree" && "$source_worktree" == "${agent_worktree_root}"/* ]]; then
|
|
399
532
|
git -C "$source_worktree" checkout --detach >/dev/null 2>&1 || true
|
|
400
533
|
fi
|
|
401
534
|
|
|
402
|
-
if [[ "$source_worktree" != "$current_worktree" && "$source_worktree" == "${
|
|
535
|
+
if [[ "$source_worktree" != "$current_worktree" && "$source_worktree" == "${agent_worktree_root}"/* ]]; then
|
|
403
536
|
git -C "$repo_root" worktree remove "$source_worktree" --force >/dev/null 2>&1 || true
|
|
404
537
|
fi
|
|
405
538
|
|
|
@@ -423,7 +556,7 @@ if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
|
|
|
423
556
|
fi
|
|
424
557
|
|
|
425
558
|
echo "[agent-branch-finish] Merged '${SOURCE_BRANCH}' into '${BASE_BRANCH}' via ${merge_status} flow and cleaned source branch/worktree."
|
|
426
|
-
if [[ "$source_worktree" == "$current_worktree" && "$source_worktree" == "${
|
|
559
|
+
if [[ "$source_worktree" == "$current_worktree" && "$source_worktree" == "${agent_worktree_root}"/* ]]; then
|
|
427
560
|
echo "[agent-branch-finish] Current worktree '${source_worktree}' still exists because it is the active shell cwd." >&2
|
|
428
561
|
echo "[agent-branch-finish] Leave this directory, then run: bash scripts/agent-worktree-prune.sh --base ${BASE_BRANCH} --delete-branches" >&2
|
|
429
562
|
fi
|
|
@@ -152,6 +152,36 @@ is_protected_branch_name() {
|
|
|
152
152
|
return 1
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
hydrate_local_helper_in_worktree() {
|
|
156
|
+
local repo="$1"
|
|
157
|
+
local worktree="$2"
|
|
158
|
+
local relative_path="$3"
|
|
159
|
+
local worktree_target="${worktree}/${relative_path}"
|
|
160
|
+
local source_path=""
|
|
161
|
+
|
|
162
|
+
if [[ -e "$worktree_target" ]]; then
|
|
163
|
+
return 0
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
if [[ -f "${repo}/${relative_path}" ]]; then
|
|
167
|
+
source_path="${repo}/${relative_path}"
|
|
168
|
+
elif [[ -f "${repo}/templates/${relative_path}" ]]; then
|
|
169
|
+
source_path="${repo}/templates/${relative_path}"
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
if [[ -z "$source_path" ]]; then
|
|
173
|
+
return 0
|
|
174
|
+
fi
|
|
175
|
+
|
|
176
|
+
mkdir -p "$(dirname "$worktree_target")"
|
|
177
|
+
cp "$source_path" "$worktree_target"
|
|
178
|
+
if [[ -x "$source_path" ]]; then
|
|
179
|
+
chmod +x "$worktree_target"
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
echo "[agent-branch-start] Hydrated local helper in worktree: ${relative_path}"
|
|
183
|
+
}
|
|
184
|
+
|
|
155
185
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
156
186
|
echo "[agent-branch-start] Not inside a git repository." >&2
|
|
157
187
|
exit 1
|
|
@@ -195,15 +225,17 @@ snapshot_name="$(resolve_active_codex_snapshot_name)"
|
|
|
195
225
|
snapshot_slug="$(sanitize_slug "$snapshot_name" "")"
|
|
196
226
|
timestamp="$(date +%Y%m%d-%H%M%S)"
|
|
197
227
|
if [[ -n "$snapshot_slug" ]]; then
|
|
198
|
-
|
|
228
|
+
branch_name_base="agent/${agent_slug}/${snapshot_slug}-${task_slug}"
|
|
199
229
|
else
|
|
200
|
-
|
|
230
|
+
branch_name_base="agent/${agent_slug}/${task_slug}"
|
|
201
231
|
fi
|
|
202
232
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
233
|
+
branch_name="$branch_name_base"
|
|
234
|
+
branch_suffix=2
|
|
235
|
+
while git show-ref --verify --quiet "refs/heads/${branch_name}"; do
|
|
236
|
+
branch_name="${branch_name_base}-${branch_suffix}"
|
|
237
|
+
branch_suffix=$((branch_suffix + 1))
|
|
238
|
+
done
|
|
207
239
|
|
|
208
240
|
if [[ "$WORKTREE_MODE" -eq 0 ]]; then
|
|
209
241
|
if [[ "$ALLOW_IN_PLACE" -ne 1 ]]; then
|
|
@@ -280,6 +312,8 @@ if [[ -n "$auto_transfer_stash_ref" ]]; then
|
|
|
280
312
|
fi
|
|
281
313
|
fi
|
|
282
314
|
|
|
315
|
+
hydrate_local_helper_in_worktree "$repo_root" "$worktree_path" "scripts/codex-agent.sh"
|
|
316
|
+
|
|
283
317
|
echo "[agent-branch-start] Created branch: ${branch_name}"
|
|
284
318
|
echo "[agent-branch-start] Worktree: ${worktree_path}"
|
|
285
319
|
echo "[agent-branch-start] Next steps:"
|
|
@@ -8,7 +8,8 @@ BASE_BRANCH_EXPLICIT=0
|
|
|
8
8
|
CODEX_BIN="${MUSAFETY_CODEX_BIN:-codex}"
|
|
9
9
|
AUTO_FINISH_RAW="${MUSAFETY_CODEX_AUTO_FINISH:-true}"
|
|
10
10
|
AUTO_REVIEW_ON_CONFLICT_RAW="${MUSAFETY_CODEX_AUTO_REVIEW_ON_CONFLICT:-true}"
|
|
11
|
-
AUTO_CLEANUP_RAW="${MUSAFETY_CODEX_AUTO_CLEANUP:-
|
|
11
|
+
AUTO_CLEANUP_RAW="${MUSAFETY_CODEX_AUTO_CLEANUP:-true}"
|
|
12
|
+
AUTO_WAIT_FOR_MERGE_RAW="${MUSAFETY_CODEX_WAIT_FOR_MERGE:-true}"
|
|
12
13
|
|
|
13
14
|
normalize_bool() {
|
|
14
15
|
local raw="${1:-}"
|
|
@@ -25,7 +26,8 @@ normalize_bool() {
|
|
|
25
26
|
|
|
26
27
|
AUTO_FINISH="$(normalize_bool "$AUTO_FINISH_RAW" "1")"
|
|
27
28
|
AUTO_REVIEW_ON_CONFLICT="$(normalize_bool "$AUTO_REVIEW_ON_CONFLICT_RAW" "1")"
|
|
28
|
-
AUTO_CLEANUP="$(normalize_bool "$AUTO_CLEANUP_RAW" "
|
|
29
|
+
AUTO_CLEANUP="$(normalize_bool "$AUTO_CLEANUP_RAW" "1")"
|
|
30
|
+
AUTO_WAIT_FOR_MERGE="$(normalize_bool "$AUTO_WAIT_FOR_MERGE_RAW" "1")"
|
|
29
31
|
|
|
30
32
|
if [[ -n "$BASE_BRANCH" ]]; then
|
|
31
33
|
BASE_BRANCH_EXPLICIT=1
|
|
@@ -74,6 +76,14 @@ while [[ $# -gt 0 ]]; do
|
|
|
74
76
|
AUTO_CLEANUP=0
|
|
75
77
|
shift
|
|
76
78
|
;;
|
|
79
|
+
--wait-for-merge)
|
|
80
|
+
AUTO_WAIT_FOR_MERGE=1
|
|
81
|
+
shift
|
|
82
|
+
;;
|
|
83
|
+
--no-wait-for-merge)
|
|
84
|
+
AUTO_WAIT_FOR_MERGE=0
|
|
85
|
+
shift
|
|
86
|
+
;;
|
|
77
87
|
--)
|
|
78
88
|
shift
|
|
79
89
|
break
|
|
@@ -306,6 +316,9 @@ run_finish_flow() {
|
|
|
306
316
|
if [[ "$AUTO_CLEANUP" -eq 1 ]]; then
|
|
307
317
|
finish_args+=(--cleanup)
|
|
308
318
|
fi
|
|
319
|
+
if [[ "$AUTO_WAIT_FOR_MERGE" -eq 1 ]]; then
|
|
320
|
+
finish_args+=(--wait-for-merge)
|
|
321
|
+
fi
|
|
309
322
|
|
|
310
323
|
if has_origin_remote; then
|
|
311
324
|
if command -v gh >/dev/null 2>&1 || command -v "${MUSAFETY_GH_BIN:-gh}" >/dev/null 2>&1; then
|
|
@@ -368,7 +381,11 @@ auto_finish_completed=0
|
|
|
368
381
|
worktree_branch="$(git -C "$worktree_path" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
369
382
|
|
|
370
383
|
if [[ "$AUTO_FINISH" -eq 1 && -n "$worktree_branch" && "$worktree_branch" != "HEAD" ]]; then
|
|
371
|
-
if [[ "$AUTO_CLEANUP" -eq 1 ]]; then
|
|
384
|
+
if [[ "$AUTO_WAIT_FOR_MERGE" -eq 1 && "$AUTO_CLEANUP" -eq 1 ]]; then
|
|
385
|
+
echo "[codex-agent] Auto-finish enabled: commit -> push/PR -> wait for merge -> cleanup."
|
|
386
|
+
elif [[ "$AUTO_WAIT_FOR_MERGE" -eq 1 ]]; then
|
|
387
|
+
echo "[codex-agent] Auto-finish enabled: commit -> push/PR -> wait for merge (keep branch/worktree)."
|
|
388
|
+
elif [[ "$AUTO_CLEANUP" -eq 1 ]]; then
|
|
372
389
|
echo "[codex-agent] Auto-finish enabled: commit -> push/PR -> merge -> cleanup."
|
|
373
390
|
else
|
|
374
391
|
echo "[codex-agent] Auto-finish enabled: commit -> push/PR -> merge (keep branch/worktree)."
|
|
@@ -401,7 +418,7 @@ if [[ -x "${repo_root}/scripts/agent-worktree-prune.sh" ]]; then
|
|
|
401
418
|
if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 ]]; then
|
|
402
419
|
prune_args+=(--base "$BASE_BRANCH")
|
|
403
420
|
fi
|
|
404
|
-
if [[ "$AUTO_CLEANUP" -eq 1 ]]; then
|
|
421
|
+
if [[ "$AUTO_CLEANUP" -eq 1 && "$auto_finish_completed" -eq 1 ]]; then
|
|
405
422
|
prune_args+=(--delete-branches --delete-remote-branches)
|
|
406
423
|
fi
|
|
407
424
|
if ! bash "${repo_root}/scripts/agent-worktree-prune.sh" "${prune_args[@]}"; then
|