@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.
@@ -6,6 +6,28 @@ AGENT_NAME="${MUSAFETY_AGENT_NAME:-agent}"
6
6
  BASE_BRANCH="${MUSAFETY_BASE_BRANCH:-}"
7
7
  BASE_BRANCH_EXPLICIT=0
8
8
  CODEX_BIN="${MUSAFETY_CODEX_BIN:-codex}"
9
+ AUTO_FINISH_RAW="${MUSAFETY_CODEX_AUTO_FINISH:-true}"
10
+ AUTO_REVIEW_ON_CONFLICT_RAW="${MUSAFETY_CODEX_AUTO_REVIEW_ON_CONFLICT:-true}"
11
+ AUTO_CLEANUP_RAW="${MUSAFETY_CODEX_AUTO_CLEANUP:-true}"
12
+ AUTO_WAIT_FOR_MERGE_RAW="${MUSAFETY_CODEX_WAIT_FOR_MERGE:-true}"
13
+
14
+ normalize_bool() {
15
+ local raw="${1:-}"
16
+ local fallback="${2:-0}"
17
+ local lowered
18
+ lowered="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')"
19
+ case "$lowered" in
20
+ 1|true|yes|on) printf '1' ;;
21
+ 0|false|no|off) printf '0' ;;
22
+ '') printf '%s' "$fallback" ;;
23
+ *) printf '%s' "$fallback" ;;
24
+ esac
25
+ }
26
+
27
+ AUTO_FINISH="$(normalize_bool "$AUTO_FINISH_RAW" "1")"
28
+ AUTO_REVIEW_ON_CONFLICT="$(normalize_bool "$AUTO_REVIEW_ON_CONFLICT_RAW" "1")"
29
+ AUTO_CLEANUP="$(normalize_bool "$AUTO_CLEANUP_RAW" "1")"
30
+ AUTO_WAIT_FOR_MERGE="$(normalize_bool "$AUTO_WAIT_FOR_MERGE_RAW" "1")"
9
31
 
10
32
  if [[ -n "$BASE_BRANCH" ]]; then
11
33
  BASE_BRANCH_EXPLICIT=1
@@ -30,6 +52,38 @@ while [[ $# -gt 0 ]]; do
30
52
  CODEX_BIN="${2:-$CODEX_BIN}"
31
53
  shift 2
32
54
  ;;
55
+ --auto-finish)
56
+ AUTO_FINISH=1
57
+ shift
58
+ ;;
59
+ --no-auto-finish)
60
+ AUTO_FINISH=0
61
+ shift
62
+ ;;
63
+ --auto-review-on-conflict)
64
+ AUTO_REVIEW_ON_CONFLICT=1
65
+ shift
66
+ ;;
67
+ --no-auto-review-on-conflict)
68
+ AUTO_REVIEW_ON_CONFLICT=0
69
+ shift
70
+ ;;
71
+ --cleanup)
72
+ AUTO_CLEANUP=1
73
+ shift
74
+ ;;
75
+ --no-cleanup)
76
+ AUTO_CLEANUP=0
77
+ shift
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
+ ;;
33
87
  --)
34
88
  shift
35
89
  break
@@ -95,6 +149,224 @@ if [[ ! -d "$worktree_path" ]]; then
95
149
  exit 1
96
150
  fi
97
151
 
152
+ has_origin_remote() {
153
+ git -C "$repo_root" remote get-url origin >/dev/null 2>&1
154
+ }
155
+
156
+ resolve_worktree_base_branch() {
157
+ local wt="$1"
158
+ if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -n "$BASE_BRANCH" ]]; then
159
+ printf '%s' "$BASE_BRANCH"
160
+ return 0
161
+ fi
162
+
163
+ local branch
164
+ branch="$(git -C "$wt" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
165
+ if [[ -z "$branch" || "$branch" == "HEAD" ]]; then
166
+ return 0
167
+ fi
168
+
169
+ local stored_base
170
+ stored_base="$(git -C "$repo_root" config --get "branch.${branch}.musafetyBase" || true)"
171
+ if [[ -n "$stored_base" ]]; then
172
+ printf '%s' "$stored_base"
173
+ return 0
174
+ fi
175
+
176
+ local configured_base
177
+ configured_base="$(git -C "$repo_root" config --get multiagent.baseBranch || true)"
178
+ if [[ -n "$configured_base" ]]; then
179
+ printf '%s' "$configured_base"
180
+ fi
181
+ }
182
+
183
+ sync_worktree_with_base() {
184
+ local wt="$1"
185
+ if ! has_origin_remote; then
186
+ return 0
187
+ fi
188
+
189
+ local base_branch
190
+ base_branch="$(resolve_worktree_base_branch "$wt")"
191
+ if [[ -z "$base_branch" ]]; then
192
+ return 0
193
+ fi
194
+
195
+ if ! git -C "$wt" fetch origin "$base_branch" --quiet; then
196
+ echo "[codex-agent] Warning: could not fetch origin/${base_branch} before task start." >&2
197
+ return 0
198
+ fi
199
+
200
+ if ! git -C "$wt" show-ref --verify --quiet "refs/remotes/origin/${base_branch}"; then
201
+ return 0
202
+ fi
203
+
204
+ local behind_count
205
+ behind_count="$(git -C "$wt" rev-list --left-right --count "HEAD...origin/${base_branch}" 2>/dev/null | awk '{print $2}')"
206
+ behind_count="${behind_count:-0}"
207
+ if [[ "$behind_count" -le 0 ]]; then
208
+ return 0
209
+ fi
210
+
211
+ local branch
212
+ branch="$(git -C "$wt" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
213
+ echo "[codex-agent] Task sync: '${branch}' is behind origin/${base_branch} by ${behind_count} commit(s). Rebasing before launch..."
214
+ if ! git -C "$wt" rebase "origin/${base_branch}"; then
215
+ echo "[codex-agent] Task sync failed. Resolve and continue in sandbox:" >&2
216
+ echo " git -C \"$wt\" rebase --continue" >&2
217
+ echo " # or abort" >&2
218
+ echo " git -C \"$wt\" rebase --abort" >&2
219
+ return 1
220
+ fi
221
+ echo "[codex-agent] Task sync complete."
222
+ return 0
223
+ }
224
+
225
+ worktree_has_changes() {
226
+ local wt="$1"
227
+ if ! git -C "$wt" diff --quiet -- . ":(exclude).omx/state/agent-file-locks.json"; then
228
+ return 0
229
+ fi
230
+ if ! git -C "$wt" diff --cached --quiet -- . ":(exclude).omx/state/agent-file-locks.json"; then
231
+ return 0
232
+ fi
233
+ if [[ -n "$(git -C "$wt" ls-files --others --exclude-standard)" ]]; then
234
+ return 0
235
+ fi
236
+ return 1
237
+ }
238
+
239
+ claim_changed_files() {
240
+ local wt="$1"
241
+ local branch="$2"
242
+ local lock_script="${repo_root}/scripts/agent-file-locks.py"
243
+
244
+ if [[ ! -x "$lock_script" ]]; then
245
+ return 0
246
+ fi
247
+
248
+ local changed_raw deleted_raw
249
+ changed_raw="$({
250
+ git -C "$wt" diff --name-only -- . ":(exclude).omx/state/agent-file-locks.json";
251
+ git -C "$wt" diff --cached --name-only -- . ":(exclude).omx/state/agent-file-locks.json";
252
+ git -C "$wt" ls-files --others --exclude-standard;
253
+ } | sed '/^$/d' | sort -u)"
254
+
255
+ if [[ -n "$changed_raw" ]]; then
256
+ mapfile -t changed_files < <(printf '%s\n' "$changed_raw")
257
+ python3 "$lock_script" claim --branch "$branch" "${changed_files[@]}" >/dev/null 2>&1 || true
258
+ fi
259
+
260
+ deleted_raw="$({
261
+ git -C "$wt" diff --name-only --diff-filter=D -- . ":(exclude).omx/state/agent-file-locks.json";
262
+ git -C "$wt" diff --cached --name-only --diff-filter=D -- . ":(exclude).omx/state/agent-file-locks.json";
263
+ } | sed '/^$/d' | sort -u)"
264
+
265
+ if [[ -n "$deleted_raw" ]]; then
266
+ mapfile -t deleted_files < <(printf '%s\n' "$deleted_raw")
267
+ python3 "$lock_script" allow-delete --branch "$branch" "${deleted_files[@]}" >/dev/null 2>&1 || true
268
+ fi
269
+ }
270
+
271
+ auto_commit_worktree_changes() {
272
+ local wt="$1"
273
+ local branch="$2"
274
+
275
+ if ! worktree_has_changes "$wt"; then
276
+ return 0
277
+ fi
278
+
279
+ claim_changed_files "$wt" "$branch"
280
+ git -C "$wt" add -A
281
+
282
+ if git -C "$wt" diff --cached --quiet -- . ":(exclude).omx/state/agent-file-locks.json"; then
283
+ return 0
284
+ fi
285
+
286
+ local default_message="Auto-finish: ${TASK_NAME}"
287
+ local commit_message="${MUSAFETY_CODEX_AUTO_COMMIT_MESSAGE:-$default_message}"
288
+
289
+ if ! git -C "$wt" commit -m "$commit_message" >/dev/null 2>&1; then
290
+ echo "[codex-agent] Auto-commit failed in sandbox. Keeping branch for manual review: $branch" >&2
291
+ return 1
292
+ fi
293
+
294
+ echo "[codex-agent] Auto-committed sandbox changes on '${branch}'."
295
+ return 0
296
+ }
297
+
298
+ looks_like_conflict_failure() {
299
+ local output="$1"
300
+ if grep -qiE 'preflight conflict detected|merge conflict detected|auto-sync failed while rebasing|rebase --continue|rebase --abort' <<< "$output"; then
301
+ return 0
302
+ fi
303
+ return 1
304
+ }
305
+
306
+ run_finish_flow() {
307
+ local wt="$1"
308
+ local branch="$2"
309
+ local finish_output=""
310
+ local -a finish_args
311
+
312
+ finish_args=(--branch "$branch")
313
+ if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 ]]; then
314
+ finish_args+=(--base "$BASE_BRANCH")
315
+ fi
316
+ if [[ "$AUTO_CLEANUP" -eq 1 ]]; then
317
+ finish_args+=(--cleanup)
318
+ fi
319
+ if [[ "$AUTO_WAIT_FOR_MERGE" -eq 1 ]]; then
320
+ finish_args+=(--wait-for-merge)
321
+ fi
322
+
323
+ if has_origin_remote; then
324
+ if command -v gh >/dev/null 2>&1 || command -v "${MUSAFETY_GH_BIN:-gh}" >/dev/null 2>&1; then
325
+ finish_args+=(--via-pr)
326
+ fi
327
+ else
328
+ echo "[codex-agent] No origin remote detected; skipping auto-finish merge/PR pipeline." >&2
329
+ return 2
330
+ fi
331
+
332
+ if finish_output="$(bash "${repo_root}/scripts/agent-branch-finish.sh" "${finish_args[@]}" 2>&1)"; then
333
+ printf '%s\n' "$finish_output"
334
+ return 0
335
+ fi
336
+
337
+ printf '%s\n' "$finish_output" >&2
338
+
339
+ if [[ "$AUTO_REVIEW_ON_CONFLICT" -eq 1 ]] && looks_like_conflict_failure "$finish_output"; then
340
+ echo "[codex-agent] Auto-finish hit conflicts. Launching Codex conflict-review pass in sandbox..." >&2
341
+ local review_prompt
342
+ review_prompt="Resolve git conflicts for branch ${branch} against ${BASE_BRANCH:-base branch}, then commit the resolution in this sandbox worktree and exit."
343
+
344
+ (
345
+ cd "$wt"
346
+ set +e
347
+ "$CODEX_BIN" "$review_prompt"
348
+ review_exit="$?"
349
+ set -e
350
+ if [[ "$review_exit" -ne 0 ]]; then
351
+ echo "[codex-agent] Conflict-review Codex pass exited with status ${review_exit}." >&2
352
+ fi
353
+ )
354
+
355
+ if finish_output="$(bash "${repo_root}/scripts/agent-branch-finish.sh" "${finish_args[@]}" 2>&1)"; then
356
+ printf '%s\n' "$finish_output"
357
+ return 0
358
+ fi
359
+
360
+ printf '%s\n' "$finish_output" >&2
361
+ fi
362
+
363
+ return 1
364
+ }
365
+
366
+ if ! sync_worktree_with_base "$worktree_path"; then
367
+ exit 1
368
+ fi
369
+
98
370
  echo "[codex-agent] Launching ${CODEX_BIN} in sandbox: $worktree_path"
99
371
  cd "$worktree_path"
100
372
  set +e
@@ -103,6 +375,42 @@ codex_exit="$?"
103
375
  set -e
104
376
 
105
377
  cd "$repo_root"
378
+ final_exit="$codex_exit"
379
+ auto_finish_completed=0
380
+
381
+ worktree_branch="$(git -C "$worktree_path" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
382
+
383
+ if [[ "$AUTO_FINISH" -eq 1 && -n "$worktree_branch" && "$worktree_branch" != "HEAD" ]]; 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
389
+ echo "[codex-agent] Auto-finish enabled: commit -> push/PR -> merge -> cleanup."
390
+ else
391
+ echo "[codex-agent] Auto-finish enabled: commit -> push/PR -> merge (keep branch/worktree)."
392
+ fi
393
+ if auto_commit_worktree_changes "$worktree_path" "$worktree_branch"; then
394
+ if run_finish_flow "$worktree_path" "$worktree_branch"; then
395
+ auto_finish_completed=1
396
+ echo "[codex-agent] Auto-finish completed for '${worktree_branch}'."
397
+ else
398
+ finish_status="$?"
399
+ if [[ "$finish_status" -eq 2 ]]; then
400
+ echo "[codex-agent] Auto-finish skipped for '${worktree_branch}' (no mergeable remote context)." >&2
401
+ else
402
+ echo "[codex-agent] Auto-finish did not complete; keeping sandbox for manual review: $worktree_path" >&2
403
+ if [[ "$final_exit" -eq 0 ]]; then
404
+ final_exit=1
405
+ fi
406
+ fi
407
+ fi
408
+ else
409
+ if [[ "$final_exit" -eq 0 ]]; then
410
+ final_exit=1
411
+ fi
412
+ fi
413
+ fi
106
414
 
107
415
  if [[ -x "${repo_root}/scripts/agent-worktree-prune.sh" ]]; then
108
416
  echo "[codex-agent] Session ended (exit=${codex_exit}). Running worktree cleanup..."
@@ -110,6 +418,9 @@ if [[ -x "${repo_root}/scripts/agent-worktree-prune.sh" ]]; then
110
418
  if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 ]]; then
111
419
  prune_args+=(--base "$BASE_BRANCH")
112
420
  fi
421
+ if [[ "$AUTO_CLEANUP" -eq 1 && "$auto_finish_completed" -eq 1 ]]; then
422
+ prune_args+=(--delete-branches --delete-remote-branches)
423
+ fi
113
424
  if ! bash "${repo_root}/scripts/agent-worktree-prune.sh" "${prune_args[@]}"; then
114
425
  echo "[codex-agent] Warning: automatic worktree cleanup failed." >&2
115
426
  fi
@@ -121,8 +432,13 @@ else
121
432
  worktree_branch="$(git -C "$worktree_path" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
122
433
  echo "[codex-agent] Sandbox worktree kept: $worktree_path"
123
434
  if [[ -n "$worktree_branch" && "$worktree_branch" != "HEAD" ]]; then
124
- echo "[codex-agent] If finished, merge + clean with: bash scripts/agent-branch-finish.sh --branch \"${worktree_branch}\""
435
+ if [[ "$auto_finish_completed" -eq 1 ]]; then
436
+ echo "[codex-agent] Branch kept intentionally. Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
437
+ else
438
+ echo "[codex-agent] If finished, merge with: bash scripts/agent-branch-finish.sh --branch \"${worktree_branch}\" --via-pr"
439
+ echo "[codex-agent] Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
440
+ fi
125
441
  fi
126
442
  fi
127
443
 
128
- exit "$codex_exit"
444
+ exit "$final_exit"