@imdeadpool/guardex 5.0.2 → 5.0.4
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/CONTRIBUTING.md +1 -0
- package/README.md +149 -303
- package/bin/multiagent-safety.js +532 -23
- package/package.json +2 -2
- package/templates/AGENTS.multiagent-safety.md +5 -3
- package/templates/githooks/pre-commit +9 -0
- package/templates/githooks/pre-push +19 -2
- package/templates/scripts/agent-branch-finish.sh +141 -8
- package/templates/scripts/agent-branch-start.sh +40 -6
- package/templates/scripts/agent-file-locks.py +1 -0
- package/templates/scripts/codex-agent.sh +21 -4
- package/templates/scripts/review-bot-watch.sh +330 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
INTERVAL_SECONDS="${MUSAFETY_REVIEW_BOT_INTERVAL_SECONDS:-30}"
|
|
5
|
+
AGENT_NAME="${MUSAFETY_REVIEW_BOT_AGENT_NAME:-guardex-review-bot}"
|
|
6
|
+
TASK_PREFIX="${MUSAFETY_REVIEW_BOT_TASK_PREFIX:-review-merge}"
|
|
7
|
+
STATE_FILE="${MUSAFETY_REVIEW_BOT_STATE_FILE:-}"
|
|
8
|
+
BASE_BRANCH="${MUSAFETY_REVIEW_BOT_BASE_BRANCH:-}"
|
|
9
|
+
ONLY_PR="${MUSAFETY_REVIEW_BOT_ONLY_PR:-}"
|
|
10
|
+
RETRY_FAILED_RAW="${MUSAFETY_REVIEW_BOT_RETRY_FAILED:-false}"
|
|
11
|
+
INCLUDE_DRAFT_RAW="${MUSAFETY_REVIEW_BOT_INCLUDE_DRAFT:-false}"
|
|
12
|
+
|
|
13
|
+
usage() {
|
|
14
|
+
cat <<'USAGE'
|
|
15
|
+
Usage: bash scripts/review-bot-watch.sh [options]
|
|
16
|
+
|
|
17
|
+
Continuously monitor GitHub pull requests targeting a base branch and dispatch
|
|
18
|
+
one Codex-agent task per newly opened/updated PR.
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
--base <branch> Base branch to watch (default: current branch)
|
|
22
|
+
--interval <seconds> Poll interval (default: 30)
|
|
23
|
+
--agent <name> Agent name for codex-agent (default: guardex-review-bot)
|
|
24
|
+
--task-prefix <prefix> Task prefix for codex-agent branches (default: review-merge)
|
|
25
|
+
--state-file <path> State file path (default: .omx/state/review-bot-watch-<base>.tsv)
|
|
26
|
+
--only-pr <number> Watch only one PR number
|
|
27
|
+
--include-draft Include draft PRs
|
|
28
|
+
--retry-failed Retry PRs that previously failed even when SHA is unchanged
|
|
29
|
+
--once Run one poll cycle and exit
|
|
30
|
+
-h, --help Show this help
|
|
31
|
+
|
|
32
|
+
Environment overrides:
|
|
33
|
+
MUSAFETY_REVIEW_BOT_PROMPT_APPEND Additional instructions appended to each Codex prompt
|
|
34
|
+
USAGE
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
normalize_bool() {
|
|
38
|
+
local raw="${1:-}"
|
|
39
|
+
local fallback="${2:-0}"
|
|
40
|
+
case "$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')" in
|
|
41
|
+
1|true|yes|on) printf '1' ;;
|
|
42
|
+
0|false|no|off) printf '0' ;;
|
|
43
|
+
'') printf '%s' "$fallback" ;;
|
|
44
|
+
*) printf '%s' "$fallback" ;;
|
|
45
|
+
esac
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
ONCE=0
|
|
49
|
+
|
|
50
|
+
while [[ $# -gt 0 ]]; do
|
|
51
|
+
case "$1" in
|
|
52
|
+
--base)
|
|
53
|
+
BASE_BRANCH="${2:-}"
|
|
54
|
+
shift 2
|
|
55
|
+
;;
|
|
56
|
+
--interval)
|
|
57
|
+
INTERVAL_SECONDS="${2:-}"
|
|
58
|
+
shift 2
|
|
59
|
+
;;
|
|
60
|
+
--agent)
|
|
61
|
+
AGENT_NAME="${2:-}"
|
|
62
|
+
shift 2
|
|
63
|
+
;;
|
|
64
|
+
--task-prefix)
|
|
65
|
+
TASK_PREFIX="${2:-}"
|
|
66
|
+
shift 2
|
|
67
|
+
;;
|
|
68
|
+
--state-file)
|
|
69
|
+
STATE_FILE="${2:-}"
|
|
70
|
+
shift 2
|
|
71
|
+
;;
|
|
72
|
+
--only-pr)
|
|
73
|
+
ONLY_PR="${2:-}"
|
|
74
|
+
shift 2
|
|
75
|
+
;;
|
|
76
|
+
--retry-failed)
|
|
77
|
+
RETRY_FAILED_RAW="true"
|
|
78
|
+
shift
|
|
79
|
+
;;
|
|
80
|
+
--include-draft)
|
|
81
|
+
INCLUDE_DRAFT_RAW="true"
|
|
82
|
+
shift
|
|
83
|
+
;;
|
|
84
|
+
--once)
|
|
85
|
+
ONCE=1
|
|
86
|
+
shift
|
|
87
|
+
;;
|
|
88
|
+
-h|--help)
|
|
89
|
+
usage
|
|
90
|
+
exit 0
|
|
91
|
+
;;
|
|
92
|
+
*)
|
|
93
|
+
echo "[review-bot-watch] Unknown option: $1" >&2
|
|
94
|
+
usage >&2
|
|
95
|
+
exit 1
|
|
96
|
+
;;
|
|
97
|
+
esac
|
|
98
|
+
done
|
|
99
|
+
|
|
100
|
+
RETRY_FAILED="$(normalize_bool "$RETRY_FAILED_RAW" "0")"
|
|
101
|
+
INCLUDE_DRAFT="$(normalize_bool "$INCLUDE_DRAFT_RAW" "0")"
|
|
102
|
+
|
|
103
|
+
if [[ ! "$INTERVAL_SECONDS" =~ ^[0-9]+$ ]] || [[ "$INTERVAL_SECONDS" -lt 5 ]]; then
|
|
104
|
+
echo "[review-bot-watch] --interval must be an integer >= 5 seconds." >&2
|
|
105
|
+
exit 1
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
if [[ -n "$ONLY_PR" ]] && [[ ! "$ONLY_PR" =~ ^[0-9]+$ ]]; then
|
|
109
|
+
echo "[review-bot-watch] --only-pr must be a numeric PR id." >&2
|
|
110
|
+
exit 1
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
114
|
+
echo "[review-bot-watch] Not inside a git repository." >&2
|
|
115
|
+
exit 1
|
|
116
|
+
fi
|
|
117
|
+
repo_root="$(git rev-parse --show-toplevel)"
|
|
118
|
+
|
|
119
|
+
if [[ -z "$BASE_BRANCH" ]]; then
|
|
120
|
+
BASE_BRANCH="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
|
121
|
+
fi
|
|
122
|
+
if [[ -z "$BASE_BRANCH" || "$BASE_BRANCH" == "HEAD" ]]; then
|
|
123
|
+
BASE_BRANCH="main"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
if ! command -v gh >/dev/null 2>&1; then
|
|
127
|
+
echo "[review-bot-watch] Missing GitHub CLI (gh)." >&2
|
|
128
|
+
echo "[review-bot-watch] Install gh and run: gh auth login" >&2
|
|
129
|
+
exit 127
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
if ! command -v codex >/dev/null 2>&1; then
|
|
133
|
+
echo "[review-bot-watch] Missing Codex CLI command: codex" >&2
|
|
134
|
+
exit 127
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
if [[ ! -x "$repo_root/scripts/codex-agent.sh" ]]; then
|
|
138
|
+
echo "[review-bot-watch] Missing scripts/codex-agent.sh. Run: gx setup" >&2
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
if ! gh auth status >/dev/null 2>&1; then
|
|
143
|
+
echo "[review-bot-watch] gh is not authenticated. Run: gh auth login" >&2
|
|
144
|
+
exit 1
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
sanitize_slug() {
|
|
148
|
+
local raw="$1"
|
|
149
|
+
local fallback="$2"
|
|
150
|
+
local slug
|
|
151
|
+
slug="$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//; s/-{2,}/-/g')"
|
|
152
|
+
if [[ -z "$slug" ]]; then
|
|
153
|
+
slug="$fallback"
|
|
154
|
+
fi
|
|
155
|
+
printf '%s' "$slug"
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
base_slug="$(sanitize_slug "$BASE_BRANCH" "base")"
|
|
159
|
+
if [[ -z "$STATE_FILE" ]]; then
|
|
160
|
+
STATE_FILE="$repo_root/.omx/state/review-bot-watch-${base_slug}.tsv"
|
|
161
|
+
fi
|
|
162
|
+
mkdir -p "$(dirname "$STATE_FILE")"
|
|
163
|
+
|
|
164
|
+
declare -A LAST_SHA
|
|
165
|
+
|
|
166
|
+
declare -A LAST_STATUS
|
|
167
|
+
|
|
168
|
+
load_state() {
|
|
169
|
+
if [[ ! -f "$STATE_FILE" ]]; then
|
|
170
|
+
return 0
|
|
171
|
+
fi
|
|
172
|
+
while IFS=$'\t' read -r pr sha status updated_at; do
|
|
173
|
+
if [[ -z "${pr:-}" ]] || [[ "${pr:0:1}" == "#" ]]; then
|
|
174
|
+
continue
|
|
175
|
+
fi
|
|
176
|
+
LAST_SHA["$pr"]="$sha"
|
|
177
|
+
LAST_STATUS["$pr"]="$status"
|
|
178
|
+
done < "$STATE_FILE"
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
save_state() {
|
|
182
|
+
{
|
|
183
|
+
echo "# pr\thead_sha\tstatus\tupdated_at"
|
|
184
|
+
for pr in "${!LAST_SHA[@]}"; do
|
|
185
|
+
printf '%s\t%s\t%s\t%s\n' "${pr}" "${LAST_SHA[$pr]}" "${LAST_STATUS[$pr]:-unknown}" "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
186
|
+
done | sort -n
|
|
187
|
+
} > "$STATE_FILE"
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
build_prompt() {
|
|
191
|
+
local pr="$1"
|
|
192
|
+
local head_branch="$2"
|
|
193
|
+
local head_sha="$3"
|
|
194
|
+
local pr_title="$4"
|
|
195
|
+
local pr_url="$5"
|
|
196
|
+
|
|
197
|
+
cat <<PROMPT
|
|
198
|
+
You are the continuous PR review+merge Codex agent.
|
|
199
|
+
|
|
200
|
+
Target PR: #${pr}
|
|
201
|
+
URL: ${pr_url}
|
|
202
|
+
Title: ${pr_title}
|
|
203
|
+
Base branch: ${BASE_BRANCH}
|
|
204
|
+
Head branch: ${head_branch}
|
|
205
|
+
Head SHA: ${head_sha}
|
|
206
|
+
|
|
207
|
+
Strict task:
|
|
208
|
+
1) Review ONLY this PR's changes using gh CLI context (gh pr view ${pr}, gh pr diff ${pr}).
|
|
209
|
+
2) If fixes are needed, implement them in your sandbox branch, run verification (at minimum npm test when available), and push your sandbox branch.
|
|
210
|
+
3) When the PR is ready and checks are green, merge this PR into ${BASE_BRANCH} with:
|
|
211
|
+
gh pr merge ${pr} --squash --delete-branch
|
|
212
|
+
4) If merge is blocked, explain the blocker and exit non-zero.
|
|
213
|
+
5) Do not touch unrelated PRs.
|
|
214
|
+
PROMPT
|
|
215
|
+
|
|
216
|
+
if [[ -n "${MUSAFETY_REVIEW_BOT_PROMPT_APPEND:-}" ]]; then
|
|
217
|
+
printf '\n%s\n' "${MUSAFETY_REVIEW_BOT_PROMPT_APPEND}"
|
|
218
|
+
fi
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
list_open_prs() {
|
|
222
|
+
gh pr list \
|
|
223
|
+
--state open \
|
|
224
|
+
--base "$BASE_BRANCH" \
|
|
225
|
+
--json number,headRefName,headRefOid,isDraft,title,url \
|
|
226
|
+
--jq '.[] | "\(.number)\t\(.headRefName)\t\(.headRefOid)\t\(.isDraft)\t\(.title | gsub("\\t"; " "))\t\(.url)"'
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
should_process_pr() {
|
|
230
|
+
local pr="$1"
|
|
231
|
+
local sha="$2"
|
|
232
|
+
|
|
233
|
+
local prev_sha="${LAST_SHA[$pr]:-}"
|
|
234
|
+
local prev_status="${LAST_STATUS[$pr]:-}"
|
|
235
|
+
|
|
236
|
+
if [[ -z "$prev_sha" ]]; then
|
|
237
|
+
return 0
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
if [[ "$prev_sha" != "$sha" ]]; then
|
|
241
|
+
return 0
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
if [[ "$prev_status" == "failed" && "$RETRY_FAILED" == "1" ]]; then
|
|
245
|
+
return 0
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
return 1
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
process_one_pr() {
|
|
252
|
+
local pr="$1"
|
|
253
|
+
local head_branch="$2"
|
|
254
|
+
local sha="$3"
|
|
255
|
+
local title="$4"
|
|
256
|
+
local url="$5"
|
|
257
|
+
|
|
258
|
+
local prompt
|
|
259
|
+
prompt="$(build_prompt "$pr" "$head_branch" "$sha" "$title" "$url")"
|
|
260
|
+
|
|
261
|
+
local task_name="${TASK_PREFIX}-pr-${pr}"
|
|
262
|
+
|
|
263
|
+
echo "[review-bot-watch] Dispatching Codex agent for PR #${pr} (${head_branch})"
|
|
264
|
+
set +e
|
|
265
|
+
bash "$repo_root/scripts/codex-agent.sh" \
|
|
266
|
+
--task "$task_name" \
|
|
267
|
+
--agent "$AGENT_NAME" \
|
|
268
|
+
--base "$BASE_BRANCH" \
|
|
269
|
+
-- exec "$prompt"
|
|
270
|
+
local exit_code="$?"
|
|
271
|
+
set -e
|
|
272
|
+
|
|
273
|
+
LAST_SHA["$pr"]="$sha"
|
|
274
|
+
if [[ "$exit_code" -eq 0 ]]; then
|
|
275
|
+
LAST_STATUS["$pr"]="success"
|
|
276
|
+
echo "[review-bot-watch] PR #${pr}: success"
|
|
277
|
+
else
|
|
278
|
+
LAST_STATUS["$pr"]="failed"
|
|
279
|
+
echo "[review-bot-watch] PR #${pr}: failed (exit=${exit_code})" >&2
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
save_state
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
load_state
|
|
286
|
+
|
|
287
|
+
echo "[review-bot-watch] Starting monitor"
|
|
288
|
+
echo "[review-bot-watch] Base branch : ${BASE_BRANCH}"
|
|
289
|
+
echo "[review-bot-watch] Interval : ${INTERVAL_SECONDS}s"
|
|
290
|
+
echo "[review-bot-watch] State file : ${STATE_FILE}"
|
|
291
|
+
if [[ -n "$ONLY_PR" ]]; then
|
|
292
|
+
echo "[review-bot-watch] Only PR : #${ONLY_PR}"
|
|
293
|
+
fi
|
|
294
|
+
|
|
295
|
+
trap 'echo "[review-bot-watch] Stopped."; exit 0' INT TERM
|
|
296
|
+
|
|
297
|
+
while true; do
|
|
298
|
+
found=0
|
|
299
|
+
while IFS=$'\t' read -r pr head_branch sha is_draft title url; do
|
|
300
|
+
if [[ -z "${pr:-}" ]]; then
|
|
301
|
+
continue
|
|
302
|
+
fi
|
|
303
|
+
|
|
304
|
+
found=1
|
|
305
|
+
|
|
306
|
+
if [[ -n "$ONLY_PR" && "$pr" != "$ONLY_PR" ]]; then
|
|
307
|
+
continue
|
|
308
|
+
fi
|
|
309
|
+
|
|
310
|
+
if [[ "$INCLUDE_DRAFT" != "1" && "$is_draft" == "true" ]]; then
|
|
311
|
+
continue
|
|
312
|
+
fi
|
|
313
|
+
|
|
314
|
+
if ! should_process_pr "$pr" "$sha"; then
|
|
315
|
+
continue
|
|
316
|
+
fi
|
|
317
|
+
|
|
318
|
+
process_one_pr "$pr" "$head_branch" "$sha" "$title" "$url"
|
|
319
|
+
done < <(list_open_prs || true)
|
|
320
|
+
|
|
321
|
+
if [[ "$found" -eq 0 ]]; then
|
|
322
|
+
echo "[review-bot-watch] No open PRs for base '${BASE_BRANCH}'."
|
|
323
|
+
fi
|
|
324
|
+
|
|
325
|
+
if [[ "$ONCE" -eq 1 ]]; then
|
|
326
|
+
break
|
|
327
|
+
fi
|
|
328
|
+
|
|
329
|
+
sleep "$INTERVAL_SECONDS"
|
|
330
|
+
done
|