@imdeadpool/guardex 6.0.0 → 6.0.1

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,33 +6,11 @@ AGENT_NAME="${GUARDEX_AGENT_NAME:-agent}"
6
6
  BASE_BRANCH="${GUARDEX_BASE_BRANCH:-}"
7
7
  BASE_BRANCH_EXPLICIT=0
8
8
  CODEX_BIN="${GUARDEX_CODEX_BIN:-codex}"
9
- AUTO_FINISH_RAW="${GUARDEX_CODEX_AUTO_FINISH:-true}"
10
- AUTO_REVIEW_ON_CONFLICT_RAW="${GUARDEX_CODEX_AUTO_REVIEW_ON_CONFLICT:-true}"
11
- AUTO_CLEANUP_RAW="${GUARDEX_CODEX_AUTO_CLEANUP:-true}"
12
- AUTO_WAIT_FOR_MERGE_RAW="${GUARDEX_CODEX_WAIT_FOR_MERGE:-true}"
13
- OPENSPEC_AUTO_INIT_RAW="${GUARDEX_OPENSPEC_AUTO_INIT:-true}"
14
- OPENSPEC_PLAN_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_PLAN_SLUG:-}"
15
- OPENSPEC_CHANGE_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CHANGE_SLUG:-}"
16
- OPENSPEC_CAPABILITY_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CAPABILITY_SLUG:-}"
17
-
18
- normalize_bool() {
19
- local raw="${1:-}"
20
- local fallback="${2:-0}"
21
- local lowered
22
- lowered="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')"
23
- case "$lowered" in
24
- 1|true|yes|on) printf '1' ;;
25
- 0|false|no|off) printf '0' ;;
26
- '') printf '%s' "$fallback" ;;
27
- *) printf '%s' "$fallback" ;;
28
- esac
29
- }
30
-
31
- AUTO_FINISH="$(normalize_bool "$AUTO_FINISH_RAW" "1")"
32
- AUTO_REVIEW_ON_CONFLICT="$(normalize_bool "$AUTO_REVIEW_ON_CONFLICT_RAW" "1")"
33
- AUTO_CLEANUP="$(normalize_bool "$AUTO_CLEANUP_RAW" "1")"
34
- AUTO_WAIT_FOR_MERGE="$(normalize_bool "$AUTO_WAIT_FOR_MERGE_RAW" "1")"
35
- OPENSPEC_AUTO_INIT="$(normalize_bool "$OPENSPEC_AUTO_INIT_RAW" "1")"
9
+ GH_PR_REF="${GUARDEX_GH_PR_REF:-}"
10
+ GH_REPO_REF="${GUARDEX_GH_REPO:-}"
11
+ GH_SYNC_FLAG=""
12
+ ROLE=""
13
+ CODEX_EXTRA_ARGS=()
36
14
 
37
15
  if [[ -n "$BASE_BRANCH" ]]; then
38
16
  BASE_BRANCH_EXPLICIT=1
@@ -57,37 +35,25 @@ while [[ $# -gt 0 ]]; do
57
35
  CODEX_BIN="${2:-$CODEX_BIN}"
58
36
  shift 2
59
37
  ;;
60
- --auto-finish)
61
- AUTO_FINISH=1
62
- shift
63
- ;;
64
- --no-auto-finish)
65
- AUTO_FINISH=0
66
- shift
67
- ;;
68
- --auto-review-on-conflict)
69
- AUTO_REVIEW_ON_CONFLICT=1
70
- shift
71
- ;;
72
- --no-auto-review-on-conflict)
73
- AUTO_REVIEW_ON_CONFLICT=0
74
- shift
38
+ --pr)
39
+ GH_PR_REF="${2:-$GH_PR_REF}"
40
+ shift 2
75
41
  ;;
76
- --cleanup)
77
- AUTO_CLEANUP=1
78
- shift
42
+ --repo)
43
+ GH_REPO_REF="${2:-$GH_REPO_REF}"
44
+ shift 2
79
45
  ;;
80
- --no-cleanup)
81
- AUTO_CLEANUP=0
46
+ --gh-sync)
47
+ GH_SYNC_FLAG="--gh-sync"
82
48
  shift
83
49
  ;;
84
- --wait-for-merge)
85
- AUTO_WAIT_FOR_MERGE=1
50
+ --no-gh-sync)
51
+ GH_SYNC_FLAG="--no-gh-sync"
86
52
  shift
87
53
  ;;
88
- --no-wait-for-merge)
89
- AUTO_WAIT_FOR_MERGE=0
90
- shift
54
+ --role)
55
+ ROLE="${2:-$ROLE}"
56
+ shift 2
91
57
  ;;
92
58
  --)
93
59
  shift
@@ -130,160 +96,6 @@ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
130
96
  fi
131
97
  repo_root="$(git rev-parse --show-toplevel)"
132
98
 
133
- sanitize_slug() {
134
- local raw="$1"
135
- local fallback="${2:-task}"
136
- local slug
137
- slug="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//; s/-{2,}/-/g')"
138
- if [[ -z "$slug" ]]; then
139
- slug="$fallback"
140
- fi
141
- printf '%s' "$slug"
142
- }
143
-
144
- resolve_openspec_plan_slug() {
145
- local branch_name="$1"
146
- local task_slug
147
- task_slug="$(sanitize_slug "$TASK_NAME" "task")"
148
- if [[ -n "$OPENSPEC_PLAN_SLUG_OVERRIDE" ]]; then
149
- sanitize_slug "$OPENSPEC_PLAN_SLUG_OVERRIDE" "$task_slug"
150
- return 0
151
- fi
152
- sanitize_slug "${branch_name//\//-}" "$task_slug"
153
- }
154
-
155
- resolve_openspec_change_slug() {
156
- local branch_name="$1"
157
- local task_slug
158
- task_slug="$(sanitize_slug "$TASK_NAME" "task")"
159
- if [[ -n "$OPENSPEC_CHANGE_SLUG_OVERRIDE" ]]; then
160
- sanitize_slug "$OPENSPEC_CHANGE_SLUG_OVERRIDE" "$task_slug"
161
- return 0
162
- fi
163
- sanitize_slug "${branch_name//\//-}" "$task_slug"
164
- }
165
-
166
- resolve_openspec_capability_slug() {
167
- local task_slug
168
- task_slug="$(sanitize_slug "$TASK_NAME" "task")"
169
- if [[ -n "$OPENSPEC_CAPABILITY_SLUG_OVERRIDE" ]]; then
170
- sanitize_slug "$OPENSPEC_CAPABILITY_SLUG_OVERRIDE" "$task_slug"
171
- return 0
172
- fi
173
- sanitize_slug "$task_slug" "general-behavior"
174
- }
175
-
176
- hydrate_local_helper_in_worktree() {
177
- local worktree="$1"
178
- local relative_path="$2"
179
- local worktree_target="${worktree}/${relative_path}"
180
- local source_path=""
181
-
182
- if [[ -e "$worktree_target" ]]; then
183
- return 0
184
- fi
185
-
186
- if [[ -f "${repo_root}/${relative_path}" ]]; then
187
- source_path="${repo_root}/${relative_path}"
188
- elif [[ -f "${repo_root}/templates/${relative_path}" ]]; then
189
- source_path="${repo_root}/templates/${relative_path}"
190
- fi
191
-
192
- if [[ -z "$source_path" ]]; then
193
- return 0
194
- fi
195
-
196
- mkdir -p "$(dirname "$worktree_target")"
197
- cp "$source_path" "$worktree_target"
198
- if [[ -x "$source_path" ]]; then
199
- chmod +x "$worktree_target"
200
- fi
201
-
202
- echo "[codex-agent] Hydrated local helper in sandbox: ${relative_path}"
203
- }
204
-
205
- resolve_start_base_branch() {
206
- if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -n "$BASE_BRANCH" ]]; then
207
- printf '%s' "$BASE_BRANCH"
208
- return 0
209
- fi
210
-
211
- local configured_base
212
- configured_base="$(git -C "$repo_root" config --get multiagent.baseBranch || true)"
213
- if [[ -n "$configured_base" ]]; then
214
- printf '%s' "$configured_base"
215
- return 0
216
- fi
217
-
218
- printf 'dev'
219
- }
220
-
221
- resolve_start_ref() {
222
- local base_branch="$1"
223
- git -C "$repo_root" fetch origin "$base_branch" --quiet >/dev/null 2>&1 || true
224
- if git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/${base_branch}"; then
225
- printf 'origin/%s' "$base_branch"
226
- return 0
227
- fi
228
- if git -C "$repo_root" show-ref --verify --quiet "refs/heads/${base_branch}"; then
229
- printf '%s' "$base_branch"
230
- return 0
231
- fi
232
- return 1
233
- }
234
-
235
- restore_repo_branch_if_changed() {
236
- local expected_branch="$1"
237
- if [[ -z "$expected_branch" || "$expected_branch" == "HEAD" ]]; then
238
- return 0
239
- fi
240
- local current_branch
241
- current_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
242
- if [[ -z "$current_branch" || "$current_branch" == "$expected_branch" ]]; then
243
- return 0
244
- fi
245
- git -C "$repo_root" checkout "$expected_branch" >/dev/null 2>&1
246
- }
247
-
248
- start_sandbox_fallback() {
249
- local base_branch start_ref timestamp task_slug agent_slug branch_name_base branch_name suffix
250
- local worktree_root worktree_path
251
-
252
- base_branch="$(resolve_start_base_branch)"
253
- if ! start_ref="$(resolve_start_ref "$base_branch")"; then
254
- echo "[codex-agent] Unable to resolve base ref for fallback sandbox start: ${base_branch}" >&2
255
- return 1
256
- fi
257
-
258
- timestamp="$(date +%Y%m%d-%H%M%S)"
259
- task_slug="$(sanitize_slug "$TASK_NAME" "task")"
260
- agent_slug="$(sanitize_slug "$AGENT_NAME" "agent")"
261
- branch_name_base="agent/${agent_slug}/${timestamp}-${task_slug}"
262
- branch_name="$branch_name_base"
263
- suffix=2
264
- while git -C "$repo_root" show-ref --verify --quiet "refs/heads/${branch_name}"; do
265
- branch_name="${branch_name_base}-${suffix}"
266
- suffix=$((suffix + 1))
267
- done
268
-
269
- worktree_root="${repo_root}/.omx/agent-worktrees"
270
- mkdir -p "$worktree_root"
271
- worktree_path="${worktree_root}/${branch_name//\//__}"
272
- if [[ -e "$worktree_path" ]]; then
273
- echo "[codex-agent] Fallback worktree path already exists: $worktree_path" >&2
274
- return 1
275
- fi
276
-
277
- git -C "$repo_root" worktree add -b "$branch_name" "$worktree_path" "$start_ref" >/dev/null
278
- git -C "$repo_root" config "branch.${branch_name}.guardexBase" "$base_branch" >/dev/null 2>&1 || true
279
- if git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/${base_branch}"; then
280
- git -C "$worktree_path" branch --set-upstream-to="origin/${base_branch}" "$branch_name" >/dev/null 2>&1 || true
281
- fi
282
-
283
- printf '[agent-branch-start] Created branch: %s\n' "$branch_name"
284
- printf '[agent-branch-start] Worktree: %s\n' "$worktree_path"
285
- }
286
-
287
99
  if [[ ! -x "${repo_root}/scripts/agent-branch-start.sh" ]]; then
288
100
  echo "[codex-agent] Missing scripts/agent-branch-start.sh. Run: gx setup" >&2
289
101
  exit 1
@@ -293,489 +105,291 @@ start_args=("$TASK_NAME" "$AGENT_NAME")
293
105
  if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 ]]; then
294
106
  start_args+=("$BASE_BRANCH")
295
107
  fi
296
-
297
- initial_repo_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
298
- start_output=""
299
- start_status=0
300
- set +e
301
- start_output="$(GUARDEX_OPENSPEC_AUTO_INIT=0 bash "${repo_root}/scripts/agent-branch-start.sh" "${start_args[@]}" 2>&1)"
302
- start_status=$?
303
- set -e
304
-
305
- worktree_path="$(printf '%s\n' "$start_output" | sed -n 's/^\[agent-branch-start\] Worktree: //p' | tail -n1)"
306
- current_repo_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
307
- resolved_repo_root="$(cd "$repo_root" && pwd -P)"
308
- resolved_worktree_path=""
309
- if [[ -n "$worktree_path" && -d "$worktree_path" ]]; then
310
- resolved_worktree_path="$(cd "$worktree_path" && pwd -P)"
311
- fi
312
-
313
- fallback_reason=""
314
- if [[ "$start_status" -ne 0 ]]; then
315
- fallback_reason="starter exited with status ${start_status}"
316
- elif [[ -z "$worktree_path" ]]; then
317
- fallback_reason="starter did not report worktree path"
318
- elif [[ -n "$resolved_worktree_path" && "$resolved_worktree_path" == "$resolved_repo_root" ]]; then
319
- fallback_reason="starter pointed to active checkout path"
320
- elif [[ -n "$initial_repo_branch" && -n "$current_repo_branch" && "$current_repo_branch" != "$initial_repo_branch" ]]; then
321
- fallback_reason="starter switched active checkout branch"
108
+ if [[ -n "$GH_PR_REF" ]]; then
109
+ start_args+=(--pr "$GH_PR_REF")
322
110
  fi
323
-
324
- if [[ -n "$fallback_reason" ]]; then
325
- if ! restore_repo_branch_if_changed "$initial_repo_branch"; then
326
- echo "[codex-agent] agent-branch-start changed the active checkout branch and restore failed." >&2
327
- echo "[codex-agent] Run 'gx setup --target ${repo_root}' and 'gx doctor --target ${repo_root}', then retry." >&2
328
- exit 1
329
- fi
330
- if [[ -n "$start_output" ]]; then
331
- printf '%s\n' "$start_output" >&2
332
- fi
333
- echo "[codex-agent] Unsafe starter output (${fallback_reason}); creating sandbox worktree directly." >&2
334
- start_output="$(start_sandbox_fallback)"
335
- printf '%s\n' "$start_output"
336
- worktree_path="$(printf '%s\n' "$start_output" | sed -n 's/^\[agent-branch-start\] Worktree: //p' | tail -n1)"
337
- else
338
- printf '%s\n' "$start_output"
339
- fi
340
-
341
- if [[ -z "$worktree_path" ]]; then
342
- echo "[codex-agent] Could not determine sandbox worktree path from sandbox startup output." >&2
343
- echo "[codex-agent] Run 'gx setup --target ${repo_root}' and 'gx doctor --target ${repo_root}', then retry." >&2
344
- exit 1
111
+ if [[ -n "$GH_REPO_REF" ]]; then
112
+ start_args+=(--repo "$GH_REPO_REF")
345
113
  fi
346
-
347
- if [[ ! -d "$worktree_path" ]]; then
348
- echo "[codex-agent] Reported worktree path does not exist: $worktree_path" >&2
349
- exit 1
114
+ if [[ -n "$GH_SYNC_FLAG" ]]; then
115
+ start_args+=("$GH_SYNC_FLAG")
350
116
  fi
351
117
 
352
- has_origin_remote() {
353
- git -C "$repo_root" remote get-url origin >/dev/null 2>&1
354
- }
355
-
356
- resolve_worktree_base_branch() {
357
- local _wt="$1"
358
- if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -n "$BASE_BRANCH" ]]; then
359
- printf '%s' "$BASE_BRANCH"
360
- return 0
361
- fi
362
-
363
- local configured_base
364
- configured_base="$(git -C "$repo_root" config --get multiagent.baseBranch || true)"
365
- if [[ -n "$configured_base" ]]; then
366
- printf '%s' "$configured_base"
367
- return 0
368
- fi
369
-
370
- printf 'dev'
371
- }
372
-
373
- sync_worktree_with_base() {
374
- local wt="$1"
375
- if ! has_origin_remote; then
376
- return 0
377
- fi
378
-
379
- local base_branch
380
- base_branch="$(resolve_worktree_base_branch "$wt")"
381
- if [[ -z "$base_branch" ]]; then
382
- return 0
383
- fi
384
-
385
- if ! git -C "$wt" fetch origin "$base_branch" --quiet; then
386
- echo "[codex-agent] Warning: could not fetch origin/${base_branch} before task start." >&2
387
- return 0
388
- fi
389
-
390
- if ! git -C "$wt" show-ref --verify --quiet "refs/remotes/origin/${base_branch}"; then
391
- return 0
392
- fi
393
-
394
- local behind_count
395
- behind_count="$(git -C "$wt" rev-list --left-right --count "HEAD...origin/${base_branch}" 2>/dev/null | awk '{print $2}')"
396
- behind_count="${behind_count:-0}"
397
- if [[ "$behind_count" -le 0 ]]; then
398
- return 0
399
- fi
118
+ derive_worktree_session_key() {
119
+ local worktree="$1"
120
+ local digest=""
400
121
 
401
- local branch
402
- branch="$(git -C "$wt" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
403
- echo "[codex-agent] Task sync: '${branch}' is behind origin/${base_branch} by ${behind_count} commit(s). Rebasing before launch..."
404
- if ! git -C "$wt" rebase "origin/${base_branch}"; then
405
- echo "[codex-agent] Task sync failed. Resolve and continue in sandbox:" >&2
406
- echo " git -C \"$wt\" rebase --continue" >&2
407
- echo " # or abort" >&2
408
- echo " git -C \"$wt\" rebase --abort" >&2
409
- return 1
122
+ if command -v sha256sum >/dev/null 2>&1; then
123
+ digest="$(printf '%s' "$worktree" | sha256sum | awk '{print $1}' | cut -c1-20)"
124
+ elif command -v shasum >/dev/null 2>&1; then
125
+ digest="$(printf '%s' "$worktree" | shasum -a 256 | awk '{print $1}' | cut -c1-20)"
410
126
  fi
411
- echo "[codex-agent] Task sync complete."
412
- return 0
413
- }
414
-
415
- ensure_openspec_plan_workspace() {
416
- local wt="$1"
417
- local branch="$2"
418
127
 
419
- if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
420
- return 0
128
+ if [[ -z "$digest" ]]; then
129
+ digest="$(printf '%s' "$worktree" | tr -cs 'a-zA-Z0-9' '-' | sed -E 's/^-+//; s/-+$//' | cut -c1-40)"
421
130
  fi
422
131
 
423
- hydrate_local_helper_in_worktree "$wt" "scripts/openspec/init-plan-workspace.sh"
424
-
425
- local openspec_script="${wt}/scripts/openspec/init-plan-workspace.sh"
426
- if [[ ! -f "$openspec_script" ]]; then
427
- echo "[codex-agent] Missing OpenSpec init script in sandbox: ${openspec_script}" >&2
428
- echo "[codex-agent] Run 'gx setup --target ${repo_root}' and retry." >&2
429
- return 1
430
- fi
431
- if [[ ! -x "$openspec_script" ]]; then
432
- chmod +x "$openspec_script" 2>/dev/null || true
132
+ if [[ -z "$digest" ]]; then
133
+ digest="sandbox"
433
134
  fi
434
135
 
435
- local plan_slug
436
- plan_slug="$(resolve_openspec_plan_slug "$branch")"
437
- local init_output=""
438
- if ! init_output="$(
439
- cd "$wt"
440
- bash "scripts/openspec/init-plan-workspace.sh" "$plan_slug" 2>&1
441
- )"; then
442
- printf '%s\n' "$init_output" >&2
443
- echo "[codex-agent] OpenSpec workspace initialization failed for plan '${plan_slug}'." >&2
444
- return 1
445
- fi
446
- if [[ -n "$init_output" ]]; then
447
- printf '%s\n' "$init_output"
448
- fi
449
- echo "[codex-agent] OpenSpec plan workspace: ${wt}/openspec/plan/${plan_slug}"
136
+ printf 'worktree:%s' "$digest"
450
137
  }
451
138
 
452
- ensure_openspec_change_workspace() {
453
- local wt="$1"
454
- local branch="$2"
455
-
456
- if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
457
- return 0
458
- fi
459
-
460
- hydrate_local_helper_in_worktree "$wt" "scripts/openspec/init-change-workspace.sh"
461
-
462
- local openspec_script="${wt}/scripts/openspec/init-change-workspace.sh"
463
- if [[ ! -f "$openspec_script" ]]; then
464
- echo "[codex-agent] Missing OpenSpec change init script in sandbox: ${openspec_script}" >&2
465
- echo "[codex-agent] Run 'gx setup --target ${repo_root}' and retry." >&2
466
- return 1
467
- fi
468
- if [[ ! -x "$openspec_script" ]]; then
469
- chmod +x "$openspec_script" 2>/dev/null || true
470
- fi
471
-
472
- local change_slug capability_slug init_output=""
473
- change_slug="$(resolve_openspec_change_slug "$branch")"
474
- capability_slug="$(resolve_openspec_capability_slug)"
475
- if ! init_output="$(
476
- cd "$wt"
477
- bash "scripts/openspec/init-change-workspace.sh" "$change_slug" "$capability_slug" 2>&1
478
- )"; then
479
- printf '%s\n' "$init_output" >&2
480
- echo "[codex-agent] OpenSpec workspace initialization failed for change '${change_slug}'." >&2
481
- return 1
482
- fi
483
- if [[ -n "$init_output" ]]; then
484
- printf '%s\n' "$init_output"
485
- fi
486
- echo "[codex-agent] OpenSpec change workspace: ${wt}/openspec/changes/${change_slug}"
487
- }
139
+ export_worktree_mem0_env() {
140
+ local worktree="$1"
141
+ local mem0_dir="${worktree}/.omx/mem0"
142
+ local notepad_path="${mem0_dir}/notepad.md"
143
+ local project_memory_path="${mem0_dir}/project-memory.json"
144
+ local scope_path="${mem0_dir}/worktree-scope.json"
488
145
 
489
- worktree_has_changes() {
490
- local wt="$1"
491
- if ! git -C "$wt" diff --quiet -- . ":(exclude).omx/state/agent-file-locks.json"; then
492
- return 0
146
+ export OMX_MEM0_SCOPE="worktree"
147
+ export OMX_MEM0_DIR="$mem0_dir"
148
+ if [[ -f "$notepad_path" ]]; then
149
+ export OMX_NOTEPAD_PATH="$notepad_path"
493
150
  fi
494
- if ! git -C "$wt" diff --cached --quiet -- . ":(exclude).omx/state/agent-file-locks.json"; then
495
- return 0
151
+ if [[ -f "$project_memory_path" ]]; then
152
+ export OMX_PROJECT_MEMORY_PATH="$project_memory_path"
496
153
  fi
497
- if [[ -n "$(git -C "$wt" ls-files --others --exclude-standard)" ]]; then
498
- return 0
154
+ if [[ -f "$scope_path" ]]; then
155
+ export OMX_MEM0_SCOPE_PATH="$scope_path"
499
156
  fi
500
- return 1
501
- }
502
157
 
503
- claim_changed_files() {
504
- local wt="$1"
505
- local branch="$2"
506
- local lock_script="${repo_root}/scripts/agent-file-locks.py"
507
-
508
- if [[ ! -x "$lock_script" ]]; then
509
- return 0
158
+ if [[ -z "${CODEX_AUTH_SESSION_KEY:-}" ]]; then
159
+ export CODEX_AUTH_SESSION_KEY="$(derive_worktree_session_key "$worktree")"
160
+ echo "[codex-agent] Scoped CODEX_AUTH_SESSION_KEY to ${CODEX_AUTH_SESSION_KEY}"
510
161
  fi
511
162
 
512
- local changed_raw deleted_raw
513
- changed_raw="$({
514
- git -C "$wt" diff --name-only -- . ":(exclude).omx/state/agent-file-locks.json";
515
- git -C "$wt" diff --cached --name-only -- . ":(exclude).omx/state/agent-file-locks.json";
516
- git -C "$wt" ls-files --others --exclude-standard;
517
- } | sed '/^$/d' | sort -u)"
518
-
519
- if [[ -n "$changed_raw" ]]; then
520
- mapfile -t changed_files < <(printf '%s\n' "$changed_raw")
521
- python3 "$lock_script" claim --branch "$branch" "${changed_files[@]}" >/dev/null 2>&1 || true
522
- fi
523
-
524
- deleted_raw="$({
525
- git -C "$wt" diff --name-only --diff-filter=D -- . ":(exclude).omx/state/agent-file-locks.json";
526
- git -C "$wt" diff --cached --name-only --diff-filter=D -- . ":(exclude).omx/state/agent-file-locks.json";
527
- } | sed '/^$/d' | sort -u)"
528
-
529
- if [[ -n "$deleted_raw" ]]; then
530
- mapfile -t deleted_files < <(printf '%s\n' "$deleted_raw")
531
- python3 "$lock_script" allow-delete --branch "$branch" "${deleted_files[@]}" >/dev/null 2>&1 || true
532
- fi
163
+ echo "[codex-agent] Worktree mem0 scope: $mem0_dir"
533
164
  }
534
165
 
535
- auto_commit_worktree_changes() {
536
- local wt="$1"
537
- local branch="$2"
538
-
539
- if ! worktree_has_changes "$wt"; then
540
- return 0
541
- fi
542
-
543
- claim_changed_files "$wt" "$branch"
544
- git -C "$wt" add -A
545
-
546
- if git -C "$wt" diff --cached --quiet -- . ":(exclude).omx/state/agent-file-locks.json"; then
547
- return 0
548
- fi
549
-
550
- local default_message="Auto-finish: ${TASK_NAME}"
551
- local commit_message="${GUARDEX_CODEX_AUTO_COMMIT_MESSAGE:-$default_message}"
552
- local commit_output=""
553
-
554
- if commit_output="$(git -C "$wt" commit -m "$commit_message" 2>&1)"; then
555
- echo "[codex-agent] Auto-committed sandbox changes on '${branch}'."
556
- return 0
557
- fi
166
+ resolve_finish_base_branch() {
167
+ local branch="$1"
168
+ local stored_base=""
558
169
 
559
- if auto_sync_for_commit_retry "$wt" "$branch"; then
560
- claim_changed_files "$wt" "$branch"
561
- git -C "$wt" add -A
562
- if commit_output="$(git -C "$wt" commit -m "$commit_message" 2>&1)"; then
563
- echo "[codex-agent] Auto-committed sandbox changes on '${branch}' after sync retry."
564
- return 0
565
- fi
170
+ stored_base="$(git -C "$repo_root" config --get "branch.${branch}.guardexBase" || true)"
171
+ if [[ -n "$stored_base" ]]; then
172
+ printf '%s' "$stored_base"
173
+ return
566
174
  fi
567
175
 
568
- echo "[codex-agent] Auto-commit failed in sandbox. Keeping branch for manual review: $branch" >&2
569
- if [[ -n "$commit_output" ]]; then
570
- printf '%s\n' "$commit_output" >&2
176
+ if [[ -n "$BASE_BRANCH" ]]; then
177
+ printf '%s' "$BASE_BRANCH"
571
178
  fi
572
- return 1
573
179
  }
574
180
 
575
- auto_sync_for_commit_retry() {
576
- local wt="$1"
577
- local branch="$2"
578
-
579
- if ! has_origin_remote; then
580
- return 1
581
- fi
582
-
583
- local base_branch
584
- base_branch="$(resolve_worktree_base_branch "$wt")"
585
- if [[ -z "$base_branch" ]]; then
586
- return 1
587
- fi
588
-
589
- if ! git -C "$wt" fetch origin "$base_branch" --quiet; then
590
- return 1
591
- fi
592
-
593
- if ! git -C "$wt" show-ref --verify --quiet "refs/remotes/origin/${base_branch}"; then
594
- return 1
595
- fi
596
-
597
- local behind_count
598
- behind_count="$(git -C "$wt" rev-list --left-right --count "HEAD...origin/${base_branch}" 2>/dev/null | awk '{print $2}')"
599
- behind_count="${behind_count:-0}"
600
- if [[ "$behind_count" -le 0 ]]; then
601
- return 1
602
- fi
603
-
604
- echo "[codex-agent] Auto-commit retry: '${branch}' is behind origin/${base_branch} by ${behind_count} commit(s). Syncing and retrying..."
181
+ render_finish_hint() {
182
+ local branch="$1"
183
+ local base="${2:-}"
184
+ local hint=""
605
185
 
606
- local stash_ref=""
607
- local stash_output=""
608
- if worktree_has_changes "$wt"; then
609
- if ! stash_output="$(git -C "$wt" stash push --include-untracked -m "codex-agent-autocommit-sync-${branch}-$(date +%s)" 2>&1)"; then
610
- return 1
611
- fi
612
- stash_ref="$(printf '%s\n' "$stash_output" | grep -o 'stash@{[0-9]\+}' | head -n 1 || true)"
186
+ hint="bash scripts/agent-branch-finish.sh --branch \"${branch}\""
187
+ if [[ -n "$base" ]]; then
188
+ hint="${hint} --base \"${base}\""
613
189
  fi
614
-
615
- if ! git -C "$wt" rebase "origin/${base_branch}" >/dev/null 2>&1; then
616
- git -C "$wt" rebase --abort >/dev/null 2>&1 || true
617
- if [[ -n "$stash_ref" ]]; then
618
- git -C "$wt" stash pop "$stash_ref" >/dev/null 2>&1 || true
619
- fi
620
- return 1
190
+ hint="${hint} --via-pr --wait-for-merge --cleanup"
191
+ if [[ -n "$GH_PR_REF" ]]; then
192
+ hint="${hint} --pr \"${GH_PR_REF}\""
621
193
  fi
622
-
623
- if [[ -n "$stash_ref" ]]; then
624
- if ! git -C "$wt" stash pop "$stash_ref" >/dev/null 2>&1; then
625
- echo "[codex-agent] Auto-commit retry could not re-apply local changes after sync. Manual resolution required in: $wt" >&2
626
- return 1
627
- fi
194
+ if [[ -n "$GH_REPO_REF" ]]; then
195
+ hint="${hint} --repo \"${GH_REPO_REF}\""
628
196
  fi
629
197
 
630
- return 0
631
- }
632
-
633
- looks_like_conflict_failure() {
634
- local output="$1"
635
- if grep -qiE 'preflight conflict detected|merge conflict detected|auto-sync failed while rebasing|rebase --continue|rebase --abort' <<< "$output"; then
636
- return 0
637
- fi
638
- return 1
198
+ printf '%s' "$hint"
639
199
  }
640
200
 
641
- run_finish_flow() {
642
- local wt="$1"
643
- local branch="$2"
644
- local finish_base_branch=""
645
- local finish_output=""
646
- local -a finish_args
647
-
648
- finish_args=(--branch "$branch")
649
- if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -n "$BASE_BRANCH" ]]; then
650
- finish_base_branch="$BASE_BRANCH"
651
- else
652
- finish_base_branch="$(resolve_worktree_base_branch "$wt")"
653
- fi
654
- if [[ -n "$finish_base_branch" ]]; then
655
- finish_args+=(--base "$finish_base_branch")
656
- fi
657
- if [[ "$AUTO_CLEANUP" -eq 1 ]]; then
658
- finish_args+=(--cleanup)
659
- fi
660
- if [[ "$AUTO_WAIT_FOR_MERGE" -eq 1 ]]; then
661
- finish_args+=(--wait-for-merge)
662
- fi
663
-
664
- if has_origin_remote; then
665
- if ! command -v "${GUARDEX_GH_BIN:-gh}" >/dev/null 2>&1 && ! command -v gh >/dev/null 2>&1; then
666
- echo "[codex-agent] Auto-finish requires GitHub CLI for PR flow; command not found: ${GUARDEX_GH_BIN:-gh}" >&2
667
- return 2
668
- fi
669
- finish_args+=(--via-pr)
670
- else
671
- echo "[codex-agent] No origin remote detected; skipping auto-finish merge/PR pipeline." >&2
672
- return 2
673
- fi
674
-
675
- if finish_output="$(bash "${repo_root}/scripts/agent-branch-finish.sh" "${finish_args[@]}" 2>&1)"; then
676
- printf '%s\n' "$finish_output"
677
- return 0
678
- fi
679
-
680
- printf '%s\n' "$finish_output" >&2
681
-
682
- if [[ "$AUTO_REVIEW_ON_CONFLICT" -eq 1 ]] && looks_like_conflict_failure "$finish_output"; then
683
- echo "[codex-agent] Auto-finish hit conflicts. Launching Codex conflict-review pass in sandbox..." >&2
684
- local review_prompt
685
- review_prompt="Resolve git conflicts for branch ${branch} against ${finish_base_branch:-dev}, then commit the resolution in this sandbox worktree and exit."
686
-
687
- (
688
- cd "$wt"
689
- set +e
690
- "$CODEX_BIN" "$review_prompt"
691
- review_exit="$?"
692
- set -e
693
- if [[ "$review_exit" -ne 0 ]]; then
694
- echo "[codex-agent] Conflict-review Codex pass exited with status ${review_exit}." >&2
695
- fi
201
+ resolve_role_model() {
202
+ local role="$1"
203
+ local config_path="$2"
204
+ python3 - "$role" "$config_path" <<'PY'
205
+ import json
206
+ import os
207
+ import re
208
+ import sys
209
+
210
+ role = sys.argv[1]
211
+ config_path = sys.argv[2]
212
+ if not os.path.exists(config_path):
213
+ print(f"codex-agent: config file not found: {config_path}", file=sys.stderr)
214
+ sys.exit(2)
215
+ try:
216
+ with open(config_path, "r", encoding="utf-8") as fh:
217
+ cfg = json.load(fh)
218
+ except json.JSONDecodeError as exc:
219
+ print(f"codex-agent: config file is not valid JSON ({config_path}): {exc}", file=sys.stderr)
220
+ sys.exit(2)
221
+ if not isinstance(cfg, dict):
222
+ print(f"codex-agent: config file root is not a JSON object: {config_path}", file=sys.stderr)
223
+ sys.exit(2)
224
+ for key in ("version", "tiers", "roles"):
225
+ if key not in cfg:
226
+ print(
227
+ f"codex-agent: config file missing required top-level key '{key}': {config_path}",
228
+ file=sys.stderr,
229
+ )
230
+ sys.exit(2)
231
+ tiers = cfg.get("tiers") or {}
232
+ roles = cfg.get("roles") or {}
233
+ if not isinstance(tiers, dict) or not tiers:
234
+ print(f"codex-agent: config 'tiers' must be a non-empty object: {config_path}", file=sys.stderr)
235
+ sys.exit(2)
236
+ if not isinstance(roles, dict) or not roles:
237
+ print(f"codex-agent: config 'roles' must be a non-empty object: {config_path}", file=sys.stderr)
238
+ sys.exit(2)
239
+ if role not in roles:
240
+ valid = ", ".join(sorted(roles.keys()))
241
+ print(
242
+ f"codex-agent: role '{role}' is not defined in {config_path}. Valid roles: {valid}",
243
+ file=sys.stderr,
696
244
  )
697
-
698
- if finish_output="$(bash "${repo_root}/scripts/agent-branch-finish.sh" "${finish_args[@]}" 2>&1)"; then
699
- printf '%s\n' "$finish_output"
700
- return 0
701
- fi
702
-
703
- printf '%s\n' "$finish_output" >&2
704
- fi
705
-
706
- return 1
245
+ sys.exit(2)
246
+ role_entry = roles[role]
247
+ if not isinstance(role_entry, dict) or "tier" not in role_entry:
248
+ print(f"codex-agent: role '{role}' entry is missing 'tier' in {config_path}", file=sys.stderr)
249
+ sys.exit(2)
250
+ tier_name = role_entry["tier"]
251
+ if tier_name not in tiers:
252
+ print(
253
+ f"codex-agent: role '{role}' points to tier '{tier_name}' which is not defined in {config_path}",
254
+ file=sys.stderr,
255
+ )
256
+ sys.exit(2)
257
+ tier_entry = tiers[tier_name]
258
+ if not isinstance(tier_entry, dict) or not tier_entry.get("model"):
259
+ print(
260
+ f"codex-agent: tier '{tier_name}' is missing a non-empty 'model' field in {config_path}",
261
+ file=sys.stderr,
262
+ )
263
+ sys.exit(2)
264
+ model = tier_entry["model"]
265
+ if re.fullmatch(r"REPLACE_WITH_[A-Z_]+_MODEL_ID", model):
266
+ print(
267
+ f"codex-agent: tier '{tier_name}' still has a placeholder model ID. "
268
+ f"Edit {config_path} and replace {model}.",
269
+ file=sys.stderr,
270
+ )
271
+ sys.exit(2)
272
+ print(f"{tier_name}\t{model}")
273
+ PY
707
274
  }
708
275
 
709
- if ! sync_worktree_with_base "$worktree_path"; then
710
- exit 1
276
+ resolved_model=""
277
+ resolved_tier=""
278
+ resolved_source=""
279
+ role_config_path="${OMX_ROLE_MODEL_CONFIG:-${repo_root}/.agents/role-model-tiers.json}"
280
+
281
+ if [[ -n "${OMX_ROLE_MODEL_OVERRIDE:-}" ]]; then
282
+ resolved_model="$OMX_ROLE_MODEL_OVERRIDE"
283
+ resolved_tier="${ROLE:-n/a}"
284
+ resolved_source="env-override"
285
+ elif [[ -n "$ROLE" ]]; then
286
+ if ! lookup_output="$(resolve_role_model "$ROLE" "$role_config_path")"; then
287
+ exit 2
288
+ fi
289
+ resolved_tier="${lookup_output%%$'\t'*}"
290
+ resolved_model="${lookup_output##*$'\t'}"
291
+ resolved_source="config"
711
292
  fi
712
293
 
713
- worktree_branch="$(git -C "$worktree_path" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
714
- if [[ -z "$worktree_branch" || "$worktree_branch" == "HEAD" ]]; then
715
- echo "[codex-agent] Could not determine sandbox branch for worktree: $worktree_path" >&2
716
- exit 1
294
+ if [[ -n "$resolved_model" ]]; then
295
+ echo "[codex-agent] role=${ROLE:-n/a} tier=${resolved_tier} model=${resolved_model} source=${resolved_source}" >&2
296
+ CODEX_EXTRA_ARGS+=("-m" "$resolved_model")
717
297
  fi
718
298
 
719
- if ! ensure_openspec_change_workspace "$worktree_path" "$worktree_branch"; then
299
+ start_output="$(bash "${repo_root}/scripts/agent-branch-start.sh" "${start_args[@]}")"
300
+ printf '%s\n' "$start_output"
301
+
302
+ worktree_path="$(printf '%s\n' "$start_output" | sed -n 's/^\[agent-branch-start\] Worktree: //p' | tail -n1)"
303
+ if [[ -z "$worktree_path" ]]; then
304
+ echo "[codex-agent] Could not determine sandbox worktree path from agent-branch-start output." >&2
720
305
  exit 1
721
306
  fi
722
307
 
723
- if ! ensure_openspec_plan_workspace "$worktree_path" "$worktree_branch"; then
308
+ if [[ ! -d "$worktree_path" ]]; then
309
+ echo "[codex-agent] Reported worktree path does not exist: $worktree_path" >&2
724
310
  exit 1
725
311
  fi
726
312
 
313
+ export_worktree_mem0_env "$worktree_path"
314
+
727
315
  echo "[codex-agent] Launching ${CODEX_BIN} in sandbox: $worktree_path"
728
316
  cd "$worktree_path"
729
317
  set +e
730
- "$CODEX_BIN" "$@"
318
+ "$CODEX_BIN" "${CODEX_EXTRA_ARGS[@]}" "$@"
731
319
  codex_exit="$?"
732
320
  set -e
733
321
 
734
322
  cd "$repo_root"
323
+
735
324
  final_exit="$codex_exit"
736
- auto_finish_completed=0
737
-
738
- if [[ "$AUTO_FINISH" -eq 1 && -n "$worktree_branch" && "$worktree_branch" != "HEAD" ]]; then
739
- if [[ "$AUTO_WAIT_FOR_MERGE" -eq 1 && "$AUTO_CLEANUP" -eq 1 ]]; then
740
- echo "[codex-agent] Auto-finish enabled: commit -> push/PR -> wait for merge -> cleanup."
741
- elif [[ "$AUTO_WAIT_FOR_MERGE" -eq 1 ]]; then
742
- echo "[codex-agent] Auto-finish enabled: commit -> push/PR -> wait for merge (keep branch/worktree)."
743
- elif [[ "$AUTO_CLEANUP" -eq 1 ]]; then
744
- echo "[codex-agent] Auto-finish enabled: commit -> push/PR -> merge -> cleanup."
745
- else
746
- echo "[codex-agent] Auto-finish enabled: commit -> push/PR -> merge (keep branch/worktree)."
747
- fi
748
- if auto_commit_worktree_changes "$worktree_path" "$worktree_branch"; then
749
- if run_finish_flow "$worktree_path" "$worktree_branch"; then
750
- auto_finish_completed=1
751
- echo "[codex-agent] Auto-finish completed for '${worktree_branch}'."
752
- else
753
- finish_status="$?"
754
- if [[ "$finish_status" -eq 2 ]]; then
755
- echo "[codex-agent] Auto-finish skipped for '${worktree_branch}' (no mergeable remote context)." >&2
756
- else
757
- echo "[codex-agent] Auto-finish did not complete; keeping sandbox for manual review: $worktree_path" >&2
758
- if [[ "$final_exit" -eq 0 ]]; then
759
- final_exit=1
325
+ worktree_branch="$(git -C "$worktree_path" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
326
+ finish_base_branch=""
327
+ finish_hint=""
328
+ if [[ -n "$worktree_branch" && "$worktree_branch" != "HEAD" ]]; then
329
+ finish_base_branch="$(resolve_finish_base_branch "$worktree_branch")"
330
+ finish_hint="$(render_finish_hint "$worktree_branch" "$finish_base_branch")"
331
+ fi
332
+
333
+ if [[ "$codex_exit" -eq 0 ]]; then
334
+ if [[ -x "${repo_root}/scripts/agent-branch-finish.sh" ]]; then
335
+ if [[ -n "$worktree_branch" && "$worktree_branch" != "HEAD" ]]; then
336
+ finish_args=(--branch "$worktree_branch")
337
+ if [[ -n "$finish_base_branch" ]]; then
338
+ finish_args+=(--base "$finish_base_branch")
339
+ fi
340
+ finish_args+=(--via-pr --wait-for-merge --cleanup)
341
+ if [[ -n "$GH_PR_REF" ]]; then
342
+ finish_args+=(--pr "$GH_PR_REF")
343
+ fi
344
+ if [[ -n "$GH_REPO_REF" ]]; then
345
+ finish_args+=(--repo "$GH_REPO_REF")
346
+ fi
347
+
348
+ echo "[codex-agent] Codex finished successfully. Auto-finishing branch via PR merge + cleanup..."
349
+ if ! bash "${repo_root}/scripts/agent-branch-finish.sh" "${finish_args[@]}"; then
350
+ echo "[codex-agent] Auto-finish failed. Sandbox is kept for manual resolve/retry." >&2
351
+ if [[ -n "$finish_hint" ]]; then
352
+ echo "[codex-agent] Retry with: ${finish_hint}" >&2
760
353
  fi
354
+ final_exit=1
761
355
  fi
356
+ else
357
+ echo "[codex-agent] Could not determine sandbox branch name; skipping auto-finish." >&2
358
+ final_exit=1
762
359
  fi
763
360
  else
764
- if [[ "$final_exit" -eq 0 ]]; then
765
- final_exit=1
361
+ echo "[codex-agent] Missing scripts/agent-branch-finish.sh; skipping auto-finish." >&2
362
+ final_exit=1
363
+ fi
364
+ else
365
+ if [[ -x "${repo_root}/scripts/agent-branch-finish.sh" && -n "$worktree_branch" && "$worktree_branch" != "HEAD" ]]; then
366
+ echo "[codex-agent] Codex exited with status ${codex_exit}. Attempting best-effort push + PR (no merge wait)..." >&2
367
+ fallback_args=(--branch "$worktree_branch" --via-pr --skip-tasks-gate)
368
+ if [[ -n "$finish_base_branch" ]]; then
369
+ fallback_args+=(--base "$finish_base_branch")
370
+ fi
371
+ if [[ -n "$GH_PR_REF" ]]; then
372
+ fallback_args+=(--pr "$GH_PR_REF")
766
373
  fi
374
+ if [[ -n "$GH_REPO_REF" ]]; then
375
+ fallback_args+=(--repo "$GH_REPO_REF")
376
+ fi
377
+ if bash "${repo_root}/scripts/agent-branch-finish.sh" "${fallback_args[@]}"; then
378
+ echo "[codex-agent] Fallback push succeeded: branch pushed + PR created/updated for manual review." >&2
379
+ else
380
+ echo "[codex-agent] Fallback push failed; sandbox retained. Retry with: ${finish_hint}" >&2
381
+ fi
382
+ else
383
+ echo "[codex-agent] Skipping auto-finish because Codex exited with status ${codex_exit} and no sandbox branch was detected."
767
384
  fi
768
385
  fi
769
386
 
770
387
  if [[ -x "${repo_root}/scripts/agent-worktree-prune.sh" ]]; then
771
- echo "[codex-agent] Session ended (exit=${codex_exit}). Running worktree cleanup..."
388
+ echo "[codex-agent] Session ended (exit=${final_exit}). Running worktree cleanup..."
772
389
  prune_args=()
773
390
  if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 ]]; then
774
391
  prune_args+=(--base "$BASE_BRANCH")
775
392
  fi
776
- if [[ "$AUTO_CLEANUP" -eq 1 && "$auto_finish_completed" -eq 1 ]]; then
777
- prune_args+=(--only-dirty-worktrees --delete-branches --delete-remote-branches)
778
- fi
779
393
  if ! bash "${repo_root}/scripts/agent-worktree-prune.sh" "${prune_args[@]}"; then
780
394
  echo "[codex-agent] Warning: automatic worktree cleanup failed." >&2
781
395
  fi
@@ -784,15 +398,13 @@ fi
784
398
  if [[ ! -d "$worktree_path" ]]; then
785
399
  echo "[codex-agent] Auto-cleaned sandbox worktree: $worktree_path"
786
400
  else
787
- worktree_branch="$(git -C "$worktree_path" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
788
401
  echo "[codex-agent] Sandbox worktree kept: $worktree_path"
789
402
  if [[ -n "$worktree_branch" && "$worktree_branch" != "HEAD" ]]; then
790
- if [[ "$auto_finish_completed" -eq 1 ]]; then
791
- echo "[codex-agent] Branch kept intentionally. Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
792
- else
793
- echo "[codex-agent] If finished, merge with: bash scripts/agent-branch-finish.sh --branch \"${worktree_branch}\" --base dev --via-pr --wait-for-merge"
794
- echo "[codex-agent] Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
403
+ if [[ -z "$finish_hint" ]]; then
404
+ finish_base_branch="$(resolve_finish_base_branch "$worktree_branch")"
405
+ finish_hint="$(render_finish_hint "$worktree_branch" "$finish_base_branch")"
795
406
  fi
407
+ echo "[codex-agent] If finished, merge + clean with: ${finish_hint}"
796
408
  fi
797
409
  fi
798
410