@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 +24 -12
- package/bin/multiagent-safety.js +72 -6
- package/package.json +1 -1
- package/templates/AGENTS.multiagent-safety.md +2 -0
- package/templates/codex/skills/guardex/SKILL.md +3 -0
- package/templates/scripts/agent-worktree-prune.sh +72 -4
- package/templates/scripts/codex-agent.sh +37 -3
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
|
-

|
|
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
|
|
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
|
|
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
|
```
|
package/bin/multiagent-safety.js
CHANGED
|
@@ -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 = [
|
|
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
|
|
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
|
@@ -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="
|
|
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:-
|
|
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
|
|
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
|
-
|
|
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"
|