@imdeadpool/guardex 7.0.43 → 7.1.0
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 +26 -0
- package/package.json +2 -1
- package/skills/gx-act/SKILL.md +82 -0
- package/src/agents/inspect.js +17 -4
- package/src/agents/launch.js +10 -1
- package/src/agents/status.js +9 -6
- package/src/budget/index.js +2 -1
- package/src/cli/args.js +52 -2
- package/src/cli/commands/agents.js +364 -0
- package/src/cli/commands/bootstrap.js +92 -0
- package/src/cli/commands/branch.js +127 -0
- package/src/cli/commands/claude.js +674 -0
- package/src/cli/commands/doctor.js +268 -0
- package/src/cli/commands/finish.js +26 -0
- package/src/cli/commands/mcp.js +122 -0
- package/src/cli/commands/misc.js +304 -0
- package/src/cli/commands/pr.js +439 -0
- package/src/cli/commands/prompt.js +92 -0
- package/src/cli/commands/release.js +305 -0
- package/src/cli/commands/report.js +244 -0
- package/src/cli/commands/review.js +32 -0
- package/src/cli/commands/setup.js +242 -0
- package/src/cli/commands/status.js +338 -0
- package/src/cli/commands/watch.js +234 -0
- package/src/cli/main.js +68 -3726
- package/src/cli/shared/repo-env.js +161 -0
- package/src/cli/shared/sandbox.js +417 -0
- package/src/cli/shared/scaffolding.js +535 -0
- package/src/cli/shared/toolchain-shims.js +420 -0
- package/src/context.js +229 -11
- package/src/core/runtime.js +6 -1
- package/src/doctor/index.js +42 -13
- package/src/finish/index.js +147 -5
- package/src/finish/preflight.js +177 -0
- package/src/finish/review-gate.js +182 -0
- package/src/git/index.js +446 -4
- package/src/hooks/index.js +0 -64
- package/src/mcp/collect.js +370 -0
- package/src/mcp/server.js +157 -0
- package/src/output/index.js +67 -1
- package/src/pr-review.js +23 -0
- package/src/pr.js +381 -0
- package/src/sandbox/index.js +13 -2
- package/src/scaffold/agent-worktree-prep.js +213 -0
- package/src/scaffold/index.js +108 -10
- package/src/speckit/index.js +226 -0
- package/src/terminal/index.js +1 -76
- package/src/terminal/tmux.js +0 -1
- package/src/toolchain/index.js +20 -0
- package/templates/AGENTS.monorepo-apps.md +26 -0
- package/templates/AGENTS.multiagent-safety.md +61 -347
- package/templates/AGENTS.multiagent-safety.min.md +11 -0
- package/templates/codex/skills/gx-act/SKILL.md +82 -0
- package/templates/githooks/pre-commit +22 -19
- package/templates/scripts/agent-branch-finish.sh +8 -30
- package/templates/scripts/agent-branch-merge.sh +4 -1
- package/templates/scripts/agent-branch-start.sh +88 -3
- package/templates/scripts/agent-preflight.sh +31 -5
- package/templates/scripts/agent-worktree-prune.sh +1 -1
- package/templates/scripts/codex-agent.sh +0 -91
- package/src/agents/detect.js +0 -160
- package/src/cockpit/keybindings.js +0 -224
- package/src/cockpit/layout.js +0 -224
|
@@ -17,16 +17,11 @@ WAIT_TIMEOUT_SECONDS_RAW="${GUARDEX_FINISH_WAIT_TIMEOUT_SECONDS:-1800}"
|
|
|
17
17
|
WAIT_POLL_SECONDS_RAW="${GUARDEX_FINISH_WAIT_POLL_SECONDS:-10}"
|
|
18
18
|
PARENT_GITLINK_AUTO_COMMIT_RAW="${GUARDEX_FINISH_PARENT_GITLINK_AUTO_COMMIT:-true}"
|
|
19
19
|
AUTO_RESOLVE_MODE_RAW="${GUARDEX_FINISH_AUTO_RESOLVE:-none}"
|
|
20
|
-
AUTO_RESOLVE_SAFE_GLOBS_DEFAULT='.omc/**:.omx/state/**:.dev-ports.json:apps/logs/**:.
|
|
20
|
+
AUTO_RESOLVE_SAFE_GLOBS_DEFAULT='.omc/**:.omx/state/**:.dev-ports.json:apps/logs/**:.codex/settings.local.json:.claude/settings.local.json:.codex/state/**:.claude/state/**'
|
|
21
21
|
AUTO_RESOLVE_SAFE_GLOBS_RAW="${GUARDEX_FINISH_AUTO_RESOLVE_SAFE_GLOBS-$AUTO_RESOLVE_SAFE_GLOBS_DEFAULT}"
|
|
22
22
|
PREFLIGHT_ENABLED_RAW="${GUARDEX_FINISH_PREFLIGHT:-true}"
|
|
23
23
|
PREFLIGHT_SCRIPT_RAW="${GUARDEX_FINISH_PREFLIGHT_SCRIPT:-scripts/agent-preflight.sh}"
|
|
24
24
|
AUTO_PROMOTE_DRAFT_RAW="${GUARDEX_FINISH_AUTO_PROMOTE:-true}"
|
|
25
|
-
# --skip-checks (or GUARDEX_FINISH_SKIP_CHECKS=1): append `--admin` to every
|
|
26
|
-
# `gh pr merge` invocation, bypassing required status checks. Requires admin
|
|
27
|
-
# permission on the target repo. Use when CI is wedged (e.g. runner billing,
|
|
28
|
-
# infrastructure outage) and the operator has accepted the risk.
|
|
29
|
-
SKIP_CHECKS_RAW="${GUARDEX_FINISH_SKIP_CHECKS:-false}"
|
|
30
25
|
|
|
31
26
|
run_guardex_cli() {
|
|
32
27
|
if [[ -n "$CLI_ENTRY" ]]; then
|
|
@@ -152,10 +147,13 @@ WAIT_POLL_SECONDS="$(normalize_int "$WAIT_POLL_SECONDS_RAW" "10" "0")"
|
|
|
152
147
|
PARENT_GITLINK_AUTO_COMMIT="$(normalize_bool "$PARENT_GITLINK_AUTO_COMMIT_RAW" "1")"
|
|
153
148
|
PREFLIGHT_ENABLED="$(normalize_bool "$PREFLIGHT_ENABLED_RAW" "1")"
|
|
154
149
|
AUTO_PROMOTE_DRAFT="$(normalize_bool "$AUTO_PROMOTE_DRAFT_RAW" "1")"
|
|
155
|
-
SKIP_CHECKS="$(normalize_bool "$SKIP_CHECKS_RAW" "0")"
|
|
156
150
|
|
|
157
151
|
while [[ $# -gt 0 ]]; do
|
|
158
152
|
case "$1" in
|
|
153
|
+
-h|--help)
|
|
154
|
+
echo "Usage: $0 [--base <branch>] [--branch <branch>] [--no-push] [--cleanup|--no-cleanup] [--wait-for-merge|--no-wait-for-merge] [--wait-timeout-seconds <n>] [--wait-poll-seconds <n>] [--parent-gitlink-commit|--no-parent-gitlink-commit] [--keep-remote-branch|--delete-remote-branch] [--mode auto|direct|pr|--via-pr|--direct-only] [--auto-resolve[=none|safe|full]|--no-auto-resolve] [--no-preflight|--preflight] [--preflight-script <path>] [--no-auto-promote|--auto-promote]"
|
|
155
|
+
exit 0
|
|
156
|
+
;;
|
|
159
157
|
--base)
|
|
160
158
|
BASE_BRANCH="${2:-}"
|
|
161
159
|
BASE_BRANCH_EXPLICIT=1
|
|
@@ -252,14 +250,6 @@ while [[ $# -gt 0 ]]; do
|
|
|
252
250
|
PREFLIGHT_SCRIPT_RAW="${2:-}"
|
|
253
251
|
shift 2
|
|
254
252
|
;;
|
|
255
|
-
--skip-checks)
|
|
256
|
-
SKIP_CHECKS=1
|
|
257
|
-
shift
|
|
258
|
-
;;
|
|
259
|
-
--no-skip-checks)
|
|
260
|
-
SKIP_CHECKS=0
|
|
261
|
-
shift
|
|
262
|
-
;;
|
|
263
253
|
--no-auto-promote)
|
|
264
254
|
AUTO_PROMOTE_DRAFT_RAW="false"
|
|
265
255
|
shift
|
|
@@ -1161,13 +1151,9 @@ wait_for_pr_merge() {
|
|
|
1161
1151
|
deadline=$(( $(date +%s) + WAIT_TIMEOUT_SECONDS ))
|
|
1162
1152
|
local wait_notice_printed=0
|
|
1163
1153
|
local merge_output=""
|
|
1164
|
-
local -a merge_flags=(--squash --delete-branch)
|
|
1165
|
-
if [[ "$SKIP_CHECKS" -eq 1 ]]; then
|
|
1166
|
-
merge_flags+=(--admin)
|
|
1167
|
-
fi
|
|
1168
1154
|
|
|
1169
1155
|
while true; do
|
|
1170
|
-
if merge_output="$("$GH_BIN" pr merge "$SOURCE_BRANCH"
|
|
1156
|
+
if merge_output="$("$GH_BIN" pr merge "$SOURCE_BRANCH" --squash --delete-branch 2>&1)"; then
|
|
1171
1157
|
return 0
|
|
1172
1158
|
fi
|
|
1173
1159
|
if is_local_branch_delete_error "$merge_output"; then
|
|
@@ -1273,11 +1259,7 @@ run_pr_flow() {
|
|
|
1273
1259
|
maybe_auto_promote_pr "$pr_url"
|
|
1274
1260
|
|
|
1275
1261
|
merge_output=""
|
|
1276
|
-
|
|
1277
|
-
if [[ "$SKIP_CHECKS" -eq 1 ]]; then
|
|
1278
|
-
merge_flags+=(--admin)
|
|
1279
|
-
fi
|
|
1280
|
-
if merge_output="$("$GH_BIN" pr merge "$SOURCE_BRANCH" "${merge_flags[@]}" 2>&1)"; then
|
|
1262
|
+
if merge_output="$("$GH_BIN" pr merge "$SOURCE_BRANCH" --squash --delete-branch 2>&1)"; then
|
|
1281
1263
|
return 0
|
|
1282
1264
|
fi
|
|
1283
1265
|
if is_local_branch_delete_error "$merge_output"; then
|
|
@@ -1291,11 +1273,7 @@ run_pr_flow() {
|
|
|
1291
1273
|
fi
|
|
1292
1274
|
|
|
1293
1275
|
auto_output=""
|
|
1294
|
-
|
|
1295
|
-
if [[ "$SKIP_CHECKS" -eq 1 ]]; then
|
|
1296
|
-
auto_flags+=(--admin)
|
|
1297
|
-
fi
|
|
1298
|
-
if auto_output="$("$GH_BIN" pr merge "$SOURCE_BRANCH" "${auto_flags[@]}" 2>&1)"; then
|
|
1276
|
+
if auto_output="$("$GH_BIN" pr merge "$SOURCE_BRANCH" --squash --delete-branch --auto 2>&1)"; then
|
|
1299
1277
|
echo "[agent-branch-finish] PR auto-merge enabled; waiting for required checks/reviews." >&2
|
|
1300
1278
|
return 2
|
|
1301
1279
|
fi
|
|
@@ -281,7 +281,10 @@ if [[ -z "$TARGET_BRANCH" ]]; then
|
|
|
281
281
|
start_output=""
|
|
282
282
|
if ! start_output="$(
|
|
283
283
|
cd "$repo_root"
|
|
284
|
-
|
|
284
|
+
# An integration lane is inherently cross-cutting, so pin it to T3 (full
|
|
285
|
+
# change + plan workspace) regardless of the branch-start default (now T1).
|
|
286
|
+
GUARDEX_OPENSPEC_AUTO_INIT=1 GUARDEX_OPENSPEC_TIER="${GUARDEX_OPENSPEC_TIER:-T3}" \
|
|
287
|
+
run_guardex_cli branch start "$TASK_NAME" "$AGENT_NAME" "$BASE_BRANCH" 2>&1
|
|
285
288
|
)"; then
|
|
286
289
|
printf '%s\n' "$start_output" >&2
|
|
287
290
|
exit 1
|
|
@@ -14,10 +14,13 @@ OPENSPEC_PLAN_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_PLAN_SLUG:-}"
|
|
|
14
14
|
OPENSPEC_CHANGE_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CHANGE_SLUG:-}"
|
|
15
15
|
OPENSPEC_CAPABILITY_SLUG_OVERRIDE="${GUARDEX_OPENSPEC_CAPABILITY_SLUG:-}"
|
|
16
16
|
OPENSPEC_MASTERPLAN_LABEL_RAW="${GUARDEX_OPENSPEC_MASTERPLAN_LABEL-masterplan}"
|
|
17
|
-
|
|
17
|
+
# Default tier is T1 (notes.md only, minimal scaffold): most tasks are small,
|
|
18
|
+
# and a full T3 plan workspace costs thousands of tokens an agent never reads.
|
|
19
|
+
# Escalate explicitly with --tier T2 (behavior change) or T3 (plan-driven).
|
|
20
|
+
OPENSPEC_TIER_RAW="${GUARDEX_OPENSPEC_TIER:-T1}"
|
|
18
21
|
REUSE_EXISTING_RAW="${GUARDEX_BRANCH_START_REUSE_EXISTING:-true}"
|
|
19
22
|
AUTO_TRANSFER_ENABLED_RAW="${GUARDEX_AUTO_TRANSFER:-true}"
|
|
20
|
-
AUTO_TRANSFER_EXCLUDE_DEFAULT='.omc/**:.omx/state/**:.dev-ports.json:apps/logs/**:.
|
|
23
|
+
AUTO_TRANSFER_EXCLUDE_DEFAULT='.omc/**:.omx/state/**:.dev-ports.json:apps/logs/**:.codex/settings.local.json:.claude/settings.local.json:.codex/state/**:.claude/state/**'
|
|
21
24
|
AUTO_TRANSFER_EXCLUDE_RAW="${GUARDEX_AUTO_TRANSFER_EXCLUDE-$AUTO_TRANSFER_EXCLUDE_DEFAULT}"
|
|
22
25
|
PRINT_NAME_ONLY=0
|
|
23
26
|
POSITIONAL_ARGS=()
|
|
@@ -39,8 +42,40 @@ run_guardex_cli() {
|
|
|
39
42
|
return 127
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
print_usage() {
|
|
46
|
+
cat <<'USAGE'
|
|
47
|
+
Usage: agent-branch-start [task] [agent] [base] [options]
|
|
48
|
+
|
|
49
|
+
Start an isolated agent/* branch + worktree for a task.
|
|
50
|
+
|
|
51
|
+
Positional:
|
|
52
|
+
task Task name/slug (default: "task")
|
|
53
|
+
agent Agent name (default: "agent")
|
|
54
|
+
base Base branch to fork from (default: repo default)
|
|
55
|
+
|
|
56
|
+
Options:
|
|
57
|
+
--task <name> Task name/slug
|
|
58
|
+
--agent <name> Agent name
|
|
59
|
+
--base <branch> Base branch to fork from
|
|
60
|
+
--worktree-root <p> Worktree root dir (default: .omx/agent-worktrees)
|
|
61
|
+
--reuse-existing Reuse an existing matching worktree (default)
|
|
62
|
+
--new Force a fresh worktree instead of reusing
|
|
63
|
+
--tier <T1|T2|T3> OpenSpec tier for scaffolding (default T1; T2 for a
|
|
64
|
+
behavior change, T3 for plan-driven work)
|
|
65
|
+
--transfer Auto-transfer uncommitted changes into the worktree (default)
|
|
66
|
+
--no-transfer Do not auto-transfer uncommitted changes
|
|
67
|
+
--transfer-exclude <globs> Colon-separated globs to exclude from transfer
|
|
68
|
+
--print-name-only Print the computed branch name and exit
|
|
69
|
+
-h, --help Show this help and exit
|
|
70
|
+
USAGE
|
|
71
|
+
}
|
|
72
|
+
|
|
42
73
|
while [[ $# -gt 0 ]]; do
|
|
43
74
|
case "$1" in
|
|
75
|
+
-h|--help)
|
|
76
|
+
print_usage
|
|
77
|
+
exit 0
|
|
78
|
+
;;
|
|
44
79
|
--task)
|
|
45
80
|
TASK_NAME="${2:-task}"
|
|
46
81
|
shift 2
|
|
@@ -292,7 +327,7 @@ normalize_tier() {
|
|
|
292
327
|
esac
|
|
293
328
|
}
|
|
294
329
|
|
|
295
|
-
if ! OPENSPEC_TIER="$(normalize_tier "$OPENSPEC_TIER_RAW" "
|
|
330
|
+
if ! OPENSPEC_TIER="$(normalize_tier "$OPENSPEC_TIER_RAW" "T1")"; then
|
|
296
331
|
echo "[agent-branch-start] Unsupported OpenSpec tier: ${OPENSPEC_TIER_RAW}" >&2
|
|
297
332
|
exit 1
|
|
298
333
|
fi
|
|
@@ -417,6 +452,20 @@ print_agent_next_steps() {
|
|
|
417
452
|
base_branch="main"
|
|
418
453
|
fi
|
|
419
454
|
|
|
455
|
+
# Pre-`Ready:` post-condition check. `git worktree add` has been observed
|
|
456
|
+
# to return 0 with the worktree dir still missing on disk (race condition
|
|
457
|
+
# during the OpenSpec / dependency-symlink phase between `worktree add`
|
|
458
|
+
# and now). If we print `Ready:` in that state, callers cd into a
|
|
459
|
+
# vanished directory and lose subsequent edits silently. Surface it as
|
|
460
|
+
# an exit-1 error instead — operator can retry + `git worktree prune`.
|
|
461
|
+
if [[ ! -d "$worktree_path" || ! -e "$worktree_path/.git" ]]; then
|
|
462
|
+
printf '[agent-branch-start] ERROR: worktree did not materialize on disk before Ready:\n' >&2
|
|
463
|
+
printf '[agent-branch-start] branch: %s\n' "$branch_name" >&2
|
|
464
|
+
printf '[agent-branch-start] expected: %s\n' "$worktree_path" >&2
|
|
465
|
+
printf '[agent-branch-start] Run `git worktree prune` to clear ghost entries, then retry.\n' >&2
|
|
466
|
+
exit 1
|
|
467
|
+
fi
|
|
468
|
+
|
|
420
469
|
echo "[agent-branch-start] Ready:"
|
|
421
470
|
echo " branch: ${branch_name}"
|
|
422
471
|
echo " worktree: ${worktree_path}"
|
|
@@ -494,6 +543,25 @@ managed_worktree_roots() {
|
|
|
494
543
|
done
|
|
495
544
|
}
|
|
496
545
|
|
|
546
|
+
branch_published_then_remote_pruned() {
|
|
547
|
+
# Detect the post-`gx branch finish --via-pr --cleanup` state: the agent
|
|
548
|
+
# branch was published (so upstream config is set), but the remote-tracking
|
|
549
|
+
# ref no longer exists (because `push origin --delete` ran during cleanup).
|
|
550
|
+
# That combination only arises after a finish that successfully merged the
|
|
551
|
+
# PR and pruned the remote branch — a freshly-created agent branch never
|
|
552
|
+
# has an upstream until publish, so this never false-positives on the
|
|
553
|
+
# "started, dirty, no commits yet" case that we want to keep reusable.
|
|
554
|
+
local repo="$1"
|
|
555
|
+
local branch="$2"
|
|
556
|
+
local upstream
|
|
557
|
+
upstream="$(git -C "$repo" config --get "branch.${branch}.remote" 2>/dev/null || true)"
|
|
558
|
+
[[ -n "$upstream" ]] || return 1
|
|
559
|
+
if git -C "$repo" show-ref --verify --quiet "refs/remotes/${upstream}/${branch}"; then
|
|
560
|
+
return 1
|
|
561
|
+
fi
|
|
562
|
+
return 0
|
|
563
|
+
}
|
|
564
|
+
|
|
497
565
|
find_matching_dirty_agent_worktree() {
|
|
498
566
|
local repo="$1"
|
|
499
567
|
local worktree_root_rel="$2"
|
|
@@ -514,6 +582,13 @@ find_matching_dirty_agent_worktree() {
|
|
|
514
582
|
fi
|
|
515
583
|
[[ "$branch" == "agent/${agent_slug}/"* ]] || continue
|
|
516
584
|
has_local_changes "$entry" || continue
|
|
585
|
+
# Skip merged-and-cleaned worktrees that happen to still be on disk
|
|
586
|
+
# (e.g. operator's shell is cwd'd inside; cleanup deferred). Reusing
|
|
587
|
+
# such a worktree would silently hand the next agent a stale HEAD.
|
|
588
|
+
if branch_published_then_remote_pruned "$repo" "$branch"; then
|
|
589
|
+
echo "[agent-branch-start] Skipping merged-and-cleaned worktree: ${entry} (branch ${branch} has no remote tracking ref)" >&2
|
|
590
|
+
continue
|
|
591
|
+
fi
|
|
517
592
|
|
|
518
593
|
descriptor="${branch#agent/${agent_slug}/}"
|
|
519
594
|
score="$(token_match_score "$task_slug" "$descriptor")"
|
|
@@ -864,6 +939,13 @@ if ! worktree_add_output="$(git -C "$repo_root" worktree add -b "$branch_name" "
|
|
|
864
939
|
printf '%s\n' "$worktree_add_output" >&2
|
|
865
940
|
exit 1
|
|
866
941
|
fi
|
|
942
|
+
# git worktree add has been observed to exit 0 while the target dir is
|
|
943
|
+
# missing — verify before downstream init runs against a phantom path.
|
|
944
|
+
if [[ ! -d "$worktree_path" || ! -e "$worktree_path/.git" ]]; then
|
|
945
|
+
printf '[agent-branch-start] ERROR: git worktree add reported success but %s is not a valid worktree.\n' "$worktree_path" >&2
|
|
946
|
+
printf '%s\n' "$worktree_add_output" >&2
|
|
947
|
+
exit 1
|
|
948
|
+
fi
|
|
867
949
|
git -C "$repo_root" config "branch.${branch_name}.guardexBase" "$BASE_BRANCH" >/dev/null 2>&1 || true
|
|
868
950
|
git -C "$repo_root" config "branch.${branch_name}.guardexWorktreeRoot" "$WORKTREE_ROOT_REL" >/dev/null 2>&1 || true
|
|
869
951
|
# Fresh agent branches should start unpublished; clear any inherited base-branch tracking.
|
|
@@ -899,6 +981,9 @@ fi
|
|
|
899
981
|
echo "[agent-branch-start] Created branch: ${branch_name}"
|
|
900
982
|
echo "[agent-branch-start] Worktree: ${worktree_path}"
|
|
901
983
|
echo "[agent-branch-start] OpenSpec tier: ${OPENSPEC_TIER}"
|
|
984
|
+
if [[ "$OPENSPEC_TIER" == "T1" ]]; then
|
|
985
|
+
echo "[agent-branch-start] T1 minimal scaffold (notes.md). Escalate: --tier T2 for a behavior change, T3 for plan-driven work."
|
|
986
|
+
fi
|
|
902
987
|
if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
|
|
903
988
|
echo "[agent-branch-start] OpenSpec change: skipped (GUARDEX_OPENSPEC_AUTO_INIT disabled)"
|
|
904
989
|
elif [[ "$OPENSPEC_SKIP_CHANGE" -eq 1 ]]; then
|
|
@@ -22,17 +22,43 @@ set -euo pipefail
|
|
|
22
22
|
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
23
23
|
cd "$repo_root"
|
|
24
24
|
|
|
25
|
-
ran=0
|
|
25
|
+
ran=0 # steps that PASSED
|
|
26
|
+
attempted=0 # steps that RAN (pass or fail) — drives stack detection
|
|
26
27
|
fail=0
|
|
28
|
+
# Quiet by default: a green `npm test` run can be hundreds of lines of TAP that
|
|
29
|
+
# floods the agent's context on every `gx branch finish`. Capture each step's
|
|
30
|
+
# output, print a one-line summary on success, and surface only the tail on
|
|
31
|
+
# failure (where it is actually useful). Stream full output live with
|
|
32
|
+
# GUARDEX_PREFLIGHT_VERBOSE=1.
|
|
33
|
+
GUARDEX_PREFLIGHT_FAIL_TAIL="${GUARDEX_PREFLIGHT_FAIL_TAIL:-40}"
|
|
27
34
|
run_step() {
|
|
28
35
|
local label="$1"
|
|
29
36
|
shift
|
|
30
37
|
echo "[agent-preflight] -> $label"
|
|
31
|
-
|
|
38
|
+
attempted=$((attempted + 1))
|
|
39
|
+
if [[ "${GUARDEX_PREFLIGHT_VERBOSE:-0}" == "1" ]]; then
|
|
40
|
+
if "$@"; then
|
|
41
|
+
ran=$((ran + 1))
|
|
42
|
+
echo "[agent-preflight] ok"
|
|
43
|
+
else
|
|
44
|
+
echo "[agent-preflight] FAIL: $label" >&2
|
|
45
|
+
fail=1
|
|
46
|
+
fi
|
|
47
|
+
return 0
|
|
48
|
+
fi
|
|
49
|
+
local out rc
|
|
50
|
+
# `if` keeps `set -e` from aborting on a failing step before we capture rc.
|
|
51
|
+
if out="$("$@" 2>&1)"; then
|
|
52
|
+
rc=0
|
|
53
|
+
else
|
|
54
|
+
rc=$?
|
|
55
|
+
fi
|
|
56
|
+
if [[ "$rc" -eq 0 ]]; then
|
|
32
57
|
ran=$((ran + 1))
|
|
33
|
-
echo "[agent-preflight] ok"
|
|
58
|
+
echo "[agent-preflight] ok ($(printf '%s\n' "$out" | wc -l | tr -d ' ') lines suppressed; GUARDEX_PREFLIGHT_VERBOSE=1 to show)"
|
|
34
59
|
else
|
|
35
|
-
echo "[agent-preflight] FAIL: $label" >&2
|
|
60
|
+
echo "[agent-preflight] FAIL: $label (exit $rc) — last ${GUARDEX_PREFLIGHT_FAIL_TAIL} lines:" >&2
|
|
61
|
+
printf '%s\n' "$out" | tail -n "$GUARDEX_PREFLIGHT_FAIL_TAIL" >&2
|
|
36
62
|
fail=1
|
|
37
63
|
fi
|
|
38
64
|
}
|
|
@@ -74,7 +100,7 @@ if [[ -f pyproject.toml ]] && command -v ruff >/dev/null 2>&1; then
|
|
|
74
100
|
run_step "ruff check" ruff check .
|
|
75
101
|
fi
|
|
76
102
|
|
|
77
|
-
if [[ "$
|
|
103
|
+
if [[ "$attempted" -eq 0 ]]; then
|
|
78
104
|
echo "[agent-preflight] No recognized project stack detected; skipping checks." >&2
|
|
79
105
|
exit 0
|
|
80
106
|
fi
|
|
@@ -253,7 +253,7 @@ branch_has_worktree() {
|
|
|
253
253
|
# content matches these are considered clean for prune purposes. Mirrors the
|
|
254
254
|
# auto-transfer + auto-resolve allowlist from PRs #546/#547 (state-file globs
|
|
255
255
|
# never carry authoritative content out of an agent branch).
|
|
256
|
-
WORKTREE_STATE_EXCLUDE_GLOBS_DEFAULT='.omc/**:.omx/state/**:.dev-ports.json:apps/logs/**:.
|
|
256
|
+
WORKTREE_STATE_EXCLUDE_GLOBS_DEFAULT='.omc/**:.omx/state/**:.dev-ports.json:apps/logs/**:.codex/settings.local.json:.claude/settings.local.json:.codex/state/**:.claude/state/**'
|
|
257
257
|
WORKTREE_STATE_EXCLUDE_GLOBS_RAW="${GUARDEX_PRUNE_STATE_EXCLUDE_GLOBS-$WORKTREE_STATE_EXCLUDE_GLOBS_DEFAULT}"
|
|
258
258
|
|
|
259
259
|
build_state_exclude_pathspec_args() {
|
|
@@ -324,7 +324,6 @@ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
|
324
324
|
exit 1
|
|
325
325
|
fi
|
|
326
326
|
repo_root="$(git rev-parse --show-toplevel)"
|
|
327
|
-
active_session_state_script="${repo_root}/scripts/agent-session-state.js"
|
|
328
327
|
|
|
329
328
|
guardex_env_helper="${repo_root}/scripts/guardex-env.sh"
|
|
330
329
|
if [[ -f "$guardex_env_helper" ]]; then
|
|
@@ -638,78 +637,6 @@ has_origin_remote() {
|
|
|
638
637
|
git -C "$repo_root" remote get-url origin >/dev/null 2>&1
|
|
639
638
|
}
|
|
640
639
|
|
|
641
|
-
run_active_session_state() {
|
|
642
|
-
local action="$1"
|
|
643
|
-
shift
|
|
644
|
-
|
|
645
|
-
if [[ ! -f "$active_session_state_script" ]]; then
|
|
646
|
-
return 0
|
|
647
|
-
fi
|
|
648
|
-
if ! command -v "$NODE_BIN" >/dev/null 2>&1; then
|
|
649
|
-
return 0
|
|
650
|
-
fi
|
|
651
|
-
|
|
652
|
-
"$NODE_BIN" "$active_session_state_script" "$action" "$@" >/dev/null 2>&1 || true
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
record_active_session_state() {
|
|
656
|
-
local wt="$1"
|
|
657
|
-
local branch="$2"
|
|
658
|
-
|
|
659
|
-
run_active_session_state \
|
|
660
|
-
start \
|
|
661
|
-
--repo "$repo_root" \
|
|
662
|
-
--branch "$branch" \
|
|
663
|
-
--task "$TASK_NAME" \
|
|
664
|
-
--agent "$AGENT_NAME" \
|
|
665
|
-
--worktree "$wt" \
|
|
666
|
-
--pid "$$" \
|
|
667
|
-
--cli "$CODEX_BIN" \
|
|
668
|
-
--task-mode "$TASK_MODE" \
|
|
669
|
-
--openspec-tier "$OPENSPEC_TIER" \
|
|
670
|
-
--routing-reason "$TASK_ROUTING_REASON"
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
clear_active_session_state() {
|
|
674
|
-
local branch="$1"
|
|
675
|
-
run_active_session_state stop --repo "$repo_root" --branch "$branch"
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
heartbeat_active_session_state() {
|
|
679
|
-
local branch="$1"
|
|
680
|
-
run_active_session_state heartbeat --repo "$repo_root" --branch "$branch" --state working
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
normalize_heartbeat_interval_seconds() {
|
|
684
|
-
local raw="${GUARDEX_ACTIVE_SESSION_HEARTBEAT_INTERVAL_SECONDS:-15}"
|
|
685
|
-
if [[ "$raw" =~ ^[0-9]+$ ]] && [[ "$raw" -ge 1 ]]; then
|
|
686
|
-
printf '%s' "$raw"
|
|
687
|
-
return 0
|
|
688
|
-
fi
|
|
689
|
-
printf '15'
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
start_active_session_heartbeat() {
|
|
693
|
-
local branch="$1"
|
|
694
|
-
local interval
|
|
695
|
-
interval="$(normalize_heartbeat_interval_seconds)"
|
|
696
|
-
(
|
|
697
|
-
while true; do
|
|
698
|
-
sleep "$interval" || break
|
|
699
|
-
heartbeat_active_session_state "$branch"
|
|
700
|
-
done
|
|
701
|
-
) &
|
|
702
|
-
active_session_heartbeat_pid="$!"
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
stop_active_session_heartbeat() {
|
|
706
|
-
if [[ -n "${active_session_heartbeat_pid:-}" ]]; then
|
|
707
|
-
kill "$active_session_heartbeat_pid" >/dev/null 2>&1 || true
|
|
708
|
-
wait "$active_session_heartbeat_pid" >/dev/null 2>&1 || true
|
|
709
|
-
active_session_heartbeat_pid=""
|
|
710
|
-
fi
|
|
711
|
-
}
|
|
712
|
-
|
|
713
640
|
origin_remote_supports_pr_finish() {
|
|
714
641
|
local origin_url
|
|
715
642
|
origin_url="$(git -C "$repo_root" remote get-url origin 2>/dev/null || true)"
|
|
@@ -1103,22 +1030,6 @@ fi
|
|
|
1103
1030
|
|
|
1104
1031
|
echo "[codex-agent] Task routing: $(describe_task_routing) (${TASK_ROUTING_REASON})"
|
|
1105
1032
|
|
|
1106
|
-
active_session_recorded=0
|
|
1107
|
-
active_session_heartbeat_pid=""
|
|
1108
|
-
cleanup_active_session_state_on_exit() {
|
|
1109
|
-
set +e
|
|
1110
|
-
if [[ "${active_session_recorded:-0}" -eq 1 && -n "${worktree_branch:-}" && "${worktree_branch:-}" != "HEAD" ]]; then
|
|
1111
|
-
stop_active_session_heartbeat
|
|
1112
|
-
clear_active_session_state "$worktree_branch"
|
|
1113
|
-
active_session_recorded=0
|
|
1114
|
-
fi
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
record_active_session_state "$worktree_path" "$worktree_branch"
|
|
1118
|
-
active_session_recorded=1
|
|
1119
|
-
start_active_session_heartbeat "$worktree_branch"
|
|
1120
|
-
trap cleanup_active_session_state_on_exit EXIT INT TERM
|
|
1121
|
-
|
|
1122
1033
|
echo "[codex-agent] Launching ${CODEX_BIN} in sandbox: $worktree_path"
|
|
1123
1034
|
cd "$worktree_path"
|
|
1124
1035
|
set +e
|
|
@@ -1131,8 +1042,6 @@ codex_exit="$?"
|
|
|
1131
1042
|
set -e
|
|
1132
1043
|
|
|
1133
1044
|
cd "$repo_root"
|
|
1134
|
-
cleanup_active_session_state_on_exit
|
|
1135
|
-
trap - EXIT INT TERM
|
|
1136
1045
|
final_exit="$codex_exit"
|
|
1137
1046
|
auto_finish_completed=0
|
|
1138
1047
|
|
package/src/agents/detect.js
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
const registry = require('./registry');
|
|
2
|
-
const { run } = require('../core/runtime');
|
|
3
|
-
|
|
4
|
-
function registryEntries() {
|
|
5
|
-
if (typeof registry.getAgentDefinitions === 'function') {
|
|
6
|
-
const definitions = registry.getAgentDefinitions();
|
|
7
|
-
if (Array.isArray(definitions)) {
|
|
8
|
-
return definitions;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
if (Array.isArray(registry.AGENT_IDS) && typeof registry.getAgentDefinition === 'function') {
|
|
13
|
-
return registry.AGENT_IDS
|
|
14
|
-
.map((agentId) => registry.getAgentDefinition(agentId))
|
|
15
|
-
.filter((entry) => entry && typeof entry === 'object');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const source =
|
|
19
|
-
registry.agents ||
|
|
20
|
-
registry.AGENTS ||
|
|
21
|
-
registry.registry ||
|
|
22
|
-
registry.entries ||
|
|
23
|
-
registry.default ||
|
|
24
|
-
registry;
|
|
25
|
-
|
|
26
|
-
if (Array.isArray(source)) {
|
|
27
|
-
return source;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (source && typeof source === 'object') {
|
|
31
|
-
return Object.entries(source)
|
|
32
|
-
.filter(([, entry]) => entry && typeof entry === 'object')
|
|
33
|
-
.map(([id, entry]) => ({ id, ...entry }));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return [];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function findAgent(agentId) {
|
|
40
|
-
if (typeof registry.getAgentDefinition === 'function') {
|
|
41
|
-
const entry = registry.getAgentDefinition(agentId);
|
|
42
|
-
if (entry) return entry;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (typeof registry.resolveAgent === 'function') {
|
|
46
|
-
try {
|
|
47
|
-
const entry = registry.resolveAgent(agentId);
|
|
48
|
-
if (entry) return entry;
|
|
49
|
-
} catch (_error) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (typeof registry.getAgent === 'function') {
|
|
55
|
-
const entry = registry.getAgent(agentId);
|
|
56
|
-
if (entry) return entry;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return registryEntries().find((entry) => entry.id === agentId);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function registryAgentIds() {
|
|
63
|
-
if (Array.isArray(registry.AGENT_IDS)) {
|
|
64
|
-
return [...registry.AGENT_IDS];
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (typeof registry.getAgentDefinitions === 'function') {
|
|
68
|
-
const definitions = registry.getAgentDefinitions();
|
|
69
|
-
if (Array.isArray(definitions)) {
|
|
70
|
-
return definitions.map((entry) => entry && entry.id).filter(Boolean);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return registryEntries().map((entry) => entry.id).filter(Boolean);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function normalizeDetectCommand(detectCommand) {
|
|
78
|
-
if (Array.isArray(detectCommand)) {
|
|
79
|
-
const [cmd, ...args] = detectCommand;
|
|
80
|
-
return { cmd, args, command: detectCommand.join(' ') };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (typeof detectCommand === 'string') {
|
|
84
|
-
const [cmd, ...args] = detectCommand.trim().split(/\s+/).filter(Boolean);
|
|
85
|
-
return { cmd, args, command: detectCommand.trim() };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (detectCommand && typeof detectCommand === 'object') {
|
|
89
|
-
const cmd = detectCommand.cmd || detectCommand.command || detectCommand.bin;
|
|
90
|
-
const args = Array.isArray(detectCommand.args) ? detectCommand.args : [];
|
|
91
|
-
return {
|
|
92
|
-
cmd,
|
|
93
|
-
args,
|
|
94
|
-
command: [cmd, ...args].filter(Boolean).join(' '),
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return { cmd: null, args: [], command: null };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function resultError(result) {
|
|
102
|
-
if (result.error) {
|
|
103
|
-
return result.error.message || String(result.error);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const output = `${result.stderr || ''}${result.stdout || ''}`.trim();
|
|
107
|
-
if (output) return output;
|
|
108
|
-
|
|
109
|
-
if (typeof result.status === 'number') {
|
|
110
|
-
return `detect command exited with status ${result.status}`;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return 'detect command failed';
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function detectionResult(entry, available, command, error = null) {
|
|
117
|
-
return {
|
|
118
|
-
id: entry.id,
|
|
119
|
-
label: entry.label || entry.id,
|
|
120
|
-
available,
|
|
121
|
-
command,
|
|
122
|
-
error,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function detectAgent(agentId) {
|
|
127
|
-
const entry = findAgent(agentId);
|
|
128
|
-
if (!entry) {
|
|
129
|
-
const known = registryAgentIds();
|
|
130
|
-
const suffix = known.length > 0 ? ` (known agents: ${known.join(', ')})` : '';
|
|
131
|
-
return detectionResult({ id: agentId, label: agentId }, false, null, `unknown agent: ${agentId}${suffix}`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const { cmd, args, command } = normalizeDetectCommand(entry.detectCommand);
|
|
135
|
-
if (!cmd) {
|
|
136
|
-
return detectionResult(entry, false, command, 'missing detectCommand');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const result = run(cmd, args, { stdio: 'pipe' });
|
|
140
|
-
if (!result.error && result.status === 0) {
|
|
141
|
-
return detectionResult(entry, true, command, null);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return detectionResult(entry, false, command, resultError(result));
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function detectAgents(agentIds) {
|
|
148
|
-
const ids = Array.isArray(agentIds) ? agentIds : registryAgentIds();
|
|
149
|
-
return ids.map((agentId) => detectAgent(agentId));
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function detectAvailableAgents() {
|
|
153
|
-
return detectAgents().filter((agent) => agent.available);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
module.exports = {
|
|
157
|
-
detectAgent,
|
|
158
|
-
detectAgents,
|
|
159
|
-
detectAvailableAgents,
|
|
160
|
-
};
|