@link-assistant/hive-mind 1.35.5 → 1.35.7
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 +16 -0
- package/package.json +1 -1
- package/src/hive.mjs +4 -1
- package/src/interactive-mode.lib.mjs +83 -22
- package/src/lib.mjs +38 -0
- package/src/solve.auto-merge.lib.mjs +32 -6
- package/src/solve.mjs +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.35.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- fca8460: fix: prevent infinite CI waiting loop when workflows complete with action_required (Issue #1466)
|
|
8
|
+
- Detect when all workflow runs completed with non-executing conclusions (action_required, cancelled, stale, skipped) and treat as "CI not triggered" instead of waiting indefinitely for check-runs that will never appear
|
|
9
|
+
- Add verbose log interceptor (setupVerboseLogInterceptor) to capture [VERBOSE] console.log output in log files, fixing the discrepancy between terminal and log file output
|
|
10
|
+
- Add case study with root cause analysis and timeline reconstruction from 5 production log files
|
|
11
|
+
- Add 14 unit tests covering action_required handling, non-executing conclusions, race conditions, and edge cases
|
|
12
|
+
|
|
13
|
+
## 1.35.6
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 4b0beaf: Fix interactive mode PR comment output: use stdin for GitHub API calls to prevent shell quoting corruption, flush comment queue before tool result timeout to prevent stuck "Waiting for result..." comments, and guard against duplicate session started comments from late system.init events
|
|
18
|
+
|
|
3
19
|
## 1.35.5
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/package.json
CHANGED
package/src/hive.mjs
CHANGED
|
@@ -95,7 +95,7 @@ if (isDirectExecution) {
|
|
|
95
95
|
const fs = (await withTimeout(use('fs'), 30000, 'loading fs')).promises;
|
|
96
96
|
// Import shared library functions
|
|
97
97
|
const lib = await import('./lib.mjs');
|
|
98
|
-
const { log, setLogFile, getAbsoluteLogPath, formatTimestamp, cleanErrorMessage, cleanupTempDirectories } = lib;
|
|
98
|
+
const { log, setLogFile, getAbsoluteLogPath, formatTimestamp, cleanErrorMessage, cleanupTempDirectories, setupVerboseLogInterceptor } = lib;
|
|
99
99
|
const yargsConfigLib = await import('./hive.config.lib.mjs');
|
|
100
100
|
const { createYargsConfig } = yargsConfigLib;
|
|
101
101
|
const claudeLib = await import('./claude.lib.mjs');
|
|
@@ -311,6 +311,9 @@ if (isDirectExecution) {
|
|
|
311
311
|
// Set global verbose mode
|
|
312
312
|
global.verboseMode = argv.verbose;
|
|
313
313
|
|
|
314
|
+
// Issue #1466: Intercept console.log to capture [VERBOSE] output in log files
|
|
315
|
+
setupVerboseLogInterceptor();
|
|
316
|
+
|
|
314
317
|
// Use the universal GitHub URL parser
|
|
315
318
|
if (githubUrl) {
|
|
316
319
|
const parsedUrl = parseGitHubUrl(githubUrl);
|
|
@@ -52,6 +52,13 @@ const CONFIG = {
|
|
|
52
52
|
// See: https://github.com/link-assistant/hive-mind/issues/1324
|
|
53
53
|
import { sanitizeUnicode } from './unicode-sanitization.lib.mjs';
|
|
54
54
|
|
|
55
|
+
// Use child_process for stdin-based API calls to avoid shell quoting issues
|
|
56
|
+
// with large/complex comment bodies containing backticks, quotes, etc.
|
|
57
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1458
|
|
58
|
+
import { execFile } from 'node:child_process';
|
|
59
|
+
import { promisify } from 'node:util';
|
|
60
|
+
const execFileAsync = promisify(execFile);
|
|
61
|
+
|
|
55
62
|
/**
|
|
56
63
|
* Truncate content in the middle, keeping start and end
|
|
57
64
|
* This helps show context while reducing size for large outputs
|
|
@@ -237,7 +244,9 @@ const getToolIcon = toolName => {
|
|
|
237
244
|
* @returns {Object} Handler object with event processing methods
|
|
238
245
|
*/
|
|
239
246
|
export const createInteractiveHandler = options => {
|
|
240
|
-
const { owner, repo, prNumber,
|
|
247
|
+
const { owner, repo, prNumber, log, verbose = false, execFile: execFileFn } = options;
|
|
248
|
+
// Use injected execFile for testability, or the real one by default
|
|
249
|
+
const runGhApi = execFileFn || execFileAsync;
|
|
241
250
|
|
|
242
251
|
// State tracking for the handler
|
|
243
252
|
const state = {
|
|
@@ -291,24 +300,35 @@ export const createInteractiveHandler = options => {
|
|
|
291
300
|
}
|
|
292
301
|
|
|
293
302
|
try {
|
|
294
|
-
// Post comment
|
|
295
|
-
|
|
303
|
+
// Post comment via gh api with stdin to avoid shell quoting issues
|
|
304
|
+
// with complex markdown bodies containing backticks, quotes, etc.
|
|
305
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1458
|
|
306
|
+
const apiUrl = `repos/${owner}/${repo}/issues/${prNumber}/comments`;
|
|
307
|
+
const jsonPayload = JSON.stringify({ body });
|
|
308
|
+
const { stdout } = await runGhApi('gh', ['api', apiUrl, '-X', 'POST', '--input', '-'], {
|
|
309
|
+
input: jsonPayload,
|
|
310
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
311
|
+
});
|
|
296
312
|
state.lastCommentTime = Date.now();
|
|
297
313
|
|
|
298
|
-
// Extract comment ID from the
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
314
|
+
// Extract comment ID from the API response JSON
|
|
315
|
+
let commentId = null;
|
|
316
|
+
try {
|
|
317
|
+
const response = JSON.parse(stdout);
|
|
318
|
+
commentId = response.id ? String(response.id) : null;
|
|
319
|
+
} catch {
|
|
320
|
+
// Fallback: try to extract from URL pattern
|
|
321
|
+
const match = stdout.match(/issuecomment-(\d+)|"id":\s*(\d+)/);
|
|
322
|
+
commentId = match ? match[1] || match[2] : null;
|
|
323
|
+
}
|
|
304
324
|
|
|
305
325
|
if (verbose) {
|
|
306
|
-
await log(`✅ Interactive mode: Comment posted${commentId ? ` (ID: ${commentId})` : ''}`, { verbose: true });
|
|
326
|
+
await log(`✅ Interactive mode: Comment posted${commentId ? ` (ID: ${commentId})` : ''} (body: ${body.length} chars)`, { verbose: true });
|
|
307
327
|
}
|
|
308
328
|
return commentId;
|
|
309
329
|
} catch (error) {
|
|
310
330
|
if (verbose) {
|
|
311
|
-
await log(`⚠️ Interactive mode: Failed to post comment: ${error.message}`, { verbose: true });
|
|
331
|
+
await log(`⚠️ Interactive mode: Failed to post comment: ${error.message} (body: ${body.length} chars)`, { verbose: true });
|
|
312
332
|
}
|
|
313
333
|
return null;
|
|
314
334
|
}
|
|
@@ -330,14 +350,22 @@ export const createInteractiveHandler = options => {
|
|
|
330
350
|
}
|
|
331
351
|
|
|
332
352
|
try {
|
|
333
|
-
|
|
353
|
+
// Edit comment via gh api with stdin to avoid shell quoting issues
|
|
354
|
+
// with complex markdown bodies containing backticks, quotes, etc.
|
|
355
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1458
|
|
356
|
+
const apiUrl = `repos/${owner}/${repo}/issues/comments/${commentId}`;
|
|
357
|
+
const jsonPayload = JSON.stringify({ body });
|
|
358
|
+
await runGhApi('gh', ['api', apiUrl, '-X', 'PATCH', '--input', '-'], {
|
|
359
|
+
input: jsonPayload,
|
|
360
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
361
|
+
});
|
|
334
362
|
if (verbose) {
|
|
335
|
-
await log(`✅ Interactive mode: Comment ${commentId} updated`, { verbose: true });
|
|
363
|
+
await log(`✅ Interactive mode: Comment ${commentId} updated (body: ${body.length} chars, payload: ${jsonPayload.length} chars)`, { verbose: true });
|
|
336
364
|
}
|
|
337
365
|
return true;
|
|
338
366
|
} catch (error) {
|
|
339
367
|
if (verbose) {
|
|
340
|
-
await log(`⚠️ Interactive mode: Failed to edit comment: ${error.message}`, { verbose: true });
|
|
368
|
+
await log(`⚠️ Interactive mode: Failed to edit comment ${commentId}: ${error.message} (body: ${body.length} chars)`, { verbose: true });
|
|
341
369
|
}
|
|
342
370
|
return false;
|
|
343
371
|
}
|
|
@@ -398,6 +426,16 @@ export const createInteractiveHandler = options => {
|
|
|
398
426
|
* @param {Object} data - Event data
|
|
399
427
|
*/
|
|
400
428
|
const handleSystemInit = async data => {
|
|
429
|
+
// Guard against duplicate init events (e.g., when a late task_notification
|
|
430
|
+
// arrives after the result event and triggers a new conversation turn)
|
|
431
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1458
|
|
432
|
+
if (state.sessionId) {
|
|
433
|
+
if (verbose) {
|
|
434
|
+
await log(`⚠️ Interactive mode: Ignoring duplicate system.init event (session already initialized: ${state.sessionId})`, { verbose: true });
|
|
435
|
+
}
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
401
439
|
state.sessionId = data.session_id;
|
|
402
440
|
state.startTime = Date.now();
|
|
403
441
|
|
|
@@ -672,21 +710,44 @@ ${createRawJsonSection(data)}`;
|
|
|
672
710
|
// If comment ID is not yet available (comment was queued), wait for it
|
|
673
711
|
// But use a timeout to avoid blocking forever
|
|
674
712
|
if (!commentId && commentIdPromise) {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
713
|
+
// First, try to flush the queue — the tool_use comment may still be
|
|
714
|
+
// waiting for rate-limit clearance. Processing it here avoids the 30s
|
|
715
|
+
// timeout that previously caused many comments to stay stuck on
|
|
716
|
+
// "Waiting for result...".
|
|
717
|
+
// See: https://github.com/link-assistant/hive-mind/issues/1458
|
|
718
|
+
if (state.commentQueue.length > 0) {
|
|
719
|
+
if (verbose) {
|
|
720
|
+
await log(`🔄 Interactive mode: Flushing comment queue (${state.commentQueue.length} items) before waiting for tool use comment`, {
|
|
721
|
+
verbose: true,
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
// Temporarily reset isProcessing to allow processQueue to run
|
|
725
|
+
const wasProcessing = state.isProcessing;
|
|
726
|
+
state.isProcessing = false;
|
|
727
|
+
await processQueue();
|
|
728
|
+
state.isProcessing = wasProcessing;
|
|
679
729
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
commentId =
|
|
730
|
+
|
|
731
|
+
// Check again after queue flush
|
|
732
|
+
commentId = pendingCall.commentId;
|
|
683
733
|
|
|
684
734
|
if (!commentId) {
|
|
685
735
|
if (verbose) {
|
|
686
|
-
await log(
|
|
736
|
+
await log(`⏳ Interactive mode: Waiting for tool use comment to be posted (tool: ${toolUseId})`, {
|
|
687
737
|
verbose: true,
|
|
688
738
|
});
|
|
689
739
|
}
|
|
740
|
+
// Wait for the comment to be posted (with 30 second timeout)
|
|
741
|
+
const timeoutPromise = new Promise(resolve => setTimeout(() => resolve(null), 30000));
|
|
742
|
+
commentId = await Promise.race([commentIdPromise, timeoutPromise]);
|
|
743
|
+
|
|
744
|
+
if (!commentId) {
|
|
745
|
+
if (verbose) {
|
|
746
|
+
await log('⚠️ Interactive mode: Timeout waiting for tool use comment, posting result separately', {
|
|
747
|
+
verbose: true,
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
}
|
|
690
751
|
}
|
|
691
752
|
}
|
|
692
753
|
|
package/src/lib.mjs
CHANGED
|
@@ -114,6 +114,43 @@ export const log = async (message, options = {}) => {
|
|
|
114
114
|
}
|
|
115
115
|
};
|
|
116
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Issue #1466: Intercept console.log to capture [VERBOSE] output in the log file.
|
|
119
|
+
*
|
|
120
|
+
* Functions in github-merge.lib.mjs and github-merge-ci.lib.mjs use console.log()
|
|
121
|
+
* directly for verbose output (e.g., `console.log('[VERBOSE] /merge: ...')`).
|
|
122
|
+
* This means verbose diagnostic data only appears in the terminal, not in log files,
|
|
123
|
+
* making debugging harder.
|
|
124
|
+
*
|
|
125
|
+
* This interceptor wraps console.log so that any message containing '[VERBOSE]'
|
|
126
|
+
* is also appended to the log file. It preserves the original console.log behavior.
|
|
127
|
+
*
|
|
128
|
+
* Call this once after setLogFile() to enable the interceptor.
|
|
129
|
+
*/
|
|
130
|
+
let verboseInterceptorInstalled = false;
|
|
131
|
+
export const setupVerboseLogInterceptor = () => {
|
|
132
|
+
if (verboseInterceptorInstalled) return;
|
|
133
|
+
verboseInterceptorInstalled = true;
|
|
134
|
+
|
|
135
|
+
const originalConsoleLog = console.log.bind(console);
|
|
136
|
+
console.log = (...args) => {
|
|
137
|
+
// Always call original console.log first
|
|
138
|
+
originalConsoleLog(...args);
|
|
139
|
+
|
|
140
|
+
// If a log file is set and the message looks like a [VERBOSE] log, append to file
|
|
141
|
+
if (logFile && args.length > 0) {
|
|
142
|
+
const firstArg = String(args[0]);
|
|
143
|
+
if (firstArg.includes('[VERBOSE]')) {
|
|
144
|
+
const message = args.map(a => String(a)).join(' ');
|
|
145
|
+
const logMessage = `[${new Date().toISOString()}] [VERBOSE] ${message}`;
|
|
146
|
+
fs.appendFile(logFile, logMessage + '\n').catch(() => {
|
|
147
|
+
// Silent fail to avoid infinite loops
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
|
|
117
154
|
/**
|
|
118
155
|
* Mask sensitive tokens in text
|
|
119
156
|
* @param {string} token - Token to mask
|
|
@@ -469,6 +506,7 @@ export default {
|
|
|
469
506
|
formatAligned,
|
|
470
507
|
displayFormattedError,
|
|
471
508
|
cleanupTempDirectories,
|
|
509
|
+
setupVerboseLogInterceptor,
|
|
472
510
|
};
|
|
473
511
|
|
|
474
512
|
/**
|
|
@@ -224,7 +224,28 @@ const getMergeBlockers = async (owner, repo, prNumber, verbose = false) => {
|
|
|
224
224
|
// - workflow_runs.length === 0 → CI was NOT triggered (fork PR, paths-ignore, etc.)
|
|
225
225
|
const workflowRuns = await getWorkflowRunsForSha(owner, repo, ciStatus.sha, verbose);
|
|
226
226
|
if (workflowRuns.length > 0) {
|
|
227
|
-
//
|
|
227
|
+
// Issue #1466: Check if ALL workflow runs are completed without producing check-runs.
|
|
228
|
+
// This happens when workflows require manual approval (first-time fork contributors,
|
|
229
|
+
// deployment approvals) — they complete with conclusion=action_required but never
|
|
230
|
+
// create check-runs. Waiting for check-runs in this case is an infinite loop.
|
|
231
|
+
//
|
|
232
|
+
// Also covers other non-executing conclusions: cancelled, stale workflows that
|
|
233
|
+
// completed without producing check-runs won't produce them in the future either.
|
|
234
|
+
const allRunsCompleted = workflowRuns.every(r => r.status === 'completed');
|
|
235
|
+
const allRunsNonExecuting = allRunsCompleted && workflowRuns.every(r => r.conclusion === 'action_required' || r.conclusion === 'cancelled' || r.conclusion === 'stale' || r.conclusion === 'skipped');
|
|
236
|
+
|
|
237
|
+
if (allRunsNonExecuting) {
|
|
238
|
+
// All workflow runs completed without executing jobs — check-runs will never appear.
|
|
239
|
+
// Treat the same as "CI not triggered" to avoid infinite waiting.
|
|
240
|
+
const conclusions = [...new Set(workflowRuns.map(r => r.conclusion))].join(', ');
|
|
241
|
+
if (verbose) {
|
|
242
|
+
console.log(`[VERBOSE] /merge: PR #${prNumber} has ${workflowRuns.length} workflow run(s) for SHA ${ciStatus.sha.substring(0, 7)}, but all completed without executing (conclusions: ${conclusions}) — check-runs will never appear`);
|
|
243
|
+
}
|
|
244
|
+
await log(formatAligned('ℹ️', 'CI workflows completed without executing:', `${conclusions} (${workflowRuns.map(r => r.name).join(', ')})`, 2));
|
|
245
|
+
return { blockers, ciStatus, noCiConfigured: false, noCiTriggered: true, workflowRunConclusions: conclusions };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Some workflow runs are still in progress or produced results — genuine race condition
|
|
228
249
|
if (verbose) {
|
|
229
250
|
console.log(`[VERBOSE] /merge: PR #${prNumber} has no CI check-runs yet, but ${workflowRuns.length} workflow run(s) were triggered for SHA ${ciStatus.sha.substring(0, 7)} - genuine race condition (waiting for check-runs to appear)`);
|
|
230
251
|
}
|
|
@@ -414,7 +435,7 @@ export const watchUntilMergeable = async params => {
|
|
|
414
435
|
|
|
415
436
|
try {
|
|
416
437
|
// Get merge blockers
|
|
417
|
-
const { blockers, noCiConfigured, noCiTriggered } = await getMergeBlockers(owner, repo, prNumber, argv.verbose);
|
|
438
|
+
const { blockers, noCiConfigured, noCiTriggered, workflowRunConclusions } = await getMergeBlockers(owner, repo, prNumber, argv.verbose);
|
|
418
439
|
|
|
419
440
|
// Check for new comments from non-bot users
|
|
420
441
|
const { hasNewComments, comments } = await checkForNonBotComments(owner, repo, prNumber, issueNumber, lastCheckTime, argv.verbose);
|
|
@@ -422,10 +443,15 @@ export const watchUntilMergeable = async params => {
|
|
|
422
443
|
// Check for uncommitted changes using shared utility
|
|
423
444
|
const hasUncommittedChanges = await checkForUncommittedChanges(tempDir, argv);
|
|
424
445
|
|
|
425
|
-
// Issue #1442: If CI workflows exist but were not triggered for this commit,
|
|
446
|
+
// Issue #1442/#1466: If CI workflows exist but were not triggered for this commit,
|
|
426
447
|
// log why before proceeding to the mergeable path.
|
|
427
448
|
if (noCiTriggered) {
|
|
428
|
-
|
|
449
|
+
if (workflowRunConclusions) {
|
|
450
|
+
// Issue #1466: Workflow runs exist but completed without executing (action_required, cancelled, etc.)
|
|
451
|
+
await log(formatAligned('ℹ️', 'CI not executed:', `Workflow runs completed with: ${workflowRunConclusions} (likely needs maintainer approval)`, 2));
|
|
452
|
+
} else {
|
|
453
|
+
await log(formatAligned('ℹ️', 'CI not triggered:', 'Workflows exist but no workflow runs for this commit (fork PR, paths-ignore, workflow conditions)', 2));
|
|
454
|
+
}
|
|
429
455
|
}
|
|
430
456
|
|
|
431
457
|
// If PR is mergeable, no blockers, no new comments, and no uncommitted changes
|
|
@@ -444,7 +470,7 @@ export const watchUntilMergeable = async params => {
|
|
|
444
470
|
// Post success comment
|
|
445
471
|
try {
|
|
446
472
|
// Issue #1345: Differentiate message when no CI is configured
|
|
447
|
-
const ciLine = noCiConfigured ? '- No CI/CD checks are configured for this repository' : noCiTriggered ? '- CI workflows exist but were not triggered for this commit' : '- All CI checks have passed';
|
|
473
|
+
const ciLine = noCiConfigured ? '- No CI/CD checks are configured for this repository' : noCiTriggered ? (workflowRunConclusions ? `- CI workflows completed without executing (${workflowRunConclusions})` : '- CI workflows exist but were not triggered for this commit') : '- All CI checks have passed';
|
|
448
474
|
const commentBody = `## 🎉 Auto-merged\n\nThis pull request has been automatically merged by hive-mind.\n${ciLine}\n\n---\n*Auto-merged by hive-mind with --auto-merge flag*`;
|
|
449
475
|
await $`gh pr comment ${prNumber} --repo ${owner}/${repo} --body ${commentBody}`;
|
|
450
476
|
} catch {
|
|
@@ -468,7 +494,7 @@ export const watchUntilMergeable = async params => {
|
|
|
468
494
|
try {
|
|
469
495
|
if (!readyToMergeCommentPosted) {
|
|
470
496
|
// Issue #1345: Differentiate message when no CI is configured
|
|
471
|
-
const ciLine = noCiConfigured ? '- No CI/CD checks are configured for this repository' : noCiTriggered ? '- CI workflows exist but were not triggered for this commit' : '- All CI checks have passed';
|
|
497
|
+
const ciLine = noCiConfigured ? '- No CI/CD checks are configured for this repository' : noCiTriggered ? (workflowRunConclusions ? `- CI workflows completed without executing (${workflowRunConclusions})` : '- CI workflows exist but were not triggered for this commit') : '- All CI checks have passed';
|
|
472
498
|
const commentBody = `## ✅ Ready to merge\n\nThis pull request is now ready to be merged:\n${ciLine}\n- No merge conflicts\n- No pending changes\n\n---\n*Monitored by hive-mind with --auto-restart-until-mergeable flag*`;
|
|
473
499
|
await $`gh pr comment ${prNumber} --repo ${owner}/${repo} --body ${commentBody}`;
|
|
474
500
|
readyToMergeCommentPosted = true;
|
package/src/solve.mjs
CHANGED
|
@@ -48,7 +48,7 @@ const fs = (await use('fs')).promises;
|
|
|
48
48
|
const crypto = (await use('crypto')).default;
|
|
49
49
|
const memoryCheck = await import('./memory-check.mjs');
|
|
50
50
|
const lib = await import('./lib.mjs');
|
|
51
|
-
const { log, setLogFile, getLogFile, getAbsoluteLogPath, cleanErrorMessage, formatAligned, getVersionInfo } = lib;
|
|
51
|
+
const { log, setLogFile, getLogFile, getAbsoluteLogPath, cleanErrorMessage, formatAligned, getVersionInfo, setupVerboseLogInterceptor } = lib;
|
|
52
52
|
const githubLib = await import('./github.lib.mjs');
|
|
53
53
|
const { sanitizeLogContent, attachLogToGitHub, getToolDisplayName } = githubLib;
|
|
54
54
|
const validation = await import('./solve.validation.lib.mjs');
|
|
@@ -111,6 +111,9 @@ try {
|
|
|
111
111
|
}
|
|
112
112
|
global.verboseMode = argv.verbose;
|
|
113
113
|
|
|
114
|
+
// Issue #1466: Intercept console.log to capture [VERBOSE] output in log files
|
|
115
|
+
setupVerboseLogInterceptor();
|
|
116
|
+
|
|
114
117
|
// Early logs go to cwd; custom log dir takes effect after argv is parsed
|
|
115
118
|
|
|
116
119
|
// Conditionally import tool-specific functions after argv is parsed
|