@link-assistant/hive-mind 0.46.1 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +10 -15
  2. package/README.md +42 -8
  3. package/package.json +16 -3
  4. package/src/agent.lib.mjs +49 -70
  5. package/src/agent.prompts.lib.mjs +6 -20
  6. package/src/buildUserMention.lib.mjs +4 -17
  7. package/src/claude-limits.lib.mjs +15 -15
  8. package/src/claude.lib.mjs +617 -626
  9. package/src/claude.prompts.lib.mjs +7 -22
  10. package/src/codex.lib.mjs +39 -71
  11. package/src/codex.prompts.lib.mjs +6 -20
  12. package/src/config.lib.mjs +3 -16
  13. package/src/contributing-guidelines.lib.mjs +5 -18
  14. package/src/exit-handler.lib.mjs +4 -4
  15. package/src/git.lib.mjs +7 -7
  16. package/src/github-issue-creator.lib.mjs +17 -17
  17. package/src/github-linking.lib.mjs +8 -33
  18. package/src/github.batch.lib.mjs +20 -16
  19. package/src/github.graphql.lib.mjs +18 -18
  20. package/src/github.lib.mjs +89 -91
  21. package/src/hive.config.lib.mjs +50 -50
  22. package/src/hive.mjs +1293 -1296
  23. package/src/instrument.mjs +7 -11
  24. package/src/interactive-mode.lib.mjs +112 -138
  25. package/src/lenv-reader.lib.mjs +1 -6
  26. package/src/lib.mjs +36 -45
  27. package/src/lino.lib.mjs +2 -2
  28. package/src/local-ci-checks.lib.mjs +15 -14
  29. package/src/memory-check.mjs +52 -60
  30. package/src/model-mapping.lib.mjs +25 -32
  31. package/src/model-validation.lib.mjs +31 -31
  32. package/src/opencode.lib.mjs +37 -62
  33. package/src/opencode.prompts.lib.mjs +7 -21
  34. package/src/protect-branch.mjs +14 -15
  35. package/src/review.mjs +28 -27
  36. package/src/reviewers-hive.mjs +64 -69
  37. package/src/sentry.lib.mjs +13 -10
  38. package/src/solve.auto-continue.lib.mjs +48 -38
  39. package/src/solve.auto-pr.lib.mjs +111 -69
  40. package/src/solve.branch-errors.lib.mjs +17 -46
  41. package/src/solve.branch.lib.mjs +16 -23
  42. package/src/solve.config.lib.mjs +263 -261
  43. package/src/solve.error-handlers.lib.mjs +21 -79
  44. package/src/solve.execution.lib.mjs +10 -18
  45. package/src/solve.feedback.lib.mjs +25 -46
  46. package/src/solve.mjs +59 -60
  47. package/src/solve.preparation.lib.mjs +10 -36
  48. package/src/solve.repo-setup.lib.mjs +4 -19
  49. package/src/solve.repository.lib.mjs +37 -37
  50. package/src/solve.results.lib.mjs +32 -46
  51. package/src/solve.session.lib.mjs +7 -22
  52. package/src/solve.validation.lib.mjs +19 -17
  53. package/src/solve.watch.lib.mjs +20 -33
  54. package/src/start-screen.mjs +24 -24
  55. package/src/task.mjs +38 -44
  56. package/src/telegram-bot.mjs +125 -121
  57. package/src/telegram-top-command.lib.mjs +32 -48
  58. package/src/usage-limit.lib.mjs +9 -13
  59. package/src/version-info.lib.mjs +1 -1
  60. package/src/version.lib.mjs +1 -1
  61. package/src/youtrack/solve.youtrack.lib.mjs +3 -8
  62. package/src/youtrack/youtrack-sync.mjs +8 -14
  63. package/src/youtrack/youtrack.lib.mjs +26 -28
@@ -46,19 +46,15 @@ if (!shouldDisableSentry()) {
46
46
  const { sentry, version } = await import('./config.lib.mjs');
47
47
 
48
48
  // Dynamically import Sentry packages only when needed
49
- // eslint-disable-next-line quotes
50
- const sentryModule = await import("@sentry/node");
49
+ const sentryModule = await import('@sentry/node');
51
50
  Sentry = sentryModule;
52
- // eslint-disable-next-line quotes
53
- const profilingModule = await import("@sentry/profiling-node");
51
+ const profilingModule = await import('@sentry/profiling-node');
54
52
  nodeProfilingIntegration = profilingModule.nodeProfilingIntegration;
55
53
 
56
54
  // Initialize Sentry with configuration
57
55
  Sentry.init({
58
56
  dsn: sentry.dsn,
59
- integrations: [
60
- nodeProfilingIntegration(),
61
- ],
57
+ integrations: [nodeProfilingIntegration()],
62
58
 
63
59
  // Application name
64
60
  environment: process.env.NODE_ENV || 'production',
@@ -125,7 +121,7 @@ if (!shouldDisableSentry()) {
125
121
  }
126
122
  context.name = `hive-mind.${context.name || 'unknown'}`;
127
123
  return context;
128
- }
124
+ },
129
125
  });
130
126
 
131
127
  // Log that Sentry has been initialized
@@ -157,7 +153,7 @@ export const isSentryEnabled = () => Sentry !== null && Sentry.getClient() !== u
157
153
  export const captureException = (error, context = {}) => {
158
154
  if (isSentryEnabled()) {
159
155
  Sentry.captureException(error, {
160
- extra: context
156
+ extra: context,
161
157
  });
162
158
  }
163
159
  };
@@ -166,7 +162,7 @@ export const captureException = (error, context = {}) => {
166
162
  export const captureMessage = (message, level = 'info', context = {}) => {
167
163
  if (isSentryEnabled()) {
168
164
  Sentry.captureMessage(message, level, {
169
- extra: context
165
+ extra: context,
170
166
  });
171
167
  }
172
168
  };
@@ -188,4 +184,4 @@ export const startTransaction = (name, op = 'task') => {
188
184
  setStatus: () => {},
189
185
  setData: () => {},
190
186
  };
191
- };
187
+ };
@@ -39,7 +39,7 @@ const CONFIG = {
39
39
  // Lines to keep at end when truncating
40
40
  LINES_TO_KEEP_END: 20,
41
41
  // Maximum JSON depth for raw JSON display
42
- MAX_JSON_DEPTH: 10
42
+ MAX_JSON_DEPTH: 10,
43
43
  };
44
44
 
45
45
  /**
@@ -54,11 +54,7 @@ const CONFIG = {
54
54
  * @returns {string} Truncated content with ellipsis indicator
55
55
  */
56
56
  const truncateMiddle = (content, options = {}) => {
57
- const {
58
- maxLines = CONFIG.MAX_LINES_BEFORE_TRUNCATION,
59
- keepStart = CONFIG.LINES_TO_KEEP_START,
60
- keepEnd = CONFIG.LINES_TO_KEEP_END
61
- } = options;
57
+ const { maxLines = CONFIG.MAX_LINES_BEFORE_TRUNCATION, keepStart = CONFIG.LINES_TO_KEEP_START, keepEnd = CONFIG.LINES_TO_KEEP_END } = options;
62
58
 
63
59
  if (!content || typeof content !== 'string') {
64
60
  return content || '';
@@ -73,13 +69,7 @@ const truncateMiddle = (content, options = {}) => {
73
69
  const endLines = lines.slice(-keepEnd);
74
70
  const removedCount = lines.length - keepStart - keepEnd;
75
71
 
76
- return [
77
- ...startLines,
78
- '',
79
- `... [${removedCount} lines truncated] ...`,
80
- '',
81
- ...endLines
82
- ].join('\n');
72
+ return [...startLines, '', `... [${removedCount} lines truncated] ...`, '', ...endLines].join('\n');
83
73
  };
84
74
 
85
75
  /**
@@ -91,15 +81,19 @@ const truncateMiddle = (content, options = {}) => {
91
81
  */
92
82
  const safeJsonStringify = (obj, indent = 2) => {
93
83
  const seen = new WeakSet();
94
- return JSON.stringify(obj, (key, value) => {
95
- if (typeof value === 'object' && value !== null) {
96
- if (seen.has(value)) {
97
- return '[Circular]';
84
+ return JSON.stringify(
85
+ obj,
86
+ (key, value) => {
87
+ if (typeof value === 'object' && value !== null) {
88
+ if (seen.has(value)) {
89
+ return '[Circular]';
90
+ }
91
+ seen.add(value);
98
92
  }
99
- seen.add(value);
100
- }
101
- return value;
102
- }, indent);
93
+ return value;
94
+ },
95
+ indent
96
+ );
103
97
  };
104
98
 
105
99
  /**
@@ -127,18 +121,15 @@ ${content}
127
121
  * @param {Object|Array} data - JSON data to display (will be wrapped in array if not already)
128
122
  * @returns {string} Collapsible JSON block
129
123
  */
130
- const createRawJsonSection = (data) => {
124
+ const createRawJsonSection = data => {
131
125
  // Ensure data is always an array at root level for easier merging
132
126
  const dataArray = Array.isArray(data) ? data : [data];
133
127
  const jsonContent = truncateMiddle(safeJsonStringify(dataArray, 2), {
134
128
  maxLines: 100,
135
129
  keepStart: 40,
136
- keepEnd: 40
130
+ keepEnd: 40,
137
131
  });
138
- return createCollapsible(
139
- '📄 Raw JSON',
140
- '```json\n' + jsonContent + '\n```'
141
- );
132
+ return createCollapsible('📄 Raw JSON', '```json\n' + jsonContent + '\n```');
142
133
  };
143
134
 
144
135
  /**
@@ -147,7 +138,7 @@ const createRawJsonSection = (data) => {
147
138
  * @param {number} ms - Duration in milliseconds
148
139
  * @returns {string} Formatted duration (e.g., "12m 7s")
149
140
  */
150
- const formatDuration = (ms) => {
141
+ const formatDuration = ms => {
151
142
  if (!ms || ms < 0) return 'unknown';
152
143
 
153
144
  const seconds = Math.floor(ms / 1000);
@@ -169,7 +160,7 @@ const formatDuration = (ms) => {
169
160
  * @param {number} cost - Cost in USD
170
161
  * @returns {string} Formatted cost (e.g., "$1.60")
171
162
  */
172
- const formatCost = (cost) => {
163
+ const formatCost = cost => {
173
164
  if (typeof cost !== 'number' || isNaN(cost)) return 'unknown';
174
165
  return `$${cost.toFixed(2)}`;
175
166
  };
@@ -180,7 +171,7 @@ const formatCost = (cost) => {
180
171
  * @param {string} text - Text to escape
181
172
  * @returns {string} Escaped text
182
173
  */
183
- const escapeMarkdown = (text) => {
174
+ const escapeMarkdown = text => {
184
175
  if (!text || typeof text !== 'string') return '';
185
176
  // Escape backticks that would break code blocks
186
177
  return text.replace(/```/g, '\\`\\`\\`');
@@ -192,20 +183,20 @@ const escapeMarkdown = (text) => {
192
183
  * @param {string} toolName - Name of the tool
193
184
  * @returns {string} Emoji icon
194
185
  */
195
- const getToolIcon = (toolName) => {
186
+ const getToolIcon = toolName => {
196
187
  const icons = {
197
- 'Bash': '💻',
198
- 'Read': '📖',
199
- 'Write': '✏️',
200
- 'Edit': '📝',
201
- 'Glob': '🔍',
202
- 'Grep': '🔎',
203
- 'WebFetch': '🌐',
204
- 'WebSearch': '🔍',
205
- 'TodoWrite': '📋',
206
- 'Task': '🎯',
207
- 'NotebookEdit': '📓',
208
- 'default': '🔧'
188
+ Bash: '💻',
189
+ Read: '📖',
190
+ Write: '✏️',
191
+ Edit: '📝',
192
+ Glob: '🔍',
193
+ Grep: '🔎',
194
+ WebFetch: '🌐',
195
+ WebSearch: '🔍',
196
+ TodoWrite: '📋',
197
+ Task: '🎯',
198
+ NotebookEdit: '📓',
199
+ default: '🔧',
209
200
  };
210
201
  return icons[toolName] || icons.default;
211
202
  };
@@ -222,7 +213,7 @@ const getToolIcon = (toolName) => {
222
213
  * @param {boolean} [options.verbose=false] - Enable verbose logging
223
214
  * @returns {Object} Handler object with event processing methods
224
215
  */
225
- export const createInteractiveHandler = (options) => {
216
+ export const createInteractiveHandler = options => {
226
217
  const { owner, repo, prNumber, $, log, verbose = false } = options;
227
218
 
228
219
  // State tracking for the handler
@@ -243,7 +234,7 @@ export const createInteractiveHandler = (options) => {
243
234
  pendingToolCalls: new Map(),
244
235
  // Simple map of tool_use_id -> { toolName, toolIcon } for standalone tool results
245
236
  // This is preserved even after pendingToolCalls entry is deleted
246
- toolUseRegistry: new Map()
237
+ toolUseRegistry: new Map(),
247
238
  };
248
239
 
249
240
  /**
@@ -364,7 +355,9 @@ export const createInteractiveHandler = (options) => {
364
355
  pendingCall.resolveCommentId(commentId);
365
356
  }
366
357
  if (verbose) {
367
- await log(`📋 Interactive mode: Updated pending tool call ${toolId} with comment ID ${commentId}`, { verbose: true });
358
+ await log(`📋 Interactive mode: Updated pending tool call ${toolId} with comment ID ${commentId}`, {
359
+ verbose: true,
360
+ });
368
361
  }
369
362
  }
370
363
  }
@@ -378,32 +371,24 @@ export const createInteractiveHandler = (options) => {
378
371
  * Handle system.init event
379
372
  * @param {Object} data - Event data
380
373
  */
381
- const handleSystemInit = async (data) => {
374
+ const handleSystemInit = async data => {
382
375
  state.sessionId = data.session_id;
383
376
  state.startTime = Date.now();
384
377
 
385
378
  const tools = data.tools || [];
386
- const toolsList = tools.length > 0
387
- ? tools.map(t => `\`${t}\``).join(', ')
388
- : '_No tools available_';
379
+ const toolsList = tools.length > 0 ? tools.map(t => `\`${t}\``).join(', ') : '_No tools available_';
389
380
 
390
381
  // Format MCP servers
391
382
  const mcpServers = data.mcp_servers || [];
392
- const mcpServersList = mcpServers.length > 0
393
- ? mcpServers.map(s => `\`${s.name}\` (${s.status || 'unknown'})`).join(', ')
394
- : '_None_';
383
+ const mcpServersList = mcpServers.length > 0 ? mcpServers.map(s => `\`${s.name}\` (${s.status || 'unknown'})`).join(', ') : '_None_';
395
384
 
396
385
  // Format slash commands
397
386
  const slashCommands = data.slash_commands || [];
398
- const slashCommandsList = slashCommands.length > 0
399
- ? slashCommands.map(c => `\`/${c}\``).join(', ')
400
- : '_None_';
387
+ const slashCommandsList = slashCommands.length > 0 ? slashCommands.map(c => `\`/${c}\``).join(', ') : '_None_';
401
388
 
402
389
  // Format agents
403
390
  const agents = data.agents || [];
404
- const agentsList = agents.length > 0
405
- ? agents.map(a => `\`${a}\``).join(', ')
406
- : '_None_';
391
+ const agentsList = agents.length > 0 ? agents.map(a => `\`${a}\``).join(', ') : '_None_';
407
392
 
408
393
  const comment = `## 🚀 Interactive session started
409
394
 
@@ -442,7 +427,7 @@ ${createRawJsonSection(data)}`;
442
427
  const displayText = truncateMiddle(text, {
443
428
  maxLines: 80,
444
429
  keepStart: 35,
445
- keepEnd: 35
430
+ keepEnd: 35,
446
431
  });
447
432
 
448
433
  // Simple format: just the message and collapsed Raw JSON
@@ -482,13 +467,9 @@ ${createRawJsonSection(data)}`;
482
467
  const truncatedCommand = truncateMiddle(input.command, {
483
468
  maxLines: 30,
484
469
  keepStart: 12,
485
- keepEnd: 12
470
+ keepEnd: 12,
486
471
  });
487
- inputDisplay = createCollapsible(
488
- '📋 Executed command',
489
- '```bash\n' + escapeMarkdown(truncatedCommand) + '\n```',
490
- true
491
- );
472
+ inputDisplay = createCollapsible('📋 Executed command', '```bash\n' + escapeMarkdown(truncatedCommand) + '\n```', true);
492
473
  } else if (toolName === 'Read' && input.file_path) {
493
474
  inputDisplay = `**File:** \`${input.file_path}\``;
494
475
  if (input.offset || input.limit) {
@@ -500,14 +481,14 @@ ${createRawJsonSection(data)}`;
500
481
  const truncatedContent = truncateMiddle(input.content, {
501
482
  maxLines: 30,
502
483
  keepStart: 12,
503
- keepEnd: 12
484
+ keepEnd: 12,
504
485
  });
505
486
  // Format content as diff with + prefix for added lines
506
- const diffContent = truncatedContent.split('\n').map(line => `+ ${line}`).join('\n');
507
- inputDisplay += '\n\n' + createCollapsible(
508
- '📄 Content',
509
- '```diff\n' + escapeMarkdown(diffContent) + '\n```'
510
- );
487
+ const diffContent = truncatedContent
488
+ .split('\n')
489
+ .map(line => `+ ${line}`)
490
+ .join('\n');
491
+ inputDisplay += '\n\n' + createCollapsible('📄 Content', '```diff\n' + escapeMarkdown(diffContent) + '\n```');
511
492
  }
512
493
  } else if (toolName === 'Edit' && input.file_path) {
513
494
  inputDisplay = `**File:** \`${input.file_path}\``;
@@ -515,13 +496,15 @@ ${createRawJsonSection(data)}`;
515
496
  const truncatedOld = truncateMiddle(input.old_string, { maxLines: 15, keepStart: 6, keepEnd: 6 });
516
497
  const truncatedNew = truncateMiddle(input.new_string, { maxLines: 15, keepStart: 6, keepEnd: 6 });
517
498
  // Format as unified diff with - for removed lines and + for added lines
518
- const diffOld = truncatedOld.split('\n').map(line => `- ${line}`).join('\n');
519
- const diffNew = truncatedNew.split('\n').map(line => `+ ${line}`).join('\n');
520
- inputDisplay += '\n\n' + createCollapsible(
521
- '🔄 Change',
522
- '```diff\n' + escapeMarkdown(diffOld + '\n' + diffNew) + '\n```',
523
- true
524
- );
499
+ const diffOld = truncatedOld
500
+ .split('\n')
501
+ .map(line => `- ${line}`)
502
+ .join('\n');
503
+ const diffNew = truncatedNew
504
+ .split('\n')
505
+ .map(line => `+ ${line}`)
506
+ .join('\n');
507
+ inputDisplay += '\n\n' + createCollapsible('🔄 Change', '```diff\n' + escapeMarkdown(diffOld + '\n' + diffNew) + '\n```', true);
525
508
  }
526
509
  } else if ((toolName === 'Glob' || toolName === 'Grep') && input.pattern) {
527
510
  inputDisplay = `**Pattern:** \`${input.pattern}\``;
@@ -549,18 +532,10 @@ ${createRawJsonSection(data)}`;
549
532
  const startTodos = todos.slice(0, KEEP_START).map(t => `- [${t.status === 'completed' ? 'x' : ' '}] ${t.content}`);
550
533
  const endTodos = todos.slice(-KEEP_END).map(t => `- [${t.status === 'completed' ? 'x' : ' '}] ${t.content}`);
551
534
 
552
- todosPreview = [
553
- ...startTodos,
554
- `- _...and ${skipped} more_`,
555
- ...endTodos
556
- ].join('\n');
535
+ todosPreview = [...startTodos, `- _...and ${skipped} more_`, ...endTodos].join('\n');
557
536
  }
558
537
 
559
- inputDisplay = createCollapsible(
560
- `📋 Todos (${todos.length} items)`,
561
- todosPreview,
562
- true
563
- );
538
+ inputDisplay = createCollapsible(`📋 Todos (${todos.length} items)`, todosPreview, true);
564
539
  } else if (toolName === 'Task') {
565
540
  inputDisplay = `**Description:** ${input.description || 'N/A'}`;
566
541
  if (input.prompt) {
@@ -572,12 +547,9 @@ ${createRawJsonSection(data)}`;
572
547
  const inputJson = truncateMiddle(safeJsonStringify(input, 2), {
573
548
  maxLines: 30,
574
549
  keepStart: 12,
575
- keepEnd: 12
550
+ keepEnd: 12,
576
551
  });
577
- inputDisplay = createCollapsible(
578
- '📥 Input',
579
- '```json\n' + inputJson + '\n```'
580
- );
552
+ inputDisplay = createCollapsible('📥 Input', '```json\n' + inputJson + '\n```');
581
553
  }
582
554
 
583
555
  // Post the tool use comment and store info for merging with result later
@@ -594,7 +566,7 @@ ${createRawJsonSection(data)}`;
594
566
  // Create a promise that will resolve with the comment ID
595
567
  // This handles both immediate posting and queued posting
596
568
  let resolveCommentId;
597
- const commentIdPromise = new Promise((resolve) => {
569
+ const commentIdPromise = new Promise(resolve => {
598
570
  resolveCommentId = resolve;
599
571
  });
600
572
 
@@ -607,7 +579,7 @@ ${createRawJsonSection(data)}`;
607
579
  toolData: data,
608
580
  inputDisplay,
609
581
  toolName,
610
- toolIcon
582
+ toolIcon,
611
583
  });
612
584
 
613
585
  // Post the comment, passing toolId for queue tracking
@@ -624,7 +596,9 @@ ${createRawJsonSection(data)}`;
624
596
  // If queued (commentId is null), processQueue will update it later
625
597
 
626
598
  if (verbose) {
627
- await log(`🔧 Interactive mode: Tool use - ${toolName}${commentId ? ` (comment: ${commentId})` : ' (queued)'}`, { verbose: true });
599
+ await log(`🔧 Interactive mode: Tool use - ${toolName}${commentId ? ` (comment: ${commentId})` : ' (queued)'}`, {
600
+ verbose: true,
601
+ });
628
602
  }
629
603
  };
630
604
 
@@ -646,18 +620,20 @@ ${createRawJsonSection(data)}`;
646
620
  if (typeof toolResult.content === 'string') {
647
621
  content = toolResult.content;
648
622
  } else if (Array.isArray(toolResult.content)) {
649
- content = toolResult.content.map(c => {
650
- if (typeof c === 'string') return c;
651
- if (c.type === 'text') return c.text || '';
652
- return safeJsonStringify(c);
653
- }).join('\n');
623
+ content = toolResult.content
624
+ .map(c => {
625
+ if (typeof c === 'string') return c;
626
+ if (c.type === 'text') return c.text || '';
627
+ return safeJsonStringify(c);
628
+ })
629
+ .join('\n');
654
630
  }
655
631
 
656
632
  // Truncate large outputs
657
633
  const truncatedContent = truncateMiddle(content, {
658
634
  maxLines: 60,
659
635
  keepStart: 25,
660
- keepEnd: 25
636
+ keepEnd: 25,
661
637
  });
662
638
 
663
639
  // Check if we have a pending tool call to merge with
@@ -671,15 +647,19 @@ ${createRawJsonSection(data)}`;
671
647
  // But use a timeout to avoid blocking forever
672
648
  if (!commentId && commentIdPromise) {
673
649
  if (verbose) {
674
- await log(`⏳ Interactive mode: Waiting for tool use comment to be posted (tool: ${toolUseId})`, { verbose: true });
650
+ await log(`⏳ Interactive mode: Waiting for tool use comment to be posted (tool: ${toolUseId})`, {
651
+ verbose: true,
652
+ });
675
653
  }
676
654
  // Wait for the comment to be posted (with 30 second timeout)
677
- const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(null), 30000));
655
+ const timeoutPromise = new Promise(resolve => setTimeout(() => resolve(null), 30000));
678
656
  commentId = await Promise.race([commentIdPromise, timeoutPromise]);
679
657
 
680
658
  if (!commentId) {
681
659
  if (verbose) {
682
- await log('⚠️ Interactive mode: Timeout waiting for tool use comment, posting result separately', { verbose: true });
660
+ await log('⚠️ Interactive mode: Timeout waiting for tool use comment, posting result separately', {
661
+ verbose: true,
662
+ });
683
663
  }
684
664
  }
685
665
  }
@@ -690,11 +670,7 @@ ${createRawJsonSection(data)}`;
690
670
 
691
671
  ${inputDisplay}
692
672
 
693
- ${createCollapsible(
694
- `📤 Output (${statusIcon} ${statusText.toLowerCase()})`,
695
- '```\n' + escapeMarkdown(truncatedContent) + '\n```',
696
- true
697
- )}
673
+ ${createCollapsible(`📤 Output (${statusIcon} ${statusText.toLowerCase()})`, '```\n' + escapeMarkdown(truncatedContent) + '\n```', true)}
698
674
 
699
675
  ---
700
676
 
@@ -706,13 +682,17 @@ ${createRawJsonSection([toolData, data])}`;
706
682
  if (editSuccess) {
707
683
  state.pendingToolCalls.delete(toolUseId);
708
684
  if (verbose) {
709
- await log(`📋 Interactive mode: Tool result merged into comment ${commentId} (${content.length} chars)`, { verbose: true });
685
+ await log(`📋 Interactive mode: Tool result merged into comment ${commentId} (${content.length} chars)`, {
686
+ verbose: true,
687
+ });
710
688
  }
711
689
  return;
712
690
  }
713
691
  // If edit failed, fall through to posting new comment
714
692
  if (verbose) {
715
- await log(`⚠️ Interactive mode: Failed to edit comment ${commentId}, posting result separately`, { verbose: true });
693
+ await log(`⚠️ Interactive mode: Failed to edit comment ${commentId}, posting result separately`, {
694
+ verbose: true,
695
+ });
716
696
  }
717
697
  }
718
698
 
@@ -725,17 +705,11 @@ ${createRawJsonSection([toolData, data])}`;
725
705
  const registryEntry = state.toolUseRegistry.get(toolUseId);
726
706
  const standaloneToolName = registryEntry?.toolName;
727
707
  const standaloneToolIcon = registryEntry?.toolIcon || '🔧';
728
- const standaloneHeader = standaloneToolName
729
- ? `${standaloneToolIcon} ${standaloneToolName} tool result`
730
- : 'Tool result';
708
+ const standaloneHeader = standaloneToolName ? `${standaloneToolIcon} ${standaloneToolName} tool result` : 'Tool result';
731
709
 
732
710
  const comment = `## ${standaloneHeader}
733
711
 
734
- ${createCollapsible(
735
- `📤 Output (${statusIcon} ${statusText.toLowerCase()})`,
736
- '```\n' + escapeMarkdown(truncatedContent) + '\n```',
737
- true
738
- )}
712
+ ${createCollapsible(`📤 Output (${statusIcon} ${statusText.toLowerCase()})`, '```\n' + escapeMarkdown(truncatedContent) + '\n```', true)}
739
713
 
740
714
  ---
741
715
 
@@ -745,7 +719,9 @@ ${createRawJsonSection(data)}`;
745
719
 
746
720
  if (verbose) {
747
721
  const contentLength = content.length;
748
- await log(`📋 Interactive mode: Tool result posted as separate comment (${contentLength} chars)`, { verbose: true });
722
+ await log(`📋 Interactive mode: Tool result posted as separate comment (${contentLength} chars)`, {
723
+ verbose: true,
724
+ });
749
725
  }
750
726
  };
751
727
 
@@ -753,7 +729,7 @@ ${createRawJsonSection(data)}`;
753
729
  * Handle result event (session complete)
754
730
  * @param {Object} data - Event data
755
731
  */
756
- const handleResult = async (data) => {
732
+ const handleResult = async data => {
757
733
  const isError = data.is_error || false;
758
734
  const statusIcon = isError ? '❌' : '✅';
759
735
  const statusText = isError ? 'Session Failed' : 'Session Complete';
@@ -763,7 +739,7 @@ ${createRawJsonSection(data)}`;
763
739
  const truncatedResult = truncateMiddle(resultText, {
764
740
  maxLines: 50,
765
741
  keepStart: 20,
766
- keepEnd: 20
742
+ keepEnd: 20,
767
743
  });
768
744
 
769
745
  // Build stats table
@@ -819,7 +795,7 @@ ${createRawJsonSection(data)}`;
819
795
  * Handle unrecognized event types
820
796
  * @param {Object} data - Event data
821
797
  */
822
- const handleUnrecognized = async (data) => {
798
+ const handleUnrecognized = async data => {
823
799
  const eventType = data.type || 'unknown';
824
800
  const subtype = data.subtype ? `.${data.subtype}` : '';
825
801
 
@@ -842,7 +818,7 @@ ${createRawJsonSection(data)}`;
842
818
  * @param {Object} data - Parsed JSON object from Claude CLI output
843
819
  * @returns {Promise<void>}
844
820
  */
845
- const processEvent = async (data) => {
821
+ const processEvent = async data => {
846
822
  if (!data || typeof data !== 'object') {
847
823
  return;
848
824
  }
@@ -865,9 +841,7 @@ ${createRawJsonSection(data)}`;
865
841
 
866
842
  case 'assistant':
867
843
  if (data.message && data.message.content) {
868
- const content = Array.isArray(data.message.content)
869
- ? data.message.content
870
- : [data.message.content];
844
+ const content = Array.isArray(data.message.content) ? data.message.content : [data.message.content];
871
845
 
872
846
  for (const item of content) {
873
847
  if (item.type === 'text' && item.text) {
@@ -881,9 +855,7 @@ ${createRawJsonSection(data)}`;
881
855
 
882
856
  case 'user':
883
857
  if (data.message && data.message.content) {
884
- const content = Array.isArray(data.message.content)
885
- ? data.message.content
886
- : [data.message.content];
858
+ const content = Array.isArray(data.message.content) ? data.message.content : [data.message.content];
887
859
 
888
860
  for (const item of content) {
889
861
  if (item.type === 'tool_result') {
@@ -933,8 +905,8 @@ ${createRawJsonSection(data)}`;
933
905
  handleToolUse,
934
906
  handleToolResult,
935
907
  handleResult,
936
- handleUnrecognized
937
- }
908
+ handleUnrecognized,
909
+ },
938
910
  };
939
911
  };
940
912
 
@@ -944,7 +916,7 @@ ${createRawJsonSection(data)}`;
944
916
  * @param {string} tool - Tool name (claude, opencode, codex)
945
917
  * @returns {boolean} Whether interactive mode is supported
946
918
  */
947
- export const isInteractiveModeSupported = (tool) => {
919
+ export const isInteractiveModeSupported = tool => {
948
920
  // Currently only supported for Claude
949
921
  return tool === 'claude';
950
922
  };
@@ -963,7 +935,9 @@ export const validateInteractiveModeConfig = async (argv, log) => {
963
935
 
964
936
  // Check tool support
965
937
  if (!isInteractiveModeSupported(argv.tool)) {
966
- await log(`⚠️ --interactive-mode is only supported for --tool claude (current: ${argv.tool})`, { level: 'warning' });
938
+ await log(`⚠️ --interactive-mode is only supported for --tool claude (current: ${argv.tool})`, {
939
+ level: 'warning',
940
+ });
967
941
  await log(' Interactive mode will be disabled for this session.', { level: 'warning' });
968
942
  return false;
969
943
  }
@@ -988,7 +962,7 @@ export const utils = {
988
962
  formatCost,
989
963
  escapeMarkdown,
990
964
  getToolIcon,
991
- CONFIG
965
+ CONFIG,
992
966
  };
993
967
 
994
968
  // Export all functions
@@ -996,5 +970,5 @@ export default {
996
970
  createInteractiveHandler,
997
971
  isInteractiveModeSupported,
998
972
  validateInteractiveModeConfig,
999
- utils
973
+ utils,
1000
974
  };
@@ -134,12 +134,7 @@ export class LenvReader {
134
134
  * @returns {Object} - Object with loaded variables
135
135
  */
136
136
  async config(options = {}) {
137
- const {
138
- path: configPath = '.lenv',
139
- configuration = null,
140
- override = false,
141
- quiet = false
142
- } = options;
137
+ const { path: configPath = '.lenv', configuration = null, override = false, quiet = false } = options;
143
138
 
144
139
  let envVars = {};
145
140