@link-assistant/hive-mind 0.39.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 +20 -0
- package/LICENSE +24 -0
- package/README.md +769 -0
- package/package.json +58 -0
- package/src/agent.lib.mjs +705 -0
- package/src/agent.prompts.lib.mjs +196 -0
- package/src/buildUserMention.lib.mjs +71 -0
- package/src/claude-limits.lib.mjs +389 -0
- package/src/claude.lib.mjs +1445 -0
- package/src/claude.prompts.lib.mjs +203 -0
- package/src/codex.lib.mjs +552 -0
- package/src/codex.prompts.lib.mjs +194 -0
- package/src/config.lib.mjs +207 -0
- package/src/contributing-guidelines.lib.mjs +268 -0
- package/src/exit-handler.lib.mjs +205 -0
- package/src/git.lib.mjs +145 -0
- package/src/github-issue-creator.lib.mjs +246 -0
- package/src/github-linking.lib.mjs +152 -0
- package/src/github.batch.lib.mjs +272 -0
- package/src/github.graphql.lib.mjs +258 -0
- package/src/github.lib.mjs +1479 -0
- package/src/hive.config.lib.mjs +254 -0
- package/src/hive.mjs +1500 -0
- package/src/instrument.mjs +191 -0
- package/src/interactive-mode.lib.mjs +1000 -0
- package/src/lenv-reader.lib.mjs +206 -0
- package/src/lib.mjs +490 -0
- package/src/lino.lib.mjs +176 -0
- package/src/local-ci-checks.lib.mjs +324 -0
- package/src/memory-check.mjs +419 -0
- package/src/model-mapping.lib.mjs +145 -0
- package/src/model-validation.lib.mjs +278 -0
- package/src/opencode.lib.mjs +479 -0
- package/src/opencode.prompts.lib.mjs +194 -0
- package/src/protect-branch.mjs +159 -0
- package/src/review.mjs +433 -0
- package/src/reviewers-hive.mjs +643 -0
- package/src/sentry.lib.mjs +284 -0
- package/src/solve.auto-continue.lib.mjs +568 -0
- package/src/solve.auto-pr.lib.mjs +1374 -0
- package/src/solve.branch-errors.lib.mjs +341 -0
- package/src/solve.branch.lib.mjs +230 -0
- package/src/solve.config.lib.mjs +342 -0
- package/src/solve.error-handlers.lib.mjs +256 -0
- package/src/solve.execution.lib.mjs +291 -0
- package/src/solve.feedback.lib.mjs +436 -0
- package/src/solve.mjs +1128 -0
- package/src/solve.preparation.lib.mjs +210 -0
- package/src/solve.repo-setup.lib.mjs +114 -0
- package/src/solve.repository.lib.mjs +961 -0
- package/src/solve.results.lib.mjs +558 -0
- package/src/solve.session.lib.mjs +135 -0
- package/src/solve.validation.lib.mjs +325 -0
- package/src/solve.watch.lib.mjs +572 -0
- package/src/start-screen.mjs +324 -0
- package/src/task.mjs +308 -0
- package/src/telegram-bot.mjs +1481 -0
- package/src/telegram-markdown.lib.mjs +64 -0
- package/src/usage-limit.lib.mjs +218 -0
- package/src/version.lib.mjs +41 -0
- package/src/youtrack/solve.youtrack.lib.mjs +116 -0
- package/src/youtrack/youtrack-sync.mjs +219 -0
- package/src/youtrack/youtrack.lib.mjs +425 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback detection module for solve.mjs
|
|
3
|
+
* Handles comment counting and feedback detection for continue mode
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Import Sentry integration
|
|
7
|
+
import { reportError } from './sentry.lib.mjs';
|
|
8
|
+
|
|
9
|
+
export const detectAndCountFeedback = async (params) => {
|
|
10
|
+
const {
|
|
11
|
+
prNumber,
|
|
12
|
+
branchName,
|
|
13
|
+
owner,
|
|
14
|
+
repo,
|
|
15
|
+
issueNumber,
|
|
16
|
+
isContinueMode,
|
|
17
|
+
argv,
|
|
18
|
+
mergeStateStatus,
|
|
19
|
+
prState,
|
|
20
|
+
workStartTime,
|
|
21
|
+
log,
|
|
22
|
+
formatAligned,
|
|
23
|
+
cleanErrorMessage,
|
|
24
|
+
$
|
|
25
|
+
} = params;
|
|
26
|
+
|
|
27
|
+
let newPrComments = 0;
|
|
28
|
+
let newIssueComments = 0;
|
|
29
|
+
let commentInfo = '';
|
|
30
|
+
let feedbackLines = [];
|
|
31
|
+
let currentUser = null;
|
|
32
|
+
|
|
33
|
+
// Get current GitHub user to filter out own comments
|
|
34
|
+
try {
|
|
35
|
+
const userResult = await $`gh api user --jq .login`;
|
|
36
|
+
if (userResult.code === 0) {
|
|
37
|
+
currentUser = userResult.stdout.toString().trim();
|
|
38
|
+
await log(formatAligned('👤', 'Current user:', currentUser, 2));
|
|
39
|
+
}
|
|
40
|
+
} catch (error) {
|
|
41
|
+
reportError(error, {
|
|
42
|
+
context: 'get_current_user',
|
|
43
|
+
operation: 'gh_api_user'
|
|
44
|
+
});
|
|
45
|
+
await log('Warning: Could not get current GitHub user', { level: 'warning' });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Debug logging to understand when comment counting doesn't run
|
|
49
|
+
if (argv.verbose) {
|
|
50
|
+
await log('\n📊 Comment counting conditions:', { verbose: true });
|
|
51
|
+
await log(` prNumber: ${prNumber || 'NOT SET'}`, { verbose: true });
|
|
52
|
+
await log(` branchName: ${branchName || 'NOT SET'}`, { verbose: true });
|
|
53
|
+
await log(` isContinueMode: ${isContinueMode}`, { verbose: true });
|
|
54
|
+
await log(` Will count comments: ${!!(prNumber && branchName)}`, { verbose: true });
|
|
55
|
+
if (!prNumber) {
|
|
56
|
+
await log(' ⚠️ Skipping: prNumber not set', { verbose: true });
|
|
57
|
+
}
|
|
58
|
+
if (!branchName) {
|
|
59
|
+
await log(' ⚠️ Skipping: branchName not set', { verbose: true });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (prNumber && branchName) {
|
|
64
|
+
try {
|
|
65
|
+
await log(`${formatAligned('💬', 'Counting comments:', 'Checking for new comments since last commit...')}`);
|
|
66
|
+
if (argv.verbose) {
|
|
67
|
+
await log(` PR #${prNumber} on branch: ${branchName}`, { verbose: true });
|
|
68
|
+
await log(` Owner/Repo: ${owner}/${repo}`, { verbose: true });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Get the last commit timestamp from the PR branch
|
|
72
|
+
let lastCommitTime = null;
|
|
73
|
+
let lastCommitResult = await $`git log -1 --format="%aI" origin/${branchName}`;
|
|
74
|
+
if (lastCommitResult.code !== 0) {
|
|
75
|
+
// Fallback to local branch if remote doesn't exist
|
|
76
|
+
lastCommitResult = await $`git log -1 --format="%aI" ${branchName}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (lastCommitResult.code === 0) {
|
|
80
|
+
lastCommitTime = new Date(lastCommitResult.stdout.toString().trim());
|
|
81
|
+
await log(formatAligned('📅', 'Last commit time:', lastCommitTime.toISOString(), 2));
|
|
82
|
+
} else {
|
|
83
|
+
// Fallback: Get last commit time from GitHub API
|
|
84
|
+
try {
|
|
85
|
+
const prCommitsResult = await $`gh api repos/${owner}/${repo}/pulls/${prNumber}/commits --jq 'last.commit.author.date'`;
|
|
86
|
+
if (prCommitsResult.code === 0 && prCommitsResult.stdout) {
|
|
87
|
+
lastCommitTime = new Date(prCommitsResult.stdout.toString().trim());
|
|
88
|
+
await log(formatAligned('📅', 'Last commit time (from API):', lastCommitTime.toISOString(), 2));
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
reportError(error, {
|
|
92
|
+
context: 'get_last_commit_time',
|
|
93
|
+
prNumber,
|
|
94
|
+
operation: 'fetch_commit_timestamp'
|
|
95
|
+
});
|
|
96
|
+
await log(`Warning: Could not get last commit time: ${cleanErrorMessage(error)}`, { level: 'warning' });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Only proceed if we have a last commit time
|
|
101
|
+
if (lastCommitTime) {
|
|
102
|
+
|
|
103
|
+
// Define log patterns to filter out comments containing logs from solve.mjs
|
|
104
|
+
const logPatterns = [
|
|
105
|
+
/📊.*Log file|solution\s+draft.*log/i,
|
|
106
|
+
/🔗.*Link:|💻.*Session:/i,
|
|
107
|
+
/Generated with.*solve\.mjs/i,
|
|
108
|
+
/Session ID:|Log file available:/i
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
// Count new PR comments after last commit (both code review comments and conversation comments)
|
|
112
|
+
let prReviewComments = [];
|
|
113
|
+
let prConversationComments = [];
|
|
114
|
+
|
|
115
|
+
// Get PR code review comments
|
|
116
|
+
const prReviewCommentsResult = await $`gh api repos/${owner}/${repo}/pulls/${prNumber}/comments`;
|
|
117
|
+
if (prReviewCommentsResult.code === 0) {
|
|
118
|
+
prReviewComments = JSON.parse(prReviewCommentsResult.stdout.toString());
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Get PR conversation comments (PR is also an issue)
|
|
122
|
+
const prConversationCommentsResult = await $`gh api repos/${owner}/${repo}/issues/${prNumber}/comments`;
|
|
123
|
+
if (prConversationCommentsResult.code === 0) {
|
|
124
|
+
prConversationComments = JSON.parse(prConversationCommentsResult.stdout.toString());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Combine and count all PR comments after last commit
|
|
128
|
+
// Filter out comments from current user if made after work started AND filter out log comments
|
|
129
|
+
const allPrComments = [...prReviewComments, ...prConversationComments];
|
|
130
|
+
const filteredPrComments = allPrComments.filter(comment => {
|
|
131
|
+
const commentTime = new Date(comment.created_at);
|
|
132
|
+
const isAfterCommit = commentTime > lastCommitTime;
|
|
133
|
+
const isNotLogPattern = !logPatterns.some(pattern => pattern.test(comment.body || ''));
|
|
134
|
+
|
|
135
|
+
// If we have a work start time and current user, filter out comments made by claude tool after work started
|
|
136
|
+
if (workStartTime && currentUser && comment.user && comment.user.login === currentUser) {
|
|
137
|
+
const isAfterWorkStart = commentTime > new Date(workStartTime);
|
|
138
|
+
if (isAfterWorkStart && argv.verbose) {
|
|
139
|
+
// Note: Filtering out own comment from user after work started
|
|
140
|
+
}
|
|
141
|
+
return isAfterCommit && !isAfterWorkStart && isNotLogPattern;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return isAfterCommit && isNotLogPattern;
|
|
145
|
+
});
|
|
146
|
+
newPrComments = filteredPrComments.length;
|
|
147
|
+
|
|
148
|
+
// Count new issue comments after last commit
|
|
149
|
+
const issueCommentsResult = await $`gh api repos/${owner}/${repo}/issues/${issueNumber}/comments`;
|
|
150
|
+
if (issueCommentsResult.code === 0) {
|
|
151
|
+
const issueComments = JSON.parse(issueCommentsResult.stdout.toString());
|
|
152
|
+
const filteredIssueComments = issueComments.filter(comment => {
|
|
153
|
+
const commentTime = new Date(comment.created_at);
|
|
154
|
+
const isAfterCommit = commentTime > lastCommitTime;
|
|
155
|
+
const isNotLogPattern = !logPatterns.some(pattern => pattern.test(comment.body || ''));
|
|
156
|
+
|
|
157
|
+
// If we have a work start time and current user, filter out comments made by claude tool after work started
|
|
158
|
+
if (workStartTime && currentUser && comment.user && comment.user.login === currentUser) {
|
|
159
|
+
const isAfterWorkStart = commentTime > new Date(workStartTime);
|
|
160
|
+
if (isAfterWorkStart && argv.verbose) {
|
|
161
|
+
// Note: Filtering out own issue comment from user after work started
|
|
162
|
+
}
|
|
163
|
+
return isAfterCommit && !isAfterWorkStart && isNotLogPattern;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return isAfterCommit && isNotLogPattern;
|
|
167
|
+
});
|
|
168
|
+
newIssueComments = filteredIssueComments.length;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
await log(formatAligned('💬', 'New PR comments:', newPrComments.toString(), 2));
|
|
172
|
+
await log(formatAligned('💬', 'New issue comments:', newIssueComments.toString(), 2));
|
|
173
|
+
|
|
174
|
+
if (argv.verbose) {
|
|
175
|
+
await log(` Total new comments: ${newPrComments + newIssueComments}`, { verbose: true });
|
|
176
|
+
await log(` Comment lines to add: ${newPrComments > 0 || newIssueComments > 0 ? 'Yes' : 'No (saving tokens)'}`, { verbose: true });
|
|
177
|
+
await log(` PR review comments fetched: ${prReviewComments.length}`, { verbose: true });
|
|
178
|
+
await log(` PR conversation comments fetched: ${prConversationComments.length}`, { verbose: true });
|
|
179
|
+
await log(` Total PR comments checked: ${allPrComments.length}`, { verbose: true });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Check if --auto-continue-only-on-new-comments is enabled and fail if no new comments
|
|
183
|
+
if (argv.autoContinueOnlyOnNewComments && (isContinueMode || argv.autoContinue)) {
|
|
184
|
+
const totalNewComments = newPrComments + newIssueComments;
|
|
185
|
+
if (totalNewComments === 0) {
|
|
186
|
+
await log('❌ auto-continue-only-on-new-comments: No new comments found since last commit');
|
|
187
|
+
await log(' This option requires new comments to proceed with auto-continue or continue mode.');
|
|
188
|
+
process.exit(1);
|
|
189
|
+
} else {
|
|
190
|
+
await log(`✅ auto-continue-only-on-new-comments: Found ${totalNewComments} new comments, continuing...`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Build comprehensive feedback info for system prompt
|
|
195
|
+
feedbackLines = []; // Reset for this execution
|
|
196
|
+
let feedbackDetected = false;
|
|
197
|
+
const feedbackSources = [];
|
|
198
|
+
|
|
199
|
+
// Add comment info if counts are > 0 to avoid wasting tokens
|
|
200
|
+
if (newPrComments > 0) {
|
|
201
|
+
feedbackLines.push(`New comments on the pull request: ${newPrComments}`);
|
|
202
|
+
}
|
|
203
|
+
if (newIssueComments > 0) {
|
|
204
|
+
feedbackLines.push(`New comments on the issue: ${newIssueComments}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Enhanced feedback detection for all continue modes
|
|
208
|
+
if (isContinueMode || argv.autoContinue) {
|
|
209
|
+
if (argv.continueOnlyOnFeedback) {
|
|
210
|
+
await log(`${formatAligned('🔍', 'Feedback detection:', 'Checking for any feedback since last commit...')}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 1. Check for new comments (already filtered above)
|
|
214
|
+
const totalNewComments = newPrComments + newIssueComments;
|
|
215
|
+
if (totalNewComments > 0) {
|
|
216
|
+
feedbackDetected = true;
|
|
217
|
+
feedbackSources.push(`New comments (${totalNewComments})`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 2. Check for edited descriptions
|
|
221
|
+
// Issue #895: Filter out edits made during current work session to prevent
|
|
222
|
+
// infinite restart loops. When the agent updates the PR description as part of
|
|
223
|
+
// its work, this should not trigger a restart. Only external edits (before work
|
|
224
|
+
// started) should be considered feedback.
|
|
225
|
+
try {
|
|
226
|
+
// Check PR description edit time
|
|
227
|
+
const prDetailsResult = await $`gh api repos/${owner}/${repo}/pulls/${prNumber}`;
|
|
228
|
+
if (prDetailsResult.code === 0) {
|
|
229
|
+
const prDetails = JSON.parse(prDetailsResult.stdout.toString());
|
|
230
|
+
const prUpdatedAt = new Date(prDetails.updated_at);
|
|
231
|
+
if (prUpdatedAt > lastCommitTime) {
|
|
232
|
+
// Issue #895: Check if the edit happened during current work session
|
|
233
|
+
// If the PR was updated after work started, it's likely the agent's own edit
|
|
234
|
+
if (workStartTime && prUpdatedAt > new Date(workStartTime)) {
|
|
235
|
+
if (argv.verbose) {
|
|
236
|
+
await log(' Note: PR description updated during current work session (likely by agent itself) - ignoring', { verbose: true });
|
|
237
|
+
}
|
|
238
|
+
// Don't treat this as external feedback
|
|
239
|
+
} else {
|
|
240
|
+
// The PR was updated after last commit but before work started - external feedback
|
|
241
|
+
feedbackLines.push('Pull request description was edited after last commit');
|
|
242
|
+
feedbackDetected = true;
|
|
243
|
+
feedbackSources.push('PR description edited');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Check issue description edit time if we have an issue
|
|
249
|
+
if (issueNumber) {
|
|
250
|
+
const issueDetailsResult = await $`gh api repos/${owner}/${repo}/issues/${issueNumber}`;
|
|
251
|
+
if (issueDetailsResult.code === 0) {
|
|
252
|
+
const issueDetails = JSON.parse(issueDetailsResult.stdout.toString());
|
|
253
|
+
const issueUpdatedAt = new Date(issueDetails.updated_at);
|
|
254
|
+
if (issueUpdatedAt > lastCommitTime) {
|
|
255
|
+
// Issue #895: Check if the edit happened during current work session
|
|
256
|
+
if (workStartTime && issueUpdatedAt > new Date(workStartTime)) {
|
|
257
|
+
if (argv.verbose) {
|
|
258
|
+
await log(' Note: Issue description updated during current work session (likely by agent itself) - ignoring', { verbose: true });
|
|
259
|
+
}
|
|
260
|
+
// Don't treat this as external feedback
|
|
261
|
+
} else {
|
|
262
|
+
// The issue was updated after last commit but before work started - external feedback
|
|
263
|
+
feedbackLines.push('Issue description was edited after last commit');
|
|
264
|
+
feedbackDetected = true;
|
|
265
|
+
feedbackSources.push('Issue description edited');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
} catch (error) {
|
|
271
|
+
reportError(error, {
|
|
272
|
+
context: 'check_description_edits',
|
|
273
|
+
prNumber,
|
|
274
|
+
operation: 'fetch_pr_timeline'
|
|
275
|
+
});
|
|
276
|
+
if (argv.verbose) {
|
|
277
|
+
await log(`Warning: Could not check description edit times: ${cleanErrorMessage(error)}`, { level: 'warning' });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 3. Check for new commits on default branch
|
|
282
|
+
try {
|
|
283
|
+
const defaultBranchResult = await $`gh api repos/${owner}/${repo}`;
|
|
284
|
+
if (defaultBranchResult.code === 0) {
|
|
285
|
+
const repoData = JSON.parse(defaultBranchResult.stdout.toString());
|
|
286
|
+
const defaultBranch = repoData.default_branch;
|
|
287
|
+
|
|
288
|
+
const commitsResult = await $`gh api repos/${owner}/${repo}/commits --field sha=${defaultBranch} --field since=${lastCommitTime.toISOString()}`;
|
|
289
|
+
if (commitsResult.code === 0) {
|
|
290
|
+
const commits = JSON.parse(commitsResult.stdout.toString());
|
|
291
|
+
if (commits.length > 0) {
|
|
292
|
+
feedbackLines.push(`New commits on ${defaultBranch} branch: ${commits.length}`);
|
|
293
|
+
feedbackDetected = true;
|
|
294
|
+
feedbackSources.push(`New commits on ${defaultBranch} (${commits.length})`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} catch (error) {
|
|
299
|
+
reportError(error, {
|
|
300
|
+
context: 'check_branch_commits',
|
|
301
|
+
branchName,
|
|
302
|
+
operation: 'fetch_commit_messages'
|
|
303
|
+
});
|
|
304
|
+
if (argv.verbose) {
|
|
305
|
+
await log(`Warning: Could not check default branch commits: ${cleanErrorMessage(error)}`, { level: 'warning' });
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 4. Check pull request state (non-open indicates closed or merged)
|
|
310
|
+
if (prState && prState !== 'OPEN') {
|
|
311
|
+
feedbackLines.push(`Pull request state: ${prState}`);
|
|
312
|
+
feedbackDetected = true;
|
|
313
|
+
feedbackSources.push(`PR state ${prState}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// 5. Check merge status (non-clean indicates issues with merging)
|
|
317
|
+
if (mergeStateStatus && mergeStateStatus !== 'CLEAN') {
|
|
318
|
+
const statusDescriptions = {
|
|
319
|
+
'DIRTY': 'Merge status is DIRTY (conflicts detected)',
|
|
320
|
+
'UNSTABLE': 'Merge status is UNSTABLE (non-passing commit status)',
|
|
321
|
+
'BLOCKED': 'Merge status is BLOCKED',
|
|
322
|
+
'BEHIND': 'Merge status is BEHIND (head ref is out of date)',
|
|
323
|
+
'HAS_HOOKS': 'Merge status is HAS_HOOKS (has pre-receive hooks)',
|
|
324
|
+
'UNKNOWN': 'Merge status is UNKNOWN'
|
|
325
|
+
};
|
|
326
|
+
const description = statusDescriptions[mergeStateStatus] || `Merge status is ${mergeStateStatus}`;
|
|
327
|
+
feedbackLines.push(description);
|
|
328
|
+
feedbackDetected = true;
|
|
329
|
+
feedbackSources.push(`Merge status ${mergeStateStatus}`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// 6. Check for failed PR checks
|
|
333
|
+
try {
|
|
334
|
+
const checksResult = await $`gh api repos/${owner}/${repo}/commits/$(gh api repos/${owner}/${repo}/pulls/${prNumber} --jq '.head.sha')/check-runs`;
|
|
335
|
+
if (checksResult.code === 0) {
|
|
336
|
+
const checksData = JSON.parse(checksResult.stdout.toString());
|
|
337
|
+
const failedChecks = checksData.check_runs?.filter(check =>
|
|
338
|
+
check.conclusion === 'failure' && new Date(check.completed_at) > lastCommitTime
|
|
339
|
+
) || [];
|
|
340
|
+
|
|
341
|
+
if (failedChecks.length > 0) {
|
|
342
|
+
feedbackLines.push(`Failed pull request checks: ${failedChecks.length}`);
|
|
343
|
+
feedbackDetected = true;
|
|
344
|
+
feedbackSources.push(`Failed PR checks (${failedChecks.length})`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} catch (error) {
|
|
348
|
+
reportError(error, {
|
|
349
|
+
context: 'check_pr_status_checks',
|
|
350
|
+
prNumber,
|
|
351
|
+
operation: 'fetch_status_checks'
|
|
352
|
+
});
|
|
353
|
+
if (argv.verbose) {
|
|
354
|
+
await log(`Warning: Could not check PR status checks: ${cleanErrorMessage(error)}`, { level: 'warning' });
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// 7. Check for review requests with changes requested
|
|
359
|
+
try {
|
|
360
|
+
const reviewsResult = await $`gh api repos/${owner}/${repo}/pulls/${prNumber}/reviews`;
|
|
361
|
+
if (reviewsResult.code === 0) {
|
|
362
|
+
const reviews = JSON.parse(reviewsResult.stdout.toString());
|
|
363
|
+
const changesRequestedReviews = reviews.filter(review =>
|
|
364
|
+
review.state === 'CHANGES_REQUESTED' && new Date(review.submitted_at) > lastCommitTime
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
if (changesRequestedReviews.length > 0) {
|
|
368
|
+
feedbackLines.push(`Changes requested in reviews: ${changesRequestedReviews.length}`);
|
|
369
|
+
feedbackDetected = true;
|
|
370
|
+
feedbackSources.push(`Changes requested (${changesRequestedReviews.length})`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
} catch (error) {
|
|
374
|
+
reportError(error, {
|
|
375
|
+
context: 'check_pr_reviews',
|
|
376
|
+
prNumber,
|
|
377
|
+
operation: 'fetch_pr_reviews'
|
|
378
|
+
});
|
|
379
|
+
if (argv.verbose) {
|
|
380
|
+
await log(`Warning: Could not check PR reviews: ${cleanErrorMessage(error)}`, { level: 'warning' });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Handle --continue-only-on-feedback option
|
|
385
|
+
if (argv.continueOnlyOnFeedback) {
|
|
386
|
+
if (feedbackDetected) {
|
|
387
|
+
await log('✅ continue-only-on-feedback: Feedback detected, continuing...');
|
|
388
|
+
await log(formatAligned('📋', 'Feedback sources:', feedbackSources.join(', '), 2));
|
|
389
|
+
} else {
|
|
390
|
+
await log('❌ continue-only-on-feedback: No feedback detected since last commit');
|
|
391
|
+
await log(' This option requires any of the following to proceed:');
|
|
392
|
+
await log(' • New comments (excluding solve.mjs logs)');
|
|
393
|
+
await log(' • Edited issue/PR descriptions');
|
|
394
|
+
await log(' • New commits on default branch');
|
|
395
|
+
await log(' • Pull request state is not OPEN (closed or merged)');
|
|
396
|
+
await log(' • Merge status is not CLEAN (conflicts, unstable, blocked, etc.)');
|
|
397
|
+
await log(' • Failed pull request checks');
|
|
398
|
+
await log(' • Changes requested via review');
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (feedbackLines.length > 0) {
|
|
405
|
+
commentInfo = '\n\n' + feedbackLines.join('\n') + '\n';
|
|
406
|
+
if (argv.verbose) {
|
|
407
|
+
await log(' Feedback info will be added to prompt:', { verbose: true });
|
|
408
|
+
feedbackLines.forEach(async line => {
|
|
409
|
+
await log(` - ${line}`, { verbose: true });
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
} else if (argv.verbose) {
|
|
413
|
+
await log(' No feedback info to add (0 new items, saving tokens)', { verbose: true });
|
|
414
|
+
}
|
|
415
|
+
} else {
|
|
416
|
+
await log('Warning: Could not determine last commit time, skipping comment counting', { level: 'warning' });
|
|
417
|
+
}
|
|
418
|
+
} catch (error) {
|
|
419
|
+
reportError(error, {
|
|
420
|
+
context: 'count_new_comments',
|
|
421
|
+
prNumber,
|
|
422
|
+
operation: 'detect_and_count_feedback'
|
|
423
|
+
});
|
|
424
|
+
await log(`Warning: Could not count new comments: ${cleanErrorMessage(error)}`, { level: 'warning' });
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
await log(formatAligned('⚠️', 'Skipping comment count:', prNumber ? 'branchName not set' : 'prNumber not set', 2));
|
|
428
|
+
if (argv.verbose) {
|
|
429
|
+
await log(` prNumber: ${prNumber || 'NOT SET'}`, { verbose: true });
|
|
430
|
+
await log(` branchName: ${branchName || 'NOT SET'}`, { verbose: true });
|
|
431
|
+
await log(' This means no new comment detection will run', { verbose: true });
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return { newPrComments, newIssueComments, commentInfo, feedbackLines };
|
|
436
|
+
};
|