@imdeadpool/guardex 7.0.27 → 7.0.34

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imdeadpool/guardex",
3
- "version": "7.0.27",
3
+ "version": "7.0.34",
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,
package/src/cli/main.js CHANGED
@@ -146,8 +146,10 @@ const {
146
146
  colorizeDoctorOutput,
147
147
  statusDot,
148
148
  printToolLogsSummary,
149
+ getInvokedCliName,
149
150
  usage,
150
151
  formatElapsedDuration,
152
+ startTransientSpinner,
151
153
  compactAutoFinishPathSegments,
152
154
  detectRecoverableAutoFinishConflict,
153
155
  printAutoFinishSummary,
@@ -889,6 +891,80 @@ function parseBooleanLike(raw) {
889
891
  return null;
890
892
  }
891
893
 
894
+ function autoDoctorEnabledForCurrentSession() {
895
+ const explicit = parseBooleanLike(process.env.GUARDEX_AUTO_DOCTOR);
896
+ if (explicit != null) {
897
+ return explicit;
898
+ }
899
+ return isInteractiveTerminal();
900
+ }
901
+
902
+ function shouldAutoRunDoctorFromStatus(statusPayload) {
903
+ const repo = statusPayload?.repo || {};
904
+ return Boolean(
905
+ autoDoctorEnabledForCurrentSession()
906
+ && repo.inGitRepo
907
+ && repo.guardexEnabled !== false
908
+ && repo.serviceStatus === 'degraded'
909
+ && repo.scan
910
+ && Number(repo.scan.findings || 0) > 0,
911
+ );
912
+ }
913
+
914
+ function runCliSubprocessWithSpinner(args, options = {}) {
915
+ return new Promise((resolve, reject) => {
916
+ const spinner = options.spinnerMessage
917
+ ? startTransientSpinner(options.spinnerMessage, {
918
+ prefix: options.spinnerPrefix || `[${TOOL_NAME}]`,
919
+ })
920
+ : { stop() {} };
921
+ const child = cp.spawn(process.execPath, [path.resolve(__filename), ...args], {
922
+ cwd: options.cwd || process.cwd(),
923
+ env: {
924
+ ...process.env,
925
+ GUARDEX_AUTO_DOCTOR: '0',
926
+ },
927
+ stdio: ['inherit', 'pipe', 'pipe'],
928
+ });
929
+
930
+ const stopSpinner = () => spinner.stop();
931
+ child.stdout.on('data', (chunk) => {
932
+ stopSpinner();
933
+ process.stdout.write(chunk);
934
+ });
935
+ child.stderr.on('data', (chunk) => {
936
+ stopSpinner();
937
+ process.stderr.write(chunk);
938
+ });
939
+ child.on('error', (error) => {
940
+ stopSpinner();
941
+ reject(error);
942
+ });
943
+ child.on('close', (code) => {
944
+ stopSpinner();
945
+ resolve(typeof code === 'number' ? code : 1);
946
+ });
947
+ });
948
+ }
949
+
950
+ async function maybeAutoRunDoctorFromDefaultStatus(statusPayload) {
951
+ if (!shouldAutoRunDoctorFromStatus(statusPayload)) {
952
+ return false;
953
+ }
954
+
955
+ const target = statusPayload?.repo?.target || process.cwd();
956
+ console.log(`[${TOOL_NAME}] Auto-repair: repo safety is degraded. Running '${SHORT_TOOL_NAME} doctor --current' now.`);
957
+ process.exitCode = await runCliSubprocessWithSpinner(
958
+ ['doctor', '--target', target, '--current'],
959
+ {
960
+ cwd: target,
961
+ spinnerPrefix: `[${TOOL_NAME}] Auto-repair:`,
962
+ spinnerMessage: 'preparing doctor workspace',
963
+ },
964
+ );
965
+ return true;
966
+ }
967
+
892
968
  function parseDotenvAssignmentValue(raw) {
893
969
  let value = String(raw || '').trim();
894
970
  if (!value) return '';
@@ -1158,6 +1234,84 @@ function promptYesNoStrict(question) {
1158
1234
  }
1159
1235
  }
1160
1236
 
1237
+ const VSCODE_EXTENSION_ID = 'Recodee.gitguardex-active-agents';
1238
+ const VSCODE_EXTENSION_DISPLAY_NAME = 'GitGuardex Active Agents';
1239
+
1240
+ function maybePromptInstallVscodeExtension(options) {
1241
+ if (options.dryRun) {
1242
+ console.log(
1243
+ `[${TOOL_NAME}] (dry-run) Would offer to install VS Code extension '${VSCODE_EXTENSION_ID}'.`,
1244
+ );
1245
+ return;
1246
+ }
1247
+
1248
+ if (envFlagIsTruthy(process.env.GUARDEX_SKIP_VSCODE_EXT_PROMPT)) {
1249
+ return;
1250
+ }
1251
+
1252
+ const codeProbe = cp.spawnSync('code', ['--version'], { stdio: 'ignore' });
1253
+ if (codeProbe.error || codeProbe.status !== 0) {
1254
+ return;
1255
+ }
1256
+
1257
+ const listProbe = cp.spawnSync('code', ['--list-extensions'], {
1258
+ encoding: 'utf8',
1259
+ stdio: ['ignore', 'pipe', 'ignore'],
1260
+ });
1261
+ if (!listProbe.error && listProbe.status === 0) {
1262
+ const alreadyInstalled = String(listProbe.stdout || '')
1263
+ .split('\n')
1264
+ .some((line) => line.trim().toLowerCase() === VSCODE_EXTENSION_ID.toLowerCase());
1265
+ if (alreadyInstalled) {
1266
+ console.log(
1267
+ `[${TOOL_NAME}] ✅ VS Code extension '${VSCODE_EXTENSION_ID}' already installed.`,
1268
+ );
1269
+ return;
1270
+ }
1271
+ }
1272
+
1273
+ let approved;
1274
+ if (options.yesGlobalInstall) {
1275
+ approved = true;
1276
+ } else if (options.noGlobalInstall) {
1277
+ approved = false;
1278
+ } else if (!isInteractiveTerminal()) {
1279
+ console.log(
1280
+ `[${TOOL_NAME}] Optional VS Code extension '${VSCODE_EXTENSION_ID}' ` +
1281
+ `(${VSCODE_EXTENSION_DISPLAY_NAME}) not installed. ` +
1282
+ `Install later: code --install-extension ${VSCODE_EXTENSION_ID}`,
1283
+ );
1284
+ return;
1285
+ } else {
1286
+ approved = promptYesNoStrict(
1287
+ `Install VS Code extension '${VSCODE_EXTENSION_ID}' (${VSCODE_EXTENSION_DISPLAY_NAME}) now?`,
1288
+ );
1289
+ }
1290
+
1291
+ if (!approved) {
1292
+ console.log(
1293
+ `[${TOOL_NAME}] ⚠️ VS Code extension skipped. ` +
1294
+ `Set GUARDEX_SKIP_VSCODE_EXT_PROMPT=1 to silence or run 'code --install-extension ${VSCODE_EXTENSION_ID}' later.`,
1295
+ );
1296
+ return;
1297
+ }
1298
+
1299
+ const install = cp.spawnSync('code', ['--install-extension', VSCODE_EXTENSION_ID], {
1300
+ stdio: 'inherit',
1301
+ });
1302
+ if (install.error || install.status !== 0) {
1303
+ console.log(
1304
+ `[${TOOL_NAME}] ⚠️ VS Code extension install failed. ` +
1305
+ `Retry manually: code --install-extension ${VSCODE_EXTENSION_ID}`,
1306
+ );
1307
+ return;
1308
+ }
1309
+ console.log(
1310
+ `[${TOOL_NAME}] ✅ VS Code extension '${VSCODE_EXTENSION_ID}' installed. ` +
1311
+ `Reload the VS Code window to activate it.`,
1312
+ );
1313
+ }
1314
+
1161
1315
  function resolveGlobalInstallApproval(options) {
1162
1316
  if (options.yesGlobalInstall && options.noGlobalInstall) {
1163
1317
  throw new Error('Cannot use both --yes-global-install and --no-global-install');
@@ -1672,12 +1826,62 @@ function setExitCodeFromScan(scan) {
1672
1826
  process.exitCode = 0;
1673
1827
  }
1674
1828
 
1675
- function status(rawArgs) {
1676
- const options = parseCommonArgs(rawArgs, {
1677
- target: process.cwd(),
1678
- json: false,
1679
- });
1829
+ function printStatusRepairHint(scanResult) {
1830
+ if (!scanResult || scanResult.guardexEnabled === false) {
1831
+ return;
1832
+ }
1833
+ if (scanResult.errors === 0 && scanResult.warnings === 0) {
1834
+ return;
1835
+ }
1836
+
1837
+ const scanHint = scanResult.errors === 0
1838
+ ? `review warning details with '${SHORT_TOOL_NAME} scan'`
1839
+ : `inspect detailed findings with '${SHORT_TOOL_NAME} scan'`;
1840
+ console.log(
1841
+ `[${TOOL_NAME}] Quick fix: run '${SHORT_TOOL_NAME} doctor' to repair drift, or ${scanHint}.`,
1842
+ );
1843
+ }
1844
+
1845
+ function countAgentWorktrees(repoRoot) {
1846
+ if (!repoRoot || typeof repoRoot !== 'string') return 0;
1847
+ const relPaths = ['.omc/agent-worktrees', '.omx/agent-worktrees'];
1848
+ let count = 0;
1849
+ for (const rel of relPaths) {
1850
+ try {
1851
+ const entries = fs.readdirSync(path.join(repoRoot, rel), { withFileTypes: true });
1852
+ count += entries.filter((entry) => entry.isDirectory()).length;
1853
+ } catch (_err) {
1854
+ // missing dir or permission error; not an active-agent signal
1855
+ }
1856
+ }
1857
+ return count;
1858
+ }
1680
1859
 
1860
+ function deriveNextStepHint({ scanResult, worktreeCount, invoked, inGitRepo }) {
1861
+ if (!inGitRepo) {
1862
+ return `${invoked} setup --target <path-to-git-repo> # initialize guardrails in a repo`;
1863
+ }
1864
+ if (!scanResult) {
1865
+ return `${invoked} setup # bootstrap repo guardrails`;
1866
+ }
1867
+ if (scanResult.guardexEnabled === false) {
1868
+ return `set GUARDEX_ON=1 in .env # re-enable guardrails, then '${invoked} doctor'`;
1869
+ }
1870
+ const branch = scanResult.branch || '';
1871
+ if (branch.startsWith('agent/')) {
1872
+ return `${invoked} branch finish --branch "${branch}" --via-pr --wait-for-merge --cleanup`;
1873
+ }
1874
+ if (worktreeCount > 0) {
1875
+ const plural = worktreeCount === 1 ? 'worktree' : 'worktrees';
1876
+ return `${invoked} finish --all # ${worktreeCount} active agent ${plural}`;
1877
+ }
1878
+ if (scanResult.errors > 0 || scanResult.warnings > 0) {
1879
+ return `${invoked} doctor # repair drift`;
1880
+ }
1881
+ return `${invoked} branch start "<task>" "<agent-name>" # start a sandboxed agent task`;
1882
+ }
1883
+
1884
+ function collectServicesSnapshot() {
1681
1885
  const toolchain = toolchainModule.detectGlobalToolchainPackages();
1682
1886
  const npmServices = GLOBAL_TOOLCHAIN_PACKAGES.map((pkg) => {
1683
1887
  const service = toolchainModule.getGlobalToolchainService(pkg);
@@ -1701,18 +1905,103 @@ function status(rawArgs) {
1701
1905
  const localCompanionServices = toolchainModule.detectOptionalLocalCompanionTools().map((tool) => ({
1702
1906
  name: tool.name,
1703
1907
  displayName: tool.displayName || tool.name,
1908
+ installCommand: tool.installCommand,
1909
+ installArgs: Array.isArray(tool.installArgs) ? [...tool.installArgs] : [],
1704
1910
  status: tool.status,
1705
1911
  }));
1706
1912
  const requiredSystemTools = toolchainModule.detectRequiredSystemTools();
1707
1913
  const services = [
1708
1914
  ...npmServices,
1709
- ...localCompanionServices,
1915
+ ...localCompanionServices.map((tool) => ({
1916
+ name: tool.name,
1917
+ displayName: tool.displayName,
1918
+ status: tool.status,
1919
+ })),
1710
1920
  ...requiredSystemTools.map((tool) => ({
1711
1921
  name: tool.name,
1712
1922
  displayName: tool.displayName || tool.name,
1713
1923
  status: tool.status,
1714
1924
  })),
1715
1925
  ];
1926
+ return { toolchain, npmServices, localCompanionServices, requiredSystemTools, services };
1927
+ }
1928
+
1929
+ function maybePromptInstallMissingCompanions(snapshot) {
1930
+ if (envFlagIsTruthy(process.env.GUARDEX_SKIP_COMPANION_PROMPT)) {
1931
+ return { handled: false, installed: false };
1932
+ }
1933
+ const interactive = Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY);
1934
+ const autoApproval = toolchainModule.parseAutoApproval('GUARDEX_AUTO_COMPANION_APPROVAL');
1935
+ if (!interactive && autoApproval == null) {
1936
+ return { handled: false, installed: false };
1937
+ }
1938
+ if (!snapshot.toolchain.ok) {
1939
+ return { handled: false, installed: false };
1940
+ }
1941
+
1942
+ const missingPackages = snapshot.npmServices
1943
+ .filter((service) => service.status !== 'active')
1944
+ .map((service) => service.packageName);
1945
+ const missingLocalTools = snapshot.localCompanionServices.filter((tool) => tool.status !== 'active');
1946
+ if (missingPackages.length === 0 && missingLocalTools.length === 0) {
1947
+ return { handled: false, installed: false };
1948
+ }
1949
+
1950
+ const missingNames = [
1951
+ ...missingPackages.map((pkg) => toolchainModule.formatGlobalToolchainServiceName(pkg)),
1952
+ ...missingLocalTools.map((tool) => tool.displayName || tool.name),
1953
+ ];
1954
+ console.log(`[${TOOL_NAME}] Missing companion tools: ${missingNames.join(', ')}.`);
1955
+
1956
+ const promptText = toolchainModule.buildMissingCompanionInstallPrompt(missingPackages, missingLocalTools);
1957
+ const approved = interactive
1958
+ ? toolchainModule.promptYesNoStrict(promptText)
1959
+ : autoApproval;
1960
+
1961
+ if (!approved) {
1962
+ console.log(
1963
+ `[${TOOL_NAME}] Skipped companion install. Set GUARDEX_SKIP_COMPANION_PROMPT=1 to silence this prompt, ` +
1964
+ `or run '${getInvokedCliName()} setup --install-only' later to install manually.`,
1965
+ );
1966
+ return { handled: true, installed: false };
1967
+ }
1968
+
1969
+ const result = toolchainModule.performCompanionInstall(missingPackages, missingLocalTools);
1970
+ if (result.status === 'installed') {
1971
+ console.log(
1972
+ `[${TOOL_NAME}] ✅ Companion tools installed (${(result.packages || []).join(', ')}).`,
1973
+ );
1974
+ return { handled: true, installed: true };
1975
+ }
1976
+ if (result.status === 'failed') {
1977
+ console.log(
1978
+ `[${TOOL_NAME}] ⚠️ Companion install failed: ${result.reason}. ` +
1979
+ `Retry with '${getInvokedCliName()} setup --install-only'.`,
1980
+ );
1981
+ return { handled: true, installed: false };
1982
+ }
1983
+ return { handled: true, installed: false };
1984
+ }
1985
+
1986
+ function status(rawArgs) {
1987
+ const { found: verboseFlag, remaining: afterVerbose } = extractFlag(rawArgs, '--verbose');
1988
+ const options = parseCommonArgs(afterVerbose, {
1989
+ target: process.cwd(),
1990
+ json: false,
1991
+ });
1992
+ const forceCompact = envFlagIsTruthy(process.env.GUARDEX_COMPACT_STATUS);
1993
+ const forceExpand = envFlagIsTruthy(process.env.GUARDEX_VERBOSE_STATUS) || verboseFlag;
1994
+ const interactive = Boolean(process.stdout.isTTY);
1995
+ const invokedBasename = getInvokedCliName();
1996
+
1997
+ let snapshot = collectServicesSnapshot();
1998
+ if (!options.json) {
1999
+ const result = maybePromptInstallMissingCompanions(snapshot);
2000
+ if (result.installed) {
2001
+ snapshot = collectServicesSnapshot();
2002
+ }
2003
+ }
2004
+ let { toolchain, npmServices, localCompanionServices, requiredSystemTools, services } = snapshot;
1716
2005
 
1717
2006
  const targetPath = path.resolve(options.target);
1718
2007
  const inGitRepo = isGitRepo(targetPath);
@@ -1752,18 +2041,27 @@ function status(rawArgs) {
1752
2041
  if (options.json) {
1753
2042
  process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
1754
2043
  process.exitCode = 0;
1755
- return;
2044
+ return payload;
1756
2045
  }
1757
2046
 
2047
+ const allServicesActive = toolchain.ok && services.every((service) => service.status === 'active');
2048
+ const compact = !forceExpand && (forceCompact || (interactive && allServicesActive));
2049
+
1758
2050
  console.log(`[${TOOL_NAME}] CLI: ${payload.cli.runtime}`);
1759
2051
  if (!toolchain.ok) {
1760
2052
  console.log(`[${TOOL_NAME}] ⚠️ Could not detect global services: ${toolchain.error}`);
1761
2053
  }
1762
2054
 
1763
- console.log(`[${TOOL_NAME}] Global services:`);
1764
- for (const service of services) {
1765
- const serviceLabel = service.displayName || service.name;
1766
- console.log(` - ${statusDot(service.status)} ${serviceLabel}: ${service.status}`);
2055
+ if (compact) {
2056
+ console.log(
2057
+ `[${TOOL_NAME}] Global services: ${services.length}/${services.length} ${statusDot('active')} active`,
2058
+ );
2059
+ } else {
2060
+ console.log(`[${TOOL_NAME}] Global services:`);
2061
+ for (const service of services) {
2062
+ const serviceLabel = service.displayName || service.name;
2063
+ console.log(` - ${statusDot(service.status)} ${serviceLabel}: ${service.status}`);
2064
+ }
1767
2065
  }
1768
2066
  const inactiveOptionalCompanions = [...npmServices, ...localCompanionServices]
1769
2067
  .filter((service) => service.status !== 'active')
@@ -1799,8 +2097,16 @@ function status(rawArgs) {
1799
2097
  console.log(
1800
2098
  `[${TOOL_NAME}] Repo safety service: ${statusDot('inactive')} inactive (no git repository at target).`,
1801
2099
  );
2100
+ const inactiveHint = deriveNextStepHint({
2101
+ scanResult: null,
2102
+ worktreeCount: 0,
2103
+ invoked: invokedBasename,
2104
+ inGitRepo,
2105
+ });
2106
+ console.log(`[${TOOL_NAME}] Next: ${inactiveHint}`);
2107
+ printToolLogsSummary({ invokedBasename, compact });
1802
2108
  process.exitCode = 0;
1803
- return;
2109
+ return payload;
1804
2110
  }
1805
2111
 
1806
2112
  if (scanResult.guardexEnabled === false) {
@@ -1809,9 +2115,23 @@ function status(rawArgs) {
1809
2115
  );
1810
2116
  console.log(`[${TOOL_NAME}] Repo: ${scanResult.repoRoot}`);
1811
2117
  console.log(`[${TOOL_NAME}] Branch: ${scanResult.branch}`);
1812
- printToolLogsSummary();
2118
+ const worktreeCountDisabled = countAgentWorktrees(scanResult.repoRoot);
2119
+ if (worktreeCountDisabled > 0) {
2120
+ const plural = worktreeCountDisabled === 1 ? 'worktree' : 'worktrees';
2121
+ console.log(
2122
+ `[${TOOL_NAME}] ⚠ ${worktreeCountDisabled} active agent ${plural} under .omc/agent-worktrees or .omx/agent-worktrees.`,
2123
+ );
2124
+ }
2125
+ const disabledHint = deriveNextStepHint({
2126
+ scanResult,
2127
+ worktreeCount: worktreeCountDisabled,
2128
+ invoked: invokedBasename,
2129
+ inGitRepo,
2130
+ });
2131
+ console.log(`[${TOOL_NAME}] Next: ${disabledHint}`);
2132
+ printToolLogsSummary({ invokedBasename, compact });
1813
2133
  process.exitCode = 0;
1814
- return;
2134
+ return payload;
1815
2135
  }
1816
2136
 
1817
2137
  if (scanResult.errors === 0 && scanResult.warnings === 0) {
@@ -1820,23 +2140,36 @@ function status(rawArgs) {
1820
2140
  console.log(
1821
2141
  `[${TOOL_NAME}] Repo safety service: ${statusDot('degraded')} degraded (${scanResult.warnings} warning(s)).`,
1822
2142
  );
1823
- console.log(`[${TOOL_NAME}] Run '${TOOL_NAME} scan' to review warning details.`);
1824
2143
  } else if (scanResult.warnings === 0) {
1825
2144
  console.log(
1826
2145
  `[${TOOL_NAME}] Repo safety service: ${statusDot('degraded')} degraded (${scanResult.errors} error(s)).`,
1827
2146
  );
1828
- console.log(`[${TOOL_NAME}] Run '${TOOL_NAME} scan' for detailed findings.`);
1829
2147
  } else {
1830
2148
  console.log(
1831
2149
  `[${TOOL_NAME}] Repo safety service: ${statusDot('degraded')} degraded (${scanResult.errors} error(s), ${scanResult.warnings} warning(s)).`,
1832
2150
  );
1833
- console.log(`[${TOOL_NAME}] Run '${TOOL_NAME} scan' for detailed findings.`);
1834
2151
  }
2152
+ printStatusRepairHint(scanResult);
1835
2153
  console.log(`[${TOOL_NAME}] Repo: ${scanResult.repoRoot}`);
1836
2154
  console.log(`[${TOOL_NAME}] Branch: ${scanResult.branch}`);
1837
- printToolLogsSummary();
2155
+ const worktreeCountActive = countAgentWorktrees(scanResult.repoRoot);
2156
+ if (worktreeCountActive > 0) {
2157
+ const plural = worktreeCountActive === 1 ? 'worktree' : 'worktrees';
2158
+ console.log(
2159
+ `[${TOOL_NAME}] ⚠ ${worktreeCountActive} active agent ${plural} → ${invokedBasename} finish --all`,
2160
+ );
2161
+ }
2162
+ const activeHint = deriveNextStepHint({
2163
+ scanResult,
2164
+ worktreeCount: worktreeCountActive,
2165
+ invoked: invokedBasename,
2166
+ inGitRepo,
2167
+ });
2168
+ console.log(`[${TOOL_NAME}] Next: ${activeHint}`);
2169
+ printToolLogsSummary({ invokedBasename, compact });
1838
2170
 
1839
2171
  process.exitCode = 0;
2172
+ return payload;
1840
2173
  }
1841
2174
 
1842
2175
  function install(rawArgs) {
@@ -2580,6 +2913,9 @@ function setup(rawArgs) {
2580
2913
  console.log(`[${TOOL_NAME}] ⚠️ ${warning}`);
2581
2914
  }
2582
2915
  }
2916
+
2917
+ maybePromptInstallVscodeExtension(options);
2918
+
2583
2919
  const requiredSystemTools = toolchainModule.detectRequiredSystemTools();
2584
2920
  const missingSystemTools = requiredSystemTools.filter((tool) => tool.status !== 'active');
2585
2921
  if (missingSystemTools.length === 0) {
@@ -3246,13 +3582,14 @@ function protect(rawArgs) {
3246
3582
  throw new Error(`Unknown protect subcommand: ${subcommand}`);
3247
3583
  }
3248
3584
 
3249
- function main() {
3585
+ async function main() {
3250
3586
  const args = process.argv.slice(2);
3251
3587
 
3252
3588
  if (args.length === 0) {
3253
3589
  toolchainModule.maybeSelfUpdateBeforeStatus();
3254
3590
  toolchainModule.maybeOpenSpecUpdateBeforeStatus();
3255
- status([]);
3591
+ const statusPayload = status([]);
3592
+ await maybeAutoRunDoctorFromDefaultStatus(statusPayload);
3256
3593
  return;
3257
3594
  }
3258
3595
 
@@ -3322,9 +3659,9 @@ function main() {
3322
3659
  throw new Error(`Unknown command: ${command}`);
3323
3660
  }
3324
3661
 
3325
- function runFromBin() {
3662
+ async function runFromBin() {
3326
3663
  try {
3327
- main();
3664
+ await main();
3328
3665
  } catch (error) {
3329
3666
  console.error(`[${TOOL_NAME}] ${error.message}`);
3330
3667
  process.exitCode = 1;
@@ -3332,7 +3669,7 @@ function runFromBin() {
3332
3669
  }
3333
3670
 
3334
3671
  if (require.main === module) {
3335
- runFromBin();
3672
+ void runFromBin();
3336
3673
  }
3337
3674
 
3338
3675
  module.exports = {
package/src/context.js CHANGED
@@ -363,27 +363,68 @@ const SUGGESTIBLE_COMMANDS = [
363
363
  'print-agents-snippet',
364
364
  'release',
365
365
  ];
366
- const CLI_COMMAND_DESCRIPTIONS = [
367
- ['status', 'Show GitGuardex CLI + service health without modifying files'],
368
- ['setup', 'Install, repair, and verify guardrails (flags: --repair, --install-only, --target, --current)'],
369
- ['doctor', 'Repair drift + verify (flags: --target, --current; auto-sandboxes on protected main)'],
370
- ['branch', 'CLI-owned branch workflow surface (start/finish/merge)'],
371
- ['locks', 'CLI-owned file lock surface (claim/allow-delete/release/status/validate)'],
372
- ['worktree', 'CLI-owned worktree cleanup surface (prune)'],
373
- ['hook', 'Hook dispatch/install surface used by managed shims'],
374
- ['migrate', 'Convert legacy repo-local installs to the zero-copy CLI-owned surface'],
375
- ['install-agent-skills', 'Install Guardex Codex/Claude skills into the user home'],
376
- ['protect', 'Manage protected branches (list/add/remove/set/reset)'],
377
- ['merge', 'Create/reuse an integration lane and merge overlapping agent branches'],
378
- ['sync', 'Sync agent branches with origin/<base>'],
379
- ['finish', 'Commit + PR + merge completed agent branches (--all, --branch)'],
380
- ['cleanup', 'Prune merged/stale agent branches and worktrees'],
381
- ['release', 'Create or update the current GitHub release with README-generated notes'],
382
- ['agents', 'Start/stop repo-scoped review + cleanup bots'],
383
- ['prompt', 'Print AI setup checklist or named slices (--exec, --part, --list-parts, --snippet)'],
384
- ['report', 'Security/safety reports (e.g. OpenSSF scorecard, session severity)'],
385
- ['help', 'Show this help output'],
386
- ['version', 'Print GitGuardex version'],
366
+ // CLI_COMMAND_GROUPS is the grouped source of truth the `gx --help` /
367
+ // `gx` no-args renderer uses. Each group is ordered roughly by how often a
368
+ // user reaches for it so the help screen answers "what do I run first?"
369
+ // before "what else can this do?". CLI_COMMAND_DESCRIPTIONS preserves the
370
+ // flat export for callers that still want the ungrouped list.
371
+ const CLI_COMMAND_GROUPS = [
372
+ {
373
+ label: 'Setup & health',
374
+ description: 'Install, repair, and check a repo. Run these first on a new clone.',
375
+ commands: [
376
+ ['setup', 'Install, repair, and verify guardrails (flags: --repair, --install-only, --target, --current)'],
377
+ ['doctor', 'Repair drift + verify (flags: --target, --current; auto-sandboxes on protected main)'],
378
+ ['status', 'Show GitGuardex CLI + service health without modifying files'],
379
+ ['migrate', 'Convert legacy repo-local installs to the zero-copy CLI-owned surface'],
380
+ ],
381
+ },
382
+ {
383
+ label: 'Branch workflow',
384
+ description: 'The sandbox commit PR → merge loop for agent-owned branches.',
385
+ commands: [
386
+ ['branch', 'CLI-owned branch workflow surface (start/finish/merge)'],
387
+ ['finish', 'Commit + PR + merge completed agent branches (--all, --branch)'],
388
+ ['merge', 'Create/reuse an integration lane and merge overlapping agent branches'],
389
+ ['sync', 'Sync agent branches with origin/<base>'],
390
+ ['cleanup', 'Prune merged/stale agent branches and worktrees'],
391
+ ],
392
+ },
393
+ {
394
+ label: 'Coordination',
395
+ description: 'File locks, worktrees, hooks, and protected-branch policy.',
396
+ commands: [
397
+ ['locks', 'CLI-owned file lock surface (claim/allow-delete/release/status/validate)'],
398
+ ['worktree', 'CLI-owned worktree cleanup surface (prune)'],
399
+ ['hook', 'Hook dispatch/install surface used by managed shims'],
400
+ ['protect', 'Manage protected branches (list/add/remove/set/reset)'],
401
+ ],
402
+ },
403
+ {
404
+ label: 'Agents & reports',
405
+ description: 'Review / cleanup bots, AI setup prompts, and safety reports.',
406
+ commands: [
407
+ ['agents', 'Start/stop repo-scoped review + cleanup bots'],
408
+ ['install-agent-skills', 'Install Guardex Codex/Claude skills into the user home'],
409
+ ['prompt', 'Print AI setup checklist or named slices (--exec, --part, --list-parts, --snippet)'],
410
+ ['report', 'Security/safety reports (e.g. OpenSSF scorecard, session severity)'],
411
+ ['release', 'Create or update the current GitHub release with README-generated notes'],
412
+ ],
413
+ },
414
+ {
415
+ label: 'Meta',
416
+ description: 'Version + help.',
417
+ commands: [
418
+ ['help', 'Show this help output'],
419
+ ['version', 'Print GitGuardex version'],
420
+ ],
421
+ },
422
+ ];
423
+ const CLI_COMMAND_DESCRIPTIONS = CLI_COMMAND_GROUPS.flatMap((group) => group.commands);
424
+ const CLI_QUICKSTART_STEPS = [
425
+ 'gx setup',
426
+ 'gx branch start "<task>" "<agent>"',
427
+ 'gx branch finish --via-pr --wait-for-merge --cleanup',
387
428
  ];
388
429
  const DEPRECATED_COMMAND_ALIASES = new Map([
389
430
  ['init', { target: 'setup', hint: 'gx setup' }],
@@ -686,6 +727,8 @@ module.exports = {
686
727
  COMMAND_TYPO_ALIASES,
687
728
  SUGGESTIBLE_COMMANDS,
688
729
  CLI_COMMAND_DESCRIPTIONS,
730
+ CLI_COMMAND_GROUPS,
731
+ CLI_QUICKSTART_STEPS,
689
732
  DEPRECATED_COMMAND_ALIASES,
690
733
  AGENT_BOT_DESCRIPTIONS,
691
734
  DOCTOR_AUTO_FINISH_DETAIL_LIMIT,