@link-assistant/hive-mind 2.11.13 ā 2.12.1
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 +4 -1
- package/src/agent-command.lib.mjs +74 -0
- package/src/agent.lib.mjs +59 -34
- package/src/agentic-cli-updater.lib.mjs +241 -0
- package/src/claude.connection.lib.mjs +209 -0
- package/src/claude.lib.mjs +6 -202
- package/src/codex.lib.mjs +0 -128
- package/src/formal-ai-isolation.lib.mjs +62 -0
- package/src/formal-ai-maintenance.lib.mjs +106 -0
- package/src/formal-ai-model.lib.mjs +25 -0
- package/src/formal-ai-runtime.lib.mjs +10 -0
- package/src/formal-ai-sidecar.lib.mjs +565 -0
- package/src/formal-ai-updater.lib.mjs +294 -0
- package/src/formal-ai-version.lib.mjs +100 -0
- package/src/formal-ai.lib.mjs +11 -16
- package/src/github-rate-limit.lib.mjs +3 -0
- package/src/github-url-parser.lib.mjs +255 -0
- package/src/github.lib.mjs +22 -343
- package/src/hive.mjs +0 -152
- package/src/interactive-mode.lib.mjs +0 -43
- package/src/isolation-runner.lib.mjs +44 -173
- package/src/limits.lib.mjs +0 -89
- package/src/model-args.lib.mjs +32 -0
- package/src/models/index.mjs +5 -19
- package/src/session-monitor.lib.mjs +14 -172
- package/src/solve.auto-merge.lib.mjs +70 -164
- package/src/solve.mjs +31 -193
- package/src/solve.repository.lib.mjs +0 -83
- package/src/solve.results.lib.mjs +2 -92
- package/src/solve.session.lib.mjs +52 -19
- package/src/solve.tool-uncommitted.lib.mjs +22 -0
- package/src/state-lock.lib.mjs +82 -0
- package/src/telegram-bot.mjs +17 -65
- package/src/telegram-fix-command.lib.mjs +1 -8
- package/src/telegram-merge-queue.lib.mjs +3 -155
- package/src/telegram-solve-queue.lib.mjs +9 -168
- package/src/telegram-task-command.lib.mjs +1 -8
- package/src/use-m-bootstrap.lib.mjs +6 -5
- package/src/use-with-retry.lib.mjs +128 -2
- package/src/working-session-summary.lib.mjs +47 -1
package/src/hive.mjs
CHANGED
|
@@ -133,7 +133,6 @@ if (isRunningDirectly) {
|
|
|
133
133
|
}
|
|
134
134
|
await log(' š Fetching repository list (using --paginate for unlimited pagination)...', { verbose: true });
|
|
135
135
|
await log(` š Command: ${repoListCmd}`, { verbose: true });
|
|
136
|
-
|
|
137
136
|
// Add delay for rate limiting
|
|
138
137
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
139
138
|
// #1756: route through execGhWithRetry for transient 5xx + rate-limit
|
|
@@ -148,7 +147,6 @@ if (isRunningDirectly) {
|
|
|
148
147
|
.filter(line => line.trim());
|
|
149
148
|
const allRepositories = repoLines.map(line => JSON.parse(line));
|
|
150
149
|
await log(` š Found ${allRepositories.length} repositories`);
|
|
151
|
-
|
|
152
150
|
// Filter repositories to only include those owned by the target user/org
|
|
153
151
|
const ownedRepositories = allRepositories.filter(repo => {
|
|
154
152
|
const repoOwner = repo.owner?.login || repo.owner;
|
|
@@ -165,7 +163,6 @@ if (isRunningDirectly) {
|
|
|
165
163
|
await log(` āļø Skipping ${archivedCount} archived repository(ies)`);
|
|
166
164
|
}
|
|
167
165
|
await log(` ā
Processing ${repositories.length} non-archived repositories owned by ${owner}`);
|
|
168
|
-
|
|
169
166
|
let collectedIssues = [];
|
|
170
167
|
let processedRepos = 0;
|
|
171
168
|
// Process repositories in batches to avoid overwhelming the API
|
|
@@ -174,7 +171,6 @@ if (isRunningDirectly) {
|
|
|
174
171
|
const repoName = repo.name;
|
|
175
172
|
const ownerName = repo.owner?.login || owner;
|
|
176
173
|
await log(` š Fetching issues from ${ownerName}/${repoName}...`, { verbose: true });
|
|
177
|
-
|
|
178
174
|
// Build the appropriate issue list command
|
|
179
175
|
let issueCmd;
|
|
180
176
|
if (fetchAllIssues) {
|
|
@@ -210,7 +206,6 @@ if (isRunningDirectly) {
|
|
|
210
206
|
// Continue with other repositories
|
|
211
207
|
}
|
|
212
208
|
}
|
|
213
|
-
|
|
214
209
|
await log(` ā
Repository fallback complete: ${collectedIssues.length} issues from ${processedRepos}/${repositories.length} repositories`);
|
|
215
210
|
return collectedIssues;
|
|
216
211
|
} catch (error) {
|
|
@@ -224,14 +219,12 @@ if (isRunningDirectly) {
|
|
|
224
219
|
return [];
|
|
225
220
|
}
|
|
226
221
|
}
|
|
227
|
-
|
|
228
222
|
// Configure command line arguments - GitHub URL as positional argument
|
|
229
223
|
const rawArgs = normalizeCliArgs(hideBin(process.argv));
|
|
230
224
|
// Use .parse() instead of .argv to ensure .strict() mode works correctly
|
|
231
225
|
// When you use .argv, strict mode doesn't trigger properly
|
|
232
226
|
// See: https://github.com/yargs/yargs/issues - .strict() only works with .parse()
|
|
233
227
|
let argv;
|
|
234
|
-
|
|
235
228
|
// Temporarily suppress stderr to prevent yargs from printing error messages
|
|
236
229
|
// We'll handle error reporting ourselves
|
|
237
230
|
const originalStderrWrite = process.stderr.write;
|
|
@@ -246,7 +239,6 @@ if (isRunningDirectly) {
|
|
|
246
239
|
}
|
|
247
240
|
return true;
|
|
248
241
|
};
|
|
249
|
-
|
|
250
242
|
try {
|
|
251
243
|
argv = parseCliArgumentsWithLino({
|
|
252
244
|
argv: ['node', 'hive', ...rawArgs],
|
|
@@ -259,14 +251,12 @@ if (isRunningDirectly) {
|
|
|
259
251
|
} catch (error) {
|
|
260
252
|
// Restore stderr before handling the error
|
|
261
253
|
process.stderr.write = originalStderrWrite;
|
|
262
|
-
|
|
263
254
|
// If .strict() mode catches an unknown argument, yargs will throw an error
|
|
264
255
|
// We should fail fast for truly invalid arguments
|
|
265
256
|
if (error.message && error.message.includes('Unknown argument')) {
|
|
266
257
|
console.error('Error:', error.message);
|
|
267
258
|
process.exit(1);
|
|
268
259
|
}
|
|
269
|
-
|
|
270
260
|
// Yargs sometimes throws "Not enough arguments" errors even when arguments are present
|
|
271
261
|
// This is a quirk with optional positional arguments [github-url]
|
|
272
262
|
// The error.argv object still contains the parsed arguments, so we can safely continue
|
|
@@ -279,27 +269,20 @@ if (isRunningDirectly) {
|
|
|
279
269
|
}
|
|
280
270
|
throw error;
|
|
281
271
|
}
|
|
282
|
-
|
|
283
272
|
// Normalize deprecated flags to new names
|
|
284
273
|
if (argv && (argv.skipToolCheck || argv.skipClaudeCheck)) argv.skipToolConnectionCheck = true;
|
|
285
274
|
if (argv && argv.toolCheck === false) argv.toolConnectionCheck = false;
|
|
286
275
|
}
|
|
287
|
-
|
|
288
276
|
let githubUrl = argv['github-url'];
|
|
289
|
-
|
|
290
277
|
// Set global verbose mode
|
|
291
278
|
global.verboseMode = argv.verbose;
|
|
292
|
-
|
|
293
279
|
const { initI18n } = await import('./i18n.lib.mjs');
|
|
294
280
|
await initI18n({ language: argv.language, uiLanguage: argv.uiLanguage, workLanguage: argv.workLanguage });
|
|
295
|
-
|
|
296
281
|
setupVerboseLogInterceptor(); // Issue #1466: capture [VERBOSE] output in log files
|
|
297
282
|
setupStdioLogInterceptor(); // Issue #1549: capture ALL terminal output in log file
|
|
298
|
-
|
|
299
283
|
// Use the universal GitHub URL parser
|
|
300
284
|
if (githubUrl) {
|
|
301
285
|
const parsedUrl = parseGitHubUrl(githubUrl);
|
|
302
|
-
|
|
303
286
|
if (!parsedUrl.valid) {
|
|
304
287
|
console.error('Error: Invalid GitHub URL format');
|
|
305
288
|
if (parsedUrl.error) console.error(` ${parsedUrl.error}`);
|
|
@@ -314,7 +297,6 @@ if (isRunningDirectly) {
|
|
|
314
297
|
console.error(' - owner/repo (will be converted to https://github.com/owner/repo)');
|
|
315
298
|
await safeExit(1, 'Error occurred');
|
|
316
299
|
}
|
|
317
|
-
|
|
318
300
|
// Check if it's a valid type for hive (user or repo)
|
|
319
301
|
if (parsedUrl.type !== 'user' && parsedUrl.type !== 'repo') {
|
|
320
302
|
console.error('Error: Invalid GitHub URL for monitoring');
|
|
@@ -322,18 +304,14 @@ if (isRunningDirectly) {
|
|
|
322
304
|
console.error('Expected: https://github.com/owner or https://github.com/owner/repo');
|
|
323
305
|
await safeExit(1, 'Error occurred');
|
|
324
306
|
}
|
|
325
|
-
|
|
326
307
|
// Use the normalized URL
|
|
327
308
|
githubUrl = parsedUrl.normalized;
|
|
328
309
|
}
|
|
329
|
-
|
|
330
310
|
// Validate GitHub URL format ONCE AND FOR ALL at the beginning
|
|
331
311
|
// Parse URL format: https://github.com/owner or https://github.com/owner/repo
|
|
332
312
|
let urlMatch = null;
|
|
333
|
-
|
|
334
313
|
// Only validate if we have a URL
|
|
335
314
|
const needsUrlValidation = githubUrl;
|
|
336
|
-
|
|
337
315
|
if (needsUrlValidation) {
|
|
338
316
|
// Do the regex matching ONCE - this result will be used everywhere
|
|
339
317
|
urlMatch = githubUrl.match(/^https:\/\/github\.com\/([^/]+)(\/([^/]+))?$/);
|
|
@@ -350,11 +328,9 @@ if (isRunningDirectly) {
|
|
|
350
328
|
await safeExit(1, 'Error occurred');
|
|
351
329
|
}
|
|
352
330
|
}
|
|
353
|
-
|
|
354
331
|
// Create log file with timestamp
|
|
355
332
|
// Use log-dir option if provided, otherwise use current working directory
|
|
356
333
|
let targetDir = argv.logDir || process.cwd();
|
|
357
|
-
|
|
358
334
|
// Verify the directory exists, create if necessary
|
|
359
335
|
try {
|
|
360
336
|
await fs.access(targetDir);
|
|
@@ -379,20 +355,16 @@ if (isRunningDirectly) {
|
|
|
379
355
|
targetDir = process.cwd();
|
|
380
356
|
}
|
|
381
357
|
}
|
|
382
|
-
|
|
383
358
|
const timestamp = formatTimestamp();
|
|
384
359
|
const logFile = path.join(targetDir, `hive-${timestamp}.log`);
|
|
385
|
-
|
|
386
360
|
// Set the log file for the lib.mjs logging system
|
|
387
361
|
setLogFile(logFile);
|
|
388
|
-
|
|
389
362
|
// Create the log file immediately
|
|
390
363
|
await fs.writeFile(logFile, `# Hive.mjs Log - ${new Date().toISOString()}\n\n`);
|
|
391
364
|
// Always use absolute path for log file display
|
|
392
365
|
const absoluteLogPath = path.resolve(logFile);
|
|
393
366
|
await log(`š Log file: ${absoluteLogPath}`);
|
|
394
367
|
await log(' (All output will be logged here)');
|
|
395
|
-
|
|
396
368
|
// Initialize Sentry integration (unless disabled)
|
|
397
369
|
if (argv.sentry) {
|
|
398
370
|
await initializeSentry({
|
|
@@ -400,7 +372,6 @@ if (isRunningDirectly) {
|
|
|
400
372
|
debug: argv.verbose,
|
|
401
373
|
version: process.env.npm_package_version || '0.12.0',
|
|
402
374
|
});
|
|
403
|
-
|
|
404
375
|
// Add breadcrumb for monitoring configuration
|
|
405
376
|
addBreadcrumb({
|
|
406
377
|
category: 'hive',
|
|
@@ -413,11 +384,9 @@ if (isRunningDirectly) {
|
|
|
413
384
|
},
|
|
414
385
|
});
|
|
415
386
|
}
|
|
416
|
-
|
|
417
387
|
// Initialize the exit handler with getAbsoluteLogPath function and Sentry cleanup
|
|
418
388
|
initializeExitHandler(getAbsoluteLogPath, log);
|
|
419
389
|
installGlobalExitHandlers();
|
|
420
|
-
|
|
421
390
|
// Validate GitHub URL requirement
|
|
422
391
|
if (!githubUrl) {
|
|
423
392
|
await log('ā GitHub URL is required', { level: 'error' });
|
|
@@ -425,7 +394,6 @@ if (isRunningDirectly) {
|
|
|
425
394
|
await log(` š Full log file: ${absoluteLogPath}`, { level: 'error' });
|
|
426
395
|
await safeExit(1, 'Error occurred');
|
|
427
396
|
}
|
|
428
|
-
|
|
429
397
|
// Validate project mode arguments
|
|
430
398
|
if (argv.projectMode) {
|
|
431
399
|
if (!argv.projectNumber) {
|
|
@@ -435,7 +403,6 @@ if (isRunningDirectly) {
|
|
|
435
403
|
});
|
|
436
404
|
await safeExit(1, 'Error occurred');
|
|
437
405
|
}
|
|
438
|
-
|
|
439
406
|
if (!argv.projectOwner) {
|
|
440
407
|
await log('ā Project mode requires --project-owner', { level: 'error' });
|
|
441
408
|
await log(' Usage: hive <github-url> --project-mode --project-number NUMBER --project-owner OWNER', {
|
|
@@ -443,24 +410,20 @@ if (isRunningDirectly) {
|
|
|
443
410
|
});
|
|
444
411
|
await safeExit(1, 'Error occurred');
|
|
445
412
|
}
|
|
446
|
-
|
|
447
413
|
if (typeof argv.projectNumber !== 'number' || argv.projectNumber <= 0) {
|
|
448
414
|
await log('ā Project number must be a positive integer', { level: 'error' });
|
|
449
415
|
await safeExit(1, 'Error occurred');
|
|
450
416
|
}
|
|
451
417
|
}
|
|
452
|
-
|
|
453
418
|
// --plan flag expansion: shortcut for --plan-model opus --worker-model sonnet (Issue #1223)
|
|
454
419
|
if (argv.plan) {
|
|
455
420
|
if (!rawArgs.includes('--plan-model')) argv.planModel = 'opus';
|
|
456
421
|
if (!rawArgs.includes('--model') && !rawArgs.includes('-m') && !rawArgs.includes('--worker-model')) argv.model = 'sonnet';
|
|
457
422
|
}
|
|
458
|
-
|
|
459
423
|
const modelExplicitlyProvided = rawArgs.includes('--model') || rawArgs.includes('-m') || rawArgs.includes('--worker-model');
|
|
460
424
|
if (argv.tool && !modelExplicitlyProvided && defaultModels[argv.tool]) {
|
|
461
425
|
argv.model = await resolveRuntimeDefaultModel(argv.tool);
|
|
462
426
|
}
|
|
463
|
-
|
|
464
427
|
// Validate model names EARLY (simple string check, always runs)
|
|
465
428
|
const tool = argv.tool || 'claude';
|
|
466
429
|
await validateAndExitOnInvalidModel(argv.model, tool, safeExit);
|
|
@@ -477,11 +440,9 @@ if (isRunningDirectly) {
|
|
|
477
440
|
if (argv.subAgentModel) {
|
|
478
441
|
await validateAndExitOnInvalidClaudeSubAgentModel(argv.subAgentModel, tool, safeExit);
|
|
479
442
|
}
|
|
480
|
-
|
|
481
443
|
// Handle -s (--skip-issues-with-prs) and --auto-continue interaction
|
|
482
444
|
const hasExplicitAutoContinue = rawArgs.includes('--auto-continue');
|
|
483
445
|
const hasExplicitNoAutoContinue = rawArgs.includes('--no-auto-continue');
|
|
484
|
-
|
|
485
446
|
if (argv.skipIssuesWithPrs) {
|
|
486
447
|
if (hasExplicitAutoContinue) {
|
|
487
448
|
await log('ā Conflicting options: --skip-issues-with-prs and --auto-continue cannot be used together', {
|
|
@@ -492,15 +453,12 @@ if (isRunningDirectly) {
|
|
|
492
453
|
await log(` š Full log file: ${absoluteLogPath}`, { level: 'error' });
|
|
493
454
|
await safeExit(1, 'Error occurred');
|
|
494
455
|
}
|
|
495
|
-
|
|
496
456
|
// -s implies disabling auto-continue unless explicitly set
|
|
497
457
|
if (!hasExplicitNoAutoContinue) {
|
|
498
458
|
argv.autoContinue = false;
|
|
499
459
|
}
|
|
500
460
|
}
|
|
501
|
-
|
|
502
461
|
// Helper function to check GitHub permissions - moved to github.lib.mjs
|
|
503
|
-
|
|
504
462
|
// Check GitHub permissions early in the process (skip in dry-run mode or when explicitly requested)
|
|
505
463
|
if (argv.dryRun || argv.skipToolConnectionCheck || argv.toolConnectionCheck === false) {
|
|
506
464
|
await log('ā© Skipping GitHub permissions check (dry-run mode or skip-tool-connection-check enabled)', {
|
|
@@ -513,13 +471,11 @@ if (isRunningDirectly) {
|
|
|
513
471
|
await safeExit(1, 'Error occurred');
|
|
514
472
|
}
|
|
515
473
|
}
|
|
516
|
-
|
|
517
474
|
// YouTrack configuration and validation
|
|
518
475
|
let youTrackConfig = null;
|
|
519
476
|
if (argv.youtrackMode) {
|
|
520
477
|
// Create YouTrack config from environment variables and CLI overrides
|
|
521
478
|
youTrackConfig = createYouTrackConfigFromEnv();
|
|
522
|
-
|
|
523
479
|
if (!youTrackConfig) {
|
|
524
480
|
await log('ā YouTrack mode requires environment variables to be set', { level: 'error' });
|
|
525
481
|
await log(' Required: YOUTRACK_URL, YOUTRACK_API_KEY, YOUTRACK_PROJECT_CODE, YOUTRACK_STAGE', {
|
|
@@ -528,7 +484,6 @@ if (isRunningDirectly) {
|
|
|
528
484
|
await log(' Example: YOUTRACK_URL=https://mycompany.youtrack.cloud', { level: 'error' });
|
|
529
485
|
process.exit(1);
|
|
530
486
|
}
|
|
531
|
-
|
|
532
487
|
// Apply CLI overrides
|
|
533
488
|
if (argv.youtrackStage) {
|
|
534
489
|
youTrackConfig.stage = argv.youtrackStage;
|
|
@@ -536,7 +491,6 @@ if (isRunningDirectly) {
|
|
|
536
491
|
if (argv.youtrackProject) {
|
|
537
492
|
youTrackConfig.projectCode = argv.youtrackProject;
|
|
538
493
|
}
|
|
539
|
-
|
|
540
494
|
// Validate configuration
|
|
541
495
|
try {
|
|
542
496
|
validateYouTrackConfig(youTrackConfig);
|
|
@@ -544,7 +498,6 @@ if (isRunningDirectly) {
|
|
|
544
498
|
await log(`ā YouTrack configuration error: ${error.message}`, { level: 'error' });
|
|
545
499
|
process.exit(1);
|
|
546
500
|
}
|
|
547
|
-
|
|
548
501
|
// Test YouTrack connection
|
|
549
502
|
const youTrackConnected = await testYouTrackConnection(youTrackConfig);
|
|
550
503
|
if (!youTrackConnected) {
|
|
@@ -552,12 +505,10 @@ if (isRunningDirectly) {
|
|
|
552
505
|
process.exit(1);
|
|
553
506
|
}
|
|
554
507
|
}
|
|
555
|
-
|
|
556
508
|
// Parse GitHub URL to determine organization, repository, or user
|
|
557
509
|
let scope = 'repository';
|
|
558
510
|
let owner = null;
|
|
559
511
|
let repo = null;
|
|
560
|
-
|
|
561
512
|
// NO DUPLICATE VALIDATION! URL was already validated at the beginning.
|
|
562
513
|
// If we have a URL but no validation results, that's a logic error.
|
|
563
514
|
if (githubUrl && urlMatch === null) {
|
|
@@ -566,12 +517,10 @@ if (isRunningDirectly) {
|
|
|
566
517
|
await log('This is a bug in the script logic', { level: 'error' });
|
|
567
518
|
await safeExit(1, 'Error occurred');
|
|
568
519
|
}
|
|
569
|
-
|
|
570
520
|
if (urlMatch) {
|
|
571
521
|
owner = urlMatch[1];
|
|
572
522
|
repo = urlMatch[3] || null;
|
|
573
523
|
}
|
|
574
|
-
|
|
575
524
|
// Determine scope
|
|
576
525
|
if (!repo) {
|
|
577
526
|
// Check if it's an organization or user (skip in dry-run mode to avoid hanging)
|
|
@@ -597,7 +546,6 @@ if (isRunningDirectly) {
|
|
|
597
546
|
} else {
|
|
598
547
|
scope = 'repository';
|
|
599
548
|
}
|
|
600
|
-
|
|
601
549
|
await log('šÆ Monitoring Configuration:');
|
|
602
550
|
if (argv.youtrackMode) {
|
|
603
551
|
await log(` š Source: YouTrack - ${youTrackConfig.url}`);
|
|
@@ -647,7 +595,6 @@ if (isRunningDirectly) {
|
|
|
647
595
|
if (argv.autoCleanup) await log(' š§¹ Auto-cleanup: ENABLED (will clean /tmp/* /var/tmp/* on success)');
|
|
648
596
|
if (argv.interactiveMode) await log(' š Interactive Mode: ENABLED');
|
|
649
597
|
await log('');
|
|
650
|
-
|
|
651
598
|
// Producer/Consumer Queue implementation
|
|
652
599
|
class IssueQueue {
|
|
653
600
|
constructor() {
|
|
@@ -658,7 +605,6 @@ if (isRunningDirectly) {
|
|
|
658
605
|
this.workers = [];
|
|
659
606
|
this.isRunning = true;
|
|
660
607
|
}
|
|
661
|
-
|
|
662
608
|
// Add issue to queue if not already processed or in queue
|
|
663
609
|
enqueue(issueUrl) {
|
|
664
610
|
if (this.completed.has(issueUrl) || this.processing.has(issueUrl) || this.queue.includes(issueUrl)) {
|
|
@@ -667,7 +613,6 @@ if (isRunningDirectly) {
|
|
|
667
613
|
this.queue.push(issueUrl);
|
|
668
614
|
return true;
|
|
669
615
|
}
|
|
670
|
-
|
|
671
616
|
// Get next issue from queue
|
|
672
617
|
dequeue() {
|
|
673
618
|
if (this.queue.length === 0) {
|
|
@@ -677,19 +622,16 @@ if (isRunningDirectly) {
|
|
|
677
622
|
this.processing.add(issue);
|
|
678
623
|
return issue;
|
|
679
624
|
}
|
|
680
|
-
|
|
681
625
|
// Mark issue as completed
|
|
682
626
|
markCompleted(issueUrl) {
|
|
683
627
|
this.processing.delete(issueUrl);
|
|
684
628
|
this.completed.add(issueUrl);
|
|
685
629
|
}
|
|
686
|
-
|
|
687
630
|
// Mark issue as failed
|
|
688
631
|
markFailed(issueUrl) {
|
|
689
632
|
this.processing.delete(issueUrl);
|
|
690
633
|
this.failed.add(issueUrl);
|
|
691
634
|
}
|
|
692
|
-
|
|
693
635
|
// Get queue statistics
|
|
694
636
|
getStats() {
|
|
695
637
|
return {
|
|
@@ -700,36 +642,28 @@ if (isRunningDirectly) {
|
|
|
700
642
|
processingIssues: Array.from(this.processing),
|
|
701
643
|
};
|
|
702
644
|
}
|
|
703
|
-
|
|
704
645
|
// Stop all workers
|
|
705
646
|
stop() {
|
|
706
647
|
this.isRunning = false;
|
|
707
648
|
}
|
|
708
649
|
}
|
|
709
|
-
|
|
710
650
|
// Create global queue instance
|
|
711
651
|
const issueQueue = new IssueQueue();
|
|
712
|
-
|
|
713
652
|
// Issue #1823: Track in-flight solve child processes. A *first* interrupt forwards a
|
|
714
653
|
// controlled SIGTERM to each (they run in their own detached process group, so the
|
|
715
654
|
// terminal's SIGINT never reaches them); a *second* interrupt force-kills the groups.
|
|
716
655
|
const activeSolveChildren = new Set();
|
|
717
|
-
|
|
718
656
|
// Worker function to process issues from queue
|
|
719
657
|
async function worker(workerId) {
|
|
720
658
|
await log(`š§ Worker ${workerId} started`, { verbose: true });
|
|
721
|
-
|
|
722
659
|
while (issueQueue.isRunning) {
|
|
723
660
|
const issueUrl = issueQueue.dequeue();
|
|
724
|
-
|
|
725
661
|
if (!issueUrl) {
|
|
726
662
|
// No work available, wait a bit
|
|
727
663
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
728
664
|
continue;
|
|
729
665
|
}
|
|
730
|
-
|
|
731
666
|
await log(`\nš· Worker ${workerId} processing: ${issueUrl}`);
|
|
732
|
-
|
|
733
667
|
// Recheck conditions before processing to avoid wasted work
|
|
734
668
|
const recheckResult = await recheckIssueConditions(issueUrl, argv);
|
|
735
669
|
if (!recheckResult.shouldProcess) {
|
|
@@ -739,18 +673,15 @@ if (isRunningDirectly) {
|
|
|
739
673
|
await log(` š Queue: ${stats.queued} waiting, ${stats.processing} processing, ${stats.completed} completed, ${stats.failed} failed`);
|
|
740
674
|
continue;
|
|
741
675
|
}
|
|
742
|
-
|
|
743
676
|
// Track if this issue failed
|
|
744
677
|
let issueFailed = false;
|
|
745
678
|
// Issue #1823: Track a graceful shutdown stop so it is neither failed nor completed.
|
|
746
679
|
let gracefulStop = false;
|
|
747
|
-
|
|
748
680
|
// Process the issue multiple times if needed
|
|
749
681
|
for (let prNum = 1; prNum <= argv.pullRequestsPerIssue; prNum++) {
|
|
750
682
|
if (argv.pullRequestsPerIssue > 1) {
|
|
751
683
|
await log(` š Creating PR ${prNum}/${argv.pullRequestsPerIssue} for issue`);
|
|
752
684
|
}
|
|
753
|
-
|
|
754
685
|
try {
|
|
755
686
|
// Execute solve command using spawn to enable real-time streaming while avoiding command-stream quoting issues
|
|
756
687
|
if (argv.dryRun) {
|
|
@@ -758,7 +689,6 @@ if (isRunningDirectly) {
|
|
|
758
689
|
} else {
|
|
759
690
|
await log(` š Executing ${solveCommand} for ${issueUrl}...`);
|
|
760
691
|
}
|
|
761
|
-
|
|
762
692
|
const startTime = Date.now();
|
|
763
693
|
// Use spawn to get real-time streaming output while avoiding command-stream's automatic quote addition
|
|
764
694
|
const { spawn } = await import('child_process');
|
|
@@ -784,7 +714,6 @@ if (isRunningDirectly) {
|
|
|
784
714
|
if (argv.dryRun) args.push('--dry-run');
|
|
785
715
|
if (argv.autoCleanup) args.push('--auto-cleanup');
|
|
786
716
|
const SKIP_AUTO_FORWARD = new Set(['model', 'worker-model', 'base-branch', 'skip-tool-connection-check', 'tool-connection-check', 'skip-tool-check', 'skip-claude-check', 'tool-check', 'dry-run', 'auto-cleanup']);
|
|
787
|
-
|
|
788
717
|
for (const optionName of getSolvePassthroughOptionNames()) {
|
|
789
718
|
if (SKIP_AUTO_FORWARD.has(optionName)) continue;
|
|
790
719
|
const camelName = kebabToCamel(optionName);
|
|
@@ -809,7 +738,6 @@ if (isRunningDirectly) {
|
|
|
809
738
|
}
|
|
810
739
|
// Log the actual command being executed so users can investigate/reproduce
|
|
811
740
|
await log(` š Command: ${solveCommand} ${args.join(' ')}`);
|
|
812
|
-
|
|
813
741
|
let exitCode = 0;
|
|
814
742
|
// Create promise to handle async spawn process
|
|
815
743
|
await new Promise(resolve => {
|
|
@@ -822,11 +750,9 @@ if (isRunningDirectly) {
|
|
|
822
750
|
// must NOT unref() ā hive keeps waiting. See docs/case-studies/issue-1823.
|
|
823
751
|
detached: true,
|
|
824
752
|
});
|
|
825
|
-
|
|
826
753
|
// Issue #1823: register the in-flight child for optional force-kill on a 2nd signal
|
|
827
754
|
activeSolveChildren.add(child);
|
|
828
755
|
log(` š§ Spawned ${solveCommand} worker-${workerId} (pid ${child.pid}, detached process group)`, { verbose: true }).catch(() => {});
|
|
829
|
-
|
|
830
756
|
// Handle stdout data - stream output in real-time
|
|
831
757
|
child.stdout.on('data', data => {
|
|
832
758
|
const lines = data.toString().split('\n');
|
|
@@ -842,7 +768,6 @@ if (isRunningDirectly) {
|
|
|
842
768
|
}
|
|
843
769
|
}
|
|
844
770
|
});
|
|
845
|
-
|
|
846
771
|
// Handle stderr data - stream output in real-time.
|
|
847
772
|
// Issue #1823: Do NOT blanket-tag stderr as ERROR ā solve relays non-error
|
|
848
773
|
// diagnostics there (codex DEBUG/INFO traces, git branch messages, etc.), which
|
|
@@ -862,7 +787,6 @@ if (isRunningDirectly) {
|
|
|
862
787
|
}
|
|
863
788
|
}
|
|
864
789
|
});
|
|
865
|
-
|
|
866
790
|
// Handle process completion and spawn failure. Issue #2135: a signalled
|
|
867
791
|
// child reports `code === null`, which `code || 0` read as success.
|
|
868
792
|
attachChildExitHandlers({
|
|
@@ -879,9 +803,7 @@ if (isRunningDirectly) {
|
|
|
879
803
|
},
|
|
880
804
|
});
|
|
881
805
|
});
|
|
882
|
-
|
|
883
806
|
const duration = Math.round((Date.now() - startTime) / 1000);
|
|
884
|
-
|
|
885
807
|
if (exitCode === 0) {
|
|
886
808
|
await log(` ā
Worker ${workerId} completed ${issueUrl} (${duration}s)`);
|
|
887
809
|
} else if (!issueQueue.isRunning && (exitCode === 130 || exitCode === 143)) {
|
|
@@ -894,7 +816,6 @@ if (isRunningDirectly) {
|
|
|
894
816
|
} else {
|
|
895
817
|
throw new Error(`${solveCommand} exited with code ${exitCode}`);
|
|
896
818
|
}
|
|
897
|
-
|
|
898
819
|
// Small delay between multiple PRs for same issue
|
|
899
820
|
if (prNum < argv.pullRequestsPerIssue) {
|
|
900
821
|
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
@@ -914,19 +835,16 @@ if (isRunningDirectly) {
|
|
|
914
835
|
break; // Stop trying more PRs for this issue
|
|
915
836
|
}
|
|
916
837
|
}
|
|
917
|
-
|
|
918
838
|
// Only mark as completed if it didn't fail and wasn't gracefully stopped mid-shutdown.
|
|
919
839
|
// Issue #1823: a graceful stop is neither a success nor a failure ā leave it in
|
|
920
840
|
// "processing" so it is not miscounted as completed (which would also trigger cleanup).
|
|
921
841
|
if (!issueFailed && !gracefulStop) {
|
|
922
842
|
issueQueue.markCompleted(issueUrl);
|
|
923
843
|
}
|
|
924
|
-
|
|
925
844
|
// Show queue stats
|
|
926
845
|
const stats = issueQueue.getStats();
|
|
927
846
|
await log(` š Queue: ${stats.queued} waiting, ${stats.processing} processing, ${stats.completed} completed, ${stats.failed} failed`);
|
|
928
847
|
await log(` š Hive log file: ${absoluteLogPath}`);
|
|
929
|
-
|
|
930
848
|
// Show which issues are currently being processed
|
|
931
849
|
if (stats.processingIssues && stats.processingIssues.length > 0) {
|
|
932
850
|
await log(' š§ Currently processing solve commands:');
|
|
@@ -935,14 +853,11 @@ if (isRunningDirectly) {
|
|
|
935
853
|
}
|
|
936
854
|
}
|
|
937
855
|
}
|
|
938
|
-
|
|
939
856
|
await log(`š§ Worker ${workerId} stopped`, { verbose: true });
|
|
940
857
|
}
|
|
941
|
-
|
|
942
858
|
// Function to check if an issue has open pull requests
|
|
943
859
|
// Note: hasOpenPullRequests function has been replaced by batchCheckPullRequestsForIssues
|
|
944
860
|
// in github.lib.mjs for better performance and reduced API calls
|
|
945
|
-
|
|
946
861
|
// Function to fetch issues from GitHub
|
|
947
862
|
async function fetchIssues() {
|
|
948
863
|
if (argv.youtrackMode) {
|
|
@@ -954,24 +869,19 @@ if (isRunningDirectly) {
|
|
|
954
869
|
} else {
|
|
955
870
|
await log(`\nš Fetching issues with label "${argv.monitorTag}"...`);
|
|
956
871
|
}
|
|
957
|
-
|
|
958
872
|
// In dry-run mode, skip actual API calls and return empty list immediately
|
|
959
873
|
if (argv.dryRun) {
|
|
960
874
|
await log(' š§Ŗ Dry-run mode: Skipping actual issue fetching');
|
|
961
875
|
return [];
|
|
962
876
|
}
|
|
963
|
-
|
|
964
877
|
try {
|
|
965
878
|
let issues = [];
|
|
966
|
-
|
|
967
879
|
if (argv.youtrackMode) {
|
|
968
880
|
// Sync YouTrack issues to GitHub
|
|
969
881
|
if (!owner || !repo) {
|
|
970
882
|
throw new Error('YouTrack mode requires a specific repository URL (not organization/user)');
|
|
971
883
|
}
|
|
972
|
-
|
|
973
884
|
const githubIssues = await syncYouTrackToGitHub(youTrackConfig, owner, repo, $, log);
|
|
974
|
-
|
|
975
885
|
// Convert to format expected by hive
|
|
976
886
|
issues = formatIssuesForHive(githubIssues).map(issue => ({
|
|
977
887
|
url: issue.html_url,
|
|
@@ -983,7 +893,6 @@ if (isRunningDirectly) {
|
|
|
983
893
|
if (!argv.projectNumber || !argv.projectOwner) {
|
|
984
894
|
throw new Error('Project mode requires --project-number and --project-owner');
|
|
985
895
|
}
|
|
986
|
-
|
|
987
896
|
issues = await fetchProjectIssues(argv.projectNumber, argv.projectOwner, argv.projectStatus);
|
|
988
897
|
} else if (argv.allIssues) {
|
|
989
898
|
// Fetch all open issues without label filter using pagination
|
|
@@ -996,10 +905,8 @@ if (isRunningDirectly) {
|
|
|
996
905
|
// User scope
|
|
997
906
|
searchCmd = `gh search issues user:${owner} is:open --json url,title,number,createdAt,repository`;
|
|
998
907
|
}
|
|
999
|
-
|
|
1000
908
|
await log(' š Fetching all issues with pagination and rate limiting...');
|
|
1001
909
|
await log(` š Command: ${searchCmd}`, { verbose: true });
|
|
1002
|
-
|
|
1003
910
|
try {
|
|
1004
911
|
issues = await fetchAllIssuesWithPagination(searchCmd);
|
|
1005
912
|
} catch (searchError) {
|
|
@@ -1010,7 +917,6 @@ if (isRunningDirectly) {
|
|
|
1010
917
|
operation: 'search_all_issues',
|
|
1011
918
|
});
|
|
1012
919
|
await log(` ā ļø Search failed: ${cleanErrorMessage(searchError)}`, { verbose: true });
|
|
1013
|
-
|
|
1014
920
|
// Check if the error is due to rate limiting or search API limit and we're not in repository scope
|
|
1015
921
|
const errorMsg = searchError.message || searchError.toString();
|
|
1016
922
|
const isSearchLimitError = errorMsg.includes('Hit search API limit') || errorMsg.includes('repository-by-repository fallback');
|
|
@@ -1035,13 +941,11 @@ if (isRunningDirectly) {
|
|
|
1035
941
|
} else {
|
|
1036
942
|
// Use label filter
|
|
1037
943
|
// execSync is used within fetchAllIssuesWithPagination
|
|
1038
|
-
|
|
1039
944
|
// For repositories, use gh issue list which works better with new repos
|
|
1040
945
|
if (scope === 'repository') {
|
|
1041
946
|
const listCmd = `gh issue list --repo ${owner}/${repo} --state open --label "${argv.monitorTag}" --json url,title,number,createdAt`;
|
|
1042
947
|
await log(' š Fetching labeled issues with pagination and rate limiting...');
|
|
1043
948
|
await log(` š Command: ${listCmd}`, { verbose: true });
|
|
1044
|
-
|
|
1045
949
|
try {
|
|
1046
950
|
issues = await fetchAllIssuesWithPagination(listCmd);
|
|
1047
951
|
} catch (listError) {
|
|
@@ -1063,11 +967,9 @@ if (isRunningDirectly) {
|
|
|
1063
967
|
} else {
|
|
1064
968
|
baseQuery = `user:${owner} is:issue is:open`;
|
|
1065
969
|
}
|
|
1066
|
-
|
|
1067
970
|
// Handle label with potential spaces
|
|
1068
971
|
let searchQuery;
|
|
1069
972
|
let searchCmd;
|
|
1070
|
-
|
|
1071
973
|
if (argv.monitorTag.includes(' ')) {
|
|
1072
974
|
searchQuery = `${baseQuery} label:"${argv.monitorTag}"`;
|
|
1073
975
|
searchCmd = `gh search issues '${searchQuery}' --json url,title,number,createdAt,repository`;
|
|
@@ -1075,11 +977,9 @@ if (isRunningDirectly) {
|
|
|
1075
977
|
searchQuery = `${baseQuery} label:${argv.monitorTag}`;
|
|
1076
978
|
searchCmd = `gh search issues '${searchQuery}' --json url,title,number,createdAt,repository`;
|
|
1077
979
|
}
|
|
1078
|
-
|
|
1079
980
|
await log(' š Fetching labeled issues with pagination and rate limiting...');
|
|
1080
981
|
await log(` š Search query: ${searchQuery}`, { verbose: true });
|
|
1081
982
|
await log(` š Command: ${searchCmd}`, { verbose: true });
|
|
1082
|
-
|
|
1083
983
|
try {
|
|
1084
984
|
issues = await fetchAllIssuesWithPagination(searchCmd);
|
|
1085
985
|
} catch (searchError) {
|
|
@@ -1091,7 +991,6 @@ if (isRunningDirectly) {
|
|
|
1091
991
|
operation: 'search_labeled_issues',
|
|
1092
992
|
});
|
|
1093
993
|
await log(` ā ļø Search failed: ${cleanErrorMessage(searchError)}`, { verbose: true });
|
|
1094
|
-
|
|
1095
994
|
// Check if the error is due to rate limiting or search API limit
|
|
1096
995
|
const errorMsg = searchError.message || searchError.toString();
|
|
1097
996
|
const isSearchLimitError = errorMsg.includes('Hit search API limit') || errorMsg.includes('repository-by-repository fallback');
|
|
@@ -1116,7 +1015,6 @@ if (isRunningDirectly) {
|
|
|
1116
1015
|
}
|
|
1117
1016
|
}
|
|
1118
1017
|
}
|
|
1119
|
-
|
|
1120
1018
|
if (issues.length === 0) {
|
|
1121
1019
|
if (argv.youtrackMode) {
|
|
1122
1020
|
await log(` ā¹ļø No issues found in YouTrack with stage "${youTrackConfig.stage}"`);
|
|
@@ -1129,7 +1027,6 @@ if (isRunningDirectly) {
|
|
|
1129
1027
|
}
|
|
1130
1028
|
return [];
|
|
1131
1029
|
}
|
|
1132
|
-
|
|
1133
1030
|
if (argv.youtrackMode) {
|
|
1134
1031
|
await log(` š Found ${issues.length} YouTrack issue(s) with stage "${youTrackConfig.stage}"`);
|
|
1135
1032
|
} else if (argv.projectMode) {
|
|
@@ -1139,7 +1036,6 @@ if (isRunningDirectly) {
|
|
|
1139
1036
|
} else {
|
|
1140
1037
|
await log(` š Found ${issues.length} issue(s) with label "${argv.monitorTag}"`);
|
|
1141
1038
|
}
|
|
1142
|
-
|
|
1143
1039
|
// Sort issues by publication date (createdAt) based on issue-order option
|
|
1144
1040
|
if (issues.length > 0 && issues[0].createdAt) {
|
|
1145
1041
|
await log(` š Sorting issues by publication date (${argv.issueOrder === 'asc' ? 'oldest first' : 'newest first'})...`);
|
|
@@ -1150,16 +1046,13 @@ if (isRunningDirectly) {
|
|
|
1150
1046
|
});
|
|
1151
1047
|
await log(' ā
Issues sorted by publication date');
|
|
1152
1048
|
}
|
|
1153
|
-
|
|
1154
1049
|
// Filter out issues from archived repositories
|
|
1155
1050
|
// This is critical because we cannot do write operations on archived repositories
|
|
1156
1051
|
let issuesToProcess = issues;
|
|
1157
|
-
|
|
1158
1052
|
// Helper function to extract repository info from issue (API response or URL)
|
|
1159
1053
|
const getRepoInfo = issue => {
|
|
1160
1054
|
let repoName = issue.repository?.name;
|
|
1161
1055
|
let repoOwner = issue.repository?.owner?.login || issue.repository?.nameWithOwner?.split('/')[0];
|
|
1162
|
-
|
|
1163
1056
|
// If repository info is not available, extract it from the issue URL
|
|
1164
1057
|
if (!repoName || !repoOwner) {
|
|
1165
1058
|
const urlMatch = issue.url?.match(/github\.com\/([^/]+)\/([^/]+)\/issues\/\d+/);
|
|
@@ -1168,15 +1061,12 @@ if (isRunningDirectly) {
|
|
|
1168
1061
|
repoName = urlMatch[2];
|
|
1169
1062
|
}
|
|
1170
1063
|
}
|
|
1171
|
-
|
|
1172
1064
|
return { repoOwner, repoName };
|
|
1173
1065
|
};
|
|
1174
|
-
|
|
1175
1066
|
// Only filter for organization/user scopes
|
|
1176
1067
|
// For repository scope, we're already working on a specific repo
|
|
1177
1068
|
if (scope !== 'repository' && issues.length > 0) {
|
|
1178
1069
|
await log(' š Checking for archived repositories...');
|
|
1179
|
-
|
|
1180
1070
|
// Extract unique repositories from issues
|
|
1181
1071
|
const uniqueRepos = new Map();
|
|
1182
1072
|
for (const issue of issues) {
|
|
@@ -1188,20 +1078,15 @@ if (isRunningDirectly) {
|
|
|
1188
1078
|
}
|
|
1189
1079
|
}
|
|
1190
1080
|
}
|
|
1191
|
-
|
|
1192
1081
|
// Batch check archived status for all repositories
|
|
1193
1082
|
const archivedStatusMap = await batchCheckArchivedRepositories(Array.from(uniqueRepos.values()));
|
|
1194
|
-
|
|
1195
1083
|
// Filter out issues from archived repositories
|
|
1196
1084
|
const filteredIssues = [];
|
|
1197
1085
|
let archivedIssuesCount = 0;
|
|
1198
|
-
|
|
1199
1086
|
for (const issue of issues) {
|
|
1200
1087
|
const { repoOwner, repoName } = getRepoInfo(issue);
|
|
1201
|
-
|
|
1202
1088
|
if (repoOwner && repoName) {
|
|
1203
1089
|
const repoKey = `${repoOwner}/${repoName}`;
|
|
1204
|
-
|
|
1205
1090
|
if (archivedStatusMap[repoKey] === true) {
|
|
1206
1091
|
await log(` āļø Skipping (archived repository): ${issue.title || 'Untitled'} (${issue.url})`, {
|
|
1207
1092
|
verbose: true,
|
|
@@ -1216,18 +1101,14 @@ if (isRunningDirectly) {
|
|
|
1216
1101
|
filteredIssues.push(issue);
|
|
1217
1102
|
}
|
|
1218
1103
|
}
|
|
1219
|
-
|
|
1220
1104
|
if (archivedIssuesCount > 0) {
|
|
1221
1105
|
await log(` āļø Skipped ${archivedIssuesCount} issue(s) from archived repositories`);
|
|
1222
1106
|
}
|
|
1223
|
-
|
|
1224
1107
|
issuesToProcess = filteredIssues;
|
|
1225
1108
|
}
|
|
1226
|
-
|
|
1227
1109
|
// Filter out issues with open PRs if option is enabled
|
|
1228
1110
|
if (argv.skipIssuesWithPrs) {
|
|
1229
1111
|
await log(' š Checking for existing pull requests using batch GraphQL query...');
|
|
1230
|
-
|
|
1231
1112
|
// Extract issue numbers and repository info from URLs
|
|
1232
1113
|
const issuesByRepo = {};
|
|
1233
1114
|
for (const issue of issuesToProcess) {
|
|
@@ -1235,7 +1116,6 @@ if (isRunningDirectly) {
|
|
|
1235
1116
|
if (urlMatch) {
|
|
1236
1117
|
const [, issueOwner, issueRepo, issueNumber] = urlMatch;
|
|
1237
1118
|
const repoKey = `${issueOwner}/${issueRepo}`;
|
|
1238
|
-
|
|
1239
1119
|
if (!issuesByRepo[repoKey]) {
|
|
1240
1120
|
issuesByRepo[repoKey] = {
|
|
1241
1121
|
owner: issueOwner,
|
|
@@ -1243,22 +1123,18 @@ if (isRunningDirectly) {
|
|
|
1243
1123
|
issues: [],
|
|
1244
1124
|
};
|
|
1245
1125
|
}
|
|
1246
|
-
|
|
1247
1126
|
issuesByRepo[repoKey].issues.push({
|
|
1248
1127
|
number: parseInt(issueNumber),
|
|
1249
1128
|
issue: issue,
|
|
1250
1129
|
});
|
|
1251
1130
|
}
|
|
1252
1131
|
}
|
|
1253
|
-
|
|
1254
1132
|
// Batch check PRs for each repository
|
|
1255
1133
|
const filteredIssues = [];
|
|
1256
1134
|
let totalSkipped = 0;
|
|
1257
|
-
|
|
1258
1135
|
for (const repoData of Object.values(issuesByRepo)) {
|
|
1259
1136
|
const issueNumbers = repoData.issues.map(i => i.number);
|
|
1260
1137
|
const prResults = await batchCheckPullRequestsForIssues(repoData.owner, repoData.repo, issueNumbers);
|
|
1261
|
-
|
|
1262
1138
|
// Process results
|
|
1263
1139
|
for (const issueData of repoData.issues) {
|
|
1264
1140
|
const prInfo = prResults[issueData.number];
|
|
@@ -1270,19 +1146,16 @@ if (isRunningDirectly) {
|
|
|
1270
1146
|
}
|
|
1271
1147
|
}
|
|
1272
1148
|
}
|
|
1273
|
-
|
|
1274
1149
|
if (totalSkipped > 0) {
|
|
1275
1150
|
await log(` āļø Skipped ${totalSkipped} issue(s) with existing pull requests`);
|
|
1276
1151
|
}
|
|
1277
1152
|
issuesToProcess = filteredIssues;
|
|
1278
1153
|
}
|
|
1279
|
-
|
|
1280
1154
|
// Apply max issues limit if set (after filtering to exclude skipped issues from count)
|
|
1281
1155
|
if (argv.maxIssues > 0 && issuesToProcess.length > argv.maxIssues) {
|
|
1282
1156
|
issuesToProcess = issuesToProcess.slice(0, argv.maxIssues);
|
|
1283
1157
|
await log(` š¢ Limiting to first ${argv.maxIssues} issues (after filtering)`);
|
|
1284
1158
|
}
|
|
1285
|
-
|
|
1286
1159
|
// In dry-run mode, show the issues that would be processed
|
|
1287
1160
|
if (argv.dryRun && issuesToProcess.length > 0) {
|
|
1288
1161
|
await log('\n š Issues that would be processed:');
|
|
@@ -1290,7 +1163,6 @@ if (isRunningDirectly) {
|
|
|
1290
1163
|
await log(` - ${issue.title || 'Untitled'} (${issue.url})`);
|
|
1291
1164
|
}
|
|
1292
1165
|
}
|
|
1293
|
-
|
|
1294
1166
|
return issuesToProcess.map(issue => issue.url);
|
|
1295
1167
|
} catch (error) {
|
|
1296
1168
|
reportError(error, {
|
|
@@ -1304,26 +1176,21 @@ if (isRunningDirectly) {
|
|
|
1304
1176
|
return [];
|
|
1305
1177
|
}
|
|
1306
1178
|
}
|
|
1307
|
-
|
|
1308
1179
|
// Main monitoring loop
|
|
1309
1180
|
async function monitor() {
|
|
1310
1181
|
await log('\nš Starting Hive Mind monitoring system...');
|
|
1311
|
-
|
|
1312
1182
|
// Start workers
|
|
1313
1183
|
await log(`\nš· Starting ${argv.concurrency} workers...`);
|
|
1314
1184
|
for (let i = 1; i <= argv.concurrency; i++) {
|
|
1315
1185
|
issueQueue.workers.push(worker(i));
|
|
1316
1186
|
}
|
|
1317
|
-
|
|
1318
1187
|
// Main monitoring loop
|
|
1319
1188
|
let iteration = 0;
|
|
1320
1189
|
while (true) {
|
|
1321
1190
|
iteration++;
|
|
1322
1191
|
await log(`\nš Monitoring iteration ${iteration} at ${new Date().toISOString()}`);
|
|
1323
|
-
|
|
1324
1192
|
// Fetch issues
|
|
1325
1193
|
const issueUrls = await fetchIssues();
|
|
1326
|
-
|
|
1327
1194
|
// Add new issues to queue
|
|
1328
1195
|
let newIssues = 0;
|
|
1329
1196
|
for (const url of issueUrls) {
|
|
@@ -1332,13 +1199,11 @@ if (isRunningDirectly) {
|
|
|
1332
1199
|
await log(` ā Added to queue: ${url}`);
|
|
1333
1200
|
}
|
|
1334
1201
|
}
|
|
1335
|
-
|
|
1336
1202
|
if (newIssues > 0) {
|
|
1337
1203
|
await log(` š„ Added ${newIssues} new issue(s) to queue`);
|
|
1338
1204
|
} else {
|
|
1339
1205
|
await log(' ā¹ļø No new issues to add (all already processed or in queue)');
|
|
1340
1206
|
}
|
|
1341
|
-
|
|
1342
1207
|
// Show current stats
|
|
1343
1208
|
const stats = issueQueue.getStats();
|
|
1344
1209
|
await log('\nš Current Status:');
|
|
@@ -1347,7 +1212,6 @@ if (isRunningDirectly) {
|
|
|
1347
1212
|
await log(` ā
Completed: ${stats.completed}`);
|
|
1348
1213
|
await log(` ā Failed: ${stats.failed}`);
|
|
1349
1214
|
await log(` š Hive log file: ${absoluteLogPath}`);
|
|
1350
|
-
|
|
1351
1215
|
// Show which issues are currently being processed
|
|
1352
1216
|
if (stats.processingIssues && stats.processingIssues.length > 0) {
|
|
1353
1217
|
await log(' š§ Currently processing solve commands:');
|
|
@@ -1355,11 +1219,9 @@ if (isRunningDirectly) {
|
|
|
1355
1219
|
await log(` - ${issueUrl}`);
|
|
1356
1220
|
}
|
|
1357
1221
|
}
|
|
1358
|
-
|
|
1359
1222
|
// If running once, wait for queue to empty then exit
|
|
1360
1223
|
if (argv.once) {
|
|
1361
1224
|
await log('\nš Single run mode - waiting for queue to empty...');
|
|
1362
|
-
|
|
1363
1225
|
while (stats.queued > 0 || stats.processing > 0) {
|
|
1364
1226
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
1365
1227
|
const currentStats = issueQueue.getStats();
|
|
@@ -1376,36 +1238,29 @@ if (isRunningDirectly) {
|
|
|
1376
1238
|
await log(` Completed: ${stats.completed}`);
|
|
1377
1239
|
await log(` Failed: ${stats.failed}`);
|
|
1378
1240
|
await log(` š Full log file: ${absoluteLogPath}`);
|
|
1379
|
-
|
|
1380
1241
|
// Perform cleanup if enabled and there were successful completions
|
|
1381
1242
|
if (stats.completed > 0) {
|
|
1382
1243
|
await cleanupTempDirectories(argv);
|
|
1383
1244
|
}
|
|
1384
|
-
|
|
1385
1245
|
// Stop workers before breaking to avoid hanging
|
|
1386
1246
|
issueQueue.stop();
|
|
1387
1247
|
break;
|
|
1388
1248
|
}
|
|
1389
|
-
|
|
1390
1249
|
// Wait for next iteration
|
|
1391
1250
|
await log(`\nā° Next check in ${argv.interval} seconds...`);
|
|
1392
1251
|
await new Promise(resolve => setTimeout(resolve, argv.interval * 1000));
|
|
1393
1252
|
}
|
|
1394
|
-
|
|
1395
1253
|
// Stop workers
|
|
1396
1254
|
issueQueue.stop();
|
|
1397
1255
|
await Promise.all(issueQueue.workers);
|
|
1398
|
-
|
|
1399
1256
|
// Perform cleanup if enabled and there were successful completions
|
|
1400
1257
|
const finalStats = issueQueue.getStats();
|
|
1401
1258
|
if (finalStats.completed > 0) {
|
|
1402
1259
|
await cleanupTempDirectories();
|
|
1403
1260
|
}
|
|
1404
|
-
|
|
1405
1261
|
await log('\nš Hive Mind monitoring stopped');
|
|
1406
1262
|
await log(` š Full log file: ${absoluteLogPath}`);
|
|
1407
1263
|
}
|
|
1408
|
-
|
|
1409
1264
|
// Issue #1823: Graceful-shutdown + force-kill logic lives in hive.shutdown.lib.mjs.
|
|
1410
1265
|
// gracefulShutdown waits (uncapped) for in-flight solve workers to finish on the first
|
|
1411
1266
|
// interrupt; on a second interrupt it force-kills their detached process groups.
|
|
@@ -1420,7 +1275,6 @@ if (isRunningDirectly) {
|
|
|
1420
1275
|
absoluteLogPath,
|
|
1421
1276
|
activeSolveChildren,
|
|
1422
1277
|
});
|
|
1423
|
-
|
|
1424
1278
|
// Handle graceful shutdown.
|
|
1425
1279
|
// Issue #1823: Tell the global exit handler (installed earlier via installGlobalExitHandlers)
|
|
1426
1280
|
// to stand down on SIGINT/SIGTERM so it does not call process.exit() and race us. From here
|
|
@@ -1429,7 +1283,6 @@ if (isRunningDirectly) {
|
|
|
1429
1283
|
delegateSignalHandling(true);
|
|
1430
1284
|
process.on('SIGINT', () => gracefulShutdown('interrupt'));
|
|
1431
1285
|
process.on('SIGTERM', () => gracefulShutdown('termination'));
|
|
1432
|
-
|
|
1433
1286
|
// Check system resources (disk space and RAM) before starting monitoring (skip in dry-run mode)
|
|
1434
1287
|
if (argv.dryRun || argv.skipToolConnectionCheck || argv.toolConnectionCheck === false) {
|
|
1435
1288
|
await log('ā© Skipping system resource check (dry-run mode or skip-tool-connection-check enabled)', {
|
|
@@ -1447,11 +1300,9 @@ if (isRunningDirectly) {
|
|
|
1447
1300
|
},
|
|
1448
1301
|
{ log }
|
|
1449
1302
|
);
|
|
1450
|
-
|
|
1451
1303
|
if (!systemCheck.success) {
|
|
1452
1304
|
await safeExit(1, 'Error occurred');
|
|
1453
1305
|
}
|
|
1454
|
-
|
|
1455
1306
|
// Validate the selected AI tool connection before starting monitoring with the same model that will be used
|
|
1456
1307
|
const isToolConnected = await validateToolConnection({ tool: argv.tool, model: argv.model, verbose: argv.verbose, validateClaudeConnection });
|
|
1457
1308
|
if (!isToolConnected) {
|
|
@@ -1459,10 +1310,8 @@ if (isRunningDirectly) {
|
|
|
1459
1310
|
await safeExit(1, 'Error occurred');
|
|
1460
1311
|
}
|
|
1461
1312
|
}
|
|
1462
|
-
|
|
1463
1313
|
// Wrap monitor function with Sentry error tracking
|
|
1464
1314
|
const monitorWithSentry = !argv.sentry ? monitor : withSentry(monitor, 'hive.monitor', 'command');
|
|
1465
|
-
|
|
1466
1315
|
// Start monitoring
|
|
1467
1316
|
try {
|
|
1468
1317
|
await monitorWithSentry();
|
|
@@ -1475,7 +1324,6 @@ if (isRunningDirectly) {
|
|
|
1475
1324
|
await log(` š Full log file: ${absoluteLogPath}`, { level: 'error' });
|
|
1476
1325
|
await safeExit(1, 'Error occurred');
|
|
1477
1326
|
}
|
|
1478
|
-
|
|
1479
1327
|
const finalStats = issueQueue.getStats(); // Issue #1718: surface worker failures via exit code
|
|
1480
1328
|
if (finalStats.failed > 0) await safeExit(1, `${finalStats.failed} task(s) failed (completed: ${finalStats.completed})`);
|
|
1481
1329
|
} catch (fatalError) {
|