@imdeadpool/guardex 7.0.41 → 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 +68 -13
  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 +76 -33
  42. package/src/doctor/index.js +3 -2
  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 +27 -1
  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 +544 -26
  64. package/templates/scripts/agent-branch-start.sh +89 -22
  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() {
@@ -419,7 +450,7 @@ meaningful_slug_tokens() {
419
450
  | awk '
420
451
  length($0) < 4 { next }
421
452
  $0 ~ /^[0-9]+$/ { next }
422
- $0 ~ /^(agent|agents|branch|codex|claude|continue|dirty|existing|fix|from|implement|make|matching|reuse|start|task|that|this|update|with|worktree|worktrees)$/ { 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 }
423
454
  !seen[$0]++ { print }
424
455
  '
425
456
  }
@@ -774,18 +805,54 @@ restore_auto_transfer_stash_on_failure() {
774
805
 
775
806
  trap 'restore_auto_transfer_stash_on_failure "$?"' EXIT
776
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
+
777
831
  current_branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
778
832
  protected_branches_raw="$(resolve_protected_branches "$repo_root")"
779
833
  if [[ -n "$current_branch" && "$current_branch" != "HEAD" ]] && is_protected_branch_name "$current_branch" "$protected_branches_raw"; then
780
834
  if has_local_changes "$repo_root"; then
781
- auto_transfer_message="guardex-auto-transfer-${timestamp}-${agent_slug}-${task_slug}"
782
- if git -C "$repo_root" stash push --include-untracked --message "$auto_transfer_message" >/dev/null 2>&1; then
783
- auto_transfer_stash_ref="$(resolve_stash_ref_by_message "$repo_root" "$auto_transfer_message")"
784
- if [[ -n "$auto_transfer_stash_ref" ]]; then
785
- auto_transfer_source_branch="$current_branch"
786
- echo "[agent-branch-start] Detected local changes on protected branch '${current_branch}'. Moving them to '${branch_name}'..."
787
- if ! maybe_fail_after_auto_transfer_stash; then
788
- 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
789
856
  fi
790
857
  fi
791
858
  fi
@@ -832,18 +899,18 @@ fi
832
899
  echo "[agent-branch-start] Created branch: ${branch_name}"
833
900
  echo "[agent-branch-start] Worktree: ${worktree_path}"
834
901
  echo "[agent-branch-start] OpenSpec tier: ${OPENSPEC_TIER}"
835
- 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
836
905
  echo "[agent-branch-start] OpenSpec change: skipped by tier ${OPENSPEC_TIER}"
837
906
  else
838
907
  echo "[agent-branch-start] OpenSpec change: openspec/changes/${openspec_change_slug}"
839
908
  fi
840
- 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
841
912
  echo "[agent-branch-start] OpenSpec plan: skipped by tier ${OPENSPEC_TIER}"
842
913
  else
843
914
  echo "[agent-branch-start] OpenSpec plan: openspec/plan/${openspec_plan_slug}"
844
915
  fi
845
- echo "[agent-branch-start] Next steps:"
846
- echo " cd \"${worktree_path}\""
847
- echo " gx locks claim --branch \"${branch_name}\" <file...>"
848
- echo " # implement + commit"
849
- 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})
@@ -64,6 +64,36 @@ normalize_bool() {
64
64
  esac
65
65
  }
66
66
 
67
+ gh_api_probe_looks_like_network_failure() {
68
+ local probe_output="$1"
69
+ [[ "$probe_output" =~ error[[:space:]]connecting[[:space:]]to[[:space:]]api\.github\.com|Could[[:space:]]not[[:space:]]resolve[[:space:]]host|could[[:space:]]not[[:space:]]resolve[[:space:]]host|Failed[[:space:]]to[[:space:]]connect|failed[[:space:]]to[[:space:]]connect|Network[[:space:]]is[[:space:]]unreachable|network[[:space:]]is[[:space:]]unreachable|Connection[[:space:]]timed[[:space:]]out|connection[[:space:]]timed[[:space:]]out|Temporary[[:space:]]failure[[:space:]]in[[:space:]]name[[:space:]]resolution|temporary[[:space:]]failure[[:space:]]in[[:space:]]name[[:space:]]resolution ]]
70
+ }
71
+
72
+ ensure_gh_auth_or_explain() {
73
+ local auth_output probe_output
74
+ if auth_output="$(gh auth status 2>&1)"; then
75
+ return 0
76
+ fi
77
+
78
+ if probe_output="$(gh api user --jq .login 2>&1)"; then
79
+ echo "[review-bot-watch] gh auth status failed, but gh api user succeeded; continuing with usable GitHub auth." >&2
80
+ return 0
81
+ fi
82
+
83
+ if gh_api_probe_looks_like_network_failure "$probe_output"; then
84
+ echo "[review-bot-watch] GitHub API is unreachable, so gh cannot validate the stored token." >&2
85
+ echo "[review-bot-watch] This is a network or Codex sandbox connectivity problem, not proof that the token is invalid." >&2
86
+ echo "$probe_output" >&2
87
+ return 1
88
+ fi
89
+
90
+ echo "[review-bot-watch] gh is not authenticated. Run: gh auth login" >&2
91
+ if [[ -n "$auth_output" ]]; then
92
+ echo "$auth_output" >&2
93
+ fi
94
+ return 1
95
+ }
96
+
67
97
  ONCE=0
68
98
 
69
99
  while [[ $# -gt 0 ]]; do
@@ -153,8 +183,7 @@ if ! command -v codex >/dev/null 2>&1; then
153
183
  exit 127
154
184
  fi
155
185
 
156
- if ! gh auth status >/dev/null 2>&1; then
157
- echo "[review-bot-watch] gh is not authenticated. Run: gh auth login" >&2
186
+ if ! ensure_gh_auth_or_explain; then
158
187
  exit 1
159
188
  fi
160
189