@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.
Files changed (63) hide show
  1. package/README.md +26 -0
  2. package/package.json +2 -1
  3. package/skills/gx-act/SKILL.md +82 -0
  4. package/src/agents/inspect.js +17 -4
  5. package/src/agents/launch.js +10 -1
  6. package/src/agents/status.js +9 -6
  7. package/src/budget/index.js +2 -1
  8. package/src/cli/args.js +52 -2
  9. package/src/cli/commands/agents.js +364 -0
  10. package/src/cli/commands/bootstrap.js +92 -0
  11. package/src/cli/commands/branch.js +127 -0
  12. package/src/cli/commands/claude.js +674 -0
  13. package/src/cli/commands/doctor.js +268 -0
  14. package/src/cli/commands/finish.js +26 -0
  15. package/src/cli/commands/mcp.js +122 -0
  16. package/src/cli/commands/misc.js +304 -0
  17. package/src/cli/commands/pr.js +439 -0
  18. package/src/cli/commands/prompt.js +92 -0
  19. package/src/cli/commands/release.js +305 -0
  20. package/src/cli/commands/report.js +244 -0
  21. package/src/cli/commands/review.js +32 -0
  22. package/src/cli/commands/setup.js +242 -0
  23. package/src/cli/commands/status.js +338 -0
  24. package/src/cli/commands/watch.js +234 -0
  25. package/src/cli/main.js +68 -3726
  26. package/src/cli/shared/repo-env.js +161 -0
  27. package/src/cli/shared/sandbox.js +417 -0
  28. package/src/cli/shared/scaffolding.js +535 -0
  29. package/src/cli/shared/toolchain-shims.js +420 -0
  30. package/src/context.js +229 -11
  31. package/src/core/runtime.js +6 -1
  32. package/src/doctor/index.js +42 -13
  33. package/src/finish/index.js +147 -5
  34. package/src/finish/preflight.js +177 -0
  35. package/src/finish/review-gate.js +182 -0
  36. package/src/git/index.js +446 -4
  37. package/src/hooks/index.js +0 -64
  38. package/src/mcp/collect.js +370 -0
  39. package/src/mcp/server.js +157 -0
  40. package/src/output/index.js +67 -1
  41. package/src/pr-review.js +23 -0
  42. package/src/pr.js +381 -0
  43. package/src/sandbox/index.js +13 -2
  44. package/src/scaffold/agent-worktree-prep.js +213 -0
  45. package/src/scaffold/index.js +108 -10
  46. package/src/speckit/index.js +226 -0
  47. package/src/terminal/index.js +1 -76
  48. package/src/terminal/tmux.js +0 -1
  49. package/src/toolchain/index.js +20 -0
  50. package/templates/AGENTS.monorepo-apps.md +26 -0
  51. package/templates/AGENTS.multiagent-safety.md +61 -347
  52. package/templates/AGENTS.multiagent-safety.min.md +11 -0
  53. package/templates/codex/skills/gx-act/SKILL.md +82 -0
  54. package/templates/githooks/pre-commit +22 -19
  55. package/templates/scripts/agent-branch-finish.sh +8 -30
  56. package/templates/scripts/agent-branch-merge.sh +4 -1
  57. package/templates/scripts/agent-branch-start.sh +88 -3
  58. package/templates/scripts/agent-preflight.sh +31 -5
  59. package/templates/scripts/agent-worktree-prune.sh +1 -1
  60. package/templates/scripts/codex-agent.sh +0 -91
  61. package/src/agents/detect.js +0 -160
  62. package/src/cockpit/keybindings.js +0 -224
  63. 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/**:.agents/settings.local.json:.codex/state/**:.claude/state/**'
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" "${merge_flags[@]}" 2>&1)"; then
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
- local -a merge_flags=(--squash --delete-branch)
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
- local -a auto_flags=(--squash --delete-branch --auto)
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
- GUARDEX_OPENSPEC_AUTO_INIT=1 run_guardex_cli branch start "$TASK_NAME" "$AGENT_NAME" "$BASE_BRANCH" 2>&1
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
- OPENSPEC_TIER_RAW="${GUARDEX_OPENSPEC_TIER:-T3}"
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/**:.agents/settings.local.json:.codex/state/**:.claude/state/**'
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" "T3")"; then
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
- if "$@"; then
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 [[ "$ran" -eq 0 ]]; then
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/**:.agents/settings.local.json:.codex/state/**:.claude/state/**'
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
 
@@ -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
- };