@link-assistant/hive-mind 1.50.0 → 1.50.2

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.50.2
4
+
5
+ ### Patch Changes
6
+
7
+ - f09dead: fix: always post GitHub comment when usage limit is reached in auto-restart mode (#1570)
8
+ - Fix silent waiting behavior in watchUntilMergeable() when usage limit is reached
9
+ - Previously the system would silently wait 40+ minutes without any user notification
10
+ - Now posts a GitHub comment to the PR using attachLogToGitHub() with usage limit details
11
+ - Comment includes reset time, session ID, and indicates auto-restart will resume automatically
12
+ - Log output now also shows the calculated resume time in UTC
13
+
14
+ ## 1.50.1
15
+
16
+ ### Patch Changes
17
+
18
+ - 494989e: Add paths filter to CI/CD workflow trigger to skip unnecessary runs for non-code file changes (#1582)
19
+ - c4fadea: fix: prevent push failures in auto-restart and cleanup by syncing with remote (#1572)
20
+ - Add `git pull` before restart sessions and cleanup push to prevent stale local state
21
+ - Add `2>&1` to all `git push` commands so stderr is captured for proper error handling
22
+ - Fix multi-line log message formatting to include timestamps on each line
23
+
3
24
  ## 1.50.0
4
25
 
5
26
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.50.0",
3
+ "version": "1.50.2",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
package/src/agent.lib.mjs CHANGED
@@ -1082,12 +1082,12 @@ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchNam
1082
1082
  if (commitResult.code === 0) {
1083
1083
  await log('✅ Changes committed successfully');
1084
1084
 
1085
- const pushResult = await $({ cwd: tempDir })`git push origin ${branchName}`;
1085
+ const pushResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
1086
1086
 
1087
1087
  if (pushResult.code === 0) {
1088
1088
  await log('✅ Changes pushed successfully');
1089
1089
  } else {
1090
- await log(`⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim()}`, {
1090
+ await log(`⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim() || pushResult.stdout?.toString().trim()}`, {
1091
1091
  level: 'warning',
1092
1092
  });
1093
1093
  }
@@ -1449,11 +1449,11 @@ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchNam
1449
1449
  if (commitResult.code === 0) {
1450
1450
  await log('✅ Changes committed successfully');
1451
1451
  await log('📤 Pushing changes to remote...');
1452
- const pushResult = await $({ cwd: tempDir })`git push origin ${branchName}`;
1452
+ const pushResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
1453
1453
  if (pushResult.code === 0) {
1454
1454
  await log('✅ Changes pushed successfully');
1455
1455
  } else {
1456
- await log(`⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim()}`, {
1456
+ await log(`⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim() || pushResult.stdout?.toString().trim()}`, {
1457
1457
  level: 'warning',
1458
1458
  });
1459
1459
  }
package/src/codex.lib.mjs CHANGED
@@ -492,12 +492,12 @@ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchNam
492
492
  if (commitResult.code === 0) {
493
493
  await log('✅ Changes committed successfully');
494
494
 
495
- const pushResult = await $({ cwd: tempDir })`git push origin ${branchName}`;
495
+ const pushResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
496
496
 
497
497
  if (pushResult.code === 0) {
498
498
  await log('✅ Changes pushed successfully');
499
499
  } else {
500
- await log(`⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim()}`, {
500
+ await log(`⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim() || pushResult.stdout?.toString().trim()}`, {
501
501
  level: 'warning',
502
502
  });
503
503
  }
package/src/lib.mjs CHANGED
@@ -83,8 +83,13 @@ export const log = async (message, options = {}) => {
83
83
  }
84
84
 
85
85
  // Write to file if log file is set
86
+ // Issue #1572: Handle multi-line messages by timestamping each line,
87
+ // so continuation lines don't appear without timestamps in the log file
86
88
  if (logFile) {
87
- const logMessage = `[${new Date().toISOString()}] [${level.toUpperCase()}] ${message}`;
89
+ const timestamp = new Date().toISOString();
90
+ const prefix = `[${timestamp}] [${level.toUpperCase()}]`;
91
+ const lines = String(message).split('\n');
92
+ const logMessage = lines.map(line => `${prefix} ${line}`).join('\n');
88
93
  await fs.appendFile(logFile, logMessage + '\n').catch(error => {
89
94
  // Silent fail for file append errors to avoid infinite loop
90
95
  // but report to Sentry in verbose mode
@@ -546,12 +546,12 @@ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchNam
546
546
  if (commitResult.code === 0) {
547
547
  await log('✅ Changes committed successfully');
548
548
 
549
- const pushResult = await $({ cwd: tempDir })`git push origin ${branchName}`;
549
+ const pushResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
550
550
 
551
551
  if (pushResult.code === 0) {
552
552
  await log('✅ Changes pushed successfully');
553
553
  } else {
554
- await log(`⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim()}`, {
554
+ await log(`⚠️ Warning: Could not push changes: ${pushResult.stderr?.toString().trim() || pushResult.stdout?.toString().trim()}`, {
555
555
  level: 'warning',
556
556
  });
557
557
  }
@@ -76,6 +76,14 @@ export const runAutoEnsureRequirements = async ({ issueUrl, owner, repo, issueNu
76
76
  for (let ensureIteration = 1; ensureIteration <= finalizeCount; ensureIteration++) {
77
77
  await log(`🔄 FINALIZE iteration ${ensureIteration}/${finalizeCount}: Restarting to verify requirements...`);
78
78
 
79
+ // Issue #1572: Sync local branch with remote before each finalize iteration
80
+ const pullResult = await $({ cwd: tempDir })`git pull origin ${branchName} 2>&1`;
81
+ if (pullResult.code === 0) {
82
+ await log(` Synced local branch ${branchName} from remote`, { verbose: true });
83
+ } else {
84
+ throw new Error(`git pull failed (code ${pullResult.code}): ${pullResult.stdout || pullResult.stderr || 'no output'}`);
85
+ }
86
+
79
87
  const ensureFeedbackLines = ['', '='.repeat(60), '🔍 FINALIZE REQUIREMENTS CHECK:', '='.repeat(60), '', 'We need to ensure all changes are correct, consistent, validated, tested, logged and fully meet all discussed requirements (check issue description and all comments in issue and in pull request). Ensure all CI/CD checks pass.', ''];
80
88
 
81
89
  const ensureResult = await executeToolIteration({
@@ -976,6 +976,16 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
976
976
  const prStateResult = await $`gh api repos/${owner}/${repo}/pulls/${prNumber} --jq '.mergeStateStatus'`;
977
977
  const mergeStateStatus = prStateResult.code === 0 ? prStateResult.stdout.toString().trim() : null;
978
978
 
979
+ // Issue #1572: Sync local branch with remote before restarting to avoid push failures.
980
+ // Without this, the restarted session works on stale local state and can't push.
981
+ const effectiveBranch = prBranch || branchName;
982
+ const pullResult = await $({ cwd: tempDir })`git pull origin ${effectiveBranch} 2>&1`;
983
+ if (pullResult.code === 0) {
984
+ await log(formatAligned('🔄', 'Synced:', `Local branch ${effectiveBranch} updated from remote`));
985
+ } else {
986
+ throw new Error(`git pull failed (code ${pullResult.code}): ${pullResult.stdout || pullResult.stderr || 'no output'}`);
987
+ }
988
+
979
989
  // Execute the AI tool using shared utility
980
990
  await log(formatAligned('🔄', 'Restarting:', `Running ${argv.tool.toUpperCase()} to address issues...`));
981
991
 
@@ -994,9 +1004,10 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
994
1004
 
995
1005
  if (!toolResult.success) {
996
1006
  // Issue #1356: Check for usage limit errors FIRST (most specific)
997
- // When usage limit is reached, silently wait for limitResetTime + buffer + jitter,
1007
+ // When usage limit is reached, wait for limitResetTime + buffer + jitter,
998
1008
  // then resume the session using --resume <sessionId> with a "Continue" prompt.
999
- // No GitHub comment is posted only log output.
1009
+ // Issue #1570: Always post a GitHub comment to notify the user about the delay
1010
+ // and when exactly execution will be resumed, so the user doesn't think the process is stuck.
1000
1011
  if (isUsageLimitReached(toolResult)) {
1001
1012
  const resumeSessionId = toolResult.sessionId;
1002
1013
  const resetTime = toolResult.limitResetTime;
@@ -1008,17 +1019,70 @@ Once the billing issue is resolved, you can re-run the CI checks or push a new c
1008
1019
  const jitterSeconds = Math.round(jitterMs / 1000);
1009
1020
  const waitMinutes = Math.round(waitMs / 60000);
1010
1021
 
1022
+ // Issue #1570: Calculate the actual resume time for user display
1023
+ const resumeDate = new Date(Date.now() + waitMs);
1024
+ const resumeTimeUTC = resumeDate
1025
+ .toISOString()
1026
+ .replace('T', ' ')
1027
+ .replace(/\.\d+Z$/, ' UTC');
1028
+
1011
1029
  await log('');
1012
1030
  await log(formatAligned('⏳', 'USAGE LIMIT REACHED', ''));
1013
1031
  await log(formatAligned('', 'Reset time:', resetTime || 'Unknown', 2));
1014
1032
  await log(formatAligned('', 'Waiting:', `${waitMinutes} min (reset + ${bufferMinutes} min buffer + ${jitterSeconds}s jitter)`, 2));
1015
- await log(formatAligned('', 'Action:', 'Silently waiting then resuming — no GitHub comment posted', 2));
1033
+ await log(formatAligned('', 'Resume at:', resumeTimeUTC, 2));
1034
+ await log(formatAligned('', 'Action:', 'Posting GitHub comment and waiting for limit reset', 2));
1016
1035
  if (resumeSessionId) {
1017
1036
  await log(formatAligned('', 'Session ID:', resumeSessionId, 2));
1018
1037
  }
1019
1038
  await log('');
1020
1039
 
1021
- // Wait silently until the limit resets (no GitHub comment)
1040
+ // Issue #1570: Post a GitHub comment to notify the user about the usage limit delay.
1041
+ // This follows the same pattern as solve.watch.lib.mjs to ensure consistent user experience.
1042
+ const shouldAttachLogs = argv.attachLogs || argv['attach-logs'];
1043
+ if (prNumber && shouldAttachLogs) {
1044
+ try {
1045
+ const logFile = getLogFile();
1046
+ if (logFile) {
1047
+ await attachLogToGitHub({
1048
+ logFile,
1049
+ targetType: 'pr',
1050
+ targetNumber: prNumber,
1051
+ owner,
1052
+ repo,
1053
+ $,
1054
+ log,
1055
+ sanitizeLogContent,
1056
+ verbose: argv.verbose,
1057
+ sessionId: resumeSessionId || latestSessionId,
1058
+ tempDir,
1059
+ anthropicTotalCostUSD: toolResult.anthropicTotalCostUSD || latestAnthropicCost,
1060
+ isUsageLimit: true,
1061
+ limitResetTime: resetTime,
1062
+ toolName: `Anthropic ${(argv.tool || 'claude').charAt(0).toUpperCase() + (argv.tool || 'claude').slice(1)} Code`,
1063
+ isAutoResumeEnabled: true,
1064
+ autoResumeMode: 'restart',
1065
+ requestedModel: argv.model,
1066
+ tool: argv.tool || 'claude',
1067
+ publicPricingEstimate: toolResult.publicPricingEstimate,
1068
+ pricingInfo: toolResult.pricingInfo,
1069
+ resultModelUsage: toolResult.resultModelUsage || null,
1070
+ });
1071
+ await log(formatAligned('', '✅ Usage limit comment posted to PR', '', 2));
1072
+ }
1073
+ } catch (commentError) {
1074
+ reportError(commentError, {
1075
+ context: 'attach_usage_limit_comment_auto_restart',
1076
+ prNumber,
1077
+ owner,
1078
+ repo,
1079
+ operation: 'usage_limit_comment',
1080
+ });
1081
+ await log(formatAligned('', `⚠️ Usage limit comment upload error: ${cleanErrorMessage(commentError)}`, '', 2));
1082
+ }
1083
+ }
1084
+
1085
+ // Wait until the limit resets
1022
1086
  await new Promise(resolve => setTimeout(resolve, waitMs));
1023
1087
 
1024
1088
  await log(formatAligned('✅', 'Usage limit wait complete', 'Resuming session...'));
package/src/solve.mjs CHANGED
@@ -573,12 +573,12 @@ try {
573
573
  const mergeResult = await $({ cwd: tempDir })`git merge ${defaultBranch} --no-edit`;
574
574
  if (mergeResult.code === 0) {
575
575
  await log(`${formatAligned('✅', 'Merge successful:', 'Pushing merged branch...')}`);
576
- const pushResult = await $({ cwd: tempDir })`git push origin ${branchName}`;
576
+ const pushResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
577
577
  if (pushResult.code === 0) {
578
578
  await log(`${formatAligned('✅', 'Push successful:', 'Branch updated with latest changes')}`);
579
579
  } else {
580
580
  await log(`${formatAligned('⚠️', 'Push failed:', 'Merge completed but push failed')}`, { level: 'warning' });
581
- await log(` Error: ${pushResult.stderr?.toString() || 'Unknown error'}`, { level: 'warning' });
581
+ await log(` Error: ${pushResult.stderr?.toString() || pushResult.stdout?.toString() || 'Unknown error'}`, { level: 'warning' });
582
582
  }
583
583
  } else {
584
584
  // Merge failed - likely due to conflicts
@@ -1343,13 +1343,13 @@ try {
1343
1343
  await log('');
1344
1344
 
1345
1345
  try {
1346
- const pushResult = await $({ cwd: tempDir })`git push origin ${branchName}`;
1346
+ const pushResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
1347
1347
  if (pushResult.code === 0) {
1348
1348
  await log('✅ Changes pushed successfully to remote branch');
1349
1349
  await log(` Branch: ${branchName}`);
1350
1350
  await log('');
1351
1351
  } else {
1352
- const errorMsg = pushResult.stderr?.toString() || 'Unknown error';
1352
+ const errorMsg = pushResult.stderr?.toString() || pushResult.stdout?.toString() || 'Unknown error';
1353
1353
  await log('⚠️ Push failed:', { level: 'error' });
1354
1354
  await log(` ${errorMsg.trim()}`, { level: 'error' });
1355
1355
  await log(' Please push manually:', { level: 'error' });
@@ -1113,12 +1113,12 @@ export const setupUpstreamAndSync = async (tempDir, forkedRepo, upstreamRemote,
1113
1113
 
1114
1114
  // Step 3: Push the updated default branch to fork to keep it in sync
1115
1115
  await log(`${formatAligned('🔄', 'Pushing to fork:', `${upstreamDefaultBranch} branch`)}`);
1116
- const pushResult = await $({ cwd: tempDir })`git push origin ${upstreamDefaultBranch}`;
1116
+ const pushResult = await $({ cwd: tempDir })`git push origin ${upstreamDefaultBranch} 2>&1`;
1117
1117
  if (pushResult.code === 0) {
1118
1118
  await log(`${formatAligned('✅', 'Fork updated:', 'Default branch pushed to fork')}`);
1119
1119
  } else {
1120
1120
  // Check if it's a non-fast-forward error (fork has diverged from upstream)
1121
- const errorMsg = pushResult.stderr ? pushResult.stderr.toString().trim() : '';
1121
+ const errorMsg = (pushResult.stderr ? pushResult.stderr.toString().trim() : '') || (pushResult.stdout ? pushResult.stdout.toString().trim() : '');
1122
1122
  const isNonFastForward = errorMsg.includes('non-fast-forward') || errorMsg.includes('rejected') || errorMsg.includes('tip of your current branch is behind');
1123
1123
 
1124
1124
  if (isNonFastForward) {
@@ -1147,7 +1147,7 @@ export const setupUpstreamAndSync = async (tempDir, forkedRepo, upstreamRemote,
1147
1147
  await log(`${formatAligned('🔄', 'Force pushing:', 'Syncing fork with upstream (--force-with-lease)')}`);
1148
1148
  const forcePushResult = await $({
1149
1149
  cwd: tempDir,
1150
- })`git push --force-with-lease origin ${upstreamDefaultBranch}`;
1150
+ })`git push --force-with-lease origin ${upstreamDefaultBranch} 2>&1`;
1151
1151
 
1152
1152
  if (forcePushResult.code === 0) {
1153
1153
  await log(`${formatAligned('✅', 'Fork synced:', 'Successfully force-pushed to align with upstream')}`);
@@ -269,6 +269,15 @@ export const cleanupClaudeFile = async (tempDir, branchName, claudeCommitHash =
269
269
  await log(formatAligned('🔄', 'Cleanup:', `Reverting ${fileName} commit`));
270
270
  await log(` Using saved commit hash: ${claudeCommitHash.substring(0, 7)}...`, { verbose: true });
271
271
 
272
+ // Issue #1572: Sync local branch with remote before cleanup to prevent push failures.
273
+ // After auto-restart sessions, the local branch may be behind the remote.
274
+ const pullResult = await $({ cwd: tempDir })`git pull origin ${branchName} 2>&1`;
275
+ if (pullResult.code === 0) {
276
+ await log(` Synced local branch before cleanup`, { verbose: true });
277
+ } else {
278
+ throw new Error(`git pull failed (code ${pullResult.code}): ${pullResult.stdout || pullResult.stderr || 'no output'}`);
279
+ }
280
+
272
281
  const commitToRevert = claudeCommitHash;
273
282
 
274
283
  // APPROACH 3: Check for modifications before reverting (proactive detection)