@imdeadpool/guardex 7.0.18 → 7.0.19
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 +28 -19
- package/bin/multiagent-safety.js +199 -122
- package/package.json +1 -1
- package/templates/AGENTS.multiagent-safety.md +3 -0
- package/templates/codex/skills/gitguardex/SKILL.md +1 -1
- package/templates/codex/skills/guardex-merge-skills-to-dev/SKILL.md +3 -3
- package/templates/githooks/pre-commit +21 -2
- package/templates/scripts/agent-branch-finish.sh +32 -19
- package/templates/scripts/agent-branch-merge.sh +24 -5
- package/templates/scripts/agent-branch-start.sh +23 -29
- package/templates/scripts/agent-file-locks.py +11 -11
- package/templates/scripts/codex-agent.sh +37 -55
- package/templates/scripts/review-bot-watch.sh +30 -7
- package/templates/vscode/guardex-active-agents/README.md +3 -2
- package/templates/vscode/guardex-active-agents/extension.js +49 -9
package/bin/multiagent-safety.js
CHANGED
|
@@ -109,7 +109,7 @@ const TEMPLATE_FILES = [
|
|
|
109
109
|
'vscode/guardex-active-agents/README.md',
|
|
110
110
|
];
|
|
111
111
|
|
|
112
|
-
const
|
|
112
|
+
const LEGACY_WORKFLOW_SHIM_SPECS = [
|
|
113
113
|
{ relativePath: 'scripts/agent-branch-start.sh', kind: 'shell', command: ['branch', 'start'] },
|
|
114
114
|
{ relativePath: 'scripts/agent-branch-finish.sh', kind: 'shell', command: ['branch', 'finish'] },
|
|
115
115
|
{ relativePath: 'scripts/agent-branch-merge.sh', kind: 'shell', command: ['branch', 'merge'] },
|
|
@@ -121,21 +121,20 @@ const SCRIPT_SHIMS = [
|
|
|
121
121
|
{ relativePath: 'scripts/openspec/init-change-workspace.sh', kind: 'shell', command: ['internal', 'run-shell', 'changeInit'] },
|
|
122
122
|
];
|
|
123
123
|
|
|
124
|
+
const LEGACY_WORKFLOW_SHIMS = LEGACY_WORKFLOW_SHIM_SPECS.map((entry) => entry.relativePath);
|
|
125
|
+
|
|
126
|
+
const MANAGED_TEMPLATE_DESTINATIONS = TEMPLATE_FILES.map((entry) => toDestinationPath(entry));
|
|
127
|
+
const MANAGED_TEMPLATE_SCRIPT_FILES = MANAGED_TEMPLATE_DESTINATIONS.filter((entry) =>
|
|
128
|
+
entry.startsWith('scripts/'),
|
|
129
|
+
);
|
|
130
|
+
|
|
124
131
|
const LEGACY_MANAGED_REPO_FILES = [
|
|
125
|
-
|
|
126
|
-
'scripts/agent-branch-finish.sh',
|
|
127
|
-
'scripts/agent-branch-merge.sh',
|
|
132
|
+
...LEGACY_WORKFLOW_SHIMS,
|
|
128
133
|
'scripts/agent-session-state.js',
|
|
129
|
-
'scripts/codex-agent.sh',
|
|
130
134
|
'scripts/guardex-docker-loader.sh',
|
|
131
135
|
'scripts/install-vscode-active-agents-extension.js',
|
|
132
|
-
'scripts/review-bot-watch.sh',
|
|
133
|
-
'scripts/agent-worktree-prune.sh',
|
|
134
|
-
'scripts/agent-file-locks.py',
|
|
135
136
|
'scripts/guardex-env.sh',
|
|
136
137
|
'scripts/install-agent-git-hooks.sh',
|
|
137
|
-
'scripts/openspec/init-plan-workspace.sh',
|
|
138
|
-
'scripts/openspec/init-change-workspace.sh',
|
|
139
138
|
'.githooks/pre-commit',
|
|
140
139
|
'.githooks/pre-push',
|
|
141
140
|
'.githooks/post-merge',
|
|
@@ -145,9 +144,8 @@ const LEGACY_MANAGED_REPO_FILES = [
|
|
|
145
144
|
'.claude/commands/gitguardex.md',
|
|
146
145
|
];
|
|
147
146
|
|
|
148
|
-
const
|
|
149
|
-
...
|
|
150
|
-
...SCRIPT_SHIMS.map((entry) => entry.relativePath),
|
|
147
|
+
const REQUIRED_MANAGED_REPO_FILES = [
|
|
148
|
+
...MANAGED_TEMPLATE_DESTINATIONS,
|
|
151
149
|
...HOOK_NAMES.map((entry) => path.posix.join('.githooks', entry)),
|
|
152
150
|
'.omx/state/agent-file-locks.json',
|
|
153
151
|
];
|
|
@@ -205,22 +203,13 @@ const USER_LEVEL_SKILL_ASSETS = [
|
|
|
205
203
|
];
|
|
206
204
|
|
|
207
205
|
const EXECUTABLE_RELATIVE_PATHS = new Set([
|
|
208
|
-
|
|
209
|
-
'scripts/guardex-docker-loader.sh',
|
|
210
|
-
'scripts/install-vscode-active-agents-extension.js',
|
|
211
|
-
...SCRIPT_SHIMS.map((entry) => entry.relativePath),
|
|
206
|
+
...MANAGED_TEMPLATE_SCRIPT_FILES,
|
|
212
207
|
...HOOK_NAMES.map((entry) => path.posix.join('.githooks', entry)),
|
|
213
208
|
]);
|
|
214
209
|
|
|
215
210
|
const CRITICAL_GUARDRAIL_PATHS = new Set([
|
|
216
211
|
'AGENTS.md',
|
|
217
212
|
...HOOK_NAMES.map((entry) => path.posix.join('.githooks', entry)),
|
|
218
|
-
'scripts/agent-branch-start.sh',
|
|
219
|
-
'scripts/agent-branch-finish.sh',
|
|
220
|
-
'scripts/agent-branch-merge.sh',
|
|
221
|
-
'scripts/agent-worktree-prune.sh',
|
|
222
|
-
'scripts/codex-agent.sh',
|
|
223
|
-
'scripts/agent-file-locks.py',
|
|
224
213
|
'scripts/guardex-env.sh',
|
|
225
214
|
]);
|
|
226
215
|
|
|
@@ -239,9 +228,10 @@ const AGENT_WORKTREE_RELATIVE_DIRS = [
|
|
|
239
228
|
const MANAGED_GITIGNORE_PATHS = [
|
|
240
229
|
'.omx/',
|
|
241
230
|
'.omc/',
|
|
242
|
-
'scripts
|
|
243
|
-
'scripts/
|
|
244
|
-
'scripts/
|
|
231
|
+
'scripts/agent-session-state.js',
|
|
232
|
+
'scripts/guardex-docker-loader.sh',
|
|
233
|
+
'scripts/guardex-env.sh',
|
|
234
|
+
'scripts/install-vscode-active-agents-extension.js',
|
|
245
235
|
'.githooks',
|
|
246
236
|
'oh-my-codex/',
|
|
247
237
|
LOCK_FILE_RELATIVE,
|
|
@@ -260,6 +250,13 @@ const OMX_SCAFFOLD_FILES = new Map([
|
|
|
260
250
|
['.omx/notepad.md', '\n\n## WORKING MEMORY\n'],
|
|
261
251
|
['.omx/project-memory.json', '{}\n'],
|
|
262
252
|
]);
|
|
253
|
+
const TARGETED_FORCEABLE_MANAGED_PATHS = new Set([
|
|
254
|
+
'AGENTS.md',
|
|
255
|
+
'.gitignore',
|
|
256
|
+
...Array.from(OMX_SCAFFOLD_FILES.keys()),
|
|
257
|
+
...REQUIRED_MANAGED_REPO_FILES,
|
|
258
|
+
...LEGACY_WORKFLOW_SHIMS,
|
|
259
|
+
]);
|
|
263
260
|
const COMMAND_TYPO_ALIASES = new Map([
|
|
264
261
|
['relaese', 'release'],
|
|
265
262
|
['realaese', 'release'],
|
|
@@ -311,7 +308,7 @@ const CLI_COMMAND_DESCRIPTIONS = [
|
|
|
311
308
|
['locks', 'CLI-owned file lock surface (claim/allow-delete/release/status/validate)'],
|
|
312
309
|
['worktree', 'CLI-owned worktree cleanup surface (prune)'],
|
|
313
310
|
['hook', 'Hook dispatch/install surface used by managed shims'],
|
|
314
|
-
['migrate', 'Convert legacy repo-local installs to the
|
|
311
|
+
['migrate', 'Convert legacy repo-local installs to the zero-copy CLI-owned surface'],
|
|
315
312
|
['install-agent-skills', 'Install Guardex Codex/Claude skills into the user home'],
|
|
316
313
|
['protect', 'Manage protected branches (list/add/remove/set/reset)'],
|
|
317
314
|
['merge', 'Create/reuse an integration lane and merge overlapping agent branches'],
|
|
@@ -680,6 +677,27 @@ function runPackageAsset(assetKey, rawArgs, options = {}) {
|
|
|
680
677
|
});
|
|
681
678
|
}
|
|
682
679
|
|
|
680
|
+
function repoLocalLegacyScriptPath(repoRoot, relativePath) {
|
|
681
|
+
const assetPath = path.join(repoRoot, relativePath);
|
|
682
|
+
return fs.existsSync(assetPath) ? assetPath : null;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function runReviewBotCommand(repoRoot, rawArgs, options = {}) {
|
|
686
|
+
const legacyScript = repoLocalLegacyScriptPath(repoRoot, 'scripts/review-bot-watch.sh');
|
|
687
|
+
if (legacyScript) {
|
|
688
|
+
return run('bash', [legacyScript, ...rawArgs], {
|
|
689
|
+
cwd: repoRoot,
|
|
690
|
+
stdio: options.stdio || 'pipe',
|
|
691
|
+
timeout: options.timeout,
|
|
692
|
+
env: packageAssetEnv(options.env),
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
return runPackageAsset('reviewBot', rawArgs, {
|
|
696
|
+
...options,
|
|
697
|
+
cwd: repoRoot,
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
|
|
683
701
|
function invokePackageAsset(assetKey, rawArgs, options = {}) {
|
|
684
702
|
const result = runPackageAsset(assetKey, rawArgs, options);
|
|
685
703
|
if (result.stdout) process.stdout.write(result.stdout);
|
|
@@ -1028,6 +1046,13 @@ function renderPythonDispatchShim(commandParts) {
|
|
|
1028
1046
|
);
|
|
1029
1047
|
}
|
|
1030
1048
|
|
|
1049
|
+
function managedForceConflictMessage(relativePath) {
|
|
1050
|
+
return (
|
|
1051
|
+
`Refusing to overwrite existing file without --force: ${relativePath}\n` +
|
|
1052
|
+
`Use '--force ${relativePath}' to rewrite only this managed file, or '--force' to rewrite all managed files.`
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1031
1056
|
function renderManagedFile(repoRoot, relativePath, content, options = {}) {
|
|
1032
1057
|
const destinationPath = path.join(repoRoot, relativePath);
|
|
1033
1058
|
const destinationExists = fs.existsSync(destinationPath);
|
|
@@ -1041,7 +1066,7 @@ function renderManagedFile(repoRoot, relativePath, content, options = {}) {
|
|
|
1041
1066
|
return { status: 'unchanged', file: relativePath };
|
|
1042
1067
|
}
|
|
1043
1068
|
if (!force && !isCriticalGuardrailPath(relativePath)) {
|
|
1044
|
-
throw new Error(
|
|
1069
|
+
throw new Error(managedForceConflictMessage(relativePath));
|
|
1045
1070
|
}
|
|
1046
1071
|
}
|
|
1047
1072
|
|
|
@@ -1089,9 +1114,7 @@ function copyTemplateFile(repoRoot, relativeTemplatePath, force, dryRun) {
|
|
|
1089
1114
|
return { status: 'unchanged', file: destinationRelativePath };
|
|
1090
1115
|
}
|
|
1091
1116
|
if (!force && !isCriticalGuardrailPath(destinationRelativePath)) {
|
|
1092
|
-
throw new Error(
|
|
1093
|
-
`Refusing to overwrite existing file without --force: ${destinationRelativePath}`,
|
|
1094
|
-
);
|
|
1117
|
+
throw new Error(managedForceConflictMessage(destinationRelativePath));
|
|
1095
1118
|
}
|
|
1096
1119
|
}
|
|
1097
1120
|
|
|
@@ -1142,6 +1165,22 @@ function ensureTemplateFilePresent(repoRoot, relativeTemplatePath, dryRun) {
|
|
|
1142
1165
|
return { status: 'created', file: destinationRelativePath };
|
|
1143
1166
|
}
|
|
1144
1167
|
|
|
1168
|
+
function ensureTargetedLegacyWorkflowShims(repoRoot, options) {
|
|
1169
|
+
const targetedPaths = Array.isArray(options.forceManagedPaths) ? options.forceManagedPaths : [];
|
|
1170
|
+
if (targetedPaths.length === 0) {
|
|
1171
|
+
return [];
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
const operations = [];
|
|
1175
|
+
for (const shim of LEGACY_WORKFLOW_SHIM_SPECS) {
|
|
1176
|
+
if (!shouldForceManagedPath(options, shim.relativePath)) {
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
operations.push(ensureGeneratedScriptShim(repoRoot, shim, { dryRun: options.dryRun, force: true }));
|
|
1180
|
+
}
|
|
1181
|
+
return operations;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1145
1184
|
function lockFilePath(repoRoot) {
|
|
1146
1185
|
return path.join(repoRoot, LOCK_FILE_RELATIVE);
|
|
1147
1186
|
}
|
|
@@ -1448,8 +1487,65 @@ function requireValue(rawArgs, index, flagName) {
|
|
|
1448
1487
|
return value;
|
|
1449
1488
|
}
|
|
1450
1489
|
|
|
1490
|
+
function normalizeManagedForcePath(rawPath) {
|
|
1491
|
+
if (typeof rawPath !== 'string') {
|
|
1492
|
+
return null;
|
|
1493
|
+
}
|
|
1494
|
+
const normalized = path.posix.normalize(rawPath.replace(/\\/g, '/'));
|
|
1495
|
+
if (!normalized || normalized === '.' || normalized.startsWith('../') || path.posix.isAbsolute(normalized)) {
|
|
1496
|
+
return null;
|
|
1497
|
+
}
|
|
1498
|
+
return normalized.startsWith('./') ? normalized.slice(2) : normalized;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
function collectForceManagedPaths(rawArgs, startIndex) {
|
|
1502
|
+
const forceManagedPaths = [];
|
|
1503
|
+
let nextIndex = startIndex;
|
|
1504
|
+
|
|
1505
|
+
while (nextIndex + 1 < rawArgs.length) {
|
|
1506
|
+
const candidate = rawArgs[nextIndex + 1];
|
|
1507
|
+
if (!candidate || candidate.startsWith('-')) {
|
|
1508
|
+
break;
|
|
1509
|
+
}
|
|
1510
|
+
const normalized = normalizeManagedForcePath(candidate);
|
|
1511
|
+
if (!normalized || !TARGETED_FORCEABLE_MANAGED_PATHS.has(normalized)) {
|
|
1512
|
+
throw new Error(`Unknown managed path after --force: ${candidate}`);
|
|
1513
|
+
}
|
|
1514
|
+
forceManagedPaths.push(normalized);
|
|
1515
|
+
nextIndex += 1;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
return { forceManagedPaths, nextIndex };
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
function appendForceArgs(args, options) {
|
|
1522
|
+
if (!options.force) {
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
args.push('--force');
|
|
1526
|
+
for (const managedPath of options.forceManagedPaths || []) {
|
|
1527
|
+
args.push(managedPath);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
function shouldForceManagedPath(options, relativePath) {
|
|
1532
|
+
if (!options.force) {
|
|
1533
|
+
return false;
|
|
1534
|
+
}
|
|
1535
|
+
const targetedPaths = Array.isArray(options.forceManagedPaths) ? options.forceManagedPaths : [];
|
|
1536
|
+
if (targetedPaths.length === 0) {
|
|
1537
|
+
return true;
|
|
1538
|
+
}
|
|
1539
|
+
const normalized = normalizeManagedForcePath(relativePath);
|
|
1540
|
+
return normalized !== null && targetedPaths.includes(normalized);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1451
1543
|
function parseCommonArgs(rawArgs, defaults) {
|
|
1452
1544
|
const options = { ...defaults };
|
|
1545
|
+
const supportsForce = Object.prototype.hasOwnProperty.call(options, 'force');
|
|
1546
|
+
if (supportsForce && !Array.isArray(options.forceManagedPaths)) {
|
|
1547
|
+
options.forceManagedPaths = [];
|
|
1548
|
+
}
|
|
1453
1549
|
|
|
1454
1550
|
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
1455
1551
|
const arg = rawArgs[index];
|
|
@@ -1471,7 +1567,17 @@ function parseCommonArgs(rawArgs, defaults) {
|
|
|
1471
1567
|
continue;
|
|
1472
1568
|
}
|
|
1473
1569
|
if (arg === '--force') {
|
|
1570
|
+
if (!supportsForce) {
|
|
1571
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
1572
|
+
}
|
|
1474
1573
|
options.force = true;
|
|
1574
|
+
const parsed = collectForceManagedPaths(rawArgs, index);
|
|
1575
|
+
if (parsed.forceManagedPaths.length > 0) {
|
|
1576
|
+
options.forceManagedPaths = Array.from(
|
|
1577
|
+
new Set([...(options.forceManagedPaths || []), ...parsed.forceManagedPaths]),
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
index = parsed.nextIndex;
|
|
1475
1581
|
continue;
|
|
1476
1582
|
}
|
|
1477
1583
|
if (arg === '--keep-stale-locks') {
|
|
@@ -1589,6 +1695,7 @@ function parseSetupArgs(rawArgs, defaults) {
|
|
|
1589
1695
|
function parseDoctorArgs(rawArgs) {
|
|
1590
1696
|
const doctorDefaults = {
|
|
1591
1697
|
target: process.cwd(),
|
|
1698
|
+
force: false,
|
|
1592
1699
|
dropStaleLocks: true,
|
|
1593
1700
|
skipAgents: false,
|
|
1594
1701
|
skipPackageJson: false,
|
|
@@ -1670,7 +1777,6 @@ function ensureParentWorkspaceView(repoRoot, dryRun) {
|
|
|
1670
1777
|
function hasGuardexBootstrapFiles(repoRoot) {
|
|
1671
1778
|
const required = [
|
|
1672
1779
|
'AGENTS.md',
|
|
1673
|
-
'scripts/agent-branch-start.sh',
|
|
1674
1780
|
'.githooks/pre-commit',
|
|
1675
1781
|
'.githooks/pre-push',
|
|
1676
1782
|
LOCK_FILE_RELATIVE,
|
|
@@ -1738,6 +1844,7 @@ function runSetupBootstrapInternal(options) {
|
|
|
1738
1844
|
target: installPayload.repoRoot,
|
|
1739
1845
|
dryRun: options.dryRun,
|
|
1740
1846
|
force: options.force,
|
|
1847
|
+
forceManagedPaths: options.forceManagedPaths,
|
|
1741
1848
|
dropStaleLocks: true,
|
|
1742
1849
|
skipAgents: options.skipAgents,
|
|
1743
1850
|
skipPackageJson: options.skipPackageJson,
|
|
@@ -1775,7 +1882,7 @@ function resolveSandboxTarget(repoRoot, worktreePath, targetPath) {
|
|
|
1775
1882
|
|
|
1776
1883
|
function buildSandboxSetupArgs(options, sandboxTarget) {
|
|
1777
1884
|
const args = ['setup', '--target', sandboxTarget, '--no-global-install', '--no-recursive'];
|
|
1778
|
-
|
|
1885
|
+
appendForceArgs(args, options);
|
|
1779
1886
|
if (options.skipAgents) args.push('--skip-agents');
|
|
1780
1887
|
if (options.skipPackageJson) args.push('--skip-package-json');
|
|
1781
1888
|
if (options.skipGitignore) args.push('--no-gitignore');
|
|
@@ -1786,7 +1893,7 @@ function buildSandboxSetupArgs(options, sandboxTarget) {
|
|
|
1786
1893
|
function buildSandboxDoctorArgs(options, sandboxTarget) {
|
|
1787
1894
|
const args = ['doctor', '--target', sandboxTarget];
|
|
1788
1895
|
if (options.dryRun) args.push('--dry-run');
|
|
1789
|
-
|
|
1896
|
+
appendForceArgs(args, options);
|
|
1790
1897
|
if (options.skipAgents) args.push('--skip-agents');
|
|
1791
1898
|
if (options.skipPackageJson) args.push('--skip-package-json');
|
|
1792
1899
|
if (options.skipGitignore) args.push('--no-gitignore');
|
|
@@ -1934,11 +2041,6 @@ function startProtectedBaseSandbox(blocked, { taskName, sandboxSuffix }) {
|
|
|
1934
2041
|
return startProtectedBaseSandboxFallback(blocked, sandboxSuffix);
|
|
1935
2042
|
}
|
|
1936
2043
|
|
|
1937
|
-
const startScript = path.join(blocked.repoRoot, 'scripts', 'agent-branch-start.sh');
|
|
1938
|
-
if (!fs.existsSync(startScript)) {
|
|
1939
|
-
return startProtectedBaseSandboxFallback(blocked, sandboxSuffix);
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
2044
|
const startResult = runPackageAsset('branchStart', [
|
|
1943
2045
|
'--task',
|
|
1944
2046
|
taskName,
|
|
@@ -2088,7 +2190,7 @@ function collectWorktreeDirtyPaths(worktreePath) {
|
|
|
2088
2190
|
}
|
|
2089
2191
|
|
|
2090
2192
|
function collectDoctorForceAddPaths(worktreePath) {
|
|
2091
|
-
return
|
|
2193
|
+
return REQUIRED_MANAGED_REPO_FILES
|
|
2092
2194
|
.filter((relativePath) => relativePath.startsWith('scripts/') || relativePath.startsWith('.githooks/'))
|
|
2093
2195
|
.filter((relativePath) => fs.existsSync(path.join(worktreePath, relativePath)));
|
|
2094
2196
|
}
|
|
@@ -2124,11 +2226,10 @@ function stripDoctorSandboxLocks(rawContent, branchName) {
|
|
|
2124
2226
|
}
|
|
2125
2227
|
|
|
2126
2228
|
function claimDoctorChangedLocks(metadata) {
|
|
2127
|
-
|
|
2128
|
-
if (!fs.existsSync(lockScript) || !metadata.branch) {
|
|
2229
|
+
if (!metadata.branch) {
|
|
2129
2230
|
return {
|
|
2130
2231
|
status: 'skipped',
|
|
2131
|
-
note: '
|
|
2232
|
+
note: 'missing sandbox branch metadata',
|
|
2132
2233
|
changedCount: 0,
|
|
2133
2234
|
deletedCount: 0,
|
|
2134
2235
|
};
|
|
@@ -2247,13 +2348,6 @@ function doctorFinishFlowIsPending(output) {
|
|
|
2247
2348
|
}
|
|
2248
2349
|
|
|
2249
2350
|
function finishDoctorSandboxBranch(blocked, metadata, options = {}) {
|
|
2250
|
-
const finishScript = path.join(metadata.worktreePath, 'scripts', 'agent-branch-finish.sh');
|
|
2251
|
-
if (!fs.existsSync(finishScript)) {
|
|
2252
|
-
return {
|
|
2253
|
-
status: 'skipped',
|
|
2254
|
-
note: `${path.relative(metadata.worktreePath, finishScript)} missing in sandbox`,
|
|
2255
|
-
};
|
|
2256
|
-
}
|
|
2257
2351
|
if (!hasOriginRemote(blocked.repoRoot)) {
|
|
2258
2352
|
return {
|
|
2259
2353
|
status: 'skipped',
|
|
@@ -2290,9 +2384,9 @@ function finishDoctorSandboxBranch(blocked, metadata, options = {}) {
|
|
|
2290
2384
|
const finishTimeoutMs = Math.max(180_000, (waitTimeoutSeconds + 60) * 1000);
|
|
2291
2385
|
const waitForMergeArg = options.waitForMerge === false ? '--no-wait-for-merge' : '--wait-for-merge';
|
|
2292
2386
|
|
|
2293
|
-
const finishResult =
|
|
2294
|
-
'
|
|
2295
|
-
[
|
|
2387
|
+
const finishResult = runPackageAsset(
|
|
2388
|
+
'branchFinish',
|
|
2389
|
+
['--branch', metadata.branch, '--base', blocked.branch, '--via-pr', waitForMergeArg, '--cleanup'],
|
|
2296
2390
|
{ cwd: metadata.worktreePath, timeout: finishTimeoutMs },
|
|
2297
2391
|
);
|
|
2298
2392
|
if (isSpawnFailure(finishResult)) {
|
|
@@ -2363,7 +2457,7 @@ function mergeDoctorSandboxRepairsBackToProtectedBase(options, blocked, metadata
|
|
|
2363
2457
|
...(autoCommitResult.stagedFiles || []),
|
|
2364
2458
|
...OMX_SCAFFOLD_DIRECTORIES,
|
|
2365
2459
|
...Array.from(OMX_SCAFFOLD_FILES.keys()),
|
|
2366
|
-
...
|
|
2460
|
+
...REQUIRED_MANAGED_REPO_FILES,
|
|
2367
2461
|
'bin',
|
|
2368
2462
|
'package.json',
|
|
2369
2463
|
'.gitignore',
|
|
@@ -3387,13 +3481,6 @@ function autoFinishReadyAgentBranches(repoRoot, options = {}) {
|
|
|
3387
3481
|
return summary;
|
|
3388
3482
|
}
|
|
3389
3483
|
|
|
3390
|
-
const finishScript = path.join(repoRoot, 'scripts', 'agent-branch-finish.sh');
|
|
3391
|
-
if (!fs.existsSync(finishScript)) {
|
|
3392
|
-
summary.enabled = false;
|
|
3393
|
-
summary.details.push(`Skipped auto-finish sweep (missing ${path.relative(repoRoot, finishScript)}).`);
|
|
3394
|
-
return summary;
|
|
3395
|
-
}
|
|
3396
|
-
|
|
3397
3484
|
const hasOrigin = gitRun(repoRoot, ['remote', 'get-url', 'origin'], { allowFailure: true }).status === 0;
|
|
3398
3485
|
if (!hasOrigin) {
|
|
3399
3486
|
summary.enabled = false;
|
|
@@ -4212,11 +4299,6 @@ function gitOutputLines(worktreePath, args) {
|
|
|
4212
4299
|
}
|
|
4213
4300
|
|
|
4214
4301
|
function claimLocksForAutoCommit(repoRoot, worktreePath, branch) {
|
|
4215
|
-
const lockScript = path.join(repoRoot, 'scripts', 'agent-file-locks.py');
|
|
4216
|
-
if (!fs.existsSync(lockScript)) {
|
|
4217
|
-
return;
|
|
4218
|
-
}
|
|
4219
|
-
|
|
4220
4302
|
const changedFiles = uniquePreserveOrder([
|
|
4221
4303
|
...gitOutputLines(worktreePath, ['diff', '--name-only', '--', '.', ':(exclude).omx/state/agent-file-locks.json']),
|
|
4222
4304
|
...gitOutputLines(worktreePath, ['diff', '--cached', '--name-only', '--', '.', ':(exclude).omx/state/agent-file-locks.json']),
|
|
@@ -5171,13 +5253,24 @@ function runInstallInternal(options) {
|
|
|
5171
5253
|
operations.push(...ensureOmxScaffold(repoRoot, Boolean(options.dryRun)));
|
|
5172
5254
|
|
|
5173
5255
|
for (const templateFile of TEMPLATE_FILES) {
|
|
5174
|
-
operations.push(
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5256
|
+
operations.push(
|
|
5257
|
+
copyTemplateFile(
|
|
5258
|
+
repoRoot,
|
|
5259
|
+
templateFile,
|
|
5260
|
+
shouldForceManagedPath(options, toDestinationPath(templateFile)),
|
|
5261
|
+
Boolean(options.dryRun),
|
|
5262
|
+
),
|
|
5263
|
+
);
|
|
5178
5264
|
}
|
|
5265
|
+
operations.push(...ensureTargetedLegacyWorkflowShims(repoRoot, options));
|
|
5179
5266
|
for (const hookName of HOOK_NAMES) {
|
|
5180
|
-
|
|
5267
|
+
const hookRelativePath = path.posix.join('.githooks', hookName);
|
|
5268
|
+
operations.push(
|
|
5269
|
+
ensureHookShim(repoRoot, hookName, {
|
|
5270
|
+
dryRun: options.dryRun,
|
|
5271
|
+
force: shouldForceManagedPath(options, hookRelativePath),
|
|
5272
|
+
}),
|
|
5273
|
+
);
|
|
5181
5274
|
}
|
|
5182
5275
|
|
|
5183
5276
|
operations.push(ensureLockRegistry(repoRoot, Boolean(options.dryRun)));
|
|
@@ -5218,13 +5311,21 @@ function runFixInternal(options) {
|
|
|
5218
5311
|
operations.push(...ensureOmxScaffold(repoRoot, Boolean(options.dryRun)));
|
|
5219
5312
|
|
|
5220
5313
|
for (const templateFile of TEMPLATE_FILES) {
|
|
5314
|
+
if (shouldForceManagedPath(options, toDestinationPath(templateFile))) {
|
|
5315
|
+
operations.push(copyTemplateFile(repoRoot, templateFile, true, Boolean(options.dryRun)));
|
|
5316
|
+
continue;
|
|
5317
|
+
}
|
|
5221
5318
|
operations.push(ensureTemplateFilePresent(repoRoot, templateFile, Boolean(options.dryRun)));
|
|
5222
5319
|
}
|
|
5223
|
-
|
|
5224
|
-
operations.push(ensureGeneratedScriptShim(repoRoot, shim, options));
|
|
5225
|
-
}
|
|
5320
|
+
operations.push(...ensureTargetedLegacyWorkflowShims(repoRoot, options));
|
|
5226
5321
|
for (const hookName of HOOK_NAMES) {
|
|
5227
|
-
|
|
5322
|
+
const hookRelativePath = path.posix.join('.githooks', hookName);
|
|
5323
|
+
operations.push(
|
|
5324
|
+
ensureHookShim(repoRoot, hookName, {
|
|
5325
|
+
dryRun: options.dryRun,
|
|
5326
|
+
force: shouldForceManagedPath(options, hookRelativePath),
|
|
5327
|
+
}),
|
|
5328
|
+
);
|
|
5228
5329
|
}
|
|
5229
5330
|
|
|
5230
5331
|
operations.push(ensureLockRegistry(repoRoot, Boolean(options.dryRun)));
|
|
@@ -5284,7 +5385,7 @@ function runScanInternal(options) {
|
|
|
5284
5385
|
const requiredPaths = [
|
|
5285
5386
|
...OMX_SCAFFOLD_DIRECTORIES,
|
|
5286
5387
|
...Array.from(OMX_SCAFFOLD_FILES.keys()),
|
|
5287
|
-
...
|
|
5388
|
+
...REQUIRED_MANAGED_REPO_FILES,
|
|
5288
5389
|
];
|
|
5289
5390
|
|
|
5290
5391
|
for (const relativePath of requiredPaths) {
|
|
@@ -5294,7 +5395,7 @@ function runScanInternal(options) {
|
|
|
5294
5395
|
level: 'error',
|
|
5295
5396
|
code: 'missing-managed-file',
|
|
5296
5397
|
path: relativePath,
|
|
5297
|
-
message: `Missing managed
|
|
5398
|
+
message: `Missing managed repo file: ${relativePath}`,
|
|
5298
5399
|
});
|
|
5299
5400
|
}
|
|
5300
5401
|
}
|
|
@@ -5738,6 +5839,7 @@ function doctor(rawArgs) {
|
|
|
5738
5839
|
'--single-repo',
|
|
5739
5840
|
'--target',
|
|
5740
5841
|
repoPath,
|
|
5842
|
+
...(options.force ? ['--force', ...(options.forceManagedPaths || [])] : []),
|
|
5741
5843
|
...(options.dropStaleLocks ? [] : ['--keep-stale-locks']),
|
|
5742
5844
|
...(options.skipAgents ? ['--skip-agents'] : []),
|
|
5743
5845
|
...(options.skipPackageJson ? ['--skip-package-json'] : []),
|
|
@@ -5902,15 +6004,7 @@ function doctor(rawArgs) {
|
|
|
5902
6004
|
function review(rawArgs) {
|
|
5903
6005
|
const options = parseReviewArgs(rawArgs);
|
|
5904
6006
|
const repoRoot = resolveRepoRoot(options.target);
|
|
5905
|
-
const
|
|
5906
|
-
if (!fs.existsSync(reviewScriptPath)) {
|
|
5907
|
-
throw new Error(
|
|
5908
|
-
`Missing review bot script: ${reviewScriptPath}\n` +
|
|
5909
|
-
`Run '${SHORT_TOOL_NAME} setup --target ${repoRoot}' then '${SHORT_TOOL_NAME} doctor --target ${repoRoot}'.`,
|
|
5910
|
-
);
|
|
5911
|
-
}
|
|
5912
|
-
|
|
5913
|
-
const result = run('bash', [reviewScriptPath, ...options.passthroughArgs], { cwd: repoRoot });
|
|
6007
|
+
const result = runReviewBotCommand(repoRoot, options.passthroughArgs);
|
|
5914
6008
|
if (isSpawnFailure(result)) {
|
|
5915
6009
|
throw result.error;
|
|
5916
6010
|
}
|
|
@@ -6049,24 +6143,9 @@ function spawnDetachedAgentProcess({ command, args, cwd, logPath }) {
|
|
|
6049
6143
|
function agents(rawArgs) {
|
|
6050
6144
|
const options = parseAgentsArgs(rawArgs);
|
|
6051
6145
|
const repoRoot = resolveRepoRoot(options.target);
|
|
6052
|
-
const reviewScriptPath = path.join(repoRoot, 'scripts', 'review-bot-watch.sh');
|
|
6053
|
-
const pruneScriptPath = path.join(repoRoot, 'scripts', 'agent-worktree-prune.sh');
|
|
6054
6146
|
const statePath = agentsStatePathForRepo(repoRoot);
|
|
6055
6147
|
|
|
6056
6148
|
if (options.subcommand === 'start') {
|
|
6057
|
-
if (!fs.existsSync(reviewScriptPath)) {
|
|
6058
|
-
throw new Error(
|
|
6059
|
-
`Missing review bot script: ${reviewScriptPath}\n` +
|
|
6060
|
-
`Run '${SHORT_TOOL_NAME} setup --target ${repoRoot}' then '${SHORT_TOOL_NAME} doctor --target ${repoRoot}'.`,
|
|
6061
|
-
);
|
|
6062
|
-
}
|
|
6063
|
-
if (!fs.existsSync(pruneScriptPath)) {
|
|
6064
|
-
throw new Error(
|
|
6065
|
-
`Missing cleanup script: ${pruneScriptPath}\n` +
|
|
6066
|
-
`Run '${SHORT_TOOL_NAME} setup --target ${repoRoot}' then '${SHORT_TOOL_NAME} doctor --target ${repoRoot}'.`,
|
|
6067
|
-
);
|
|
6068
|
-
}
|
|
6069
|
-
|
|
6070
6149
|
const existingState = readAgentsState(repoRoot);
|
|
6071
6150
|
const existingReviewPid = Number.parseInt(String(existingState?.review?.pid || ''), 10);
|
|
6072
6151
|
const existingCleanupPid = Number.parseInt(String(existingState?.cleanup?.pid || ''), 10);
|
|
@@ -6091,8 +6170,17 @@ function agents(rawArgs) {
|
|
|
6091
6170
|
|
|
6092
6171
|
if (!reviewRunning) {
|
|
6093
6172
|
reviewPid = spawnDetachedAgentProcess({
|
|
6094
|
-
command:
|
|
6095
|
-
args: [
|
|
6173
|
+
command: process.execPath,
|
|
6174
|
+
args: [
|
|
6175
|
+
path.resolve(__filename),
|
|
6176
|
+
'internal',
|
|
6177
|
+
'run-shell',
|
|
6178
|
+
'reviewBot',
|
|
6179
|
+
'--target',
|
|
6180
|
+
repoRoot,
|
|
6181
|
+
'--interval',
|
|
6182
|
+
String(options.reviewIntervalSeconds),
|
|
6183
|
+
],
|
|
6096
6184
|
cwd: repoRoot,
|
|
6097
6185
|
logPath: reviewLogPath,
|
|
6098
6186
|
});
|
|
@@ -6143,7 +6231,7 @@ function agents(rawArgs) {
|
|
|
6143
6231
|
review: {
|
|
6144
6232
|
pid: reviewPid,
|
|
6145
6233
|
intervalSeconds: reviewIntervalSeconds,
|
|
6146
|
-
script:
|
|
6234
|
+
script: path.resolve(__filename),
|
|
6147
6235
|
logPath: reviewLogPath,
|
|
6148
6236
|
},
|
|
6149
6237
|
cleanup: {
|
|
@@ -6174,7 +6262,7 @@ function agents(rawArgs) {
|
|
|
6174
6262
|
return;
|
|
6175
6263
|
}
|
|
6176
6264
|
|
|
6177
|
-
const reviewStop = stopAgentProcessByPid(existingState?.review?.pid, '
|
|
6265
|
+
const reviewStop = stopAgentProcessByPid(existingState?.review?.pid, 'internal run-shell reviewBot');
|
|
6178
6266
|
const cleanupStop = stopAgentProcessByPid(existingState?.cleanup?.pid, `${path.basename(__filename)} cleanup`);
|
|
6179
6267
|
|
|
6180
6268
|
if (fs.existsSync(statePath)) {
|
|
@@ -6870,7 +6958,7 @@ function doctorAudit(rawArgs) {
|
|
|
6870
6958
|
ok('git core.hooksPath is .githooks');
|
|
6871
6959
|
}
|
|
6872
6960
|
|
|
6873
|
-
for (const relativePath of
|
|
6961
|
+
for (const relativePath of REQUIRED_MANAGED_REPO_FILES) {
|
|
6874
6962
|
const absolutePath = path.join(repoRoot, relativePath);
|
|
6875
6963
|
if (!fs.existsSync(absolutePath)) {
|
|
6876
6964
|
fail(`missing ${relativePath}`);
|
|
@@ -7084,7 +7172,10 @@ function internal(rawArgs) {
|
|
|
7084
7172
|
throw new Error(`Unknown internal command: ${subcommand || '(missing)'}`);
|
|
7085
7173
|
}
|
|
7086
7174
|
const { target, passthrough } = extractTargetedArgs(rest);
|
|
7087
|
-
const
|
|
7175
|
+
const repoRoot = resolveRepoRoot(target);
|
|
7176
|
+
const result = assetKey === 'reviewBot'
|
|
7177
|
+
? runReviewBotCommand(repoRoot, passthrough)
|
|
7178
|
+
: runPackageAsset(assetKey, passthrough, { cwd: repoRoot });
|
|
7088
7179
|
if (result.stdout) process.stdout.write(result.stdout);
|
|
7089
7180
|
if (result.stderr) process.stderr.write(result.stderr);
|
|
7090
7181
|
process.exitCode = result.status;
|
|
@@ -7149,7 +7240,7 @@ function migrate(rawArgs) {
|
|
|
7149
7240
|
}
|
|
7150
7241
|
|
|
7151
7242
|
const removableLegacyFiles = LEGACY_MANAGED_REPO_FILES.filter(
|
|
7152
|
-
(relativePath) => !
|
|
7243
|
+
(relativePath) => !REQUIRED_MANAGED_REPO_FILES.includes(relativePath),
|
|
7153
7244
|
);
|
|
7154
7245
|
const removalOps = removableLegacyFiles.map((relativePath) => removeLegacyManagedRepoFile(repoRoot, relativePath, { dryRun, force }));
|
|
7155
7246
|
removalOps.push(removeLegacyPackageScripts(repoRoot, dryRun));
|
|
@@ -7160,10 +7251,6 @@ function migrate(rawArgs) {
|
|
|
7160
7251
|
function cleanup(rawArgs) {
|
|
7161
7252
|
const options = parseCleanupArgs(rawArgs);
|
|
7162
7253
|
const repoRoot = resolveRepoRoot(options.target);
|
|
7163
|
-
const pruneScript = path.join(repoRoot, 'scripts', 'agent-worktree-prune.sh');
|
|
7164
|
-
if (!fs.existsSync(pruneScript)) {
|
|
7165
|
-
throw new Error(`Missing cleanup script: ${pruneScript}. Run '${SHORT_TOOL_NAME} setup' first.`);
|
|
7166
|
-
}
|
|
7167
7254
|
|
|
7168
7255
|
const args = [];
|
|
7169
7256
|
if (options.base) {
|
|
@@ -7229,11 +7316,6 @@ function cleanup(rawArgs) {
|
|
|
7229
7316
|
function merge(rawArgs) {
|
|
7230
7317
|
const options = parseMergeArgs(rawArgs);
|
|
7231
7318
|
const repoRoot = resolveRepoRoot(options.target);
|
|
7232
|
-
const mergeScript = path.join(repoRoot, 'scripts', 'agent-branch-merge.sh');
|
|
7233
|
-
|
|
7234
|
-
if (!fs.existsSync(mergeScript)) {
|
|
7235
|
-
throw new Error(`Missing merge script: ${mergeScript}. Run '${SHORT_TOOL_NAME} setup' first.`);
|
|
7236
|
-
}
|
|
7237
7319
|
|
|
7238
7320
|
const args = [];
|
|
7239
7321
|
if (options.base) {
|
|
@@ -7269,11 +7351,6 @@ function merge(rawArgs) {
|
|
|
7269
7351
|
function finish(rawArgs, defaults = {}) {
|
|
7270
7352
|
const options = parseFinishArgs(rawArgs, defaults);
|
|
7271
7353
|
const repoRoot = resolveRepoRoot(options.target);
|
|
7272
|
-
const finishScript = path.join(repoRoot, 'scripts', 'agent-branch-finish.sh');
|
|
7273
|
-
|
|
7274
|
-
if (!fs.existsSync(finishScript)) {
|
|
7275
|
-
throw new Error(`Missing finish script: ${finishScript}. Run '${SHORT_TOOL_NAME} setup' first.`);
|
|
7276
|
-
}
|
|
7277
7354
|
|
|
7278
7355
|
const worktreeEntries = listAgentWorktrees(repoRoot);
|
|
7279
7356
|
const worktreeByBranch = new Map(worktreeEntries.map((entry) => [entry.branch, entry.worktreePath]));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imdeadpool/guardex",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.19",
|
|
4
4
|
"description": "Guardian T-Rex for your multi-agent repo. Isolated worktrees, file locks, and PR-only merges stop parallel Codex & Claude agents from overwriting each other's work. Auto-wires Oh My Codex, Oh My Claude, OpenSpec, and Caveman.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"preferGlobal": true,
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
`GUARDEX_ON=0` disables Guardex for that repo.
|
|
8
8
|
`GUARDEX_ON=1` explicitly enables Guardex for that repo again.
|
|
9
9
|
|
|
10
|
+
**Task-size routing.** Small tasks stay in direct caveman-only mode. For typos, single-file tweaks, one-liners, version bumps, or similarly bounded asks, solve directly and do not escalate into heavy OMX orchestration just because a keyword appears. Treat `quick:`, `simple:`, `tiny:`, `minor:`, `small:`, `just:`, and `only:` as explicit lightweight escape hatches.
|
|
11
|
+
Promote to OMX orchestration only when the task is medium/large: multi-file behavior changes, API/schema work, refactors, migrations, architecture, cross-cutting scope, or long prompts. Heavy OMX modes (`ralph`, `autopilot`, `team`, `ultrawork`, `swarm`, `ralplan`) are for that larger scope. If the task grows while working, upgrade then.
|
|
12
|
+
|
|
10
13
|
**Isolation.** Every task runs on a dedicated `agent/*` branch + worktree. Start with `gx branch start "<task>" "<agent-name>"`. Treat the base branch (`main`/`dev`) as read-only while an agent branch is active. Never `git checkout <branch>` on a primary working tree (including nested repos); use `git worktree add` instead. The `.githooks/post-checkout` hook auto-reverts primary-branch switches during agent sessions - bypass only with `GUARDEX_ALLOW_PRIMARY_BRANCH_SWITCH=1`.
|
|
11
14
|
For every new task, including follow-up work in the same chat/session, if an assigned agent sub-branch/worktree is already open, continue in that sub-branch instead of creating a fresh lane unless the user explicitly redirects scope.
|
|
12
15
|
Never implement directly on the local/base branch checkout; keep it unchanged and perform all edits in the agent sub-branch/worktree.
|
|
@@ -8,4 +8,4 @@ Use when repo safety may be broken.
|
|
|
8
8
|
`gx status` -> `gx doctor` -> `gx status --strict`
|
|
9
9
|
|
|
10
10
|
Bootstrap: `gx setup`
|
|
11
|
-
Ops: `
|
|
11
|
+
Ops: `gx branch start "<task>" "<agent>"`, `gx locks claim --branch "<agent-branch>" <file...>`, `gx branch finish --branch "<agent-branch>" --base <base> --via-pr --wait-for-merge --cleanup`, `gx finish --all`, `gx cleanup`
|