@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.
- package/CHANGELOG.md +44 -0
- package/package.json +3 -1
- package/src/cli-arguments.lib.mjs +68 -0
- package/src/configure-claude.lib.mjs +37 -14
- package/src/hive-screens.lib.mjs +36 -22
- package/src/hive.mjs +10 -16
- package/src/memory-check.mjs +10 -11
- package/src/review.mjs +72 -59
- package/src/reviewers-hive.mjs +108 -92
- package/src/solve.auto-merge.lib.mjs +37 -0
- package/src/solve.config.lib.mjs +12 -17
- package/src/solve.mjs +17 -35
- package/src/solve.results.lib.mjs +86 -12
- package/src/solve.watch.lib.mjs +36 -0
- package/src/start-screen.mjs +74 -15
- package/src/task.agent-command.lib.mjs +61 -0
- package/src/task.config.lib.mjs +122 -0
- package/src/task.mjs +217 -232
- package/src/task.split.lib.mjs +221 -0
- package/src/telegram-bot.mjs +27 -111
- package/src/telegram-command-execution.lib.mjs +98 -0
- package/src/telegram-solve-queue.lib.mjs +2 -1
- package/src/telegram-task-command.lib.mjs +133 -0
- package/src/tool-comments.lib.mjs +12 -1
package/src/reviewers-hive.mjs
CHANGED
|
@@ -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
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
}
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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,
|
|
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:
|
|
1110
|
-
//
|
|
1111
|
-
//
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
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
|
|
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 "
|
|
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
|
|
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
|
|
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 = `##
|
|
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
|
|
1224
|
+
const { ok, commentId, stderr } = await postTrackedComment({ $, owner, repo, targetNumber, body: comment });
|
|
1220
1225
|
|
|
1221
|
-
if (
|
|
1222
|
-
await log(`✅
|
|
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
|
|
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: '
|
|
1240
|
+
operation: 'post_working_session_summary_comment',
|
|
1236
1241
|
});
|
|
1237
|
-
await log(`⚠️ Error attaching
|
|
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
|
+
};
|
package/src/solve.watch.lib.mjs
CHANGED
|
@@ -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...'));
|