@imdeadpool/guardex 7.0.14 → 7.0.15
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/README.md +31 -4
- package/bin/multiagent-safety.js +914 -104
- package/package.json +2 -2
- package/templates/scripts/agent-branch-finish.sh +35 -6
- package/templates/scripts/agent-branch-start.sh +50 -12
- package/templates/scripts/agent-worktree-prune.sh +78 -44
- package/templates/scripts/codex-agent.sh +49 -2
- package/templates/scripts/guardex-docker-loader.sh +123 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imdeadpool/guardex",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.15",
|
|
4
4
|
"description": "GitGuardex: hardened multi-agent git guardrails for parallel agent work.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"preferGlobal": true,
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"bugs": {
|
|
61
61
|
"url": "https://github.com/recodeee/gitguardex/issues"
|
|
62
62
|
},
|
|
63
|
-
"homepage": "https://
|
|
63
|
+
"homepage": "https://github.com/recodeee/gitguardex-frontend",
|
|
64
64
|
"funding": "https://github.com/sponsors/recodeecom",
|
|
65
65
|
"publishConfig": {
|
|
66
66
|
"access": "public"
|
|
@@ -144,24 +144,44 @@ else
|
|
|
144
144
|
common_git_dir="$(cd "$repo_root/$common_git_dir_raw" && pwd -P)"
|
|
145
145
|
fi
|
|
146
146
|
repo_common_root="$(cd "$common_git_dir/.." && pwd -P)"
|
|
147
|
-
agent_worktree_root="${repo_common_root}/.omx/agent-worktrees"
|
|
148
147
|
|
|
149
148
|
if [[ -z "$SOURCE_BRANCH" ]]; then
|
|
150
149
|
SOURCE_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
|
151
150
|
fi
|
|
152
151
|
|
|
152
|
+
stored_worktree_root_rel="$(git -C "$repo_root" config --get "branch.${SOURCE_BRANCH}.guardexWorktreeRoot" || true)"
|
|
153
|
+
if [[ -z "$stored_worktree_root_rel" ]]; then
|
|
154
|
+
stored_worktree_root_rel=".omx/agent-worktrees"
|
|
155
|
+
fi
|
|
156
|
+
agent_worktree_root="${repo_common_root}/${stored_worktree_root_rel}"
|
|
157
|
+
|
|
153
158
|
if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -z "$BASE_BRANCH" ]]; then
|
|
154
159
|
echo "[agent-branch-finish] --base requires a non-empty branch name." >&2
|
|
155
160
|
exit 1
|
|
156
161
|
fi
|
|
157
162
|
|
|
158
163
|
if [[ "$BASE_BRANCH_EXPLICIT" -eq 0 ]]; then
|
|
159
|
-
|
|
160
|
-
if [[ -n "$
|
|
161
|
-
BASE_BRANCH="$
|
|
164
|
+
source_branch_base="$(git -C "$repo_root" config --get "branch.${SOURCE_BRANCH}.guardexBase" || true)"
|
|
165
|
+
if [[ -n "$source_branch_base" ]]; then
|
|
166
|
+
BASE_BRANCH="$source_branch_base"
|
|
167
|
+
else
|
|
168
|
+
configured_base="$(git -C "$repo_root" config --get multiagent.baseBranch || true)"
|
|
169
|
+
if [[ -n "$configured_base" ]]; then
|
|
170
|
+
BASE_BRANCH="$configured_base"
|
|
171
|
+
fi
|
|
162
172
|
fi
|
|
163
173
|
fi
|
|
164
174
|
|
|
175
|
+
if [[ -z "$BASE_BRANCH" ]]; then
|
|
176
|
+
for fallback_branch in dev main master; do
|
|
177
|
+
if git -C "$repo_root" show-ref --verify --quiet "refs/heads/${fallback_branch}" \
|
|
178
|
+
|| git -C "$repo_root" show-ref --verify --quiet "refs/remotes/origin/${fallback_branch}"; then
|
|
179
|
+
BASE_BRANCH="$fallback_branch"
|
|
180
|
+
break
|
|
181
|
+
fi
|
|
182
|
+
done
|
|
183
|
+
fi
|
|
184
|
+
|
|
165
185
|
if [[ -z "$BASE_BRANCH" ]]; then
|
|
166
186
|
BASE_BRANCH="dev"
|
|
167
187
|
fi
|
|
@@ -268,8 +288,17 @@ if [[ "$should_require_sync" -eq 1 ]] && git -C "$repo_root" show-ref --verify -
|
|
|
268
288
|
fi
|
|
269
289
|
fi
|
|
270
290
|
|
|
271
|
-
|
|
272
|
-
|
|
291
|
+
integration_stamp="$(date +%Y%m%d-%H%M%S)"
|
|
292
|
+
integration_worktree_base="${agent_worktree_root}/__integrate-${BASE_BRANCH//\//__}-${integration_stamp}"
|
|
293
|
+
integration_branch_base="__agent_integrate_${BASE_BRANCH//\//_}_$(date +%Y%m%d_%H%M%S)"
|
|
294
|
+
integration_worktree="$integration_worktree_base"
|
|
295
|
+
integration_branch="$integration_branch_base"
|
|
296
|
+
integration_suffix=1
|
|
297
|
+
while [[ -e "$integration_worktree" ]] || git -C "$repo_root" show-ref --verify --quiet "refs/heads/${integration_branch}"; do
|
|
298
|
+
integration_worktree="${integration_worktree_base}-${integration_suffix}"
|
|
299
|
+
integration_branch="${integration_branch_base}_${integration_suffix}"
|
|
300
|
+
integration_suffix=$((integration_suffix + 1))
|
|
301
|
+
done
|
|
273
302
|
mkdir -p "$(dirname "$integration_worktree")"
|
|
274
303
|
|
|
275
304
|
git -C "$repo_root" worktree add "$integration_worktree" "$start_ref" >/dev/null
|
|
@@ -5,7 +5,8 @@ TASK_NAME="task"
|
|
|
5
5
|
AGENT_NAME="agent"
|
|
6
6
|
BASE_BRANCH=""
|
|
7
7
|
BASE_BRANCH_EXPLICIT=0
|
|
8
|
-
WORKTREE_ROOT_REL="
|
|
8
|
+
WORKTREE_ROOT_REL=""
|
|
9
|
+
WORKTREE_ROOT_EXPLICIT=0
|
|
9
10
|
OPENSPEC_AUTO_INIT_RAW="${GUARDEX_OPENSPEC_AUTO_INIT:-false}"
|
|
10
11
|
OPENSPEC_PLAN_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_PLAN_SLUG:-}"
|
|
11
12
|
OPENSPEC_CHANGE_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CHANGE_SLUG:-}"
|
|
@@ -44,6 +45,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
44
45
|
;;
|
|
45
46
|
--worktree-root)
|
|
46
47
|
WORKTREE_ROOT_REL="${2:-.omx/agent-worktrees}"
|
|
48
|
+
WORKTREE_ROOT_EXPLICIT=1
|
|
47
49
|
shift 2
|
|
48
50
|
;;
|
|
49
51
|
--)
|
|
@@ -123,12 +125,41 @@ shorten_slug() {
|
|
|
123
125
|
printf '%s' "$shortened"
|
|
124
126
|
}
|
|
125
127
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
env_flag_truthy() {
|
|
129
|
+
local raw="${1:-}"
|
|
130
|
+
local lowered
|
|
131
|
+
lowered="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')"
|
|
132
|
+
case "$lowered" in
|
|
133
|
+
1|true|yes|on) return 0 ;;
|
|
134
|
+
*) return 1 ;;
|
|
135
|
+
esac
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
default_worktree_root_rel() {
|
|
139
|
+
local raw_agent="$1"
|
|
140
|
+
local override="${GUARDEX_AGENT_TYPE:-}"
|
|
141
|
+
local lowered_agent lowered_override
|
|
142
|
+
lowered_agent="$(printf '%s' "$raw_agent" | tr '[:upper:]' '[:lower:]')"
|
|
143
|
+
lowered_override="$(printf '%s' "$override" | tr '[:upper:]' '[:lower:]')"
|
|
144
|
+
|
|
145
|
+
if [[ -n "${CLAUDE_CODE_SESSION_ID:-}" ]] || env_flag_truthy "${CLAUDECODE:-}"; then
|
|
146
|
+
printf '.omc/agent-worktrees'
|
|
147
|
+
return 0
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
if [[ "$lowered_agent" == *claude* ]] || [[ "$lowered_override" == *claude* ]]; then
|
|
151
|
+
printf '.omc/agent-worktrees'
|
|
152
|
+
return 0
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
printf '.omx/agent-worktrees'
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# Collapse arbitrary agent identifiers to a clean role token. Priority:
|
|
159
|
+
# GUARDEX_AGENT_TYPE env override, then recognizable claude/codex aliases, then
|
|
160
|
+
# a small legacy compatibility set, then the literal requested role after slug
|
|
161
|
+
# sanitization. This preserves explicit roles such as planner/executor while
|
|
162
|
+
# keeping the older bot -> codex fallback stable for existing callers.
|
|
132
163
|
normalize_role() {
|
|
133
164
|
local raw_agent="$1"
|
|
134
165
|
local override="${GUARDEX_AGENT_TYPE:-}"
|
|
@@ -150,10 +181,13 @@ normalize_role() {
|
|
|
150
181
|
printf 'claude'
|
|
151
182
|
return 0
|
|
152
183
|
fi
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
184
|
+
local sanitized
|
|
185
|
+
sanitized="$(sanitize_slug "$raw_agent" "codex")"
|
|
186
|
+
if [[ "$sanitized" == "bot" ]]; then
|
|
187
|
+
printf 'codex'
|
|
188
|
+
return 0
|
|
189
|
+
fi
|
|
190
|
+
printf '%s' "$sanitized"
|
|
157
191
|
}
|
|
158
192
|
|
|
159
193
|
# Timestamp the branch/worktree/openspec slug so parallel agents never collide
|
|
@@ -413,6 +447,9 @@ fi
|
|
|
413
447
|
|
|
414
448
|
task_slug="$(sanitize_slug "$TASK_NAME" "task")"
|
|
415
449
|
agent_slug="$(normalize_role "$AGENT_NAME")"
|
|
450
|
+
if [[ "$WORKTREE_ROOT_EXPLICIT" -eq 0 ]]; then
|
|
451
|
+
WORKTREE_ROOT_REL="$(default_worktree_root_rel "$AGENT_NAME")"
|
|
452
|
+
fi
|
|
416
453
|
branch_timestamp="$(compose_branch_timestamp)"
|
|
417
454
|
branch_descriptor="$(compose_branch_descriptor "$task_slug" "$branch_timestamp")"
|
|
418
455
|
branch_name_base="agent/${agent_slug}/${branch_descriptor}"
|
|
@@ -497,6 +534,7 @@ if ! worktree_add_output="$(git -C "$repo_root" worktree add -b "$branch_name" "
|
|
|
497
534
|
exit 1
|
|
498
535
|
fi
|
|
499
536
|
git -C "$repo_root" config "branch.${branch_name}.guardexBase" "$BASE_BRANCH" >/dev/null 2>&1 || true
|
|
537
|
+
git -C "$repo_root" config "branch.${branch_name}.guardexWorktreeRoot" "$WORKTREE_ROOT_REL" >/dev/null 2>&1 || true
|
|
500
538
|
# Fresh agent branches should start unpublished; clear any inherited base-branch tracking.
|
|
501
539
|
git -C "$worktree_path" branch --unset-upstream "$branch_name" >/dev/null 2>&1 || true
|
|
502
540
|
|
|
@@ -533,4 +571,4 @@ echo "[agent-branch-start] Next steps:"
|
|
|
533
571
|
echo " cd \"${worktree_path}\""
|
|
534
572
|
echo " python3 scripts/agent-file-locks.py claim --branch \"${branch_name}\" <file...>"
|
|
535
573
|
echo " # implement + commit"
|
|
536
|
-
echo " bash scripts/agent-branch-finish.sh --branch \"${branch_name}\" --base
|
|
574
|
+
echo " bash scripts/agent-branch-finish.sh --branch \"${branch_name}\" --base ${BASE_BRANCH} --via-pr --wait-for-merge"
|
|
@@ -18,6 +18,10 @@ GH_BIN="${GUARDEX_GH_BIN:-gh}"
|
|
|
18
18
|
PR_MERGED_LOOKUP_DISABLED=0
|
|
19
19
|
PR_MERGED_LOOKUP_LOADED=0
|
|
20
20
|
declare -A MERGED_PR_BRANCHES=()
|
|
21
|
+
WORKTREE_ROOT_RELS=(
|
|
22
|
+
".omx/agent-worktrees"
|
|
23
|
+
".omc/agent-worktrees"
|
|
24
|
+
)
|
|
21
25
|
|
|
22
26
|
if [[ -n "$BASE_BRANCH" ]]; then
|
|
23
27
|
BASE_BRANCH_EXPLICIT=1
|
|
@@ -77,13 +81,36 @@ fi
|
|
|
77
81
|
|
|
78
82
|
repo_root="$(git rev-parse --show-toplevel)"
|
|
79
83
|
current_pwd="$(pwd -P)"
|
|
80
|
-
worktree_root="${repo_root}/.omx/agent-worktrees"
|
|
81
84
|
repo_common_dir="$(
|
|
82
85
|
git -C "$repo_root" rev-parse --git-common-dir \
|
|
83
86
|
| awk -v root="$repo_root" '{ if ($0 ~ /^\//) { print $0 } else { print root "/" $0 } }'
|
|
84
87
|
)"
|
|
85
88
|
repo_common_dir="$(cd "$repo_common_dir" && pwd -P)"
|
|
86
89
|
|
|
90
|
+
resolve_worktree_root_rel_for_entry() {
|
|
91
|
+
local entry="$1"
|
|
92
|
+
case "$entry" in
|
|
93
|
+
*/.omc/agent-worktrees/*)
|
|
94
|
+
printf '%s' '.omc/agent-worktrees'
|
|
95
|
+
;;
|
|
96
|
+
*)
|
|
97
|
+
printf '%s' '.omx/agent-worktrees'
|
|
98
|
+
;;
|
|
99
|
+
esac
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
is_managed_worktree_path() {
|
|
103
|
+
local entry="$1"
|
|
104
|
+
local rel root
|
|
105
|
+
for rel in "${WORKTREE_ROOT_RELS[@]}"; do
|
|
106
|
+
root="${repo_root}/${rel}"
|
|
107
|
+
if [[ "$entry" == "${root}"/* ]]; then
|
|
108
|
+
return 0
|
|
109
|
+
fi
|
|
110
|
+
done
|
|
111
|
+
return 1
|
|
112
|
+
}
|
|
113
|
+
|
|
87
114
|
resolve_base_branch() {
|
|
88
115
|
local configured=""
|
|
89
116
|
local current=""
|
|
@@ -308,54 +335,59 @@ relocated_foreign=0
|
|
|
308
335
|
skipped_foreign=0
|
|
309
336
|
|
|
310
337
|
relocate_foreign_worktree_entries() {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
continue
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
entry_common_dir="$(resolve_worktree_common_dir "$entry" || true)"
|
|
322
|
-
[[ -n "$entry_common_dir" ]] || continue
|
|
338
|
+
local rel="" worktree_root="" entry=""
|
|
339
|
+
for rel in "${WORKTREE_ROOT_RELS[@]}"; do
|
|
340
|
+
worktree_root="${repo_root}/${rel}"
|
|
341
|
+
[[ -d "$worktree_root" ]] || continue
|
|
342
|
+
|
|
343
|
+
for entry in "${worktree_root}"/*; do
|
|
344
|
+
[[ -d "$entry" ]] || continue
|
|
345
|
+
if ! git -C "$entry" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
346
|
+
continue
|
|
347
|
+
fi
|
|
323
348
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
349
|
+
local entry_common_dir=""
|
|
350
|
+
entry_common_dir="$(resolve_worktree_common_dir "$entry" || true)"
|
|
351
|
+
[[ -n "$entry_common_dir" ]] || continue
|
|
327
352
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
continue
|
|
332
|
-
fi
|
|
353
|
+
if [[ "$entry_common_dir" == "$repo_common_dir" ]]; then
|
|
354
|
+
continue
|
|
355
|
+
fi
|
|
333
356
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
357
|
+
if [[ "$(basename "$entry_common_dir")" != ".git" ]]; then
|
|
358
|
+
skipped_foreign=$((skipped_foreign + 1))
|
|
359
|
+
echo "[agent-worktree-prune] Skipping foreign worktree with unsupported git common dir: ${entry}"
|
|
360
|
+
continue
|
|
361
|
+
fi
|
|
339
362
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
363
|
+
local owner_repo_root
|
|
364
|
+
owner_repo_root="$(dirname "$entry_common_dir")"
|
|
365
|
+
local owner_worktree_root_rel owner_worktree_root
|
|
366
|
+
owner_worktree_root_rel="$(resolve_worktree_root_rel_for_entry "$entry")"
|
|
367
|
+
owner_worktree_root="${owner_repo_root}/${owner_worktree_root_rel}"
|
|
368
|
+
local target_path
|
|
369
|
+
target_path="$(select_unique_worktree_path "$owner_worktree_root" "$(basename "$entry")")"
|
|
370
|
+
|
|
371
|
+
if [[ "$entry" == "$current_pwd" || "$current_pwd" == "${entry}"/* ]]; then
|
|
372
|
+
skipped_foreign=$((skipped_foreign + 1))
|
|
373
|
+
echo "[agent-worktree-prune] Skipping active foreign worktree: ${entry}"
|
|
374
|
+
continue
|
|
375
|
+
fi
|
|
345
376
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
377
|
+
echo "[agent-worktree-prune] Relocating foreign worktree to owning repo: ${entry} -> ${target_path}"
|
|
378
|
+
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
379
|
+
relocated_foreign=$((relocated_foreign + 1))
|
|
380
|
+
continue
|
|
381
|
+
fi
|
|
351
382
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
383
|
+
mkdir -p "$owner_worktree_root"
|
|
384
|
+
if git -C "$owner_repo_root" worktree move "$entry" "$target_path" >/dev/null 2>&1; then
|
|
385
|
+
relocated_foreign=$((relocated_foreign + 1))
|
|
386
|
+
else
|
|
387
|
+
skipped_foreign=$((skipped_foreign + 1))
|
|
388
|
+
echo "[agent-worktree-prune] Failed to relocate foreign worktree: ${entry}" >&2
|
|
389
|
+
fi
|
|
390
|
+
done
|
|
359
391
|
done
|
|
360
392
|
}
|
|
361
393
|
|
|
@@ -371,7 +403,9 @@ process_entry() {
|
|
|
371
403
|
local branch_ref="$2"
|
|
372
404
|
|
|
373
405
|
[[ -z "$wt" ]] && return
|
|
374
|
-
|
|
406
|
+
if ! is_managed_worktree_path "$wt"; then
|
|
407
|
+
return
|
|
408
|
+
fi
|
|
375
409
|
|
|
376
410
|
local branch=""
|
|
377
411
|
if [[ -n "$branch_ref" ]]; then
|
|
@@ -249,6 +249,35 @@ resolve_start_ref() {
|
|
|
249
249
|
return 1
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
+
origin_remote_looks_like_github() {
|
|
253
|
+
local wt="$1"
|
|
254
|
+
local origin_url=""
|
|
255
|
+
origin_url="$(git -C "$wt" remote get-url origin 2>/dev/null || true)"
|
|
256
|
+
[[ -n "$origin_url" && "$origin_url" =~ github\.com[:/] ]]
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
auto_finish_context_is_ready() {
|
|
260
|
+
local wt="$1"
|
|
261
|
+
local gh_bin="${GUARDEX_GH_BIN:-gh}"
|
|
262
|
+
|
|
263
|
+
if ! git -C "$wt" remote get-url origin >/dev/null 2>&1; then
|
|
264
|
+
return 1
|
|
265
|
+
fi
|
|
266
|
+
if ! command -v "$gh_bin" >/dev/null 2>&1; then
|
|
267
|
+
return 1
|
|
268
|
+
fi
|
|
269
|
+
|
|
270
|
+
if [[ -n "${GUARDEX_GH_BIN:-}" ]]; then
|
|
271
|
+
return 0
|
|
272
|
+
fi
|
|
273
|
+
|
|
274
|
+
if ! origin_remote_looks_like_github "$wt"; then
|
|
275
|
+
return 1
|
|
276
|
+
fi
|
|
277
|
+
|
|
278
|
+
"$gh_bin" auth status >/dev/null 2>&1
|
|
279
|
+
}
|
|
280
|
+
|
|
252
281
|
restore_repo_branch_if_changed() {
|
|
253
282
|
local expected_branch="$1"
|
|
254
283
|
if [[ -z "$expected_branch" || "$expected_branch" == "HEAD" ]]; then
|
|
@@ -372,6 +401,17 @@ has_origin_remote() {
|
|
|
372
401
|
git -C "$repo_root" remote get-url origin >/dev/null 2>&1
|
|
373
402
|
}
|
|
374
403
|
|
|
404
|
+
origin_remote_supports_pr_finish() {
|
|
405
|
+
local origin_url
|
|
406
|
+
origin_url="$(git -C "$repo_root" remote get-url origin 2>/dev/null || true)"
|
|
407
|
+
case "$origin_url" in
|
|
408
|
+
''|/*|./*|../*|file://*)
|
|
409
|
+
return 1
|
|
410
|
+
;;
|
|
411
|
+
esac
|
|
412
|
+
return 0
|
|
413
|
+
}
|
|
414
|
+
|
|
375
415
|
resolve_worktree_base_branch() {
|
|
376
416
|
local _wt="$1"
|
|
377
417
|
if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -n "$BASE_BRANCH" ]]; then
|
|
@@ -685,7 +725,12 @@ run_finish_flow() {
|
|
|
685
725
|
echo "[codex-agent] Auto-finish requires GitHub CLI for PR flow; command not found: ${GUARDEX_GH_BIN:-gh}" >&2
|
|
686
726
|
return 2
|
|
687
727
|
fi
|
|
688
|
-
|
|
728
|
+
if origin_remote_supports_pr_finish; then
|
|
729
|
+
finish_args+=(--via-pr)
|
|
730
|
+
else
|
|
731
|
+
echo "[codex-agent] Origin remote does not provide a mergeable PR surface; skipping auto-finish merge/PR pipeline." >&2
|
|
732
|
+
return 2
|
|
733
|
+
fi
|
|
689
734
|
else
|
|
690
735
|
echo "[codex-agent] No origin remote detected; skipping auto-finish merge/PR pipeline." >&2
|
|
691
736
|
return 2
|
|
@@ -764,7 +809,9 @@ if [[ "$AUTO_FINISH" -eq 1 && -n "$worktree_branch" && "$worktree_branch" != "HE
|
|
|
764
809
|
else
|
|
765
810
|
echo "[codex-agent] Auto-finish enabled: commit -> push/PR -> merge (keep branch/worktree)."
|
|
766
811
|
fi
|
|
767
|
-
if
|
|
812
|
+
if ! auto_finish_context_is_ready "$worktree_path"; then
|
|
813
|
+
echo "[codex-agent] Auto-finish skipped for '${worktree_branch}' (no mergeable remote context)." >&2
|
|
814
|
+
elif auto_commit_worktree_changes "$worktree_path" "$worktree_branch"; then
|
|
768
815
|
if run_finish_flow "$worktree_path" "$worktree_branch"; then
|
|
769
816
|
auto_finish_completed=1
|
|
770
817
|
echo "[codex-agent] Auto-finish completed for '${worktree_branch}'."
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
compose_file="${GUARDEX_DOCKER_COMPOSE_FILE:-}"
|
|
6
|
+
service="${GUARDEX_DOCKER_SERVICE:-}"
|
|
7
|
+
mode="${GUARDEX_DOCKER_MODE:-auto}"
|
|
8
|
+
workdir_override="${GUARDEX_DOCKER_WORKDIR:-}"
|
|
9
|
+
|
|
10
|
+
usage() {
|
|
11
|
+
cat >&2 <<'EOF'
|
|
12
|
+
Usage: bash scripts/guardex-docker-loader.sh [--] <command...>
|
|
13
|
+
|
|
14
|
+
Environment:
|
|
15
|
+
GUARDEX_DOCKER_SERVICE=<compose-service> required unless compose defines exactly one service
|
|
16
|
+
GUARDEX_DOCKER_COMPOSE_FILE=<path> optional docker compose file override
|
|
17
|
+
GUARDEX_DOCKER_MODE=auto|exec|run default: auto
|
|
18
|
+
GUARDEX_DOCKER_WORKDIR=<path> optional working directory override inside the container
|
|
19
|
+
EOF
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
choose_compose_cmd() {
|
|
23
|
+
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
|
|
24
|
+
printf 'docker compose'
|
|
25
|
+
return 0
|
|
26
|
+
fi
|
|
27
|
+
if command -v docker-compose >/dev/null 2>&1; then
|
|
28
|
+
printf 'docker-compose'
|
|
29
|
+
return 0
|
|
30
|
+
fi
|
|
31
|
+
return 1
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
mapfile_from_lines() {
|
|
35
|
+
local raw="$1"
|
|
36
|
+
local -n out_ref="$2"
|
|
37
|
+
out_ref=()
|
|
38
|
+
while IFS= read -r line; do
|
|
39
|
+
[[ -n "$line" ]] || continue
|
|
40
|
+
out_ref+=("$line")
|
|
41
|
+
done <<<"$raw"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if [[ "${1:-}" == "--" ]]; then
|
|
45
|
+
shift
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
if [[ $# -eq 0 ]]; then
|
|
49
|
+
usage
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
if [[ "$mode" != "auto" && "$mode" != "exec" && "$mode" != "run" ]]; then
|
|
54
|
+
echo "[guardex-docker-loader] Invalid GUARDEX_DOCKER_MODE: $mode" >&2
|
|
55
|
+
usage
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
compose_cmd_raw="$(choose_compose_cmd)" || {
|
|
60
|
+
echo "[guardex-docker-loader] Docker Compose is not available. Install docker compose or docker-compose first." >&2
|
|
61
|
+
exit 1
|
|
62
|
+
}
|
|
63
|
+
IFS=' ' read -r -a compose_cmd <<<"$compose_cmd_raw"
|
|
64
|
+
compose_args=()
|
|
65
|
+
if [[ -n "$compose_file" ]]; then
|
|
66
|
+
compose_args=(-f "$compose_file")
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
cd "$repo_root"
|
|
70
|
+
|
|
71
|
+
services_raw="$("${compose_cmd[@]}" "${compose_args[@]}" config --services 2>/dev/null || true)"
|
|
72
|
+
declare -a services
|
|
73
|
+
mapfile_from_lines "$services_raw" services
|
|
74
|
+
if [[ ${#services[@]} -eq 0 ]]; then
|
|
75
|
+
echo "[guardex-docker-loader] No Docker Compose services found. Add a compose file or set GUARDEX_DOCKER_COMPOSE_FILE." >&2
|
|
76
|
+
exit 1
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
if [[ -z "$service" ]]; then
|
|
80
|
+
if [[ ${#services[@]} -eq 1 ]]; then
|
|
81
|
+
service="${services[0]}"
|
|
82
|
+
else
|
|
83
|
+
echo "[guardex-docker-loader] Multiple services found (${services[*]}). Set GUARDEX_DOCKER_SERVICE." >&2
|
|
84
|
+
exit 1
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
service_known=0
|
|
89
|
+
for candidate in "${services[@]}"; do
|
|
90
|
+
if [[ "$candidate" == "$service" ]]; then
|
|
91
|
+
service_known=1
|
|
92
|
+
break
|
|
93
|
+
fi
|
|
94
|
+
done
|
|
95
|
+
if [[ $service_known -ne 1 ]]; then
|
|
96
|
+
echo "[guardex-docker-loader] Compose service not found: $service" >&2
|
|
97
|
+
exit 1
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
run_mode="$mode"
|
|
101
|
+
if [[ "$run_mode" == "auto" ]]; then
|
|
102
|
+
run_mode="run"
|
|
103
|
+
running_raw="$("${compose_cmd[@]}" "${compose_args[@]}" ps --status running --services 2>/dev/null || true)"
|
|
104
|
+
declare -a running_services
|
|
105
|
+
mapfile_from_lines "$running_raw" running_services
|
|
106
|
+
for candidate in "${running_services[@]}"; do
|
|
107
|
+
if [[ "$candidate" == "$service" ]]; then
|
|
108
|
+
run_mode="exec"
|
|
109
|
+
break
|
|
110
|
+
fi
|
|
111
|
+
done
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
workdir_args=()
|
|
115
|
+
if [[ -n "$workdir_override" ]]; then
|
|
116
|
+
workdir_args=(-w "$workdir_override")
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
if [[ "$run_mode" == "exec" ]]; then
|
|
120
|
+
exec "${compose_cmd[@]}" "${compose_args[@]}" exec -T "${workdir_args[@]}" "$service" "$@"
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
exec "${compose_cmd[@]}" "${compose_args[@]}" run --rm -T "${workdir_args[@]}" "$service" "$@"
|