@link-assistant/hive-mind 0.46.0 → 0.47.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 +26 -13
  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
@@ -15,10 +15,7 @@ import { log, maskToken, cleanErrorMessage } from './lib.mjs';
15
15
  import { reportError } from './sentry.lib.mjs';
16
16
  import { githubLimits, timeouts } from './config.lib.mjs';
17
17
  // Import batch operations from separate module
18
- import {
19
- batchCheckPullRequestsForIssues as batchCheckPRs,
20
- batchCheckArchivedRepositories as batchCheckArchived
21
- } from './github.batch.lib.mjs';
18
+ import { batchCheckPullRequestsForIssues as batchCheckPRs, batchCheckArchivedRepositories as batchCheckArchived } from './github.batch.lib.mjs';
22
19
 
23
20
  /**
24
21
  * Build cost estimation string for log comments
@@ -34,9 +31,7 @@ const buildCostInfoString = (totalCostUSD, anthropicTotalCostUSD, pricingInfo) =
34
31
  if (pricingInfo.provider) costInfo += `\n- Provider: ${pricingInfo.provider}`;
35
32
  }
36
33
  if (totalCostUSD !== null && totalCostUSD !== undefined) {
37
- costInfo += pricingInfo?.isFreeModel
38
- ? '\n- Public pricing estimate: $0.00 (Free model)'
39
- : `\n- Public pricing estimate: $${totalCostUSD.toFixed(6)} USD`;
34
+ costInfo += pricingInfo?.isFreeModel ? '\n- Public pricing estimate: $0.00 (Free model)' : `\n- Public pricing estimate: $${totalCostUSD.toFixed(6)} USD`;
40
35
  } else {
41
36
  costInfo += '\n- Public pricing estimate: unknown';
42
37
  }
@@ -51,7 +46,7 @@ const buildCostInfoString = (totalCostUSD, anthropicTotalCostUSD, pricingInfo) =
51
46
  costInfo += `\n- Calculated by Anthropic: $${anthropicTotalCostUSD.toFixed(6)} USD`;
52
47
  if (totalCostUSD !== null) {
53
48
  const diff = anthropicTotalCostUSD - totalCostUSD;
54
- const pct = totalCostUSD > 0 ? ((diff / totalCostUSD) * 100) : 0;
49
+ const pct = totalCostUSD > 0 ? (diff / totalCostUSD) * 100 : 0;
55
50
  costInfo += `\n- Difference: $${diff.toFixed(6)} (${pct > 0 ? '+' : ''}${pct.toFixed(2)}%)`;
56
51
  } else {
57
52
  costInfo += '\n- Difference: unknown';
@@ -66,13 +61,18 @@ export const maskGitHubToken = maskToken;
66
61
  // Helper function to get GitHub tokens from local config files
67
62
  export const getGitHubTokensFromFiles = async () => {
68
63
  const tokens = [];
69
-
64
+
70
65
  try {
71
66
  // Check ~/.config/gh/hosts.yml
72
67
  const hostsFile = path.join(os.homedir(), '.config/gh/hosts.yml');
73
- if (await fs.access(hostsFile).then(() => true).catch(() => false)) {
68
+ if (
69
+ await fs
70
+ .access(hostsFile)
71
+ .then(() => true)
72
+ .catch(() => false)
73
+ ) {
74
74
  const hostsContent = await fs.readFile(hostsFile, 'utf8');
75
-
75
+
76
76
  // Look for oauth_token and api_token patterns
77
77
  const oauthMatches = hostsContent.match(/oauth_token:\s*([^\s\n]+)/g);
78
78
  if (oauthMatches) {
@@ -83,7 +83,7 @@ export const getGitHubTokensFromFiles = async () => {
83
83
  }
84
84
  }
85
85
  }
86
-
86
+
87
87
  const apiMatches = hostsContent.match(/api_token:\s*([^\s\n]+)/g);
88
88
  if (apiMatches) {
89
89
  for (const match of apiMatches) {
@@ -99,29 +99,26 @@ export const getGitHubTokensFromFiles = async () => {
99
99
  if (global.verboseMode) {
100
100
  reportError(error, {
101
101
  context: 'github_token_file_access',
102
- level: 'debug'
102
+ level: 'debug',
103
103
  });
104
104
  }
105
105
  }
106
-
106
+
107
107
  return tokens;
108
108
  };
109
109
  // Helper function to get GitHub tokens from gh command output
110
110
  export const getGitHubTokensFromCommand = async () => {
111
111
  const { $ } = await use('command-stream');
112
112
  const tokens = [];
113
-
113
+
114
114
  try {
115
115
  // Run gh auth status to get token info
116
116
  const authResult = await $`gh auth status 2>&1`.catch(() => ({ stdout: '', stderr: '' }));
117
117
  const authOutput = authResult.stdout?.toString() + authResult.stderr?.toString() || '';
118
-
118
+
119
119
  // Look for token patterns in the output
120
- const tokenPatterns = [
121
- /(?:token|oauth|api)[:\s]*([a-zA-Z0-9_]{20,})/gi,
122
- /gh[pou]_[a-zA-Z0-9_]{20,}/gi
123
- ];
124
-
120
+ const tokenPatterns = [/(?:token|oauth|api)[:\s]*([a-zA-Z0-9_]{20,})/gi, /gh[pou]_[a-zA-Z0-9_]{20,}/gi];
121
+
125
122
  for (const pattern of tokenPatterns) {
126
123
  const matches = authOutput.match(pattern);
127
124
  if (matches) {
@@ -139,25 +136,25 @@ export const getGitHubTokensFromCommand = async () => {
139
136
  if (global.verboseMode) {
140
137
  reportError(error, {
141
138
  context: 'github_token_gh_auth',
142
- level: 'debug'
139
+ level: 'debug',
143
140
  });
144
141
  }
145
142
  }
146
-
143
+
147
144
  return tokens;
148
145
  };
149
146
  // Helper function to escape code blocks in log content for safe embedding in markdown
150
147
  // When log content is placed inside a markdown code block, any triple backticks (```)
151
148
  // in the content will prematurely close the outer code block, breaking the markdown.
152
149
  // This function escapes those backticks by replacing them with \`\`\` (with backslashes).
153
- export const escapeCodeBlocksInLog = (logContent) => {
150
+ export const escapeCodeBlocksInLog = logContent => {
154
151
  // Replace all occurrences of triple backticks with escaped version
155
152
  // We add backslashes before backticks to prevent them from being
156
153
  // interpreted as markdown code block delimiters
157
154
  return logContent.replace(/```/g, '\\`\\`\\`');
158
155
  };
159
156
  // Helper function to sanitize log content by masking GitHub tokens
160
- export const sanitizeLogContent = async (logContent) => {
157
+ export const sanitizeLogContent = async logContent => {
161
158
  let sanitized = logContent;
162
159
 
163
160
  try {
@@ -179,7 +176,7 @@ export const sanitizeLogContent = async (logContent) => {
179
176
  const tokenPatterns = [
180
177
  /gh[pou]_[a-zA-Z0-9_]{20,}/g,
181
178
  /(?:^|[\s:=])([a-f0-9]{40})(?=[\s\n]|$)/gm, // 40-char hex tokens (like personal access tokens)
182
- /(?:^|[\s:=])([a-zA-Z0-9_]{20,})(?=[\s\n]|$)/gm // General long tokens
179
+ /(?:^|[\s:=])([a-zA-Z0-9_]{20,})(?=[\s\n]|$)/gm, // General long tokens
183
180
  ];
184
181
 
185
182
  for (const pattern of tokenPatterns) {
@@ -192,11 +189,10 @@ export const sanitizeLogContent = async (logContent) => {
192
189
  }
193
190
 
194
191
  await log(` 🔒 Sanitized ${allTokens.length} detected GitHub tokens in log content`, { verbose: true });
195
-
196
192
  } catch (error) {
197
193
  reportError(error, {
198
194
  context: 'sanitize_log_content',
199
- level: 'warning'
195
+ level: 'warning',
200
196
  });
201
197
  await log(` ⚠️ Warning: Could not fully sanitize log content: ${error.message}`, { verbose: true });
202
198
  }
@@ -206,7 +202,7 @@ export const sanitizeLogContent = async (logContent) => {
206
202
  // Helper function to check if a file exists in a GitHub branch
207
203
  export const checkFileInBranch = async (owner, repo, fileName, branchName) => {
208
204
  const { $ } = await use('command-stream');
209
-
205
+
210
206
  try {
211
207
  // Use GitHub CLI to check if file exists in the branch
212
208
  const result = await $`gh api repos/${owner}/${repo}/contents/${fileName}?ref=${branchName}`;
@@ -220,7 +216,7 @@ export const checkFileInBranch = async (owner, repo, fileName, branchName) => {
220
216
  owner,
221
217
  repo,
222
218
  fileName,
223
- branchName
219
+ branchName,
224
220
  });
225
221
  }
226
222
  return false;
@@ -256,14 +252,14 @@ export const checkGitHubPermissions = async () => {
256
252
  warnings.push({
257
253
  scope: 'workflow',
258
254
  issue: 'Cannot push changes to .github/workflows/ directory',
259
- solution: 'Run: gh auth refresh -h github.com -s workflow'
255
+ solution: 'Run: gh auth refresh -h github.com -s workflow',
260
256
  });
261
257
  }
262
258
  if (!scopes.includes('repo')) {
263
259
  warnings.push({
264
260
  scope: 'repo',
265
261
  issue: 'Limited repository access (may not be able to create PRs or push to private repos)',
266
- solution: 'Run: gh auth refresh -h github.com -s repo'
262
+ solution: 'Run: gh auth refresh -h github.com -s repo',
267
263
  });
268
264
  }
269
265
  // Display warnings
@@ -274,15 +270,23 @@ export const checkGitHubPermissions = async () => {
274
270
  await log(` Impact: ${warning.issue}`, { level: 'warning' });
275
271
  await log(` Solution: ${warning.solution}`, { level: 'warning' });
276
272
  }
277
- await log('\n 💡 You can continue, but some operations may fail due to insufficient permissions.', { level: 'warning' });
278
- await log(' 💡 To avoid issues, it\'s recommended to refresh your authentication with the missing scopes.', { level: 'warning' });
273
+ await log('\n 💡 You can continue, but some operations may fail due to insufficient permissions.', {
274
+ level: 'warning',
275
+ });
276
+ await log(" 💡 To avoid issues, it's recommended to refresh your authentication with the missing scopes.", {
277
+ level: 'warning',
278
+ });
279
279
  } else {
280
280
  await log('✅ All required permissions: Available');
281
281
  }
282
282
  return true;
283
283
  } catch (error) {
284
- await log(`⚠️ Warning: Could not check GitHub permissions: ${maskToken(error.message || error.toString())}`, { level: 'warning' });
285
- await log(' Continuing anyway, but some operations may fail if permissions are insufficient', { level: 'warning' });
284
+ await log(`⚠️ Warning: Could not check GitHub permissions: ${maskToken(error.message || error.toString())}`, {
285
+ level: 'warning',
286
+ });
287
+ await log(' Continuing anyway, but some operations may fail if permissions are insufficient', {
288
+ level: 'warning',
289
+ });
286
290
  return true; // Continue despite permission check failure
287
291
  }
288
292
  };
@@ -309,8 +313,7 @@ export const checkRepositoryWritePermission = async (owner, repo, options = {})
309
313
  const permResult = await $`gh api repos/${owner}/${repo} --jq .permissions`;
310
314
  if (permResult.code !== 0) {
311
315
  // API call failed - might be a private repo or network issue
312
- const errorOutput = (permResult.stderr ? permResult.stderr.toString() : '') +
313
- (permResult.stdout ? permResult.stdout.toString() : '');
316
+ const errorOutput = (permResult.stderr ? permResult.stderr.toString() : '') + (permResult.stdout ? permResult.stdout.toString() : '');
314
317
  // If it's a 404, the repo doesn't exist or we don't have read access
315
318
  if (errorOutput.includes('404') || errorOutput.includes('Not Found')) {
316
319
  await log('❌ Repository not found or no access', { level: 'error' });
@@ -318,7 +321,9 @@ export const checkRepositoryWritePermission = async (owner, repo, options = {})
318
321
  return false;
319
322
  }
320
323
  // For other errors, warn but continue (repo might still be accessible)
321
- await log(`⚠️ Warning: Could not check repository permissions: ${cleanErrorMessage(errorOutput)}`, { level: 'warning' });
324
+ await log(`⚠️ Warning: Could not check repository permissions: ${cleanErrorMessage(errorOutput)}`, {
325
+ level: 'warning',
326
+ });
322
327
  return true;
323
328
  }
324
329
  // Parse permissions
@@ -370,7 +375,7 @@ export const checkRepositoryWritePermission = async (owner, repo, options = {})
370
375
  context: 'check_repository_write_permission',
371
376
  owner,
372
377
  repo,
373
- operation: 'verify_write_access'
378
+ operation: 'verify_write_access',
374
379
  });
375
380
  // On unexpected errors, warn but allow to continue (better than blocking)
376
381
  await log(`⚠️ Warning: Error checking repository permissions: ${cleanErrorMessage(error)}`, { level: 'warning' });
@@ -393,9 +398,10 @@ export const checkMaintainerCanModifyPR = async (owner, repo, prNumber) => {
393
398
  // Use GitHub API to check PR details including maintainer_can_modify
394
399
  const prResult = await $`gh api repos/${owner}/${repo}/pulls/${prNumber} --jq '{maintainer_can_modify: .maintainer_can_modify, head: .head}'`;
395
400
  if (prResult.code !== 0) {
396
- const errorOutput = (prResult.stderr ? prResult.stderr.toString() : '') +
397
- (prResult.stdout ? prResult.stdout.toString() : '');
398
- await log(`⚠️ Warning: Could not check maintainer_can_modify: ${cleanErrorMessage(errorOutput)}`, { level: 'warning' });
401
+ const errorOutput = (prResult.stderr ? prResult.stderr.toString() : '') + (prResult.stdout ? prResult.stdout.toString() : '');
402
+ await log(`⚠️ Warning: Could not check maintainer_can_modify: ${cleanErrorMessage(errorOutput)}`, {
403
+ level: 'warning',
404
+ });
399
405
  return { canModify: false, forkOwner: null, forkRepo: null };
400
406
  }
401
407
  // Parse PR data
@@ -409,7 +415,9 @@ export const checkMaintainerCanModifyPR = async (owner, repo, prNumber) => {
409
415
  await log(` Fork: ${forkOwner}/${forkRepo}`, { verbose: true });
410
416
  }
411
417
  } else {
412
- await log('ℹ️ Maintainer can modify: NO (contributor has not enabled "Allow edits by maintainers")', { verbose: true });
418
+ await log('ℹ️ Maintainer can modify: NO (contributor has not enabled "Allow edits by maintainers")', {
419
+ verbose: true,
420
+ });
413
421
  }
414
422
  return { canModify, forkOwner, forkRepo };
415
423
  } catch (error) {
@@ -418,7 +426,7 @@ export const checkMaintainerCanModifyPR = async (owner, repo, prNumber) => {
418
426
  owner,
419
427
  repo,
420
428
  prNumber,
421
- operation: 'check_maintainer_modify_permission'
429
+ operation: 'check_maintainer_modify_permission',
422
430
  });
423
431
  await log(`⚠️ Warning: Error checking maintainer_can_modify: ${cleanErrorMessage(error)}`, { level: 'warning' });
424
432
  return { canModify: false, forkOwner: null, forkRepo: null };
@@ -448,8 +456,7 @@ Thank you! 🙏`;
448
456
  await log('✅ Comment posted successfully', { verbose: true });
449
457
  return true;
450
458
  } else {
451
- const errorOutput = (commentResult.stderr ? commentResult.stderr.toString() : '') +
452
- (commentResult.stdout ? commentResult.stdout.toString() : '');
459
+ const errorOutput = (commentResult.stderr ? commentResult.stderr.toString() : '') + (commentResult.stdout ? commentResult.stdout.toString() : '');
453
460
  await log(`⚠️ Warning: Failed to post comment: ${cleanErrorMessage(errorOutput)}`, { level: 'warning' });
454
461
  return false;
455
462
  }
@@ -459,7 +466,7 @@ Thank you! 🙏`;
459
466
  owner,
460
467
  repo,
461
468
  prNumber,
462
- operation: 'post_comment_request_access'
469
+ operation: 'post_comment_request_access',
463
470
  });
464
471
  await log(`⚠️ Warning: Error posting comment: ${cleanErrorMessage(error)}`, { level: 'warning' });
465
472
  return false;
@@ -508,7 +515,7 @@ export async function attachLogToGitHub(options) {
508
515
  resumeCommand = null,
509
516
  // New parameters for agent tool pricing support
510
517
  publicPricingEstimate = null,
511
- pricingInfo = null
518
+ pricingInfo = null,
512
519
  } = options;
513
520
  const targetName = targetType === 'pr' ? 'Pull Request' : 'Issue';
514
521
  const ghCommand = targetType === 'pr' ? 'pr' : 'issue';
@@ -660,7 +667,7 @@ ${logContent}
660
667
  context: 'check_repo_visibility',
661
668
  level: 'warning',
662
669
  owner,
663
- repo
670
+ repo,
664
671
  });
665
672
  // Default to public if we can't determine visibility
666
673
  await log(' ⚠️ Could not determine repository visibility, defaulting to public gist', { verbose: true });
@@ -670,9 +677,7 @@ ${logContent}
670
677
  const tempLogFile = `/tmp/solution-draft-log-${targetType}-${Date.now()}.txt`;
671
678
  // Use the original sanitized content (before escaping) for gist since it's a text file
672
679
  await fs.writeFile(tempLogFile, await sanitizeLogContent(rawLogContent));
673
- const gistCommand = isPublicRepo
674
- ? `gh gist create "${tempLogFile}" --public --desc "Solution draft log for https://github.com/${owner}/${repo}/${targetType === 'pr' ? 'pull' : 'issues'}/${targetNumber}" --filename "solution-draft-log.txt"`
675
- : `gh gist create "${tempLogFile}" --desc "Solution draft log for https://github.com/${owner}/${repo}/${targetType === 'pr' ? 'pull' : 'issues'}/${targetNumber}" --filename "solution-draft-log.txt"`;
680
+ const gistCommand = isPublicRepo ? `gh gist create "${tempLogFile}" --public --desc "Solution draft log for https://github.com/${owner}/${repo}/${targetType === 'pr' ? 'pull' : 'issues'}/${targetNumber}" --filename "solution-draft-log.txt"` : `gh gist create "${tempLogFile}" --desc "Solution draft log for https://github.com/${owner}/${repo}/${targetType === 'pr' ? 'pull' : 'issues'}/${targetNumber}" --filename "solution-draft-log.txt"`;
676
681
  if (verbose) {
677
682
  await log(` 🔐 Creating ${isPublicRepo ? 'public' : 'private'} gist...`, { verbose: true });
678
683
  }
@@ -785,7 +790,7 @@ This log file contains the complete execution trace of the AI ${targetType === '
785
790
  }
786
791
  } else {
787
792
  await log(` ❌ Failed to create gist: ${gistResult.stderr ? gistResult.stderr.toString().trim() : 'unknown error'}`);
788
-
793
+
789
794
  // Fallback to truncated comment
790
795
  await log(' 🔄 Falling back to truncated comment...');
791
796
  return await attachTruncatedLog(options);
@@ -793,7 +798,7 @@ This log file contains the complete execution trace of the AI ${targetType === '
793
798
  } catch (gistError) {
794
799
  reportError(gistError, {
795
800
  context: 'create_gist',
796
- level: 'error'
801
+ level: 'error',
797
802
  });
798
803
  await log(` ❌ Error creating gist: ${gistError.message}`);
799
804
  // Try regular comment as last resort
@@ -827,7 +832,7 @@ async function attachTruncatedLog(options) {
827
832
  const GITHUB_COMMENT_LIMIT = 65536;
828
833
  const maxContentLength = GITHUB_COMMENT_LIMIT - 500;
829
834
  const truncatedContent = logContent.substring(0, maxContentLength) + '\n\n[... Log truncated due to length ...]';
830
-
835
+
831
836
  const truncatedComment = `## 🤖 Solution Draft Log (Truncated)
832
837
  This log file contains the complete execution trace of the AI ${targetType === 'pr' ? 'solution draft' : 'analysis'} process.
833
838
  ⚠️ **Log was truncated** due to GitHub comment size limits.
@@ -841,11 +846,11 @@ ${truncatedContent}
841
846
  *Now working session is ended, feel free to review and add any feedback on the solution draft.*`;
842
847
  const tempFile = `/tmp/log-truncated-comment-${targetType}-${Date.now()}.md`;
843
848
  await fs.writeFile(tempFile, truncatedComment);
844
-
849
+
845
850
  const result = await $`gh ${ghCommand} comment ${targetNumber} --repo ${owner}/${repo} --body-file "${tempFile}"`;
846
-
851
+
847
852
  await fs.unlink(tempFile).catch(() => {});
848
-
853
+
849
854
  if (result.code === 0) {
850
855
  await log(` ✅ Truncated solution draft log uploaded to ${targetName}`);
851
856
  await log(` 📊 Log size: ${Math.round(logStats.size / 1024)}KB (truncated)`);
@@ -861,18 +866,18 @@ ${truncatedContent}
861
866
  async function attachRegularComment(options, logComment) {
862
867
  const fs = (await use('fs')).promises;
863
868
  const { targetType, targetNumber, owner, repo, $, log, logFile } = options;
864
-
869
+
865
870
  const targetName = targetType === 'pr' ? 'Pull Request' : 'Issue';
866
871
  const ghCommand = targetType === 'pr' ? 'pr' : 'issue';
867
872
  const logStats = await fs.stat(logFile);
868
-
873
+
869
874
  const tempFile = `/tmp/log-comment-${targetType}-${Date.now()}.md`;
870
875
  await fs.writeFile(tempFile, logComment);
871
-
876
+
872
877
  const result = await $`gh ${ghCommand} comment ${targetNumber} --repo ${owner}/${repo} --body-file "${tempFile}"`;
873
-
878
+
874
879
  await fs.unlink(tempFile).catch(() => {});
875
-
880
+
876
881
  if (result.code === 0) {
877
882
  await log(` ✅ Solution draft log uploaded to ${targetName} as comment`);
878
883
  await log(` 📊 Log size: ${Math.round(logStats.size / 1024)}KB`);
@@ -890,16 +895,7 @@ async function attachRegularComment(options, logComment) {
890
895
  export function isRateLimitError(error) {
891
896
  const errorMessage = (error.message || error.toString()).toLowerCase();
892
897
  // Common rate limit error patterns
893
- const rateLimitPatterns = [
894
- 'rate limit',
895
- 'secondary rate limit',
896
- 'exceeded.*limit',
897
- 'too many requests',
898
- 'abuse detection',
899
- 'wait a few minutes',
900
- 'http 403.*rate',
901
- 'api rate limit exceeded'
902
- ];
898
+ const rateLimitPatterns = ['rate limit', 'secondary rate limit', 'exceeded.*limit', 'too many requests', 'abuse detection', 'wait a few minutes', 'http 403.*rate', 'api rate limit exceeded'];
903
899
  return rateLimitPatterns.some(pattern => {
904
900
  return new RegExp(pattern).test(errorMessage);
905
901
  });
@@ -991,7 +987,7 @@ export async function fetchProjectIssues(projectNumber, owner, statusFilter) {
991
987
  } catch (error) {
992
988
  reportError(error, {
993
989
  context: 'github.lib.mjs - GitHub CLI auth status check',
994
- level: 'error'
990
+ level: 'error',
995
991
  });
996
992
  throw new Error('GitHub CLI authentication failed. Please run: gh auth login');
997
993
  }
@@ -1000,7 +996,9 @@ export async function fetchProjectIssues(projectNumber, owner, statusFilter) {
1000
996
  await new Promise(resolve => setTimeout(resolve, timeouts.githubRepoDelay));
1001
997
  const startTime = Date.now();
1002
998
  // Fetch all project items
1003
- await log(` 🔎 Executing: gh project item-list ${projectNumber} --owner ${owner} --format json --limit 100`, { verbose: true });
999
+ await log(` 🔎 Executing: gh project item-list ${projectNumber} --owner ${owner} --format json --limit 100`, {
1000
+ verbose: true,
1001
+ });
1004
1002
  const result = await $`gh project item-list ${projectNumber} --owner ${owner} --format json --limit 100`;
1005
1003
  const endTime = Date.now();
1006
1004
  const projectData = JSON.parse(result.stdout || '{"items": []}');
@@ -1028,7 +1026,7 @@ export async function fetchProjectIssues(projectNumber, owner, statusFilter) {
1028
1026
  number: item.content.number,
1029
1027
  repository: item.content.repository,
1030
1028
  labels: item.content.labels || [],
1031
- state: item.content.state || 'open'
1029
+ state: item.content.state || 'open',
1032
1030
  }));
1033
1031
  await log(` ✅ Found ${issues.length} issues with status "${statusFilter}"`);
1034
1032
  if (issues.length > 0) {
@@ -1074,7 +1072,7 @@ export function parseGitHubUrl(url) {
1074
1072
  if (!url || typeof url !== 'string') {
1075
1073
  return {
1076
1074
  valid: false,
1077
- error: 'Invalid input: URL must be a non-empty string'
1075
+ error: 'Invalid input: URL must be a non-empty string',
1078
1076
  };
1079
1077
  }
1080
1078
  // Trim whitespace and remove trailing slashes
@@ -1084,7 +1082,7 @@ export function parseGitHubUrl(url) {
1084
1082
  if (/\s/.test(normalizedUrl) || /^[!@#$%^&*()[\]{}|\\:;"'<>,?`~]/.test(normalizedUrl)) {
1085
1083
  return {
1086
1084
  valid: false,
1087
- error: 'Invalid GitHub URL format'
1085
+ error: 'Invalid GitHub URL format',
1088
1086
  };
1089
1087
  }
1090
1088
  // Handle protocol normalization
@@ -1099,7 +1097,7 @@ export function parseGitHubUrl(url) {
1099
1097
  // Has github.com somewhere but not at the start - likely malformed
1100
1098
  return {
1101
1099
  valid: false,
1102
- error: 'Invalid GitHub URL format'
1100
+ error: 'Invalid GitHub URL format',
1103
1101
  };
1104
1102
  }
1105
1103
  }
@@ -1119,7 +1117,7 @@ export function parseGitHubUrl(url) {
1119
1117
  return {
1120
1118
  valid: false,
1121
1119
  error: 'Invalid character in URL: backslash (\\) is not allowed in URL paths',
1122
- suggestion: suggestedUrl + urlAfterPath
1120
+ suggestion: suggestedUrl + urlAfterPath,
1123
1121
  };
1124
1122
  }
1125
1123
 
@@ -1132,19 +1130,19 @@ export function parseGitHubUrl(url) {
1132
1130
  reportError(e, {
1133
1131
  context: 'github.lib.mjs - URL parsing',
1134
1132
  level: 'debug',
1135
- url: normalizedUrl
1133
+ url: normalizedUrl,
1136
1134
  });
1137
1135
  }
1138
1136
  return {
1139
1137
  valid: false,
1140
- error: 'Invalid URL format'
1138
+ error: 'Invalid URL format',
1141
1139
  };
1142
1140
  }
1143
1141
  // Ensure it's a GitHub URL
1144
1142
  if (urlObj.hostname !== 'github.com' && urlObj.hostname !== 'www.github.com') {
1145
1143
  return {
1146
1144
  valid: false,
1147
- error: 'Not a GitHub URL'
1145
+ error: 'Not a GitHub URL',
1148
1146
  };
1149
1147
  }
1150
1148
  // Normalize hostname
@@ -1160,7 +1158,7 @@ export function parseGitHubUrl(url) {
1160
1158
  normalized: normalizedUrl,
1161
1159
  hostname: 'github.com',
1162
1160
  protocol: 'https',
1163
- path: urlObj.pathname
1161
+ path: urlObj.pathname,
1164
1162
  };
1165
1163
  // No path - just github.com
1166
1164
  if (pathParts.length === 0) {
@@ -1343,7 +1341,7 @@ export async function ghPrView({ prNumber, owner, repo, jsonFields = 'headRefNam
1343
1341
  stdout,
1344
1342
  stderr,
1345
1343
  data,
1346
- output: stdout + stderr
1344
+ output: stdout + stderr,
1347
1345
  };
1348
1346
  } catch (error) {
1349
1347
  return {
@@ -1351,7 +1349,7 @@ export async function ghPrView({ prNumber, owner, repo, jsonFields = 'headRefNam
1351
1349
  stdout: error.stdout?.toString() || '',
1352
1350
  stderr: error.stderr?.toString() || error.message || '',
1353
1351
  data: null,
1354
- output: (error.stdout?.toString() || '') + (error.stderr?.toString() || error.message || '')
1352
+ output: (error.stdout?.toString() || '') + (error.stderr?.toString() || error.message || ''),
1355
1353
  };
1356
1354
  }
1357
1355
  }
@@ -1383,7 +1381,7 @@ export async function ghIssueView({ issueNumber, owner, repo, jsonFields = 'numb
1383
1381
  stdout,
1384
1382
  stderr,
1385
1383
  data,
1386
- output: stdout + stderr
1384
+ output: stdout + stderr,
1387
1385
  };
1388
1386
  } catch (error) {
1389
1387
  return {
@@ -1391,7 +1389,7 @@ export async function ghIssueView({ issueNumber, owner, repo, jsonFields = 'numb
1391
1389
  stdout: error.stdout?.toString() || '',
1392
1390
  stderr: error.stderr?.toString() || error.message || '',
1393
1391
  data: null,
1394
- output: (error.stdout?.toString() || '') + (error.stderr?.toString() || error.message || '')
1392
+ output: (error.stdout?.toString() || '') + (error.stderr?.toString() || error.message || ''),
1395
1393
  };
1396
1394
  }
1397
1395
  }
@@ -1458,7 +1456,7 @@ export async function detectRepositoryVisibility(owner, repo) {
1458
1456
  context: 'detect_repository_visibility',
1459
1457
  owner,
1460
1458
  repo,
1461
- operation: 'get_repo_visibility'
1459
+ operation: 'get_repo_visibility',
1462
1460
  });
1463
1461
  // Default to public (safer to keep temp directories on error)
1464
1462
  if (global.verboseMode) {
@@ -1491,5 +1489,5 @@ export default {
1491
1489
  ghIssueView,
1492
1490
  handlePRNotFoundError,
1493
1491
  detectRepositoryVisibility,
1494
- batchCheckArchivedRepositories
1492
+ batchCheckArchivedRepositories,
1495
1493
  };