@imdeadpool/guardex 5.0.1 → 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 +128 -298
- package/bin/multiagent-safety.js +738 -20
- package/package.json +2 -2
- package/templates/AGENTS.multiagent-safety.md +7 -1
- package/templates/codex/skills/guardex/SKILL.md +2 -0
- package/templates/githooks/pre-commit +22 -0
- package/templates/scripts/agent-branch-finish.sh +216 -33
- package/templates/scripts/agent-branch-start.sh +40 -6
- package/templates/scripts/agent-worktree-prune.sh +57 -14
- package/templates/scripts/codex-agent.sh +318 -2
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",
|
|
@@ -11,8 +11,14 @@
|
|
|
11
11
|
- If ownership is unclear or overlaps, stop that edit, post a blocker comment, and let the leader/integrator reassign scope.
|
|
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
|
-
- Agent completion
|
|
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 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
|
+
- 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`.
|
|
15
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.
|
|
16
22
|
|
|
17
23
|
1. Explicit ownership before edits
|
|
18
24
|
|
|
@@ -36,4 +36,6 @@ gx scan
|
|
|
36
36
|
- Keep agent work isolated (`agent/*` branches + lock claims).
|
|
37
37
|
- For every new user message/task, restart the full loop on a fresh agent branch/worktree.
|
|
38
38
|
- For one-command Codex sandbox startup, use `bash scripts/codex-agent.sh "<task>" "<agent-name>"`.
|
|
39
|
+
- `scripts/codex-agent.sh` auto-syncs the sandbox branch against base before each task and auto-finishes merge/PR flow after Codex exits.
|
|
40
|
+
- Auto-finish keeps the branch/worktree by default; remove merged branches explicitly with `gx cleanup` (or `gx cleanup --branch "<agent-branch>"`).
|
|
39
41
|
- Do not bypass protected branch safeguards unless explicitly required.
|
|
@@ -50,9 +50,31 @@ case "$codex_require_agent_branch" in
|
|
|
50
50
|
*) should_require_codex_agent_branch=1 ;;
|
|
51
51
|
esac
|
|
52
52
|
|
|
53
|
+
is_codex_managed_only_commit_on_protected=0
|
|
54
|
+
if [[ "$is_codex_session" == "1" && "$is_protected_branch" == "1" ]]; then
|
|
55
|
+
deleted_paths="$(git diff --cached --name-only --diff-filter=D)"
|
|
56
|
+
staged_paths="$(git diff --cached --name-only --diff-filter=ACMRTUXB)"
|
|
57
|
+
if [[ -z "$deleted_paths" && -n "$staged_paths" ]]; then
|
|
58
|
+
managed_only=1
|
|
59
|
+
while IFS= read -r staged_path; do
|
|
60
|
+
case "$staged_path" in
|
|
61
|
+
AGENTS.md|.gitignore) ;;
|
|
62
|
+
*) managed_only=0; break ;;
|
|
63
|
+
esac
|
|
64
|
+
done <<< "$staged_paths"
|
|
65
|
+
if [[ "$managed_only" == "1" ]]; then
|
|
66
|
+
is_codex_managed_only_commit_on_protected=1
|
|
67
|
+
fi
|
|
68
|
+
fi
|
|
69
|
+
fi
|
|
70
|
+
|
|
53
71
|
if [[ "$should_require_codex_agent_branch" == "1" && "${MUSAFETY_ALLOW_CODEX_ON_NON_AGENT:-0}" != "1" ]]; then
|
|
54
72
|
if [[ "$is_codex_session" == "1" && "$branch" != agent/* ]]; then
|
|
55
73
|
if [[ "$is_protected_branch" == "1" ]]; then
|
|
74
|
+
if [[ "$is_codex_managed_only_commit_on_protected" == "1" ]]; then
|
|
75
|
+
exit 0
|
|
76
|
+
fi
|
|
77
|
+
|
|
56
78
|
cat >&2 <<'MSG'
|
|
57
79
|
[guardex-preedit-guard] Codex edit/commit detected on a protected branch.
|
|
58
80
|
GuardeX requires Codex work to run from an isolated agent/* branch.
|
|
@@ -5,9 +5,49 @@ BASE_BRANCH=""
|
|
|
5
5
|
BASE_BRANCH_EXPLICIT=0
|
|
6
6
|
SOURCE_BRANCH=""
|
|
7
7
|
PUSH_ENABLED=1
|
|
8
|
-
DELETE_REMOTE_BRANCH=
|
|
8
|
+
DELETE_REMOTE_BRANCH=0
|
|
9
|
+
DELETE_REMOTE_BRANCH_EXPLICIT=0
|
|
9
10
|
MERGE_MODE="auto"
|
|
10
11
|
GH_BIN="${MUSAFETY_GH_BIN:-gh}"
|
|
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}"
|
|
16
|
+
|
|
17
|
+
normalize_bool() {
|
|
18
|
+
local raw="${1:-}"
|
|
19
|
+
local fallback="${2:-0}"
|
|
20
|
+
local lowered
|
|
21
|
+
lowered="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')"
|
|
22
|
+
case "$lowered" in
|
|
23
|
+
1|true|yes|on) printf '1' ;;
|
|
24
|
+
0|false|no|off) printf '0' ;;
|
|
25
|
+
'') printf '%s' "$fallback" ;;
|
|
26
|
+
*) printf '%s' "$fallback" ;;
|
|
27
|
+
esac
|
|
28
|
+
}
|
|
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
|
+
|
|
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")"
|
|
11
51
|
|
|
12
52
|
while [[ $# -gt 0 ]]; do
|
|
13
53
|
case "$1" in
|
|
@@ -26,8 +66,38 @@ while [[ $# -gt 0 ]]; do
|
|
|
26
66
|
;;
|
|
27
67
|
--keep-remote-branch)
|
|
28
68
|
DELETE_REMOTE_BRANCH=0
|
|
69
|
+
DELETE_REMOTE_BRANCH_EXPLICIT=1
|
|
70
|
+
shift
|
|
71
|
+
;;
|
|
72
|
+
--delete-remote-branch)
|
|
73
|
+
DELETE_REMOTE_BRANCH=1
|
|
74
|
+
DELETE_REMOTE_BRANCH_EXPLICIT=1
|
|
75
|
+
shift
|
|
76
|
+
;;
|
|
77
|
+
--cleanup)
|
|
78
|
+
CLEANUP_AFTER_MERGE=1
|
|
79
|
+
shift
|
|
80
|
+
;;
|
|
81
|
+
--no-cleanup)
|
|
82
|
+
CLEANUP_AFTER_MERGE=0
|
|
83
|
+
shift
|
|
84
|
+
;;
|
|
85
|
+
--wait-for-merge)
|
|
86
|
+
WAIT_FOR_MERGE=1
|
|
87
|
+
shift
|
|
88
|
+
;;
|
|
89
|
+
--no-wait-for-merge)
|
|
90
|
+
WAIT_FOR_MERGE=0
|
|
29
91
|
shift
|
|
30
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
|
+
;;
|
|
31
101
|
--mode)
|
|
32
102
|
MERGE_MODE="${2:-auto}"
|
|
33
103
|
shift 2
|
|
@@ -42,12 +112,16 @@ while [[ $# -gt 0 ]]; do
|
|
|
42
112
|
;;
|
|
43
113
|
*)
|
|
44
114
|
echo "[agent-branch-finish] Unknown argument: $1" >&2
|
|
45
|
-
echo "Usage: $0 [--base <branch>] [--branch <branch>] [--no-push] [--keep-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
|
|
46
116
|
exit 1
|
|
47
117
|
;;
|
|
48
118
|
esac
|
|
49
119
|
done
|
|
50
120
|
|
|
121
|
+
if [[ "$CLEANUP_AFTER_MERGE" -eq 1 && "$DELETE_REMOTE_BRANCH_EXPLICIT" -eq 0 ]]; then
|
|
122
|
+
DELETE_REMOTE_BRANCH=1
|
|
123
|
+
fi
|
|
124
|
+
|
|
51
125
|
case "$MERGE_MODE" in
|
|
52
126
|
auto|direct|pr) ;;
|
|
53
127
|
*)
|
|
@@ -63,6 +137,14 @@ fi
|
|
|
63
137
|
|
|
64
138
|
repo_root="$(git rev-parse --show-toplevel)"
|
|
65
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"
|
|
66
148
|
|
|
67
149
|
if [[ -z "$SOURCE_BRANCH" ]]; then
|
|
68
150
|
SOURCE_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
|
@@ -136,7 +218,7 @@ created_source_probe=0
|
|
|
136
218
|
source_probe_path=""
|
|
137
219
|
|
|
138
220
|
if [[ -z "$source_worktree" ]]; then
|
|
139
|
-
source_probe_path="${
|
|
221
|
+
source_probe_path="${agent_worktree_root}/__source-probe-${SOURCE_BRANCH//\//__}-$(date +%Y%m%d-%H%M%S)"
|
|
140
222
|
mkdir -p "$(dirname "$source_probe_path")"
|
|
141
223
|
git -C "$repo_root" worktree add "$source_probe_path" "$SOURCE_BRANCH" >/dev/null
|
|
142
224
|
source_worktree="$source_probe_path"
|
|
@@ -194,7 +276,7 @@ if [[ "$should_require_sync" -eq 1 ]] && git -C "$repo_root" show-ref --verify -
|
|
|
194
276
|
fi
|
|
195
277
|
fi
|
|
196
278
|
|
|
197
|
-
integration_worktree="${
|
|
279
|
+
integration_worktree="${agent_worktree_root}/__integrate-${BASE_BRANCH//\//__}-$(date +%Y%m%d-%H%M%S)"
|
|
198
280
|
integration_branch="__agent_integrate_${BASE_BRANCH//\//_}_$(date +%Y%m%d_%H%M%S)"
|
|
199
281
|
mkdir -p "$(dirname "$integration_worktree")"
|
|
200
282
|
|
|
@@ -254,6 +336,78 @@ is_local_branch_delete_error() {
|
|
|
254
336
|
return 1
|
|
255
337
|
}
|
|
256
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
|
+
|
|
257
411
|
run_pr_flow() {
|
|
258
412
|
if ! command -v "$GH_BIN" >/dev/null 2>&1; then
|
|
259
413
|
echo "[agent-branch-finish] PR fallback requested but GitHub CLI not found: ${GH_BIN}" >&2
|
|
@@ -285,6 +439,11 @@ run_pr_flow() {
|
|
|
285
439
|
return 0
|
|
286
440
|
fi
|
|
287
441
|
|
|
442
|
+
if [[ "$WAIT_FOR_MERGE" -eq 1 ]]; then
|
|
443
|
+
wait_for_pr_merge
|
|
444
|
+
return $?
|
|
445
|
+
fi
|
|
446
|
+
|
|
288
447
|
auto_output=""
|
|
289
448
|
if auto_output="$("$GH_BIN" pr merge "$SOURCE_BRANCH" --squash --delete-branch --auto 2>&1)"; then
|
|
290
449
|
echo "[agent-branch-finish] PR auto-merge enabled; waiting for required checks/reviews." >&2
|
|
@@ -330,6 +489,10 @@ if [[ "$PUSH_ENABLED" -eq 1 ]]; then
|
|
|
330
489
|
if [[ -n "$pr_url" ]]; then
|
|
331
490
|
echo "[agent-branch-finish] PR: ${pr_url}" >&2
|
|
332
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
|
|
333
496
|
echo "[agent-branch-finish] Merge pending review/check policy. Branch cleanup skipped for now." >&2
|
|
334
497
|
exit 0
|
|
335
498
|
fi
|
|
@@ -347,43 +510,63 @@ if [[ -x "${repo_root}/scripts/agent-file-locks.py" ]]; then
|
|
|
347
510
|
python3 "${repo_root}/scripts/agent-file-locks.py" release --branch "$SOURCE_BRANCH" >/dev/null 2>&1 || true
|
|
348
511
|
fi
|
|
349
512
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
513
|
+
base_worktree="$(get_worktree_for_branch "$BASE_BRANCH")"
|
|
514
|
+
if [[ -n "$base_worktree" ]] && is_clean_worktree "$base_worktree" && [[ "$PUSH_ENABLED" -eq 1 ]]; then
|
|
515
|
+
git -C "$base_worktree" pull --ff-only origin "$BASE_BRANCH" >/dev/null 2>&1 || true
|
|
516
|
+
fi
|
|
517
|
+
|
|
518
|
+
if [[ "$CLEANUP_AFTER_MERGE" -eq 1 ]]; then
|
|
519
|
+
if [[ "$source_worktree" == "$repo_root" ]]; then
|
|
520
|
+
if is_clean_worktree "$source_worktree"; then
|
|
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
|
|
528
|
+
git -C "$source_worktree" pull --ff-only origin "$BASE_BRANCH" >/dev/null 2>&1 || true
|
|
529
|
+
fi
|
|
355
530
|
fi
|
|
531
|
+
elif [[ "$source_worktree" == "$current_worktree" && "$source_worktree" == "${agent_worktree_root}"/* ]]; then
|
|
532
|
+
git -C "$source_worktree" checkout --detach >/dev/null 2>&1 || true
|
|
356
533
|
fi
|
|
357
|
-
elif [[ "$source_worktree" == "$current_worktree" && "$source_worktree" == "${repo_root}/.omx/agent-worktrees"/* ]]; then
|
|
358
|
-
git -C "$source_worktree" checkout --detach >/dev/null 2>&1 || true
|
|
359
|
-
fi
|
|
360
534
|
|
|
361
|
-
if [[ "$source_worktree" != "$current_worktree" && "$source_worktree" == "${
|
|
362
|
-
|
|
363
|
-
fi
|
|
535
|
+
if [[ "$source_worktree" != "$current_worktree" && "$source_worktree" == "${agent_worktree_root}"/* ]]; then
|
|
536
|
+
git -C "$repo_root" worktree remove "$source_worktree" --force >/dev/null 2>&1 || true
|
|
537
|
+
fi
|
|
364
538
|
|
|
365
|
-
git -C "$repo_root" branch -d "$SOURCE_BRANCH"
|
|
539
|
+
git -C "$repo_root" branch -d "$SOURCE_BRANCH"
|
|
366
540
|
|
|
367
|
-
if [[ "$PUSH_ENABLED" -eq 1 && "$DELETE_REMOTE_BRANCH" -eq 1 ]]; then
|
|
368
|
-
|
|
369
|
-
|
|
541
|
+
if [[ "$PUSH_ENABLED" -eq 1 && "$DELETE_REMOTE_BRANCH" -eq 1 ]]; then
|
|
542
|
+
if git -C "$repo_root" ls-remote --exit-code --heads origin "$SOURCE_BRANCH" >/dev/null 2>&1; then
|
|
543
|
+
git -C "$repo_root" push origin --delete "$SOURCE_BRANCH"
|
|
544
|
+
fi
|
|
370
545
|
fi
|
|
371
|
-
fi
|
|
372
546
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
547
|
+
if [[ -x "${repo_root}/scripts/agent-worktree-prune.sh" ]]; then
|
|
548
|
+
prune_args=(--base "$BASE_BRANCH" --delete-branches)
|
|
549
|
+
if [[ "$DELETE_REMOTE_BRANCH" -eq 1 ]]; then
|
|
550
|
+
prune_args+=(--delete-remote-branches)
|
|
551
|
+
fi
|
|
552
|
+
if ! bash "${repo_root}/scripts/agent-worktree-prune.sh" "${prune_args[@]}"; then
|
|
553
|
+
echo "[agent-branch-finish] Warning: automatic worktree prune failed." >&2
|
|
554
|
+
echo "[agent-branch-finish] You can run manual cleanup: bash scripts/agent-worktree-prune.sh --base ${BASE_BRANCH} --delete-branches" >&2
|
|
555
|
+
fi
|
|
556
|
+
fi
|
|
377
557
|
|
|
378
|
-
|
|
379
|
-
if
|
|
380
|
-
echo "[agent-branch-finish]
|
|
381
|
-
echo "[agent-branch-finish]
|
|
558
|
+
echo "[agent-branch-finish] Merged '${SOURCE_BRANCH}' into '${BASE_BRANCH}' via ${merge_status} flow and cleaned source branch/worktree."
|
|
559
|
+
if [[ "$source_worktree" == "$current_worktree" && "$source_worktree" == "${agent_worktree_root}"/* ]]; then
|
|
560
|
+
echo "[agent-branch-finish] Current worktree '${source_worktree}' still exists because it is the active shell cwd." >&2
|
|
561
|
+
echo "[agent-branch-finish] Leave this directory, then run: bash scripts/agent-worktree-prune.sh --base ${BASE_BRANCH} --delete-branches" >&2
|
|
562
|
+
fi
|
|
563
|
+
else
|
|
564
|
+
if [[ -x "${repo_root}/scripts/agent-worktree-prune.sh" ]]; then
|
|
565
|
+
if ! bash "${repo_root}/scripts/agent-worktree-prune.sh" --base "$BASE_BRANCH"; then
|
|
566
|
+
echo "[agent-branch-finish] Warning: temporary worktree prune failed." >&2
|
|
567
|
+
fi
|
|
382
568
|
fi
|
|
383
|
-
fi
|
|
384
569
|
|
|
385
|
-
echo "[agent-branch-finish] Merged '${SOURCE_BRANCH}' into '${BASE_BRANCH}' via ${merge_status} flow and
|
|
386
|
-
|
|
387
|
-
echo "[agent-branch-finish] Current worktree '${source_worktree}' still exists because it is the active shell cwd." >&2
|
|
388
|
-
echo "[agent-branch-finish] Leave this directory, then run: bash scripts/agent-worktree-prune.sh --base ${BASE_BRANCH}" >&2
|
|
570
|
+
echo "[agent-branch-finish] Merged '${SOURCE_BRANCH}' into '${BASE_BRANCH}' via ${merge_status} flow and kept source branch/worktree."
|
|
571
|
+
echo "[agent-branch-finish] Cleanup later with: bash scripts/agent-worktree-prune.sh --base ${BASE_BRANCH} --delete-branches --delete-remote-branches"
|
|
389
572
|
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:"
|
|
@@ -5,6 +5,9 @@ BASE_BRANCH="${MUSAFETY_BASE_BRANCH:-}"
|
|
|
5
5
|
BASE_BRANCH_EXPLICIT=0
|
|
6
6
|
DRY_RUN=0
|
|
7
7
|
FORCE_DIRTY=0
|
|
8
|
+
DELETE_BRANCHES=0
|
|
9
|
+
DELETE_REMOTE_BRANCHES=0
|
|
10
|
+
TARGET_BRANCH=""
|
|
8
11
|
|
|
9
12
|
if [[ -n "$BASE_BRANCH" ]]; then
|
|
10
13
|
BASE_BRANCH_EXPLICIT=1
|
|
@@ -25,9 +28,21 @@ while [[ $# -gt 0 ]]; do
|
|
|
25
28
|
FORCE_DIRTY=1
|
|
26
29
|
shift
|
|
27
30
|
;;
|
|
31
|
+
--delete-branches)
|
|
32
|
+
DELETE_BRANCHES=1
|
|
33
|
+
shift
|
|
34
|
+
;;
|
|
35
|
+
--delete-remote-branches)
|
|
36
|
+
DELETE_REMOTE_BRANCHES=1
|
|
37
|
+
shift
|
|
38
|
+
;;
|
|
39
|
+
--branch)
|
|
40
|
+
TARGET_BRANCH="${2:-}"
|
|
41
|
+
shift 2
|
|
42
|
+
;;
|
|
28
43
|
*)
|
|
29
44
|
echo "[agent-worktree-prune] Unknown argument: $1" >&2
|
|
30
|
-
echo "Usage: $0 [--base <branch>] [--dry-run] [--force-dirty]" >&2
|
|
45
|
+
echo "Usage: $0 [--base <branch>] [--dry-run] [--force-dirty] [--delete-branches] [--delete-remote-branches] [--branch <agent/...>]" >&2
|
|
31
46
|
exit 1
|
|
32
47
|
;;
|
|
33
48
|
esac
|
|
@@ -73,6 +88,11 @@ if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -z "$BASE_BRANCH" ]]; then
|
|
|
73
88
|
exit 1
|
|
74
89
|
fi
|
|
75
90
|
|
|
91
|
+
if [[ -n "$TARGET_BRANCH" && "$TARGET_BRANCH" != agent/* ]]; then
|
|
92
|
+
echo "[agent-worktree-prune] --branch must reference an agent/* branch: ${TARGET_BRANCH}" >&2
|
|
93
|
+
exit 1
|
|
94
|
+
fi
|
|
95
|
+
|
|
76
96
|
if [[ "$BASE_BRANCH_EXPLICIT" -eq 0 ]]; then
|
|
77
97
|
BASE_BRANCH="$(resolve_base_branch)"
|
|
78
98
|
fi
|
|
@@ -124,6 +144,10 @@ process_entry() {
|
|
|
124
144
|
branch="${branch_ref#refs/heads/}"
|
|
125
145
|
fi
|
|
126
146
|
|
|
147
|
+
if [[ -n "$TARGET_BRANCH" && "$branch" != "$TARGET_BRANCH" ]]; then
|
|
148
|
+
return
|
|
149
|
+
fi
|
|
150
|
+
|
|
127
151
|
if [[ "$wt" == "$current_pwd" ]]; then
|
|
128
152
|
skipped_active=$((skipped_active + 1))
|
|
129
153
|
echo "[agent-worktree-prune] Skipping active cwd worktree: ${wt}"
|
|
@@ -138,7 +162,9 @@ process_entry() {
|
|
|
138
162
|
remove_reason="missing-branch"
|
|
139
163
|
elif [[ "$branch" == agent/* ]]; then
|
|
140
164
|
if git -C "$repo_root" merge-base --is-ancestor "$branch" "$BASE_BRANCH"; then
|
|
141
|
-
|
|
165
|
+
if [[ "$DELETE_BRANCHES" -eq 1 ]]; then
|
|
166
|
+
remove_reason="merged-agent-branch"
|
|
167
|
+
fi
|
|
142
168
|
fi
|
|
143
169
|
elif [[ "$branch" == __agent_integrate_* || "$branch" == __source-probe-* ]]; then
|
|
144
170
|
remove_reason="temporary-worktree"
|
|
@@ -163,10 +189,16 @@ process_entry() {
|
|
|
163
189
|
fi
|
|
164
190
|
|
|
165
191
|
if git -C "$repo_root" show-ref --verify --quiet "refs/heads/${branch}" && ! branch_has_worktree "$branch"; then
|
|
166
|
-
if [[ "$branch" == agent/* ]]; then
|
|
192
|
+
if [[ "$branch" == agent/* && "$DELETE_BRANCHES" -eq 1 ]]; then
|
|
167
193
|
if run_cmd git -C "$repo_root" branch -d "$branch" >/dev/null 2>&1; then
|
|
168
194
|
removed_branches=$((removed_branches + 1))
|
|
169
195
|
echo "[agent-worktree-prune] Deleted merged branch: ${branch}"
|
|
196
|
+
if [[ "$DELETE_REMOTE_BRANCHES" -eq 1 ]]; then
|
|
197
|
+
if git -C "$repo_root" ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then
|
|
198
|
+
run_cmd git -C "$repo_root" push origin --delete "$branch" >/dev/null 2>&1 || true
|
|
199
|
+
echo "[agent-worktree-prune] Deleted merged remote branch: ${branch}"
|
|
200
|
+
fi
|
|
201
|
+
fi
|
|
170
202
|
fi
|
|
171
203
|
elif [[ "$branch" == __agent_integrate_* || "$branch" == __source-probe-* ]]; then
|
|
172
204
|
run_cmd git -C "$repo_root" branch -D "$branch" >/dev/null 2>&1 || true
|
|
@@ -199,18 +231,29 @@ done < <(git -C "$repo_root" worktree list --porcelain)
|
|
|
199
231
|
|
|
200
232
|
process_entry "$current_wt" "$current_branch_ref"
|
|
201
233
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if git -C "$repo_root" merge-base --is-ancestor "$branch" "$BASE_BRANCH"; then
|
|
208
|
-
if run_cmd git -C "$repo_root" branch -d "$branch" >/dev/null 2>&1; then
|
|
209
|
-
removed_branches=$((removed_branches + 1))
|
|
210
|
-
echo "[agent-worktree-prune] Deleted stale merged branch: ${branch}"
|
|
234
|
+
if [[ "$DELETE_BRANCHES" -eq 1 ]]; then
|
|
235
|
+
while IFS= read -r branch; do
|
|
236
|
+
[[ -z "$branch" ]] && continue
|
|
237
|
+
if [[ -n "$TARGET_BRANCH" && "$branch" != "$TARGET_BRANCH" ]]; then
|
|
238
|
+
continue
|
|
211
239
|
fi
|
|
212
|
-
|
|
213
|
-
|
|
240
|
+
if branch_has_worktree "$branch"; then
|
|
241
|
+
continue
|
|
242
|
+
fi
|
|
243
|
+
if git -C "$repo_root" merge-base --is-ancestor "$branch" "$BASE_BRANCH"; then
|
|
244
|
+
if run_cmd git -C "$repo_root" branch -d "$branch" >/dev/null 2>&1; then
|
|
245
|
+
removed_branches=$((removed_branches + 1))
|
|
246
|
+
echo "[agent-worktree-prune] Deleted stale merged branch: ${branch}"
|
|
247
|
+
if [[ "$DELETE_REMOTE_BRANCHES" -eq 1 ]]; then
|
|
248
|
+
if git -C "$repo_root" ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then
|
|
249
|
+
run_cmd git -C "$repo_root" push origin --delete "$branch" >/dev/null 2>&1 || true
|
|
250
|
+
echo "[agent-worktree-prune] Deleted stale merged remote branch: ${branch}"
|
|
251
|
+
fi
|
|
252
|
+
fi
|
|
253
|
+
fi
|
|
254
|
+
fi
|
|
255
|
+
done < <(git -C "$repo_root" for-each-ref --format='%(refname:short)' refs/heads/agent)
|
|
256
|
+
fi
|
|
214
257
|
|
|
215
258
|
run_cmd git -C "$repo_root" worktree prune
|
|
216
259
|
|