@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
@@ -0,0 +1,265 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const cp = require('node:child_process');
6
+
7
+ const { TEMPLATE_FILES, toDestinationPath, TEMPLATE_ROOT, PACKAGE_ROOT } = require('../context');
8
+
9
+ const TOOL_NAME = 'gx';
10
+
11
+ const WORKFLOW_TEMPLATE_PREFIX = 'github/workflows/';
12
+
13
+ function listWorkflowTemplates() {
14
+ return TEMPLATE_FILES.filter((entry) => entry.startsWith(WORKFLOW_TEMPLATE_PREFIX));
15
+ }
16
+
17
+ function resolveTemplateSource(relativeTemplatePath) {
18
+ const localTemplate = path.join(TEMPLATE_ROOT, relativeTemplatePath);
19
+ if (fs.existsSync(localTemplate)) return localTemplate;
20
+ const packageTemplate = path.join(PACKAGE_ROOT, 'templates', relativeTemplatePath);
21
+ if (fs.existsSync(packageTemplate)) return packageTemplate;
22
+ return null;
23
+ }
24
+
25
+ function copyFileEnsuringDir(sourcePath, destinationAbsolute) {
26
+ fs.mkdirSync(path.dirname(destinationAbsolute), { recursive: true });
27
+ fs.copyFileSync(sourcePath, destinationAbsolute);
28
+ }
29
+
30
+ function shouldCopy(destinationAbsolute, options) {
31
+ if (!fs.existsSync(destinationAbsolute)) return { copy: true, reason: 'create' };
32
+ if (options.force) return { copy: true, reason: 'overwrite' };
33
+ return { copy: false, reason: 'exists' };
34
+ }
35
+
36
+ function planCiInitOperations(options) {
37
+ const targetRoot = path.resolve(options.target || process.cwd());
38
+ const operations = [];
39
+ for (const templateRelative of listWorkflowTemplates()) {
40
+ const destinationRelative = toDestinationPath(templateRelative);
41
+ const destinationAbsolute = path.join(targetRoot, destinationRelative);
42
+ const sourcePath = resolveTemplateSource(templateRelative);
43
+ if (!sourcePath) {
44
+ operations.push({
45
+ template: templateRelative,
46
+ destination: destinationRelative,
47
+ status: 'missing-source',
48
+ });
49
+ continue;
50
+ }
51
+ const decision = shouldCopy(destinationAbsolute, options);
52
+ operations.push({
53
+ template: templateRelative,
54
+ source: sourcePath,
55
+ destination: destinationRelative,
56
+ destinationAbsolute,
57
+ status: decision.copy ? decision.reason : 'skipped',
58
+ });
59
+ }
60
+ return { targetRoot, operations };
61
+ }
62
+
63
+ function performCiInitOperations(operations, { dryRun }) {
64
+ const summary = { copied: [], overwritten: [], skipped: [], missing: [] };
65
+ for (const op of operations) {
66
+ if (op.status === 'missing-source') {
67
+ summary.missing.push(op.template);
68
+ continue;
69
+ }
70
+ if (op.status === 'skipped') {
71
+ summary.skipped.push(op.destination);
72
+ continue;
73
+ }
74
+ if (!dryRun) {
75
+ copyFileEnsuringDir(op.source, op.destinationAbsolute);
76
+ }
77
+ if (op.status === 'overwrite') {
78
+ summary.overwritten.push(op.destination);
79
+ } else {
80
+ summary.copied.push(op.destination);
81
+ }
82
+ }
83
+ return summary;
84
+ }
85
+
86
+ function maybeStageOnAgentBranch(targetRoot, summary, options) {
87
+ if (options.dryRun || options.noStage) return null;
88
+ if (!summary.copied.length && !summary.overwritten.length) return null;
89
+ // Best-effort stage: only when the target is itself a git repo. Failures
90
+ // are non-fatal — the user can always `git add` themselves.
91
+ const isGit = cp.spawnSync('git', ['-C', targetRoot, 'rev-parse', '--is-inside-work-tree'], {
92
+ encoding: 'utf8',
93
+ });
94
+ if (isGit.status !== 0) return { staged: false, reason: 'target is not a git repo' };
95
+ const files = [...summary.copied, ...summary.overwritten];
96
+ const add = cp.spawnSync('git', ['-C', targetRoot, 'add', '--', ...files], { encoding: 'utf8' });
97
+ if (add.status !== 0) {
98
+ return { staged: false, reason: (add.stderr || add.stdout || '').trim() };
99
+ }
100
+ return { staged: true, count: files.length };
101
+ }
102
+
103
+ function formatCiInitReport({ targetRoot, summary, stageResult, dryRun }) {
104
+ const lines = [];
105
+ const mode = dryRun ? 'dry-run' : 'apply';
106
+ lines.push(`${TOOL_NAME} ci-init (${mode}) — target: ${targetRoot}`);
107
+ if (summary.copied.length) {
108
+ lines.push(` created (${summary.copied.length}):`);
109
+ for (const file of summary.copied) lines.push(` + ${file}`);
110
+ }
111
+ if (summary.overwritten.length) {
112
+ lines.push(` overwritten (${summary.overwritten.length}):`);
113
+ for (const file of summary.overwritten) lines.push(` ~ ${file}`);
114
+ }
115
+ if (summary.skipped.length) {
116
+ lines.push(` skipped (already exists, pass --force to overwrite):`);
117
+ for (const file of summary.skipped) lines.push(` = ${file}`);
118
+ }
119
+ if (summary.missing.length) {
120
+ lines.push(` missing source (${summary.missing.length}):`);
121
+ for (const file of summary.missing) lines.push(` ? ${file}`);
122
+ }
123
+ if (stageResult) {
124
+ if (stageResult.staged) {
125
+ lines.push(` staged ${stageResult.count} file(s) for commit.`);
126
+ } else {
127
+ lines.push(` not staged: ${stageResult.reason}`);
128
+ }
129
+ }
130
+ if (dryRun) {
131
+ lines.push(` (no files written; re-run without --dry-run to apply)`);
132
+ }
133
+ return lines.join('\n');
134
+ }
135
+
136
+ function parseCiInitArgs(rawArgs) {
137
+ const options = {
138
+ target: null,
139
+ dryRun: false,
140
+ force: false,
141
+ json: false,
142
+ noStage: false,
143
+ help: false,
144
+ };
145
+ const args = Array.isArray(rawArgs) ? [...rawArgs] : [];
146
+ while (args.length > 0) {
147
+ const arg = args.shift();
148
+ if (arg === '--help' || arg === '-h' || arg === 'help') {
149
+ options.help = true;
150
+ continue;
151
+ }
152
+ if (arg === '--dry-run') {
153
+ options.dryRun = true;
154
+ continue;
155
+ }
156
+ if (arg === '--force') {
157
+ options.force = true;
158
+ continue;
159
+ }
160
+ if (arg === '--json') {
161
+ options.json = true;
162
+ continue;
163
+ }
164
+ if (arg === '--no-stage') {
165
+ options.noStage = true;
166
+ continue;
167
+ }
168
+ if (arg === '--target') {
169
+ options.target = args.shift();
170
+ continue;
171
+ }
172
+ if (arg.startsWith('--target=')) {
173
+ options.target = arg.slice('--target='.length);
174
+ continue;
175
+ }
176
+ const err = new Error(`Unknown ci-init argument: ${arg}`);
177
+ err.code = 'CI_INIT_BAD_ARG';
178
+ throw err;
179
+ }
180
+ return options;
181
+ }
182
+
183
+ function renderCiInitHelp() {
184
+ return [
185
+ `${TOOL_NAME} ci-init — scaffold budget-friendly GitHub Actions workflows into a target repo.`,
186
+ '',
187
+ 'Usage:',
188
+ ` ${TOOL_NAME} ci-init [--target <path>] [--dry-run] [--force] [--no-stage] [--json]`,
189
+ '',
190
+ 'Options:',
191
+ ` --target <path> Repo to scaffold into (default: current working directory).`,
192
+ ` --dry-run Show what would be written; do not touch the filesystem.`,
193
+ ` --force Overwrite existing files instead of skipping them.`,
194
+ ` --no-stage Skip the post-copy 'git add' step.`,
195
+ ` --json Emit a structured summary instead of the text report.`,
196
+ '',
197
+ 'Files copied (from gitguardex templates/github/workflows/):',
198
+ ` - ci.yml PR-time CI with draft-skip + concurrency-cancel.`,
199
+ ` - ci-full.yml Weekly cross-runtime matrix + label opt-in.`,
200
+ ` - cr.yml AI code review with agent/* + draft skip.`,
201
+ ` - README.md Documents the budget posture and customization knobs.`,
202
+ '',
203
+ 'The command stages copied files with git add when the target is a git repo;',
204
+ 'pair with `gx branch start "<task>" "claude-code"` to land them on a new agent',
205
+ 'branch instead of the primary checkout.',
206
+ ].join('\n');
207
+ }
208
+
209
+ function runCiInitCommand(rawArgs) {
210
+ let options;
211
+ try {
212
+ options = parseCiInitArgs(rawArgs);
213
+ } catch (err) {
214
+ console.error(`[${TOOL_NAME}] ${err.message}`);
215
+ console.error(renderCiInitHelp());
216
+ process.exitCode = 1;
217
+ return;
218
+ }
219
+
220
+ if (options.help) {
221
+ console.log(renderCiInitHelp());
222
+ return;
223
+ }
224
+
225
+ const { targetRoot, operations } = planCiInitOperations(options);
226
+ const summary = performCiInitOperations(operations, { dryRun: options.dryRun });
227
+ const stageResult =
228
+ summary.copied.length || summary.overwritten.length
229
+ ? maybeStageOnAgentBranch(targetRoot, summary, options)
230
+ : null;
231
+
232
+ if (options.json) {
233
+ process.stdout.write(
234
+ `${JSON.stringify(
235
+ {
236
+ targetRoot,
237
+ dryRun: options.dryRun,
238
+ force: options.force,
239
+ summary,
240
+ stageResult,
241
+ },
242
+ null,
243
+ 2,
244
+ )}\n`,
245
+ );
246
+ } else {
247
+ console.log(formatCiInitReport({ targetRoot, summary, stageResult, dryRun: options.dryRun }));
248
+ }
249
+
250
+ if (summary.missing.length > 0) {
251
+ process.exitCode = 1;
252
+ } else {
253
+ process.exitCode = 0;
254
+ }
255
+ }
256
+
257
+ module.exports = {
258
+ runCiInitCommand,
259
+ parseCiInitArgs,
260
+ planCiInitOperations,
261
+ performCiInitOperations,
262
+ formatCiInitReport,
263
+ renderCiInitHelp,
264
+ listWorkflowTemplates,
265
+ };
package/src/cli/args.js CHANGED
@@ -261,6 +261,69 @@ function parseReviewArgs(rawArgs) {
261
261
  };
262
262
  }
263
263
 
264
+ function parsePrReviewArgs(rawArgs) {
265
+ const parsed = parseTargetFlag(rawArgs, process.cwd());
266
+ const options = {
267
+ target: parsed.target,
268
+ provider: 'codex',
269
+ pr: '',
270
+ post: false,
271
+ artifact: '',
272
+ timeoutMs: 10 * 60 * 1000,
273
+ };
274
+
275
+ for (let index = 0; index < parsed.args.length; index += 1) {
276
+ const arg = parsed.args[index];
277
+ if (arg === '--provider') {
278
+ const next = requireValue(parsed.args, index, '--provider');
279
+ if (!['codex', 'claude'].includes(next)) {
280
+ throw new Error(`Invalid --provider value: ${next} (expected codex|claude)`);
281
+ }
282
+ options.provider = next;
283
+ index += 1;
284
+ continue;
285
+ }
286
+ if (arg === '--pr') {
287
+ options.pr = requireValue(parsed.args, index, '--pr');
288
+ index += 1;
289
+ continue;
290
+ }
291
+ if (arg === '--post') {
292
+ options.post = true;
293
+ continue;
294
+ }
295
+ if (arg === '--no-post') {
296
+ options.post = false;
297
+ continue;
298
+ }
299
+ if (arg === '--artifact' || arg === '--output') {
300
+ options.artifact = requireValue(parsed.args, index, arg);
301
+ index += 1;
302
+ continue;
303
+ }
304
+ if (arg === '--timeout-ms') {
305
+ const raw = requireValue(parsed.args, index, '--timeout-ms');
306
+ const parsedTimeout = Number.parseInt(raw, 10);
307
+ if (!Number.isFinite(parsedTimeout) || parsedTimeout <= 0) {
308
+ throw new Error('--timeout-ms requires a positive integer');
309
+ }
310
+ options.timeoutMs = parsedTimeout;
311
+ index += 1;
312
+ continue;
313
+ }
314
+ throw new Error(`Unknown option: ${arg}`);
315
+ }
316
+
317
+ if (!options.pr) {
318
+ throw new Error('--pr requires a pull request number');
319
+ }
320
+ if (!/^\d+$/.test(String(options.pr))) {
321
+ throw new Error(`--pr must be a pull request number (received: ${options.pr})`);
322
+ }
323
+
324
+ return options;
325
+ }
326
+
264
327
  function parseAgentsArgs(rawArgs) {
265
328
  const parsed = parseTargetFlag(rawArgs, process.cwd());
266
329
  const [subcommandRaw = '', ...rest] = parsed.args;
@@ -268,14 +331,77 @@ function parseAgentsArgs(rawArgs) {
268
331
  const options = {
269
332
  target: parsed.target,
270
333
  subcommand,
334
+ task: '',
335
+ agent: '',
336
+ base: '',
337
+ claims: [],
338
+ count: 1,
339
+ agentSelectionSpecs: [],
340
+ panel: false,
341
+ terminal: process.env.GUARDEX_AGENT_TERMINAL || 'kitty',
342
+ dryRun: false,
271
343
  reviewIntervalSeconds: 30,
272
344
  cleanupIntervalSeconds: 60,
273
345
  idleMinutes: DEFAULT_SHADOW_CLEANUP_IDLE_MINUTES,
346
+ staleAgeMinutes: 24 * 60,
274
347
  pid: null,
348
+ branch: '',
349
+ json: false,
350
+ sessionId: '',
351
+ finishArgs: [],
352
+ metadata: {},
275
353
  };
354
+ let terminalProvided = false;
276
355
 
277
356
  for (let index = 0; index < rest.length; index += 1) {
278
357
  const arg = rest[index];
358
+ if (subcommand === 'finish') {
359
+ if (arg === '--json') {
360
+ options.json = true;
361
+ continue;
362
+ }
363
+ if (arg === '--session') {
364
+ const next = rest[index + 1];
365
+ if (!next) {
366
+ throw new Error('--session requires an agent session id');
367
+ }
368
+ options.sessionId = next;
369
+ index += 1;
370
+ continue;
371
+ }
372
+ if (arg === '--branch') {
373
+ const next = rest[index + 1];
374
+ if (!next) {
375
+ throw new Error('--branch requires an agent branch name');
376
+ }
377
+ options.branch = next;
378
+ index += 1;
379
+ continue;
380
+ }
381
+ options.finishArgs.push(arg);
382
+ if (
383
+ ['--base', '--commit-message', '--mode'].includes(arg) &&
384
+ rest[index + 1] &&
385
+ !rest[index + 1].startsWith('-')
386
+ ) {
387
+ options.finishArgs.push(rest[index + 1]);
388
+ index += 1;
389
+ }
390
+ continue;
391
+ }
392
+ if (arg === '--branch') {
393
+ const next = rest[index + 1];
394
+ if (!next) {
395
+ throw new Error('--branch requires an agent branch name');
396
+ }
397
+ options.branch = next;
398
+ index += 1;
399
+ continue;
400
+ }
401
+ if (arg === '--json') {
402
+ options.json = true;
403
+ continue;
404
+ }
279
405
  if (arg === '--review-interval') {
280
406
  const next = rest[index + 1];
281
407
  if (!next) {
@@ -315,6 +441,19 @@ function parseAgentsArgs(rawArgs) {
315
441
  index += 1;
316
442
  continue;
317
443
  }
444
+ if (arg === '--older-than-minutes') {
445
+ const next = rest[index + 1];
446
+ if (!next) {
447
+ throw new Error('--older-than-minutes requires an integer minutes value');
448
+ }
449
+ const parsedValue = Number.parseInt(next, 10);
450
+ if (!Number.isInteger(parsedValue) || parsedValue < 1) {
451
+ throw new Error('--older-than-minutes must be an integer >= 1');
452
+ }
453
+ options.staleAgeMinutes = parsedValue;
454
+ index += 1;
455
+ continue;
456
+ }
318
457
  if (arg === '--pid') {
319
458
  const next = rest[index + 1];
320
459
  if (!next) {
@@ -328,15 +467,170 @@ function parseAgentsArgs(rawArgs) {
328
467
  index += 1;
329
468
  continue;
330
469
  }
470
+ if (arg === '--agent') {
471
+ const next = rest[index + 1];
472
+ if (!next || next.startsWith('-')) {
473
+ throw new Error('--agent requires an agent id');
474
+ }
475
+ options.agent = next;
476
+ index += 1;
477
+ continue;
478
+ }
479
+ if (arg === '--agents') {
480
+ const next = rest[index + 1];
481
+ if (!next || next.startsWith('-')) {
482
+ throw new Error('--agents requires a comma-separated agent list');
483
+ }
484
+ options.agentSelectionSpecs.push(next);
485
+ index += 1;
486
+ continue;
487
+ }
488
+ if (arg === '--count') {
489
+ const next = rest[index + 1];
490
+ if (!next || next.startsWith('-')) {
491
+ throw new Error('--count requires a positive integer');
492
+ }
493
+ const parsedValue = Number.parseInt(next, 10);
494
+ if (!Number.isInteger(parsedValue) || parsedValue < 1) {
495
+ throw new Error('--count requires a positive integer');
496
+ }
497
+ options.count = parsedValue;
498
+ index += 1;
499
+ continue;
500
+ }
501
+ if (arg === '--codex-count' || arg === '--codex-accounts') {
502
+ const next = rest[index + 1];
503
+ if (!next || next.startsWith('-')) {
504
+ throw new Error(`${arg} requires a positive integer`);
505
+ }
506
+ const parsedValue = Number.parseInt(next, 10);
507
+ if (!Number.isInteger(parsedValue) || parsedValue < 1) {
508
+ throw new Error(`${arg} requires a positive integer`);
509
+ }
510
+ options.agent = 'codex';
511
+ options.count = parsedValue;
512
+ index += 1;
513
+ continue;
514
+ }
515
+ if (arg === '--panel') {
516
+ options.panel = true;
517
+ continue;
518
+ }
519
+ if (arg === '--terminal') {
520
+ const next = rest[index + 1];
521
+ if (!next || next.startsWith('-')) {
522
+ throw new Error('--terminal requires kitty or none');
523
+ }
524
+ options.terminal = next;
525
+ terminalProvided = true;
526
+ index += 1;
527
+ continue;
528
+ }
529
+ if (arg === '--base') {
530
+ const next = rest[index + 1];
531
+ if (!next || next.startsWith('-')) {
532
+ throw new Error('--base requires a branch name');
533
+ }
534
+ options.base = next;
535
+ index += 1;
536
+ continue;
537
+ }
538
+ if (arg === '--dry-run') {
539
+ options.dryRun = true;
540
+ continue;
541
+ }
542
+ if (arg === '--claim') {
543
+ const next = rest[index + 1];
544
+ if (!next || next.startsWith('-')) {
545
+ throw new Error('--claim requires a file path');
546
+ }
547
+ options.claims.push(next);
548
+ index += 1;
549
+ continue;
550
+ }
551
+ if (arg === '--meta') {
552
+ const next = rest[index + 1];
553
+ if (!next || next.startsWith('-')) {
554
+ throw new Error('--meta requires a key=value pair');
555
+ }
556
+ const splitIndex = next.indexOf('=');
557
+ const key = splitIndex >= 0 ? next.slice(0, splitIndex).trim() : '';
558
+ if (!key) {
559
+ throw new Error('--meta requires a non-empty key=value pair');
560
+ }
561
+ options.metadata[key] = splitIndex >= 0 ? next.slice(splitIndex + 1) : '';
562
+ index += 1;
563
+ continue;
564
+ }
565
+ if (!arg.startsWith('-') && options.subcommand === 'start' && !options.task) {
566
+ options.task = arg;
567
+ continue;
568
+ }
331
569
  throw new Error(`Unknown option: ${arg}`);
332
570
  }
333
571
 
334
- if (!['start', 'stop', 'status'].includes(options.subcommand)) {
572
+ if (!['start', 'stop', 'status', 'files', 'diff', 'locks', 'finish', 'cleanup-sessions'].includes(options.subcommand)) {
335
573
  throw new Error(`Unknown agents subcommand: ${options.subcommand}`);
336
574
  }
337
575
  if (options.pid !== null && options.subcommand !== 'stop') {
338
576
  throw new Error('--pid is only supported with `gx agents stop`');
339
577
  }
578
+ if (
579
+ (options.task || options.agent || options.base || options.claims.length > 0 || Object.keys(options.metadata).length > 0 || terminalProvided) &&
580
+ options.subcommand !== 'start'
581
+ ) {
582
+ throw new Error('--task, --agent, --agents, --count, --base, --claim, --meta, --terminal, and --panel are only supported with `gx agents start`');
583
+ }
584
+ if (
585
+ (options.agentSelectionSpecs.length > 0 || options.count !== 1 || options.panel) &&
586
+ options.subcommand !== 'start'
587
+ ) {
588
+ throw new Error('--agents, --count, and --panel are only supported with `gx agents start`');
589
+ }
590
+ if (options.dryRun && !['start', 'cleanup-sessions'].includes(options.subcommand)) {
591
+ throw new Error('--dry-run is only supported with `gx agents start|cleanup-sessions`');
592
+ }
593
+ if (options.subcommand === 'start' && options.dryRun && !options.task && !(options.panel && !options.json)) {
594
+ throw new Error('gx agents start --dry-run requires a task');
595
+ }
596
+ if (
597
+ options.subcommand === 'start' &&
598
+ !options.task &&
599
+ !options.panel &&
600
+ (options.agentSelectionSpecs.length > 0 || options.count !== 1)
601
+ ) {
602
+ throw new Error('gx agents start --agents|--count requires a task');
603
+ }
604
+ if (options.claims.length > 0 && !options.task && !options.panel) {
605
+ throw new Error('gx agents start --claim requires a task');
606
+ }
607
+ if (['files', 'diff', 'locks'].includes(options.subcommand) && !options.branch) {
608
+ throw new Error('--branch requires an agent branch name');
609
+ }
610
+ if (options.subcommand === 'finish') {
611
+ if (!options.sessionId && !options.branch) {
612
+ throw new Error('agents finish requires --session <id> or --branch <agent/...>');
613
+ }
614
+ if (options.sessionId && options.branch) {
615
+ throw new Error('agents finish accepts only one of --session or --branch');
616
+ }
617
+ }
618
+ if (options.branch && !['files', 'diff', 'locks', 'finish'].includes(options.subcommand)) {
619
+ throw new Error('--branch is only supported with `gx agents files|diff|locks|finish`');
620
+ }
621
+ if (
622
+ options.json &&
623
+ !['status', 'files', 'diff', 'locks', 'cleanup-sessions', 'finish'].includes(options.subcommand) &&
624
+ !(options.subcommand === 'start' && options.dryRun)
625
+ ) {
626
+ throw new Error('--json is only supported with `gx agents start --dry-run|status|files|diff|locks|finish|cleanup-sessions`');
627
+ }
628
+ if (options.subcommand === 'start' && options.json && !options.dryRun) {
629
+ throw new Error('gx agents start --json requires --dry-run');
630
+ }
631
+ if (options.staleAgeMinutes !== 24 * 60 && options.subcommand !== 'cleanup-sessions') {
632
+ throw new Error('--older-than-minutes is only supported with `gx agents cleanup-sessions`');
633
+ }
340
634
 
341
635
  return options;
342
636
  }
@@ -771,6 +1065,7 @@ function parseFinishArgs(rawArgs, defaults = {}) {
771
1065
  keepRemote: false,
772
1066
  noAutoCommit: false,
773
1067
  parentGitlinkCommit: defaults.parentGitlinkCommit ?? true,
1068
+ advanceSubmodules: false,
774
1069
  failFast: false,
775
1070
  commitMessage: '',
776
1071
  mergeMode: defaults.mergeMode || 'pr',
@@ -878,6 +1173,14 @@ function parseFinishArgs(rawArgs, defaults = {}) {
878
1173
  options.failFast = true;
879
1174
  continue;
880
1175
  }
1176
+ if (arg === '--advance-submodules') {
1177
+ options.advanceSubmodules = true;
1178
+ continue;
1179
+ }
1180
+ if (arg === '--no-advance-submodules') {
1181
+ options.advanceSubmodules = false;
1182
+ continue;
1183
+ }
881
1184
  throw new Error(`Unknown option: ${arg}`);
882
1185
  }
883
1186
 
@@ -898,6 +1201,7 @@ module.exports = {
898
1201
  parseDoctorArgs,
899
1202
  parseTargetFlag,
900
1203
  parseReviewArgs,
1204
+ parsePrReviewArgs,
901
1205
  parseAgentsArgs,
902
1206
  parseReportArgs,
903
1207
  parseSyncArgs,