@link-assistant/hive-mind 1.59.6 → 1.60.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.
@@ -7,7 +7,7 @@ const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text(
7
7
  const { $: __rawDollar$ } = await use('command-stream');
8
8
  const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
9
9
  const $ = wrapDollarWithGhRetry(__rawDollar$);
10
- const yargs = (await use('yargs@latest')).default;
10
+ const { getLinoYargsFactory, hideBin, parseCliArgumentsWithLino } = await import('./cli-arguments.lib.mjs');
11
11
  const path = (await use('path')).default;
12
12
  const fs = (await use('fs')).promises;
13
13
 
@@ -46,97 +46,113 @@ const log = async (message, options = {}) => {
46
46
  };
47
47
 
48
48
  // Configure command line arguments
49
- const argv = yargs(process.argv.slice(2))
50
- .usage('Usage: $0 <github-url> [options]')
51
- .positional('github-url', {
52
- type: 'string',
53
- description: 'GitHub organization, repository, or user URL to monitor for pull requests',
54
- })
55
- .option('review-label', {
56
- type: 'string',
57
- description: 'GitHub label to identify PRs needing review',
58
- default: 'needs-review',
59
- alias: 'l',
60
- })
61
- .option('all-prs', {
62
- type: 'boolean',
63
- description: 'Review all open pull requests regardless of labels',
64
- default: false,
65
- alias: 'a',
66
- })
67
- .option('skip-draft', {
68
- type: 'boolean',
69
- description: 'Skip draft pull requests',
70
- default: true,
71
- alias: 'd',
72
- })
73
- .option('skip-approved', {
74
- type: 'boolean',
75
- description: 'Skip pull requests that already have approvals',
76
- default: true,
77
- })
78
- .option('concurrency', {
79
- type: 'number',
80
- description: 'Number of concurrent review.mjs instances',
81
- default: 2,
82
- alias: 'c',
83
- })
84
- .option('reviews-per-pr', {
85
- type: 'number',
86
- description: 'Number of reviews to generate per PR (for diverse perspectives)',
87
- default: 1,
88
- alias: 'r',
89
- })
90
- .option('model', {
91
- type: 'string',
92
- description: 'Model to use for review.mjs (opus or sonnet)',
93
- alias: 'm',
94
- default: 'opus',
95
- choices: ['opus', 'sonnet'],
96
- })
97
- .option('focus', {
98
- type: 'string',
99
- description: 'Focus areas for reviews (security, performance, logic, style, tests, all)',
100
- default: 'all',
101
- alias: 'f',
102
- })
103
- .option('auto-approve', {
104
- type: 'boolean',
105
- description: 'Auto-approve PRs that pass review criteria',
106
- default: false,
107
- })
108
- .option('interval', {
109
- type: 'number',
110
- description: 'Polling interval in seconds',
111
- default: 300, // 5 minutes
112
- alias: 'i',
113
- })
114
- .option('max-prs', {
115
- type: 'number',
116
- description: 'Maximum number of PRs to process (0 = unlimited)',
117
- default: 0,
118
- })
119
- .option('dry-run', {
120
- type: 'boolean',
121
- description: 'List PRs that would be reviewed without actually reviewing them',
122
- default: false,
123
- })
124
- .option('verbose', {
125
- type: 'boolean',
126
- description: 'Enable verbose logging',
127
- alias: 'v',
128
- default: false,
129
- })
130
- .option('once', {
131
- type: 'boolean',
132
- description: 'Run once and exit instead of continuous monitoring',
133
- default: false,
134
- })
135
- .demandCommand(1, 'GitHub URL is required')
136
- .help('h')
137
- .alias('h', 'help').argv;
138
-
139
- const githubUrl = argv['github-url'] || argv._[0];
49
+ const createReviewersHiveYargsConfig = yargsInstance =>
50
+ yargsInstance
51
+ .usage('Usage: $0 <github-url> [options]')
52
+ .command('$0 <github-url>', 'Monitor pull requests for review', yargs =>
53
+ yargs.positional('github-url', {
54
+ type: 'string',
55
+ description: 'GitHub organization, repository, or user URL to monitor for pull requests',
56
+ })
57
+ )
58
+ .option('review-label', {
59
+ type: 'string',
60
+ description: 'GitHub label to identify PRs needing review',
61
+ default: 'needs-review',
62
+ alias: 'l',
63
+ })
64
+ .option('all-prs', {
65
+ type: 'boolean',
66
+ description: 'Review all open pull requests regardless of labels',
67
+ default: false,
68
+ alias: 'a',
69
+ })
70
+ .option('skip-draft', {
71
+ type: 'boolean',
72
+ description: 'Skip draft pull requests',
73
+ default: true,
74
+ alias: 'd',
75
+ })
76
+ .option('skip-approved', {
77
+ type: 'boolean',
78
+ description: 'Skip pull requests that already have approvals',
79
+ default: true,
80
+ })
81
+ .option('concurrency', {
82
+ type: 'number',
83
+ description: 'Number of concurrent review.mjs instances',
84
+ default: 2,
85
+ alias: 'c',
86
+ })
87
+ .option('reviews-per-pr', {
88
+ type: 'number',
89
+ description: 'Number of reviews to generate per PR (for diverse perspectives)',
90
+ default: 1,
91
+ alias: 'r',
92
+ })
93
+ .option('model', {
94
+ type: 'string',
95
+ description: 'Model to use for review.mjs (opus or sonnet)',
96
+ alias: 'm',
97
+ default: 'opus',
98
+ choices: ['opus', 'sonnet'],
99
+ })
100
+ .option('focus', {
101
+ type: 'string',
102
+ description: 'Focus areas for reviews (security, performance, logic, style, tests, all)',
103
+ default: 'all',
104
+ alias: 'f',
105
+ })
106
+ .option('auto-approve', {
107
+ type: 'boolean',
108
+ description: 'Auto-approve PRs that pass review criteria',
109
+ default: false,
110
+ })
111
+ .option('interval', {
112
+ type: 'number',
113
+ description: 'Polling interval in seconds',
114
+ default: 300, // 5 minutes
115
+ alias: 'i',
116
+ })
117
+ .option('max-prs', {
118
+ type: 'number',
119
+ description: 'Maximum number of PRs to process (0 = unlimited)',
120
+ default: 0,
121
+ })
122
+ .option('dry-run', {
123
+ type: 'boolean',
124
+ description: 'List PRs that would be reviewed without actually reviewing them',
125
+ default: false,
126
+ })
127
+ .option('verbose', {
128
+ type: 'boolean',
129
+ description: 'Enable verbose logging',
130
+ alias: 'v',
131
+ default: false,
132
+ })
133
+ .option('once', {
134
+ type: 'boolean',
135
+ description: 'Run once and exit instead of continuous monitoring',
136
+ default: false,
137
+ })
138
+ .demandCommand(1, 'GitHub URL is required')
139
+ .help('h')
140
+ .alias('h', 'help');
141
+
142
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
143
+ const helpYargs = createReviewersHiveYargsConfig(getLinoYargsFactory()(hideBin(process.argv)));
144
+ helpYargs.showHelp();
145
+ process.exit(0);
146
+ }
147
+
148
+ const argv = parseCliArgumentsWithLino({
149
+ argv: process.argv,
150
+ commandName: 'reviewers-hive',
151
+ createYargsConfig: createReviewersHiveYargsConfig,
152
+ positionalAliases: ['github-url'],
153
+ });
154
+
155
+ const githubUrl = argv['github-url'] || argv.githubUrl || argv._[0];
140
156
 
141
157
  // Set global verbose mode
142
158
  global.verboseMode = argv.verbose;
@@ -59,6 +59,10 @@ const { checkForExistingComment, checkForNonBotComments, getMergeBlockers } = au
59
59
  const toolComments = await import('./tool-comments.lib.mjs');
60
60
  const { READY_TO_MERGE_MARKER, AUTO_RESTART_MARKER, AUTO_MERGED_MARKER, postTrackedComment } = toolComments;
61
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
+
62
66
  // Issue #1574: Interruptible sleep so CTRL+C is never blocked by a lingering timer
63
67
  const { interruptibleSleep } = await import('./interruptible-sleep.lib.mjs');
64
68
  const { formatAutoIterationLimit, hasReachedAutoIterationLimit, normalizeAutoIterationLimit, shouldSyncBeforeRestart } = await import('./auto-iteration-limits.lib.mjs');
@@ -594,6 +598,10 @@ No further AI sessions will be started automatically for this run. Please review
594
598
  // Execute the AI tool using shared utility
595
599
  await log(formatAligned('🔄', 'Restarting:', `Running ${argv.tool.toUpperCase()} to address issues...`));
596
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
+
597
605
  const toolResult = await executeToolIteration({
598
606
  issueUrl,
599
607
  owner,
@@ -891,6 +899,35 @@ No further AI sessions will be started automatically for this run. Please review
891
899
  }
892
900
  }
893
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
+
894
931
  await log('');
895
932
  await log(formatAligned('✅', `${argv.tool.toUpperCase()} execution completed:`, 'Checking if PR is now mergeable...'));
896
933
  }
@@ -10,23 +10,13 @@
10
10
  import { enhanceErrorMessage, detectMalformedFlags } from './option-suggestions.lib.mjs';
11
11
  import { defaultModels, buildModelOptionDescription, resolveDefaultFallbackModel, resolveRuntimeDefaultModel } from './models/index.mjs';
12
12
  import { validateBranchName } from './solve.branch.lib.mjs';
13
- import { resolveYargsFactory } from './yargs-factory.lib.mjs';
13
+ import { getLinoYargsFactory, hideBin, parseCliArgumentsWithLino } from './cli-arguments.lib.mjs';
14
14
 
15
15
  // Re-export for use by telegram-bot.mjs (avoids extra import lines there)
16
16
  export { detectMalformedFlags };
17
17
 
18
18
  // Export an initialization function that accepts 'use'
19
- export const initializeConfig = async use => {
20
- // Import yargs with specific version for hideBin support
21
- const yargsModule = await use('yargs@17.7.2');
22
- const yargs = resolveYargsFactory(yargsModule);
23
- const helpersModule = await use('yargs@17.7.2/helpers');
24
- // Node 24 CJS/ESM interop may return the whole module object instead of named exports directly
25
- const helpers = helpersModule.default || helpersModule;
26
- const hideBin = helpers.hideBin || (argv => argv.slice(2));
27
-
28
- return { yargs, hideBin };
29
- };
19
+ export const initializeConfig = async () => ({ yargs: getLinoYargsFactory(), hideBin });
30
20
 
31
21
  // Solve option definitions as a plain data structure.
32
22
  // This is the single source of truth for all solve command options.
@@ -498,12 +488,12 @@ export const SOLVE_OPTION_DEFINITIONS = {
498
488
  },
499
489
  'attach-solution-summary': {
500
490
  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.',
491
+ 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
492
  default: false,
503
493
  },
504
494
  'auto-attach-solution-summary': {
505
495
  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.',
496
+ 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
497
  default: true,
508
498
  },
509
499
  'auto-accept-invite': {
@@ -582,8 +572,8 @@ export const createYargsConfig = yargsInstance => {
582
572
  };
583
573
 
584
574
  // Parse command line arguments - now needs yargs and hideBin passed in
585
- export const parseArguments = async (yargs, hideBin) => {
586
- const rawArgs = hideBin(process.argv);
575
+ export const parseArguments = async (yargs = getLinoYargsFactory(), hideBinFn = hideBin) => {
576
+ const rawArgs = hideBinFn(process.argv);
587
577
 
588
578
  // Issue #1092: Detect malformed flag patterns BEFORE yargs parsing
589
579
  // This catches cases like "-- model" which yargs silently treats as positional arguments
@@ -621,7 +611,12 @@ export const parseArguments = async (yargs, hideBin) => {
621
611
 
622
612
  try {
623
613
  yargsInstance = createYargsConfig(yargs());
624
- argv = await yargsInstance.parse(rawArgs);
614
+ argv = parseCliArgumentsWithLino({
615
+ argv: process.argv,
616
+ commandName: 'solve',
617
+ createYargsConfig,
618
+ positionalAliases: ['issue-url'],
619
+ });
625
620
  } finally {
626
621
  // Always restore stderr.write
627
622
  process.stderr.write = originalStderrWrite;
package/src/solve.mjs CHANGED
@@ -31,7 +31,7 @@ const { processAutoContinueForIssue } = autoContinue;
31
31
  const repository = await import('./solve.repository.lib.mjs');
32
32
  const { setupTempDirectory, cleanupTempDirectory } = repository;
33
33
  const results = await import('./solve.results.lib.mjs');
34
- const { cleanupClaudeFile, showSessionSummary, verifyResults, buildClaudeResumeCommand, buildSolveResumeCommand, checkForAiCreatedComments, attachSolutionSummary, verifyPullRequestIssueLinkAfterAutoRestart } = results;
34
+ const { cleanupClaudeFile, showSessionSummary, verifyResults, buildClaudeResumeCommand, buildSolveResumeCommand, maybeAttachWorkingSessionSummary, verifyPullRequestIssueLinkAfterAutoRestart } = results;
35
35
  const claudeLib = await import('./claude.lib.mjs');
36
36
  const { executeClaude, checkPlaywrightMcpAvailability } = claudeLib;
37
37
  const githubLinking = await import('./github-linking.lib.mjs');
@@ -1106,40 +1106,22 @@ try {
1106
1106
  await safeExit(0, 'Auto-continue child process will handle post-processing');
1107
1107
  }
1108
1108
 
1109
- // Issue #1263: Handle solution summary attachment
1110
- // --attach-solution-summary: Always attach if result summary is available
1111
- // --auto-attach-solution-summary: Only attach if AI didn't create any comments during session
1112
- if (success && resultSummary && (argv.attachSolutionSummary || argv.autoAttachSolutionSummary)) {
1113
- let shouldAttachSummary = false;
1114
-
1115
- if (argv.attachSolutionSummary) {
1116
- // Explicit flag - always attach
1117
- shouldAttachSummary = true;
1118
- await log('📝 --attach-solution-summary enabled, attaching result summary...');
1119
- } else if (argv.autoAttachSolutionSummary) {
1120
- // Auto mode - only attach if AI didn't create comments
1121
- await log('🔍 Checking if AI created any comments during session (--auto-attach-solution-summary)...');
1122
- const aiCreatedComments = await checkForAiCreatedComments(workStartTime, owner, repo, prNumber, issueNumber);
1123
- if (aiCreatedComments) {
1124
- await log('ℹ️ AI created comments during session, skipping solution summary attachment');
1125
- } else {
1126
- shouldAttachSummary = true;
1127
- await log('📝 No AI comments detected, attaching solution summary...');
1128
- }
1129
- }
1130
-
1131
- if (shouldAttachSummary) {
1132
- await attachSolutionSummary({
1133
- resultSummary,
1134
- prNumber,
1135
- issueNumber,
1136
- owner,
1137
- repo,
1138
- });
1139
- }
1140
- } else if ((argv.attachSolutionSummary || argv.autoAttachSolutionSummary) && !resultSummary) {
1141
- await log('ℹ️ No solution summary available from AI tool output', { verbose: true });
1142
- }
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
+ });
1143
1125
 
1144
1126
  // Search for newly created pull requests and comments
1145
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);
@@ -1179,11 +1179,17 @@ export const checkForAiCreatedComments = async (sessionStartTime, owner, repo, p
1179
1179
  };
1180
1180
 
1181
1181
  /**
1182
- * Attach the AI's solution summary as a comment to the PR or issue.
1182
+ * Attach the AI's working session summary as a comment to the PR or issue.
1183
1183
  * The summary is extracted from the tool's result field and posted
1184
- * with a "Solution summary" header.
1184
+ * with a "Working session summary" header.
1185
1185
  *
1186
1186
  * Issue #1263: Support for --attach-solution-summary and --auto-attach-solution-summary
1187
+ * Issue #1728: Renamed comment header from "Solution summary" to "Working session
1188
+ * summary" so it accurately describes continuation/restart iterations too. CLI
1189
+ * flag names are preserved for backwards compatibility. Posting now uses
1190
+ * postTrackedComment so the comment ID is registered in the in-memory tool-
1191
+ * comment set — that way the next iteration's --auto-attach-solution-summary
1192
+ * check doesn't mistake a previous iteration's summary for an AI comment.
1187
1193
  *
1188
1194
  * @param {Object} options - Options object
1189
1195
  * @param {string} options.resultSummary - The AI's result summary text
@@ -1195,34 +1201,33 @@ export const checkForAiCreatedComments = async (sessionStartTime, owner, repo, p
1195
1201
  */
1196
1202
  export const attachSolutionSummary = async ({ resultSummary, prNumber, issueNumber, owner, repo }) => {
1197
1203
  if (!resultSummary || typeof resultSummary !== 'string') {
1198
- await log('⚠️ No solution summary available to attach', { verbose: true });
1204
+ await log('⚠️ No working session summary available to attach', { verbose: true });
1199
1205
  return false;
1200
1206
  }
1201
1207
 
1202
1208
  const targetNumber = prNumber || issueNumber;
1203
1209
  const targetType = prNumber ? 'pr' : 'issue';
1204
- const ghCommand = prNumber ? 'pr' : 'issue';
1205
1210
 
1206
1211
  if (!targetNumber) {
1207
- await log('⚠️ No PR or issue number to attach solution summary to', { verbose: true });
1212
+ await log('⚠️ No PR or issue number to attach working session summary to', { verbose: true });
1208
1213
  return false;
1209
1214
  }
1210
1215
 
1211
1216
  try {
1212
- const comment = `## Solution summary
1217
+ const comment = `## Working session summary
1213
1218
 
1214
1219
  ${resultSummary}
1215
1220
 
1216
1221
  ---
1217
1222
  *This summary was automatically extracted from the AI working session output.*`;
1218
1223
 
1219
- const result = await $`gh ${ghCommand} comment ${targetNumber} --repo ${owner}/${repo} --body ${comment}`;
1224
+ const { ok, commentId, stderr } = await postTrackedComment({ $, owner, repo, targetNumber, body: comment });
1220
1225
 
1221
- if (result.code === 0) {
1222
- await log(`✅ Solution summary attached to ${targetType} #${targetNumber}`);
1226
+ if (ok) {
1227
+ await log(`✅ Working session summary attached to ${targetType} #${targetNumber}${commentId ? ` (id=${commentId})` : ''}`);
1223
1228
  return true;
1224
1229
  } else {
1225
- await log(`⚠️ Failed to attach solution summary: ${result.stderr?.toString() || 'Unknown error'}`, {
1230
+ await log(`⚠️ Failed to attach working session summary: ${stderr || 'Unknown error'}`, {
1226
1231
  level: 'warning',
1227
1232
  });
1228
1233
  return false;
@@ -1232,9 +1237,78 @@ ${resultSummary}
1232
1237
  context: 'attach_solution_summary',
1233
1238
  targetType,
1234
1239
  targetNumber,
1235
- operation: 'post_solution_summary_comment',
1240
+ operation: 'post_working_session_summary_comment',
1236
1241
  });
1237
- await log(`⚠️ Error attaching solution summary: ${error.message}`, { level: 'warning' });
1242
+ await log(`⚠️ Error attaching working session summary: ${error.message}`, { level: 'warning' });
1238
1243
  return false;
1239
1244
  }
1240
1245
  };
1246
+
1247
+ /**
1248
+ * Decide whether to attach a working session summary for a single working
1249
+ * session and, if so, post it. Single source of truth for the attach decision
1250
+ * shared by every working-session call site:
1251
+ *
1252
+ * - solve.mjs (top-level, end-of-run)
1253
+ * - solve.auto-merge.lib.mjs (auto-restart-until-mergeable iterations)
1254
+ * - solve.watch.lib.mjs (watch / temporary auto-restart iterations)
1255
+ *
1256
+ * Issue #1728: Before this helper, only solve.mjs ran the attach decision, so
1257
+ * iterations inside auto-restart-until-mergeable / watch silently dropped the
1258
+ * AI's `resultSummary` whenever the AI itself posted no comment. Centralising
1259
+ * the decision here means every working session ends with either an AI-authored
1260
+ * comment OR an automated "Working session summary" comment, matching the
1261
+ * issue's "unify logic for all working sessions" requirement.
1262
+ *
1263
+ * @param {Object} options
1264
+ * @param {Object} options.argv - parsed CLI arguments (reads attachSolutionSummary
1265
+ * and autoAttachSolutionSummary; flag names preserved for backwards compat)
1266
+ * @param {string|null|undefined} options.resultSummary - AI's last-message summary
1267
+ * @param {Date} options.workStartTime - the iteration's own start time, used to
1268
+ * scope the AI-comment check to this iteration only
1269
+ * @param {string} options.owner
1270
+ * @param {string} options.repo
1271
+ * @param {number|null} options.prNumber
1272
+ * @param {number|null} options.issueNumber
1273
+ * @param {boolean} [options.success=true] - skip attachment for failed iterations
1274
+ * @returns {Promise<{attached: boolean, reason: string}>}
1275
+ */
1276
+ export const maybeAttachWorkingSessionSummary = async ({ argv, resultSummary, workStartTime, owner, repo, prNumber, issueNumber, success = true }) => {
1277
+ if (!success) {
1278
+ return { attached: false, reason: 'iteration_failed' };
1279
+ }
1280
+
1281
+ const attachFlag = argv && (argv.attachSolutionSummary || argv['attach-solution-summary']);
1282
+ const autoAttachFlag = argv && (argv.autoAttachSolutionSummary || argv['auto-attach-solution-summary']);
1283
+
1284
+ if (!attachFlag && !autoAttachFlag) {
1285
+ return { attached: false, reason: 'flag_disabled' };
1286
+ }
1287
+
1288
+ if (!resultSummary || typeof resultSummary !== 'string') {
1289
+ await log('ℹ️ No working session summary available from AI tool output', { verbose: true });
1290
+ return { attached: false, reason: 'no_result_summary' };
1291
+ }
1292
+
1293
+ let shouldAttach = false;
1294
+ if (attachFlag) {
1295
+ shouldAttach = true;
1296
+ await log('📝 --attach-solution-summary enabled, attaching working session summary...');
1297
+ } else if (autoAttachFlag) {
1298
+ await log('🔍 Checking if AI created any comments during session (--auto-attach-solution-summary)...');
1299
+ const aiCreatedComments = await checkForAiCreatedComments(workStartTime, owner, repo, prNumber, issueNumber);
1300
+ if (aiCreatedComments) {
1301
+ await log('ℹ️ AI created comments during session, skipping working session summary attachment');
1302
+ return { attached: false, reason: 'ai_comments_present' };
1303
+ }
1304
+ shouldAttach = true;
1305
+ await log('📝 No AI comments detected, attaching working session summary...');
1306
+ }
1307
+
1308
+ if (!shouldAttach) {
1309
+ return { attached: false, reason: 'no_attach_decision' };
1310
+ }
1311
+
1312
+ const ok = await attachSolutionSummary({ resultSummary, prNumber, issueNumber, owner, repo });
1313
+ return { attached: !!ok, reason: ok ? 'attached' : 'post_failed' };
1314
+ };
@@ -46,6 +46,10 @@ const { formatAutoIterationLimit, hasReachedAutoIterationLimit, normalizeAutoIte
46
46
  const toolComments = await import('./tool-comments.lib.mjs');
47
47
  const { AUTO_RESTART_MARKER, postTrackedComment } = toolComments;
48
48
 
49
+ // Issue #1728: Per-iteration working session summary attachment helper
50
+ const resultsLib = await import('./solve.results.lib.mjs');
51
+ const { maybeAttachWorkingSessionSummary } = resultsLib;
52
+
49
53
  /**
50
54
  * Monitor for feedback in a loop and trigger restart when detected
51
55
  */
@@ -232,6 +236,10 @@ export const watchForFeedback = async params => {
232
236
  await log(formatAligned('🔄', 'Restarting:', `Re-running ${argv.tool.toUpperCase()} to handle feedback...`));
233
237
  }
234
238
 
239
+ // Issue #1728: Scope the AI-comment check that gates --auto-attach-solution-summary
240
+ // to comments posted during *this* iteration only, not across the whole watch loop.
241
+ const iterationStartTime = new Date();
242
+
235
243
  // Execute tool using shared utility
236
244
  const toolResult = await executeToolIteration({
237
245
  issueUrl,
@@ -424,6 +432,34 @@ export const watchForFeedback = async params => {
424
432
  }
425
433
  }
426
434
 
435
+ // Issue #1728: Attach a "Working session summary" comment for this
436
+ // iteration if the AI didn't post any comments of its own (and
437
+ // --auto-attach-solution-summary is enabled, which it is by default).
438
+ // Same fix as in solve.auto-merge.lib.mjs — every working session,
439
+ // not just the top-level run, should honour the auto-attach flag.
440
+ try {
441
+ await maybeAttachWorkingSessionSummary({
442
+ argv,
443
+ resultSummary: toolResult.resultSummary,
444
+ workStartTime: iterationStartTime,
445
+ owner,
446
+ repo,
447
+ prNumber,
448
+ issueNumber,
449
+ success: true,
450
+ });
451
+ } catch (summaryError) {
452
+ reportError(summaryError, {
453
+ context: 'attach_watch_working_session_summary',
454
+ prNumber,
455
+ owner,
456
+ repo,
457
+ autoRestartCount,
458
+ operation: 'attach_working_session_summary',
459
+ });
460
+ await log(formatAligned('', `⚠️ Working session summary error: ${cleanErrorMessage(summaryError)}`, '', 2));
461
+ }
462
+
427
463
  await log('');
428
464
  if (isTemporaryWatch) {
429
465
  await log(formatAligned('✅', `${argv.tool.toUpperCase()} execution completed:`, 'Checking for remaining changes...'));