@imdeadpool/guardex 7.0.41 → 7.1.0

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 (118) hide show
  1. package/README.md +94 -13
  2. package/package.json +3 -1
  3. package/skills/gitguardex/SKILL.md +13 -0
  4. package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
  5. package/skills/gx-act/SKILL.md +82 -0
  6. package/src/agents/cleanup-sessions.js +126 -0
  7. package/src/agents/finish.js +172 -0
  8. package/src/agents/inspect.js +202 -0
  9. package/src/agents/launch.js +249 -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 +146 -0
  15. package/src/agents/terminal.js +152 -0
  16. package/src/budget/index.js +344 -0
  17. package/src/ci-init/index.js +265 -0
  18. package/src/cli/args.js +357 -3
  19. package/src/cli/commands/agents.js +364 -0
  20. package/src/cli/commands/bootstrap.js +92 -0
  21. package/src/cli/commands/branch.js +127 -0
  22. package/src/cli/commands/claude.js +674 -0
  23. package/src/cli/commands/doctor.js +268 -0
  24. package/src/cli/commands/finish.js +26 -0
  25. package/src/cli/commands/mcp.js +122 -0
  26. package/src/cli/commands/misc.js +304 -0
  27. package/src/cli/commands/pr.js +439 -0
  28. package/src/cli/commands/prompt.js +92 -0
  29. package/src/cli/commands/release.js +305 -0
  30. package/src/cli/commands/report.js +244 -0
  31. package/src/cli/commands/review.js +32 -0
  32. package/src/cli/commands/setup.js +242 -0
  33. package/src/cli/commands/status.js +338 -0
  34. package/src/cli/commands/watch.js +234 -0
  35. package/src/cli/main.js +85 -3613
  36. package/src/cli/shared/repo-env.js +161 -0
  37. package/src/cli/shared/sandbox.js +417 -0
  38. package/src/cli/shared/scaffolding.js +535 -0
  39. package/src/cli/shared/toolchain-shims.js +420 -0
  40. package/src/cockpit/action-runner.js +3 -0
  41. package/src/cockpit/actions.js +80 -0
  42. package/src/cockpit/control.js +1121 -0
  43. package/src/cockpit/index.js +426 -0
  44. package/src/cockpit/kitty-layout.js +549 -0
  45. package/src/cockpit/kitty-tree.js +144 -0
  46. package/src/cockpit/logs-reader.js +182 -0
  47. package/src/cockpit/menu.js +204 -0
  48. package/src/cockpit/pane-actions.js +597 -0
  49. package/src/cockpit/pane-menu.js +387 -0
  50. package/src/cockpit/projects-finder.js +178 -0
  51. package/src/cockpit/render.js +215 -0
  52. package/src/cockpit/settings-render.js +128 -0
  53. package/src/cockpit/settings.js +124 -0
  54. package/src/cockpit/shortcuts.js +24 -0
  55. package/src/cockpit/sidebar.js +311 -0
  56. package/src/cockpit/state.js +72 -0
  57. package/src/cockpit/theme.js +128 -0
  58. package/src/cockpit/welcome.js +266 -0
  59. package/src/context.js +304 -43
  60. package/src/core/runtime.js +6 -1
  61. package/src/doctor/index.js +45 -15
  62. package/src/finish/index.js +186 -7
  63. package/src/finish/preflight.js +177 -0
  64. package/src/finish/review-gate.js +182 -0
  65. package/src/git/index.js +511 -4
  66. package/src/hooks/index.js +0 -64
  67. package/src/kitty/command.js +101 -0
  68. package/src/kitty/runtime.js +250 -0
  69. package/src/mcp/collect.js +370 -0
  70. package/src/mcp/server.js +157 -0
  71. package/src/output/index.js +68 -2
  72. package/src/pr-review.js +264 -0
  73. package/src/pr.js +381 -0
  74. package/src/sandbox/index.js +13 -2
  75. package/src/scaffold/agent-worktree-prep.js +213 -0
  76. package/src/scaffold/index.js +127 -10
  77. package/src/speckit/index.js +226 -0
  78. package/src/submodule/index.js +288 -0
  79. package/src/terminal/index.js +45 -0
  80. package/src/terminal/kitty.js +622 -0
  81. package/src/terminal/tmux.js +125 -0
  82. package/src/tmux/command.js +27 -0
  83. package/src/tmux/session.js +89 -0
  84. package/src/toolchain/index.js +20 -0
  85. package/templates/AGENTS.monorepo-apps.md +26 -0
  86. package/templates/AGENTS.multiagent-safety.md +63 -323
  87. package/templates/AGENTS.multiagent-safety.min.md +11 -0
  88. package/templates/codex/skills/gitguardex/SKILL.md +2 -0
  89. package/templates/codex/skills/gx-act/SKILL.md +82 -0
  90. package/templates/githooks/pre-commit +44 -20
  91. package/templates/github/workflows/README.md +87 -0
  92. package/templates/github/workflows/ci-full.yml +55 -0
  93. package/templates/github/workflows/ci.yml +56 -0
  94. package/templates/github/workflows/cr.yml +20 -1
  95. package/templates/scripts/agent-branch-finish.sh +519 -23
  96. package/templates/scripts/agent-branch-merge.sh +4 -1
  97. package/templates/scripts/agent-branch-start.sh +176 -24
  98. package/templates/scripts/agent-preflight.sh +115 -0
  99. package/templates/scripts/agent-worktree-prune.sh +96 -5
  100. package/templates/scripts/codex-agent.sh +41 -97
  101. package/templates/scripts/openspec/init-plan-workspace.sh +43 -0
  102. package/templates/scripts/review-bot-watch.sh +31 -2
  103. package/templates/scripts/agent-session-state.js +0 -171
  104. package/templates/scripts/install-vscode-active-agents-extension.js +0 -135
  105. package/templates/vscode/guardex-active-agents/README.md +0 -34
  106. package/templates/vscode/guardex-active-agents/extension.js +0 -3782
  107. package/templates/vscode/guardex-active-agents/fileicons/gitguardex-fileicons.json +0 -54
  108. package/templates/vscode/guardex-active-agents/fileicons/icons/agent.svg +0 -5
  109. package/templates/vscode/guardex-active-agents/fileicons/icons/branch.svg +0 -7
  110. package/templates/vscode/guardex-active-agents/fileicons/icons/config.svg +0 -4
  111. package/templates/vscode/guardex-active-agents/fileicons/icons/hook.svg +0 -4
  112. package/templates/vscode/guardex-active-agents/fileicons/icons/openspec.svg +0 -5
  113. package/templates/vscode/guardex-active-agents/fileicons/icons/plan.svg +0 -4
  114. package/templates/vscode/guardex-active-agents/fileicons/icons/spec.svg +0 -5
  115. package/templates/vscode/guardex-active-agents/icon.png +0 -0
  116. package/templates/vscode/guardex-active-agents/media/active-agents-hivemind.svg +0 -14
  117. package/templates/vscode/guardex-active-agents/package.json +0 -169
  118. package/templates/vscode/guardex-active-agents/session-schema.js +0 -1348
@@ -0,0 +1,364 @@
1
+ // `gx agents` — repo-scoped review + cleanup bots. Pure code-motion from
2
+ // src/cli/main.js.
3
+ const {
4
+ fs,
5
+ path,
6
+ cp,
7
+ TOOL_NAME,
8
+ AGENTS_BOTS_STATE_RELATIVE,
9
+ } = require('../../context');
10
+ const { resolveRepoRoot } = require('../../git');
11
+ const { run } = require('../../core/runtime');
12
+ const agentInspect = require('../../agents/inspect');
13
+ const agentStatus = require('../../agents/status');
14
+ const agentCleanupSessions = require('../../agents/cleanup-sessions');
15
+ const agentsFinishModule = require('../../agents/finish');
16
+ const agentsStart = require('../../agents/start');
17
+ const { parseAgentsArgs } = require('../args');
18
+
19
+ const finishAgentSession = (...callArgs) => agentsFinishModule.finishAgentSession(...callArgs);
20
+
21
+ function agentsStatePathForRepo(repoRoot) {
22
+ return path.join(repoRoot, AGENTS_BOTS_STATE_RELATIVE);
23
+ }
24
+
25
+ function readAgentsState(repoRoot) {
26
+ const statePath = agentsStatePathForRepo(repoRoot);
27
+ if (!fs.existsSync(statePath)) {
28
+ return null;
29
+ }
30
+ try {
31
+ return JSON.parse(fs.readFileSync(statePath, 'utf8'));
32
+ } catch (_error) {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ function writeAgentsState(repoRoot, state) {
38
+ const statePath = agentsStatePathForRepo(repoRoot);
39
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
40
+ fs.writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
41
+ }
42
+
43
+ function processAlive(pid) {
44
+ const normalizedPid = Number.parseInt(String(pid || ''), 10);
45
+ if (!Number.isInteger(normalizedPid) || normalizedPid <= 0) {
46
+ return false;
47
+ }
48
+ try {
49
+ process.kill(normalizedPid, 0);
50
+ } catch (_error) {
51
+ return false;
52
+ }
53
+
54
+ const state = readProcessState(normalizedPid);
55
+ if (state.startsWith('Z')) {
56
+ return false;
57
+ }
58
+ return true;
59
+ }
60
+
61
+ function sleepSeconds(seconds) {
62
+ const result = run('sleep', [String(seconds)]);
63
+ if (Boolean(result?.error) && typeof result?.status !== 'number') {
64
+ throw new Error(`sleep command failed for ${seconds}s`);
65
+ }
66
+ if (result.status !== 0) {
67
+ throw new Error(`sleep command failed for ${seconds}s`);
68
+ }
69
+ }
70
+
71
+ function readProcessCommand(pid) {
72
+ const result = run('ps', ['-o', 'command=', '-p', String(pid)]);
73
+ if ((Boolean(result?.error) && typeof result?.status !== 'number') || result.status !== 0) {
74
+ return '';
75
+ }
76
+ return String(result.stdout || '').trim();
77
+ }
78
+
79
+ function readProcessState(pid) {
80
+ const result = run('ps', ['-o', 'stat=', '-p', String(pid)]);
81
+ if ((Boolean(result?.error) && typeof result?.status !== 'number') || result.status !== 0) {
82
+ return '';
83
+ }
84
+ return String(result.stdout || '').trim();
85
+ }
86
+
87
+ function stopAgentProcessByPid(pid, expectedToken = '') {
88
+ const normalizedPid = Number.parseInt(String(pid || ''), 10);
89
+ if (!Number.isInteger(normalizedPid) || normalizedPid <= 0) {
90
+ return { status: 'invalid', pid: normalizedPid };
91
+ }
92
+ if (!processAlive(normalizedPid)) {
93
+ return { status: 'not-running', pid: normalizedPid };
94
+ }
95
+
96
+ if (expectedToken) {
97
+ const cmdline = readProcessCommand(normalizedPid);
98
+ if (cmdline && !cmdline.includes(expectedToken)) {
99
+ return { status: 'mismatch', pid: normalizedPid, command: cmdline };
100
+ }
101
+ }
102
+
103
+ try {
104
+ process.kill(-normalizedPid, 'SIGTERM');
105
+ } catch (_error) {
106
+ try {
107
+ process.kill(normalizedPid, 'SIGTERM');
108
+ } catch (_err) {
109
+ return { status: 'term-failed', pid: normalizedPid };
110
+ }
111
+ }
112
+
113
+ const deadline = Date.now() + 3_000;
114
+ while (Date.now() < deadline) {
115
+ if (!processAlive(normalizedPid)) {
116
+ return { status: 'stopped', pid: normalizedPid };
117
+ }
118
+ sleepSeconds(0.1);
119
+ }
120
+
121
+ try {
122
+ process.kill(-normalizedPid, 'SIGKILL');
123
+ } catch (_error) {
124
+ try {
125
+ process.kill(normalizedPid, 'SIGKILL');
126
+ } catch (_err) {
127
+ return { status: 'kill-failed', pid: normalizedPid };
128
+ }
129
+ }
130
+ sleepSeconds(0.1);
131
+
132
+ return {
133
+ status: processAlive(normalizedPid) ? 'kill-failed' : 'stopped',
134
+ pid: normalizedPid,
135
+ };
136
+ }
137
+
138
+ function spawnDetachedAgentProcess({ command, args, cwd, logPath }) {
139
+ fs.mkdirSync(path.dirname(logPath), { recursive: true });
140
+ const logHandle = fs.openSync(logPath, 'a');
141
+ fs.writeSync(
142
+ logHandle,
143
+ `[${new Date().toISOString()}] spawn: ${command} ${args.join(' ')}\n`,
144
+ );
145
+ const child = cp.spawn(command, args, {
146
+ cwd,
147
+ detached: true,
148
+ stdio: ['ignore', logHandle, logHandle],
149
+ env: process.env,
150
+ });
151
+ fs.closeSync(logHandle);
152
+ if (child.error) {
153
+ throw child.error;
154
+ }
155
+ child.unref();
156
+ const pid = Number.parseInt(String(child.pid || ''), 10);
157
+ if (!Number.isInteger(pid) || pid <= 0) {
158
+ throw new Error(`Failed to spawn detached process for ${command}`);
159
+ }
160
+ return pid;
161
+ }
162
+
163
+ function agents(rawArgs) {
164
+ const options = parseAgentsArgs(rawArgs);
165
+ if (['files', 'diff', 'locks'].includes(options.subcommand)) {
166
+ process.stdout.write(agentInspect.runInspectCommand(options));
167
+ process.exitCode = 0;
168
+ return;
169
+ }
170
+
171
+ const repoRoot = resolveRepoRoot(options.target);
172
+ const statePath = agentsStatePathForRepo(repoRoot);
173
+
174
+ if (options.subcommand === 'finish') {
175
+ const result = finishAgentSession(repoRoot, options);
176
+ if (options.json) {
177
+ process.stdout.write(`${JSON.stringify(result.evidence, null, 2)}\n`);
178
+ }
179
+ process.exitCode = 0;
180
+ return;
181
+ }
182
+
183
+ if (options.subcommand === 'cleanup-sessions') {
184
+ process.stdout.write(agentCleanupSessions.runCleanupSessionsCommand(repoRoot, options));
185
+ process.exitCode = 0;
186
+ return;
187
+ }
188
+
189
+ if (options.subcommand === 'start') {
190
+ if (agentsStart.shouldUseInteractivePanel(options, process.stdin, process.stdout)) {
191
+ agentsStart.startInteractiveAgentPanel(repoRoot, options, {
192
+ onDone(result) {
193
+ process.exitCode = result.status;
194
+ },
195
+ });
196
+ return;
197
+ }
198
+ if (options.dryRun) {
199
+ const output = agentsStart.dryRunStart(options, repoRoot);
200
+ process.stdout.write(output.endsWith('\n') ? output : `${output}\n`);
201
+ process.exitCode = 0;
202
+ return;
203
+ }
204
+ if (options.panel && !options.task) {
205
+ process.stderr.write('[gitguardex] gx agents start --panel requires an interactive terminal when no task is provided.\n');
206
+ process.exitCode = 1;
207
+ return;
208
+ }
209
+ if (options.task) {
210
+ const result = agentsStart.startAgentLane(repoRoot, options);
211
+ if (result.stdout) process.stdout.write(result.stdout);
212
+ if (result.stderr) process.stderr.write(result.stderr);
213
+ process.exitCode = result.status;
214
+ return;
215
+ }
216
+
217
+ const existingState = readAgentsState(repoRoot);
218
+ const existingReviewPid = Number.parseInt(String(existingState?.review?.pid || ''), 10);
219
+ const existingCleanupPid = Number.parseInt(String(existingState?.cleanup?.pid || ''), 10);
220
+ const reviewRunning = processAlive(existingReviewPid);
221
+ const cleanupRunning = processAlive(existingCleanupPid);
222
+
223
+ if (reviewRunning && cleanupRunning) {
224
+ console.log(
225
+ `[${TOOL_NAME}] Repo agents already running (review pid=${existingReviewPid}, cleanup pid=${existingCleanupPid}).`,
226
+ );
227
+ process.exitCode = 0;
228
+ return;
229
+ }
230
+
231
+ const reviewLogPath = path.join(repoRoot, '.omx', 'logs', 'agent-review.log');
232
+ const cleanupLogPath = path.join(repoRoot, '.omx', 'logs', 'agent-cleanup.log');
233
+
234
+ let reviewPid = existingReviewPid;
235
+ let cleanupPid = existingCleanupPid;
236
+ let startedAny = false;
237
+ let reusedAny = false;
238
+
239
+ const mainScriptPath = require.resolve('../main.js');
240
+ if (!reviewRunning) {
241
+ reviewPid = spawnDetachedAgentProcess({
242
+ command: process.execPath,
243
+ args: [
244
+ mainScriptPath,
245
+ 'internal',
246
+ 'run-shell',
247
+ 'reviewBot',
248
+ '--target',
249
+ repoRoot,
250
+ '--interval',
251
+ String(options.reviewIntervalSeconds),
252
+ ],
253
+ cwd: repoRoot,
254
+ logPath: reviewLogPath,
255
+ });
256
+ startedAny = true;
257
+ } else {
258
+ reusedAny = true;
259
+ }
260
+
261
+ if (!cleanupRunning) {
262
+ cleanupPid = spawnDetachedAgentProcess({
263
+ command: process.execPath,
264
+ args: [
265
+ mainScriptPath,
266
+ 'cleanup',
267
+ '--target',
268
+ repoRoot,
269
+ '--watch',
270
+ '--interval',
271
+ String(options.cleanupIntervalSeconds),
272
+ '--idle-minutes',
273
+ String(options.idleMinutes),
274
+ ],
275
+ cwd: repoRoot,
276
+ logPath: cleanupLogPath,
277
+ });
278
+ startedAny = true;
279
+ } else {
280
+ reusedAny = true;
281
+ }
282
+
283
+ const priorReviewInterval = Number.parseInt(String(existingState?.review?.intervalSeconds || ''), 10);
284
+ const priorCleanupInterval = Number.parseInt(String(existingState?.cleanup?.intervalSeconds || ''), 10);
285
+ const priorIdleMinutes = Number.parseInt(String(existingState?.cleanup?.idleMinutes || ''), 10);
286
+ const reviewIntervalSeconds = reviewRunning && Number.isInteger(priorReviewInterval) && priorReviewInterval >= 5
287
+ ? priorReviewInterval
288
+ : options.reviewIntervalSeconds;
289
+ const cleanupIntervalSeconds = cleanupRunning && Number.isInteger(priorCleanupInterval) && priorCleanupInterval >= 5
290
+ ? priorCleanupInterval
291
+ : options.cleanupIntervalSeconds;
292
+ const idleMinutes = cleanupRunning && Number.isInteger(priorIdleMinutes) && priorIdleMinutes >= 1
293
+ ? priorIdleMinutes
294
+ : options.idleMinutes;
295
+
296
+ writeAgentsState(repoRoot, {
297
+ schemaVersion: 1,
298
+ repoRoot,
299
+ startedAt: new Date().toISOString(),
300
+ review: {
301
+ pid: reviewPid,
302
+ intervalSeconds: reviewIntervalSeconds,
303
+ script: mainScriptPath,
304
+ logPath: reviewLogPath,
305
+ },
306
+ cleanup: {
307
+ pid: cleanupPid,
308
+ intervalSeconds: cleanupIntervalSeconds,
309
+ idleMinutes,
310
+ script: mainScriptPath,
311
+ logPath: cleanupLogPath,
312
+ },
313
+ });
314
+
315
+ console.log(
316
+ `[${TOOL_NAME}] Started repo agents in ${repoRoot} (review pid=${reviewPid}, cleanup pid=${cleanupPid}).`,
317
+ );
318
+ if (reusedAny && startedAny) {
319
+ console.log(`[${TOOL_NAME}] Reused healthy bot process(es) and started only missing ones.`);
320
+ }
321
+ console.log(`[${TOOL_NAME}] Logs: ${reviewLogPath}, ${cleanupLogPath}`);
322
+ process.exitCode = 0;
323
+ return;
324
+ }
325
+
326
+ if (options.subcommand === 'stop') {
327
+ if (options.pid) {
328
+ const stopResult = stopAgentProcessByPid(options.pid);
329
+ const success = ['stopped', 'not-running'].includes(stopResult.status);
330
+ console.log(
331
+ `[${TOOL_NAME}] Stopped agent pid ${options.pid} (${stopResult.status}).`,
332
+ );
333
+ process.exitCode = success ? 0 : 1;
334
+ return;
335
+ }
336
+
337
+ const existingState = readAgentsState(repoRoot);
338
+ if (!existingState) {
339
+ console.log(`[${TOOL_NAME}] Repo agents are not running for ${repoRoot}.`);
340
+ process.exitCode = 0;
341
+ return;
342
+ }
343
+
344
+ const reviewStop = stopAgentProcessByPid(existingState?.review?.pid, 'internal run-shell reviewBot');
345
+ const cleanupStop = stopAgentProcessByPid(existingState?.cleanup?.pid, `${path.basename(require.resolve('../main.js'))} cleanup`);
346
+
347
+ if (fs.existsSync(statePath)) {
348
+ fs.unlinkSync(statePath);
349
+ }
350
+
351
+ console.log(
352
+ `[${TOOL_NAME}] Stopped repo agents in ${repoRoot} (review=${reviewStop.status}, cleanup=${cleanupStop.status}).`,
353
+ );
354
+ process.exitCode = 0;
355
+ return;
356
+ }
357
+
358
+ process.stdout.write(agentStatus.runStatusCommand(repoRoot, options));
359
+ process.exitCode = 0;
360
+ }
361
+
362
+ module.exports = {
363
+ agents,
364
+ };
@@ -0,0 +1,92 @@
1
+ // Deprecated direct aliases: `install`, `fix`, `scan`. Pure code-motion from
2
+ // src/cli/main.js — no behavior changes.
3
+ const { TOOL_NAME } = require('../../context');
4
+ const { parseCommonArgs } = require('../args');
5
+ const { printOperations } = require('../../scaffold');
6
+ const {
7
+ runInstallInternal,
8
+ runFixInternal,
9
+ runScanInternal,
10
+ printScanResult,
11
+ setExitCodeFromScan,
12
+ } = require('../shared/scaffolding');
13
+ const { assertProtectedMainWriteAllowed } = require('../shared/sandbox');
14
+ const { describeGuardexRepoToggle } = require('../shared/repo-env');
15
+
16
+ function install(rawArgs) {
17
+ const options = parseCommonArgs(rawArgs, {
18
+ target: process.cwd(),
19
+ force: false,
20
+ skipAgents: false,
21
+ skipPackageJson: false,
22
+ skipGitignore: false,
23
+ dryRun: false,
24
+ allowProtectedBaseWrite: false,
25
+ });
26
+
27
+ assertProtectedMainWriteAllowed(options, 'install');
28
+ const payload = runInstallInternal(options);
29
+ printOperations('Install target', payload, options.dryRun);
30
+
31
+ if (!options.dryRun) {
32
+ if (payload.guardexEnabled === false) {
33
+ console.log(
34
+ `[${TOOL_NAME}] Guardex is disabled for this repo (${describeGuardexRepoToggle(payload.guardexToggle)}). Skipping repo bootstrap.`,
35
+ );
36
+ process.exitCode = 0;
37
+ return;
38
+ }
39
+ if (!options.skipAgents) {
40
+ console.log(`[${TOOL_NAME}] AGENTS.md managed policy block is configured by install.`);
41
+ }
42
+ console.log(`[${TOOL_NAME}] Installed. Next step: ${TOOL_NAME} setup`);
43
+ }
44
+
45
+ process.exitCode = 0;
46
+ }
47
+
48
+ function fix(rawArgs) {
49
+ const options = parseCommonArgs(rawArgs, {
50
+ target: process.cwd(),
51
+ dropStaleLocks: true,
52
+ skipAgents: false,
53
+ skipPackageJson: false,
54
+ skipGitignore: false,
55
+ dryRun: false,
56
+ allowProtectedBaseWrite: false,
57
+ });
58
+
59
+ assertProtectedMainWriteAllowed(options, 'fix');
60
+ const payload = runFixInternal(options);
61
+ printOperations('Fix target', payload, options.dryRun);
62
+
63
+ if (!options.dryRun) {
64
+ if (payload.guardexEnabled === false) {
65
+ console.log(
66
+ `[${TOOL_NAME}] Guardex is disabled for this repo (${describeGuardexRepoToggle(payload.guardexToggle)}). Skipping repo repair.`,
67
+ );
68
+ process.exitCode = 0;
69
+ return;
70
+ }
71
+ console.log(`[${TOOL_NAME}] Repair complete. Next step: ${TOOL_NAME} scan`);
72
+ }
73
+
74
+ process.exitCode = 0;
75
+ }
76
+
77
+ function scan(rawArgs) {
78
+ const options = parseCommonArgs(rawArgs, {
79
+ target: process.cwd(),
80
+ json: false,
81
+ });
82
+
83
+ const result = runScanInternal(options);
84
+ printScanResult(result, options.json);
85
+ setExitCodeFromScan(result);
86
+ }
87
+
88
+ module.exports = {
89
+ install,
90
+ fix,
91
+ scan,
92
+ };
@@ -0,0 +1,127 @@
1
+ // `gx branch`, `gx pivot`, `gx ship`, `gx locks`, `gx worktree` — branch
2
+ // workflow surface. Pure code-motion from src/cli/main.js.
3
+ const { TOOL_NAME, SHORT_TOOL_NAME } = require('../../context');
4
+ const { resolveRepoRoot } = require('../../git');
5
+ const {
6
+ run,
7
+ extractTargetedArgs,
8
+ runPackageAsset,
9
+ invokePackageAsset,
10
+ } = require('../../core/runtime');
11
+ const { finish, merge } = require('./finish');
12
+
13
+ function branch(rawArgs) {
14
+ const activeCwd = process.cwd();
15
+ const [subcommand, ...rest] = rawArgs;
16
+ if (subcommand === 'start') {
17
+ const { target, passthrough } = extractTargetedArgs(rest);
18
+ invokePackageAsset('branchStart', passthrough, { cwd: resolveRepoRoot(target) });
19
+ return;
20
+ }
21
+ if (subcommand === 'finish') {
22
+ const { target, passthrough } = extractTargetedArgs(rest);
23
+ invokePackageAsset('branchFinish', passthrough, {
24
+ cwd: resolveRepoRoot(target),
25
+ env: { GUARDEX_FINISH_ACTIVE_CWD: activeCwd },
26
+ });
27
+ return;
28
+ }
29
+ if (subcommand === 'merge') return merge(rest);
30
+ throw new Error(
31
+ `Usage: ${SHORT_TOOL_NAME} branch <start|finish|merge> [options] ` +
32
+ `(examples: '${SHORT_TOOL_NAME} branch start "<task>" "<agent>"', '${SHORT_TOOL_NAME} branch finish --branch <agent/...>')`,
33
+ );
34
+ }
35
+
36
+ // `gx pivot` — single-tool-call escape from a protected branch into an isolated
37
+ // agent worktree. AI agents (Claude Code / Codex) cannot set the bypass env
38
+ // vars from inside a tool call, so they need a whitelisted command that does
39
+ // the whole hop: branch+worktree creation, dirty-tree migration, and a clean
40
+ // trailer (`WORKTREE_PATH=...`, `BRANCH=...`, `NEXT_STEP=cd ...`) the agent can
41
+ // parse to know exactly where to `cd`.
42
+ //
43
+ // On an existing agent/* branch, `gx pivot` short-circuits and just prints the
44
+ // current worktree path — safe to call as a no-op.
45
+ function pivot(rawArgs) {
46
+ const { target, passthrough } = extractTargetedArgs(rawArgs);
47
+ const repoRoot = resolveRepoRoot(target);
48
+ const headProc = run('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: repoRoot });
49
+ const currentBranch = String(headProc.stdout || '').trim();
50
+ if (currentBranch.startsWith('agent/')) {
51
+ const wtProc = run('git', ['rev-parse', '--show-toplevel'], { cwd: repoRoot });
52
+ const wtPath = String(wtProc.stdout || '').trim() || repoRoot;
53
+ process.stdout.write(`[${TOOL_NAME} pivot] Already on agent branch '${currentBranch}'.\n`);
54
+ process.stdout.write(`WORKTREE_PATH=${wtPath}\n`);
55
+ process.stdout.write(`BRANCH=${currentBranch}\n`);
56
+ process.stdout.write(`NEXT_STEP=cd "${wtPath}"\n`);
57
+ process.exitCode = 0;
58
+ return;
59
+ }
60
+ const result = runPackageAsset('branchStart', passthrough, { cwd: repoRoot });
61
+ if (result.stdout) process.stdout.write(result.stdout);
62
+ if (result.stderr) process.stderr.write(result.stderr);
63
+ if (result.status !== 0) {
64
+ process.exitCode = result.status || 1;
65
+ return;
66
+ }
67
+ const stdoutText = String(result.stdout || '');
68
+ const wtMatch = stdoutText.match(/^\[agent-branch-start\] Worktree:\s+(.+)$/m);
69
+ const branchMatch = stdoutText.match(/^\[agent-branch-start\] (?:Created branch|Reusing existing branch):\s+(.+)$/m);
70
+ if (wtMatch) {
71
+ const wtPath = wtMatch[1].trim();
72
+ process.stdout.write('\n');
73
+ process.stdout.write(`WORKTREE_PATH=${wtPath}\n`);
74
+ if (branchMatch) process.stdout.write(`BRANCH=${branchMatch[1].trim()}\n`);
75
+ process.stdout.write(`NEXT_STEP=cd "${wtPath}"\n`);
76
+ }
77
+ process.exitCode = 0;
78
+ }
79
+
80
+ // `gx ship` — alias for the canonical "I am done" command. Defaults to
81
+ // `finish --via-pr --wait-for-merge --cleanup` so AI agents don't strand
82
+ // commits or worktrees by accident. Any explicit user-supplied flags survive.
83
+ function ship(rawArgs) {
84
+ const args = Array.isArray(rawArgs) ? rawArgs.slice() : [];
85
+ const ensureFlag = (flag) => {
86
+ if (!args.includes(flag)) args.push(flag);
87
+ };
88
+ ensureFlag('--via-pr');
89
+ ensureFlag('--wait-for-merge');
90
+ ensureFlag('--cleanup');
91
+ // `gx ship` enforces the merge gate (clean review + green CI) by default;
92
+ // an explicit --no-gate-review / --skip-review-gate opts back out.
93
+ if (!args.includes('--no-gate-review') && !args.includes('--skip-review-gate')) {
94
+ ensureFlag('--gate-review');
95
+ }
96
+ return finish(args);
97
+ }
98
+
99
+ function locks(rawArgs) {
100
+ const { target, passthrough } = extractTargetedArgs(rawArgs);
101
+ const result = runPackageAsset('lockTool', passthrough, { cwd: resolveRepoRoot(target) });
102
+ if (result.stdout) process.stdout.write(result.stdout);
103
+ if (result.stderr) process.stderr.write(result.stderr);
104
+ process.exitCode = result.status;
105
+ }
106
+
107
+ function worktree(rawArgs) {
108
+ const activeCwd = process.cwd();
109
+ const [subcommand, ...rest] = rawArgs;
110
+ if (subcommand === 'prune') {
111
+ const { target, passthrough } = extractTargetedArgs(rest);
112
+ invokePackageAsset('worktreePrune', passthrough, {
113
+ cwd: resolveRepoRoot(target),
114
+ env: { GUARDEX_PRUNE_ACTIVE_CWD: process.env.GUARDEX_PRUNE_ACTIVE_CWD || activeCwd },
115
+ });
116
+ return;
117
+ }
118
+ throw new Error(`Usage: ${SHORT_TOOL_NAME} worktree prune [cleanup-options]`);
119
+ }
120
+
121
+ module.exports = {
122
+ branch,
123
+ pivot,
124
+ ship,
125
+ locks,
126
+ worktree,
127
+ };