@link-assistant/hive-mind 0.39.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 (63) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/LICENSE +24 -0
  3. package/README.md +769 -0
  4. package/package.json +58 -0
  5. package/src/agent.lib.mjs +705 -0
  6. package/src/agent.prompts.lib.mjs +196 -0
  7. package/src/buildUserMention.lib.mjs +71 -0
  8. package/src/claude-limits.lib.mjs +389 -0
  9. package/src/claude.lib.mjs +1445 -0
  10. package/src/claude.prompts.lib.mjs +203 -0
  11. package/src/codex.lib.mjs +552 -0
  12. package/src/codex.prompts.lib.mjs +194 -0
  13. package/src/config.lib.mjs +207 -0
  14. package/src/contributing-guidelines.lib.mjs +268 -0
  15. package/src/exit-handler.lib.mjs +205 -0
  16. package/src/git.lib.mjs +145 -0
  17. package/src/github-issue-creator.lib.mjs +246 -0
  18. package/src/github-linking.lib.mjs +152 -0
  19. package/src/github.batch.lib.mjs +272 -0
  20. package/src/github.graphql.lib.mjs +258 -0
  21. package/src/github.lib.mjs +1479 -0
  22. package/src/hive.config.lib.mjs +254 -0
  23. package/src/hive.mjs +1500 -0
  24. package/src/instrument.mjs +191 -0
  25. package/src/interactive-mode.lib.mjs +1000 -0
  26. package/src/lenv-reader.lib.mjs +206 -0
  27. package/src/lib.mjs +490 -0
  28. package/src/lino.lib.mjs +176 -0
  29. package/src/local-ci-checks.lib.mjs +324 -0
  30. package/src/memory-check.mjs +419 -0
  31. package/src/model-mapping.lib.mjs +145 -0
  32. package/src/model-validation.lib.mjs +278 -0
  33. package/src/opencode.lib.mjs +479 -0
  34. package/src/opencode.prompts.lib.mjs +194 -0
  35. package/src/protect-branch.mjs +159 -0
  36. package/src/review.mjs +433 -0
  37. package/src/reviewers-hive.mjs +643 -0
  38. package/src/sentry.lib.mjs +284 -0
  39. package/src/solve.auto-continue.lib.mjs +568 -0
  40. package/src/solve.auto-pr.lib.mjs +1374 -0
  41. package/src/solve.branch-errors.lib.mjs +341 -0
  42. package/src/solve.branch.lib.mjs +230 -0
  43. package/src/solve.config.lib.mjs +342 -0
  44. package/src/solve.error-handlers.lib.mjs +256 -0
  45. package/src/solve.execution.lib.mjs +291 -0
  46. package/src/solve.feedback.lib.mjs +436 -0
  47. package/src/solve.mjs +1128 -0
  48. package/src/solve.preparation.lib.mjs +210 -0
  49. package/src/solve.repo-setup.lib.mjs +114 -0
  50. package/src/solve.repository.lib.mjs +961 -0
  51. package/src/solve.results.lib.mjs +558 -0
  52. package/src/solve.session.lib.mjs +135 -0
  53. package/src/solve.validation.lib.mjs +325 -0
  54. package/src/solve.watch.lib.mjs +572 -0
  55. package/src/start-screen.mjs +324 -0
  56. package/src/task.mjs +308 -0
  57. package/src/telegram-bot.mjs +1481 -0
  58. package/src/telegram-markdown.lib.mjs +64 -0
  59. package/src/usage-limit.lib.mjs +218 -0
  60. package/src/version.lib.mjs +41 -0
  61. package/src/youtrack/solve.youtrack.lib.mjs +116 -0
  62. package/src/youtrack/youtrack-sync.mjs +219 -0
  63. package/src/youtrack/youtrack.lib.mjs +425 -0
@@ -0,0 +1,552 @@
1
+ #!/usr/bin/env node
2
+ // Codex CLI-related utility functions
3
+
4
+ // Check if use is already defined (when imported from solve.mjs)
5
+ // If not, fetch it (when running standalone)
6
+ if (typeof globalThis.use === 'undefined') {
7
+ globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
8
+ }
9
+
10
+ const { $ } = await use('command-stream');
11
+ const fs = (await use('fs')).promises;
12
+ const path = (await use('path')).default;
13
+ const os = (await use('os')).default;
14
+
15
+ // Import log from general lib
16
+ import { log } from './lib.mjs';
17
+ import { reportError } from './sentry.lib.mjs';
18
+ import { timeouts } from './config.lib.mjs';
19
+ import { detectUsageLimit, formatUsageLimitMessage } from './usage-limit.lib.mjs';
20
+
21
+ // Model mapping to translate aliases to full model IDs for Codex
22
+ export const mapModelToId = (model) => {
23
+ const modelMap = {
24
+ 'gpt5': 'gpt-5',
25
+ 'gpt5-codex': 'gpt-5-codex',
26
+ 'o3': 'o3',
27
+ 'o3-mini': 'o3-mini',
28
+ 'gpt4': 'gpt-4',
29
+ 'gpt4o': 'gpt-4o',
30
+ 'claude': 'claude-3-5-sonnet',
31
+ 'sonnet': 'claude-3-5-sonnet',
32
+ 'opus': 'claude-3-opus',
33
+ };
34
+
35
+ // Return mapped model ID if it's an alias, otherwise return as-is
36
+ return modelMap[model] || model;
37
+ };
38
+
39
+ // Function to validate Codex CLI connection
40
+ export const validateCodexConnection = async (model = 'gpt-5') => {
41
+ // Map model alias to full ID
42
+ const mappedModel = mapModelToId(model);
43
+
44
+ // Retry configuration
45
+ const maxRetries = 3;
46
+ let retryCount = 0;
47
+
48
+ const attemptValidation = async () => {
49
+ try {
50
+ if (retryCount === 0) {
51
+ await log('šŸ” Validating Codex CLI connection...');
52
+ } else {
53
+ await log(`šŸ”„ Retry attempt ${retryCount}/${maxRetries} for Codex validation...`);
54
+ }
55
+
56
+ // Check if Codex CLI is installed and get version
57
+ try {
58
+ const versionResult = await $`timeout ${Math.floor(timeouts.codexCli / 1000)} codex --version`;
59
+ if (versionResult.code === 0) {
60
+ const version = versionResult.stdout?.toString().trim();
61
+ if (retryCount === 0) {
62
+ await log(`šŸ“¦ Codex CLI version: ${version}`);
63
+ }
64
+ }
65
+ } catch (versionError) {
66
+ if (retryCount === 0) {
67
+ await log(`āš ļø Codex CLI version check failed (${versionError.code}), proceeding with connection test...`);
68
+ }
69
+ }
70
+
71
+ // Test basic Codex functionality with a simple "echo hi" command
72
+ // Using exec mode with JSON output for validation
73
+ const testResult = await $`printf "echo hi" | timeout ${Math.floor(timeouts.codexCli / 1000)} codex exec --model ${mappedModel} --json --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox`;
74
+
75
+ if (testResult.code !== 0) {
76
+ const stderr = testResult.stderr?.toString() || '';
77
+ const stdout = testResult.stdout?.toString() || '';
78
+
79
+ // Check for authentication errors in both stderr and stdout
80
+ // Codex CLI may return auth errors in JSON format on stdout
81
+ if (stderr.includes('auth') || stderr.includes('login') ||
82
+ stdout.includes('Not logged in') || stdout.includes('401 Unauthorized')) {
83
+ const authError = new Error('Codex authentication failed - 401 Unauthorized');
84
+ authError.isAuthError = true;
85
+ await log('āŒ Codex authentication failed', { level: 'error' });
86
+ await log(' šŸ’” Please run: codex login', { level: 'error' });
87
+ throw authError;
88
+ }
89
+
90
+ await log(`āŒ Codex validation failed with exit code ${testResult.code}`, { level: 'error' });
91
+ if (stderr) await log(` Error: ${stderr.trim()}`, { level: 'error' });
92
+ if (stdout && !stderr) await log(` Output: ${stdout.trim()}`, { level: 'error' });
93
+ return false;
94
+ }
95
+
96
+ // Success
97
+ await log('āœ… Codex CLI connection validated successfully');
98
+ return true;
99
+ } catch (error) {
100
+ await log(`āŒ Failed to validate Codex CLI connection: ${error.message}`, { level: 'error' });
101
+ await log(' šŸ’” Make sure Codex CLI is installed and accessible', { level: 'error' });
102
+ return false;
103
+ }
104
+ };
105
+
106
+ // Start the validation
107
+ return await attemptValidation();
108
+ };
109
+
110
+ // Function to handle Codex runtime switching (if applicable)
111
+ export const handleCodexRuntimeSwitch = async () => {
112
+ // Codex is typically run as a CLI tool, runtime switching may not be applicable
113
+ // This function can be used for any runtime-specific configurations if needed
114
+ await log('ā„¹ļø Codex runtime handling not required for this operation');
115
+ };
116
+
117
+ // Main function to execute Codex with prompts and settings
118
+ export const executeCodex = async (params) => {
119
+ const {
120
+ issueUrl,
121
+ issueNumber,
122
+ prNumber,
123
+ prUrl,
124
+ branchName,
125
+ tempDir,
126
+ isContinueMode,
127
+ mergeStateStatus,
128
+ forkedRepo,
129
+ feedbackLines,
130
+ forkActionsUrl,
131
+ owner,
132
+ repo,
133
+ argv,
134
+ log,
135
+ formatAligned,
136
+ getResourceSnapshot,
137
+ codexPath = 'codex',
138
+ $
139
+ } = params;
140
+
141
+ // Import prompt building functions from codex.prompts.lib.mjs
142
+ const { buildUserPrompt, buildSystemPrompt } = await import('./codex.prompts.lib.mjs');
143
+
144
+ // Build the user prompt
145
+ const prompt = buildUserPrompt({
146
+ issueUrl,
147
+ issueNumber,
148
+ prNumber,
149
+ prUrl,
150
+ branchName,
151
+ tempDir,
152
+ isContinueMode,
153
+ mergeStateStatus,
154
+ forkedRepo,
155
+ feedbackLines,
156
+ forkActionsUrl,
157
+ owner,
158
+ repo,
159
+ argv
160
+ });
161
+
162
+ // Build the system prompt
163
+ const systemPrompt = buildSystemPrompt({
164
+ owner,
165
+ repo,
166
+ issueNumber,
167
+ prNumber,
168
+ branchName,
169
+ tempDir,
170
+ isContinueMode,
171
+ forkedRepo,
172
+ argv
173
+ });
174
+
175
+ // Log prompt details in verbose mode
176
+ if (argv.verbose) {
177
+ await log('\nšŸ“ Final prompt structure:', { verbose: true });
178
+ await log(` Characters: ${prompt.length}`, { verbose: true });
179
+ await log(` System prompt characters: ${systemPrompt.length}`, { verbose: true });
180
+ if (feedbackLines && feedbackLines.length > 0) {
181
+ await log(' Feedback info: Included', { verbose: true });
182
+ }
183
+
184
+ if (argv.dryRun) {
185
+ await log('\nšŸ“‹ User prompt content:', { verbose: true });
186
+ await log('---BEGIN USER PROMPT---', { verbose: true });
187
+ await log(prompt, { verbose: true });
188
+ await log('---END USER PROMPT---', { verbose: true });
189
+ await log('\nšŸ“‹ System prompt content:', { verbose: true });
190
+ await log('---BEGIN SYSTEM PROMPT---', { verbose: true });
191
+ await log(systemPrompt, { verbose: true });
192
+ await log('---END SYSTEM PROMPT---', { verbose: true });
193
+ }
194
+ }
195
+
196
+ // Execute the Codex command
197
+ return await executeCodexCommand({
198
+ tempDir,
199
+ branchName,
200
+ prompt,
201
+ systemPrompt,
202
+ argv,
203
+ log,
204
+ formatAligned,
205
+ getResourceSnapshot,
206
+ forkedRepo,
207
+ feedbackLines,
208
+ codexPath,
209
+ $
210
+ });
211
+ };
212
+
213
+ export const executeCodexCommand = async (params) => {
214
+ const {
215
+ tempDir,
216
+ branchName,
217
+ prompt,
218
+ systemPrompt,
219
+ argv,
220
+ log,
221
+ formatAligned,
222
+ getResourceSnapshot,
223
+ forkedRepo,
224
+ feedbackLines,
225
+ codexPath,
226
+ $
227
+ } = params;
228
+
229
+ // Retry configuration
230
+ const maxRetries = 3;
231
+ let retryCount = 0;
232
+
233
+ const executeWithRetry = async () => {
234
+ // Execute codex command from the cloned repository directory
235
+ if (retryCount === 0) {
236
+ await log(`\n${formatAligned('šŸ¤–', 'Executing Codex:', argv.model.toUpperCase())}`);
237
+ } else {
238
+ await log(`\n${formatAligned('šŸ”„', 'Retry attempt:', `${retryCount}/${maxRetries}`)}`);
239
+ }
240
+
241
+ if (argv.verbose) {
242
+ await log(` Model: ${argv.model}`, { verbose: true });
243
+ await log(` Working directory: ${tempDir}`, { verbose: true });
244
+ await log(` Branch: ${branchName}`, { verbose: true });
245
+ await log(` Prompt length: ${prompt.length} chars`, { verbose: true });
246
+ await log(` System prompt length: ${systemPrompt.length} chars`, { verbose: true });
247
+ if (feedbackLines && feedbackLines.length > 0) {
248
+ await log(` Feedback info included: Yes (${feedbackLines.length} lines)`, { verbose: true });
249
+ } else {
250
+ await log(' Feedback info included: No', { verbose: true });
251
+ }
252
+ }
253
+
254
+ // Take resource snapshot before execution
255
+ const resourcesBefore = await getResourceSnapshot();
256
+ await log('šŸ“ˆ System resources before execution:', { verbose: true });
257
+ await log(` Memory: ${resourcesBefore.memory.split('\n')[1]}`, { verbose: true });
258
+ await log(` Load: ${resourcesBefore.load}`, { verbose: true });
259
+
260
+ // Build Codex command
261
+ let execCommand;
262
+
263
+ // Map model alias to full ID
264
+ const mappedModel = mapModelToId(argv.model);
265
+
266
+ // Build codex command arguments
267
+ // Codex uses exec mode for non-interactive execution
268
+ // --json provides structured output
269
+ // --full-auto enables automatic execution with workspace-write sandbox
270
+ let codexArgs = `exec --model ${mappedModel} --json --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox`;
271
+
272
+ if (argv.resume) {
273
+ // Codex supports resuming sessions
274
+ await log(`šŸ”„ Resuming from session: ${argv.resume}`);
275
+ codexArgs = `exec resume ${argv.resume} --json --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox`;
276
+ }
277
+
278
+ // For Codex, we combine system and user prompts into a single message
279
+ // Codex doesn't have separate system prompt support in CLI mode
280
+ const combinedPrompt = systemPrompt ? `${systemPrompt}\n\n${prompt}` : prompt;
281
+
282
+ // Write the combined prompt to a file for piping
283
+ // Use OS temporary directory instead of repository workspace to avoid polluting the repo
284
+ const promptFile = path.join(os.tmpdir(), `codex_prompt_${Date.now()}_${process.pid}.txt`);
285
+ await fs.writeFile(promptFile, combinedPrompt);
286
+
287
+ // Build the full command - pipe the prompt file to codex
288
+ const fullCommand = `(cd "${tempDir}" && cat "${promptFile}" | ${codexPath} ${codexArgs})`;
289
+
290
+ await log(`\n${formatAligned('šŸ“', 'Raw command:', '')}`);
291
+ await log(`${fullCommand}`);
292
+ await log('');
293
+
294
+ try {
295
+ // Pipe the prompt file to codex via stdin
296
+ if (argv.resume) {
297
+ execCommand = $({
298
+ cwd: tempDir,
299
+ mirror: false
300
+ })`cat ${promptFile} | ${codexPath} exec resume ${argv.resume} --json --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox`;
301
+ } else {
302
+ execCommand = $({
303
+ cwd: tempDir,
304
+ mirror: false
305
+ })`cat ${promptFile} | ${codexPath} exec --model ${mappedModel} --json --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox`;
306
+ }
307
+
308
+ await log(`${formatAligned('šŸ“‹', 'Command details:', '')}`);
309
+ await log(formatAligned('šŸ“‚', 'Working directory:', tempDir, 2));
310
+ await log(formatAligned('🌿', 'Branch:', branchName, 2));
311
+ await log(formatAligned('šŸ¤–', 'Model:', `Codex ${argv.model.toUpperCase()}`, 2));
312
+ if (argv.fork && forkedRepo) {
313
+ await log(formatAligned('šŸ“', 'Fork:', forkedRepo, 2));
314
+ }
315
+
316
+ await log(`\n${formatAligned('ā–¶ļø', 'Streaming output:', '')}\n`);
317
+
318
+ let exitCode = 0;
319
+ let sessionId = null;
320
+ let limitReached = false;
321
+ let limitResetTime = null;
322
+ let lastMessage = '';
323
+ let authError = false;
324
+
325
+ for await (const chunk of execCommand.stream()) {
326
+ if (chunk.type === 'stdout') {
327
+ const output = chunk.data.toString();
328
+ await log(output);
329
+ lastMessage = output;
330
+
331
+ // Try to parse JSON output to extract session info
332
+ // Codex CLI uses thread_id instead of session_id
333
+ try {
334
+ const lines = output.split('\n');
335
+ for (const line of lines) {
336
+ if (!line.trim()) continue;
337
+ const data = JSON.parse(line);
338
+ // Check for both thread_id (codex) and session_id (legacy)
339
+ if ((data.thread_id || data.session_id) && !sessionId) {
340
+ sessionId = data.thread_id || data.session_id;
341
+ await log(`šŸ“Œ Session ID: ${sessionId}`);
342
+ }
343
+
344
+ // Check for authentication errors (401 Unauthorized)
345
+ // These should never be retried as they indicate missing/invalid credentials
346
+ if (data.type === 'error' && data.message &&
347
+ (data.message.includes('401 Unauthorized') ||
348
+ data.message.includes('401') ||
349
+ data.message.includes('Unauthorized'))) {
350
+ authError = true;
351
+ await log('\nāŒ Authentication error detected: 401 Unauthorized', { level: 'error' });
352
+ await log(' This error cannot be resolved by retrying.', { level: 'error' });
353
+ await log(' šŸ’” Please run: codex login', { level: 'error' });
354
+ }
355
+
356
+ // Also check turn.failed events for auth errors
357
+ if (data.type === 'turn.failed' && data.error && data.error.message &&
358
+ (data.error.message.includes('401 Unauthorized') ||
359
+ data.error.message.includes('401') ||
360
+ data.error.message.includes('Unauthorized'))) {
361
+ authError = true;
362
+ await log('\nāŒ Authentication error detected in turn.failed event', { level: 'error' });
363
+ await log(' This error cannot be resolved by retrying.', { level: 'error' });
364
+ await log(' šŸ’” Please run: codex login', { level: 'error' });
365
+ }
366
+ }
367
+ } catch {
368
+ // Not JSON, continue
369
+ }
370
+ }
371
+
372
+ if (chunk.type === 'stderr') {
373
+ const errorOutput = chunk.data.toString();
374
+ if (errorOutput) {
375
+ await log(errorOutput, { stream: 'stderr' });
376
+ }
377
+ } else if (chunk.type === 'exit') {
378
+ exitCode = chunk.code;
379
+ }
380
+ }
381
+
382
+ // Check for authentication errors first - these should never be retried
383
+ if (authError) {
384
+ const resourcesAfter = await getResourceSnapshot();
385
+ await log('\nšŸ“ˆ System resources after execution:', { verbose: true });
386
+ await log(` Memory: ${resourcesAfter.memory.split('\n')[1]}`, { verbose: true });
387
+ await log(` Load: ${resourcesAfter.load}`, { verbose: true });
388
+
389
+ // Throw an error to stop retries and propagate the auth failure
390
+ const error = new Error('Codex authentication failed - 401 Unauthorized. Please run: codex login');
391
+ error.isAuthError = true;
392
+ throw error;
393
+ }
394
+
395
+ if (exitCode !== 0) {
396
+ // Check for usage limit errors first (more specific)
397
+ const limitInfo = detectUsageLimit(lastMessage);
398
+ if (limitInfo.isUsageLimit) {
399
+ limitReached = true;
400
+ limitResetTime = limitInfo.resetTime;
401
+
402
+ // Format and display user-friendly message
403
+ const messageLines = formatUsageLimitMessage({
404
+ tool: 'Codex',
405
+ resetTime: limitInfo.resetTime,
406
+ sessionId,
407
+ resumeCommand: sessionId ? `${process.argv[0]} ${process.argv[1]} ${argv.url} --resume ${sessionId}` : null
408
+ });
409
+
410
+ for (const line of messageLines) {
411
+ await log(line, { level: 'warning' });
412
+ }
413
+ } else {
414
+ await log(`\n\nāŒ Codex command failed with exit code ${exitCode}`, { level: 'error' });
415
+ }
416
+
417
+ const resourcesAfter = await getResourceSnapshot();
418
+ await log('\nšŸ“ˆ System resources after execution:', { verbose: true });
419
+ await log(` Memory: ${resourcesAfter.memory.split('\n')[1]}`, { verbose: true });
420
+ await log(` Load: ${resourcesAfter.load}`, { verbose: true });
421
+
422
+ return {
423
+ success: false,
424
+ sessionId,
425
+ limitReached,
426
+ limitResetTime
427
+ };
428
+ }
429
+
430
+ await log('\n\nāœ… Codex command completed');
431
+
432
+ return {
433
+ success: true,
434
+ sessionId,
435
+ limitReached,
436
+ limitResetTime
437
+ };
438
+ } catch (error) {
439
+ // Don't report auth errors to Sentry as they are user configuration issues
440
+ if (!error.isAuthError) {
441
+ reportError(error, {
442
+ context: 'execute_codex',
443
+ command: params.command,
444
+ codexPath: params.codexPath,
445
+ operation: 'run_codex_command'
446
+ });
447
+ }
448
+
449
+ await log(`\n\nāŒ Error executing Codex command: ${error.message}`, { level: 'error' });
450
+
451
+ // Re-throw auth errors to stop any outer retry loops
452
+ if (error.isAuthError) {
453
+ throw error;
454
+ }
455
+
456
+ return {
457
+ success: false,
458
+ sessionId: null,
459
+ limitReached: false,
460
+ limitResetTime: null
461
+ };
462
+ }
463
+ };
464
+
465
+ // Start the execution with retry logic
466
+ return await executeWithRetry();
467
+ };
468
+
469
+ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchName, $, log, autoCommit = false, autoRestartEnabled = true) => {
470
+ // Similar to Claude and OpenCode version, check for uncommitted changes
471
+ await log('\nšŸ” Checking for uncommitted changes...');
472
+ try {
473
+ const gitStatusResult = await $({ cwd: tempDir })`git status --porcelain 2>&1`;
474
+
475
+ if (gitStatusResult.code === 0) {
476
+ const statusOutput = gitStatusResult.stdout.toString().trim();
477
+
478
+ if (statusOutput) {
479
+ await log('šŸ“ Found uncommitted changes');
480
+ await log('Changes:');
481
+ for (const line of statusOutput.split('\n')) {
482
+ await log(` ${line}`);
483
+ }
484
+
485
+ if (autoCommit) {
486
+ await log('šŸ’¾ Auto-committing changes (--auto-commit-uncommitted-changes is enabled)...');
487
+
488
+ const addResult = await $({ cwd: tempDir })`git add -A`;
489
+ if (addResult.code === 0) {
490
+ const commitMessage = 'Auto-commit: Changes made by Codex during problem-solving session';
491
+ const commitResult = await $({ cwd: tempDir })`git commit -m ${commitMessage}`;
492
+
493
+ if (commitResult.code === 0) {
494
+ await log('āœ… Changes committed successfully');
495
+
496
+ const pushResult = await $({ cwd: tempDir })`git push origin ${branchName}`;
497
+
498
+ if (pushResult.code === 0) {
499
+ await log('āœ… Changes pushed successfully');
500
+ } else {
501
+ await log(`āš ļø Warning: Could not push changes: ${pushResult.stderr?.toString().trim()}`, { level: 'warning' });
502
+ }
503
+ } else {
504
+ await log(`āš ļø Warning: Could not commit changes: ${commitResult.stderr?.toString().trim()}`, { level: 'warning' });
505
+ }
506
+ } else {
507
+ await log(`āš ļø Warning: Could not stage changes: ${addResult.stderr?.toString().trim()}`, { level: 'warning' });
508
+ }
509
+ return false;
510
+ } else if (autoRestartEnabled) {
511
+ await log('');
512
+ await log('āš ļø IMPORTANT: Uncommitted changes detected!');
513
+ await log(' Codex made changes that were not committed.');
514
+ await log('');
515
+ await log('šŸ”„ AUTO-RESTART: Restarting Codex to handle uncommitted changes...');
516
+ await log(' Codex will review the changes and decide what to commit.');
517
+ await log('');
518
+ return true;
519
+ } else {
520
+ await log('');
521
+ await log('āš ļø Uncommitted changes detected but auto-restart is disabled.');
522
+ await log(' Use --auto-restart-on-uncommitted-changes to enable or commit manually.');
523
+ await log('');
524
+ return false;
525
+ }
526
+ } else {
527
+ await log('āœ… No uncommitted changes found');
528
+ return false;
529
+ }
530
+ } else {
531
+ await log(`āš ļø Warning: Could not check git status: ${gitStatusResult.stderr?.toString().trim()}`, { level: 'warning' });
532
+ return false;
533
+ }
534
+ } catch (gitError) {
535
+ reportError(gitError, {
536
+ context: 'check_uncommitted_changes_codex',
537
+ tempDir,
538
+ operation: 'git_status_check'
539
+ });
540
+ await log(`āš ļø Warning: Error checking for uncommitted changes: ${gitError.message}`, { level: 'warning' });
541
+ return false;
542
+ }
543
+ };
544
+
545
+ // Export all functions as default object too
546
+ export default {
547
+ validateCodexConnection,
548
+ handleCodexRuntimeSwitch,
549
+ executeCodex,
550
+ executeCodexCommand,
551
+ checkForUncommittedChanges
552
+ };