@link-assistant/hive-mind 1.59.5 → 1.59.7

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 (43) hide show
  1. package/CHANGELOG.md +158 -0
  2. package/package.json +1 -1
  3. package/src/bidirectional-interactive.lib.mjs +1 -0
  4. package/src/contributing-guidelines.lib.mjs +3 -2
  5. package/src/github-error-reporter.lib.mjs +3 -2
  6. package/src/github-merge-ci-signals.lib.mjs +8 -2
  7. package/src/github-merge-ci.lib.mjs +8 -2
  8. package/src/github-merge-ready-sync.lib.mjs +7 -1
  9. package/src/github-merge-repo-actions.lib.mjs +7 -1
  10. package/src/github-merge.lib.mjs +40 -32
  11. package/src/github-rate-limit.lib.mjs +276 -0
  12. package/src/github.batch.lib.mjs +1 -0
  13. package/src/hive.mjs +2 -2
  14. package/src/hive.recheck.lib.mjs +1 -0
  15. package/src/lib.mjs +30 -4
  16. package/src/limits.lib.mjs +1 -0
  17. package/src/protect-branch.mjs +3 -2
  18. package/src/queue-config.lib.mjs +7 -3
  19. package/src/review.mjs +3 -2
  20. package/src/reviewers-hive.mjs +3 -2
  21. package/src/solve.accept-invite.lib.mjs +7 -1
  22. package/src/solve.auto-continue.lib.mjs +3 -2
  23. package/src/solve.auto-ensure.lib.mjs +3 -2
  24. package/src/solve.auto-merge-helpers.lib.mjs +3 -2
  25. package/src/solve.auto-merge.lib.mjs +40 -2
  26. package/src/solve.auto-pr.lib.mjs +1 -0
  27. package/src/solve.branch-errors.lib.mjs +1 -0
  28. package/src/solve.config.lib.mjs +2 -2
  29. package/src/solve.error-handlers.lib.mjs +1 -0
  30. package/src/solve.execution.lib.mjs +3 -2
  31. package/src/solve.feedback.lib.mjs +1 -0
  32. package/src/solve.mjs +20 -36
  33. package/src/solve.preparation.lib.mjs +1 -0
  34. package/src/solve.progress-monitoring.lib.mjs +1 -0
  35. package/src/solve.repository.lib.mjs +3 -3
  36. package/src/solve.restart-shared.lib.mjs +3 -2
  37. package/src/solve.results.lib.mjs +89 -14
  38. package/src/solve.session.lib.mjs +1 -0
  39. package/src/solve.watch.lib.mjs +39 -2
  40. package/src/telegram-accept-invitations.lib.mjs +7 -1
  41. package/src/token-sanitization.lib.mjs +1 -0
  42. package/src/tool-comments.lib.mjs +12 -1
  43. package/src/youtrack/youtrack-sync.mjs +1 -0
package/src/hive.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // Import Sentry instrumentation first (must be before other imports)
3
3
  import './instrument.mjs';
4
+ import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller
4
5
  const earlyArgs = process.argv.slice(2);
5
6
  if (earlyArgs.includes('--version')) {
6
7
  const { getVersion } = await import('./version.lib.mjs');
@@ -28,7 +29,6 @@ if (earlyArgs.includes('--help') || earlyArgs.includes('-h')) {
28
29
  // Reuse createYargsConfig from shared module to avoid duplication
29
30
  const { createYargsConfig } = await import('./hive.config.lib.mjs');
30
31
  const helpYargs = createYargsConfig(yargs(rawArgs)).version(false);
31
- // Show help and exit
32
32
  helpYargs.showHelp();
33
33
  process.exit(0);
34
34
  } catch (error) {
@@ -1497,4 +1497,4 @@ if (isRunningDirectly) {
1497
1497
  console.error('\nPlease report this issue at: https://github.com/link-assistant/hive-mind/issues');
1498
1498
  process.exit(1);
1499
1499
  }
1500
- } // End of main execution block
1500
+ }
@@ -5,6 +5,7 @@ import { log, cleanErrorMessage } from './lib.mjs';
5
5
  import { batchCheckPullRequestsForIssues, batchCheckArchivedRepositories } from './github.lib.mjs';
6
6
  import { reportError } from './sentry.lib.mjs';
7
7
 
8
+ import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller
8
9
  /**
9
10
  * Recheck conditions for an issue right before processing
10
11
  * This ensures the issue should still be processed even if conditions changed since queuing
package/src/lib.mjs CHANGED
@@ -478,12 +478,16 @@ export const isTransientNetworkError = error => {
478
478
  /**
479
479
  * Retry a GitHub CLI / API operation with exponential backoff on transient network errors.
480
480
  * Unlike the generic `retry()`, this function:
481
- * - Only retries on transient network errors (TCP reset, TLS timeout, etc.)
482
- * - Immediately rethrows non-transient errors (404, 403, auth failures)
481
+ * - Retries on transient network errors (TCP reset, TLS timeout, etc.)
482
+ * - Retries on GitHub API rate-limit errors, sleeping until reset + buffer + jitter
483
+ * (issue #1726 — see src/github-rate-limit.lib.mjs)
484
+ * - Immediately rethrows non-transient errors (404, 403 non-rate-limit, auth failures)
483
485
  * - Logs stderr to the log file when a command fails (fixing terminal/log parity)
484
486
  *
485
487
  * Issue #1536: Most gh commands had no retry logic, causing solve to abort on
486
488
  * intermittent network issues.
489
+ * Issue #1726: Rate limit errors silently surfaced as command failure with no retry,
490
+ * causing the merge subsystem to swallow them as "no workflows found".
487
491
  *
488
492
  * @param {Function} fn - Async function to execute (should call gh CLI or GitHub API)
489
493
  * @param {Object} [options] - Options
@@ -496,11 +500,20 @@ export const isTransientNetworkError = error => {
496
500
  */
497
501
  export const ghRetry = async (fn, options = {}) => {
498
502
  const { maxAttempts = 3, delay = 1000, backoff = 2, label = 'gh command' } = options;
503
+ const { isRateLimitError, parseRateLimitReset, fetchNextRateLimitReset, computeRateLimitWait } = await import('./github-rate-limit.lib.mjs');
499
504
 
500
505
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
501
506
  try {
502
507
  return await fn();
503
508
  } catch (error) {
509
+ if (isRateLimitError(error) && attempt < maxAttempts) {
510
+ const reset = parseRateLimitReset(error) || (await fetchNextRateLimitReset());
511
+ const { waitMs, deadline, bufferMs, jitterMs } = computeRateLimitWait(reset);
512
+ const resetSummary = reset ? `reset at ${reset.toISOString()}` : 'reset time unknown';
513
+ await log(`âŗ ${label}: GitHub API rate limit hit (attempt ${attempt}/${maxAttempts}). Waiting ${Math.round(waitMs / 60000)} min (${resetSummary}; buffer ${Math.round(bufferMs / 60000)} min + jitter ${Math.round(jitterMs / 1000)}s) until ${deadline.toISOString()}.`, { level: 'warn' });
514
+ await sleep(waitMs);
515
+ continue;
516
+ }
504
517
  if (isTransientNetworkError(error) && attempt < maxAttempts) {
505
518
  const waitTime = delay * Math.pow(backoff, attempt - 1);
506
519
  await log(`âš ī¸ ${label}: Network error (attempt ${attempt}/${maxAttempts}), retrying in ${waitTime / 1000}s...`, { level: 'warn' });
@@ -527,6 +540,7 @@ export const ghRetry = async (fn, options = {}) => {
527
540
  */
528
541
  export const ghCmdRetry = async (cmdFn, options = {}) => {
529
542
  const { maxAttempts = 3, delay = 1000, backoff = 2, label = 'gh command' } = options;
543
+ const { isRateLimitError, parseRateLimitReset, fetchNextRateLimitReset, computeRateLimitWait } = await import('./github-rate-limit.lib.mjs');
530
544
 
531
545
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
532
546
  const result = await cmdFn();
@@ -541,9 +555,21 @@ export const ghCmdRetry = async (cmdFn, options = {}) => {
541
555
  return result;
542
556
  }
543
557
 
544
- // Check if this is a transient network error worth retrying
545
558
  const combinedOutput = (result.stdout?.toString() || '') + ' ' + (result.stderr?.toString() || '');
546
- if (isTransientNetworkError({ message: combinedOutput }) && attempt < maxAttempts) {
559
+ const errorLike = { message: combinedOutput, stdout: result.stdout, stderr: result.stderr };
560
+
561
+ // Issue #1726: rate-limit errors deserve a long, deterministic wait.
562
+ if (isRateLimitError(errorLike) && attempt < maxAttempts) {
563
+ const reset = parseRateLimitReset(errorLike) || (await fetchNextRateLimitReset());
564
+ const { waitMs, deadline, bufferMs, jitterMs } = computeRateLimitWait(reset);
565
+ const resetSummary = reset ? `reset at ${reset.toISOString()}` : 'reset time unknown';
566
+ await log(`âŗ ${label}: GitHub API rate limit hit (attempt ${attempt}/${maxAttempts}). Waiting ${Math.round(waitMs / 60000)} min (${resetSummary}; buffer ${Math.round(bufferMs / 60000)} min + jitter ${Math.round(jitterMs / 1000)}s) until ${deadline.toISOString()}.`, { level: 'warn' });
567
+ await sleep(waitMs);
568
+ continue;
569
+ }
570
+
571
+ // Check if this is a transient network error worth retrying
572
+ if (isTransientNetworkError(errorLike) && attempt < maxAttempts) {
547
573
  const waitTime = delay * Math.pow(backoff, attempt - 1);
548
574
  await log(`âš ī¸ ${label}: Network error (attempt ${attempt}/${maxAttempts}), retrying in ${waitTime / 1000}s...`, { level: 'warn' });
549
575
  await sleep(waitTime);
@@ -12,6 +12,7 @@ import { promisify } from 'node:util';
12
12
  import dayjs from 'dayjs';
13
13
  import utc from 'dayjs/plugin/utc.js';
14
14
 
15
+ import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller
15
16
  // Initialize dayjs plugins
16
17
  dayjs.extend(utc);
17
18
 
@@ -18,8 +18,9 @@
18
18
  const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
19
19
 
20
20
  // Use command-stream for consistent $ behavior across runtimes
21
- const { $ } = await use('command-stream');
22
-
21
+ const { $: __rawDollar$ } = await use('command-stream');
22
+ const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
23
+ const $ = wrapDollarWithGhRetry(__rawDollar$);
23
24
  // Parse command line arguments
24
25
  const args = process.argv.slice(2);
25
26
 
@@ -88,7 +88,7 @@ function normalizeMetricName(name) {
88
88
  * (cpu (65% enqueue))
89
89
  * (claude-5-hour (65% dequeue-one-at-a-time))
90
90
  * (claude-weekly (97% dequeue-one-at-a-time))
91
- * (github-api (75% enqueue))
91
+ * (github-api (50% enqueue))
92
92
  * )
93
93
  * ```
94
94
  *
@@ -253,7 +253,11 @@ export const QUEUE_CONFIG = {
253
253
  claudeWeekly: getThresholdConfig('claudeWeekly', 'HIVE_MIND_CLAUDE_WEEKLY_THRESHOLD', 'HIVE_MIND_CLAUDE_WEEKLY_STRATEGY', 0.97, 'dequeue-one-at-a-time'),
254
254
  codex5Hour: getThresholdConfig('codex5Hour', 'HIVE_MIND_CODEX_5_HOUR_SESSION_THRESHOLD', 'HIVE_MIND_CODEX_5_HOUR_SESSION_STRATEGY', 0.65, 'dequeue-one-at-a-time'),
255
255
  codexWeekly: getThresholdConfig('codexWeekly', 'HIVE_MIND_CODEX_WEEKLY_THRESHOLD', 'HIVE_MIND_CODEX_WEEKLY_STRATEGY', 0.97, 'dequeue-one-at-a-time'),
256
- githubApi: getThresholdConfig('githubApi', 'HIVE_MIND_GITHUB_API_THRESHOLD', 'HIVE_MIND_GITHUB_API_STRATEGY', 0.75, 'enqueue'),
256
+ // Issue #1726: lowered default from 0.75 to 0.50 to start backing off earlier
257
+ // and leave a wider safety margin before the hard 5,000/hr ceiling. Hosted
258
+ // runners hit the ceiling repeatedly with the 75% setting (see
259
+ // docs/case-studies/issue-1726).
260
+ githubApi: getThresholdConfig('githubApi', 'HIVE_MIND_GITHUB_API_THRESHOLD', 'HIVE_MIND_GITHUB_API_STRATEGY', 0.5, 'enqueue'),
257
261
  },
258
262
 
259
263
  // Legacy flat threshold values for backward compatibility
@@ -265,7 +269,7 @@ export const QUEUE_CONFIG = {
265
269
  CLAUDE_WEEKLY_THRESHOLD: getThresholdConfig('claudeWeekly', 'HIVE_MIND_CLAUDE_WEEKLY_THRESHOLD', 'HIVE_MIND_CLAUDE_WEEKLY_STRATEGY', 0.97, 'dequeue-one-at-a-time').value,
266
270
  CODEX_5_HOUR_SESSION_THRESHOLD: getThresholdConfig('codex5Hour', 'HIVE_MIND_CODEX_5_HOUR_SESSION_THRESHOLD', 'HIVE_MIND_CODEX_5_HOUR_SESSION_STRATEGY', 0.65, 'dequeue-one-at-a-time').value,
267
271
  CODEX_WEEKLY_THRESHOLD: getThresholdConfig('codexWeekly', 'HIVE_MIND_CODEX_WEEKLY_THRESHOLD', 'HIVE_MIND_CODEX_WEEKLY_STRATEGY', 0.97, 'dequeue-one-at-a-time').value,
268
- GITHUB_API_THRESHOLD: getThresholdConfig('githubApi', 'HIVE_MIND_GITHUB_API_THRESHOLD', 'HIVE_MIND_GITHUB_API_STRATEGY', 0.75, 'enqueue').value,
272
+ GITHUB_API_THRESHOLD: getThresholdConfig('githubApi', 'HIVE_MIND_GITHUB_API_THRESHOLD', 'HIVE_MIND_GITHUB_API_STRATEGY', 0.5, 'enqueue').value,
269
273
 
270
274
  // Timing
271
275
  // MIN_START_INTERVAL_MS: Time to allow solve command to start actual claude process
package/src/review.mjs CHANGED
@@ -35,8 +35,9 @@ if (earlyArgs.includes('--help') || earlyArgs.includes('-h')) {
35
35
  const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
36
36
 
37
37
  // Use command-stream for consistent $ behavior across runtimes
38
- const { $ } = await use('command-stream');
39
-
38
+ const { $: __rawDollar$ } = await use('command-stream');
39
+ const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
40
+ const $ = wrapDollarWithGhRetry(__rawDollar$);
40
41
  const yargs = (await use('yargs@latest')).default;
41
42
  const os = (await use('os')).default;
42
43
  const path = (await use('path')).default;
@@ -4,8 +4,9 @@
4
4
  const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
5
5
 
6
6
  // Use command-stream for consistent $ behavior across runtimes
7
- const { $ } = await use('command-stream');
8
-
7
+ const { $: __rawDollar$ } = await use('command-stream');
8
+ const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
9
+ const $ = wrapDollarWithGhRetry(__rawDollar$);
9
10
  const yargs = (await use('yargs@latest')).default;
10
11
  const path = (await use('path')).default;
11
12
  const fs = (await use('fs')).promises;
@@ -16,8 +16,14 @@
16
16
 
17
17
  import { promisify } from 'util';
18
18
  import { exec as execCallback } from 'child_process';
19
+ import { ghWithRateLimitRetry } from './github-rate-limit.lib.mjs';
19
20
 
20
- const exec = promisify(execCallback);
21
+ const execRaw = promisify(execCallback);
22
+ // Issue #1726: rate-limit safe gh wrapper.
23
+ const exec = (cmd, opts) =>
24
+ ghWithRateLimitRetry(() => execRaw(cmd, opts), {
25
+ label: `gh exec (${cmd.split(/\s+/).slice(0, 3).join(' ')})`,
26
+ });
21
27
 
22
28
  // Import retry utility (issue #1536)
23
29
  const lib = await import('./lib.mjs');
@@ -13,8 +13,9 @@ if (typeof globalThis.use === 'undefined') {
13
13
  const use = globalThis.use;
14
14
 
15
15
  // Use command-stream for consistent $ behavior across runtimes
16
- const { $ } = await use('command-stream');
17
-
16
+ const { $: __rawDollar$ } = await use('command-stream');
17
+ const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
18
+ const $ = wrapDollarWithGhRetry(__rawDollar$);
18
19
  // Import shared library functions
19
20
  const lib = await import('./lib.mjs');
20
21
  const { log, cleanErrorMessage } = lib;
@@ -18,8 +18,9 @@ if (typeof globalThis.use === 'undefined') {
18
18
  const use = globalThis.use;
19
19
 
20
20
  // Use command-stream for consistent $ behavior across runtimes
21
- const { $ } = await use('command-stream');
22
-
21
+ const { $: __rawDollar$ } = await use('command-stream');
22
+ const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
23
+ const $ = wrapDollarWithGhRetry(__rawDollar$);
23
24
  // Import shared library functions
24
25
  const lib = await import('./lib.mjs');
25
26
  const { log } = lib;
@@ -21,8 +21,9 @@ if (typeof globalThis.use === 'undefined') {
21
21
  const use = globalThis.use;
22
22
 
23
23
  // Use command-stream for consistent $ behavior across runtimes
24
- const { $ } = await use('command-stream');
25
-
24
+ const { $: __rawDollar$ } = await use('command-stream');
25
+ const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
26
+ const $ = wrapDollarWithGhRetry(__rawDollar$);
26
27
  // Import shared library functions
27
28
  const lib = await import('./lib.mjs');
28
29
  const { log, formatAligned } = lib;
@@ -17,8 +17,9 @@ if (typeof globalThis.use === 'undefined') {
17
17
  const use = globalThis.use;
18
18
 
19
19
  // Use command-stream for consistent $ behavior across runtimes
20
- const { $ } = await use('command-stream');
21
-
20
+ const { $: __rawDollar$ } = await use('command-stream');
21
+ const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
22
+ const $ = wrapDollarWithGhRetry(__rawDollar$);
22
23
  // Import shared library functions
23
24
  const lib = await import('./lib.mjs');
24
25
  const { log, cleanErrorMessage, formatAligned, getLogFile } = lib;
@@ -58,6 +59,10 @@ const { checkForExistingComment, checkForNonBotComments, getMergeBlockers } = au
58
59
  const toolComments = await import('./tool-comments.lib.mjs');
59
60
  const { READY_TO_MERGE_MARKER, AUTO_RESTART_MARKER, AUTO_MERGED_MARKER, postTrackedComment } = toolComments;
60
61
 
62
+ // Issue #1728: Per-iteration working session summary attachment helper
63
+ const resultsLib = await import('./solve.results.lib.mjs');
64
+ const { maybeAttachWorkingSessionSummary } = resultsLib;
65
+
61
66
  // Issue #1574: Interruptible sleep so CTRL+C is never blocked by a lingering timer
62
67
  const { interruptibleSleep } = await import('./interruptible-sleep.lib.mjs');
63
68
  const { formatAutoIterationLimit, hasReachedAutoIterationLimit, normalizeAutoIterationLimit, shouldSyncBeforeRestart } = await import('./auto-iteration-limits.lib.mjs');
@@ -593,6 +598,10 @@ No further AI sessions will be started automatically for this run. Please review
593
598
  // Execute the AI tool using shared utility
594
599
  await log(formatAligned('🔄', 'Restarting:', `Running ${argv.tool.toUpperCase()} to address issues...`));
595
600
 
601
+ // Issue #1728: Scope the AI-comment check that gates --auto-attach-solution-summary
602
+ // to comments posted during *this* iteration only, not across the whole watch loop.
603
+ const iterationStartTime = new Date();
604
+
596
605
  const toolResult = await executeToolIteration({
597
606
  issueUrl,
598
607
  owner,
@@ -890,6 +899,35 @@ No further AI sessions will be started automatically for this run. Please review
890
899
  }
891
900
  }
892
901
 
902
+ // Issue #1728: Attach a "Working session summary" comment for this
903
+ // iteration if the AI didn't post any comments of its own (and
904
+ // --auto-attach-solution-summary is enabled, which it is by default).
905
+ // Before this fix, only the top-level solve.mjs flow honoured this
906
+ // flag, so iterations inside auto-restart-until-mergeable silently
907
+ // dropped the AI's last message — see #1728.
908
+ try {
909
+ await maybeAttachWorkingSessionSummary({
910
+ argv,
911
+ resultSummary: toolResult.resultSummary,
912
+ workStartTime: iterationStartTime,
913
+ owner,
914
+ repo,
915
+ prNumber,
916
+ issueNumber,
917
+ success: true,
918
+ });
919
+ } catch (summaryError) {
920
+ reportError(summaryError, {
921
+ context: 'attach_auto_restart_working_session_summary',
922
+ prNumber,
923
+ owner,
924
+ repo,
925
+ iteration,
926
+ operation: 'attach_working_session_summary',
927
+ });
928
+ await log(formatAligned('', `âš ī¸ Working session summary error: ${cleanErrorMessage(summaryError)}`, '', 2));
929
+ }
930
+
893
931
  await log('');
894
932
  await log(formatAligned('✅', `${argv.tool.toUpperCase()} execution completed:`, 'Checking if PR is now mergeable...'));
895
933
  }
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { closingIssueNumbersContain, parseClosingIssueNumbers } from './pr-issue-linking.lib.mjs';
7
7
 
8
+ import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller
8
9
  export async function handleAutoPrCreation({ argv, tempDir, branchName, issueNumber, owner, repo, defaultBranch, forkedRepo, isContinueMode, prNumber, log, formatAligned, $, reportError, path, fs }) {
9
10
  // Skip auto-PR creation if:
10
11
  // 1. Auto-PR creation is disabled AND we're not in continue mode with no PR
@@ -9,6 +9,7 @@
9
9
  // Import Sentry integration
10
10
  import { reportError } from './sentry.lib.mjs';
11
11
 
12
+ import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller
12
13
  export async function handleBranchCheckoutError({ branchName, prNumber, errorOutput, issueUrl, owner, repo, tempDir, argv, formatAligned, log, $ }) {
13
14
  // Check if this is a PR from a fork
14
15
  let isForkPR = false;
@@ -498,12 +498,12 @@ export const SOLVE_OPTION_DEFINITIONS = {
498
498
  },
499
499
  'attach-solution-summary': {
500
500
  type: 'boolean',
501
- description: 'Attach the AI solution summary (from the result field) as a comment to the PR/issue after completion. The summary is extracted from the AI tool JSON output and posted under a "Solution summary" header.',
501
+ description: 'Attach the AI working session summary (from the result field) as a comment to the PR/issue after every working session. The summary is extracted from the AI tool JSON output and posted under a "Working session summary" header. Applies to the top-level run, auto-restart-until-mergeable iterations, and watch-mode iterations.',
502
502
  default: false,
503
503
  },
504
504
  'auto-attach-solution-summary': {
505
505
  type: 'boolean',
506
- description: 'Automatically attach solution summary only if the AI did not create any comments during the session. This provides visible feedback when the AI completes silently. Enabled by default; use --no-auto-attach-solution-summary to disable.',
506
+ description: 'Automatically attach a "Working session summary" comment at the end of any working session in which the AI did not create any comments itself. This provides visible feedback when the AI completes silently, including inside auto-restart-until-mergeable and watch-mode iterations. Enabled by default; use --no-auto-attach-solution-summary to disable.',
507
507
  default: true,
508
508
  },
509
509
  'auto-accept-invite': {
@@ -5,6 +5,7 @@
5
5
  // Import exit handler
6
6
  import { safeExit } from './exit-handler.lib.mjs';
7
7
 
8
+ import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller
8
9
  // Import Sentry integration
9
10
  import { reportError } from './sentry.lib.mjs';
10
11
 
@@ -12,8 +12,9 @@ if (typeof globalThis.use === 'undefined') {
12
12
  const use = globalThis.use;
13
13
 
14
14
  // Use command-stream for consistent $ behavior across runtimes
15
- const { $ } = await use('command-stream');
16
-
15
+ const { $: __rawDollar$ } = await use('command-stream');
16
+ const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
17
+ const $ = wrapDollarWithGhRetry(__rawDollar$);
17
18
  const os = (await use('os')).default;
18
19
  const path = (await use('path')).default;
19
20
  const fs = (await use('fs')).promises;
@@ -6,6 +6,7 @@
6
6
  // Import Sentry integration
7
7
  import { reportError } from './sentry.lib.mjs';
8
8
 
9
+ import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller
9
10
  export const detectAndCountFeedback = async params => {
10
11
  const { prNumber, branchName, owner, repo, issueNumber, isContinueMode, argv, mergeStateStatus, prState, workStartTime, log, formatAligned, cleanErrorMessage, $ } = params;
11
12
 
package/src/solve.mjs CHANGED
@@ -7,7 +7,9 @@ await handleSolveEarlyExit(earlyArgs);
7
7
 
8
8
  const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
9
9
  globalThis.use = use;
10
- const { $ } = await use('command-stream');
10
+ const { $: __rawDollar$ } = await use('command-stream');
11
+ const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
12
+ const $ = wrapDollarWithGhRetry(__rawDollar$);
11
13
  const config = await import('./solve.config.lib.mjs');
12
14
  const { initializeConfig, parseArguments } = config;
13
15
  // Import Sentry integration
@@ -29,7 +31,7 @@ const { processAutoContinueForIssue } = autoContinue;
29
31
  const repository = await import('./solve.repository.lib.mjs');
30
32
  const { setupTempDirectory, cleanupTempDirectory } = repository;
31
33
  const results = await import('./solve.results.lib.mjs');
32
- const { cleanupClaudeFile, showSessionSummary, verifyResults, buildClaudeResumeCommand, buildSolveResumeCommand, checkForAiCreatedComments, attachSolutionSummary, verifyPullRequestIssueLinkAfterAutoRestart } = results;
34
+ const { cleanupClaudeFile, showSessionSummary, verifyResults, buildClaudeResumeCommand, buildSolveResumeCommand, maybeAttachWorkingSessionSummary, verifyPullRequestIssueLinkAfterAutoRestart } = results;
33
35
  const claudeLib = await import('./claude.lib.mjs');
34
36
  const { executeClaude, checkPlaywrightMcpAvailability } = claudeLib;
35
37
  const githubLinking = await import('./github-linking.lib.mjs');
@@ -1104,40 +1106,22 @@ try {
1104
1106
  await safeExit(0, 'Auto-continue child process will handle post-processing');
1105
1107
  }
1106
1108
 
1107
- // Issue #1263: Handle solution summary attachment
1108
- // --attach-solution-summary: Always attach if result summary is available
1109
- // --auto-attach-solution-summary: Only attach if AI didn't create any comments during session
1110
- if (success && resultSummary && (argv.attachSolutionSummary || argv.autoAttachSolutionSummary)) {
1111
- let shouldAttachSummary = false;
1112
-
1113
- if (argv.attachSolutionSummary) {
1114
- // Explicit flag - always attach
1115
- shouldAttachSummary = true;
1116
- await log('📝 --attach-solution-summary enabled, attaching result summary...');
1117
- } else if (argv.autoAttachSolutionSummary) {
1118
- // Auto mode - only attach if AI didn't create comments
1119
- await log('🔍 Checking if AI created any comments during session (--auto-attach-solution-summary)...');
1120
- const aiCreatedComments = await checkForAiCreatedComments(workStartTime, owner, repo, prNumber, issueNumber);
1121
- if (aiCreatedComments) {
1122
- await log('â„šī¸ AI created comments during session, skipping solution summary attachment');
1123
- } else {
1124
- shouldAttachSummary = true;
1125
- await log('📝 No AI comments detected, attaching solution summary...');
1126
- }
1127
- }
1128
-
1129
- if (shouldAttachSummary) {
1130
- await attachSolutionSummary({
1131
- resultSummary,
1132
- prNumber,
1133
- issueNumber,
1134
- owner,
1135
- repo,
1136
- });
1137
- }
1138
- } else if ((argv.attachSolutionSummary || argv.autoAttachSolutionSummary) && !resultSummary) {
1139
- await log('â„šī¸ No solution summary available from AI tool output', { verbose: true });
1140
- }
1109
+ // Issue #1263 / #1728: Working session summary attachment.
1110
+ // Routed through the shared maybeAttachWorkingSessionSummary helper so that
1111
+ // top-level solve, auto-restart-until-mergeable, and watch-mode iterations
1112
+ // all use identical attach logic. The helper internally honours
1113
+ // --attach-solution-summary (always attach) and --auto-attach-solution-summary
1114
+ // (attach only if no AI comment was posted during the session).
1115
+ await maybeAttachWorkingSessionSummary({
1116
+ argv,
1117
+ resultSummary,
1118
+ workStartTime,
1119
+ owner,
1120
+ repo,
1121
+ prNumber,
1122
+ issueNumber,
1123
+ success,
1124
+ });
1141
1125
 
1142
1126
  // Search for newly created pull requests and comments
1143
1127
  const verifyResult = await verifyResults(owner, repo, branchName, issueNumber, prNumber, prUrl, referenceTime, argv, shouldAttachLogs, shouldRestart, sessionId, tempDir, anthropicTotalCostUSD, publicPricingEstimate, pricingInfo, errorDuringExecution, sessionType, resultModelUsage, streamTokenUsage, subAgentCalls);
@@ -3,6 +3,7 @@
3
3
  * Handles timestamp collection, feedback detection, and pre-execution checks
4
4
  */
5
5
 
6
+ import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller
6
7
  // Import feedback detection functionality
7
8
  const feedback = await import('./solve.feedback.lib.mjs');
8
9
  const { detectAndCountFeedback } = feedback;
@@ -28,6 +28,7 @@
28
28
  // comment is excluded from --auto-attach-solution-summary's AI-comment check.
29
29
  import { LIVE_PROGRESS_SECTION_START_MARKER, LIVE_PROGRESS_SECTION_END_MARKER, postTrackedCommentFromFile, trackToolCommentId } from './tool-comments.lib.mjs';
30
30
 
31
+ import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller
31
32
  /**
32
33
  * Configuration constants for progress monitoring
33
34
  */
@@ -11,9 +11,9 @@ if (typeof globalThis.use === 'undefined') {
11
11
  }
12
12
  const use = globalThis.use;
13
13
 
14
- // Use command-stream for consistent $ behavior across runtimes
15
- const { $ } = await use('command-stream');
16
-
14
+ // Use command-stream for consistent $ behavior; wrap with rate-limit retry (#1726)
15
+ const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
16
+ const $ = wrapDollarWithGhRetry((await use('command-stream')).$);
17
17
  const os = (await use('os')).default;
18
18
  const path = (await use('path')).default;
19
19
  const fs = (await use('fs')).promises;
@@ -20,8 +20,9 @@ if (typeof globalThis.use === 'undefined') {
20
20
  const use = globalThis.use;
21
21
 
22
22
  // Use command-stream for consistent $ behavior across runtimes
23
- const { $ } = await use('command-stream');
24
-
23
+ const { $: __rawDollar$ } = await use('command-stream');
24
+ const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
25
+ const $ = wrapDollarWithGhRetry(__rawDollar$);
25
26
  // Import path and fs for cleanup operations
26
27
  const path = (await use('path')).default;
27
28
  const fs = (await use('fs')).promises;