@link-assistant/hive-mind 1.78.0 → 1.78.2

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/package.json +1 -1
  3. package/src/agent.lib.mjs +2 -1
  4. package/src/claude.lib.mjs +2 -2
  5. package/src/claude.runtime-switch.lib.mjs +2 -1
  6. package/src/codex.lib.mjs +2 -1
  7. package/src/config.lib.mjs +2 -1
  8. package/src/contributing-guidelines.lib.mjs +2 -1
  9. package/src/gemini.lib.mjs +2 -1
  10. package/src/github-entity-validation.lib.mjs +2 -1
  11. package/src/github-error-reporter.lib.mjs +2 -1
  12. package/src/github.batch.lib.mjs +2 -1
  13. package/src/github.lib.mjs +2 -1
  14. package/src/handoff-skill.lib.mjs +2 -1
  15. package/src/hive.mjs +11 -10
  16. package/src/interactive-codex-events.lib.mjs +167 -0
  17. package/src/interactive-mode.lib.mjs +62 -152
  18. package/src/interactive-mode.shared.lib.mjs +1 -0
  19. package/src/interactive-system-events.lib.mjs +224 -0
  20. package/src/isolation-runner.lib.mjs +2 -1
  21. package/src/lenv-reader.lib.mjs +2 -1
  22. package/src/lib.mjs +2 -1
  23. package/src/lino.lib.mjs +2 -1
  24. package/src/local-ci-checks.lib.mjs +2 -1
  25. package/src/log-upload.lib.mjs +2 -1
  26. package/src/memory-check.mjs +2 -1
  27. package/src/models/index.mjs +2 -1
  28. package/src/npm-global-prefix.lib.mjs +160 -0
  29. package/src/opencode.lib.mjs +2 -1
  30. package/src/playwright-mcp.lib.mjs +2 -1
  31. package/src/protect-branch.mjs +2 -1
  32. package/src/queue-config.lib.mjs +2 -1
  33. package/src/qwen.lib.mjs +2 -1
  34. package/src/review.mjs +2 -1
  35. package/src/reviewers-hive.mjs +2 -1
  36. package/src/session-monitor.lib.mjs +82 -22
  37. package/src/solve.auto-continue.lib.mjs +2 -1
  38. package/src/solve.auto-ensure.lib.mjs +2 -1
  39. package/src/solve.auto-merge-helpers.lib.mjs +2 -1
  40. package/src/solve.auto-merge.lib.mjs +2 -1
  41. package/src/solve.bootstrap.lib.mjs +2 -1
  42. package/src/solve.escalate.lib.mjs +2 -1
  43. package/src/solve.execution.lib.mjs +2 -1
  44. package/src/solve.fork-detection.lib.mjs +2 -1
  45. package/src/solve.fork-sync.lib.mjs +2 -1
  46. package/src/solve.keep-working.lib.mjs +2 -1
  47. package/src/solve.mjs +2 -2
  48. package/src/solve.repository.lib.mjs +2 -1
  49. package/src/solve.restart-shared.lib.mjs +2 -1
  50. package/src/solve.results.lib.mjs +2 -1
  51. package/src/solve.validation.lib.mjs +2 -1
  52. package/src/solve.watch.lib.mjs +2 -1
  53. package/src/telegram-bot.mjs +2 -1
  54. package/src/token-sanitization.lib.mjs +2 -1
  55. package/src/tool-comments.lib.mjs +2 -1
  56. package/src/use-m-bootstrap.lib.mjs +23 -0
  57. package/src/useless-tools.lib.mjs +2 -1
  58. package/src/youtrack/youtrack.lib.mjs +2 -1
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Ensure npm's global install directory is writable before `use-m` runs.
5
+ *
6
+ * Issue #1897: `use-m` loads runtime dependencies (command-stream, getenv,
7
+ * yargs, …) by shelling out to `npm install -g <alias>@npm:<pkg>@latest`.
8
+ * npm installs into the global prefix reported by `npm root -g`. When the
9
+ * CLI is launched with a system-wide Node.js whose global `node_modules`
10
+ * directory is owned by root (e.g. `/opt/node-v24.16.0-linux-x64/lib/node_modules`),
11
+ * the install fails with `EACCES: permission denied` and the whole process
12
+ * crashes at the very first `use()` call with an unhandled error:
13
+ *
14
+ * Error: Failed to install command-stream@latest globally.
15
+ * ... npm error code EACCES
16
+ * ... npm error syscall rename
17
+ * ... npm error path /opt/node-.../lib/node_modules/command-stream-v-latest
18
+ *
19
+ * This commonly happens when the package was installed with one runtime
20
+ * (e.g. `bun add -g`, which writes to a user-owned `~/.bun/...`) but launched
21
+ * under a system Node whose global prefix needs root.
22
+ *
23
+ * The fix mirrors npm's own documented recommendation for EACCES errors:
24
+ * point the global prefix at a user-writable directory. We detect the
25
+ * non-writable prefix up front and redirect `npm_config_prefix` (which both
26
+ * `npm install -g` and `npm root -g` honour) to `~/.npm-global`, so use-m's
27
+ * install succeeds without sudo. When the prefix is already writable we do
28
+ * nothing — the common case stays a no-op with no extra `npm` spawn.
29
+ */
30
+
31
+ import { access, mkdir } from 'node:fs/promises';
32
+ import { constants as fsConstants } from 'node:fs';
33
+ import { dirname, join } from 'node:path';
34
+ import { homedir } from 'node:os';
35
+ import { exec } from 'node:child_process';
36
+ import { promisify } from 'node:util';
37
+
38
+ const execAsync = promisify(exec);
39
+
40
+ /**
41
+ * Derive the likely global `node_modules` path from the Node binary location
42
+ * without spawning npm. For a standard POSIX layout the node binary lives at
43
+ * `<prefix>/bin/node` and global packages at `<prefix>/lib/node_modules`.
44
+ *
45
+ * @param {string} execPath - Absolute path to the running node binary.
46
+ * @returns {string} Candidate global `node_modules` directory.
47
+ */
48
+ export const deriveGlobalNodeModules = execPath => join(dirname(dirname(execPath)), 'lib', 'node_modules');
49
+
50
+ /**
51
+ * Check whether a directory is writable, walking up to the nearest existing
52
+ * ancestor when the leaf does not yet exist (npm would create it on install).
53
+ *
54
+ * @param {string} startDir - Directory to test.
55
+ * @param {(path: string, mode: number) => Promise<void>} accessFn - fs.access.
56
+ * @returns {Promise<boolean>}
57
+ */
58
+ export const isPathWritable = async (startDir, accessFn = access) => {
59
+ let current = startDir;
60
+ for (;;) {
61
+ try {
62
+ await accessFn(current, fsConstants.W_OK);
63
+ return true;
64
+ } catch (error) {
65
+ if (error && error.code === 'ENOENT') {
66
+ const parent = dirname(current);
67
+ if (parent === current) return false; // reached filesystem root
68
+ current = parent;
69
+ continue;
70
+ }
71
+ // EACCES / EPERM / anything else → treat as not writable.
72
+ return false;
73
+ }
74
+ }
75
+ };
76
+
77
+ const queryNpmRoot = async runner => {
78
+ try {
79
+ const { stdout } = await runner('npm root -g');
80
+ const value = String(stdout).trim();
81
+ return value || null;
82
+ } catch {
83
+ return null;
84
+ }
85
+ };
86
+
87
+ /**
88
+ * Detect a non-writable npm global prefix and, if found, redirect global
89
+ * installs to a user-writable directory by setting `npm_config_prefix`.
90
+ *
91
+ * Idempotent and dependency-injectable for tests. Returns an object describing
92
+ * what happened: `{ changed: boolean, reason: string, prefix?, previousRoot? }`.
93
+ *
94
+ * @param {object} [options]
95
+ * @param {Record<string, string>} [options.env=process.env] - Environment to mutate.
96
+ * @param {string} [options.execPath=process.execPath] - Running node binary path.
97
+ * @param {string} [options.platform=process.platform] - OS platform.
98
+ * @param {string} [options.home] - User home directory.
99
+ * @param {Function} [options.accessFn=fs.access]
100
+ * @param {Function} [options.mkdirFn=fs.mkdir]
101
+ * @param {Function} [options.runner=execAsync] - Runs a shell command, returns {stdout}.
102
+ * @param {(message: string) => void} [options.log] - Informational logger.
103
+ * @returns {Promise<{changed: boolean, reason: string, prefix?: string, previousRoot?: string, error?: Error}>}
104
+ */
105
+ export const ensureWritableNpmGlobalPrefix = async (options = {}) => {
106
+ const { env = process.env, execPath = process.execPath, platform = process.platform, home = homedir(), accessFn = access, mkdirFn = mkdir, runner = execAsync, log = () => {}, isBunRuntime = typeof Bun !== 'undefined', isDenoRuntime = typeof Deno !== 'undefined' } = options;
107
+
108
+ // Windows' global layout differs (`<prefix>/node_modules`, AppData), and the
109
+ // EACCES scenario this guards against is POSIX-specific. Skip to avoid false
110
+ // positives that would needlessly relocate the prefix.
111
+ if (platform === 'win32') return { changed: false, reason: 'win32' };
112
+
113
+ // This workaround only protects use-m's Node/npm resolver. Bun and Deno use
114
+ // different install paths and should not have npm configuration changed.
115
+ if (isBunRuntime) return { changed: false, reason: 'bun-runtime' };
116
+ if (isDenoRuntime) return { changed: false, reason: 'deno-runtime' };
117
+
118
+ // Respect an explicitly configured prefix — the user (or a parent process)
119
+ // already chose where global installs go.
120
+ if (env.npm_config_prefix || env.NPM_CONFIG_PREFIX) return { changed: false, reason: 'preset' };
121
+
122
+ // Fast path: derive the likely global node_modules from the node binary and
123
+ // check writability without spawning npm. Most installs land here.
124
+ const derived = deriveGlobalNodeModules(execPath);
125
+ if (await isPathWritable(derived, accessFn)) {
126
+ return { changed: false, reason: 'writable' };
127
+ }
128
+
129
+ // The cheap heuristic says non-writable. Confirm against the authoritative
130
+ // path npm/use-m actually use before changing anything (handles custom prefixes).
131
+ const authoritative = (await queryNpmRoot(runner)) || derived;
132
+ if (await isPathWritable(authoritative, accessFn)) {
133
+ return { changed: false, reason: 'writable' };
134
+ }
135
+
136
+ // Global prefix is genuinely not writable by the current user (issue #1897).
137
+ if (!home) {
138
+ return { changed: false, reason: 'no-home' };
139
+ }
140
+
141
+ const prefix = join(home, '.npm-global');
142
+ try {
143
+ // npm installs into `<prefix>/lib/node_modules`; create it so the very
144
+ // first `npm install -g` does not have to.
145
+ await mkdirFn(join(prefix, 'lib', 'node_modules'), { recursive: true });
146
+ } catch (error) {
147
+ return { changed: false, reason: 'mkdir-failed', error };
148
+ }
149
+
150
+ env.npm_config_prefix = prefix;
151
+ // Make globally-installed binaries from the new prefix resolvable too.
152
+ const binDir = join(prefix, 'bin');
153
+ const pathParts = String(env.PATH || '').split(':');
154
+ if (!pathParts.includes(binDir)) {
155
+ env.PATH = env.PATH ? `${binDir}:${env.PATH}` : binDir;
156
+ }
157
+
158
+ log(`ℹ️ npm global directory (${authoritative}) is not writable; redirecting global installs to ${prefix} (issue #1897).`);
159
+ return { changed: true, reason: 'redirected', prefix, previousRoot: authoritative };
160
+ };
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
  // OpenCode-related utility functions
3
4
 
4
5
  // Check if use is already defined (when imported from solve.mjs)
5
6
  // If not, fetch it (when running standalone)
6
7
  if (typeof globalThis.use === 'undefined') {
7
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
8
+ await ensureUseM();
8
9
  }
9
10
 
10
11
  const { $ } = await use('command-stream');
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
  // Playwright MCP session-level disable/restore utilities.
3
4
  if (typeof globalThis.use === 'undefined') {
4
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
5
+ await ensureUseM();
5
6
  }
6
7
  const { $ } = await use('command-stream');
7
8
  const fs = (await use('fs')).promises;
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
  /**
3
4
  * Branch Protection Script
4
5
  *
@@ -15,7 +16,7 @@
15
16
  */
16
17
 
17
18
  // Use use-m to dynamically import modules for cross-runtime compatibility
18
- const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
19
+ const use = await ensureUseM();
19
20
 
20
21
  // Use command-stream for consistent $ behavior across runtimes
21
22
  const { $: __rawDollar$ } = await use('command-stream');
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  /**
4
5
  * Queue Configuration Module
@@ -26,7 +27,7 @@
26
27
  // Use use-m to dynamically import modules
27
28
  if (typeof globalThis.use === 'undefined') {
28
29
  try {
29
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
30
+ await ensureUseM();
30
31
  } catch (error) {
31
32
  console.error('❌ Fatal error: Failed to load dependencies for queue configuration');
32
33
  console.error(` ${error.message}`);
package/src/qwen.lib.mjs CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
  // Qwen Code CLI-related utility functions
3
4
 
4
5
  // Check if use is already defined (when imported from solve.mjs)
5
6
  // If not, fetch it (when running standalone)
6
7
  if (typeof globalThis.use === 'undefined') {
7
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
8
+ await ensureUseM();
8
9
  }
9
10
 
10
11
  const { $ } = await use('command-stream');
package/src/review.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  // Early exit paths - handle these before loading all modules to speed up testing
4
5
  const earlyArgs = process.argv.slice(2);
@@ -32,7 +33,7 @@ if (earlyArgs.includes('--help') || earlyArgs.includes('-h')) {
32
33
  }
33
34
 
34
35
  // Use use-m to dynamically import modules for cross-runtime compatibility
35
- const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
36
+ const use = await ensureUseM();
36
37
 
37
38
  // Use command-stream for consistent $ behavior across runtimes
38
39
  const { $: __rawDollar$ } = await use('command-stream');
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  // Use use-m to dynamically import modules for cross-runtime compatibility
4
- const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
5
+ const use = await ensureUseM();
5
6
 
6
7
  // Use command-stream for consistent $ behavior across runtimes
7
8
  const { $: __rawDollar$ } = await use('command-stream');
@@ -15,8 +15,9 @@
15
15
  * @see https://github.com/link-assistant/hive-mind/issues/380
16
16
  */
17
17
 
18
- import { promisify } from 'util';
19
18
  import { exec as execCallback } from 'child_process';
19
+ import fs from 'fs/promises';
20
+ import { promisify } from 'util';
20
21
  import { formatSessionCompletionMessage, getSessionCompletionExitCode } from './work-session-formatting.lib.mjs';
21
22
  import { notifySubscribers, getSubscriberCount } from './telegram-subscribers.lib.mjs';
22
23
 
@@ -157,6 +158,45 @@ function normalizeSessionUrl(url) {
157
158
  return url.replace(/#.*$/, '').replace(/\/+$/, '').toLowerCase();
158
159
  }
159
160
 
161
+ const GITHUB_PULL_REQUEST_URL_RE = /https:\/\/github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)\/pull\/([0-9]+)/g;
162
+
163
+ export function extractPullRequestUrlFromText(text, { owner = null, repo = null } = {}) {
164
+ if (!text) return null;
165
+
166
+ const expectedOwner = owner ? String(owner).toLowerCase() : null;
167
+ const expectedRepo = repo ? String(repo).toLowerCase() : null;
168
+ const value = String(text);
169
+ GITHUB_PULL_REQUEST_URL_RE.lastIndex = 0;
170
+
171
+ let match;
172
+ while ((match = GITHUB_PULL_REQUEST_URL_RE.exec(value)) !== null) {
173
+ const [, matchOwner, matchRepo, pullNumber] = match;
174
+ if (expectedOwner && matchOwner.toLowerCase() !== expectedOwner) continue;
175
+ if (expectedRepo && matchRepo.toLowerCase() !== expectedRepo) continue;
176
+ return `https://github.com/${matchOwner}/${matchRepo}/pull/${pullNumber}`;
177
+ }
178
+
179
+ return null;
180
+ }
181
+
182
+ async function resolvePullRequestUrlFromSessionLog(logPath, ctx, { verbose = false, readFile = fs.readFile } = {}) {
183
+ if (!logPath) return null;
184
+
185
+ try {
186
+ const logText = await readFile(logPath, 'utf8');
187
+ const pullRequestUrl = extractPullRequestUrlFromText(logText, { owner: ctx.owner, repo: ctx.repo });
188
+ if (pullRequestUrl && verbose) {
189
+ console.log(`[VERBOSE] Found PR ${pullRequestUrl} in completed session log ${logPath}`);
190
+ }
191
+ return pullRequestUrl;
192
+ } catch (error) {
193
+ if (verbose) {
194
+ console.log(`[VERBOSE] Could not inspect session log ${logPath} for PR URL: ${error?.message || error}`);
195
+ }
196
+ return null;
197
+ }
198
+ }
199
+
160
200
  function isNonIsolationSessionActive(sessionName, sessionInfo, verbose = false) {
161
201
  const startTime = sessionInfo.startTime instanceof Date ? sessionInfo.startTime : new Date(sessionInfo.startTime);
162
202
  const elapsed = Date.now() - startTime.getTime();
@@ -272,13 +312,19 @@ export async function monitorSessions(bot, verbose = false, options = {}) {
272
312
  try {
273
313
  const finalExitCode = getSessionCompletionExitCode({ exitCode, statusResult });
274
314
 
275
- // Issue #1688: When the original /solve URL was an issue, look up the
276
- // linked PR so the completion message can include both an `Issue:` and
277
- // a `Pull request:` line. Failures are logged and ignored — the
278
- // notification still goes out without the PR line.
315
+ // Issue #1688/#1905: When the original /solve URL was an issue, look up
316
+ // the created PR so the completion message can include both an
317
+ // `Issue:` and a `Pull request:` line. The linked-issue API can lag
318
+ // behind the solver's own verification log, so we also inspect the
319
+ // completed session log before giving up.
279
320
  let pullRequestUrl = null;
280
321
  try {
281
- pullRequestUrl = await resolvePullRequestUrlForSession(sessionInfo, { verbose, lookupLinkedPullRequest: options.lookupLinkedPullRequest });
322
+ pullRequestUrl = await resolvePullRequestUrlForSession(sessionInfo, {
323
+ verbose,
324
+ lookupLinkedPullRequest: options.lookupLinkedPullRequest,
325
+ statusResult,
326
+ readFile: options.readFile,
327
+ });
282
328
  } catch (lookupError) {
283
329
  if (verbose) {
284
330
  console.log(`[VERBOSE] Pull request lookup failed for ${sessionName}: ${lookupError?.message || lookupError}`);
@@ -395,36 +441,50 @@ export async function monitorSessions(bot, verbose = false, options = {}) {
395
441
  * @param {Object} [options]
396
442
  * @param {boolean} [options.verbose]
397
443
  * @param {Function} [options.lookupLinkedPullRequest] - Optional override `(ctx) => Promise<string|null>`
444
+ * @param {Object} [options.statusResult] - Completed start-command status payload, including logPath
445
+ * @param {Function} [options.readFile] - Optional test override for reading session logs
398
446
  * @returns {Promise<string|null>} PR URL or null
399
447
  *
400
448
  * @see https://github.com/link-assistant/hive-mind/issues/1688
449
+ * @see https://github.com/link-assistant/hive-mind/issues/1905
401
450
  */
402
- async function resolvePullRequestUrlForSession(sessionInfo, { verbose = false, lookupLinkedPullRequest = null } = {}) {
451
+ async function resolvePullRequestUrlForSession(sessionInfo, { verbose = false, lookupLinkedPullRequest = null, statusResult = null, readFile = fs.readFile } = {}) {
403
452
  const ctx = sessionInfo?.urlContext;
404
453
  if (!ctx || ctx.type !== 'issue' || !ctx.owner || !ctx.repo || !ctx.number) {
405
454
  return null;
406
455
  }
407
456
 
408
457
  if (typeof lookupLinkedPullRequest === 'function') {
409
- return await lookupLinkedPullRequest(ctx);
410
- }
411
-
412
- try {
413
- const { batchCheckPullRequestsForIssues } = await import('./github.lib.mjs');
414
- const result = await batchCheckPullRequestsForIssues(ctx.owner, ctx.repo, [ctx.number]);
415
- const linkedPRs = result?.[ctx.number]?.linkedPRs || [];
416
- if (linkedPRs.length > 0 && linkedPRs[0].url) {
458
+ const linkedPullRequestUrl = await lookupLinkedPullRequest(ctx);
459
+ if (linkedPullRequestUrl) return linkedPullRequestUrl;
460
+ } else {
461
+ try {
462
+ const { batchCheckPullRequestsForIssues } = await import('./github.lib.mjs');
463
+ const result = await batchCheckPullRequestsForIssues(ctx.owner, ctx.repo, [ctx.number]);
464
+ const linkedPRs = result?.[ctx.number]?.linkedPRs || [];
465
+ if (linkedPRs.length > 0 && linkedPRs[0].url) {
466
+ if (verbose) {
467
+ console.log(`[VERBOSE] Found linked PR ${linkedPRs[0].url} for issue ${ctx.owner}/${ctx.repo}#${ctx.number}`);
468
+ }
469
+ return linkedPRs[0].url;
470
+ }
471
+ } catch (error) {
417
472
  if (verbose) {
418
- console.log(`[VERBOSE] Found linked PR ${linkedPRs[0].url} for issue ${ctx.owner}/${ctx.repo}#${ctx.number}`);
473
+ console.log(`[VERBOSE] batchCheckPullRequestsForIssues failed for ${ctx.owner}/${ctx.repo}#${ctx.number}: ${error?.message || error}`);
419
474
  }
420
- return linkedPRs[0].url;
421
- }
422
- } catch (error) {
423
- if (verbose) {
424
- console.log(`[VERBOSE] batchCheckPullRequestsForIssues failed for ${ctx.owner}/${ctx.repo}#${ctx.number}: ${error?.message || error}`);
425
475
  }
426
- throw error;
427
476
  }
477
+
478
+ const logPath = statusResult?.logPath || sessionInfo?.logPath || null;
479
+ const pullRequestUrlFromLog = await resolvePullRequestUrlFromSessionLog(logPath, ctx, { verbose, readFile });
480
+ if (pullRequestUrlFromLog) return pullRequestUrlFromLog;
481
+
482
+ if (verbose && logPath) {
483
+ console.log(`[VERBOSE] No PR URL found for issue ${ctx.owner}/${ctx.repo}#${ctx.number} in session log ${logPath}`);
484
+ } else if (verbose) {
485
+ console.log(`[VERBOSE] No session log path available for PR URL fallback for issue ${ctx.owner}/${ctx.repo}#${ctx.number}`);
486
+ }
487
+
428
488
  return null;
429
489
  }
430
490
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  // Session continuation module for solve command
4
5
  // Handles session resumption, PR detection, and limit reset waiting
@@ -8,7 +9,7 @@
8
9
  // Check if use is already defined globally (when imported from solve.mjs)
9
10
  // If not, fetch it (when running standalone)
10
11
  if (typeof globalThis.use === 'undefined') {
11
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
12
+ await ensureUseM();
12
13
  }
13
14
  const use = globalThis.use;
14
15
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  /**
4
5
  * Finalize module for solve.mjs
@@ -13,7 +14,7 @@
13
14
  // Check if use is already defined globally (when imported from solve.mjs)
14
15
  // If not, fetch it (when running standalone)
15
16
  if (typeof globalThis.use === 'undefined') {
16
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
17
+ await ensureUseM();
17
18
  }
18
19
  const use = globalThis.use;
19
20
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  /**
4
5
  * Helper functions for the auto-merge module.
@@ -16,7 +17,7 @@
16
17
  // Check if use is already defined globally (when imported from solve.mjs)
17
18
  // If not, fetch it (when running standalone)
18
19
  if (typeof globalThis.use === 'undefined') {
19
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
20
+ await ensureUseM();
20
21
  }
21
22
  const use = globalThis.use;
22
23
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  /**
4
5
  * Auto-merge and auto-restart-until-mergeable module for solve.mjs
@@ -12,7 +13,7 @@
12
13
  // Check if use is already defined globally (when imported from solve.mjs)
13
14
  // If not, fetch it (when running standalone)
14
15
  if (typeof globalThis.use === 'undefined') {
15
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
16
+ await ensureUseM();
16
17
  }
17
18
  const use = globalThis.use;
18
19
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  /**
4
5
  * Handle lightweight early-exit paths before solve loads its full dependency graph.
@@ -20,7 +21,7 @@ export async function handleSolveEarlyExit(earlyArgs) {
20
21
 
21
22
  if (earlyArgs.includes('--help') || earlyArgs.includes('-h')) {
22
23
  // Load minimal modules needed for help output.
23
- const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
24
+ const use = await ensureUseM();
24
25
  globalThis.use = use;
25
26
  const { initializeConfig, createYargsConfig } = await import('./solve.config.lib.mjs');
26
27
  const { yargs, hideBin } = await initializeConfig(use);
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  /**
4
5
  * Escalate-mode module for solve.mjs
@@ -326,7 +327,7 @@ export const runEscalation = async ({ issueUrl, owner, repo, issueNumber, prNumb
326
327
  }
327
328
 
328
329
  if (typeof globalThis.use === 'undefined') {
329
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
330
+ await ensureUseM();
330
331
  }
331
332
  const use = globalThis.use;
332
333
  const { $: __rawDollar$ } = await use('command-stream');
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  // Main execution logic module for solve command
4
5
  // Extracted from solve.mjs to keep files under 1500 lines
@@ -7,7 +8,7 @@
7
8
  // Check if use is already defined globally (when imported from solve.mjs)
8
9
  // If not, fetch it (when running standalone)
9
10
  if (typeof globalThis.use === 'undefined') {
10
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
11
+ await ensureUseM();
11
12
  }
12
13
  const use = globalThis.use;
13
14
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  /**
4
5
  * Fork-detection helpers for solve.mjs
@@ -14,7 +15,7 @@
14
15
  */
15
16
 
16
17
  if (typeof globalThis.use === 'undefined') {
17
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
18
+ await ensureUseM();
18
19
  }
19
20
  const use = globalThis.use;
20
21
  const { $ } = await use('command-stream');
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  // Fork upstream-sync module for the solve command.
4
5
  // Extracted from solve.repository.lib.mjs to keep files under 1500 lines (#1893).
@@ -7,7 +8,7 @@
7
8
  // Check if use is already defined globally (when imported from solve.mjs)
8
9
  // If not, fetch it (when running standalone)
9
10
  if (typeof globalThis.use === 'undefined') {
10
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
11
+ await ensureUseM();
11
12
  }
12
13
  const use = globalThis.use;
13
14
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  /**
4
5
  * Keep-working-until-done module for solve.mjs
@@ -25,7 +26,7 @@
25
26
  // Check if use is already defined globally (when imported from solve.mjs)
26
27
  // If not, fetch it (when running standalone)
27
28
  if (typeof globalThis.use === 'undefined') {
28
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
29
+ await ensureUseM();
29
30
  }
30
31
  const use = globalThis.use;
31
32
 
package/src/solve.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  // Import Sentry instrumentation first (must be before other imports)
3
3
  import './instrument.mjs';
4
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
4
5
  const earlyArgs = process.argv.slice(2);
5
6
  const { handleSolveEarlyExit } = await import('./solve.bootstrap.lib.mjs');
6
7
  await handleSolveEarlyExit(earlyArgs);
7
8
 
8
- const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
9
- globalThis.use = use;
9
+ const use = (globalThis.use = await ensureUseM());
10
10
  const { $: __rawDollar$ } = await use('command-stream');
11
11
  const { configureGitHubRateLimitLogging, wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
12
12
  const $ = wrapDollarWithGhRetry(__rawDollar$);
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  // Repository management module for solve command
4
5
  // Extracted from solve.mjs to keep files under 1500 lines
@@ -7,7 +8,7 @@
7
8
  // Check if use is already defined globally (when imported from solve.mjs)
8
9
  // If not, fetch it (when running standalone)
9
10
  if (typeof globalThis.use === 'undefined') {
10
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
11
+ await ensureUseM();
11
12
  }
12
13
  const use = globalThis.use;
13
14
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  /**
4
5
  * Shared utilities for watch mode and auto-restart-until-mergeable mode
@@ -15,7 +16,7 @@
15
16
  // Check if use is already defined globally (when imported from solve.mjs)
16
17
  // If not, fetch it (when running standalone)
17
18
  if (typeof globalThis.use === 'undefined') {
18
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
19
+ await ensureUseM();
19
20
  }
20
21
  const use = globalThis.use;
21
22
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  // Results processing module for solve command
4
5
  // Extracted from solve.mjs to keep files under 1500 lines
@@ -7,7 +8,7 @@
7
8
  // Check if use is already defined globally (when imported from solve.mjs)
8
9
  // If not, fetch it (when running standalone)
9
10
  if (typeof globalThis.use === 'undefined') {
10
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
11
+ await ensureUseM();
11
12
  }
12
13
  const use = globalThis.use;
13
14
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  // Validation module for solve command
4
5
  // Extracted from solve.mjs to keep files under 1500 lines
@@ -7,7 +8,7 @@
7
8
  // Check if use is already defined globally (when imported from solve.mjs)
8
9
  // If not, fetch it (when running standalone)
9
10
  if (typeof globalThis.use === 'undefined') {
10
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
11
+ await ensureUseM();
11
12
  }
12
13
  const use = globalThis.use;
13
14
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
2
3
 
3
4
  /**
4
5
  * Watch mode module for solve.mjs
@@ -10,7 +11,7 @@
10
11
  // Check if use is already defined globally (when imported from solve.mjs)
11
12
  // If not, fetch it (when running standalone)
12
13
  if (typeof globalThis.use === 'undefined') {
13
- globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
14
+ await ensureUseM();
14
15
  }
15
16
  const use = globalThis.use;
16
17