@imdeadpool/guardex 7.0.39 → 7.0.43

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.
Files changed (85) hide show
  1. package/README.md +85 -16
  2. package/package.json +2 -1
  3. package/skills/gitguardex/SKILL.md +13 -0
  4. package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
  5. package/src/agents/cleanup-sessions.js +126 -0
  6. package/src/agents/detect.js +160 -0
  7. package/src/agents/finish.js +172 -0
  8. package/src/agents/inspect.js +189 -0
  9. package/src/agents/launch.js +240 -0
  10. package/src/agents/registry.js +133 -0
  11. package/src/agents/selection-panel.js +571 -0
  12. package/src/agents/sessions.js +151 -0
  13. package/src/agents/start.js +591 -0
  14. package/src/agents/status.js +143 -0
  15. package/src/agents/terminal.js +152 -0
  16. package/src/budget/index.js +343 -0
  17. package/src/ci-init/index.js +265 -0
  18. package/src/cli/args.js +305 -1
  19. package/src/cli/main.js +262 -132
  20. package/src/cockpit/action-runner.js +3 -0
  21. package/src/cockpit/actions.js +80 -0
  22. package/src/cockpit/control.js +1121 -0
  23. package/src/cockpit/index.js +426 -0
  24. package/src/cockpit/keybindings.js +224 -0
  25. package/src/cockpit/kitty-layout.js +549 -0
  26. package/src/cockpit/kitty-tree.js +144 -0
  27. package/src/cockpit/layout.js +224 -0
  28. package/src/cockpit/logs-reader.js +182 -0
  29. package/src/cockpit/menu.js +204 -0
  30. package/src/cockpit/pane-actions.js +597 -0
  31. package/src/cockpit/pane-menu.js +387 -0
  32. package/src/cockpit/projects-finder.js +178 -0
  33. package/src/cockpit/render.js +215 -0
  34. package/src/cockpit/settings-render.js +128 -0
  35. package/src/cockpit/settings.js +124 -0
  36. package/src/cockpit/shortcuts.js +24 -0
  37. package/src/cockpit/sidebar.js +311 -0
  38. package/src/cockpit/state.js +72 -0
  39. package/src/cockpit/theme.js +128 -0
  40. package/src/cockpit/welcome.js +266 -0
  41. package/src/context.js +78 -35
  42. package/src/doctor/index.js +4 -3
  43. package/src/finish/index.js +39 -2
  44. package/src/git/index.js +65 -0
  45. package/src/kitty/command.js +101 -0
  46. package/src/kitty/runtime.js +250 -0
  47. package/src/output/index.js +1 -1
  48. package/src/pr-review.js +241 -0
  49. package/src/scaffold/index.js +19 -0
  50. package/src/submodule/index.js +288 -0
  51. package/src/terminal/index.js +120 -0
  52. package/src/terminal/kitty.js +622 -0
  53. package/src/terminal/tmux.js +126 -0
  54. package/src/tmux/command.js +27 -0
  55. package/src/tmux/session.js +89 -0
  56. package/templates/AGENTS.multiagent-safety.md +421 -37
  57. package/templates/codex/skills/gitguardex/SKILL.md +2 -0
  58. package/templates/githooks/pre-commit +22 -1
  59. package/templates/github/workflows/README.md +87 -0
  60. package/templates/github/workflows/ci-full.yml +55 -0
  61. package/templates/github/workflows/ci.yml +56 -0
  62. package/templates/github/workflows/cr.yml +20 -1
  63. package/templates/scripts/agent-branch-finish.sh +545 -27
  64. package/templates/scripts/agent-branch-start.sh +193 -21
  65. package/templates/scripts/agent-preflight.sh +89 -0
  66. package/templates/scripts/agent-worktree-prune.sh +96 -5
  67. package/templates/scripts/codex-agent.sh +41 -6
  68. package/templates/scripts/openspec/init-plan-workspace.sh +43 -0
  69. package/templates/scripts/review-bot-watch.sh +31 -2
  70. package/templates/scripts/agent-session-state.js +0 -171
  71. package/templates/scripts/install-vscode-active-agents-extension.js +0 -135
  72. package/templates/vscode/guardex-active-agents/README.md +0 -34
  73. package/templates/vscode/guardex-active-agents/extension.js +0 -3782
  74. package/templates/vscode/guardex-active-agents/fileicons/gitguardex-fileicons.json +0 -54
  75. package/templates/vscode/guardex-active-agents/fileicons/icons/agent.svg +0 -5
  76. package/templates/vscode/guardex-active-agents/fileicons/icons/branch.svg +0 -7
  77. package/templates/vscode/guardex-active-agents/fileicons/icons/config.svg +0 -4
  78. package/templates/vscode/guardex-active-agents/fileicons/icons/hook.svg +0 -4
  79. package/templates/vscode/guardex-active-agents/fileicons/icons/openspec.svg +0 -5
  80. package/templates/vscode/guardex-active-agents/fileicons/icons/plan.svg +0 -4
  81. package/templates/vscode/guardex-active-agents/fileicons/icons/spec.svg +0 -5
  82. package/templates/vscode/guardex-active-agents/icon.png +0 -0
  83. package/templates/vscode/guardex-active-agents/media/active-agents-hivemind.svg +0 -14
  84. package/templates/vscode/guardex-active-agents/package.json +0 -169
  85. package/templates/vscode/guardex-active-agents/session-schema.js +0 -1348
package/src/cli/main.js CHANGED
@@ -5,7 +5,17 @@ const sandboxModule = require('../sandbox');
5
5
  const toolchainModule = require('../toolchain');
6
6
  const finishCommands = require('../finish');
7
7
  const doctorModule = require('../doctor');
8
+ const submoduleModule = require('../submodule');
9
+ const agentInspect = require('../agents/inspect');
10
+ const agentStatus = require('../agents/status');
11
+ const agentCleanupSessions = require('../agents/cleanup-sessions');
12
+ const { finishAgentSession } = require('../agents/finish');
8
13
  const sessionSeverityReport = require('../report/session-severity');
14
+ const budgetModule = require('../budget');
15
+ const ciInitModule = require('../ci-init');
16
+ const cockpitModule = require('../cockpit');
17
+ const agentsStart = require('../agents/start');
18
+ const prReviewModule = require('../pr-review');
9
19
  const {
10
20
  fs,
11
21
  path,
@@ -83,6 +93,7 @@ const {
83
93
  readConfiguredProtectedBranches,
84
94
  readProtectedBranches,
85
95
  ensureSetupProtectedBranches,
96
+ ensureSubmoduleAutoSync,
86
97
  writeProtectedBranches,
87
98
  readGitConfig,
88
99
  resolveBaseBranch,
@@ -127,6 +138,7 @@ const {
127
138
  parseDoctorArgs,
128
139
  parseTargetFlag,
129
140
  parseReviewArgs,
141
+ parsePrReviewArgs,
130
142
  parseAgentsArgs,
131
143
  parseReportArgs,
132
144
  parseSyncArgs,
@@ -177,6 +189,7 @@ const {
177
189
  installUserLevelAsset,
178
190
  removeLegacyManagedRepoFile,
179
191
  ensureAgentsSnippet,
192
+ ensureClaudeAgentsLink,
180
193
  ensureManagedGitignore,
181
194
  buildRepoVscodeSettings,
182
195
  ensureRepoVscodeSettings,
@@ -377,6 +390,9 @@ function runSetupBootstrapInternal(options) {
377
390
  installPayload.operations.push(
378
391
  ensureSetupProtectedBranches(installPayload.repoRoot, Boolean(options.dryRun)),
379
392
  );
393
+ installPayload.operations.push(
394
+ ...ensureSubmoduleAutoSync(installPayload.repoRoot, Boolean(options.dryRun)),
395
+ );
380
396
 
381
397
  let parentWorkspace = null;
382
398
  if (options.parentWorkspaceView) {
@@ -880,6 +896,17 @@ function isInteractiveTerminal() {
880
896
  return Boolean(process.stdin.isTTY && process.stdout.isTTY);
881
897
  }
882
898
 
899
+ function legacyDefaultStatusEnabled() {
900
+ return envFlagIsTruthy(process.env.GUARDEX_LEGACY_STATUS);
901
+ }
902
+
903
+ function defaultCockpitDisabled() {
904
+ const raw = process.env.GUARDEX_DEFAULT_COCKPIT;
905
+ if (raw == null) return false;
906
+ const normalized = String(raw).trim().toLowerCase();
907
+ return ['0', 'false', 'no', 'off'].includes(normalized);
908
+ }
909
+
883
910
  function parseAutoApproval(name) {
884
911
  const raw = process.env[name];
885
912
  if (raw == null) return null;
@@ -1241,84 +1268,6 @@ function promptYesNoStrict(question) {
1241
1268
  }
1242
1269
  }
1243
1270
 
1244
- const VSCODE_EXTENSION_ID = 'Recodee.gitguardex-active-agents';
1245
- const VSCODE_EXTENSION_DISPLAY_NAME = 'GitGuardex Active Agents';
1246
-
1247
- function maybePromptInstallVscodeExtension(options) {
1248
- if (options.dryRun) {
1249
- console.log(
1250
- `[${TOOL_NAME}] (dry-run) Would offer to install VS Code extension '${VSCODE_EXTENSION_ID}'.`,
1251
- );
1252
- return;
1253
- }
1254
-
1255
- if (envFlagIsTruthy(process.env.GUARDEX_SKIP_VSCODE_EXT_PROMPT)) {
1256
- return;
1257
- }
1258
-
1259
- const codeProbe = cp.spawnSync('code', ['--version'], { stdio: 'ignore' });
1260
- if (codeProbe.error || codeProbe.status !== 0) {
1261
- return;
1262
- }
1263
-
1264
- const listProbe = cp.spawnSync('code', ['--list-extensions'], {
1265
- encoding: 'utf8',
1266
- stdio: ['ignore', 'pipe', 'ignore'],
1267
- });
1268
- if (!listProbe.error && listProbe.status === 0) {
1269
- const alreadyInstalled = String(listProbe.stdout || '')
1270
- .split('\n')
1271
- .some((line) => line.trim().toLowerCase() === VSCODE_EXTENSION_ID.toLowerCase());
1272
- if (alreadyInstalled) {
1273
- console.log(
1274
- `[${TOOL_NAME}] ✅ VS Code extension '${VSCODE_EXTENSION_ID}' already installed.`,
1275
- );
1276
- return;
1277
- }
1278
- }
1279
-
1280
- let approved;
1281
- if (options.yesGlobalInstall) {
1282
- approved = true;
1283
- } else if (options.noGlobalInstall) {
1284
- approved = false;
1285
- } else if (!isInteractiveTerminal()) {
1286
- console.log(
1287
- `[${TOOL_NAME}] Optional VS Code extension '${VSCODE_EXTENSION_ID}' ` +
1288
- `(${VSCODE_EXTENSION_DISPLAY_NAME}) not installed. ` +
1289
- `Install later: code --install-extension ${VSCODE_EXTENSION_ID}`,
1290
- );
1291
- return;
1292
- } else {
1293
- approved = promptYesNoStrict(
1294
- `Install VS Code extension '${VSCODE_EXTENSION_ID}' (${VSCODE_EXTENSION_DISPLAY_NAME}) now?`,
1295
- );
1296
- }
1297
-
1298
- if (!approved) {
1299
- console.log(
1300
- `[${TOOL_NAME}] ⚠️ VS Code extension skipped. ` +
1301
- `Set GUARDEX_SKIP_VSCODE_EXT_PROMPT=1 to silence or run 'code --install-extension ${VSCODE_EXTENSION_ID}' later.`,
1302
- );
1303
- return;
1304
- }
1305
-
1306
- const install = cp.spawnSync('code', ['--install-extension', VSCODE_EXTENSION_ID], {
1307
- stdio: 'inherit',
1308
- });
1309
- if (install.error || install.status !== 0) {
1310
- console.log(
1311
- `[${TOOL_NAME}] ⚠️ VS Code extension install failed. ` +
1312
- `Retry manually: code --install-extension ${VSCODE_EXTENSION_ID}`,
1313
- );
1314
- return;
1315
- }
1316
- console.log(
1317
- `[${TOOL_NAME}] ✅ VS Code extension '${VSCODE_EXTENSION_ID}' installed. ` +
1318
- `Reload the VS Code window to activate it.`,
1319
- );
1320
- }
1321
-
1322
1271
  function resolveGlobalInstallApproval(options) {
1323
1272
  if (options.yesGlobalInstall && options.noGlobalInstall) {
1324
1273
  throw new Error('Cannot use both --yes-global-install and --no-global-install');
@@ -1557,6 +1506,7 @@ function runInstallInternal(options) {
1557
1506
 
1558
1507
  if (!options.skipAgents) {
1559
1508
  operations.push(ensureAgentsSnippet(repoRoot, Boolean(options.dryRun), { force: Boolean(options.force) }));
1509
+ operations.push(ensureClaudeAgentsLink(repoRoot, Boolean(options.dryRun)));
1560
1510
  }
1561
1511
 
1562
1512
  const hookResult = configureHooks(repoRoot, Boolean(options.dryRun));
@@ -1640,6 +1590,7 @@ function runFixInternal(options) {
1640
1590
 
1641
1591
  if (!options.skipAgents) {
1642
1592
  operations.push(ensureAgentsSnippet(repoRoot, Boolean(options.dryRun), { force: Boolean(options.force) }));
1593
+ operations.push(ensureClaudeAgentsLink(repoRoot, Boolean(options.dryRun)));
1643
1594
  }
1644
1595
 
1645
1596
  const hookResult = configureHooks(repoRoot, Boolean(options.dryRun));
@@ -1947,6 +1898,7 @@ function collectServicesSnapshot() {
1947
1898
  ...requiredSystemTools.map((tool) => ({
1948
1899
  name: tool.name,
1949
1900
  displayName: tool.displayName || tool.name,
1901
+ command: tool.command,
1950
1902
  status: tool.status,
1951
1903
  })),
1952
1904
  ];
@@ -2016,9 +1968,7 @@ function status(rawArgs) {
2016
1968
  target: process.cwd(),
2017
1969
  json: false,
2018
1970
  });
2019
- const forceCompact = envFlagIsTruthy(process.env.GUARDEX_COMPACT_STATUS);
2020
1971
  const forceExpand = envFlagIsTruthy(process.env.GUARDEX_VERBOSE_STATUS) || verboseFlag;
2021
- const interactive = Boolean(process.stdout.isTTY);
2022
1972
  const invokedBasename = getInvokedCliName();
2023
1973
 
2024
1974
  let snapshot = collectServicesSnapshot();
@@ -2071,17 +2021,24 @@ function status(rawArgs) {
2071
2021
  return payload;
2072
2022
  }
2073
2023
 
2074
- const allServicesActive = toolchain.ok && services.every((service) => service.status === 'active');
2075
- const compact = !forceExpand && (forceCompact || (interactive && allServicesActive));
2024
+ const compact = !forceExpand;
2025
+ const activeServiceCount = services.filter((service) => service.status === 'active').length;
2026
+ const inactiveServiceCount = services.length - activeServiceCount;
2076
2027
 
2077
2028
  console.log(`[${TOOL_NAME}] CLI: ${payload.cli.runtime}`);
2078
2029
  if (!toolchain.ok) {
2079
- console.log(`[${TOOL_NAME}] ⚠️ Could not detect global services: ${toolchain.error}`);
2030
+ const detectionError = compact
2031
+ ? String(toolchain.error || '').split(/\r?\n/).find(Boolean) || 'unknown error'
2032
+ : toolchain.error;
2033
+ console.log(`[${TOOL_NAME}] ⚠️ Could not detect global services: ${detectionError}`);
2080
2034
  }
2081
2035
 
2082
2036
  if (compact) {
2037
+ const serviceSummary = inactiveServiceCount === 0
2038
+ ? `${activeServiceCount}/${services.length} ${statusDot('active')} active`
2039
+ : `${activeServiceCount}/${services.length} ${statusDot('degraded')} active (${inactiveServiceCount} inactive)`;
2083
2040
  console.log(
2084
- `[${TOOL_NAME}] Global services: ${services.length}/${services.length} ${statusDot('active')} active`,
2041
+ `[${TOOL_NAME}] Global services: ${serviceSummary}`,
2085
2042
  );
2086
2043
  } else {
2087
2044
  console.log(`[${TOOL_NAME}] Global services:`);
@@ -2094,19 +2051,25 @@ function status(rawArgs) {
2094
2051
  .filter((service) => service.status !== 'active')
2095
2052
  .map((service) => service.displayName || service.name);
2096
2053
  if (inactiveOptionalCompanions.length > 0) {
2097
- console.log(
2098
- `[${TOOL_NAME}] Optional companion tools inactive: ${inactiveOptionalCompanions.join(', ')}`,
2099
- );
2100
- for (const warning of toolchainModule.describeMissingGlobalDependencyWarnings(
2101
- npmServices
2102
- .filter((service) => service.status === 'inactive')
2103
- .map((service) => service.packageName),
2104
- )) {
2105
- console.log(`[${TOOL_NAME}] ${warning}`);
2054
+ if (compact) {
2055
+ console.log(
2056
+ `[${TOOL_NAME}] Optional companion tools inactive: ${inactiveOptionalCompanions.length} (run '${SHORT_TOOL_NAME} setup')`,
2057
+ );
2058
+ } else {
2059
+ console.log(
2060
+ `[${TOOL_NAME}] Optional companion tools inactive: ${inactiveOptionalCompanions.join(', ')}`,
2061
+ );
2062
+ for (const warning of toolchainModule.describeMissingGlobalDependencyWarnings(
2063
+ npmServices
2064
+ .filter((service) => service.status === 'inactive')
2065
+ .map((service) => service.packageName),
2066
+ )) {
2067
+ console.log(`[${TOOL_NAME}] ${warning}`);
2068
+ }
2069
+ console.log(
2070
+ `[${TOOL_NAME}] Run '${SHORT_TOOL_NAME} setup' to install missing companions with an explicit Y/N prompt.`,
2071
+ );
2106
2072
  }
2107
- console.log(
2108
- `[${TOOL_NAME}] Run '${SHORT_TOOL_NAME} setup' to install missing companions with an explicit Y/N prompt.`,
2109
- );
2110
2073
  }
2111
2074
  const missingSystemTools = requiredSystemTools.filter((tool) => tool.status !== 'active');
2112
2075
  if (missingSystemTools.length > 0) {
@@ -2114,9 +2077,11 @@ function status(rawArgs) {
2114
2077
  .map((tool) => tool.displayName || tool.name)
2115
2078
  .join(', ');
2116
2079
  console.log(`[${TOOL_NAME}] ⚠️ Missing required system tool(s): ${tools}`);
2117
- for (const tool of missingSystemTools) {
2118
- const reasonText = tool.reason ? ` (${tool.reason})` : '';
2119
- console.log(` - install ${tool.name}: ${tool.installHint}${reasonText}`);
2080
+ if (!compact) {
2081
+ for (const tool of missingSystemTools) {
2082
+ const reasonText = tool.reason ? ` (${tool.reason})` : '';
2083
+ console.log(` - install ${tool.name}: ${tool.installHint}${reasonText}`);
2084
+ }
2120
2085
  }
2121
2086
  }
2122
2087
 
@@ -2387,6 +2352,10 @@ function doctor(rawArgs) {
2387
2352
  target: topRepoRoot,
2388
2353
  };
2389
2354
 
2355
+ if (!singleRepoOptions.json) {
2356
+ printRequiredSystemToolStatus();
2357
+ }
2358
+
2390
2359
  const blocked = protectedBaseWriteBlock(singleRepoOptions, { requireBootstrap: false });
2391
2360
  if (blocked) {
2392
2361
  doctorModule.runDoctorInSandbox(singleRepoOptions, blocked, {
@@ -2501,6 +2470,13 @@ function review(rawArgs) {
2501
2470
  process.exitCode = typeof result.status === 'number' ? result.status : 1;
2502
2471
  }
2503
2472
 
2473
+ function prReview(rawArgs) {
2474
+ const options = parsePrReviewArgs(rawArgs);
2475
+ const result = prReviewModule.runPrReview(options);
2476
+ prReviewModule.printPrReviewResult(result);
2477
+ process.exitCode = 0;
2478
+ }
2479
+
2504
2480
  function agentsStatePathForRepo(repoRoot) {
2505
2481
  return path.join(repoRoot, AGENTS_BOTS_STATE_RELATIVE);
2506
2482
  }
@@ -2642,10 +2618,58 @@ function spawnDetachedAgentProcess({ command, args, cwd, logPath }) {
2642
2618
 
2643
2619
  function agents(rawArgs) {
2644
2620
  const options = parseAgentsArgs(rawArgs);
2621
+ if (['files', 'diff', 'locks'].includes(options.subcommand)) {
2622
+ process.stdout.write(agentInspect.runInspectCommand(options));
2623
+ process.exitCode = 0;
2624
+ return;
2625
+ }
2626
+
2645
2627
  const repoRoot = resolveRepoRoot(options.target);
2646
2628
  const statePath = agentsStatePathForRepo(repoRoot);
2647
2629
 
2630
+ if (options.subcommand === 'finish') {
2631
+ const result = finishAgentSession(repoRoot, options);
2632
+ if (options.json) {
2633
+ process.stdout.write(`${JSON.stringify(result.evidence, null, 2)}\n`);
2634
+ }
2635
+ process.exitCode = 0;
2636
+ return;
2637
+ }
2638
+
2639
+ if (options.subcommand === 'cleanup-sessions') {
2640
+ process.stdout.write(agentCleanupSessions.runCleanupSessionsCommand(repoRoot, options));
2641
+ process.exitCode = 0;
2642
+ return;
2643
+ }
2644
+
2648
2645
  if (options.subcommand === 'start') {
2646
+ if (agentsStart.shouldUseInteractivePanel(options, process.stdin, process.stdout)) {
2647
+ agentsStart.startInteractiveAgentPanel(repoRoot, options, {
2648
+ onDone(result) {
2649
+ process.exitCode = result.status;
2650
+ },
2651
+ });
2652
+ return;
2653
+ }
2654
+ if (options.dryRun) {
2655
+ const output = agentsStart.dryRunStart(options, repoRoot);
2656
+ process.stdout.write(output.endsWith('\n') ? output : `${output}\n`);
2657
+ process.exitCode = 0;
2658
+ return;
2659
+ }
2660
+ if (options.panel && !options.task) {
2661
+ process.stderr.write('[gitguardex] gx agents start --panel requires an interactive terminal when no task is provided.\n');
2662
+ process.exitCode = 1;
2663
+ return;
2664
+ }
2665
+ if (options.task) {
2666
+ const result = agentsStart.startAgentLane(repoRoot, options);
2667
+ if (result.stdout) process.stdout.write(result.stdout);
2668
+ if (result.stderr) process.stderr.write(result.stderr);
2669
+ process.exitCode = result.status;
2670
+ return;
2671
+ }
2672
+
2649
2673
  const existingState = readAgentsState(repoRoot);
2650
2674
  const existingReviewPid = Number.parseInt(String(existingState?.review?.pid || ''), 10);
2651
2675
  const existingCleanupPid = Number.parseInt(String(existingState?.cleanup?.pid || ''), 10);
@@ -2786,18 +2810,7 @@ function agents(rawArgs) {
2786
2810
  return;
2787
2811
  }
2788
2812
 
2789
- const existingState = readAgentsState(repoRoot);
2790
- if (!existingState) {
2791
- console.log(`[${TOOL_NAME}] Repo agents status: inactive (${repoRoot})`);
2792
- process.exitCode = 0;
2793
- return;
2794
- }
2795
-
2796
- const reviewPid = Number.parseInt(String(existingState?.review?.pid || ''), 10);
2797
- const cleanupPid = Number.parseInt(String(existingState?.cleanup?.pid || ''), 10);
2798
- console.log(
2799
- `[${TOOL_NAME}] Repo agents status: review=${processAlive(reviewPid) ? 'running' : 'stopped'}(pid=${reviewPid || 0}), cleanup=${processAlive(cleanupPid) ? 'running' : 'stopped'}(pid=${cleanupPid || 0})`,
2800
- );
2813
+ process.stdout.write(agentStatus.runStatusCommand(repoRoot, options));
2801
2814
  process.exitCode = 0;
2802
2815
  }
2803
2816
 
@@ -2955,20 +2968,7 @@ function setup(rawArgs) {
2955
2968
  }
2956
2969
  }
2957
2970
 
2958
- maybePromptInstallVscodeExtension(options);
2959
-
2960
- const requiredSystemTools = toolchainModule.detectRequiredSystemTools();
2961
- const missingSystemTools = requiredSystemTools.filter((tool) => tool.status !== 'active');
2962
- if (missingSystemTools.length === 0) {
2963
- console.log(`[${TOOL_NAME}] ✅ Required system tools available (${requiredSystemTools.map((tool) => tool.name).join(', ')}).`);
2964
- } else {
2965
- const names = missingSystemTools.map((tool) => tool.name).join(', ');
2966
- console.log(`[${TOOL_NAME}] ⚠️ Missing required system tool(s): ${names}`);
2967
- for (const tool of missingSystemTools) {
2968
- const reasonText = tool.reason ? ` (${tool.reason})` : '';
2969
- console.log(`[${TOOL_NAME}] Install ${tool.name}: ${tool.installHint}${reasonText}`);
2970
- }
2971
- }
2971
+ printRequiredSystemToolStatus();
2972
2972
 
2973
2973
  const topRepoRoot = resolveRepoRoot(options.target);
2974
2974
  const discoveredRepos = options.recursive
@@ -3082,6 +3082,22 @@ function setup(rawArgs) {
3082
3082
  }
3083
3083
  }
3084
3084
 
3085
+ function printRequiredSystemToolStatus() {
3086
+ const requiredSystemTools = toolchainModule.detectRequiredSystemTools();
3087
+ const missingSystemTools = requiredSystemTools.filter((tool) => tool.status !== 'active');
3088
+ if (missingSystemTools.length === 0) {
3089
+ console.log(`[${TOOL_NAME}] ✅ Required system tools available (${requiredSystemTools.map((tool) => tool.name).join(', ')}).`);
3090
+ return;
3091
+ }
3092
+
3093
+ const names = missingSystemTools.map((tool) => tool.name).join(', ');
3094
+ console.log(`[${TOOL_NAME}] ⚠️ Missing required system tool(s): ${names}`);
3095
+ for (const tool of missingSystemTools) {
3096
+ const reasonText = tool.reason ? ` (${tool.reason})` : '';
3097
+ console.log(`[${TOOL_NAME}] Install ${tool.name}: ${tool.installHint}${reasonText}`);
3098
+ }
3099
+ }
3100
+
3085
3101
  function ensureMainBranch(repoRoot) {
3086
3102
  const branchResult = gitRun(repoRoot, ['rev-parse', '--abbrev-ref', 'HEAD'], { allowFailure: true });
3087
3103
  if (branchResult.status !== 0) {
@@ -3249,6 +3265,25 @@ function renderGeneratedReleaseNotes(entries, currentTag, previousTag) {
3249
3265
  return `GitGuardex ${currentTag}\n\n${intro}\n\n${sections}`;
3250
3266
  }
3251
3267
 
3268
+ function describeGhAuthFailure(ghBin, authStatus) {
3269
+ if (authStatus.error) {
3270
+ return `unable to run '${ghBin} auth status': ${authStatus.error.message}`;
3271
+ }
3272
+
3273
+ const authDetails = (authStatus.stderr || authStatus.stdout || '').trim();
3274
+ const apiProbe = run(ghBin, ['api', 'user', '--jq', '.login'], { timeout: 20_000 });
3275
+ if (apiProbe.status === 0) {
3276
+ return '';
3277
+ }
3278
+
3279
+ const apiDetails = (apiProbe.stderr || apiProbe.stdout || apiProbe.error?.message || '').trim();
3280
+ if (/error connecting to api\.github\.com|could not resolve host|failed to connect|network is unreachable|connection timed out|temporary failure in name resolution/i.test(apiDetails)) {
3281
+ return `GitHub API is unreachable, so '${ghBin} auth status' cannot validate the stored token. This is a network or sandbox connectivity problem, not proof that the token is invalid.${apiDetails ? `\n${apiDetails}` : ''}`;
3282
+ }
3283
+
3284
+ return `'${ghBin}' auth is unavailable.${authDetails ? `\n${authDetails}` : ''}`;
3285
+ }
3286
+
3252
3287
  function buildReleaseNotesFromReadme(repoRoot, currentTag, previousTag) {
3253
3288
  const readme = readRepoReadme(repoRoot);
3254
3289
  const entries = parseReadmeReleaseEntries(readme);
@@ -3276,12 +3311,11 @@ function release(rawArgs) {
3276
3311
  }
3277
3312
 
3278
3313
  const ghAuthStatus = run(GH_BIN, ['auth', 'status'], { timeout: 20_000 });
3279
- if (ghAuthStatus.error) {
3280
- throw new Error(`Release blocked: unable to run '${GH_BIN} auth status': ${ghAuthStatus.error.message}`);
3281
- }
3282
3314
  if (ghAuthStatus.status !== 0) {
3283
- const details = (ghAuthStatus.stderr || ghAuthStatus.stdout || '').trim();
3284
- throw new Error(`Release blocked: '${GH_BIN}' auth is unavailable.${details ? `\n${details}` : ''}`);
3315
+ const ghAuthFailure = describeGhAuthFailure(GH_BIN, ghAuthStatus);
3316
+ if (ghAuthFailure) {
3317
+ throw new Error(`Release blocked: ${ghAuthFailure}`);
3318
+ }
3285
3319
  }
3286
3320
 
3287
3321
  const releasePackageJson = readReleaseRepoPackageJson(repoRoot);
@@ -3416,6 +3450,7 @@ function prompt(rawArgs) {
3416
3450
  }
3417
3451
 
3418
3452
  function branch(rawArgs) {
3453
+ const activeCwd = process.cwd();
3419
3454
  const [subcommand, ...rest] = rawArgs;
3420
3455
  if (subcommand === 'start') {
3421
3456
  const { target, passthrough } = extractTargetedArgs(rest);
@@ -3424,7 +3459,10 @@ function branch(rawArgs) {
3424
3459
  }
3425
3460
  if (subcommand === 'finish') {
3426
3461
  const { target, passthrough } = extractTargetedArgs(rest);
3427
- invokePackageAsset('branchFinish', passthrough, { cwd: resolveRepoRoot(target) });
3462
+ invokePackageAsset('branchFinish', passthrough, {
3463
+ cwd: resolveRepoRoot(target),
3464
+ env: { GUARDEX_FINISH_ACTIVE_CWD: activeCwd },
3465
+ });
3428
3466
  return;
3429
3467
  }
3430
3468
  if (subcommand === 'merge') return merge(rest);
@@ -3501,10 +3539,14 @@ function locks(rawArgs) {
3501
3539
  }
3502
3540
 
3503
3541
  function worktree(rawArgs) {
3542
+ const activeCwd = process.cwd();
3504
3543
  const [subcommand, ...rest] = rawArgs;
3505
3544
  if (subcommand === 'prune') {
3506
3545
  const { target, passthrough } = extractTargetedArgs(rest);
3507
- invokePackageAsset('worktreePrune', passthrough, { cwd: resolveRepoRoot(target) });
3546
+ invokePackageAsset('worktreePrune', passthrough, {
3547
+ cwd: resolveRepoRoot(target),
3548
+ env: { GUARDEX_PRUNE_ACTIVE_CWD: process.env.GUARDEX_PRUNE_ACTIVE_CWD || activeCwd },
3549
+ });
3508
3550
  return;
3509
3551
  }
3510
3552
  throw new Error(`Usage: ${SHORT_TOOL_NAME} worktree prune [cleanup-options]`);
@@ -3616,6 +3658,81 @@ function sync(rawArgs) {
3616
3658
  return finishCommands.sync(rawArgs);
3617
3659
  }
3618
3660
 
3661
+ function submodule(rawArgs) {
3662
+ const parsed = parseTargetFlag(rawArgs || [], process.cwd());
3663
+ const [subcommand, ...rest] = parsed.args;
3664
+
3665
+ if (!subcommand || subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
3666
+ console.log(
3667
+ `${TOOL_NAME} submodule commands:\n` +
3668
+ ` ${TOOL_NAME} submodule advance [<path>] [--push] [--dry-run] [--branch <ref>] [--no-commit] [--target <path>]\n\n` +
3669
+ ` advance — for each submodule listed in .gitmodules, fetch the tracked branch's\n` +
3670
+ ` remote tip, advance the parent pointer, and (when on a non-protected\n` +
3671
+ ` branch) commit the bump. Use --push to publish in one step.`,
3672
+ );
3673
+ return;
3674
+ }
3675
+
3676
+ if (subcommand !== 'advance') {
3677
+ throw new Error(`Unknown submodule subcommand: ${subcommand}. Try '${SHORT_TOOL_NAME} submodule help'.`);
3678
+ }
3679
+
3680
+ let push = false;
3681
+ let dryRun = false;
3682
+ let commit = true;
3683
+ let branchOverride = '';
3684
+ let pathArg = '';
3685
+ for (let i = 0; i < rest.length; i += 1) {
3686
+ const arg = rest[i];
3687
+ if (arg === '--push') {
3688
+ push = true;
3689
+ continue;
3690
+ }
3691
+ if (arg === '--dry-run' || arg === '-n') {
3692
+ dryRun = true;
3693
+ continue;
3694
+ }
3695
+ if (arg === '--no-commit') {
3696
+ commit = false;
3697
+ continue;
3698
+ }
3699
+ if (arg === '--branch' || arg === '-b') {
3700
+ branchOverride = rest[i + 1] || '';
3701
+ i += 1;
3702
+ continue;
3703
+ }
3704
+ if (arg.startsWith('--branch=')) {
3705
+ branchOverride = arg.slice('--branch='.length);
3706
+ continue;
3707
+ }
3708
+ if (arg.startsWith('--')) {
3709
+ throw new Error(`Unknown option for '${SHORT_TOOL_NAME} submodule advance': ${arg}`);
3710
+ }
3711
+ if (pathArg) {
3712
+ throw new Error(`'${SHORT_TOOL_NAME} submodule advance' accepts at most one submodule path (got '${pathArg}' and '${arg}')`);
3713
+ }
3714
+ pathArg = arg;
3715
+ }
3716
+
3717
+ const result = submoduleModule.advance({
3718
+ target: parsed.target,
3719
+ path: pathArg,
3720
+ push,
3721
+ dryRun,
3722
+ commit,
3723
+ branch: branchOverride,
3724
+ });
3725
+ submoduleModule.printAdvanceResult(result);
3726
+ }
3727
+
3728
+ function cockpit(rawArgs) {
3729
+ cockpitModule.openCockpit(rawArgs, {
3730
+ resolveRepoRoot,
3731
+ toolName: TOOL_NAME,
3732
+ });
3733
+ process.exitCode = 0;
3734
+ }
3735
+
3619
3736
  function protect(rawArgs) {
3620
3737
  const parsed = parseTargetFlag(rawArgs, process.cwd());
3621
3738
  const [subcommand, ...rest] = parsed.args;
@@ -3698,6 +3815,14 @@ async function main() {
3698
3815
  const args = process.argv.slice(2);
3699
3816
 
3700
3817
  if (args.length === 0) {
3818
+ if (isInteractiveTerminal() && !legacyDefaultStatusEnabled() && !defaultCockpitDisabled()) {
3819
+ cockpitModule.openDefaultCockpit({
3820
+ resolveRepoRoot,
3821
+ toolName: TOOL_NAME,
3822
+ });
3823
+ process.exitCode = 0;
3824
+ return;
3825
+ }
3701
3826
  toolchainModule.maybeSelfUpdateBeforeStatus();
3702
3827
  toolchainModule.maybeOpenSpecUpdateBeforeStatus();
3703
3828
  const statusPayload = status([]);
@@ -3747,6 +3872,7 @@ async function main() {
3747
3872
  }
3748
3873
 
3749
3874
  if (command === 'prompt') return prompt(rest);
3875
+ if (command === 'pr-review') return prReview(rest);
3750
3876
  if (command === 'doctor') return doctor(rest);
3751
3877
  if (command === 'branch') return branch(rest);
3752
3878
  if (command === 'pivot') return pivot(rest);
@@ -3758,13 +3884,17 @@ async function main() {
3758
3884
  if (command === 'install-agent-skills') return installAgentSkills(rest);
3759
3885
  if (command === 'internal') return internal(rest);
3760
3886
  if (command === 'agents') return agents(rest);
3887
+ if (command === 'cockpit') return cockpit(rest);
3761
3888
  if (command === 'merge') return merge(rest);
3762
3889
  if (command === 'finish') return finish(rest);
3763
3890
  if (command === 'report') return report(rest);
3764
3891
  if (command === 'protect') return protect(rest);
3765
3892
  if (command === 'sync') return sync(rest);
3893
+ if (command === 'submodule') return submodule(rest);
3766
3894
  if (command === 'cleanup') return cleanup(rest);
3767
3895
  if (command === 'release') return release(rest);
3896
+ if (command === 'budget') return budgetModule.runBudgetCommand(rest);
3897
+ if (command === 'ci-init') return ciInitModule.runCiInitCommand(rest);
3768
3898
 
3769
3899
  const suggestion = maybeSuggestCommand(command);
3770
3900
  if (suggestion) {
@@ -0,0 +1,3 @@
1
+ 'use strict';
2
+
3
+ module.exports = require('./pane-actions');
@@ -0,0 +1,80 @@
1
+ 'use strict';
2
+
3
+ function parseAgentBranchStartMetadata(output) {
4
+ const outputText = String(output || '');
5
+ const branchMatch = outputText.match(/^\[agent-branch-start\] (?:Created branch|Reusing existing branch): (.+)$/m);
6
+ const worktreeMatch = outputText.match(/^\[agent-branch-start\] Worktree: (.+)$/m);
7
+ return {
8
+ branch: branchMatch ? branchMatch[1].trim() : undefined,
9
+ worktreePath: worktreeMatch ? worktreeMatch[1].trim() : undefined,
10
+ };
11
+ }
12
+
13
+ function firstString(...values) {
14
+ for (const value of values) {
15
+ if (typeof value === 'string' && value.length > 0) return value;
16
+ }
17
+ return undefined;
18
+ }
19
+
20
+ function normalizeStartResult(result) {
21
+ const payload = result && typeof result === 'object' ? result : {};
22
+ const metadata = parseAgentBranchStartMetadata(payload.stdout);
23
+ const ok = Object.prototype.hasOwnProperty.call(payload, 'ok')
24
+ ? Boolean(payload.ok)
25
+ : typeof payload.status === 'number'
26
+ ? payload.status === 0
27
+ : true;
28
+
29
+ return {
30
+ ok,
31
+ sessionId: firstString(payload.sessionId, payload.session?.id, payload.id),
32
+ branch: firstString(payload.branch, payload.lane?.branch, metadata.branch),
33
+ worktreePath: firstString(payload.worktreePath, payload.worktree?.path, payload.path, metadata.worktreePath),
34
+ message: firstString(
35
+ payload.message,
36
+ ok ? payload.stdout : payload.stderr,
37
+ ok ? 'Started agent lane.' : 'Failed to start agent lane.',
38
+ ),
39
+ };
40
+ }
41
+
42
+ function resolveStartImplementation(deps = {}) {
43
+ if (typeof deps.startImplementation === 'function') return deps.startImplementation;
44
+ if (typeof deps.startAgentLane === 'function') return deps.startAgentLane;
45
+ if (typeof deps.startAgent === 'function') return deps.startAgent;
46
+
47
+ const startModule = require('../agents/start');
48
+ if (typeof startModule === 'function') return startModule;
49
+ if (typeof startModule.startAgentLane === 'function') return startModule.startAgentLane;
50
+ if (typeof startModule.startAgent === 'function') return startModule.startAgent;
51
+ if (typeof startModule.start === 'function') return startModule.start;
52
+
53
+ throw new Error('gx agents start implementation is unavailable');
54
+ }
55
+
56
+ function startAgentLane(options = {}, deps = {}) {
57
+ const repoRoot = firstString(options.repoRoot, deps.repoRoot, process.cwd());
58
+ const normalizedOptions = {
59
+ task: options.task,
60
+ agent: options.agent,
61
+ base: options.base,
62
+ claims: Array.isArray(options.claims) ? options.claims : [],
63
+ metadata: options.metadata && typeof options.metadata === 'object' ? options.metadata : {},
64
+ };
65
+ const startImplementation = resolveStartImplementation(deps);
66
+ const result = startImplementation(repoRoot, normalizedOptions);
67
+
68
+ if (result && typeof result.then === 'function') {
69
+ return result.then(normalizeStartResult);
70
+ }
71
+
72
+ return normalizeStartResult(result);
73
+ }
74
+
75
+ module.exports = {
76
+ startAgentLane,
77
+ normalizeStartResult,
78
+ parseAgentBranchStartMetadata,
79
+ resolveStartImplementation,
80
+ };