@imdeadpool/guardex 7.0.39 → 7.0.43

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 (85) hide show
  1. package/README.md +85 -16
  2. package/package.json +2 -1
  3. package/skills/gitguardex/SKILL.md +13 -0
  4. package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
  5. package/src/agents/cleanup-sessions.js +126 -0
  6. package/src/agents/detect.js +160 -0
  7. package/src/agents/finish.js +172 -0
  8. package/src/agents/inspect.js +189 -0
  9. package/src/agents/launch.js +240 -0
  10. package/src/agents/registry.js +133 -0
  11. package/src/agents/selection-panel.js +571 -0
  12. package/src/agents/sessions.js +151 -0
  13. package/src/agents/start.js +591 -0
  14. package/src/agents/status.js +143 -0
  15. package/src/agents/terminal.js +152 -0
  16. package/src/budget/index.js +343 -0
  17. package/src/ci-init/index.js +265 -0
  18. package/src/cli/args.js +305 -1
  19. package/src/cli/main.js +262 -132
  20. package/src/cockpit/action-runner.js +3 -0
  21. package/src/cockpit/actions.js +80 -0
  22. package/src/cockpit/control.js +1121 -0
  23. package/src/cockpit/index.js +426 -0
  24. package/src/cockpit/keybindings.js +224 -0
  25. package/src/cockpit/kitty-layout.js +549 -0
  26. package/src/cockpit/kitty-tree.js +144 -0
  27. package/src/cockpit/layout.js +224 -0
  28. package/src/cockpit/logs-reader.js +182 -0
  29. package/src/cockpit/menu.js +204 -0
  30. package/src/cockpit/pane-actions.js +597 -0
  31. package/src/cockpit/pane-menu.js +387 -0
  32. package/src/cockpit/projects-finder.js +178 -0
  33. package/src/cockpit/render.js +215 -0
  34. package/src/cockpit/settings-render.js +128 -0
  35. package/src/cockpit/settings.js +124 -0
  36. package/src/cockpit/shortcuts.js +24 -0
  37. package/src/cockpit/sidebar.js +311 -0
  38. package/src/cockpit/state.js +72 -0
  39. package/src/cockpit/theme.js +128 -0
  40. package/src/cockpit/welcome.js +266 -0
  41. package/src/context.js +78 -35
  42. package/src/doctor/index.js +4 -3
  43. package/src/finish/index.js +39 -2
  44. package/src/git/index.js +65 -0
  45. package/src/kitty/command.js +101 -0
  46. package/src/kitty/runtime.js +250 -0
  47. package/src/output/index.js +1 -1
  48. package/src/pr-review.js +241 -0
  49. package/src/scaffold/index.js +19 -0
  50. package/src/submodule/index.js +288 -0
  51. package/src/terminal/index.js +120 -0
  52. package/src/terminal/kitty.js +622 -0
  53. package/src/terminal/tmux.js +126 -0
  54. package/src/tmux/command.js +27 -0
  55. package/src/tmux/session.js +89 -0
  56. package/templates/AGENTS.multiagent-safety.md +421 -37
  57. package/templates/codex/skills/gitguardex/SKILL.md +2 -0
  58. package/templates/githooks/pre-commit +22 -1
  59. package/templates/github/workflows/README.md +87 -0
  60. package/templates/github/workflows/ci-full.yml +55 -0
  61. package/templates/github/workflows/ci.yml +56 -0
  62. package/templates/github/workflows/cr.yml +20 -1
  63. package/templates/scripts/agent-branch-finish.sh +545 -27
  64. package/templates/scripts/agent-branch-start.sh +193 -21
  65. package/templates/scripts/agent-preflight.sh +89 -0
  66. package/templates/scripts/agent-worktree-prune.sh +96 -5
  67. package/templates/scripts/codex-agent.sh +41 -6
  68. package/templates/scripts/openspec/init-plan-workspace.sh +43 -0
  69. package/templates/scripts/review-bot-watch.sh +31 -2
  70. package/templates/scripts/agent-session-state.js +0 -171
  71. package/templates/scripts/install-vscode-active-agents-extension.js +0 -135
  72. package/templates/vscode/guardex-active-agents/README.md +0 -34
  73. package/templates/vscode/guardex-active-agents/extension.js +0 -3782
  74. package/templates/vscode/guardex-active-agents/fileicons/gitguardex-fileicons.json +0 -54
  75. package/templates/vscode/guardex-active-agents/fileicons/icons/agent.svg +0 -5
  76. package/templates/vscode/guardex-active-agents/fileicons/icons/branch.svg +0 -7
  77. package/templates/vscode/guardex-active-agents/fileicons/icons/config.svg +0 -4
  78. package/templates/vscode/guardex-active-agents/fileicons/icons/hook.svg +0 -4
  79. package/templates/vscode/guardex-active-agents/fileicons/icons/openspec.svg +0 -5
  80. package/templates/vscode/guardex-active-agents/fileicons/icons/plan.svg +0 -4
  81. package/templates/vscode/guardex-active-agents/fileicons/icons/spec.svg +0 -5
  82. package/templates/vscode/guardex-active-agents/icon.png +0 -0
  83. package/templates/vscode/guardex-active-agents/media/active-agents-hivemind.svg +0 -14
  84. package/templates/vscode/guardex-active-agents/package.json +0 -169
  85. package/templates/vscode/guardex-active-agents/session-schema.js +0 -1348
@@ -9,13 +9,16 @@ WORKTREE_ROOT_REL=""
9
9
  WORKTREE_ROOT_EXPLICIT=0
10
10
  NODE_BIN="${GUARDEX_NODE_BIN:-node}"
11
11
  CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}"
12
- OPENSPEC_AUTO_INIT_RAW="${GUARDEX_OPENSPEC_AUTO_INIT:-false}"
12
+ OPENSPEC_AUTO_INIT_RAW="${GUARDEX_OPENSPEC_AUTO_INIT:-true}"
13
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
  OPENSPEC_TIER_RAW="${GUARDEX_OPENSPEC_TIER:-T3}"
18
18
  REUSE_EXISTING_RAW="${GUARDEX_BRANCH_START_REUSE_EXISTING:-true}"
19
+ 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/**'
21
+ AUTO_TRANSFER_EXCLUDE_RAW="${GUARDEX_AUTO_TRANSFER_EXCLUDE-$AUTO_TRANSFER_EXCLUDE_DEFAULT}"
19
22
  PRINT_NAME_ONLY=0
20
23
  POSITIONAL_ARGS=()
21
24
 
@@ -67,6 +70,18 @@ while [[ $# -gt 0 ]]; do
67
70
  REUSE_EXISTING_RAW="false"
68
71
  shift
69
72
  ;;
73
+ --no-transfer)
74
+ AUTO_TRANSFER_ENABLED_RAW="false"
75
+ shift
76
+ ;;
77
+ --transfer)
78
+ AUTO_TRANSFER_ENABLED_RAW="true"
79
+ shift
80
+ ;;
81
+ --transfer-exclude)
82
+ AUTO_TRANSFER_EXCLUDE_RAW="${2:-}"
83
+ shift 2
84
+ ;;
70
85
  --in-place|--allow-in-place)
71
86
  echo "[agent-branch-start] In-place branch mode is disabled." >&2
72
87
  echo "[agent-branch-start] This command always creates an isolated worktree to keep your active checkout unchanged." >&2
@@ -389,11 +404,27 @@ print_reused_agent_worktree() {
389
404
  echo "[agent-branch-start] OpenSpec tier: ${OPENSPEC_TIER}"
390
405
  echo "[agent-branch-start] OpenSpec change: existing worktree"
391
406
  echo "[agent-branch-start] OpenSpec plan: existing worktree"
392
- echo "[agent-branch-start] Next steps:"
393
- echo " cd \"${worktree_path}\""
394
- echo " gx locks claim --branch \"${branch_name}\" <file...>"
395
- echo " # continue work in this existing sandbox"
396
- echo " gx branch finish --branch \"${branch_name}\" --via-pr --wait-for-merge"
407
+ print_agent_next_steps "$branch_name" "$worktree_path" "continue work in this existing sandbox" "$BASE_BRANCH"
408
+ }
409
+
410
+ print_agent_next_steps() {
411
+ local branch_name="$1"
412
+ local worktree_path="$2"
413
+ local work_step="$3"
414
+ local base_branch="${4:-main}"
415
+
416
+ if [[ -z "$base_branch" ]]; then
417
+ base_branch="main"
418
+ fi
419
+
420
+ echo "[agent-branch-start] Ready:"
421
+ echo " branch: ${branch_name}"
422
+ echo " worktree: ${worktree_path}"
423
+ echo " next:"
424
+ echo " cd \"${worktree_path}\""
425
+ echo " gx locks claim --branch \"${branch_name}\" <file...>"
426
+ echo " # ${work_step}"
427
+ echo " gx branch finish --branch \"${branch_name}\" --base ${base_branch} --via-pr --wait-for-merge --cleanup"
397
428
  }
398
429
 
399
430
  has_local_changes() {
@@ -410,6 +441,101 @@ has_local_changes() {
410
441
  return 1
411
442
  }
412
443
 
444
+ meaningful_slug_tokens() {
445
+ local raw="$1"
446
+ printf '%s' "$raw" \
447
+ | tr '[:upper:]' '[:lower:]' \
448
+ | tr '/_' '--' \
449
+ | tr '-' '\n' \
450
+ | awk '
451
+ length($0) < 4 { next }
452
+ $0 ~ /^[0-9]+$/ { next }
453
+ $0 ~ /^(agent|agents|branch|codex|claude|continue|dirty|existing|fix|from|implement|make|matching|openspec|reuse|start|task|that|this|update|with|worktree|worktrees)$/ { next }
454
+ !seen[$0]++ { print }
455
+ '
456
+ }
457
+
458
+ token_match_score() {
459
+ local task_slug="$1"
460
+ local branch_descriptor="$2"
461
+ local task_tokens branch_tokens token score
462
+ task_tokens="$(meaningful_slug_tokens "$task_slug")"
463
+ branch_tokens="$(meaningful_slug_tokens "$branch_descriptor")"
464
+ score=0
465
+
466
+ if [[ -z "$task_tokens" ]] || [[ -z "$branch_tokens" ]]; then
467
+ printf '0'
468
+ return 0
469
+ fi
470
+
471
+ while IFS= read -r token; do
472
+ if grep -Fxq "$token" <<<"$branch_tokens"; then
473
+ score=$((score + 1))
474
+ fi
475
+ done <<<"$task_tokens"
476
+
477
+ printf '%s' "$score"
478
+ }
479
+
480
+ managed_worktree_roots() {
481
+ local repo="$1"
482
+ local explicit_root="$2"
483
+ local root
484
+ local seen_roots=$'\n'
485
+
486
+ for root in \
487
+ "${repo}/${explicit_root}" \
488
+ "${repo}/.omx/agent-worktrees" \
489
+ "${repo}/.omc/agent-worktrees"; do
490
+ if [[ -n "$root" && "$seen_roots" != *$'\n'"$root"$'\n'* ]]; then
491
+ seen_roots+="${root}"$'\n'
492
+ printf '%s\n' "$root"
493
+ fi
494
+ done
495
+ }
496
+
497
+ find_matching_dirty_agent_worktree() {
498
+ local repo="$1"
499
+ local worktree_root_rel="$2"
500
+ local task_slug="$3"
501
+ local agent_slug="$4"
502
+ local best_score=0
503
+ local best_branch=""
504
+ local best_worktree=""
505
+ local best_count=0
506
+ local root entry branch descriptor score
507
+
508
+ while IFS= read -r root; do
509
+ [[ -d "$root" ]] || continue
510
+ while IFS= read -r entry; do
511
+ [[ -d "$entry" ]] || continue
512
+ if ! branch="$(git -C "$entry" rev-parse --abbrev-ref HEAD 2>/dev/null)"; then
513
+ continue
514
+ fi
515
+ [[ "$branch" == "agent/${agent_slug}/"* ]] || continue
516
+ has_local_changes "$entry" || continue
517
+
518
+ descriptor="${branch#agent/${agent_slug}/}"
519
+ score="$(token_match_score "$task_slug" "$descriptor")"
520
+ [[ "$score" =~ ^[0-9]+$ ]] || score=0
521
+ [[ "$score" -gt 0 ]] || continue
522
+
523
+ if [[ "$score" -gt "$best_score" ]]; then
524
+ best_score="$score"
525
+ best_branch="$branch"
526
+ best_worktree="$entry"
527
+ best_count=1
528
+ elif [[ "$score" -eq "$best_score" ]]; then
529
+ best_count=$((best_count + 1))
530
+ fi
531
+ done < <(find "$root" -mindepth 1 -maxdepth 1 -type d -print 2>/dev/null | sort)
532
+ done < <(managed_worktree_roots "$repo" "$worktree_root_rel")
533
+
534
+ if [[ "$best_score" -gt 0 && "$best_count" -eq 1 && -n "$best_branch" && -n "$best_worktree" ]]; then
535
+ printf '%s\t%s\n' "$best_branch" "$best_worktree"
536
+ fi
537
+ }
538
+
413
539
  resolve_stash_ref_by_message() {
414
540
  local root="$1"
415
541
  local message="$2"
@@ -597,6 +723,16 @@ if [[ "$PRINT_NAME_ONLY" -eq 1 ]]; then
597
723
  exit 0
598
724
  fi
599
725
 
726
+ if [[ "$REUSE_EXISTING_WORKTREE" -eq 1 ]]; then
727
+ matching_dirty_worktree="$(find_matching_dirty_agent_worktree "$repo_root" "$WORKTREE_ROOT_REL" "$task_slug" "$agent_slug")"
728
+ if [[ -n "$matching_dirty_worktree" ]]; then
729
+ IFS=$'\t' read -r reused_branch reused_worktree <<<"$matching_dirty_worktree"
730
+ echo "[agent-branch-start] Matched dirty managed worktree for requested task."
731
+ print_reused_agent_worktree "$reused_branch" "$reused_worktree"
732
+ exit 0
733
+ fi
734
+ fi
735
+
600
736
  if [[ "$BASE_BRANCH_EXPLICIT" -eq 0 ]]; then
601
737
  current_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
602
738
  protected_branches_raw="$(resolve_protected_branches "$repo_root")"
@@ -669,18 +805,54 @@ restore_auto_transfer_stash_on_failure() {
669
805
 
670
806
  trap 'restore_auto_transfer_stash_on_failure "$?"' EXIT
671
807
 
808
+ auto_transfer_enabled_lc="$(printf '%s' "$AUTO_TRANSFER_ENABLED_RAW" | tr '[:upper:]' '[:lower:]')"
809
+ case "$auto_transfer_enabled_lc" in
810
+ 0|false|no|off) AUTO_TRANSFER_ENABLED=0 ;;
811
+ *) AUTO_TRANSFER_ENABLED=1 ;;
812
+ esac
813
+
814
+ build_auto_transfer_stash_argv() {
815
+ # Emit NUL-separated argv: --include-untracked --message <msg> [-- :/ :(exclude,glob)PAT ...]
816
+ local msg="$1"
817
+ printf '%s\0' --include-untracked --message "$msg"
818
+ if [[ -z "${AUTO_TRANSFER_EXCLUDE_RAW:-}" ]]; then
819
+ return 0
820
+ fi
821
+ printf '%s\0' '--' ':/'
822
+ local -a patterns=()
823
+ IFS=':' read -ra patterns <<< "$AUTO_TRANSFER_EXCLUDE_RAW"
824
+ local pattern
825
+ for pattern in "${patterns[@]}"; do
826
+ [[ -z "$pattern" ]] && continue
827
+ printf '%s\0' ":(exclude,glob)${pattern}"
828
+ done
829
+ }
830
+
672
831
  current_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
673
832
  protected_branches_raw="$(resolve_protected_branches "$repo_root")"
674
833
  if [[ -n "$current_branch" && "$current_branch" != "HEAD" ]] && is_protected_branch_name "$current_branch" "$protected_branches_raw"; then
675
834
  if has_local_changes "$repo_root"; then
676
- auto_transfer_message="guardex-auto-transfer-${timestamp}-${agent_slug}-${task_slug}"
677
- if git -C "$repo_root" stash push --include-untracked --message "$auto_transfer_message" >/dev/null 2>&1; then
678
- auto_transfer_stash_ref="$(resolve_stash_ref_by_message "$repo_root" "$auto_transfer_message")"
679
- if [[ -n "$auto_transfer_stash_ref" ]]; then
680
- auto_transfer_source_branch="$current_branch"
681
- echo "[agent-branch-start] Detected local changes on protected branch '${current_branch}'. Moving them to '${branch_name}'..."
682
- if ! maybe_fail_after_auto_transfer_stash; then
683
- exit 1
835
+ if [[ "$AUTO_TRANSFER_ENABLED" -eq 0 ]]; then
836
+ echo "[agent-branch-start] Detected local changes on protected branch '${current_branch}'; --no-transfer set, leaving them in place." >&2
837
+ echo "[agent-branch-start] If you intended those changes for '${branch_name}', stash them manually and apply inside the worktree." >&2
838
+ else
839
+ auto_transfer_message="guardex-auto-transfer-${timestamp}-${agent_slug}-${task_slug}"
840
+ stash_argv=()
841
+ while IFS= read -r -d '' arg; do
842
+ stash_argv+=("$arg")
843
+ done < <(build_auto_transfer_stash_argv "$auto_transfer_message")
844
+ if git -C "$repo_root" stash push "${stash_argv[@]}" >/dev/null 2>&1; then
845
+ auto_transfer_stash_ref="$(resolve_stash_ref_by_message "$repo_root" "$auto_transfer_message")"
846
+ if [[ -n "$auto_transfer_stash_ref" ]]; then
847
+ auto_transfer_source_branch="$current_branch"
848
+ echo "[agent-branch-start] Detected local changes on protected branch '${current_branch}'. Moving them to '${branch_name}' (state-file globs excluded)..."
849
+ if ! maybe_fail_after_auto_transfer_stash; then
850
+ exit 1
851
+ fi
852
+ else
853
+ if has_local_changes "$repo_root"; then
854
+ echo "[agent-branch-start] Local changes on '${current_branch}' all match the auto-transfer exclude list; leaving them in place on '${current_branch}'." >&2
855
+ fi
684
856
  fi
685
857
  fi
686
858
  fi
@@ -727,18 +899,18 @@ fi
727
899
  echo "[agent-branch-start] Created branch: ${branch_name}"
728
900
  echo "[agent-branch-start] Worktree: ${worktree_path}"
729
901
  echo "[agent-branch-start] OpenSpec tier: ${OPENSPEC_TIER}"
730
- if [[ "$OPENSPEC_SKIP_CHANGE" -eq 1 ]]; then
902
+ if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
903
+ echo "[agent-branch-start] OpenSpec change: skipped (GUARDEX_OPENSPEC_AUTO_INIT disabled)"
904
+ elif [[ "$OPENSPEC_SKIP_CHANGE" -eq 1 ]]; then
731
905
  echo "[agent-branch-start] OpenSpec change: skipped by tier ${OPENSPEC_TIER}"
732
906
  else
733
907
  echo "[agent-branch-start] OpenSpec change: openspec/changes/${openspec_change_slug}"
734
908
  fi
735
- if [[ "$OPENSPEC_SKIP_PLAN" -eq 1 ]]; then
909
+ if [[ "$OPENSPEC_AUTO_INIT" -ne 1 ]]; then
910
+ echo "[agent-branch-start] OpenSpec plan: skipped (GUARDEX_OPENSPEC_AUTO_INIT disabled)"
911
+ elif [[ "$OPENSPEC_SKIP_PLAN" -eq 1 ]]; then
736
912
  echo "[agent-branch-start] OpenSpec plan: skipped by tier ${OPENSPEC_TIER}"
737
913
  else
738
914
  echo "[agent-branch-start] OpenSpec plan: openspec/plan/${openspec_plan_slug}"
739
915
  fi
740
- echo "[agent-branch-start] Next steps:"
741
- echo " cd \"${worktree_path}\""
742
- echo " gx locks claim --branch \"${branch_name}\" <file...>"
743
- echo " # implement + commit"
744
- echo " gx branch finish --branch \"${branch_name}\" --base ${BASE_BRANCH} --via-pr --wait-for-merge"
916
+ print_agent_next_steps "$branch_name" "$worktree_path" "implement + commit" "$BASE_BRANCH"
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env bash
2
+ # Pre-flight verification gate for gitguardex-managed projects.
3
+ #
4
+ # Runs in the agent's worktree from `gx branch finish` BEFORE the push
5
+ # happens. Returns non-zero to refuse the push so a broken commit
6
+ # never reaches the PR / CI / merge funnel.
7
+ #
8
+ # Auto-detects the project's stack and runs conventional verification:
9
+ # - Node/pnpm: pnpm typecheck && pnpm lint && pnpm test (each only
10
+ # if the script exists in package.json)
11
+ # - Node/npm: npm test (only if defined)
12
+ # - Rust: cargo check
13
+ # - Python: ruff check (only if ruff is installed)
14
+ #
15
+ # Override per-project by replacing this file (delete the symlink under
16
+ # scripts/agent-preflight.sh and write your own).
17
+ #
18
+ # Skip a single run with `gx branch finish --no-preflight`.
19
+
20
+ set -euo pipefail
21
+
22
+ repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
23
+ cd "$repo_root"
24
+
25
+ ran=0
26
+ fail=0
27
+ run_step() {
28
+ local label="$1"
29
+ shift
30
+ echo "[agent-preflight] -> $label"
31
+ if "$@"; then
32
+ ran=$((ran + 1))
33
+ echo "[agent-preflight] ok"
34
+ else
35
+ echo "[agent-preflight] FAIL: $label" >&2
36
+ fail=1
37
+ fi
38
+ }
39
+
40
+ has_package_script() {
41
+ local script_name="$1"
42
+ [[ -f package.json ]] || return 1
43
+ grep -E "\"${script_name}\"\\s*:" package.json >/dev/null 2>&1
44
+ }
45
+
46
+ # Node detection
47
+ if [[ -f package.json ]]; then
48
+ pkg_manager=""
49
+ if command -v pnpm >/dev/null 2>&1 && [[ -f pnpm-lock.yaml ]]; then
50
+ pkg_manager="pnpm"
51
+ elif command -v npm >/dev/null 2>&1 && [[ -f package-lock.json ]]; then
52
+ pkg_manager="npm"
53
+ fi
54
+
55
+ case "$pkg_manager" in
56
+ pnpm)
57
+ has_package_script typecheck && run_step "pnpm typecheck" pnpm typecheck
58
+ has_package_script lint && run_step "pnpm lint" pnpm lint
59
+ has_package_script test && run_step "pnpm test" pnpm test
60
+ ;;
61
+ npm)
62
+ has_package_script test && run_step "npm test" npm test
63
+ ;;
64
+ esac
65
+ fi
66
+
67
+ # Rust detection
68
+ if [[ -f Cargo.toml ]] && command -v cargo >/dev/null 2>&1; then
69
+ run_step "cargo check" cargo check --quiet
70
+ fi
71
+
72
+ # Python detection (ruff if available; pytest is too project-specific to default)
73
+ if [[ -f pyproject.toml ]] && command -v ruff >/dev/null 2>&1; then
74
+ run_step "ruff check" ruff check .
75
+ fi
76
+
77
+ if [[ "$ran" -eq 0 ]]; then
78
+ echo "[agent-preflight] No recognized project stack detected; skipping checks." >&2
79
+ exit 0
80
+ fi
81
+
82
+ if [[ "$fail" -ne 0 ]]; then
83
+ echo "[agent-preflight] Verification failed; refusing push." >&2
84
+ echo "[agent-preflight] Fix the issues, or re-run with: gx branch finish --no-preflight ..." >&2
85
+ exit 1
86
+ fi
87
+
88
+ echo "[agent-preflight] ${ran} step(s) passed."
89
+ exit 0
@@ -82,7 +82,12 @@ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
82
82
  fi
83
83
 
84
84
  repo_root="$(git rev-parse --show-toplevel)"
85
- current_pwd="$(pwd -P)"
85
+ current_pwd="${GUARDEX_PRUNE_ACTIVE_CWD:-$(pwd -P)}"
86
+ if [[ -d "$current_pwd" ]]; then
87
+ current_pwd="$(cd "$current_pwd" && pwd -P)"
88
+ else
89
+ current_pwd=""
90
+ fi
86
91
  repo_common_dir="$(
87
92
  git -C "$repo_root" rev-parse --git-common-dir \
88
93
  | awk -v root="$repo_root" '{ if ($0 ~ /^\//) { print $0 } else { print root "/" $0 } }'
@@ -244,11 +249,71 @@ branch_has_worktree() {
244
249
  git -C "$repo_root" worktree list --porcelain | grep -q "^branch refs/heads/${branch}$"
245
250
  }
246
251
 
252
+ # Globs treated as agent state, not real work. Worktrees whose only "dirty"
253
+ # content matches these are considered clean for prune purposes. Mirrors the
254
+ # auto-transfer + auto-resolve allowlist from PRs #546/#547 (state-file globs
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/**'
257
+ WORKTREE_STATE_EXCLUDE_GLOBS_RAW="${GUARDEX_PRUNE_STATE_EXCLUDE_GLOBS-$WORKTREE_STATE_EXCLUDE_GLOBS_DEFAULT}"
258
+
259
+ build_state_exclude_pathspec_args() {
260
+ # Emit one ':(exclude,glob)<pat>' arg per non-empty pattern.
261
+ if [[ -z "$WORKTREE_STATE_EXCLUDE_GLOBS_RAW" ]]; then
262
+ return 0
263
+ fi
264
+ local -a globs=()
265
+ IFS=':' read -ra globs <<< "$WORKTREE_STATE_EXCLUDE_GLOBS_RAW"
266
+ local pattern
267
+ for pattern in "${globs[@]}"; do
268
+ [[ -z "$pattern" ]] && continue
269
+ printf '%s\0' ":(exclude,glob)${pattern}"
270
+ done
271
+ }
272
+
273
+ # Capture the state-exclude pathspecs once; reused by is_clean_worktree and
274
+ # the dirt-summary logger below.
275
+ STATE_EXCLUDE_PATHSPEC_ARGS=()
276
+ while IFS= read -r -d '' arg; do
277
+ STATE_EXCLUDE_PATHSPEC_ARGS+=("$arg")
278
+ done < <(build_state_exclude_pathspec_args)
279
+
247
280
  is_clean_worktree() {
248
281
  local wt="$1"
249
- git -C "$wt" diff --quiet -- . ":(exclude).omx/state/agent-file-locks.json" \
250
- && git -C "$wt" diff --cached --quiet -- . ":(exclude).omx/state/agent-file-locks.json" \
251
- && [[ -z "$(git -C "$wt" ls-files --others --exclude-standard)" ]]
282
+ git -C "$wt" diff --quiet -- . "${STATE_EXCLUDE_PATHSPEC_ARGS[@]}" \
283
+ && git -C "$wt" diff --cached --quiet -- . "${STATE_EXCLUDE_PATHSPEC_ARGS[@]}" \
284
+ && [[ -z "$(git -C "$wt" ls-files --others --exclude-standard -- . "${STATE_EXCLUDE_PATHSPEC_ARGS[@]}")" ]]
285
+ }
286
+
287
+ # Returns a short, human-readable summary of why a worktree is considered dirty:
288
+ # up to 3 modified-tracked paths + up to 3 untracked paths + a "(+N more)" tail.
289
+ # Used to surface actionable context next to "Skipping dirty worktree" log lines
290
+ # (previously gave no clue what was actually dirty).
291
+ summarize_worktree_dirt() {
292
+ local wt="$1"
293
+ local modified_paths untracked_paths
294
+ modified_paths="$(git -C "$wt" diff --name-only --diff-filter=AMDR -- . "${STATE_EXCLUDE_PATHSPEC_ARGS[@]}" 2>/dev/null | head -3)"
295
+ local modified_count
296
+ modified_count="$(git -C "$wt" diff --name-only --diff-filter=AMDR -- . "${STATE_EXCLUDE_PATHSPEC_ARGS[@]}" 2>/dev/null | wc -l | tr -d ' ')"
297
+ untracked_paths="$(git -C "$wt" ls-files --others --exclude-standard -- . "${STATE_EXCLUDE_PATHSPEC_ARGS[@]}" 2>/dev/null | head -3)"
298
+ local untracked_count
299
+ untracked_count="$(git -C "$wt" ls-files --others --exclude-standard -- . "${STATE_EXCLUDE_PATHSPEC_ARGS[@]}" 2>/dev/null | wc -l | tr -d ' ')"
300
+
301
+ if [[ -n "$modified_paths" ]]; then
302
+ while IFS= read -r p; do
303
+ [[ -n "$p" ]] && echo " modified: ${p}"
304
+ done <<< "$modified_paths"
305
+ if [[ "$modified_count" -gt 3 ]]; then
306
+ echo " modified: (+$((modified_count - 3)) more)"
307
+ fi
308
+ fi
309
+ if [[ -n "$untracked_paths" ]]; then
310
+ while IFS= read -r p; do
311
+ [[ -n "$p" ]] && echo " untracked: ${p}"
312
+ done <<< "$untracked_paths"
313
+ if [[ "$untracked_count" -gt 3 ]]; then
314
+ echo " untracked: (+$((untracked_count - 3)) more)"
315
+ fi
316
+ fi
252
317
  }
253
318
 
254
319
  resolve_worktree_common_dir() {
@@ -317,6 +382,26 @@ read_branch_activity_epoch() {
317
382
 
318
383
  skipped_recent=0
319
384
 
385
+ has_live_process_in_worktree() {
386
+ local wt="$1"
387
+ local proc_cwd=""
388
+
389
+ [[ -d /proc ]] || return 1
390
+
391
+ for proc_cwd in /proc/[0-9]*/cwd; do
392
+ [[ -e "$proc_cwd" ]] || continue
393
+ local live_cwd=""
394
+ live_cwd="$(readlink "$proc_cwd" 2>/dev/null || true)"
395
+ [[ -n "$live_cwd" ]] || continue
396
+ live_cwd="${live_cwd% (deleted)}"
397
+ if [[ "$live_cwd" == "$wt" || "$live_cwd" == "${wt}"/* ]]; then
398
+ return 0
399
+ fi
400
+ done
401
+
402
+ return 1
403
+ }
404
+
320
405
  branch_idle_gate() {
321
406
  local branch="$1"
322
407
  local wt="$2"
@@ -431,11 +516,16 @@ process_entry() {
431
516
  return
432
517
  fi
433
518
 
434
- if [[ "$wt" == "$current_pwd" ]]; then
519
+ if [[ -n "$current_pwd" && ( "$wt" == "$current_pwd" || "$current_pwd" == "${wt}"/* ) ]]; then
435
520
  skipped_active=$((skipped_active + 1))
436
521
  echo "[agent-worktree-prune] Skipping active cwd worktree: ${wt}"
437
522
  return
438
523
  fi
524
+ if has_live_process_in_worktree "$wt"; then
525
+ skipped_active=$((skipped_active + 1))
526
+ echo "[agent-worktree-prune] Skipping live process worktree: ${wt}"
527
+ return
528
+ fi
439
529
 
440
530
  local remove_reason=""
441
531
  local branch_delete_mode="safe"
@@ -477,6 +567,7 @@ process_entry() {
477
567
  if [[ "$FORCE_DIRTY" -ne 1 ]] && ! is_clean_worktree "$wt"; then
478
568
  skipped_dirty=$((skipped_dirty + 1))
479
569
  echo "[agent-worktree-prune] Skipping dirty worktree (${remove_reason}): ${wt}"
570
+ summarize_worktree_dirt "$wt"
480
571
  return
481
572
  fi
482
573
 
@@ -6,6 +6,7 @@ AGENT_NAME="${GUARDEX_AGENT_NAME:-agent}"
6
6
  BASE_BRANCH="${GUARDEX_BASE_BRANCH:-}"
7
7
  BASE_BRANCH_EXPLICIT=0
8
8
  CODEX_BIN="${GUARDEX_CODEX_BIN:-codex}"
9
+ CODEX_APPROVAL_POLICY="${GUARDEX_CODEX_APPROVAL_POLICY-never}"
9
10
  NODE_BIN="${GUARDEX_NODE_BIN:-node}"
10
11
  CLI_ENTRY="${GUARDEX_CLI_ENTRY:-}"
11
12
  AUTO_FINISH_RAW="${GUARDEX_CODEX_AUTO_FINISH:-true}"
@@ -67,6 +68,29 @@ normalize_tier() {
67
68
  esac
68
69
  }
69
70
 
71
+ codex_args_include_approval_policy() {
72
+ local arg
73
+ for arg in "$@"; do
74
+ case "$arg" in
75
+ -a|--ask-for-approval|--approval-policy|--dangerously-bypass-approvals-and-sandbox)
76
+ return 0
77
+ ;;
78
+ --ask-for-approval=*|--approval-policy=*)
79
+ return 0
80
+ ;;
81
+ esac
82
+ done
83
+ return 1
84
+ }
85
+
86
+ build_codex_launch_args() {
87
+ CODEX_LAUNCH_ARGS=()
88
+ if [[ -n "$CODEX_APPROVAL_POLICY" ]] && ! codex_args_include_approval_policy "$@"; then
89
+ CODEX_LAUNCH_ARGS+=("-a" "$CODEX_APPROVAL_POLICY")
90
+ fi
91
+ CODEX_LAUNCH_ARGS+=("$@")
92
+ }
93
+
70
94
  string_contains_any() {
71
95
  local haystack="$1"
72
96
  shift
@@ -735,9 +759,14 @@ print_takeover_prompt() {
735
759
 
736
760
  finish_cmd="gx branch finish --branch \"${branch}\" --base ${base_branch} --via-pr --wait-for-merge --cleanup"
737
761
 
738
- echo "[codex-agent] Takeover sandbox: ${wt}"
739
- echo "[codex-agent] Takeover routing: $(describe_task_routing) (${TASK_ROUTING_REASON})"
740
- echo "[codex-agent] Takeover prompt: Continue \`${change_slug}\` on branch \`${branch}\`. Work inside \`${wt}\`, review \`${change_artifact}\`, continue from the current state instead of creating a new sandbox, and when the work is done run \`${finish_cmd}\`."
762
+ echo "[codex-agent] Resume this sandbox:"
763
+ echo " change: ${change_slug}"
764
+ echo " branch: ${branch}"
765
+ echo " worktree: ${wt}"
766
+ echo " spec: ${change_artifact}"
767
+ echo " routing: $(describe_task_routing) (${TASK_ROUTING_REASON})"
768
+ echo " rule: continue current state; do not create a new sandbox"
769
+ echo " finish: ${finish_cmd}"
741
770
  }
742
771
 
743
772
  sync_worktree_with_base() {
@@ -1034,7 +1063,8 @@ run_finish_flow() {
1034
1063
  (
1035
1064
  cd "$wt"
1036
1065
  set +e
1037
- "$CODEX_BIN" "$review_prompt"
1066
+ build_codex_launch_args "$review_prompt"
1067
+ "$CODEX_BIN" "${CODEX_LAUNCH_ARGS[@]}"
1038
1068
  review_exit="$?"
1039
1069
  set -e
1040
1070
  if [[ "$review_exit" -ne 0 ]]; then
@@ -1092,10 +1122,11 @@ trap cleanup_active_session_state_on_exit EXIT INT TERM
1092
1122
  echo "[codex-agent] Launching ${CODEX_BIN} in sandbox: $worktree_path"
1093
1123
  cd "$worktree_path"
1094
1124
  set +e
1125
+ build_codex_launch_args "$@"
1095
1126
  GUARDEX_TASK_MODE="$TASK_MODE" \
1096
1127
  GUARDEX_OPENSPEC_TIER="$OPENSPEC_TIER" \
1097
1128
  GUARDEX_TASK_ROUTING_REASON="$TASK_ROUTING_REASON" \
1098
- "$CODEX_BIN" "$@"
1129
+ "$CODEX_BIN" "${CODEX_LAUNCH_ARGS[@]}"
1099
1130
  codex_exit="$?"
1100
1131
  set -e
1101
1132
 
@@ -1161,7 +1192,11 @@ else
1161
1192
  echo "[codex-agent] Branch kept intentionally. Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
1162
1193
  else
1163
1194
  print_takeover_prompt "$worktree_path" "$worktree_branch"
1164
- echo "[codex-agent] If finished, merge with: gx branch finish --branch \"${worktree_branch}\" --base dev --via-pr --wait-for-merge"
1195
+ finish_base_branch="$(resolve_worktree_base_branch "$worktree_path")"
1196
+ if [[ -z "$finish_base_branch" ]]; then
1197
+ finish_base_branch="dev"
1198
+ fi
1199
+ echo "[codex-agent] If finished, merge with: gx branch finish --branch \"${worktree_branch}\" --base ${finish_base_branch} --via-pr --wait-for-merge --cleanup"
1165
1200
  echo "[codex-agent] Cleanup on demand: gx cleanup --branch \"${worktree_branch}\""
1166
1201
  fi
1167
1202
  fi
@@ -84,6 +84,7 @@ if [[ ! -f "$PLAN_DIR/README.md" ]]; then
84
84
  echo
85
85
  echo "Each role folder contains OpenSpec-style artifacts:"
86
86
  echo "- \`.openspec.yaml\`"
87
+ echo "- \`prompt.md\` (copy/paste role prompt)"
87
88
  echo "- \`proposal.md\`"
88
89
  echo "- \`tasks.md\` (Spec / Tests / Implementation / Checkpoints checklists)"
89
90
  echo "- \`specs/<role>/spec.md\`"
@@ -108,6 +109,7 @@ Drive this plan from draft to execution-ready status with strict checkpoint disc
108
109
  - \`openspec/plan/${PLAN_SLUG}/checkpoints.md\`
109
110
  - \`openspec/plan/${PLAN_SLUG}/open-questions.md\`
110
111
  - \`openspec/plan/${PLAN_SLUG}/planner/plan.md\`
112
+ - role \`prompt.md\` files for copy/paste helper startup
111
113
  - role \`tasks.md\` files for planner/architect/critic/executor/writer/verifier
112
114
 
113
115
  ## Coordinator responsibilities
@@ -282,6 +284,7 @@ Role workspace for \`${role}\`.
282
284
 
283
285
  Default artifacts:
284
286
  - \`.openspec.yaml\`
287
+ - \`prompt.md\`
285
288
  - \`proposal.md\`
286
289
  - \`tasks.md\`
287
290
  - \`specs/<role>/spec.md\`
@@ -302,12 +305,52 @@ plan: ${PLAN_SLUG}
302
305
  role: ${role}
303
306
  status: draft
304
307
  artifacts:
308
+ prompt: prompt.md
305
309
  proposal: proposal.md
306
310
  tasks: tasks.md
307
311
  spec: specs/${ROLE_SPEC_SLUG}/spec.md
308
312
  ROLEYAMLEOF
309
313
  fi
310
314
 
315
+ if [[ ! -f "$ROLE_DIR/prompt.md" ]]; then
316
+ cat > "$ROLE_DIR/prompt.md" <<ROLEPROMPTEOF
317
+ # ${role} Prompt
318
+
319
+ You are the \`${role}\` role for OpenSpec plan \`${PLAN_SLUG}\`.
320
+
321
+ ## Objective
322
+
323
+ Complete only this role's assigned checklist and leave compact evidence for the coordinator.
324
+
325
+ ## Source of truth
326
+
327
+ - \`openspec/plan/${PLAN_SLUG}/summary.md\`
328
+ - \`openspec/plan/${PLAN_SLUG}/checkpoints.md\`
329
+ - \`openspec/plan/${PLAN_SLUG}/open-questions.md\`
330
+ - \`openspec/plan/${PLAN_SLUG}/${role}/tasks.md\`
331
+ - \`openspec/plan/${PLAN_SLUG}/${role}/proposal.md\`
332
+
333
+ ## Before edits
334
+
335
+ 1. Confirm branch/worktree with \`git status --short --branch\`.
336
+ 2. Claim every touched file before editing:
337
+ - Prefer Colony \`task_claim_file\` when an active task exists.
338
+ - Otherwise run \`gx locks claim --branch <agent-branch> <file...>\`.
339
+ 3. Stay inside assigned files/modules; coordinate before touching shared paths.
340
+
341
+ ## Working rules
342
+
343
+ - Update \`${role}/tasks.md\` as each item completes.
344
+ - Record durable unresolved questions in \`open-questions.md\`.
345
+ - Keep handoffs short: files changed, behavior touched, verification, risks.
346
+ - Do not revert another agent's edits.
347
+
348
+ ## Cleanup
349
+
350
+ Only the owner/finalizer lane runs \`gx branch finish --branch <agent-branch> --base dev --via-pr --wait-for-merge --cleanup\`. If blocked, append \`BLOCKED:\` with branch, task, blocker, next, evidence.
351
+ ROLEPROMPTEOF
352
+ fi
353
+
311
354
  if [[ ! -f "$ROLE_DIR/proposal.md" ]]; then
312
355
  cat > "$ROLE_DIR/proposal.md" <<ROLEPROPOSALEOF
313
356
  # Proposal: ${role} (${PLAN_SLUG})