@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.
@@ -109,7 +109,7 @@ const TEMPLATE_FILES = [
109
109
  'vscode/guardex-active-agents/README.md',
110
110
  ];
111
111
 
112
- const SCRIPT_SHIMS = [
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
- 'scripts/agent-branch-start.sh',
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 REQUIRED_WORKFLOW_FILES = [
149
- ...TEMPLATE_FILES.map((entry) => toDestinationPath(entry)),
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
- 'scripts/agent-session-state.js',
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/agent-branch-start.sh',
244
- 'scripts/agent-file-locks.py',
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 new shim-based CLI-owned surface'],
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(`Refusing to overwrite existing file without --force: ${relativePath}`);
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
- if (options.force) args.push('--force');
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
- if (options.force) args.push('--force');
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 REQUIRED_WORKFLOW_FILES
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
- const lockScript = path.join(metadata.worktreePath, 'scripts', 'agent-file-locks.py');
2128
- if (!fs.existsSync(lockScript) || !metadata.branch) {
2229
+ if (!metadata.branch) {
2129
2230
  return {
2130
2231
  status: 'skipped',
2131
- note: 'lock helper unavailable in sandbox',
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 = run(
2294
- 'bash',
2295
- [finishScript, '--branch', metadata.branch, '--base', blocked.branch, '--via-pr', waitForMergeArg, '--cleanup'],
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
- ...REQUIRED_WORKFLOW_FILES,
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(copyTemplateFile(repoRoot, templateFile, Boolean(options.force), Boolean(options.dryRun)));
5175
- }
5176
- for (const shim of SCRIPT_SHIMS) {
5177
- operations.push(ensureGeneratedScriptShim(repoRoot, shim, options));
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
- operations.push(ensureHookShim(repoRoot, hookName, options));
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
- for (const shim of SCRIPT_SHIMS) {
5224
- operations.push(ensureGeneratedScriptShim(repoRoot, shim, options));
5225
- }
5320
+ operations.push(...ensureTargetedLegacyWorkflowShims(repoRoot, options));
5226
5321
  for (const hookName of HOOK_NAMES) {
5227
- operations.push(ensureHookShim(repoRoot, hookName, options));
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
- ...REQUIRED_WORKFLOW_FILES,
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 workflow file: ${relativePath}`,
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 reviewScriptPath = path.join(repoRoot, 'scripts', 'review-bot-watch.sh');
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: 'bash',
6095
- args: [reviewScriptPath, '--interval', String(options.reviewIntervalSeconds)],
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: reviewScriptPath,
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, 'review-bot-watch.sh');
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 REQUIRED_WORKFLOW_FILES) {
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 result = runPackageAsset(assetKey, passthrough, { cwd: resolveRepoRoot(target) });
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) => !REQUIRED_WORKFLOW_FILES.includes(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.18",
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: `bash scripts/codex-agent.sh "<task>" "<agent>"`, `gx finish --all`, `gx cleanup`
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`