@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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.61.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 728b0ed: Add Telegram `/task` issue creation from repository links and issue text while preserving `/split` behavior.
8
+
9
+ ## 1.60.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Add issue-based task splitting with `/task` and `/split` Telegram commands.
14
+
3
15
  ## 1.59.7
4
16
 
5
17
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.59.7",
3
+ "version": "1.61.0",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -72,8 +72,10 @@
72
72
  "@secretlint/secretlint-rule-preset-recommend": "^11.2.5",
73
73
  "@sentry/node": "^10.15.0",
74
74
  "@sentry/profiling-node": "^10.15.0",
75
+ "agent-commander": "^0.4.2",
75
76
  "dayjs": "^1.11.19",
76
77
  "decimal.js-light": "^2.5.1",
78
+ "lino-arguments": "^0.3.0",
77
79
  "secretlint": "^11.2.5",
78
80
  "semver": "^7.7.3"
79
81
  },
@@ -0,0 +1,68 @@
1
+ import { getenv, makeConfig, yargs as linoYargs } from 'lino-arguments';
2
+
3
+ export { getenv };
4
+
5
+ export const hideBin = argv => argv.slice(2);
6
+
7
+ export const getLinoYargsFactory = () => linoYargs;
8
+
9
+ const toKebabCase = key =>
10
+ key
11
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
12
+ .replace(/[_\s]+/g, '-')
13
+ .toLowerCase();
14
+
15
+ const toCamelCase = key => key.replace(/[-_\s]+(.)?/g, (_, char) => (char ? char.toUpperCase() : ''));
16
+
17
+ const ensureFullArgv = (argv, commandName) => {
18
+ if (argv.length >= 2 && (argv[0].includes('/') || argv[0] === 'node' || argv[0].endsWith('node'))) {
19
+ return argv;
20
+ }
21
+ return ['node', commandName, ...argv];
22
+ };
23
+
24
+ export function addCliCompatibilityAliases(parsed, { positionalAliases = [] } = {}) {
25
+ const result = {};
26
+ for (const [key, value] of Object.entries(parsed)) {
27
+ if (value === undefined) continue;
28
+ result[key] = value;
29
+ }
30
+
31
+ for (const [key, value] of Object.entries(result)) {
32
+ const kebabKey = toKebabCase(key);
33
+ if (kebabKey && result[kebabKey] === undefined) {
34
+ result[kebabKey] = value;
35
+ }
36
+ }
37
+
38
+ const positionalValues = [];
39
+ for (const alias of positionalAliases) {
40
+ const camelAlias = toCamelCase(alias);
41
+ const value = result[alias] ?? result[camelAlias];
42
+ if (value !== undefined) {
43
+ result[alias] = value;
44
+ result[camelAlias] = value;
45
+ positionalValues.push(value);
46
+ }
47
+ }
48
+ result._ = positionalValues;
49
+
50
+ return result;
51
+ }
52
+
53
+ export function parseCliArgumentsWithLino({ argv = process.argv, commandName = 'cli', createYargsConfig, positionalAliases = [], lenv = { enabled: true }, env = { enabled: false }, getenv: getenvOptions = { enabled: true } } = {}) {
54
+ const fullArgv = ensureFullArgv(argv, commandName);
55
+ const parsed = makeConfig({
56
+ argv: fullArgv,
57
+ lenv,
58
+ env,
59
+ getenv: getenvOptions,
60
+ yargs: ({ yargs, getenv: getenvHelper }) => {
61
+ const parser = yargs.exitProcess(false);
62
+ const configured = createYargsConfig ? createYargsConfig(parser, getenvHelper) : parser;
63
+ return configured.exitProcess(false);
64
+ },
65
+ });
66
+
67
+ return addCliCompatibilityAliases(parsed, { positionalAliases });
68
+ }
@@ -16,25 +16,48 @@ import os from 'node:os';
16
16
  import path from 'node:path';
17
17
 
18
18
  import { REQUIRED_CLAUDE_QUIET_ENV, REQUIRED_CLAUDE_QUIET_SETTINGS, REQUIRED_CLAUDE_QUIET_ATTRIBUTION, REQUIRED_CLAUDE_QUIET_PERMISSIONS, ensureClaudeQuietConfig } from './claude-quiet-config.lib.mjs';
19
+ import { parseCliArgumentsWithLino } from './cli-arguments.lib.mjs';
19
20
  import { buildDisallowedToolsList, ensureDisallowedToolsInSettings } from './useless-tools.lib.mjs';
20
21
 
21
22
  export const resolveSettingsPath = settingsPath => settingsPath || path.join(os.homedir(), '.claude', 'settings.json');
22
23
 
24
+ export const createConfigureClaudeYargsConfig = yargsInstance =>
25
+ yargsInstance
26
+ .usage('Usage: configure-claude [options]')
27
+ .option('settings-path', {
28
+ type: 'string',
29
+ description: 'Path to settings.json',
30
+ alias: 's',
31
+ })
32
+ .option('verify', {
33
+ type: 'boolean',
34
+ description: 'Report configuration status without writing',
35
+ default: false,
36
+ })
37
+ .option('help', {
38
+ type: 'boolean',
39
+ description: 'Show this help and exit',
40
+ alias: 'h',
41
+ default: false,
42
+ })
43
+ .help(false)
44
+ .version(false)
45
+ .strict();
46
+
23
47
  export const parseConfigureClaudeArgs = argv => {
24
- const args = { settingsPath: null, verify: false, help: false };
25
- for (let i = 0; i < argv.length; i++) {
26
- const arg = argv[i];
27
- if (arg === '--settings-path' || arg === '-s') {
28
- args.settingsPath = argv[++i];
29
- } else if (arg.startsWith('--settings-path=')) {
30
- args.settingsPath = arg.slice('--settings-path='.length);
31
- } else if (arg === '--verify') {
32
- args.verify = true;
33
- } else if (arg === '--help' || arg === '-h') {
34
- args.help = true;
35
- }
36
- }
37
- return args;
48
+ const help = argv.includes('--help') || argv.includes('-h');
49
+ const parsed = parseCliArgumentsWithLino({
50
+ argv: argv.filter(arg => arg !== '--help' && arg !== '-h'),
51
+ commandName: 'configure-claude',
52
+ createYargsConfig: createConfigureClaudeYargsConfig,
53
+ lenv: { enabled: false },
54
+ getenv: { enabled: false },
55
+ });
56
+ return {
57
+ settingsPath: parsed.settingsPath || parsed['settings-path'] || null,
58
+ verify: parsed.verify === true,
59
+ help,
60
+ };
38
61
  };
39
62
 
40
63
  export const CONFIGURE_CLAUDE_HELP = `Usage: configure-claude [options]
@@ -15,6 +15,7 @@ import fs from 'node:fs/promises';
15
15
  import os from 'node:os';
16
16
  import path from 'node:path';
17
17
  import { promisify } from 'node:util';
18
+ import { parseCliArgumentsWithLino } from './cli-arguments.lib.mjs';
18
19
 
19
20
  const execAsync = promisify(execCallback);
20
21
 
@@ -52,6 +53,9 @@ References:
52
53
 
53
54
  const ACTION_FLAGS = new Set(['--enter', '--close', '--list']);
54
55
  const SELECTION_FLAGS = new Set(['--oldest', '--newest', '--all']);
56
+ const HIVE_SCREENS_FLAGS = new Set([...ACTION_FLAGS, ...SELECTION_FLAGS, '--help', '-h', '--verbose', '-v']);
57
+
58
+ const createHiveScreensYargsConfig = yargsInstance => yargsInstance.usage('Usage: hive-screens (--list | --enter | --close) [--oldest|--newest|--all] [--verbose]').option('enter', { type: 'boolean', default: false }).option('close', { type: 'boolean', default: false }).option('list', { type: 'boolean', default: false }).option('oldest', { type: 'boolean', default: false }).option('newest', { type: 'boolean', default: false }).option('all', { type: 'boolean', default: false }).option('verbose', { type: 'boolean', alias: 'v', default: false }).option('help', { type: 'boolean', alias: 'h', default: false }).help(false).version(false).strict(false);
55
59
 
56
60
  /**
57
61
  * Parse the argv for `hive-screens`. Returns the parsed flags plus an
@@ -59,6 +63,7 @@ const SELECTION_FLAGS = new Set(['--oldest', '--newest', '--all']);
59
63
  * with a non-zero status without throwing).
60
64
  */
61
65
  export const parseHiveScreensArgs = argv => {
66
+ const help = argv.includes('--help') || argv.includes('-h');
62
67
  const result = {
63
68
  enter: false,
64
69
  close: false,
@@ -70,32 +75,41 @@ export const parseHiveScreensArgs = argv => {
70
75
  };
71
76
 
72
77
  for (const arg of argv) {
73
- if (arg === '--help' || arg === '-h') {
74
- result.help = true;
75
- continue;
76
- }
77
- if (arg === '--verbose' || arg === '-v') {
78
- result.verbose = true;
79
- continue;
78
+ if (!HIVE_SCREENS_FLAGS.has(arg)) {
79
+ result.error = `Unknown option: ${arg}`;
80
+ return result;
80
81
  }
81
- if (ACTION_FLAGS.has(arg)) {
82
- if (arg === '--enter') result.enter = true;
83
- else if (arg === '--close') result.close = true;
84
- else result.list = true;
85
- continue;
86
- }
87
- if (SELECTION_FLAGS.has(arg)) {
88
- if (result.selection && result.selection !== arg.slice(2)) {
89
- result.error = `Conflicting selection flags: --${result.selection} and ${arg}`;
90
- return result;
91
- }
92
- result.selection = arg.slice(2);
93
- continue;
94
- }
95
- result.error = `Unknown option: ${arg}`;
82
+ }
83
+
84
+ let parsed;
85
+ try {
86
+ parsed = parseCliArgumentsWithLino({
87
+ argv: argv.filter(arg => arg !== '--help' && arg !== '-h'),
88
+ commandName: 'hive-screens',
89
+ createYargsConfig: createHiveScreensYargsConfig,
90
+ lenv: { enabled: false },
91
+ getenv: { enabled: false },
92
+ });
93
+ } catch (err) {
94
+ result.error = err.message || String(err);
96
95
  return result;
97
96
  }
98
97
 
98
+ result.help = help;
99
+ result.verbose = parsed.verbose === true || parsed.v === true;
100
+ result.enter = parsed.enter === true;
101
+ result.close = parsed.close === true;
102
+ result.list = parsed.list === true;
103
+
104
+ for (const selection of ['oldest', 'newest', 'all']) {
105
+ if (parsed[selection] !== true) continue;
106
+ if (result.selection && result.selection !== selection) {
107
+ result.error = `Conflicting selection flags: --${result.selection} and --${selection}`;
108
+ return result;
109
+ }
110
+ result.selection = selection;
111
+ }
112
+
99
113
  if (result.help) return result;
100
114
 
101
115
  const actions = [result.enter, result.close, result.list].filter(Boolean).length;
package/src/hive.mjs CHANGED
@@ -17,15 +17,9 @@ if (earlyArgs.includes('--version')) {
17
17
  if (earlyArgs.includes('--help') || earlyArgs.includes('-h')) {
18
18
  try {
19
19
  // Load minimal modules needed for help
20
- const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
21
- globalThis.use = use;
22
- const yargsModule = await use('yargs@17.7.2');
23
- const { resolveYargsFactory } = await import('./yargs-factory.lib.mjs');
24
- const yargs = resolveYargsFactory(yargsModule);
25
- const helpersModuleHelp = await use('yargs@17.7.2/helpers');
26
- const _helpersHelp = helpersModuleHelp.default || helpersModuleHelp;
27
- const hideBinHelp = _helpersHelp.hideBin || (argv => argv.slice(2));
28
- const rawArgs = hideBinHelp(process.argv);
20
+ const { getLinoYargsFactory, hideBin } = await import('./cli-arguments.lib.mjs');
21
+ const yargs = getLinoYargsFactory();
22
+ const rawArgs = hideBin(process.argv);
29
23
  // Reuse createYargsConfig from shared module to avoid duplication
30
24
  const { createYargsConfig } = await import('./hive.config.lib.mjs');
31
25
  const helpYargs = createYargsConfig(yargs(rawArgs)).version(false);
@@ -71,12 +65,7 @@ if (isRunningDirectly) {
71
65
  30000, // 30 second timeout
72
66
  'loading command-stream'
73
67
  );
74
- const yargsModule = await withTimeout(use('yargs@17.7.2'), 30000, 'loading yargs');
75
- const { resolveYargsFactory } = await import('./yargs-factory.lib.mjs');
76
- const yargs = resolveYargsFactory(yargsModule);
77
- const helpersModuleMain = await withTimeout(use('yargs@17.7.2/helpers'), 30000, 'loading yargs helpers');
78
- const _helpersMain = helpersModuleMain.default || helpersModuleMain;
79
- const hideBin = _helpersMain.hideBin || (argv => argv.slice(2));
68
+ const { parseCliArgumentsWithLino, hideBin } = await import('./cli-arguments.lib.mjs');
80
69
  const path = (await withTimeout(use('path'), 30000, 'loading path')).default;
81
70
  const fs = (await withTimeout(use('fs'), 30000, 'loading fs')).promises;
82
71
  // Import shared library functions
@@ -258,7 +247,12 @@ if (isRunningDirectly) {
258
247
  };
259
248
 
260
249
  try {
261
- argv = await createYargsConfig(yargs()).parse(rawArgs);
250
+ argv = parseCliArgumentsWithLino({
251
+ argv: process.argv,
252
+ commandName: 'hive',
253
+ createYargsConfig,
254
+ positionalAliases: ['github-url'],
255
+ });
262
256
  // Restore stderr if parsing succeeded
263
257
  process.stderr.write = originalStderrWrite;
264
258
  } catch (error) {
@@ -6,7 +6,7 @@ if (typeof globalThis.use === 'undefined') {
6
6
  globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
7
7
  }
8
8
  const use = globalThis.use;
9
- const { resolveYargsFactory } = await import('./yargs-factory.lib.mjs');
9
+ const { getLinoYargsFactory, hideBin, parseCliArgumentsWithLino } = await import('./cli-arguments.lib.mjs');
10
10
 
11
11
  // Temporarily unset CI to avoid command-stream trace logs
12
12
  const originalCI = process.env.CI;
@@ -18,12 +18,6 @@ const { $ } = await use('command-stream');
18
18
  // These are filtered out by consuming code when parsing JSON
19
19
  const $silent = $({ mirror: false, capture: true });
20
20
 
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
21
  const fs = (await use('fs')).promises;
28
22
 
29
23
  // Import log function from lib.mjs
@@ -325,9 +319,8 @@ export const checkSystem = async (requirements = {}, options = {}) => {
325
319
  };
326
320
 
327
321
  // CLI interface when run directly
328
- if (import.meta.url === `file://${process.argv[1]}`) {
329
- // Create yargs instance with all options
330
- const yargsInstance = yargs(hideBin(process.argv))
322
+ const createMemoryCheckYargsConfig = yargsInstance =>
323
+ yargsInstance
331
324
  .scriptName('memory-check.mjs')
332
325
  .usage('Usage: $0 [options]')
333
326
  .option('min-memory', {
@@ -368,13 +361,19 @@ if (import.meta.url === `file://${process.argv[1]}`) {
368
361
  .help('h')
369
362
  .alias('h', 'help');
370
363
 
364
+ if (import.meta.url === `file://${process.argv[1]}`) {
371
365
  // Check for help before parsing
372
366
  if (process.argv.includes('--help') || process.argv.includes('-h')) {
367
+ const yargsInstance = createMemoryCheckYargsConfig(getLinoYargsFactory()(hideBin(process.argv)));
373
368
  yargsInstance.showHelp();
374
369
  process.exit(0);
375
370
  }
376
371
 
377
- const argv = await yargsInstance.parseAsync();
372
+ const argv = parseCliArgumentsWithLino({
373
+ argv: process.argv,
374
+ commandName: 'memory-check.mjs',
375
+ createYargsConfig: createMemoryCheckYargsConfig,
376
+ });
378
377
 
379
378
  // If we get here, help wasn't requested or yargs didn't handle it
380
379
  // Set up logging based on options
package/src/review.mjs CHANGED
@@ -38,13 +38,13 @@ const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text(
38
38
  const { $: __rawDollar$ } = await use('command-stream');
39
39
  const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
40
40
  const $ = wrapDollarWithGhRetry(__rawDollar$);
41
- const yargs = (await use('yargs@latest')).default;
42
41
  const os = (await use('os')).default;
43
42
  const path = (await use('path')).default;
44
43
  const fs = (await use('fs')).promises;
45
44
 
46
45
  // Import shared functions from lib.mjs to follow DRY principle
47
46
  import { log, setLogFile, getLogFile, formatAligned } from './lib.mjs';
47
+ import { parseCliArgumentsWithLino } from './cli-arguments.lib.mjs';
48
48
  import { reportError } from './sentry.lib.mjs';
49
49
  import * as memoryCheck from './memory-check.mjs';
50
50
 
@@ -53,64 +53,77 @@ import { executeClaudeCommand } from './claude.lib.mjs';
53
53
  import { defaultModels, getClaudeModelChoices, buildModelOptionDescription } from './models/index.mjs';
54
54
 
55
55
  // Configure command line arguments - GitHub PR URL as positional argument
56
- // Use yargs().parse(args) instead of yargs(args).argv to ensure .strict() mode works
57
- const argv = yargs()
58
- .usage('Usage: $0 <pr-url> [options]')
59
- .positional('pr-url', {
60
- type: 'string',
61
- description: 'The GitHub pull request URL to review',
62
- })
63
- .option('resume', {
64
- type: 'string',
65
- description: 'Resume from a previous session ID (when limit was reached)',
66
- alias: 'r',
67
- })
68
- .option('dry-run', {
69
- type: 'boolean',
70
- description: 'Prepare everything but do not execute Claude',
71
- alias: 'n',
72
- })
73
- .option('model', {
74
- type: 'string',
75
- description: buildModelOptionDescription(),
76
- alias: 'm',
77
- default: defaultModels.claude,
78
- choices: getClaudeModelChoices(),
79
- })
80
- .option('focus', {
81
- type: 'string',
82
- description: 'Focus areas for review (security, performance, logic, style, tests)',
83
- alias: 'f',
84
- default: 'all',
85
- })
86
- .option('approve', {
87
- type: 'boolean',
88
- description: 'If review passes, approve the PR',
89
- default: false,
90
- })
91
- .option('verbose', {
92
- type: 'boolean',
93
- description: 'Enable verbose logging for debugging',
94
- alias: 'v',
95
- default: false,
96
- })
97
- .option('execute-tool-with-bun', {
98
- type: 'boolean',
99
- description: 'Execute the AI tool using bunx (experimental, may improve speed and memory usage)',
100
- default: false,
101
- })
102
- .demandCommand(1, 'The GitHub pull request URL is required')
103
- .parserConfiguration({
104
- 'boolean-negation': true,
105
- })
106
- .help('h')
107
- .alias('h', 'help')
108
- // Use yargs built-in strict mode to reject unrecognized options
109
- // This prevents issues like #453 and #482 where unknown options are silently ignored
110
- .strict()
111
- .parse(process.argv.slice(2));
112
-
113
- const prUrl = argv['pr-url'] || argv._[0];
56
+ const createReviewYargsConfig = yargsInstance =>
57
+ yargsInstance
58
+ .usage('Usage: $0 <pr-url> [options]')
59
+ .command('$0 <pr-url>', 'Review a GitHub pull request', yargs =>
60
+ yargs.positional('pr-url', {
61
+ type: 'string',
62
+ description: 'The GitHub pull request URL to review',
63
+ })
64
+ )
65
+ .option('resume', {
66
+ type: 'string',
67
+ description: 'Resume from a previous session ID (when limit was reached)',
68
+ alias: 'r',
69
+ })
70
+ .option('dry-run', {
71
+ type: 'boolean',
72
+ description: 'Prepare everything but do not execute Claude',
73
+ alias: 'n',
74
+ })
75
+ .option('model', {
76
+ type: 'string',
77
+ description: buildModelOptionDescription(),
78
+ alias: 'm',
79
+ default: defaultModels.claude,
80
+ choices: getClaudeModelChoices(),
81
+ })
82
+ .option('focus', {
83
+ type: 'string',
84
+ description: 'Focus areas for review (security, performance, logic, style, tests)',
85
+ alias: 'f',
86
+ default: 'all',
87
+ })
88
+ .option('approve', {
89
+ type: 'boolean',
90
+ description: 'If review passes, approve the PR',
91
+ default: false,
92
+ })
93
+ .option('verbose', {
94
+ type: 'boolean',
95
+ description: 'Enable verbose logging for debugging',
96
+ alias: 'v',
97
+ default: false,
98
+ })
99
+ .option('execute-tool-with-bun', {
100
+ type: 'boolean',
101
+ description: 'Execute the AI tool using bunx (experimental, may improve speed and memory usage)',
102
+ default: false,
103
+ })
104
+ .check(parsed => {
105
+ if (!parsed['pr-url'] && !parsed.prUrl && !parsed._?.[0]) {
106
+ throw new Error('The GitHub pull request URL is required');
107
+ }
108
+ return true;
109
+ })
110
+ .parserConfiguration({
111
+ 'boolean-negation': true,
112
+ })
113
+ .help('h')
114
+ .alias('h', 'help')
115
+ // Use yargs built-in strict mode to reject unrecognized options
116
+ // This prevents issues like #453 and #482 where unknown options are silently ignored
117
+ .strict();
118
+
119
+ const argv = parseCliArgumentsWithLino({
120
+ argv: process.argv,
121
+ commandName: 'review',
122
+ createYargsConfig: createReviewYargsConfig,
123
+ positionalAliases: ['pr-url'],
124
+ });
125
+
126
+ const prUrl = argv['pr-url'] || argv.prUrl || argv._[0];
114
127
 
115
128
  // Set global verbose mode for log function
116
129
  global.verboseMode = argv.verbose;