@link-assistant/hive-mind 1.12.0 â 1.13.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 +18 -0
- package/package.json +1 -1
- package/src/hive.config.lib.mjs +10 -0
- package/src/hive.mjs +2 -0
- package/src/solve.auto-merge.lib.mjs +598 -0
- package/src/solve.config.lib.mjs +10 -0
- package/src/solve.mjs +38 -0
- package/src/solve.restart-shared.lib.mjs +372 -0
- package/src/solve.watch.lib.mjs +31 -280
package/src/solve.watch.lib.mjs
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Watch mode module for solve.mjs
|
|
5
5
|
* Monitors for feedback continuously and restarts when changes are detected
|
|
6
|
+
*
|
|
7
|
+
* Uses shared utilities from solve.restart-shared.lib.mjs for common functions.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
// Check if use is already defined globally (when imported from solve.mjs)
|
|
@@ -15,10 +17,6 @@ const use = globalThis.use;
|
|
|
15
17
|
// Use command-stream for consistent $ behavior across runtimes
|
|
16
18
|
const { $ } = await use('command-stream');
|
|
17
19
|
|
|
18
|
-
// Import path and fs for cleanup operations
|
|
19
|
-
const path = (await use('path')).default;
|
|
20
|
-
const fs = (await use('fs')).promises;
|
|
21
|
-
|
|
22
20
|
// Import shared library functions
|
|
23
21
|
const lib = await import('./lib.mjs');
|
|
24
22
|
const { log, cleanErrorMessage, formatAligned, getLogFile } = lib;
|
|
@@ -35,75 +33,9 @@ const { sanitizeLogContent, attachLogToGitHub } = githubLib;
|
|
|
35
33
|
|
|
36
34
|
const { detectAndCountFeedback } = feedbackLib;
|
|
37
35
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const checkPRMerged = async (owner, repo, prNumber) => {
|
|
42
|
-
try {
|
|
43
|
-
const prResult = await $`gh api repos/${owner}/${repo}/pulls/${prNumber} --jq '.merged'`;
|
|
44
|
-
if (prResult.code === 0) {
|
|
45
|
-
return prResult.stdout.toString().trim() === 'true';
|
|
46
|
-
}
|
|
47
|
-
} catch (error) {
|
|
48
|
-
reportError(error, {
|
|
49
|
-
context: 'check_pr_merged',
|
|
50
|
-
owner,
|
|
51
|
-
repo,
|
|
52
|
-
prNumber,
|
|
53
|
-
operation: 'check_merge_status',
|
|
54
|
-
});
|
|
55
|
-
// If we can't check, assume not merged
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
return false;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Clean up .playwright-mcp/ folder to prevent browser automation artifacts
|
|
63
|
-
* from triggering auto-restart (Issue #1124)
|
|
64
|
-
*/
|
|
65
|
-
const cleanupPlaywrightMcpFolder = async (tempDir, argv) => {
|
|
66
|
-
if (argv.playwrightMcpAutoCleanup !== false) {
|
|
67
|
-
const playwrightMcpDir = path.join(tempDir, '.playwright-mcp');
|
|
68
|
-
try {
|
|
69
|
-
const playwrightMcpExists = await fs
|
|
70
|
-
.stat(playwrightMcpDir)
|
|
71
|
-
.then(() => true)
|
|
72
|
-
.catch(() => false);
|
|
73
|
-
if (playwrightMcpExists) {
|
|
74
|
-
await fs.rm(playwrightMcpDir, { recursive: true, force: true });
|
|
75
|
-
await log('đ§š Cleaned up .playwright-mcp/ folder (browser automation artifacts)', { verbose: true });
|
|
76
|
-
}
|
|
77
|
-
} catch (cleanupError) {
|
|
78
|
-
// Non-critical error, just log and continue
|
|
79
|
-
await log(`â ī¸ Could not clean up .playwright-mcp/ folder: ${cleanupError.message}`, { verbose: true });
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Check if there are uncommitted changes in the repository
|
|
86
|
-
*/
|
|
87
|
-
const checkForUncommittedChanges = async (tempDir, $, argv = {}) => {
|
|
88
|
-
// First, clean up .playwright-mcp/ folder to prevent false positives (Issue #1124)
|
|
89
|
-
await cleanupPlaywrightMcpFolder(tempDir, argv);
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
const gitStatusResult = await $({ cwd: tempDir })`git status --porcelain 2>&1`;
|
|
93
|
-
if (gitStatusResult.code === 0) {
|
|
94
|
-
const statusOutput = gitStatusResult.stdout.toString().trim();
|
|
95
|
-
return statusOutput.length > 0;
|
|
96
|
-
}
|
|
97
|
-
} catch (error) {
|
|
98
|
-
reportError(error, {
|
|
99
|
-
context: 'check_pr_closed',
|
|
100
|
-
tempDir,
|
|
101
|
-
operation: 'check_close_status',
|
|
102
|
-
});
|
|
103
|
-
// If we can't check, assume no uncommitted changes
|
|
104
|
-
}
|
|
105
|
-
return false;
|
|
106
|
-
};
|
|
36
|
+
// Import shared utilities from the restart-shared module
|
|
37
|
+
const restartShared = await import('./solve.restart-shared.lib.mjs');
|
|
38
|
+
const { checkPRMerged, checkForUncommittedChanges, getUncommittedChangesDetails, executeToolIteration, buildUncommittedChangesFeedback, isApiError } = restartShared;
|
|
107
39
|
|
|
108
40
|
/**
|
|
109
41
|
* Monitor for feedback in a loop and trigger restart when detected
|
|
@@ -143,7 +75,6 @@ export const watchForFeedback = async params => {
|
|
|
143
75
|
await log('Press Ctrl+C to stop watching manually');
|
|
144
76
|
await log('');
|
|
145
77
|
|
|
146
|
-
// let lastCheckTime = new Date(); // Not currently used
|
|
147
78
|
let iteration = 0;
|
|
148
79
|
let autoRestartCount = 0;
|
|
149
80
|
let firstIterationInTemporaryMode = isTemporaryWatch;
|
|
@@ -164,7 +95,7 @@ export const watchForFeedback = async params => {
|
|
|
164
95
|
|
|
165
96
|
// In temporary watch mode, check if all changes have been committed
|
|
166
97
|
if (isTemporaryWatch && !firstIterationInTemporaryMode) {
|
|
167
|
-
const hasUncommitted = await checkForUncommittedChanges(tempDir,
|
|
98
|
+
const hasUncommitted = await checkForUncommittedChanges(tempDir, argv);
|
|
168
99
|
if (!hasUncommitted) {
|
|
169
100
|
await log('');
|
|
170
101
|
await log(formatAligned('â
', 'CHANGES COMMITTED!', 'Exiting auto-restart mode'));
|
|
@@ -219,7 +150,7 @@ export const watchForFeedback = async params => {
|
|
|
219
150
|
// In temporary watch mode, also check for uncommitted changes as a restart trigger
|
|
220
151
|
let hasUncommittedInTempMode = false;
|
|
221
152
|
if (isTemporaryWatch && !firstIterationInTemporaryMode) {
|
|
222
|
-
hasUncommittedInTempMode = await checkForUncommittedChanges(tempDir,
|
|
153
|
+
hasUncommittedInTempMode = await checkForUncommittedChanges(tempDir, argv);
|
|
223
154
|
}
|
|
224
155
|
|
|
225
156
|
const shouldRestart = hasFeedback || firstIterationInTemporaryMode || hasUncommittedInTempMode;
|
|
@@ -228,24 +159,10 @@ export const watchForFeedback = async params => {
|
|
|
228
159
|
// Handle uncommitted changes in temporary watch mode (first iteration or subsequent)
|
|
229
160
|
if (firstIterationInTemporaryMode || hasUncommittedInTempMode) {
|
|
230
161
|
await log(formatAligned('đ', 'UNCOMMITTED CHANGES:', '', 2));
|
|
231
|
-
// Get uncommitted changes for display
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const statusOutput = gitStatusResult.stdout.toString().trim();
|
|
236
|
-
for (const line of statusOutput.split('\n')) {
|
|
237
|
-
await log(formatAligned('', `âĸ ${line}`, '', 4));
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
} catch (e) {
|
|
241
|
-
reportError(e, {
|
|
242
|
-
context: 'check_claude_file_exists',
|
|
243
|
-
owner,
|
|
244
|
-
repo,
|
|
245
|
-
branchName,
|
|
246
|
-
operation: 'check_file_in_branch',
|
|
247
|
-
});
|
|
248
|
-
// Ignore errors
|
|
162
|
+
// Get uncommitted changes for display using shared utility
|
|
163
|
+
const changes = await getUncommittedChangesDetails(tempDir);
|
|
164
|
+
for (const line of changes) {
|
|
165
|
+
await log(formatAligned('', `âĸ ${line}`, '', 4));
|
|
249
166
|
}
|
|
250
167
|
await log('');
|
|
251
168
|
|
|
@@ -261,16 +178,8 @@ export const watchForFeedback = async params => {
|
|
|
261
178
|
|
|
262
179
|
// Get uncommitted files list for the comment
|
|
263
180
|
let uncommittedFilesList = '';
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if (gitStatusResult.code === 0) {
|
|
267
|
-
const statusOutput = gitStatusResult.stdout.toString().trim();
|
|
268
|
-
if (statusOutput) {
|
|
269
|
-
uncommittedFilesList = '\n\n**Uncommitted files:**\n```\n' + statusOutput + '\n```';
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
} catch {
|
|
273
|
-
// If we can't get the file list, continue without it
|
|
181
|
+
if (changes.length > 0) {
|
|
182
|
+
uncommittedFilesList = '\n\n**Uncommitted files:**\n```\n' + changes.join('\n') + '\n```';
|
|
274
183
|
}
|
|
275
184
|
|
|
276
185
|
const commentBody = `## đ Auto-restart ${autoRestartCount}/${maxAutoRestartIterations}\n\nDetected uncommitted changes from previous run. Starting new session to review and commit them.${uncommittedFilesList}\n\n---\n*Auto-restart will stop after changes are committed or after ${remainingIterations} more iteration${remainingIterations !== 1 ? 's' : ''}. Please wait until working session will end and give your feedback.*`;
|
|
@@ -289,39 +198,12 @@ export const watchForFeedback = async params => {
|
|
|
289
198
|
}
|
|
290
199
|
}
|
|
291
200
|
|
|
292
|
-
// Add uncommitted changes info to feedbackLines
|
|
201
|
+
// Add uncommitted changes info to feedbackLines using shared utility
|
|
293
202
|
if (!feedbackLines) {
|
|
294
203
|
feedbackLines = [];
|
|
295
204
|
}
|
|
296
|
-
|
|
297
|
-
feedbackLines.push(
|
|
298
|
-
feedbackLines.push('The following uncommitted changes were found in the repository:');
|
|
299
|
-
|
|
300
|
-
try {
|
|
301
|
-
const gitStatusResult = await $({ cwd: tempDir })`git status --porcelain 2>&1`;
|
|
302
|
-
if (gitStatusResult.code === 0) {
|
|
303
|
-
const statusOutput = gitStatusResult.stdout.toString().trim();
|
|
304
|
-
feedbackLines.push('');
|
|
305
|
-
for (const line of statusOutput.split('\n')) {
|
|
306
|
-
feedbackLines.push(` ${line}`);
|
|
307
|
-
}
|
|
308
|
-
feedbackLines.push('');
|
|
309
|
-
feedbackLines.push('IMPORTANT: You MUST handle these uncommitted changes by either:');
|
|
310
|
-
feedbackLines.push('1. COMMITTING them if they are part of the solution (git add + git commit + git push)');
|
|
311
|
-
feedbackLines.push('2. REVERTING them if they are not needed (git checkout -- <file> or git clean -fd)');
|
|
312
|
-
feedbackLines.push('');
|
|
313
|
-
feedbackLines.push('DO NOT leave uncommitted changes behind. The session will auto-restart until all changes are resolved.');
|
|
314
|
-
}
|
|
315
|
-
} catch (e) {
|
|
316
|
-
reportError(e, {
|
|
317
|
-
context: 'recheck_claude_file',
|
|
318
|
-
owner,
|
|
319
|
-
repo,
|
|
320
|
-
branchName,
|
|
321
|
-
operation: 'verify_file_in_branch',
|
|
322
|
-
});
|
|
323
|
-
// Ignore errors
|
|
324
|
-
}
|
|
205
|
+
const uncommittedFeedback = buildUncommittedChangesFeedback(changes, autoRestartCount, maxAutoRestartIterations);
|
|
206
|
+
feedbackLines.push(...uncommittedFeedback);
|
|
325
207
|
} else {
|
|
326
208
|
await log(formatAligned('đĸ', 'FEEDBACK DETECTED!', '', 2));
|
|
327
209
|
feedbackLines.forEach(async line => {
|
|
@@ -331,152 +213,23 @@ export const watchForFeedback = async params => {
|
|
|
331
213
|
await log(formatAligned('đ', 'Restarting:', `Re-running ${argv.tool.toUpperCase()} to handle feedback...`));
|
|
332
214
|
}
|
|
333
215
|
|
|
334
|
-
//
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
toolResult = await executeOpenCode({
|
|
348
|
-
issueUrl,
|
|
349
|
-
issueNumber,
|
|
350
|
-
prNumber,
|
|
351
|
-
prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
|
|
352
|
-
branchName,
|
|
353
|
-
tempDir,
|
|
354
|
-
isContinueMode: true,
|
|
355
|
-
mergeStateStatus,
|
|
356
|
-
forkedRepo: argv.fork,
|
|
357
|
-
feedbackLines,
|
|
358
|
-
owner,
|
|
359
|
-
repo,
|
|
360
|
-
argv,
|
|
361
|
-
log,
|
|
362
|
-
formatAligned,
|
|
363
|
-
getResourceSnapshot,
|
|
364
|
-
opencodePath,
|
|
365
|
-
$,
|
|
366
|
-
});
|
|
367
|
-
} else if (argv.tool === 'codex') {
|
|
368
|
-
// Use Codex
|
|
369
|
-
const codexExecLib = await import('./codex.lib.mjs');
|
|
370
|
-
const { executeCodex } = codexExecLib;
|
|
371
|
-
|
|
372
|
-
// Get codex path
|
|
373
|
-
const codexPath = argv.codexPath || 'codex';
|
|
374
|
-
|
|
375
|
-
toolResult = await executeCodex({
|
|
376
|
-
issueUrl,
|
|
377
|
-
issueNumber,
|
|
378
|
-
prNumber,
|
|
379
|
-
prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
|
|
380
|
-
branchName,
|
|
381
|
-
tempDir,
|
|
382
|
-
isContinueMode: true,
|
|
383
|
-
mergeStateStatus,
|
|
384
|
-
forkedRepo: argv.fork,
|
|
385
|
-
feedbackLines,
|
|
386
|
-
forkActionsUrl: null,
|
|
387
|
-
owner,
|
|
388
|
-
repo,
|
|
389
|
-
argv,
|
|
390
|
-
log,
|
|
391
|
-
setLogFile: () => {},
|
|
392
|
-
getLogFile: () => '',
|
|
393
|
-
formatAligned,
|
|
394
|
-
getResourceSnapshot,
|
|
395
|
-
codexPath,
|
|
396
|
-
$,
|
|
397
|
-
});
|
|
398
|
-
} else if (argv.tool === 'agent') {
|
|
399
|
-
// Use Agent
|
|
400
|
-
const agentExecLib = await import('./agent.lib.mjs');
|
|
401
|
-
const { executeAgent } = agentExecLib;
|
|
402
|
-
|
|
403
|
-
// Get agent path
|
|
404
|
-
const agentPath = argv.agentPath || 'agent';
|
|
405
|
-
|
|
406
|
-
toolResult = await executeAgent({
|
|
407
|
-
issueUrl,
|
|
408
|
-
issueNumber,
|
|
409
|
-
prNumber,
|
|
410
|
-
prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
|
|
411
|
-
branchName,
|
|
412
|
-
tempDir,
|
|
413
|
-
isContinueMode: true,
|
|
414
|
-
mergeStateStatus,
|
|
415
|
-
forkedRepo: argv.fork,
|
|
416
|
-
feedbackLines,
|
|
417
|
-
forkActionsUrl: null,
|
|
418
|
-
owner,
|
|
419
|
-
repo,
|
|
420
|
-
argv,
|
|
421
|
-
log,
|
|
422
|
-
formatAligned,
|
|
423
|
-
getResourceSnapshot,
|
|
424
|
-
agentPath,
|
|
425
|
-
$,
|
|
426
|
-
});
|
|
427
|
-
} else {
|
|
428
|
-
// Use Claude (default)
|
|
429
|
-
const claudeExecLib = await import('./claude.lib.mjs');
|
|
430
|
-
const { executeClaude, checkPlaywrightMcpAvailability } = claudeExecLib;
|
|
431
|
-
|
|
432
|
-
// Get claude path
|
|
433
|
-
const claudePath = argv.claudePath || 'claude';
|
|
434
|
-
|
|
435
|
-
// Check for Playwright MCP availability if using Claude tool
|
|
436
|
-
if (argv.tool === 'claude' || !argv.tool) {
|
|
437
|
-
// If flag is true (default), check if Playwright MCP is actually available
|
|
438
|
-
if (argv.promptPlaywrightMcp) {
|
|
439
|
-
const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
|
|
440
|
-
if (playwrightMcpAvailable) {
|
|
441
|
-
await log('đ Playwright MCP detected - enabling browser automation hints', { verbose: true });
|
|
442
|
-
} else {
|
|
443
|
-
await log('âšī¸ Playwright MCP not detected - browser automation hints will be disabled', {
|
|
444
|
-
verbose: true,
|
|
445
|
-
});
|
|
446
|
-
argv.promptPlaywrightMcp = false;
|
|
447
|
-
}
|
|
448
|
-
} else {
|
|
449
|
-
await log('âšī¸ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
toolResult = await executeClaude({
|
|
454
|
-
issueUrl,
|
|
455
|
-
issueNumber,
|
|
456
|
-
prNumber,
|
|
457
|
-
prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
|
|
458
|
-
branchName,
|
|
459
|
-
tempDir,
|
|
460
|
-
isContinueMode: true,
|
|
461
|
-
mergeStateStatus,
|
|
462
|
-
forkedRepo: argv.fork,
|
|
463
|
-
feedbackLines,
|
|
464
|
-
owner,
|
|
465
|
-
repo,
|
|
466
|
-
argv,
|
|
467
|
-
log,
|
|
468
|
-
formatAligned,
|
|
469
|
-
getResourceSnapshot,
|
|
470
|
-
claudePath,
|
|
471
|
-
$,
|
|
472
|
-
});
|
|
473
|
-
}
|
|
216
|
+
// Execute tool using shared utility
|
|
217
|
+
const toolResult = await executeToolIteration({
|
|
218
|
+
issueUrl,
|
|
219
|
+
owner,
|
|
220
|
+
repo,
|
|
221
|
+
issueNumber,
|
|
222
|
+
prNumber,
|
|
223
|
+
branchName: prBranch || branchName,
|
|
224
|
+
tempDir,
|
|
225
|
+
mergeStateStatus,
|
|
226
|
+
feedbackLines,
|
|
227
|
+
argv,
|
|
228
|
+
});
|
|
474
229
|
|
|
475
230
|
if (!toolResult.success) {
|
|
476
|
-
// Check if this is an API error
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
if (isApiError) {
|
|
231
|
+
// Check if this is an API error using shared utility
|
|
232
|
+
if (isApiError(toolResult)) {
|
|
480
233
|
consecutiveApiErrors++;
|
|
481
234
|
await log(formatAligned('â ī¸', `${argv.tool.toUpperCase()} execution failed`, `API error detected (${consecutiveApiErrors}/${MAX_API_ERROR_RETRIES})`, 2));
|
|
482
235
|
|
|
@@ -578,8 +331,6 @@ export const watchForFeedback = async params => {
|
|
|
578
331
|
}
|
|
579
332
|
}
|
|
580
333
|
|
|
581
|
-
// Note: lastCheckTime tracking removed as it was not being used
|
|
582
|
-
|
|
583
334
|
// Clear the first iteration flag after handling initial uncommitted changes
|
|
584
335
|
if (firstIterationInTemporaryMode) {
|
|
585
336
|
firstIterationInTemporaryMode = false;
|