@covibes/zeroshot 5.3.0 → 5.4.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 (82) hide show
  1. package/README.md +94 -14
  2. package/cli/commands/providers.js +8 -9
  3. package/cli/index.js +3032 -2409
  4. package/cli/message-formatters-normal.js +28 -6
  5. package/cluster-templates/base-templates/debug-workflow.json +1 -1
  6. package/cluster-templates/base-templates/full-workflow.json +72 -188
  7. package/cluster-templates/base-templates/worker-validator.json +59 -3
  8. package/cluster-templates/conductor-bootstrap.json +4 -4
  9. package/lib/docker-config.js +8 -0
  10. package/lib/git-remote-utils.js +165 -0
  11. package/lib/id-detector.js +10 -7
  12. package/lib/provider-defaults.js +62 -0
  13. package/lib/provider-names.js +2 -1
  14. package/lib/settings/claude-auth.js +78 -0
  15. package/lib/settings.js +161 -63
  16. package/package.json +7 -2
  17. package/scripts/setup-merge-queue.sh +170 -0
  18. package/src/agent/agent-config.js +135 -82
  19. package/src/agent/agent-context-builder.js +297 -188
  20. package/src/agent/agent-hook-executor.js +310 -113
  21. package/src/agent/agent-lifecycle.js +385 -325
  22. package/src/agent/agent-stuck-detector.js +7 -7
  23. package/src/agent/agent-task-executor.js +824 -565
  24. package/src/agent/output-extraction.js +41 -24
  25. package/src/agent/output-reformatter.js +1 -1
  26. package/src/agent/schema-utils.js +108 -73
  27. package/src/agent-wrapper.js +10 -1
  28. package/src/agents/git-pusher-template.js +285 -0
  29. package/src/claude-task-runner.js +85 -34
  30. package/src/config-validator.js +922 -657
  31. package/src/input-helpers.js +65 -0
  32. package/src/isolation-manager.js +289 -199
  33. package/src/issue-providers/README.md +305 -0
  34. package/src/issue-providers/azure-devops-provider.js +273 -0
  35. package/src/issue-providers/base-provider.js +232 -0
  36. package/src/issue-providers/github-provider.js +179 -0
  37. package/src/issue-providers/gitlab-provider.js +241 -0
  38. package/src/issue-providers/index.js +196 -0
  39. package/src/issue-providers/jira-provider.js +239 -0
  40. package/src/ledger.js +22 -5
  41. package/src/lib/safe-exec.js +88 -0
  42. package/src/orchestrator.js +1107 -811
  43. package/src/preflight.js +313 -159
  44. package/src/process-metrics.js +98 -56
  45. package/src/providers/anthropic/cli-builder.js +53 -25
  46. package/src/providers/anthropic/index.js +72 -2
  47. package/src/providers/anthropic/output-parser.js +32 -14
  48. package/src/providers/base-provider.js +70 -0
  49. package/src/providers/capabilities.js +9 -0
  50. package/src/providers/google/output-parser.js +47 -38
  51. package/src/providers/index.js +19 -3
  52. package/src/providers/openai/cli-builder.js +14 -3
  53. package/src/providers/openai/index.js +1 -0
  54. package/src/providers/openai/output-parser.js +44 -30
  55. package/src/providers/opencode/cli-builder.js +42 -0
  56. package/src/providers/opencode/index.js +103 -0
  57. package/src/providers/opencode/models.js +55 -0
  58. package/src/providers/opencode/output-parser.js +122 -0
  59. package/src/schemas/sub-cluster.js +68 -39
  60. package/src/status-footer.js +94 -48
  61. package/src/sub-cluster-wrapper.js +76 -35
  62. package/src/template-resolver.js +12 -9
  63. package/src/tui/data-poller.js +123 -99
  64. package/src/tui/formatters.js +4 -3
  65. package/src/tui/keybindings.js +259 -318
  66. package/src/tui/renderer.js +11 -21
  67. package/task-lib/attachable-watcher.js +118 -81
  68. package/task-lib/claude-recovery.js +61 -24
  69. package/task-lib/commands/episodes.js +105 -0
  70. package/task-lib/commands/list.js +2 -2
  71. package/task-lib/commands/logs.js +231 -189
  72. package/task-lib/commands/schedules.js +111 -61
  73. package/task-lib/config.js +0 -2
  74. package/task-lib/runner.js +84 -27
  75. package/task-lib/scheduler.js +1 -1
  76. package/task-lib/store.js +467 -168
  77. package/task-lib/tui/formatters.js +94 -45
  78. package/task-lib/tui/renderer.js +13 -3
  79. package/task-lib/tui.js +73 -32
  80. package/task-lib/watcher.js +128 -90
  81. package/src/agents/git-pusher-agent.json +0 -20
  82. package/src/github.js +0 -139
package/src/preflight.js CHANGED
@@ -9,10 +9,18 @@
9
9
  * Provides CLEAR, ACTIONABLE error messages with recovery instructions.
10
10
  */
11
11
 
12
- const { execSync } = require('child_process');
12
+ const { execSync } = require('./lib/safe-exec'); // Enforces timeouts
13
13
  const path = require('path');
14
14
  const fs = require('fs');
15
15
  const os = require('os');
16
+ const {
17
+ isValidAnthropicKey,
18
+ ANTHROPIC_KEY_PREFIX,
19
+ resolveClaudeAuth,
20
+ } = require('../lib/settings/claude-auth.js');
21
+ const { loadSettings, getClaudeCommand } = require('../lib/settings.js');
22
+ const { normalizeProviderName } = require('../lib/provider-names');
23
+ const { detectGitContext } = require('../lib/git-remote-utils');
16
24
 
17
25
  /**
18
26
  * Validation result
@@ -122,29 +130,55 @@ function checkMacOsKeychain() {
122
130
  */
123
131
  function checkClaudeAuth() {
124
132
  const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
133
+
134
+ // Helper to create consistent result objects
135
+ const authResult = (authenticated, error = null, method = null) => ({
136
+ authenticated,
137
+ error,
138
+ configDir,
139
+ ...(method && { method }),
140
+ });
141
+
142
+ // Check for Bedrock bearer token (highest priority)
143
+ if (process.env.AWS_BEARER_TOKEN_BEDROCK) {
144
+ if (!process.env.AWS_REGION) {
145
+ return authResult(false, 'AWS_BEARER_TOKEN_BEDROCK set but AWS_REGION is missing');
146
+ }
147
+ return authResult(true, null, 'bedrock_api_key');
148
+ }
149
+
150
+ // Check for ANTHROPIC_API_KEY environment variable
151
+ const apiKeyEnv = process.env.ANTHROPIC_API_KEY;
152
+ if (apiKeyEnv) {
153
+ if (!isValidAnthropicKey(apiKeyEnv)) {
154
+ return authResult(false, `ANTHROPIC_API_KEY must start with ${ANTHROPIC_KEY_PREFIX}`);
155
+ }
156
+ return authResult(true, null, 'env_api_key');
157
+ }
158
+
159
+ // Check for settings-based auth (anthropicApiKey or bedrockApiKey in settings)
160
+ const settings = loadSettings();
161
+ const settingsAuth = resolveClaudeAuth(settings);
162
+ if (settingsAuth.ANTHROPIC_API_KEY) {
163
+ return authResult(true, null, 'settings_api_key');
164
+ }
165
+ if (settingsAuth.AWS_BEARER_TOKEN_BEDROCK) {
166
+ if (!settingsAuth.AWS_REGION && !process.env.AWS_REGION) {
167
+ return authResult(false, 'Bedrock configured in settings but AWS_REGION is missing');
168
+ }
169
+ return authResult(true, null, 'settings_bedrock');
170
+ }
171
+
125
172
  const credentialsPath = path.join(configDir, '.credentials.json');
126
173
 
127
174
  // Check if credentials file exists
128
175
  if (!fs.existsSync(credentialsPath)) {
129
176
  // No credentials file - check macOS Keychain as fallback
130
177
  // Only use Keychain when using default config dir (not custom CLAUDE_CONFIG_DIR)
131
- const isDefaultConfigDir = !process.env.CLAUDE_CONFIG_DIR;
132
- if (isDefaultConfigDir) {
133
- const keychainResult = checkMacOsKeychain();
134
- if (keychainResult.authenticated) {
135
- return {
136
- authenticated: true,
137
- error: null,
138
- configDir,
139
- method: 'keychain',
140
- };
141
- }
178
+ if (!process.env.CLAUDE_CONFIG_DIR && checkMacOsKeychain().authenticated) {
179
+ return authResult(true, null, 'keychain');
142
180
  }
143
- return {
144
- authenticated: false,
145
- error: 'No credentials file found',
146
- configDir,
147
- };
181
+ return authResult(false, 'No credentials file found');
148
182
  }
149
183
 
150
184
  // Check if credentials file has content
@@ -154,42 +188,21 @@ function checkClaudeAuth() {
154
188
 
155
189
  // Check for OAuth token (primary auth method)
156
190
  if (creds.claudeAiOauth?.accessToken) {
157
- // Check if token is expired
158
191
  const expiresAt = creds.claudeAiOauth.expiresAt;
159
192
  if (expiresAt && new Date(expiresAt) < new Date()) {
160
- return {
161
- authenticated: false,
162
- error: 'OAuth token expired',
163
- configDir,
164
- };
193
+ return authResult(false, 'OAuth token expired');
165
194
  }
166
- return {
167
- authenticated: true,
168
- error: null,
169
- configDir,
170
- };
195
+ return authResult(true);
171
196
  }
172
197
 
173
198
  // Check for API key auth
174
199
  if (creds.apiKey) {
175
- return {
176
- authenticated: true,
177
- error: null,
178
- configDir,
179
- };
200
+ return authResult(true);
180
201
  }
181
202
 
182
- return {
183
- authenticated: false,
184
- error: 'No valid authentication found in credentials',
185
- configDir,
186
- };
203
+ return authResult(false, 'No valid authentication found in credentials');
187
204
  } catch (err) {
188
- return {
189
- authenticated: false,
190
- error: `Failed to parse credentials: ${err.message}`,
191
- configDir,
192
- };
205
+ return authResult(false, `Failed to parse credentials: ${err.message}`);
193
206
  }
194
207
  }
195
208
 
@@ -274,6 +287,176 @@ function checkDocker() {
274
287
  }
275
288
  }
276
289
 
290
+ function buildClaudeCommand(options) {
291
+ const { command, args } = getClaudeCommand();
292
+ return options.claudeCommand || [command, ...args].join(' ');
293
+ }
294
+
295
+ function validateClaudeProvider(options) {
296
+ const errors = [];
297
+ const warnings = [];
298
+ const claudeCommand = buildClaudeCommand(options);
299
+
300
+ const claude = getClaudeVersion(claudeCommand);
301
+ if (!claude.installed) {
302
+ errors.push(
303
+ formatError(
304
+ 'Claude command not available',
305
+ claude.error,
306
+ claudeCommand === 'claude'
307
+ ? [
308
+ 'Install Claude CLI: npm install -g @anthropic-ai/claude-code',
309
+ 'Or: brew install claude (macOS)',
310
+ 'Then run: claude --version',
311
+ ]
312
+ : [
313
+ `Command '${claudeCommand}' not found`,
314
+ 'Check settings: zeroshot settings',
315
+ 'Update claudeCommand: zeroshot settings set claudeCommand "your-command"',
316
+ 'Or install the missing command',
317
+ ]
318
+ )
319
+ );
320
+ } else if (claude.version) {
321
+ const [major, minor] = claude.version.split('.').map(Number);
322
+ if (major < 1 || (major === 1 && minor < 0)) {
323
+ warnings.push(
324
+ `⚠️ Claude CLI version ${claude.version} may be outdated. Consider upgrading.`
325
+ );
326
+ }
327
+ }
328
+
329
+ if (process.getuid && process.getuid() === 0) {
330
+ errors.push(
331
+ formatError(
332
+ 'Running as root',
333
+ 'Claude CLI refuses --dangerously-skip-permissions flag when running as root (UID 0)',
334
+ [
335
+ 'Run as non-root user in Docker: docker run --user 1000:1000 ...',
336
+ 'Or create non-root user: adduser testuser && su - testuser',
337
+ 'Or use existing node user: docker run --user node ...',
338
+ 'Security: Claude CLI blocks this flag as root to prevent privilege escalation',
339
+ ]
340
+ )
341
+ );
342
+ }
343
+
344
+ return { errors, warnings };
345
+ }
346
+
347
+ function validateCliProvider(command, title, detail, recovery) {
348
+ const errors = [];
349
+ if (!commandExists(command)) {
350
+ errors.push(formatError(title, detail, recovery));
351
+ }
352
+
353
+ return { errors, warnings: [] };
354
+ }
355
+
356
+ function validateProvider(providerName, options) {
357
+ const validatorByProvider = {
358
+ claude: () => validateClaudeProvider(options),
359
+ codex: () =>
360
+ validateCliProvider('codex', 'Codex CLI not available', 'Command "codex" not installed', [
361
+ 'Install Codex CLI: npm install -g @openai/codex',
362
+ 'Then run: codex --version',
363
+ ]),
364
+ gemini: () =>
365
+ validateCliProvider('gemini', 'Gemini CLI not available', 'Command "gemini" not installed', [
366
+ 'Install Gemini CLI: npm install -g @google/gemini-cli',
367
+ 'Then run: gemini --version',
368
+ ]),
369
+ opencode: () =>
370
+ validateCliProvider(
371
+ 'opencode',
372
+ 'Opencode CLI not available',
373
+ 'Command "opencode" not installed',
374
+ ['Install Opencode CLI: see https://opencode.ai', 'Then run: opencode --version']
375
+ ),
376
+ };
377
+
378
+ const validator = validatorByProvider[providerName];
379
+ if (!validator) {
380
+ return {
381
+ errors: [
382
+ formatError('Unknown provider', `Provider "${providerName}" is not supported`, [
383
+ 'Use claude, codex, gemini, or opencode',
384
+ ]),
385
+ ],
386
+ warnings: [],
387
+ };
388
+ }
389
+
390
+ return validator();
391
+ }
392
+
393
+ function validateGhRequirement() {
394
+ const errors = [];
395
+ const gh = checkGhAuth();
396
+ if (!gh.installed) {
397
+ errors.push(
398
+ formatError('GitHub CLI (gh) not installed', 'Required for fetching issues by number', [
399
+ 'Install: brew install gh (macOS) or apt install gh (Linux)',
400
+ 'Or download from: https://cli.github.com/',
401
+ ])
402
+ );
403
+ } else if (!gh.authenticated) {
404
+ errors.push(
405
+ formatError('GitHub CLI (gh) not authenticated', gh.error, [
406
+ 'Run: gh auth login',
407
+ 'Select GitHub.com, HTTPS, and authenticate via browser',
408
+ 'Then verify: gh auth status',
409
+ ])
410
+ );
411
+ }
412
+
413
+ return errors;
414
+ }
415
+
416
+ function validateDockerRequirement() {
417
+ const errors = [];
418
+ const docker = checkDocker();
419
+ if (!docker.available) {
420
+ errors.push(
421
+ formatError(
422
+ 'Docker not available',
423
+ docker.error,
424
+ docker.error.includes('daemon')
425
+ ? ['Start Docker Desktop', 'Or run: sudo systemctl start docker (Linux)']
426
+ : [
427
+ 'Install Docker Desktop from: https://docker.com/products/docker-desktop',
428
+ 'Then start Docker and verify: docker info',
429
+ ]
430
+ )
431
+ );
432
+ }
433
+
434
+ return errors;
435
+ }
436
+
437
+ function isGitRepository() {
438
+ try {
439
+ execSync('git rev-parse --git-dir', { stdio: 'pipe' });
440
+ return true;
441
+ } catch {
442
+ return false;
443
+ }
444
+ }
445
+
446
+ function validateGitRequirement() {
447
+ if (isGitRepository()) {
448
+ return [];
449
+ }
450
+
451
+ return [
452
+ formatError('Not in a git repository', 'Worktree isolation requires a git repository', [
453
+ 'Run from within a git repository',
454
+ 'Or use --docker instead of --worktree for non-git directories',
455
+ 'Initialize a repo with: git init',
456
+ ]),
457
+ ];
458
+ }
459
+
277
460
  /**
278
461
  * Run all preflight checks
279
462
  * @param {Object} options - Preflight options
@@ -289,145 +472,116 @@ function runPreflight(options = {}) {
289
472
  const errors = [];
290
473
  const warnings = [];
291
474
 
292
- const { loadSettings, getClaudeCommand } = require('../lib/settings.js');
293
- const { normalizeProviderName } = require('../lib/provider-names');
294
475
  const settings = loadSettings();
295
476
  const providerName = normalizeProviderName(
296
477
  options.provider || settings.defaultProvider || 'claude'
297
478
  );
298
479
 
299
- if (providerName === 'claude') {
300
- const { command, args } = getClaudeCommand();
301
- const claudeCommand = options.claudeCommand || [command, ...args].join(' ');
302
-
303
- const claude = getClaudeVersion(claudeCommand);
304
- if (!claude.installed) {
305
- errors.push(
306
- formatError(
307
- 'Claude command not available',
308
- claude.error,
309
- claudeCommand === 'claude'
310
- ? [
311
- 'Install Claude CLI: npm install -g @anthropic-ai/claude-code',
312
- 'Or: brew install claude (macOS)',
313
- 'Then run: claude --version',
314
- ]
315
- : [
316
- `Command '${claudeCommand}' not found`,
317
- 'Check settings: zeroshot settings',
318
- 'Update claudeCommand: zeroshot settings set claudeCommand "your-command"',
319
- 'Or install the missing command',
320
- ]
321
- )
322
- );
323
- } else if (claude.version) {
324
- const [major, minor] = claude.version.split('.').map(Number);
325
- if (major < 1 || (major === 1 && minor < 0)) {
326
- warnings.push(
327
- `⚠️ Claude CLI version ${claude.version} may be outdated. Consider upgrading.`
480
+ const providerResult = validateProvider(providerName, options);
481
+ errors.push(...providerResult.errors);
482
+ warnings.push(...providerResult.warnings);
483
+
484
+ // 4. Check issue provider CLI (if required)
485
+ if (options.issueProvider) {
486
+ const { getProvider } = require('./issue-providers');
487
+ const ProviderClass = getProvider(options.issueProvider);
488
+
489
+ if (ProviderClass) {
490
+ const tool = ProviderClass.getRequiredTool();
491
+
492
+ // Check if tool is installed
493
+ if (!commandExists(tool.name)) {
494
+ errors.push(
495
+ formatError(
496
+ `${ProviderClass.displayName} CLI (${tool.name}) not installed`,
497
+ `Required for fetching ${ProviderClass.displayName} issues`,
498
+ [tool.installHint, `Then verify: ${tool.checkCmd}`]
499
+ )
328
500
  );
501
+ } else {
502
+ // Check provider authentication (abstracted per provider)
503
+ // Use targetHost from URL input if provided, otherwise detect from git context
504
+ // This ensures we check auth for the actual target, not the current repo
505
+ const targetHost =
506
+ options.targetHost || detectGitContext(options.cwd || process.cwd())?.host;
507
+ const authResult = ProviderClass.checkAuth(targetHost);
508
+ if (!authResult.authenticated) {
509
+ errors.push(
510
+ formatError(
511
+ `${ProviderClass.displayName} CLI (${tool.name}) not authenticated`,
512
+ authResult.error,
513
+ authResult.recovery
514
+ )
515
+ );
516
+ }
329
517
  }
330
518
  }
519
+ }
331
520
 
332
- // Claude CLI refuses --dangerously-skip-permissions when running as root.
333
- if (process.getuid && process.getuid() === 0) {
334
- errors.push(
335
- formatError(
336
- 'Running as root',
337
- 'Claude CLI refuses --dangerously-skip-permissions flag when running as root (UID 0)',
338
- [
339
- 'Run as non-root user in Docker: docker run --user 1000:1000 ...',
340
- 'Or create non-root user: adduser testuser && su - testuser',
341
- 'Or use existing node user: docker run --user node ...',
342
- 'Security: Claude CLI blocks this flag as root to prevent privilege escalation',
343
- ]
344
- )
345
- );
346
- }
347
- } else if (providerName === 'codex') {
348
- if (!commandExists('codex')) {
521
+ // 5. Check PR/MR CLI tools (if --pr or --ship mode is active)
522
+ if (options.autoPr) {
523
+ const { getPlatformForPR, getPRToolForPlatform, getProvider } = require('./issue-providers');
524
+
525
+ let platform;
526
+ let prGitContext;
527
+ try {
528
+ // Detect git platform (independent of issue provider)
529
+ platform = getPlatformForPR(options.cwd || process.cwd());
530
+ // Get git context for hostname (needed for multi-instance auth checks)
531
+ prGitContext = detectGitContext(options.cwd || process.cwd());
532
+ } catch (error) {
533
+ // If platform detection fails, show clear error
349
534
  errors.push(
350
- formatError('Codex CLI not available', 'Command "codex" not installed', [
351
- 'Install Codex CLI: npm install -g @openai/codex',
352
- 'Then run: codex --version',
535
+ formatError('--pr mode requires a git repository', error.message, [
536
+ 'Ensure you are in a git repository with a remote URL from GitHub, GitLab, or Azure DevOps',
353
537
  ])
354
538
  );
539
+ // Skip CLI tool check if platform unknown
355
540
  }
356
- } else if (providerName === 'gemini') {
357
- if (!commandExists('gemini')) {
358
- errors.push(
359
- formatError('Gemini CLI not available', 'Command "gemini" not installed', [
360
- 'Install Gemini CLI: npm install -g @google/gemini-cli',
361
- 'Then run: gemini --version',
362
- ])
363
- );
541
+
542
+ if (platform) {
543
+ // Get PR tool info from the provider (unified source of truth)
544
+ const tool = getPRToolForPlatform(platform);
545
+ const ProviderClass = getProvider(platform);
546
+
547
+ if (tool && !commandExists(tool.name)) {
548
+ errors.push(
549
+ formatError(
550
+ `${tool.displayName} CLI (${tool.name}) not installed`,
551
+ `Required for --pr mode with ${tool.displayName} repositories`,
552
+ [tool.installHint, `Then verify: ${tool.checkCmd}`]
553
+ )
554
+ );
555
+ } else if (tool && ProviderClass) {
556
+ // Check provider authentication (abstracted per provider)
557
+ // Pass hostname for multi-instance providers (e.g., GitLab with self-hosted)
558
+ const authResult = ProviderClass.checkAuth(prGitContext?.host);
559
+ if (!authResult.authenticated) {
560
+ errors.push(
561
+ formatError(
562
+ `${tool.displayName} CLI (${tool.name}) not authenticated`,
563
+ authResult.error,
564
+ authResult.recovery
565
+ )
566
+ );
567
+ }
568
+ }
364
569
  }
365
- } else {
366
- errors.push(
367
- formatError('Unknown provider', `Provider "${providerName}" is not supported`, [
368
- 'Use claude, codex, or gemini',
369
- ])
370
- );
371
570
  }
372
571
 
373
- // 4. Check gh CLI (if required)
572
+ // Legacy gh check for backward compatibility
374
573
  if (options.requireGh) {
375
- const gh = checkGhAuth();
376
- if (!gh.installed) {
377
- errors.push(
378
- formatError('GitHub CLI (gh) not installed', 'Required for fetching issues by number', [
379
- 'Install: brew install gh (macOS) or apt install gh (Linux)',
380
- 'Or download from: https://cli.github.com/',
381
- ])
382
- );
383
- } else if (!gh.authenticated) {
384
- errors.push(
385
- formatError('GitHub CLI (gh) not authenticated', gh.error, [
386
- 'Run: gh auth login',
387
- 'Select GitHub.com, HTTPS, and authenticate via browser',
388
- 'Then verify: gh auth status',
389
- ])
390
- );
391
- }
574
+ errors.push(...validateGhRequirement());
392
575
  }
393
576
 
394
- // 5. Check Docker (if required)
577
+ // 6. Check Docker (if required)
395
578
  if (options.requireDocker) {
396
- const docker = checkDocker();
397
- if (!docker.available) {
398
- errors.push(
399
- formatError(
400
- 'Docker not available',
401
- docker.error,
402
- docker.error.includes('daemon')
403
- ? ['Start Docker Desktop', 'Or run: sudo systemctl start docker (Linux)']
404
- : [
405
- 'Install Docker Desktop from: https://docker.com/products/docker-desktop',
406
- 'Then start Docker and verify: docker info',
407
- ]
408
- )
409
- );
410
- }
579
+ errors.push(...validateDockerRequirement());
411
580
  }
412
581
 
413
- // 6. Check git repo (if required for worktree isolation)
582
+ // 7. Check git repo (if required for worktree isolation)
414
583
  if (options.requireGit) {
415
- let isGitRepo = false;
416
- try {
417
- execSync('git rev-parse --git-dir', { stdio: 'pipe' });
418
- isGitRepo = true;
419
- } catch {
420
- // Not a git repo
421
- }
422
- if (!isGitRepo) {
423
- errors.push(
424
- formatError('Not in a git repository', 'Worktree isolation requires a git repository', [
425
- 'Run from within a git repository',
426
- 'Or use --docker instead of --worktree for non-git directories',
427
- 'Initialize a repo with: git init',
428
- ])
429
- );
430
- }
584
+ errors.push(...validateGitRequirement());
431
585
  }
432
586
 
433
587
  return {