@imdeadpool/guardex 5.0.0 → 5.0.1

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 CHANGED
@@ -50,26 +50,29 @@ Related tools:
50
50
 
51
51
  - [oh-my-codex (OMX)](https://github.com/Yeachan-Heo/oh-my-codex)
52
52
  - [OpenSpec](https://github.com/Fission-AI/OpenSpec)
53
+ - [codex-account-switcher-cli](https://github.com/recodeecom/codex-account-switcher-cli)
53
54
 
54
55
  ## Fast setup (recommended)
55
56
 
56
57
  ```sh
57
58
  # inside your repo
58
59
  gx setup
60
+ # alias:
61
+ gx init
59
62
  ```
60
63
 
61
64
  That one command runs:
62
65
 
63
- 1. detects whether OMX/OpenSpec are already globally installed,
66
+ 1. detects whether OMX/OpenSpec/codex-auth are already globally installed,
64
67
  2. asks strict Y/N approval only if something is missing,
65
68
  3. installs guardrail scripts/hooks,
66
69
  4. repairs common safety problems,
67
70
  5. installs local Codex + Claude gx helper skill files if missing,
68
71
  6. scans and reports final status.
69
72
 
70
- ## Setup screenshot
73
+ ## Setup behavior screenshot
71
74
 
72
- ![gx setup success screenshot](https://raw.githubusercontent.com/recodeecom/multiagent-safety/main/docs/images/setup-success.svg)
75
+ ![gx status/setup behavior screenshot](https://raw.githubusercontent.com/recodeecom/multiagent-safety/main/docs/images/setup-success.svg)
73
76
 
74
77
  ## Status logs screenshot
75
78
 
@@ -217,10 +220,11 @@ Use this exact checklist to setup multi-agent safety in this repository for Code
217
220
 
218
221
  2) Bootstrap safety in this repo:
219
222
  gx setup
223
+ # alias: gx init
220
224
 
221
- - Setup detects global OMX/OpenSpec first.
225
+ - Setup detects global OMX/OpenSpec/codex-auth first.
222
226
  - If one is missing and setup asks for approval, reply explicitly:
223
- - y = run: npm i -g oh-my-codex @fission-ai/openspec (missing ones only)
227
+ - y = run: npm i -g oh-my-codex @fission-ai/openspec @imdeadpool/codex-account-switcher (missing ones only)
224
228
  - n = skip global installs
225
229
 
226
230
  3) If setup reports warnings/errors, repair + re-check:
@@ -231,6 +235,9 @@ Use this exact checklist to setup multi-agent safety in this repository for Code
231
235
  bash scripts/agent-branch-start.sh "task" "agent-name"
232
236
  python3 scripts/agent-file-locks.py claim --branch "$(git rev-parse --abbrev-ref HEAD)" <file...>
233
237
  bash scripts/agent-branch-finish.sh --branch "$(git rev-parse --abbrev-ref HEAD)"
238
+ - For every new user message/task, repeat the same cycle:
239
+ start isolated agent branch/worktree -> claim file locks -> implement/verify ->
240
+ finish via PR/merge cleanup with scripts/agent-branch-finish.sh.
234
241
 
235
242
  5) Optional: create OpenSpec planning workspace:
236
243
  bash scripts/openspec/init-plan-workspace.sh "<plan-slug>"
@@ -250,8 +257,9 @@ Use this exact checklist to setup multi-agent safety in this repository for Code
250
257
 
251
258
  ```sh
252
259
  gx status [--target <path>] [--json]
253
- gx setup [--target <path>] [--dry-run] [--yes-global-install|--no-global-install] [--no-gitignore]
254
- gx doctor [--target <path>] [--dry-run] [--json] [--keep-stale-locks] [--no-gitignore]
260
+ gx setup [--target <path>] [--dry-run] [--yes-global-install|--no-global-install] [--no-gitignore] [--allow-protected-base-write]
261
+ gx init [--target <path>] [--dry-run] [--yes-global-install|--no-global-install] [--no-gitignore] [--allow-protected-base-write]
262
+ gx doctor [--target <path>] [--dry-run] [--json] [--keep-stale-locks] [--no-gitignore] [--allow-protected-base-write]
255
263
  gx copy-prompt
256
264
  gx copy-commands
257
265
  gx protect list [--target <path>]
@@ -262,24 +270,28 @@ gx protect reset [--target <path>]
262
270
  gx sync --check [--target <path>] [--base <branch>] [--json]
263
271
  gx sync [--target <path>] [--base <branch>] [--strategy rebase|merge] [--ff-only]
264
272
  gx report scorecard [--target <path>] [--repo github.com/<owner>/<repo>] [--scorecard-json <file>] [--output-dir <path>] [--date YYYY-MM-DD]
265
- bash scripts/agent-worktree-prune.sh --base dev # manual stale worktree cleanup
273
+ bash scripts/agent-worktree-prune.sh # manual stale worktree cleanup (auto base detection)
274
+ bash scripts/agent-worktree-prune.sh --force-dirty # remove stale dirty worktrees too
266
275
  bash scripts/openspec/init-plan-workspace.sh <plan-slug> # optional OpenSpec plan scaffold
267
276
  ```
268
277
 
269
278
  No command defaults to `gx status` (non-mutating health/status view).
270
- `gx status` reports CLI/runtime info, global OMX/OpenSpec service status, and repo safety service state.
279
+ `gx status` reports CLI/runtime info, global OMX/OpenSpec/codex-auth service status, and repo safety service state.
280
+ `gx init` is an alias of `gx setup`.
271
281
  When run in an interactive terminal, default `GuardeX` checks npm for a newer version first
272
282
  and asks `[y/N]` whether to update immediately (default is `N`).
273
283
 
274
- - Interactive setup: prompts for Y/N approval before global OMX/OpenSpec install.
284
+ - Interactive setup: prompts for Y/N approval before global OMX/OpenSpec/codex-auth install.
275
285
  - Interactive prompt is strict (`[y/n]`) and waits for explicit answer.
276
286
  - Non-interactive setup: skips global installs by default; use `--yes-global-install` to force.
287
+ - In already-initialized repos, `setup` / `install` / `fix` / `doctor` block writes on protected `main` by default; start an agent branch first. Use `--allow-protected-base-write` only for emergency in-place maintenance.
288
+ - `scripts/codex-agent.sh` now auto-runs worktree prune after a Codex session; clean sandbox branches are removed automatically, dirty ones are kept.
277
289
 
278
290
  ## Advanced commands
279
291
 
280
292
  ```sh
281
- gx install [--target <path>] [--force] [--skip-agents] [--skip-package-json] [--no-gitignore] [--dry-run]
282
- gx fix [--target <path>] [--dry-run] [--keep-stale-locks] [--no-gitignore]
293
+ gx install [--target <path>] [--force] [--skip-agents] [--skip-package-json] [--no-gitignore] [--dry-run] [--allow-protected-base-write]
294
+ gx fix [--target <path>] [--dry-run] [--keep-stale-locks] [--no-gitignore] [--allow-protected-base-write]
283
295
  gx scan [--target <path>] [--json]
284
296
  gx report help
285
297
  ```
@@ -10,7 +10,11 @@ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
10
10
  const TOOL_NAME = 'guardex';
11
11
  const SHORT_TOOL_NAME = 'gx';
12
12
  const LEGACY_NAMES = ['musafety', 'multiagent-safety'];
13
- const GLOBAL_TOOLCHAIN_PACKAGES = ['oh-my-codex', '@fission-ai/openspec'];
13
+ const GLOBAL_TOOLCHAIN_PACKAGES = [
14
+ 'oh-my-codex',
15
+ '@fission-ai/openspec',
16
+ '@imdeadpool/codex-account-switcher',
17
+ ];
14
18
  const MAINTAINER_RELEASE_REPO = path.resolve(
15
19
  process.env.MUSAFETY_RELEASE_REPO || '/tmp/multiagent-safety',
16
20
  );
@@ -80,6 +84,7 @@ const COMMAND_TYPO_ALIASES = new Map([
80
84
  ['realaese', 'release'],
81
85
  ['relase', 'release'],
82
86
  ['setpu', 'setup'],
87
+ ['inti', 'init'],
83
88
  ['intsall', 'install'],
84
89
  ['docter', 'doctor'],
85
90
  ['doctro', 'doctor'],
@@ -88,6 +93,7 @@ const COMMAND_TYPO_ALIASES = new Map([
88
93
  const SUGGESTIBLE_COMMANDS = [
89
94
  'status',
90
95
  'setup',
96
+ 'init',
91
97
  'doctor',
92
98
  'report',
93
99
  'copy-prompt',
@@ -105,6 +111,7 @@ const SUGGESTIBLE_COMMANDS = [
105
111
  const CLI_COMMAND_DESCRIPTIONS = [
106
112
  ['status', 'Show GuardeX CLI + service health without modifying files'],
107
113
  ['setup', 'Install + repair guardrails in a git repo (supports --no-gitignore)'],
114
+ ['init', 'Alias of setup (bootstrap + repair guardrails in a git repo)'],
108
115
  ['doctor', 'Repair safety setup drift, then verify repo safety'],
109
116
  ['report', 'Generate security/safety reports (for example: OpenSSF scorecard)'],
110
117
  ['copy-prompt', 'Print the AI-ready setup checklist'],
@@ -127,10 +134,11 @@ const AI_SETUP_PROMPT = `Use this exact checklist to setup GuardeX (Guardian T-R
127
134
 
128
135
  2) Bootstrap safety in this repo:
129
136
  gx setup
137
+ # alias: gx init
130
138
 
131
- - Setup detects global OMX/OpenSpec first.
139
+ - Setup detects global OMX/OpenSpec/codex-auth first.
132
140
  - If one is missing and setup asks for approval, reply explicitly:
133
- - y = run: npm i -g oh-my-codex @fission-ai/openspec (missing ones only)
141
+ - y = run: npm i -g oh-my-codex @fission-ai/openspec @imdeadpool/codex-account-switcher (missing ones only)
134
142
  - n = skip global installs
135
143
 
136
144
  3) If setup reports warnings/errors, repair + re-check:
@@ -141,6 +149,9 @@ const AI_SETUP_PROMPT = `Use this exact checklist to setup GuardeX (Guardian T-R
141
149
  bash scripts/agent-branch-start.sh "task" "agent-name"
142
150
  python3 scripts/agent-file-locks.py claim --branch "$(git rev-parse --abbrev-ref HEAD)" <file...>
143
151
  bash scripts/agent-branch-finish.sh --branch "$(git rev-parse --abbrev-ref HEAD)"
152
+ - For every new user message/task, repeat the same cycle:
153
+ start isolated agent branch/worktree -> claim file locks -> implement/verify ->
154
+ finish via PR/merge cleanup with scripts/agent-branch-finish.sh.
144
155
 
145
156
  5) Optional: create OpenSpec planning workspace:
146
157
  bash scripts/openspec/init-plan-workspace.sh "<plan-slug>"
@@ -151,6 +162,9 @@ const AI_SETUP_PROMPT = `Use this exact checklist to setup GuardeX (Guardian T-R
151
162
  7) Optional: sync your current agent branch with latest base branch:
152
163
  gx sync --check
153
164
  gx sync
165
+
166
+ 8) Optional (GitHub remote cleanup): enable:
167
+ Settings -> General -> Pull Requests -> Automatically delete head branches
154
168
  `;
155
169
 
156
170
  const AI_SETUP_COMMANDS = `npm i -g @imdeadpool/guardex
@@ -272,7 +286,9 @@ ${commandCatalogLines().join('\n')}
272
286
  NOTES
273
287
  - Running ${TOOL_NAME} with no command defaults to: ${SHORT_TOOL_NAME} status
274
288
  - Short alias: ${SHORT_TOOL_NAME}
289
+ - ${SHORT_TOOL_NAME} init is an alias of ${SHORT_TOOL_NAME} setup
275
290
  - ${TOOL_NAME} setup asks for Y/N approval before global installs
291
+ - In initialized repos, setup/install/fix/doctor block in-place writes on protected main by default
276
292
  - Legacy command aliases are still supported: ${LEGACY_NAMES.join(', ')}`);
277
293
 
278
294
  if (outsideGitRepo) {
@@ -473,7 +489,7 @@ function ensurePackageScripts(repoRoot, dryRun) {
473
489
  'agent:codex': 'bash ./scripts/codex-agent.sh',
474
490
  'agent:branch:start': 'bash ./scripts/agent-branch-start.sh',
475
491
  'agent:branch:finish': 'bash ./scripts/agent-branch-finish.sh',
476
- 'agent:cleanup': 'bash ./scripts/agent-worktree-prune.sh --base dev',
492
+ 'agent:cleanup': 'bash ./scripts/agent-worktree-prune.sh',
477
493
  'agent:hooks:install': 'bash ./scripts/install-agent-git-hooks.sh',
478
494
  'agent:locks:claim': 'python3 ./scripts/agent-file-locks.py claim',
479
495
  'agent:locks:allow-delete': 'python3 ./scripts/agent-file-locks.py allow-delete',
@@ -630,6 +646,10 @@ function parseCommonArgs(rawArgs, defaults) {
630
646
  options.skipGitignore = true;
631
647
  continue;
632
648
  }
649
+ if (arg === '--allow-protected-base-write') {
650
+ options.allowProtectedBaseWrite = true;
651
+ continue;
652
+ }
633
653
 
634
654
  throw new Error(`Unknown option: ${arg}`);
635
655
  }
@@ -641,6 +661,44 @@ function parseCommonArgs(rawArgs, defaults) {
641
661
  return options;
642
662
  }
643
663
 
664
+ function hasGuardexBootstrapFiles(repoRoot) {
665
+ const required = [
666
+ 'AGENTS.md',
667
+ 'scripts/agent-branch-start.sh',
668
+ '.githooks/pre-commit',
669
+ LOCK_FILE_RELATIVE,
670
+ ];
671
+ return required.every((relativePath) => fs.existsSync(path.join(repoRoot, relativePath)));
672
+ }
673
+
674
+ function assertProtectedMainWriteAllowed(options, commandName) {
675
+ if (options.dryRun || options.allowProtectedBaseWrite) {
676
+ return;
677
+ }
678
+
679
+ const repoRoot = resolveRepoRoot(options.target);
680
+ if (!hasGuardexBootstrapFiles(repoRoot)) {
681
+ return;
682
+ }
683
+
684
+ const branch = currentBranchName(repoRoot);
685
+ if (branch !== 'main') {
686
+ return;
687
+ }
688
+
689
+ const protectedBranches = readProtectedBranches(repoRoot);
690
+ if (!protectedBranches.includes(branch)) {
691
+ return;
692
+ }
693
+
694
+ throw new Error(
695
+ `${commandName} blocked on protected branch '${branch}' in an initialized repo.\n` +
696
+ `Keep local '${branch}' pull-only: start an agent branch/worktree first:\n` +
697
+ ` bash scripts/agent-branch-start.sh "<task>" "codex"\n` +
698
+ `Override once only when intentional: --allow-protected-base-write`,
699
+ );
700
+ }
701
+
644
702
  function parseTargetFlag(rawArgs, defaultTarget = process.cwd()) {
645
703
  const remaining = [];
646
704
  let target = defaultTarget;
@@ -1777,8 +1835,10 @@ function install(rawArgs) {
1777
1835
  skipPackageJson: false,
1778
1836
  skipGitignore: false,
1779
1837
  dryRun: false,
1838
+ allowProtectedBaseWrite: false,
1780
1839
  });
1781
1840
 
1841
+ assertProtectedMainWriteAllowed(options, 'install');
1782
1842
  const payload = runInstallInternal(options);
1783
1843
  printOperations('Install target', payload, options.dryRun);
1784
1844
 
@@ -1797,8 +1857,10 @@ function fix(rawArgs) {
1797
1857
  skipPackageJson: false,
1798
1858
  skipGitignore: false,
1799
1859
  dryRun: false,
1860
+ allowProtectedBaseWrite: false,
1800
1861
  });
1801
1862
 
1863
+ assertProtectedMainWriteAllowed(options, 'fix');
1802
1864
  const payload = runFixInternal(options);
1803
1865
  printOperations('Fix target', payload, options.dryRun);
1804
1866
 
@@ -1829,8 +1891,10 @@ function doctor(rawArgs) {
1829
1891
  skipGitignore: false,
1830
1892
  dryRun: false,
1831
1893
  json: false,
1894
+ allowProtectedBaseWrite: false,
1832
1895
  });
1833
1896
 
1897
+ assertProtectedMainWriteAllowed(options, 'doctor');
1834
1898
  const fixPayload = runFixInternal(options);
1835
1899
  const scanResult = runScanInternal({ target: options.target, json: false });
1836
1900
  const musafe = scanResult.errors === 0 && scanResult.warnings === 0;
@@ -1974,6 +2038,7 @@ function setup(rawArgs) {
1974
2038
  dryRun: false,
1975
2039
  yesGlobalInstall: false,
1976
2040
  noGlobalInstall: false,
2041
+ allowProtectedBaseWrite: false,
1977
2042
  });
1978
2043
 
1979
2044
  const globalInstallStatus = installGlobalToolchain(options);
@@ -1982,7 +2047,7 @@ function setup(rawArgs) {
1982
2047
  `[${TOOL_NAME}] ✅ Global tools installed (${(globalInstallStatus.packages || []).join(', ')}).`,
1983
2048
  );
1984
2049
  } else if (globalInstallStatus.status === 'already-installed') {
1985
- console.log(`[${TOOL_NAME}] ✅ OMX/OpenSpec global tools already installed. Skipping.`);
2050
+ console.log(`[${TOOL_NAME}] ✅ OMX/OpenSpec/codex-auth global tools already installed. Skipping.`);
1986
2051
  } else if (globalInstallStatus.status === 'failed') {
1987
2052
  console.log(
1988
2053
  `[${TOOL_NAME}] ⚠️ Global install failed: ${globalInstallStatus.reason}\n` +
@@ -1996,6 +2061,7 @@ function setup(rawArgs) {
1996
2061
  );
1997
2062
  }
1998
2063
 
2064
+ assertProtectedMainWriteAllowed(options, 'setup');
1999
2065
  const installPayload = runInstallInternal(options);
2000
2066
  printOperations('Setup/install', installPayload, options.dryRun);
2001
2067
 
@@ -2403,7 +2469,7 @@ function main() {
2403
2469
  return;
2404
2470
  }
2405
2471
 
2406
- if (command === 'setup') {
2472
+ if (command === 'setup' || command === 'init') {
2407
2473
  setup(rest);
2408
2474
  return;
2409
2475
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imdeadpool/guardex",
3
- "version": "5.0.0",
3
+ "version": "5.0.1",
4
4
  "description": "GuardeX: the Guardian T-Rex for your repo, with hardened multi-agent git guardrails.",
5
5
  "license": "MIT",
6
6
  "preferGlobal": true,
@@ -10,7 +10,9 @@
10
10
  - Before deleting/replacing code, each agent must read the latest session comments/handoffs first and confirm the target code is in their owned scope.
11
11
  - If ownership is unclear or overlaps, stop that edit, post a blocker comment, and let the leader/integrator reassign scope.
12
12
  - For git isolation, each agent must start on a dedicated branch via `scripts/agent-branch-start.sh "<task-or-plan>" "<agent-name>"`.
13
+ - Treat the base branch (`main` or the user's current local base branch) as read-only while the agent branch is active.
13
14
  - Agent completion must use `scripts/agent-branch-finish.sh` (direct merge to base when allowed; auto PR fallback for protected bases, then cleanup after merge).
15
+ - Per-message loop is mandatory: for every new user message/task, start a fresh agent branch/worktree, claim ownership locks, implement and verify, finish via PR/merge cleanup, then repeat for the next message/task.
14
16
 
15
17
  1. Explicit ownership before edits
16
18
 
@@ -19,6 +19,8 @@ If guardrails are missing entirely, run:
19
19
 
20
20
  ```sh
21
21
  gx setup
22
+ # alias
23
+ gx init
22
24
  ```
23
25
 
24
26
  Then verify:
@@ -32,5 +34,6 @@ gx scan
32
34
 
33
35
  - Prefer `gx doctor` for one-step repair + verification.
34
36
  - Keep agent work isolated (`agent/*` branches + lock claims).
37
+ - For every new user message/task, restart the full loop on a fresh agent branch/worktree.
35
38
  - For one-command Codex sandbox startup, use `bash scripts/codex-agent.sh "<task>" "<agent-name>"`.
36
39
  - Do not bypass protected branch safeguards unless explicitly required.
@@ -1,22 +1,33 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
- BASE_BRANCH="dev"
4
+ BASE_BRANCH="${MUSAFETY_BASE_BRANCH:-}"
5
+ BASE_BRANCH_EXPLICIT=0
5
6
  DRY_RUN=0
7
+ FORCE_DIRTY=0
8
+
9
+ if [[ -n "$BASE_BRANCH" ]]; then
10
+ BASE_BRANCH_EXPLICIT=1
11
+ fi
6
12
 
7
13
  while [[ $# -gt 0 ]]; do
8
14
  case "$1" in
9
15
  --base)
10
- BASE_BRANCH="${2:-dev}"
16
+ BASE_BRANCH="${2:-}"
17
+ BASE_BRANCH_EXPLICIT=1
11
18
  shift 2
12
19
  ;;
13
20
  --dry-run)
14
21
  DRY_RUN=1
15
22
  shift
16
23
  ;;
24
+ --force-dirty)
25
+ FORCE_DIRTY=1
26
+ shift
27
+ ;;
17
28
  *)
18
29
  echo "[agent-worktree-prune] Unknown argument: $1" >&2
19
- echo "Usage: $0 [--base <branch>] [--dry-run]" >&2
30
+ echo "Usage: $0 [--base <branch>] [--dry-run] [--force-dirty]" >&2
20
31
  exit 1
21
32
  ;;
22
33
  esac
@@ -31,6 +42,46 @@ repo_root="$(git rev-parse --show-toplevel)"
31
42
  current_pwd="$(pwd -P)"
32
43
  worktree_root="${repo_root}/.omx/agent-worktrees"
33
44
 
45
+ resolve_base_branch() {
46
+ local configured=""
47
+ local current=""
48
+
49
+ configured="$(git -C "$repo_root" config --get multiagent.baseBranch || true)"
50
+ if [[ -n "$configured" ]] && git -C "$repo_root" show-ref --verify --quiet "refs/heads/${configured}"; then
51
+ printf '%s' "$configured"
52
+ return 0
53
+ fi
54
+
55
+ current="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
56
+ if [[ -n "$current" && "$current" != "HEAD" ]] && git -C "$repo_root" show-ref --verify --quiet "refs/heads/${current}"; then
57
+ printf '%s' "$current"
58
+ return 0
59
+ fi
60
+
61
+ for fallback in main dev; do
62
+ if git -C "$repo_root" show-ref --verify --quiet "refs/heads/${fallback}"; then
63
+ printf '%s' "$fallback"
64
+ return 0
65
+ fi
66
+ done
67
+
68
+ printf '%s' ""
69
+ }
70
+
71
+ if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 && -z "$BASE_BRANCH" ]]; then
72
+ echo "[agent-worktree-prune] --base requires a non-empty branch name." >&2
73
+ exit 1
74
+ fi
75
+
76
+ if [[ "$BASE_BRANCH_EXPLICIT" -eq 0 ]]; then
77
+ BASE_BRANCH="$(resolve_base_branch)"
78
+ fi
79
+
80
+ if [[ -z "$BASE_BRANCH" ]]; then
81
+ echo "[agent-worktree-prune] Unable to infer base branch. Pass --base <branch>." >&2
82
+ exit 1
83
+ fi
84
+
34
85
  if ! git -C "$repo_root" show-ref --verify --quiet "refs/heads/${BASE_BRANCH}"; then
35
86
  echo "[agent-worktree-prune] Base branch not found: ${BASE_BRANCH}" >&2
36
87
  exit 1
@@ -49,9 +100,17 @@ branch_has_worktree() {
49
100
  git -C "$repo_root" worktree list --porcelain | grep -q "^branch refs/heads/${branch}$"
50
101
  }
51
102
 
103
+ is_clean_worktree() {
104
+ local wt="$1"
105
+ git -C "$wt" diff --quiet -- . ":(exclude).omx/state/agent-file-locks.json" \
106
+ && git -C "$wt" diff --cached --quiet -- . ":(exclude).omx/state/agent-file-locks.json" \
107
+ && [[ -z "$(git -C "$wt" ls-files --others --exclude-standard)" ]]
108
+ }
109
+
52
110
  removed_worktrees=0
53
111
  removed_branches=0
54
112
  skipped_active=0
113
+ skipped_dirty=0
55
114
 
56
115
  process_entry() {
57
116
  local wt="$1"
@@ -89,6 +148,12 @@ process_entry() {
89
148
  return
90
149
  fi
91
150
 
151
+ if [[ "$FORCE_DIRTY" -ne 1 ]] && ! is_clean_worktree "$wt"; then
152
+ skipped_dirty=$((skipped_dirty + 1))
153
+ echo "[agent-worktree-prune] Skipping dirty worktree (${remove_reason}): ${wt}"
154
+ return
155
+ fi
156
+
92
157
  echo "[agent-worktree-prune] Removing worktree (${remove_reason}): ${wt}"
93
158
  run_cmd git -C "$repo_root" worktree remove "$wt" --force
94
159
  removed_worktrees=$((removed_worktrees + 1))
@@ -149,7 +214,10 @@ done < <(git -C "$repo_root" for-each-ref --format='%(refname:short)' refs/heads
149
214
 
150
215
  run_cmd git -C "$repo_root" worktree prune
151
216
 
152
- echo "[agent-worktree-prune] Summary: removed_worktrees=${removed_worktrees}, removed_branches=${removed_branches}, skipped_active=${skipped_active}"
217
+ echo "[agent-worktree-prune] Summary: base=${BASE_BRANCH}, removed_worktrees=${removed_worktrees}, removed_branches=${removed_branches}, skipped_active=${skipped_active}, skipped_dirty=${skipped_dirty}"
153
218
  if [[ "$skipped_active" -gt 0 ]]; then
154
219
  echo "[agent-worktree-prune] Tip: leave active agent worktree directories, then run this command again for full cleanup." >&2
155
220
  fi
221
+ if [[ "$skipped_dirty" -gt 0 ]]; then
222
+ echo "[agent-worktree-prune] Tip: dirty worktrees were preserved. Clean/finish them first, or pass --force-dirty to remove anyway." >&2
223
+ fi
@@ -65,7 +65,13 @@ if ! command -v "$CODEX_BIN" >/dev/null 2>&1; then
65
65
  exit 127
66
66
  fi
67
67
 
68
- if [[ ! -x "scripts/agent-branch-start.sh" ]]; then
68
+ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
69
+ echo "[codex-agent] Not inside a git repository." >&2
70
+ exit 1
71
+ fi
72
+ repo_root="$(git rev-parse --show-toplevel)"
73
+
74
+ if [[ ! -x "${repo_root}/scripts/agent-branch-start.sh" ]]; then
69
75
  echo "[codex-agent] Missing scripts/agent-branch-start.sh. Run: gx setup" >&2
70
76
  exit 1
71
77
  fi
@@ -75,7 +81,7 @@ if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 ]]; then
75
81
  start_args+=("$BASE_BRANCH")
76
82
  fi
77
83
 
78
- start_output="$(bash scripts/agent-branch-start.sh "${start_args[@]}")"
84
+ start_output="$(bash "${repo_root}/scripts/agent-branch-start.sh" "${start_args[@]}")"
79
85
  printf '%s\n' "$start_output"
80
86
 
81
87
  worktree_path="$(printf '%s\n' "$start_output" | sed -n 's/^\[agent-branch-start\] Worktree: //p' | tail -n1)"
@@ -91,4 +97,32 @@ fi
91
97
 
92
98
  echo "[codex-agent] Launching ${CODEX_BIN} in sandbox: $worktree_path"
93
99
  cd "$worktree_path"
94
- exec "$CODEX_BIN" "$@"
100
+ set +e
101
+ "$CODEX_BIN" "$@"
102
+ codex_exit="$?"
103
+ set -e
104
+
105
+ cd "$repo_root"
106
+
107
+ if [[ -x "${repo_root}/scripts/agent-worktree-prune.sh" ]]; then
108
+ echo "[codex-agent] Session ended (exit=${codex_exit}). Running worktree cleanup..."
109
+ prune_args=()
110
+ if [[ "$BASE_BRANCH_EXPLICIT" -eq 1 ]]; then
111
+ prune_args+=(--base "$BASE_BRANCH")
112
+ fi
113
+ if ! bash "${repo_root}/scripts/agent-worktree-prune.sh" "${prune_args[@]}"; then
114
+ echo "[codex-agent] Warning: automatic worktree cleanup failed." >&2
115
+ fi
116
+ fi
117
+
118
+ if [[ ! -d "$worktree_path" ]]; then
119
+ echo "[codex-agent] Auto-cleaned sandbox worktree: $worktree_path"
120
+ else
121
+ worktree_branch="$(git -C "$worktree_path" rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
122
+ echo "[codex-agent] Sandbox worktree kept: $worktree_path"
123
+ if [[ -n "$worktree_branch" && "$worktree_branch" != "HEAD" ]]; then
124
+ echo "[codex-agent] If finished, merge + clean with: bash scripts/agent-branch-finish.sh --branch \"${worktree_branch}\""
125
+ fi
126
+ fi
127
+
128
+ exit "$codex_exit"