@link-assistant/hive-mind 0.46.0 → 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 +26 -13
  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
@@ -42,7 +42,7 @@ const checkPRMerged = async (owner, repo, prNumber) => {
42
42
  owner,
43
43
  repo,
44
44
  prNumber,
45
- operation: 'check_merge_status'
45
+ operation: 'check_merge_status',
46
46
  });
47
47
  // If we can't check, assume not merged
48
48
  return false;
@@ -64,7 +64,7 @@ const checkForUncommittedChanges = async (tempDir, $) => {
64
64
  reportError(error, {
65
65
  context: 'check_pr_closed',
66
66
  tempDir,
67
- operation: 'check_close_status'
67
+ operation: 'check_close_status',
68
68
  });
69
69
  // If we can't check, assume no uncommitted changes
70
70
  }
@@ -74,18 +74,8 @@ const checkForUncommittedChanges = async (tempDir, $) => {
74
74
  /**
75
75
  * Monitor for feedback in a loop and trigger restart when detected
76
76
  */
77
- export const watchForFeedback = async (params) => {
78
- const {
79
- issueUrl,
80
- owner,
81
- repo,
82
- issueNumber,
83
- prNumber,
84
- prBranch,
85
- branchName,
86
- tempDir,
87
- argv
88
- } = params;
77
+ export const watchForFeedback = async params => {
78
+ const { issueUrl, owner, repo, issueNumber, prNumber, prBranch, branchName, tempDir, argv } = params;
89
79
 
90
80
  const watchInterval = argv.watchInterval || 60; // seconds
91
81
  const isTemporaryWatch = argv.temporaryWatch || false;
@@ -186,7 +176,7 @@ export const watchForFeedback = async (params) => {
186
176
  log,
187
177
  formatAligned,
188
178
  cleanErrorMessage,
189
- $
179
+ $,
190
180
  });
191
181
 
192
182
  // Check if there's any feedback or if it's the first iteration in temporary mode
@@ -219,7 +209,7 @@ export const watchForFeedback = async (params) => {
219
209
  owner,
220
210
  repo,
221
211
  branchName,
222
- operation: 'check_file_in_branch'
212
+ operation: 'check_file_in_branch',
223
213
  });
224
214
  // Ignore errors
225
215
  }
@@ -258,7 +248,7 @@ export const watchForFeedback = async (params) => {
258
248
  owner,
259
249
  repo,
260
250
  prNumber,
261
- operation: 'comment_on_pr'
251
+ operation: 'comment_on_pr',
262
252
  });
263
253
  // Don't fail if comment posting fails
264
254
  await log(formatAligned('', 'āš ļø Could not post comment to PR', '', 2));
@@ -294,7 +284,7 @@ export const watchForFeedback = async (params) => {
294
284
  owner,
295
285
  repo,
296
286
  branchName,
297
- operation: 'verify_file_in_branch'
287
+ operation: 'verify_file_in_branch',
298
288
  });
299
289
  // Ignore errors
300
290
  }
@@ -338,7 +328,7 @@ export const watchForFeedback = async (params) => {
338
328
  formatAligned,
339
329
  getResourceSnapshot,
340
330
  opencodePath,
341
- $
331
+ $,
342
332
  });
343
333
  } else if (argv.tool === 'codex') {
344
334
  // Use Codex
@@ -369,7 +359,7 @@ export const watchForFeedback = async (params) => {
369
359
  formatAligned,
370
360
  getResourceSnapshot,
371
361
  codexPath,
372
- $
362
+ $,
373
363
  });
374
364
  } else if (argv.tool === 'agent') {
375
365
  // Use Agent
@@ -398,7 +388,7 @@ export const watchForFeedback = async (params) => {
398
388
  formatAligned,
399
389
  getResourceSnapshot,
400
390
  agentPath,
401
- $
391
+ $,
402
392
  });
403
393
  } else {
404
394
  // Use Claude (default)
@@ -415,7 +405,9 @@ export const watchForFeedback = async (params) => {
415
405
  await log('šŸŽ­ Playwright MCP detected - enabling browser automation hints', { verbose: true });
416
406
  argv.promptPlaywrightMcp = true;
417
407
  } else {
418
- await log('ā„¹ļø Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
408
+ await log('ā„¹ļø Playwright MCP not detected - browser automation hints will be disabled', {
409
+ verbose: true,
410
+ });
419
411
  argv.promptPlaywrightMcp = false;
420
412
  }
421
413
  }
@@ -438,17 +430,13 @@ export const watchForFeedback = async (params) => {
438
430
  formatAligned,
439
431
  getResourceSnapshot,
440
432
  claudePath,
441
- $
433
+ $,
442
434
  });
443
435
  }
444
436
 
445
437
  if (!toolResult.success) {
446
438
  // Check if this is an API error (404, 401, 400, etc.) from the result
447
- const isApiError = toolResult.result &&
448
- (toolResult.result.includes('API Error:') ||
449
- toolResult.result.includes('not_found_error') ||
450
- toolResult.result.includes('authentication_error') ||
451
- toolResult.result.includes('invalid_request_error'));
439
+ const isApiError = toolResult.result && (toolResult.result.includes('API Error:') || toolResult.result.includes('not_found_error') || toolResult.result.includes('authentication_error') || toolResult.result.includes('invalid_request_error'));
452
440
 
453
441
  if (isApiError) {
454
442
  consecutiveApiErrors++;
@@ -512,14 +500,13 @@ export const watchForFeedback = async (params) => {
512
500
  } else {
513
501
  await log(formatAligned('', 'No feedback detected', 'Continuing to watch...', 2));
514
502
  }
515
-
516
503
  } catch (error) {
517
504
  reportError(error, {
518
505
  context: 'watch_pr_general',
519
506
  prNumber,
520
507
  owner,
521
508
  repo,
522
- operation: 'watch_pull_request'
509
+ operation: 'watch_pull_request',
523
510
  });
524
511
  await log(formatAligned('āš ļø', 'Check failed:', cleanErrorMessage(error), 2));
525
512
  if (!isTemporaryWatch) {
@@ -545,14 +532,14 @@ export const watchForFeedback = async (params) => {
545
532
  // Return latest session data for accurate pricing in log uploads
546
533
  return {
547
534
  latestSessionId,
548
- latestAnthropicCost
535
+ latestAnthropicCost,
549
536
  };
550
537
  };
551
538
 
552
539
  /**
553
540
  * Start watch mode after initial execution
554
541
  */
555
- export const startWatchMode = async (params) => {
542
+ export const startWatchMode = async params => {
556
543
  const { argv } = params;
557
544
 
558
545
  if (argv.verbose) {
@@ -581,4 +568,4 @@ export const startWatchMode = async (params) => {
581
568
 
582
569
  // Start the watch loop and return session data
583
570
  return await watchForFeedback(params);
584
- };
571
+ };
@@ -88,7 +88,7 @@ async function waitForSessionReady(sessionName, maxWaitSeconds = 5) {
88
88
  if (code === 0) {
89
89
  // Marker file exists, session is ready!
90
90
  // Clean up the marker file
91
- await execAsync(`rm -f ${markerFile}`).catch(() => { });
91
+ await execAsync(`rm -f ${markerFile}`).catch(() => {});
92
92
  return true;
93
93
  }
94
94
  } catch {
@@ -101,7 +101,7 @@ async function waitForSessionReady(sessionName, maxWaitSeconds = 5) {
101
101
 
102
102
  // Marker file didn't appear, session is still busy
103
103
  // Clean up any leftover marker file from the queued command
104
- await execAsync(`rm -f ${markerFile}`).catch(() => { });
104
+ await execAsync(`rm -f ${markerFile}`).catch(() => {});
105
105
  } catch {
106
106
  // Error sending test command or checking marker
107
107
  }
@@ -141,16 +141,16 @@ async function createOrEnterScreen(sessionName, command, args, autoTerminate = f
141
141
  console.log('Sending command to existing session...');
142
142
 
143
143
  // Build the full command to send to the existing session
144
- const quotedArgs = args.map(arg => {
145
- // If arg contains spaces or special chars, wrap in single quotes
146
- if (arg.includes(' ') || arg.includes('&') || arg.includes('|') ||
147
- arg.includes(';') || arg.includes('$') || arg.includes('*') ||
148
- arg.includes('?') || arg.includes('(') || arg.includes(')')) {
149
- // Escape single quotes within the argument
150
- return `'${arg.replace(/'/g, "'\\''")}'`;
151
- }
152
- return arg;
153
- }).join(' ');
144
+ const quotedArgs = args
145
+ .map(arg => {
146
+ // If arg contains spaces or special chars, wrap in single quotes
147
+ if (arg.includes(' ') || arg.includes('&') || arg.includes('|') || arg.includes(';') || arg.includes('$') || arg.includes('*') || arg.includes('?') || arg.includes('(') || arg.includes(')')) {
148
+ // Escape single quotes within the argument
149
+ return `'${arg.replace(/'/g, "'\\''")}'`;
150
+ }
151
+ return arg;
152
+ })
153
+ .join(' ');
154
154
 
155
155
  const fullCommand = `${command} ${quotedArgs}`;
156
156
 
@@ -176,16 +176,16 @@ async function createOrEnterScreen(sessionName, command, args, autoTerminate = f
176
176
 
177
177
  // Create a detached session with the command
178
178
  // Quote arguments properly to preserve spaces and special characters
179
- const quotedArgs = args.map(arg => {
180
- // If arg contains spaces or special chars, wrap in single quotes
181
- if (arg.includes(' ') || arg.includes('&') || arg.includes('|') ||
182
- arg.includes(';') || arg.includes('$') || arg.includes('*') ||
183
- arg.includes('?') || arg.includes('(') || arg.includes(')')) {
184
- // Escape single quotes within the argument
185
- return `'${arg.replace(/'/g, "'\\''")}'`;
186
- }
187
- return arg;
188
- }).join(' ');
179
+ const quotedArgs = args
180
+ .map(arg => {
181
+ // If arg contains spaces or special chars, wrap in single quotes
182
+ if (arg.includes(' ') || arg.includes('&') || arg.includes('|') || arg.includes(';') || arg.includes('$') || arg.includes('*') || arg.includes('?') || arg.includes('(') || arg.includes(')')) {
183
+ // Escape single quotes within the argument
184
+ return `'${arg.replace(/'/g, "'\\''")}'`;
185
+ }
186
+ return arg;
187
+ })
188
+ .join(' ');
189
189
 
190
190
  let screenCommand;
191
191
  if (autoTerminate) {
@@ -318,7 +318,7 @@ async function main() {
318
318
  }
319
319
 
320
320
  // Run the main function
321
- main().catch((error) => {
321
+ main().catch(error => {
322
322
  console.error('Unexpected error:', error);
323
323
  process.exit(1);
324
- });
324
+ });
package/src/task.mjs CHANGED
@@ -48,18 +48,18 @@ let logFile = null;
48
48
  // Helper function to log to both console and file
49
49
  const log = async (message, options = {}) => {
50
50
  const { level = 'info', verbose = false } = options;
51
-
51
+
52
52
  // Skip verbose logs unless --verbose is enabled
53
53
  if (verbose && !global.verboseMode) {
54
54
  return;
55
55
  }
56
-
56
+
57
57
  // Write to file if log file is set
58
58
  if (logFile) {
59
59
  const logMessage = `[${new Date().toISOString()}] [${level.toUpperCase()}] ${message}`;
60
60
  await fs.appendFile(logFile, logMessage + '\n').catch(() => {});
61
61
  }
62
-
62
+
63
63
  // Write to console based on level
64
64
  switch (level) {
65
65
  case 'error':
@@ -82,72 +82,72 @@ const argv = yargs()
82
82
  .usage('Usage: $0 <task-description> [options]')
83
83
  .positional('task-description', {
84
84
  type: 'string',
85
- description: 'The task to clarify and decompose'
85
+ description: 'The task to clarify and decompose',
86
86
  })
87
87
  .option('clarify', {
88
88
  type: 'boolean',
89
89
  description: 'Enable clarification mode (asks clarifying questions about the task)',
90
- default: true
90
+ default: true,
91
91
  })
92
92
  .option('decompose', {
93
93
  type: 'boolean',
94
94
  description: 'Enable decomposition mode (breaks down the task into subtasks)',
95
- default: true
95
+ default: true,
96
96
  })
97
97
  .option('only-clarify', {
98
98
  type: 'boolean',
99
99
  description: 'Only run clarification mode, skip decomposition',
100
- default: false
100
+ default: false,
101
101
  })
102
102
  .option('only-decompose', {
103
103
  type: 'boolean',
104
- description: 'Only run decomposition mode, skip clarification',
105
- default: false
104
+ description: 'Only run decomposition mode, skip clarification',
105
+ default: false,
106
106
  })
107
107
  .option('model', {
108
108
  type: 'string',
109
109
  description: 'Model to use (opus, sonnet, or full model ID like claude-sonnet-4-5-20250929)',
110
110
  alias: 'm',
111
111
  default: 'sonnet',
112
- choices: ['opus', 'sonnet', 'claude-sonnet-4-5-20250929', 'claude-opus-4-5-20251101']
112
+ choices: ['opus', 'sonnet', 'claude-sonnet-4-5-20250929', 'claude-opus-4-5-20251101'],
113
113
  })
114
114
  .option('verbose', {
115
115
  type: 'boolean',
116
116
  description: 'Enable verbose logging for debugging',
117
117
  alias: 'v',
118
- default: false
118
+ default: false,
119
119
  })
120
120
  .option('output-format', {
121
121
  type: 'string',
122
122
  description: 'Output format (text or json)',
123
123
  alias: 'o',
124
124
  default: 'text',
125
- choices: ['text', 'json']
125
+ choices: ['text', 'json'],
126
126
  })
127
- .check((argv) => {
127
+ .check(argv => {
128
128
  if (!argv['task-description'] && !argv._[0]) {
129
129
  throw new Error('Please provide a task description');
130
130
  }
131
-
131
+
132
132
  // Handle mutual exclusivity of only-clarify and only-decompose
133
133
  if (argv['only-clarify'] && argv['only-decompose']) {
134
134
  throw new Error('Cannot use both --only-clarify and --only-decompose at the same time');
135
135
  }
136
-
136
+
137
137
  // If only-clarify is set, disable decompose
138
138
  if (argv['only-clarify']) {
139
139
  argv.decompose = false;
140
140
  }
141
-
141
+
142
142
  // If only-decompose is set, disable clarify
143
143
  if (argv['only-decompose']) {
144
144
  argv.clarify = false;
145
145
  }
146
-
146
+
147
147
  return true;
148
148
  })
149
149
  .parserConfiguration({
150
- 'boolean-negation': true
150
+ 'boolean-negation': true,
151
151
  })
152
152
  .help()
153
153
  .alias('h', 'help')
@@ -194,38 +194,33 @@ const executeClaude = (prompt, model) => {
194
194
  // Map model alias to full ID
195
195
  const mappedModel = mapModelToId(model);
196
196
 
197
- const args = [
198
- '-p', prompt,
199
- '--output-format', 'text',
200
- '--dangerously-skip-permissions',
201
- '--model', mappedModel
202
- ];
203
-
197
+ const args = ['-p', prompt, '--output-format', 'text', '--dangerously-skip-permissions', '--model', mappedModel];
198
+
204
199
  const child = spawn(claudePath, args, {
205
200
  stdio: ['ignore', 'pipe', 'pipe'],
206
- env: process.env
201
+ env: process.env,
207
202
  });
208
-
203
+
209
204
  let stdout = '';
210
205
  let stderr = '';
211
-
212
- child.stdout.on('data', (data) => {
206
+
207
+ child.stdout.on('data', data => {
213
208
  stdout += data.toString();
214
209
  });
215
-
216
- child.stderr.on('data', (data) => {
210
+
211
+ child.stderr.on('data', data => {
217
212
  stderr += data.toString();
218
213
  });
219
-
220
- child.on('close', (code) => {
214
+
215
+ child.on('close', code => {
221
216
  if (code === 0) {
222
217
  resolve(stdout.trim());
223
218
  } else {
224
219
  reject(new Error(`Claude exited with code ${code}: ${stderr}`));
225
220
  }
226
221
  });
227
-
228
- child.on('error', (error) => {
222
+
223
+ child.on('error', error => {
229
224
  reject(error);
230
225
  });
231
226
  });
@@ -236,14 +231,14 @@ try {
236
231
  task: taskDescription,
237
232
  timestamp: new Date().toISOString(),
238
233
  clarification: null,
239
- decomposition: null
234
+ decomposition: null,
240
235
  };
241
236
 
242
237
  // Phase 1: Clarification
243
238
  if (argv.clarify) {
244
239
  await log('\nšŸ¤” Phase 1: Task Clarification');
245
240
  await log(' Analyzing task and generating clarifying questions...');
246
-
241
+
247
242
  const clarifyPrompt = `Task: "${taskDescription}"
248
243
 
249
244
  Please help clarify this task by:
@@ -259,7 +254,7 @@ Provide your response in a clear, structured format that helps refine the task u
259
254
  console.log('\nšŸ“ Clarification Results:');
260
255
  console.log(clarificationOutput);
261
256
  }
262
-
257
+
263
258
  results.clarification = clarificationOutput;
264
259
  await log('\nāœ… Clarification phase completed');
265
260
  }
@@ -268,13 +263,13 @@ Provide your response in a clear, structured format that helps refine the task u
268
263
  if (argv.decompose) {
269
264
  await log('\nšŸ” Phase 2: Task Decomposition');
270
265
  await log(' Breaking down task into manageable subtasks...');
271
-
266
+
272
267
  let decomposePrompt = `Task: "${taskDescription}"`;
273
-
268
+
274
269
  if (results.clarification) {
275
270
  decomposePrompt += `\n\nClarification analysis:\n${results.clarification}`;
276
271
  }
277
-
272
+
278
273
  decomposePrompt += `\n\nPlease decompose this task by:
279
274
  1. Breaking it down into 3-8 specific, actionable subtasks
280
275
  2. Ordering the subtasks logically (dependencies and sequence)
@@ -289,7 +284,7 @@ Provide your response as a structured breakdown that someone could use as a impl
289
284
  console.log('\nšŸ” Decomposition Results:');
290
285
  console.log(decompositionOutput);
291
286
  }
292
-
287
+
293
288
  results.decomposition = decompositionOutput;
294
289
  await log('\nāœ… Decomposition phase completed');
295
290
  }
@@ -301,8 +296,7 @@ Provide your response as a structured breakdown that someone could use as a impl
301
296
 
302
297
  await log('\nšŸŽ‰ Task processing completed successfully');
303
298
  await log(`šŸ’” Review the session log for details: ${logFile}`);
304
-
305
299
  } catch (error) {
306
300
  await log(`āŒ Error processing task: ${error.message}`, { level: 'error' });
307
301
  process.exit(1);
308
- }
302
+ }