@imdeadpool/guardex 5.0.0 → 5.0.2

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