@link-assistant/hive-mind 0.46.1 → 0.47.1

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 -15
  2. package/README.md +42 -8
  3. package/package.json +16 -3
  4. package/src/agent.lib.mjs +49 -70
  5. package/src/agent.prompts.lib.mjs +6 -20
  6. package/src/buildUserMention.lib.mjs +4 -17
  7. package/src/claude-limits.lib.mjs +15 -15
  8. package/src/claude.lib.mjs +617 -626
  9. package/src/claude.prompts.lib.mjs +7 -22
  10. package/src/codex.lib.mjs +39 -71
  11. package/src/codex.prompts.lib.mjs +6 -20
  12. package/src/config.lib.mjs +3 -16
  13. package/src/contributing-guidelines.lib.mjs +5 -18
  14. package/src/exit-handler.lib.mjs +4 -4
  15. package/src/git.lib.mjs +7 -7
  16. package/src/github-issue-creator.lib.mjs +17 -17
  17. package/src/github-linking.lib.mjs +8 -33
  18. package/src/github.batch.lib.mjs +20 -16
  19. package/src/github.graphql.lib.mjs +18 -18
  20. package/src/github.lib.mjs +89 -91
  21. package/src/hive.config.lib.mjs +50 -50
  22. package/src/hive.mjs +1293 -1296
  23. package/src/instrument.mjs +7 -11
  24. package/src/interactive-mode.lib.mjs +112 -138
  25. package/src/lenv-reader.lib.mjs +1 -6
  26. package/src/lib.mjs +36 -45
  27. package/src/lino.lib.mjs +2 -2
  28. package/src/local-ci-checks.lib.mjs +15 -14
  29. package/src/memory-check.mjs +52 -60
  30. package/src/model-mapping.lib.mjs +25 -32
  31. package/src/model-validation.lib.mjs +31 -31
  32. package/src/opencode.lib.mjs +37 -62
  33. package/src/opencode.prompts.lib.mjs +7 -21
  34. package/src/protect-branch.mjs +14 -15
  35. package/src/review.mjs +28 -27
  36. package/src/reviewers-hive.mjs +64 -69
  37. package/src/sentry.lib.mjs +13 -10
  38. package/src/solve.auto-continue.lib.mjs +48 -38
  39. package/src/solve.auto-pr.lib.mjs +111 -69
  40. package/src/solve.branch-errors.lib.mjs +17 -46
  41. package/src/solve.branch.lib.mjs +16 -23
  42. package/src/solve.config.lib.mjs +263 -261
  43. package/src/solve.error-handlers.lib.mjs +21 -79
  44. package/src/solve.execution.lib.mjs +10 -18
  45. package/src/solve.feedback.lib.mjs +25 -46
  46. package/src/solve.mjs +59 -60
  47. package/src/solve.preparation.lib.mjs +10 -36
  48. package/src/solve.repo-setup.lib.mjs +4 -19
  49. package/src/solve.repository.lib.mjs +37 -37
  50. package/src/solve.results.lib.mjs +32 -46
  51. package/src/solve.session.lib.mjs +7 -22
  52. package/src/solve.validation.lib.mjs +19 -17
  53. package/src/solve.watch.lib.mjs +20 -33
  54. package/src/start-screen.mjs +24 -24
  55. package/src/task.mjs +38 -44
  56. package/src/telegram-bot.mjs +125 -121
  57. package/src/telegram-top-command.lib.mjs +32 -48
  58. package/src/usage-limit.lib.mjs +9 -13
  59. package/src/version-info.lib.mjs +1 -1
  60. package/src/version.lib.mjs +1 -1
  61. package/src/youtrack/solve.youtrack.lib.mjs +3 -8
  62. package/src/youtrack/youtrack-sync.mjs +8 -14
  63. package/src/youtrack/youtrack.lib.mjs +26 -28
@@ -24,10 +24,7 @@ const lib = await import('./lib.mjs');
24
24
  const sentryLib = await import('./sentry.lib.mjs');
25
25
  const { reportError } = sentryLib;
26
26
 
27
- const {
28
- log,
29
- formatAligned
30
- } = lib;
27
+ const { log, formatAligned } = lib;
31
28
 
32
29
  // Import exit handler
33
30
  import { safeExit } from './exit-handler.lib.mjs';
@@ -58,14 +55,14 @@ export const getRootRepository = async (owner, repo) => {
58
55
  context: 'get_root_repository',
59
56
  owner,
60
57
  repo,
61
- operation: 'determine_fork_root'
58
+ operation: 'determine_fork_root',
62
59
  });
63
60
  return null;
64
61
  }
65
62
  };
66
63
 
67
64
  // Check if current user has a fork of the given root repository
68
- export const checkExistingForkOfRoot = async (rootRepo) => {
65
+ export const checkExistingForkOfRoot = async rootRepo => {
69
66
  try {
70
67
  const userResult = await $`gh api user --jq .login`;
71
68
  if (userResult.code !== 0) {
@@ -79,7 +76,11 @@ export const checkExistingForkOfRoot = async (rootRepo) => {
79
76
  return null;
80
77
  }
81
78
 
82
- const forks = forksResult.stdout.toString().trim().split('\n').filter(f => f);
79
+ const forks = forksResult.stdout
80
+ .toString()
81
+ .trim()
82
+ .split('\n')
83
+ .filter(f => f);
83
84
 
84
85
  if (forks.length > 0) {
85
86
  return forks[0];
@@ -90,14 +91,14 @@ export const checkExistingForkOfRoot = async (rootRepo) => {
90
91
  reportError(error, {
91
92
  context: 'check_existing_fork_of_root',
92
93
  rootRepo,
93
- operation: 'search_user_forks'
94
+ operation: 'search_user_forks',
94
95
  });
95
96
  return null;
96
97
  }
97
98
  };
98
99
 
99
100
  // Create or find temporary directory for cloning the repository
100
- export const setupTempDirectory = async (argv) => {
101
+ export const setupTempDirectory = async argv => {
101
102
  let tempDir;
102
103
  let isResuming = argv.resume;
103
104
 
@@ -119,7 +120,7 @@ export const setupTempDirectory = async (argv) => {
119
120
  reportError(err, {
120
121
  context: 'resume_session_lookup',
121
122
  sessionId: argv.resume,
122
- operation: 'find_session_log'
123
+ operation: 'find_session_log',
123
124
  });
124
125
  await log(`Warning: Session log for ${argv.resume} not found, but continuing with resume attempt`);
125
126
  tempDir = path.join(os.tmpdir(), `gh-issue-solver-resume-${argv.resume}-${Date.now()}`);
@@ -169,8 +170,7 @@ const tryInitializeEmptyRepository = async (owner, repo) => {
169
170
  } else {
170
171
  const errorOutput = createResult.stdout.toString() + createResult.stderr.toString();
171
172
  // Check if it's a permission error
172
- if (errorOutput.includes('403') || errorOutput.includes('Forbidden') ||
173
- errorOutput.includes('not have permission') || errorOutput.includes('Resource not accessible')) {
173
+ if (errorOutput.includes('403') || errorOutput.includes('Forbidden') || errorOutput.includes('not have permission') || errorOutput.includes('Resource not accessible')) {
174
174
  await log(`${formatAligned('❌', 'No access:', 'You do not have write access to this repository')}`);
175
175
  return false;
176
176
  } else {
@@ -184,7 +184,7 @@ const tryInitializeEmptyRepository = async (owner, repo) => {
184
184
  context: 'initialize_empty_repository',
185
185
  owner,
186
186
  repo,
187
- operation: 'create_readme'
187
+ operation: 'create_readme',
188
188
  });
189
189
  await log(`${formatAligned('❌', 'Error:', 'Failed to initialize repository')}`);
190
190
  return false;
@@ -225,7 +225,7 @@ export const setupRepository = async (argv, owner, repo, forkOwner = null, issue
225
225
 
226
226
  if (existingForkOwner === currentUser) {
227
227
  const targetRepo = `${owner}/${repo}`;
228
- const targetIsRoot = (targetRepo === rootRepo);
228
+ const targetIsRoot = targetRepo === rootRepo;
229
229
 
230
230
  if (!targetIsRoot) {
231
231
  await log('');
@@ -234,7 +234,7 @@ export const setupRepository = async (argv, owner, repo, forkOwner = null, issue
234
234
  await log(' 🔍 What happened:');
235
235
  await log(` You are trying to fork ${targetRepo}`);
236
236
  await log(` But you already have a fork of ${rootRepo}: ${existingFork}`);
237
- await log(' GitHub doesn\'t allow multiple forks of the same root repository');
237
+ await log(" GitHub doesn't allow multiple forks of the same root repository");
238
238
  await log('');
239
239
  await log(' 📦 Root repository analysis:');
240
240
  await log(` • Target repository: ${targetRepo}`);
@@ -330,8 +330,7 @@ export const setupRepository = async (argv, owner, repo, forkOwner = null, issue
330
330
  }
331
331
 
332
332
  // Always capture output to parse actual fork name
333
- const forkOutput = (forkResult.stderr ? forkResult.stderr.toString() : '') +
334
- (forkResult.stdout ? forkResult.stdout.toString() : '');
333
+ const forkOutput = (forkResult.stderr ? forkResult.stderr.toString() : '') + (forkResult.stdout ? forkResult.stdout.toString() : '');
335
334
 
336
335
  // Parse actual fork name from output (e.g., "konard/netkeep80-jsonRVM already exists")
337
336
  // GitHub may create forks with modified names to avoid conflicts
@@ -354,10 +353,7 @@ export const setupRepository = async (argv, owner, repo, forkOwner = null, issue
354
353
  break;
355
354
  } else {
356
355
  // Fork creation failed - check if it's because fork already exists
357
- if (forkOutput.includes('already exists') ||
358
- forkOutput.includes('Name already exists') ||
359
- forkOutput.includes('fork of') ||
360
- forkOutput.includes('HTTP 422')) {
356
+ if (forkOutput.includes('already exists') || forkOutput.includes('Name already exists') || forkOutput.includes('fork of') || forkOutput.includes('HTTP 422')) {
361
357
  // Fork already exists (likely created by another concurrent worker)
362
358
  await log(`${formatAligned('ℹ️', 'Fork exists:', actualForkName)}`);
363
359
  forkExists = true;
@@ -365,9 +361,7 @@ export const setupRepository = async (argv, owner, repo, forkOwner = null, issue
365
361
  }
366
362
 
367
363
  // Check if it's an empty repository (HTTP 403) - try to auto-fix
368
- if (forkOutput.includes('HTTP 403') &&
369
- (forkOutput.includes('Empty repositories cannot be forked') ||
370
- forkOutput.includes('contains no Git content'))) {
364
+ if (forkOutput.includes('HTTP 403') && (forkOutput.includes('Empty repositories cannot be forked') || forkOutput.includes('contains no Git content'))) {
371
365
  // Empty repository detected - try to initialize it
372
366
  await log('');
373
367
  await log(`${formatAligned('⚠️', 'EMPTY REPOSITORY', 'detected')}`, { level: 'warn' });
@@ -388,11 +382,13 @@ export const setupRepository = async (argv, owner, repo, forkOwner = null, issue
388
382
  } else {
389
383
  // Failed to initialize - provide helpful suggestions
390
384
  await log('');
391
- await log(`${formatAligned('❌', 'Cannot proceed:', 'Unable to initialize empty repository')}`, { level: 'error' });
385
+ await log(`${formatAligned('❌', 'Cannot proceed:', 'Unable to initialize empty repository')}`, {
386
+ level: 'error',
387
+ });
392
388
  await log('');
393
389
  await log(' 🔍 What happened:');
394
390
  await log(` The repository ${owner}/${repo} is empty and cannot be forked.`);
395
- await log(' GitHub doesn\'t allow forking repositories with no content.');
391
+ await log(" GitHub doesn't allow forking repositories with no content.");
396
392
  await log(' Auto-fix failed: You need write access to initialize the repository.');
397
393
  await log('');
398
394
  await log(' 💡 How to fix:');
@@ -462,7 +458,7 @@ Thank you!`;
462
458
  // Fork still doesn't exist and creation failed
463
459
  if (attempt < maxForkRetries) {
464
460
  const delay = baseDelay * Math.pow(2, attempt - 1); // Exponential backoff
465
- await log(`${formatAligned('⏳', 'Retry:', `Attempt ${attempt}/${maxForkRetries} failed, waiting ${delay/1000}s before retry...`)}`);
461
+ await log(`${formatAligned('⏳', 'Retry:', `Attempt ${attempt}/${maxForkRetries} failed, waiting ${delay / 1000}s before retry...`)}`);
466
462
  await log(` Error: ${forkOutput.split('\n')[0]}`); // Show first line of error
467
463
  await new Promise(resolve => setTimeout(resolve, delay));
468
464
  } else {
@@ -485,7 +481,7 @@ Thank you!`;
485
481
  for (let attempt = 1; attempt <= maxVerifyRetries; attempt++) {
486
482
  const delay = baseDelay * Math.pow(2, attempt - 1);
487
483
  if (attempt > 1) {
488
- await log(`${formatAligned('⏳', 'Verifying fork:', `Attempt ${attempt}/${maxVerifyRetries} (waiting ${delay/1000}s)...`)}`);
484
+ await log(`${formatAligned('⏳', 'Verifying fork:', `Attempt ${attempt}/${maxVerifyRetries} (waiting ${delay / 1000}s)...`)}`);
489
485
  await new Promise(resolve => setTimeout(resolve, delay));
490
486
  }
491
487
 
@@ -582,7 +578,7 @@ export const cloneRepository = async (repoToClone, tempDir, argv, owner, repo) =
582
578
  }
583
579
  await log('');
584
580
  await log(' 💡 Common causes:');
585
- await log(' • Repository doesn\'t exist or is private');
581
+ await log(" • Repository doesn't exist or is private");
586
582
  await log(' • No GitHub authentication');
587
583
  await log(' • Network connectivity issues');
588
584
  if (argv.fork) {
@@ -688,9 +684,7 @@ export const setupUpstreamAndSync = async (tempDir, forkedRepo, upstreamRemote,
688
684
  } else {
689
685
  // Check if it's a non-fast-forward error (fork has diverged from upstream)
690
686
  const errorMsg = pushResult.stderr ? pushResult.stderr.toString().trim() : '';
691
- const isNonFastForward = errorMsg.includes('non-fast-forward') ||
692
- errorMsg.includes('rejected') ||
693
- errorMsg.includes('tip of your current branch is behind');
687
+ const isNonFastForward = errorMsg.includes('non-fast-forward') || errorMsg.includes('rejected') || errorMsg.includes('tip of your current branch is behind');
694
688
 
695
689
  if (isNonFastForward) {
696
690
  // Fork has diverged from upstream
@@ -716,7 +710,9 @@ export const setupUpstreamAndSync = async (tempDir, forkedRepo, upstreamRemote,
716
710
  // Use --force-with-lease for safer force push
717
711
  // This will only force push if the remote hasn't changed since our last fetch
718
712
  await log(`${formatAligned('🔄', 'Force pushing:', 'Syncing fork with upstream (--force-with-lease)')}`);
719
- const forcePushResult = await $({ cwd: tempDir })`git push --force-with-lease origin ${upstreamDefaultBranch}`;
713
+ const forcePushResult = await $({
714
+ cwd: tempDir,
715
+ })`git push --force-with-lease origin ${upstreamDefaultBranch}`;
720
716
 
721
717
  if (forcePushResult.code === 0) {
722
718
  await log(`${formatAligned('✅', 'Fork synced:', 'Successfully force-pushed to align with upstream')}`);
@@ -724,7 +720,9 @@ export const setupUpstreamAndSync = async (tempDir, forkedRepo, upstreamRemote,
724
720
  } else {
725
721
  // Force push also failed - this is a more serious issue
726
722
  await log('');
727
- await log(`${formatAligned('❌', 'FATAL ERROR:', 'Failed to sync fork with upstream')}`, { level: 'error' });
723
+ await log(`${formatAligned('❌', 'FATAL ERROR:', 'Failed to sync fork with upstream')}`, {
724
+ level: 'error',
725
+ });
728
726
  await log('');
729
727
  await log(' 🔍 What happened:');
730
728
  await log(` Fork branch ${upstreamDefaultBranch} has diverged from upstream`);
@@ -862,12 +860,14 @@ export const setupPrForkRemote = async (tempDir, argv, prForkOwner, repo, isCont
862
860
  prForkRepoName = `${owner}-${repo}`;
863
861
  }
864
862
 
865
- await log(`${formatAligned('🔗', 'Setting up pr-fork:', 'Branch exists in another user\'s fork')}`);
863
+ await log(`${formatAligned('🔗', 'Setting up pr-fork:', "Branch exists in another user's fork")}`);
866
864
  await log(`${formatAligned('', 'PR fork owner:', prForkOwner)}`);
867
865
  await log(`${formatAligned('', 'Current user:', currentUser)}`);
868
866
  await log(`${formatAligned('', 'Action:', `Adding ${prForkOwner}/${prForkRepoName} as pr-fork remote`)}`);
869
867
 
870
- const addRemoteResult = await $({ cwd: tempDir })`git remote add pr-fork https://github.com/${prForkOwner}/${prForkRepoName}.git`;
868
+ const addRemoteResult = await $({
869
+ cwd: tempDir,
870
+ })`git remote add pr-fork https://github.com/${prForkOwner}/${prForkRepoName}.git`;
871
871
  if (addRemoteResult.code !== 0) {
872
872
  await log(`${formatAligned('❌', 'Error:', 'Failed to add pr-fork remote')}`);
873
873
  if (addRemoteResult.stderr) {
@@ -945,7 +945,7 @@ export const cleanupTempDirectory = async (tempDir, argv, limitReached) => {
945
945
  reportError(cleanupError, {
946
946
  context: 'cleanup_temp_directory',
947
947
  tempDir,
948
- operation: 'remove_temp_dir'
948
+ operation: 'remove_temp_dir',
949
949
  });
950
950
  await log(' ⚠️ (failed)');
951
951
  }
@@ -18,27 +18,18 @@ const path = (await use('path')).default;
18
18
 
19
19
  // Import shared library functions
20
20
  const lib = await import('./lib.mjs');
21
- const {
22
- log,
23
- getLogFile,
24
- formatAligned
25
- } = lib;
21
+ const { log, getLogFile, formatAligned } = lib;
26
22
 
27
23
  // Import exit handler
28
24
  import { safeExit } from './exit-handler.lib.mjs';
29
25
 
30
26
  // Import GitHub-related functions
31
27
  const githubLib = await import('./github.lib.mjs');
32
- const {
33
- sanitizeLogContent,
34
- attachLogToGitHub
35
- } = githubLib;
28
+ const { sanitizeLogContent, attachLogToGitHub } = githubLib;
36
29
 
37
30
  // Import auto-continue functions
38
31
  const autoContinue = await import('./solve.auto-continue.lib.mjs');
39
- const {
40
- autoContinueWhenLimitResets
41
- } = autoContinue;
32
+ const { autoContinueWhenLimitResets } = autoContinue;
42
33
 
43
34
  // Import error handling functions
44
35
  // const errorHandlers = await import('./solve.error-handlers.lib.mjs'); // Not currently used
@@ -122,24 +113,26 @@ const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
122
113
  const firstCommitLine = branchCommits[0];
123
114
  const [firstCommitHash, firstCommitMessage] = firstCommitLine.split('|');
124
115
 
125
- await log(` First commit on branch: ${firstCommitHash.substring(0, 7)} - "${firstCommitMessage}"`, { verbose: true });
116
+ await log(` First commit on branch: ${firstCommitHash.substring(0, 7)} - "${firstCommitMessage}"`, {
117
+ verbose: true,
118
+ });
126
119
 
127
120
  // Safety check: Verify commit message matches expected pattern
128
- const expectedMessagePatterns = [
129
- /^Initial commit with task details/i,
130
- /^Add CLAUDE\.md/i,
131
- /^CLAUDE\.md/i
132
- ];
121
+ const expectedMessagePatterns = [/^Initial commit with task details/i, /^Add CLAUDE\.md/i, /^CLAUDE\.md/i];
133
122
 
134
123
  const messageMatches = expectedMessagePatterns.some(pattern => pattern.test(firstCommitMessage));
135
124
  if (!messageMatches) {
136
125
  await log(' First commit message does not match expected CLAUDE.md pattern', { verbose: true });
137
- await log(' Expected patterns: "Initial commit with task details...", "Add CLAUDE.md", etc.', { verbose: true });
126
+ await log(' Expected patterns: "Initial commit with task details...", "Add CLAUDE.md", etc.', {
127
+ verbose: true,
128
+ });
138
129
  return null;
139
130
  }
140
131
 
141
132
  // Safety check: Verify the commit ONLY adds CLAUDE.md file (no other files)
142
- const filesChangedResult = await $({ cwd: tempDir })`git diff-tree --no-commit-id --name-only -r ${firstCommitHash} 2>&1`;
133
+ const filesChangedResult = await $({
134
+ cwd: tempDir,
135
+ })`git diff-tree --no-commit-id --name-only -r ${firstCommitHash} 2>&1`;
143
136
  if (filesChangedResult.code !== 0 || !filesChangedResult.stdout) {
144
137
  await log(' Could not get files changed in first commit', { verbose: true });
145
138
  return null;
@@ -157,7 +150,9 @@ const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
157
150
  // CRITICAL SAFETY CHECK: Only allow revert if CLAUDE.md is the ONLY file changed
158
151
  // This prevents Issue #617 where reverting a commit deleted .gitignore, LICENSE, README.md
159
152
  if (filesChanged.length > 1) {
160
- await log(` ⚠️ First commit changes more than just CLAUDE.md (${filesChanged.length} files)`, { verbose: true });
153
+ await log(` ⚠️ First commit changes more than just CLAUDE.md (${filesChanged.length} files)`, {
154
+ verbose: true,
155
+ });
161
156
  await log(` Files: ${filesChanged.join(', ')}`, { verbose: true });
162
157
  await log(' Refusing to revert to prevent data loss (Issue #617 safety)', { verbose: true });
163
158
  return null;
@@ -174,7 +169,7 @@ const detectClaudeMdCommitFromBranch = async (tempDir, branchName) => {
174
169
  context: 'detect_claude_md_commit',
175
170
  tempDir,
176
171
  branchName,
177
- operation: 'detect_commit_from_branch_structure'
172
+ operation: 'detect_commit_from_branch_structure',
178
173
  });
179
174
  await log(` Error detecting CLAUDE.md commit: ${error.message}`, { verbose: true });
180
175
  return null;
@@ -226,7 +221,9 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
226
221
  }
227
222
 
228
223
  // Create a manual revert commit
229
- const commitResult = await $({ cwd: tempDir })`git commit -m "Revert: Remove CLAUDE.md changes from initial commit" 2>&1`;
224
+ const commitResult = await $({
225
+ cwd: tempDir,
226
+ })`git commit -m "Revert: Remove CLAUDE.md changes from initial commit" 2>&1`;
230
227
 
231
228
  if (commitResult.code === 0) {
232
229
  await log(formatAligned('📦', 'Committed:', 'CLAUDE.md revert (manual)'));
@@ -324,7 +321,7 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
324
321
  reportError(e, {
325
322
  context: 'cleanup_claude_file',
326
323
  tempDir,
327
- operation: 'revert_claude_md_commit'
324
+ operation: 'revert_claude_md_commit',
328
325
  });
329
326
  // If revert fails, that's okay - the task is still complete
330
327
  await log(' CLAUDE.md revert failed or not needed', { verbose: true });
@@ -338,7 +335,7 @@ export const showSessionSummary = async (sessionId, limitReached, argv, issueUrl
338
335
  if (sessionId) {
339
336
  await log(`✅ Session ID: ${sessionId}`);
340
337
  // Always use absolute path for log file display
341
- const path = (await use('path'));
338
+ const path = await use('path');
342
339
  const absoluteLogPath = path.resolve(getLogFile());
343
340
  await log(`✅ Complete log file: ${absoluteLogPath}`);
344
341
 
@@ -423,10 +420,7 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
423
420
 
424
421
  // If we created a PR earlier in this session, it would be prNumber
425
422
  // Or if the PR was updated during the session (updatedAt > referenceTime)
426
- const isPrFromSession = (prNumber && pr.number.toString() === prNumber) ||
427
- (prUrl && pr.url === prUrl) ||
428
- new Date(pr.updatedAt) > referenceTime ||
429
- new Date(pr.createdAt) > referenceTime;
423
+ const isPrFromSession = (prNumber && pr.number.toString() === prNumber) || (prUrl && pr.url === prUrl) || new Date(pr.updatedAt) > referenceTime || new Date(pr.createdAt) > referenceTime;
430
424
 
431
425
  if (isPrFromSession) {
432
426
  await log(` ✅ Found pull request #${pr.number}: "${pr.title}"`);
@@ -441,12 +435,7 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
441
435
  // This ensures we only detect actual GitHub-recognized linking keywords
442
436
  // (fixes, closes, resolves and their variants) in proper format
443
437
  // See: https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue
444
- const hasLinkingKeyword = hasGitHubLinkingKeyword(
445
- prBody,
446
- issueNumber,
447
- argv.fork ? owner : null,
448
- argv.fork ? repo : null
449
- );
438
+ const hasLinkingKeyword = hasGitHubLinkingKeyword(prBody, issueNumber, argv.fork ? owner : null, argv.fork ? repo : null);
450
439
 
451
440
  if (!hasLinkingKeyword) {
452
441
  await log(` 📝 Updating PR body to link issue #${issueNumber}...`);
@@ -514,7 +503,7 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
514
503
  anthropicTotalCostUSD,
515
504
  // Pass agent tool pricing data when available
516
505
  publicPricingEstimate,
517
- pricingInfo
506
+ pricingInfo,
518
507
  });
519
508
  }
520
509
 
@@ -552,9 +541,7 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
552
541
  const allComments = JSON.parse(allCommentsResult.stdout.toString().trim() || '[]');
553
542
 
554
543
  // Filter for new comments by current user
555
- const newCommentsByUser = allComments.filter(comment =>
556
- comment.user.login === currentUser && new Date(comment.created_at) > referenceTime
557
- );
544
+ const newCommentsByUser = allComments.filter(comment => comment.user.login === currentUser && new Date(comment.created_at) > referenceTime);
558
545
 
559
546
  if (newCommentsByUser.length > 0) {
560
547
  const lastComment = newCommentsByUser[newCommentsByUser.length - 1];
@@ -578,7 +565,7 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
578
565
  anthropicTotalCostUSD,
579
566
  // Pass agent tool pricing data when available
580
567
  publicPricingEstimate,
581
- pricingInfo
568
+ pricingInfo,
582
569
  });
583
570
  }
584
571
 
@@ -611,12 +598,11 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
611
598
  await safeExit(0, 'Process completed successfully');
612
599
  }
613
600
  return; // Return normally for watch mode
614
-
615
601
  } catch (searchError) {
616
602
  reportError(searchError, {
617
603
  context: 'verify_pr_creation',
618
604
  issueNumber,
619
- operation: 'search_for_pr'
605
+ operation: 'search_for_pr',
620
606
  });
621
607
  await log('\n⚠️ Could not verify results:', searchError.message);
622
608
  await log('\n💡 Check the log file for details:');
@@ -654,7 +640,7 @@ export const handleExecutionError = async (error, shouldAttachLogs, owner, repo,
654
640
  log,
655
641
  sanitizeLogContent,
656
642
  verbose: argv.verbose || false,
657
- errorMessage: cleanErrorMessage(error)
643
+ errorMessage: cleanErrorMessage(error),
658
644
  });
659
645
 
660
646
  if (logUploadSuccess) {
@@ -664,7 +650,7 @@ export const handleExecutionError = async (error, shouldAttachLogs, owner, repo,
664
650
  reportError(attachError, {
665
651
  context: 'attach_success_log',
666
652
  prNumber: global.createdPR?.number,
667
- operation: 'attach_log_to_pr'
653
+ operation: 'attach_log_to_pr',
668
654
  });
669
655
  await log(`⚠️ Could not attach failure log: ${attachError.message}`, { level: 'warning' });
670
656
  }
@@ -685,11 +671,11 @@ export const handleExecutionError = async (error, shouldAttachLogs, owner, repo,
685
671
  reportError(closeError, {
686
672
  context: 'close_success_pr',
687
673
  prNumber: global.createdPR?.number,
688
- operation: 'close_pull_request'
674
+ operation: 'close_pull_request',
689
675
  });
690
676
  await log(`⚠️ Could not close pull request: ${closeError.message}`, { level: 'warning' });
691
677
  }
692
678
  }
693
679
 
694
680
  await safeExit(1, 'Execution error');
695
- };
681
+ };
@@ -3,14 +3,7 @@
3
3
  * Handles starting and ending work sessions, PR status changes, and session comments
4
4
  */
5
5
 
6
- export async function startWorkSession({
7
- isContinueMode,
8
- prNumber,
9
- argv,
10
- log,
11
- formatAligned,
12
- $
13
- }) {
6
+ export async function startWorkSession({ isContinueMode, prNumber, argv, log, formatAligned, $ }) {
14
7
  // Record work start time and convert PR to draft if in continue/watch mode
15
8
  const workStartTime = new Date();
16
9
  if (isContinueMode && prNumber && (argv.watch || argv.autoContinue)) {
@@ -39,7 +32,7 @@ export async function startWorkSession({
39
32
  reportError(error, {
40
33
  context: 'convert_pr_to_draft',
41
34
  prNumber,
42
- operation: 'pr_status_change'
35
+ operation: 'pr_status_change',
43
36
  });
44
37
  await log('Warning: Could not check/convert PR draft status', { level: 'warning' });
45
38
  }
@@ -57,7 +50,7 @@ export async function startWorkSession({
57
50
  reportError(error, {
58
51
  context: 'post_start_comment',
59
52
  prNumber,
60
- operation: 'create_pr_comment'
53
+ operation: 'create_pr_comment',
61
54
  });
62
55
  await log('Warning: Could not post work start comment', { level: 'warning' });
63
56
  }
@@ -66,15 +59,7 @@ export async function startWorkSession({
66
59
  return workStartTime;
67
60
  }
68
61
 
69
- export async function endWorkSession({
70
- isContinueMode,
71
- prNumber,
72
- argv,
73
- log,
74
- formatAligned,
75
- $,
76
- logsAttached = false
77
- }) {
62
+ export async function endWorkSession({ isContinueMode, prNumber, argv, log, formatAligned, $, logsAttached = false }) {
78
63
  // Post end work session comment and convert PR back to ready if in continue mode
79
64
  if (isContinueMode && prNumber && (argv.watch || argv.autoContinue)) {
80
65
  const workEndTime = new Date();
@@ -96,7 +81,7 @@ export async function endWorkSession({
96
81
  reportError(error, {
97
82
  context: 'post_end_comment',
98
83
  prNumber,
99
- operation: 'create_pr_comment'
84
+ operation: 'create_pr_comment',
100
85
  });
101
86
  await log('Warning: Could not post work end comment', { level: 'warning' });
102
87
  }
@@ -127,9 +112,9 @@ export async function endWorkSession({
127
112
  reportError(error, {
128
113
  context: 'convert_pr_to_ready',
129
114
  prNumber,
130
- operation: 'pr_status_change'
115
+ operation: 'pr_status_change',
131
116
  });
132
117
  await log('Warning: Could not convert PR to ready status', { level: 'warning' });
133
118
  }
134
119
  }
135
- }
120
+ }
@@ -21,7 +21,7 @@ const memoryCheck = await import('./memory-check.mjs');
21
21
  const lib = await import('./lib.mjs');
22
22
  const {
23
23
  log,
24
- setLogFile
24
+ setLogFile,
25
25
  // getLogFile - not currently used
26
26
  } = lib;
27
27
 
@@ -29,7 +29,7 @@ const {
29
29
  const githubLib = await import('./github.lib.mjs');
30
30
  const {
31
31
  checkGitHubPermissions,
32
- parseGitHubUrl
32
+ parseGitHubUrl,
33
33
  // isGitHubUrlType - not currently used
34
34
  } = githubLib;
35
35
 
@@ -39,9 +39,7 @@ const claudeLib = await import('./claude.lib.mjs');
39
39
  const sentryLib = await import('./sentry.lib.mjs');
40
40
  const { reportError } = sentryLib;
41
41
 
42
- const {
43
- validateClaudeConnection
44
- } = claudeLib;
42
+ const { validateClaudeConnection } = claudeLib;
45
43
 
46
44
  // Wrapper function for disk space check using imported module
47
45
  const checkDiskSpace = async (minSpaceMB = 500) => {
@@ -56,7 +54,7 @@ const checkMemory = async (minMemoryMB = 256) => {
56
54
  };
57
55
 
58
56
  // Validate GitHub issue or pull request URL format
59
- export const validateGitHubUrl = (issueUrl) => {
57
+ export const validateGitHubUrl = issueUrl => {
60
58
  if (!issueUrl) {
61
59
  return { isValid: false, isIssueUrl: null, isPrUrl: null };
62
60
  }
@@ -100,12 +98,12 @@ export const validateGitHubUrl = (issueUrl) => {
100
98
  normalizedUrl: parsedUrl.normalized,
101
99
  owner: parsedUrl.owner,
102
100
  repo: parsedUrl.repo,
103
- number: parsedUrl.number
101
+ number: parsedUrl.number,
104
102
  };
105
103
  };
106
104
 
107
105
  // Show security warning for attach-logs option
108
- export const showAttachLogsWarning = async (shouldAttachLogs) => {
106
+ export const showAttachLogsWarning = async shouldAttachLogs => {
109
107
  if (!shouldAttachLogs) return;
110
108
 
111
109
  await log('');
@@ -146,7 +144,7 @@ export const initializeLogFile = async (logDir = null) => {
146
144
  } catch (error) {
147
145
  reportError(error, {
148
146
  context: 'create_log_directory',
149
- operation: 'mkdir_log_dir'
147
+ operation: 'mkdir_log_dir',
150
148
  });
151
149
  // If directory doesn't exist, try to create it
152
150
  try {
@@ -155,7 +153,7 @@ export const initializeLogFile = async (logDir = null) => {
155
153
  reportError(mkdirError, {
156
154
  context: 'create_log_directory_fallback',
157
155
  targetDir,
158
- operation: 'mkdir_recursive'
156
+ operation: 'mkdir_recursive',
159
157
  });
160
158
  await log(`⚠️ Unable to create log directory: ${targetDir}`, { level: 'error' });
161
159
  await log(' Falling back to current working directory', { level: 'error' });
@@ -179,7 +177,7 @@ export const initializeLogFile = async (logDir = null) => {
179
177
  };
180
178
 
181
179
  // Validate GitHub URL requirement
182
- export const validateUrlRequirement = async (issueUrl) => {
180
+ export const validateUrlRequirement = async issueUrl => {
183
181
  if (!issueUrl) {
184
182
  await log('❌ GitHub issue URL is required', { level: 'error' });
185
183
  await log(' Usage: solve <github-issue-url> [options]', { level: 'error' });
@@ -263,25 +261,29 @@ export const performSystemChecks = async (minDiskSpace = 500, skipToolConnection
263
261
  return false;
264
262
  }
265
263
  } else {
266
- await log('⏩ Skipping tool connection validation (dry-run mode or skip-tool-connection-check enabled)', { verbose: true });
267
- await log('⏩ Skipping GitHub authentication check (dry-run mode or skip-tool-connection-check enabled)', { verbose: true });
264
+ await log('⏩ Skipping tool connection validation (dry-run mode or skip-tool-connection-check enabled)', {
265
+ verbose: true,
266
+ });
267
+ await log('⏩ Skipping GitHub authentication check (dry-run mode or skip-tool-connection-check enabled)', {
268
+ verbose: true,
269
+ });
268
270
  }
269
271
 
270
272
  return true;
271
273
  };
272
274
 
273
275
  // Parse URL components
274
- export const parseUrlComponents = (issueUrl) => {
276
+ export const parseUrlComponents = issueUrl => {
275
277
  const urlParts = issueUrl.split('/');
276
278
  return {
277
279
  owner: urlParts[3],
278
280
  repo: urlParts[4],
279
- urlNumber: urlParts[6] // Could be issue or PR number
281
+ urlNumber: urlParts[6], // Could be issue or PR number
280
282
  };
281
283
  };
282
284
 
283
285
  // Helper function to parse time string and calculate wait time
284
- export const parseResetTime = (timeStr) => {
286
+ export const parseResetTime = timeStr => {
285
287
  // Normalize and parse time formats like:
286
288
  // "5:30am", "11:45pm", "12:16 PM", "07:05 Am", "5am", "5 AM"
287
289
  const normalized = (timeStr || '').toString().trim();
@@ -308,7 +310,7 @@ export const parseResetTime = (timeStr) => {
308
310
  };
309
311
 
310
312
  // Calculate milliseconds until the next occurrence of the specified time
311
- export const calculateWaitTime = (resetTime) => {
313
+ export const calculateWaitTime = resetTime => {
312
314
  const { hour, minute } = parseResetTime(resetTime);
313
315
 
314
316
  const now = new Date();