@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.
- package/README.md +37 -13
- package/bin/multiagent-safety.js +307 -7
- package/package.json +1 -1
- package/templates/AGENTS.multiagent-safety.md +7 -1
- package/templates/codex/skills/guardex/SKILL.md +5 -0
- package/templates/githooks/pre-commit +22 -0
- package/templates/scripts/agent-branch-finish.sh +81 -31
- package/templates/scripts/agent-worktree-prune.sh +128 -17
- package/templates/scripts/codex-agent.sh +336 -3
|
@@ -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
|
|
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
|
-
|
|
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"
|