@link-assistant/hive-mind 1.59.7 → 1.61.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.
@@ -7,7 +7,7 @@ const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text(
7
7
  const { $: __rawDollar$ } = await use('command-stream');
8
8
  const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
9
9
  const $ = wrapDollarWithGhRetry(__rawDollar$);
10
- const yargs = (await use('yargs@latest')).default;
10
+ const { getLinoYargsFactory, hideBin, parseCliArgumentsWithLino } = await import('./cli-arguments.lib.mjs');
11
11
  const path = (await use('path')).default;
12
12
  const fs = (await use('fs')).promises;
13
13
 
@@ -46,97 +46,113 @@ const log = async (message, options = {}) => {
46
46
  };
47
47
 
48
48
  // Configure command line arguments
49
- const argv = yargs(process.argv.slice(2))
50
- .usage('Usage: $0 <github-url> [options]')
51
- .positional('github-url', {
52
- type: 'string',
53
- description: 'GitHub organization, repository, or user URL to monitor for pull requests',
54
- })
55
- .option('review-label', {
56
- type: 'string',
57
- description: 'GitHub label to identify PRs needing review',
58
- default: 'needs-review',
59
- alias: 'l',
60
- })
61
- .option('all-prs', {
62
- type: 'boolean',
63
- description: 'Review all open pull requests regardless of labels',
64
- default: false,
65
- alias: 'a',
66
- })
67
- .option('skip-draft', {
68
- type: 'boolean',
69
- description: 'Skip draft pull requests',
70
- default: true,
71
- alias: 'd',
72
- })
73
- .option('skip-approved', {
74
- type: 'boolean',
75
- description: 'Skip pull requests that already have approvals',
76
- default: true,
77
- })
78
- .option('concurrency', {
79
- type: 'number',
80
- description: 'Number of concurrent review.mjs instances',
81
- default: 2,
82
- alias: 'c',
83
- })
84
- .option('reviews-per-pr', {
85
- type: 'number',
86
- description: 'Number of reviews to generate per PR (for diverse perspectives)',
87
- default: 1,
88
- alias: 'r',
89
- })
90
- .option('model', {
91
- type: 'string',
92
- description: 'Model to use for review.mjs (opus or sonnet)',
93
- alias: 'm',
94
- default: 'opus',
95
- choices: ['opus', 'sonnet'],
96
- })
97
- .option('focus', {
98
- type: 'string',
99
- description: 'Focus areas for reviews (security, performance, logic, style, tests, all)',
100
- default: 'all',
101
- alias: 'f',
102
- })
103
- .option('auto-approve', {
104
- type: 'boolean',
105
- description: 'Auto-approve PRs that pass review criteria',
106
- default: false,
107
- })
108
- .option('interval', {
109
- type: 'number',
110
- description: 'Polling interval in seconds',
111
- default: 300, // 5 minutes
112
- alias: 'i',
113
- })
114
- .option('max-prs', {
115
- type: 'number',
116
- description: 'Maximum number of PRs to process (0 = unlimited)',
117
- default: 0,
118
- })
119
- .option('dry-run', {
120
- type: 'boolean',
121
- description: 'List PRs that would be reviewed without actually reviewing them',
122
- default: false,
123
- })
124
- .option('verbose', {
125
- type: 'boolean',
126
- description: 'Enable verbose logging',
127
- alias: 'v',
128
- default: false,
129
- })
130
- .option('once', {
131
- type: 'boolean',
132
- description: 'Run once and exit instead of continuous monitoring',
133
- default: false,
134
- })
135
- .demandCommand(1, 'GitHub URL is required')
136
- .help('h')
137
- .alias('h', 'help').argv;
138
-
139
- const githubUrl = argv['github-url'] || argv._[0];
49
+ const createReviewersHiveYargsConfig = yargsInstance =>
50
+ yargsInstance
51
+ .usage('Usage: $0 <github-url> [options]')
52
+ .command('$0 <github-url>', 'Monitor pull requests for review', yargs =>
53
+ yargs.positional('github-url', {
54
+ type: 'string',
55
+ description: 'GitHub organization, repository, or user URL to monitor for pull requests',
56
+ })
57
+ )
58
+ .option('review-label', {
59
+ type: 'string',
60
+ description: 'GitHub label to identify PRs needing review',
61
+ default: 'needs-review',
62
+ alias: 'l',
63
+ })
64
+ .option('all-prs', {
65
+ type: 'boolean',
66
+ description: 'Review all open pull requests regardless of labels',
67
+ default: false,
68
+ alias: 'a',
69
+ })
70
+ .option('skip-draft', {
71
+ type: 'boolean',
72
+ description: 'Skip draft pull requests',
73
+ default: true,
74
+ alias: 'd',
75
+ })
76
+ .option('skip-approved', {
77
+ type: 'boolean',
78
+ description: 'Skip pull requests that already have approvals',
79
+ default: true,
80
+ })
81
+ .option('concurrency', {
82
+ type: 'number',
83
+ description: 'Number of concurrent review.mjs instances',
84
+ default: 2,
85
+ alias: 'c',
86
+ })
87
+ .option('reviews-per-pr', {
88
+ type: 'number',
89
+ description: 'Number of reviews to generate per PR (for diverse perspectives)',
90
+ default: 1,
91
+ alias: 'r',
92
+ })
93
+ .option('model', {
94
+ type: 'string',
95
+ description: 'Model to use for review.mjs (opus or sonnet)',
96
+ alias: 'm',
97
+ default: 'opus',
98
+ choices: ['opus', 'sonnet'],
99
+ })
100
+ .option('focus', {
101
+ type: 'string',
102
+ description: 'Focus areas for reviews (security, performance, logic, style, tests, all)',
103
+ default: 'all',
104
+ alias: 'f',
105
+ })
106
+ .option('auto-approve', {
107
+ type: 'boolean',
108
+ description: 'Auto-approve PRs that pass review criteria',
109
+ default: false,
110
+ })
111
+ .option('interval', {
112
+ type: 'number',
113
+ description: 'Polling interval in seconds',
114
+ default: 300, // 5 minutes
115
+ alias: 'i',
116
+ })
117
+ .option('max-prs', {
118
+ type: 'number',
119
+ description: 'Maximum number of PRs to process (0 = unlimited)',
120
+ default: 0,
121
+ })
122
+ .option('dry-run', {
123
+ type: 'boolean',
124
+ description: 'List PRs that would be reviewed without actually reviewing them',
125
+ default: false,
126
+ })
127
+ .option('verbose', {
128
+ type: 'boolean',
129
+ description: 'Enable verbose logging',
130
+ alias: 'v',
131
+ default: false,
132
+ })
133
+ .option('once', {
134
+ type: 'boolean',
135
+ description: 'Run once and exit instead of continuous monitoring',
136
+ default: false,
137
+ })
138
+ .demandCommand(1, 'GitHub URL is required')
139
+ .help('h')
140
+ .alias('h', 'help');
141
+
142
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
143
+ const helpYargs = createReviewersHiveYargsConfig(getLinoYargsFactory()(hideBin(process.argv)));
144
+ helpYargs.showHelp();
145
+ process.exit(0);
146
+ }
147
+
148
+ const argv = parseCliArgumentsWithLino({
149
+ argv: process.argv,
150
+ commandName: 'reviewers-hive',
151
+ createYargsConfig: createReviewersHiveYargsConfig,
152
+ positionalAliases: ['github-url'],
153
+ });
154
+
155
+ const githubUrl = argv['github-url'] || argv.githubUrl || argv._[0];
140
156
 
141
157
  // Set global verbose mode
142
158
  global.verboseMode = argv.verbose;
@@ -10,23 +10,13 @@
10
10
  import { enhanceErrorMessage, detectMalformedFlags } from './option-suggestions.lib.mjs';
11
11
  import { defaultModels, buildModelOptionDescription, resolveDefaultFallbackModel, resolveRuntimeDefaultModel } from './models/index.mjs';
12
12
  import { validateBranchName } from './solve.branch.lib.mjs';
13
- import { resolveYargsFactory } from './yargs-factory.lib.mjs';
13
+ import { getLinoYargsFactory, hideBin, parseCliArgumentsWithLino } from './cli-arguments.lib.mjs';
14
14
 
15
15
  // Re-export for use by telegram-bot.mjs (avoids extra import lines there)
16
16
  export { detectMalformedFlags };
17
17
 
18
18
  // Export an initialization function that accepts 'use'
19
- export const initializeConfig = async use => {
20
- // Import yargs with specific version for hideBin support
21
- const yargsModule = await use('yargs@17.7.2');
22
- const yargs = resolveYargsFactory(yargsModule);
23
- const helpersModule = await use('yargs@17.7.2/helpers');
24
- // Node 24 CJS/ESM interop may return the whole module object instead of named exports directly
25
- const helpers = helpersModule.default || helpersModule;
26
- const hideBin = helpers.hideBin || (argv => argv.slice(2));
27
-
28
- return { yargs, hideBin };
29
- };
19
+ export const initializeConfig = async () => ({ yargs: getLinoYargsFactory(), hideBin });
30
20
 
31
21
  // Solve option definitions as a plain data structure.
32
22
  // This is the single source of truth for all solve command options.
@@ -582,8 +572,8 @@ export const createYargsConfig = yargsInstance => {
582
572
  };
583
573
 
584
574
  // Parse command line arguments - now needs yargs and hideBin passed in
585
- export const parseArguments = async (yargs, hideBin) => {
586
- const rawArgs = hideBin(process.argv);
575
+ export const parseArguments = async (yargs = getLinoYargsFactory(), hideBinFn = hideBin) => {
576
+ const rawArgs = hideBinFn(process.argv);
587
577
 
588
578
  // Issue #1092: Detect malformed flag patterns BEFORE yargs parsing
589
579
  // This catches cases like "-- model" which yargs silently treats as positional arguments
@@ -621,7 +611,12 @@ export const parseArguments = async (yargs, hideBin) => {
621
611
 
622
612
  try {
623
613
  yargsInstance = createYargsConfig(yargs());
624
- argv = await yargsInstance.parse(rawArgs);
614
+ argv = parseCliArgumentsWithLino({
615
+ argv: process.argv,
616
+ commandName: 'solve',
617
+ createYargsConfig,
618
+ positionalAliases: ['issue-url'],
619
+ });
625
620
  } finally {
626
621
  // Always restore stderr.write
627
622
  process.stderr.write = originalStderrWrite;
@@ -3,6 +3,7 @@
3
3
 
4
4
  import { exec } from 'child_process';
5
5
  import { promisify } from 'util';
6
+ import { parseCliArgumentsWithLino } from './cli-arguments.lib.mjs';
6
7
 
7
8
  const execAsync = promisify(exec);
8
9
 
@@ -10,6 +11,72 @@ const execAsync = promisify(exec);
10
11
  // This ensures consistent URL validation across all commands (hive, solve, start-screen)
11
12
  const { parseGitHubUrl } = await import('./github.lib.mjs');
12
13
 
14
+ const START_SCREEN_USAGE = ['Usage: start-screen [--auto-terminate] <solve|hive> <github-url> [additional-args...]', '', 'Options:', ' --auto-terminate Session terminates after command completes (old behavior)', ' By default, session stays alive for review and reattachment', '', 'Examples:', ' start-screen solve https://github.com/user/repo/issues/123 --dry-run', ' start-screen --auto-terminate solve https://github.com/user/repo/issues/456', ' start-screen hive https://github.com/user/repo --flag value'];
15
+
16
+ const printUsage = (log = console.error) => {
17
+ for (const line of START_SCREEN_USAGE) {
18
+ log(line);
19
+ }
20
+ };
21
+
22
+ const createStartScreenYargsConfig = yargsInstance =>
23
+ yargsInstance
24
+ .usage(START_SCREEN_USAGE[0])
25
+ .command('$0 <command> <github-url> [additional-args..]', 'Launch solve or hive in a GNU screen session', yargs =>
26
+ yargs
27
+ .positional('command', {
28
+ type: 'string',
29
+ description: 'Command to run',
30
+ })
31
+ .positional('github-url', {
32
+ type: 'string',
33
+ description: 'GitHub repository or issue URL',
34
+ })
35
+ .positional('additional-args', {
36
+ array: true,
37
+ type: 'string',
38
+ description: 'Arguments to pass through to the command',
39
+ })
40
+ )
41
+ .option('auto-terminate', {
42
+ type: 'boolean',
43
+ description: 'Session terminates after command completes',
44
+ default: false,
45
+ })
46
+ .option('help', {
47
+ type: 'boolean',
48
+ description: 'Show help',
49
+ alias: 'h',
50
+ default: false,
51
+ })
52
+ .parserConfiguration({
53
+ 'boolean-negation': true,
54
+ 'unknown-options-as-args': true,
55
+ })
56
+ .help(false)
57
+ .version(false)
58
+ .strict(false);
59
+
60
+ const parseStartScreenArgs = args => {
61
+ const help = args.includes('--help') || args.includes('-h');
62
+ const parsed = parseCliArgumentsWithLino({
63
+ argv: args.filter(arg => arg !== '--help' && arg !== '-h'),
64
+ commandName: 'start-screen',
65
+ createYargsConfig: createStartScreenYargsConfig,
66
+ positionalAliases: ['command', 'github-url', 'additional-args'],
67
+ lenv: { enabled: false },
68
+ getenv: { enabled: false },
69
+ });
70
+ const additionalArgs = parsed.additionalArgs || parsed['additional-args'] || [];
71
+ return {
72
+ autoTerminate: parsed.autoTerminate === true || parsed['auto-terminate'] === true,
73
+ help,
74
+ command: parsed.command,
75
+ githubUrl: parsed.githubUrl || parsed['github-url'],
76
+ commandArgs: Array.isArray(additionalArgs) ? additionalArgs : [additionalArgs],
77
+ };
78
+ };
79
+
13
80
  /**
14
81
  * Generate a screen session name based on the command and GitHub URL
15
82
  * @param {string} command - Either 'solve' or 'hive'
@@ -221,23 +288,18 @@ async function createOrEnterScreen(sessionName, command, args, autoTerminate = f
221
288
  async function main() {
222
289
  const args = process.argv.slice(2);
223
290
 
291
+ if (args.includes('--help') || args.includes('-h')) {
292
+ printUsage(console.log);
293
+ process.exit(0);
294
+ }
295
+
224
296
  if (args.length < 2) {
225
- console.error('Usage: start-screen [--auto-terminate] <solve|hive> <github-url> [additional-args...]');
226
- console.error('');
227
- console.error('Options:');
228
- console.error(' --auto-terminate Session terminates after command completes (old behavior)');
229
- console.error(' By default, session stays alive for review and reattachment');
230
- console.error('');
231
- console.error('Examples:');
232
- console.error(' start-screen solve https://github.com/user/repo/issues/123 --dry-run');
233
- console.error(' start-screen --auto-terminate solve https://github.com/user/repo/issues/456');
234
- console.error(' start-screen hive https://github.com/user/repo --flag value');
297
+ printUsage(console.error);
235
298
  process.exit(1);
236
299
  }
237
300
 
238
301
  // Check for --auto-terminate flag at the beginning
239
302
  // Also validate that first arg is not an unrecognized option with em-dash or other invalid dash
240
- let autoTerminate = false;
241
303
  let argsOffset = 0;
242
304
 
243
305
  // Check for various dash characters in first argument (em-dash \u2014, en-dash \u2013, etc.)
@@ -249,7 +311,6 @@ async function main() {
249
311
  }
250
312
 
251
313
  if (args[0] === '--auto-terminate') {
252
- autoTerminate = true;
253
314
  argsOffset = 1;
254
315
 
255
316
  if (args.length < 3) {
@@ -277,9 +338,7 @@ async function main() {
277
338
  }
278
339
  }
279
340
 
280
- const command = args[argsOffset];
281
- const githubUrl = args[argsOffset + 1];
282
- const commandArgs = args.slice(argsOffset + 2);
341
+ const { autoTerminate, command, githubUrl, commandArgs } = parseStartScreenArgs(args);
283
342
 
284
343
  // Validate command
285
344
  if (command !== 'solve' && command !== 'hive') {
@@ -0,0 +1,61 @@
1
+ import path from 'path';
2
+ import { constants as fsConstants, promises as fs } from 'fs';
3
+ import { createRequire } from 'module';
4
+
5
+ const require = createRequire(import.meta.url);
6
+
7
+ function startAgentExecutableName() {
8
+ return process.platform === 'win32' ? 'start-agent.cmd' : 'start-agent';
9
+ }
10
+
11
+ async function canExecute(filePath) {
12
+ try {
13
+ await fs.access(filePath, fsConstants.X_OK);
14
+ return true;
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ export function getBundledStartAgentCandidate() {
21
+ try {
22
+ const entryPath = require.resolve('agent-commander');
23
+ return path.join(path.dirname(path.dirname(entryPath)), 'bin', 'start-agent.mjs');
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+
29
+ export async function resolveStartAgentCommand(options = {}) {
30
+ const { cwd = process.cwd(), runCommand } = options;
31
+ const candidates = [getBundledStartAgentCandidate(), path.join(cwd, 'node_modules', '.bin', startAgentExecutableName())].filter(Boolean);
32
+
33
+ for (const candidate of candidates) {
34
+ if (await canExecute(candidate)) return candidate;
35
+ }
36
+
37
+ if (!runCommand) return null;
38
+
39
+ const lookupCommand = process.platform === 'win32' ? 'where' : 'which';
40
+ const result = await runCommand(lookupCommand, ['start-agent']);
41
+ if (result.code !== 0) return null;
42
+
43
+ return (
44
+ (result.stdout || '')
45
+ .split(/\r?\n/)
46
+ .map(line => line.trim())
47
+ .find(Boolean) || null
48
+ );
49
+ }
50
+
51
+ export function buildStartAgentArgs(options) {
52
+ const { tool, workingDirectory, prompt, systemPrompt, model, isolation, screenName, verbose } = options;
53
+ const args = ['--tool', tool, '--working-directory', workingDirectory, '--prompt', prompt, '--system-prompt', systemPrompt, '--model', model, '--isolation', isolation, '--read-only'];
54
+
55
+ if (isolation === 'screen' && screenName) {
56
+ args.push('--screen-name', screenName);
57
+ }
58
+ if (verbose) args.push('--verbose');
59
+
60
+ return args;
61
+ }
@@ -0,0 +1,122 @@
1
+ import { buildModelOptionDescription, defaultModels } from './models/index.mjs';
2
+ import { parseCliArgumentsWithLino } from './cli-arguments.lib.mjs';
3
+
4
+ export const TASK_TOOL_CHOICES = ['claude', 'codex', 'opencode', 'agent'];
5
+
6
+ export function getDefaultTaskModel(tool) {
7
+ return defaultModels[tool] || defaultModels.claude;
8
+ }
9
+
10
+ export const createYargsConfig = yargsInstance =>
11
+ yargsInstance
12
+ .usage('Usage: task.mjs <task-description> [options]')
13
+ .command('$0 <task-input>', 'Clarify, decompose, or split a task', yargs => {
14
+ yargs.positional('task-input', {
15
+ type: 'string',
16
+ description: 'GitHub issue URL for --split, or a task description for clarify/decompose mode',
17
+ });
18
+ })
19
+ .option('clarify', {
20
+ type: 'boolean',
21
+ description: 'Enable clarification mode',
22
+ default: true,
23
+ })
24
+ .option('decompose', {
25
+ type: 'boolean',
26
+ description: 'Enable decomposition mode',
27
+ default: true,
28
+ })
29
+ .option('only-clarify', {
30
+ type: 'boolean',
31
+ description: 'Only run clarification mode',
32
+ default: false,
33
+ })
34
+ .option('only-decompose', {
35
+ type: 'boolean',
36
+ description: 'Only run decomposition mode',
37
+ default: false,
38
+ })
39
+ .option('split', {
40
+ type: 'boolean',
41
+ description: 'Split a GitHub issue into smaller GitHub issues',
42
+ default: false,
43
+ })
44
+ .option('split-count', {
45
+ type: 'number',
46
+ description: 'Number of issues to split into',
47
+ default: 2,
48
+ })
49
+ .option('tool', {
50
+ type: 'string',
51
+ description: 'AI tool to use through agent-commander read-only mode',
52
+ choices: TASK_TOOL_CHOICES,
53
+ default: 'claude',
54
+ })
55
+ .option('model', {
56
+ type: 'string',
57
+ description: buildModelOptionDescription(),
58
+ alias: 'm',
59
+ })
60
+ .option('isolation', {
61
+ type: 'string',
62
+ description: 'agent-commander isolation mode',
63
+ choices: ['screen', 'none', 'docker'],
64
+ default: 'screen',
65
+ })
66
+ .option('screen-name', {
67
+ type: 'string',
68
+ description: 'Screen session name when --isolation screen is used',
69
+ })
70
+ .option('dry-run', {
71
+ type: 'boolean',
72
+ description: 'Print planned split issues without creating or linking GitHub issues',
73
+ default: false,
74
+ })
75
+ .option('verbose', {
76
+ type: 'boolean',
77
+ description: 'Enable verbose logging',
78
+ alias: 'v',
79
+ default: false,
80
+ })
81
+ .option('output-format', {
82
+ type: 'string',
83
+ description: 'Output format',
84
+ alias: 'o',
85
+ choices: ['text', 'json'],
86
+ default: 'text',
87
+ })
88
+ .check(argv => {
89
+ if (!argv['task-input'] && !argv._[0]) {
90
+ throw new Error('Please provide a GitHub issue URL or task description');
91
+ }
92
+ if (argv['only-clarify'] && argv['only-decompose']) {
93
+ throw new Error('Cannot use both --only-clarify and --only-decompose at the same time');
94
+ }
95
+ if (argv.split && (argv['only-clarify'] || argv['only-decompose'])) {
96
+ throw new Error('Cannot use --split with --only-clarify or --only-decompose');
97
+ }
98
+ if (argv.split && argv['split-count'] < 2) {
99
+ throw new Error('--split-count must be at least 2');
100
+ }
101
+ if (argv['only-clarify']) argv.decompose = false;
102
+ if (argv['only-decompose']) argv.clarify = false;
103
+ if (argv.split) {
104
+ argv.clarify = false;
105
+ argv.decompose = false;
106
+ }
107
+ return true;
108
+ })
109
+ .parserConfiguration({
110
+ 'boolean-negation': true,
111
+ })
112
+ .strict()
113
+ .help('h')
114
+ .alias('h', 'help');
115
+
116
+ export const parseTaskArguments = (argv = process.argv) =>
117
+ parseCliArgumentsWithLino({
118
+ argv,
119
+ commandName: 'task',
120
+ createYargsConfig,
121
+ positionalAliases: ['task-input'],
122
+ });