@link-assistant/hive-mind 0.39.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 +20 -0
  2. package/LICENSE +24 -0
  3. package/README.md +769 -0
  4. package/package.json +58 -0
  5. package/src/agent.lib.mjs +705 -0
  6. package/src/agent.prompts.lib.mjs +196 -0
  7. package/src/buildUserMention.lib.mjs +71 -0
  8. package/src/claude-limits.lib.mjs +389 -0
  9. package/src/claude.lib.mjs +1445 -0
  10. package/src/claude.prompts.lib.mjs +203 -0
  11. package/src/codex.lib.mjs +552 -0
  12. package/src/codex.prompts.lib.mjs +194 -0
  13. package/src/config.lib.mjs +207 -0
  14. package/src/contributing-guidelines.lib.mjs +268 -0
  15. package/src/exit-handler.lib.mjs +205 -0
  16. package/src/git.lib.mjs +145 -0
  17. package/src/github-issue-creator.lib.mjs +246 -0
  18. package/src/github-linking.lib.mjs +152 -0
  19. package/src/github.batch.lib.mjs +272 -0
  20. package/src/github.graphql.lib.mjs +258 -0
  21. package/src/github.lib.mjs +1479 -0
  22. package/src/hive.config.lib.mjs +254 -0
  23. package/src/hive.mjs +1500 -0
  24. package/src/instrument.mjs +191 -0
  25. package/src/interactive-mode.lib.mjs +1000 -0
  26. package/src/lenv-reader.lib.mjs +206 -0
  27. package/src/lib.mjs +490 -0
  28. package/src/lino.lib.mjs +176 -0
  29. package/src/local-ci-checks.lib.mjs +324 -0
  30. package/src/memory-check.mjs +419 -0
  31. package/src/model-mapping.lib.mjs +145 -0
  32. package/src/model-validation.lib.mjs +278 -0
  33. package/src/opencode.lib.mjs +479 -0
  34. package/src/opencode.prompts.lib.mjs +194 -0
  35. package/src/protect-branch.mjs +159 -0
  36. package/src/review.mjs +433 -0
  37. package/src/reviewers-hive.mjs +643 -0
  38. package/src/sentry.lib.mjs +284 -0
  39. package/src/solve.auto-continue.lib.mjs +568 -0
  40. package/src/solve.auto-pr.lib.mjs +1374 -0
  41. package/src/solve.branch-errors.lib.mjs +341 -0
  42. package/src/solve.branch.lib.mjs +230 -0
  43. package/src/solve.config.lib.mjs +342 -0
  44. package/src/solve.error-handlers.lib.mjs +256 -0
  45. package/src/solve.execution.lib.mjs +291 -0
  46. package/src/solve.feedback.lib.mjs +436 -0
  47. package/src/solve.mjs +1128 -0
  48. package/src/solve.preparation.lib.mjs +210 -0
  49. package/src/solve.repo-setup.lib.mjs +114 -0
  50. package/src/solve.repository.lib.mjs +961 -0
  51. package/src/solve.results.lib.mjs +558 -0
  52. package/src/solve.session.lib.mjs +135 -0
  53. package/src/solve.validation.lib.mjs +325 -0
  54. package/src/solve.watch.lib.mjs +572 -0
  55. package/src/start-screen.mjs +324 -0
  56. package/src/task.mjs +308 -0
  57. package/src/telegram-bot.mjs +1481 -0
  58. package/src/telegram-markdown.lib.mjs +64 -0
  59. package/src/usage-limit.lib.mjs +218 -0
  60. package/src/version.lib.mjs +41 -0
  61. package/src/youtrack/solve.youtrack.lib.mjs +116 -0
  62. package/src/youtrack/youtrack-sync.mjs +219 -0
  63. package/src/youtrack/youtrack.lib.mjs +425 -0
@@ -0,0 +1,324 @@
1
+ #!/usr/bin/env node
2
+ // start-screen.mjs - Launch solve or hive commands in GNU screen sessions
3
+
4
+ import { exec } from 'child_process';
5
+ import { promisify } from 'util';
6
+
7
+ const execAsync = promisify(exec);
8
+
9
+ // Import the shared parseGitHubUrl function from github.lib.mjs
10
+ // This ensures consistent URL validation across all commands (hive, solve, start-screen)
11
+ const { parseGitHubUrl } = await import('./github.lib.mjs');
12
+
13
+ /**
14
+ * Generate a screen session name based on the command and GitHub URL
15
+ * @param {string} command - Either 'solve' or 'hive'
16
+ * @param {string} githubUrl - GitHub repository or issue URL
17
+ * @returns {string} The generated screen session name
18
+ */
19
+ function generateScreenName(command, githubUrl) {
20
+ const parsed = parseGitHubUrl(githubUrl);
21
+
22
+ if (!parsed.valid) {
23
+ // Fallback to simple naming if parsing fails
24
+ const sanitized = githubUrl.replace(/[^a-zA-Z0-9-]/g, '-').substring(0, 30);
25
+ return `${command}-${sanitized}`;
26
+ }
27
+
28
+ // Build name parts
29
+ const parts = [command];
30
+
31
+ if (parsed.owner) {
32
+ parts.push(parsed.owner);
33
+ }
34
+
35
+ if (parsed.repo) {
36
+ parts.push(parsed.repo);
37
+ }
38
+
39
+ if (parsed.number) {
40
+ parts.push(parsed.number);
41
+ }
42
+
43
+ return parts.join('-');
44
+ }
45
+
46
+ /**
47
+ * Check if a screen session exists
48
+ * @param {string} sessionName - The name of the screen session
49
+ * @returns {Promise<boolean>} Whether the session exists
50
+ */
51
+ async function screenSessionExists(sessionName) {
52
+ try {
53
+ const { stdout } = await execAsync('screen -ls');
54
+ return stdout.includes(sessionName);
55
+ } catch {
56
+ // screen -ls returns non-zero exit code when no sessions exist
57
+ return false;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Wait for a screen session to be ready to accept commands
63
+ * A session is considered ready when it can execute a test command
64
+ * @param {string} sessionName - The name of the screen session
65
+ * @param {number} maxWaitSeconds - Maximum time to wait in seconds (default: 5)
66
+ * @returns {Promise<boolean>} Whether the session became ready
67
+ */
68
+ async function waitForSessionReady(sessionName, maxWaitSeconds = 5) {
69
+ const startTime = Date.now();
70
+ const maxWaitMs = maxWaitSeconds * 1000;
71
+
72
+ // Use a unique marker file for this check to avoid conflicts
73
+ const markerFile = `/tmp/screen-ready-${sessionName}-${Date.now()}.marker`;
74
+
75
+ while (Date.now() - startTime < maxWaitMs) {
76
+ try {
77
+ // Send a test command that creates a marker file
78
+ // This command will only execute when the session is actually ready at a prompt
79
+ await execAsync(`screen -S ${sessionName} -X stuff "touch ${markerFile} 2>/dev/null\n"`);
80
+
81
+ // Wait for the marker file to appear
82
+ const checkStartTime = Date.now();
83
+ const checkTimeout = 1000; // 1 second to check if marker appears
84
+
85
+ while (Date.now() - checkStartTime < checkTimeout) {
86
+ try {
87
+ const { code } = await execAsync(`test -f ${markerFile}`);
88
+ if (code === 0) {
89
+ // Marker file exists, session is ready!
90
+ // Clean up the marker file
91
+ await execAsync(`rm -f ${markerFile}`).catch(() => { });
92
+ return true;
93
+ }
94
+ } catch {
95
+ // Marker file doesn't exist yet
96
+ }
97
+
98
+ // Wait before checking again
99
+ await new Promise(resolve => setTimeout(resolve, 100));
100
+ }
101
+
102
+ // Marker file didn't appear, session is still busy
103
+ // Clean up any leftover marker file from the queued command
104
+ await execAsync(`rm -f ${markerFile}`).catch(() => { });
105
+ } catch {
106
+ // Error sending test command or checking marker
107
+ }
108
+
109
+ // Wait before trying again
110
+ await new Promise(resolve => setTimeout(resolve, 500));
111
+ }
112
+
113
+ // Timeout reached, session is not ready
114
+ return false;
115
+ }
116
+
117
+ /**
118
+ * Create or enter a screen session with the given command
119
+ * @param {string} sessionName - The name of the screen session
120
+ * @param {string} command - The command to run ('solve' or 'hive')
121
+ * @param {string[]} args - Arguments to pass to the command
122
+ * @param {boolean} autoTerminate - If true, session terminates after command completes
123
+ */
124
+ async function createOrEnterScreen(sessionName, command, args, autoTerminate = false) {
125
+ const sessionExists = await screenSessionExists(sessionName);
126
+
127
+ if (sessionExists) {
128
+ console.log(`Screen session '${sessionName}' already exists.`);
129
+ console.log('Checking if session is ready to accept commands...');
130
+
131
+ // Wait for the session to be ready (at a prompt)
132
+ const isReady = await waitForSessionReady(sessionName);
133
+
134
+ if (isReady) {
135
+ console.log('Session is ready.');
136
+ } else {
137
+ console.log('Session might still be running a command. Will attempt to send command anyway.');
138
+ console.log('Note: The command will execute once the current operation completes.');
139
+ }
140
+
141
+ console.log('Sending command to existing session...');
142
+
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(' ');
154
+
155
+ const fullCommand = `${command} ${quotedArgs}`;
156
+
157
+ // Escape the command for screen's stuff command
158
+ // We need to escape special characters for the shell
159
+ const escapedCommand = fullCommand.replace(/'/g, "'\\''");
160
+
161
+ try {
162
+ // Send the command to the existing screen session
163
+ // The \n at the end simulates pressing Enter
164
+ await execAsync(`screen -S ${sessionName} -X stuff '${escapedCommand}\n'`);
165
+ console.log(`Command sent to session '${sessionName}' successfully.`);
166
+ console.log(`To attach and view the session, run: screen -r ${sessionName}`);
167
+ } catch (error) {
168
+ console.error('Failed to send command to existing screen session:', error.message);
169
+ console.error('You may need to terminate the old session and try again.');
170
+ process.exit(1);
171
+ }
172
+ return;
173
+ }
174
+
175
+ console.log(`Creating screen session: ${sessionName}`);
176
+
177
+ // Create a detached session with the command
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(' ');
189
+
190
+ let screenCommand;
191
+ if (autoTerminate) {
192
+ // Old behavior: session terminates after command completes
193
+ const fullCommand = `${command} ${quotedArgs}`;
194
+ screenCommand = `screen -dmS ${sessionName} ${fullCommand}`;
195
+ } else {
196
+ // New behavior: wrap the command in a bash shell that will stay alive after the command finishes
197
+ // This allows the user to reattach to the screen session after the command completes
198
+ const fullCommand = `${command} ${quotedArgs}`;
199
+ const escapedCommand = fullCommand.replace(/'/g, "'\\''");
200
+ screenCommand = `screen -dmS ${sessionName} bash -c '${escapedCommand}; exec bash'`;
201
+ }
202
+
203
+ try {
204
+ await execAsync(screenCommand);
205
+ console.log(`Started ${command} in detached screen session: ${sessionName}`);
206
+ if (autoTerminate) {
207
+ console.log('Note: Session will terminate after command completes (--auto-terminate mode)');
208
+ } else {
209
+ console.log('Session will remain active after command completes');
210
+ }
211
+ console.log(`To attach to this session, run: screen -r ${sessionName}`);
212
+ } catch (error) {
213
+ console.error('Failed to create screen session:', error.message);
214
+ process.exit(1);
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Main function
220
+ */
221
+ async function main() {
222
+ const args = process.argv.slice(2);
223
+
224
+ 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');
235
+ process.exit(1);
236
+ }
237
+
238
+ // Check for --auto-terminate flag at the beginning
239
+ // Also validate that first arg is not an unrecognized option with em-dash or other invalid dash
240
+ let autoTerminate = false;
241
+ let argsOffset = 0;
242
+
243
+ // Check for various dash characters in first argument (em-dash \u2014, en-dash \u2013, etc.)
244
+ if (args[0] && /^[\u2010\u2011\u2012\u2013\u2014]/.test(args[0])) {
245
+ console.error(`Unknown option: ${args[0]}`);
246
+ console.error('Usage: start-screen [--auto-terminate] <solve|hive> <github-url> [additional-args...]');
247
+ console.error('Note: Use regular hyphens (--) not em-dashes or en-dashes.');
248
+ process.exit(1);
249
+ }
250
+
251
+ if (args[0] === '--auto-terminate') {
252
+ autoTerminate = true;
253
+ argsOffset = 1;
254
+
255
+ if (args.length < 3) {
256
+ console.error('Error: --auto-terminate requires a command and GitHub URL');
257
+ console.error('Usage: start-screen [--auto-terminate] <solve|hive> <github-url> [additional-args...]');
258
+ process.exit(1);
259
+ }
260
+ } else if (args[0] && args[0].startsWith('-') && args[0] !== '--help' && args[0] !== '-h') {
261
+ // First arg is an unrecognized option
262
+ console.error(`Unknown option: ${args[0]}`);
263
+ console.error('Usage: start-screen [--auto-terminate] <solve|hive> <github-url> [additional-args...]');
264
+ process.exit(1);
265
+ }
266
+
267
+ // Check if the next arg (after --auto-terminate if present) looks like an unrecognized option
268
+ if (args[argsOffset] && args[argsOffset].startsWith('-')) {
269
+ // Check for various dash characters (em-dash, en-dash, etc.)
270
+ const firstArg = args[argsOffset];
271
+ const hasInvalidDash = /^[\u2010\u2011\u2012\u2013\u2014]/.test(firstArg);
272
+ if (hasInvalidDash || (firstArg.startsWith('-') && firstArg !== '--help' && firstArg !== '-h')) {
273
+ console.error(`Unknown option: ${firstArg}`);
274
+ console.error('Usage: start-screen [--auto-terminate] <solve|hive> <github-url> [additional-args...]');
275
+ console.error('Expected command to be "solve" or "hive", not an option.');
276
+ process.exit(1);
277
+ }
278
+ }
279
+
280
+ const command = args[argsOffset];
281
+ const githubUrl = args[argsOffset + 1];
282
+ const commandArgs = args.slice(argsOffset + 2);
283
+
284
+ // Validate command
285
+ if (command !== 'solve' && command !== 'hive') {
286
+ console.error(`Error: Invalid command '${command}'. Must be 'solve' or 'hive'.`);
287
+ process.exit(1);
288
+ }
289
+
290
+ // Validate GitHub URL
291
+ const parsed = parseGitHubUrl(githubUrl);
292
+ if (!parsed.valid) {
293
+ console.error(`Error: Invalid GitHub URL. ${parsed.error}`);
294
+ console.error('Please provide a valid GitHub repository or issue URL.');
295
+ process.exit(1);
296
+ }
297
+
298
+ // Generate screen session name
299
+ const sessionName = generateScreenName(command, githubUrl);
300
+
301
+ // Check for screen availability
302
+ try {
303
+ await execAsync('which screen');
304
+ } catch {
305
+ console.error('Error: GNU Screen is not installed or not in PATH.');
306
+ console.error('Please install it using your package manager:');
307
+ console.error(' Ubuntu/Debian: sudo apt-get install screen');
308
+ console.error(' macOS: brew install screen');
309
+ console.error(' RHEL/CentOS: sudo yum install screen');
310
+ process.exit(1);
311
+ }
312
+
313
+ // Prepare full argument list for the command
314
+ const fullArgs = [githubUrl, ...commandArgs];
315
+
316
+ // Create or enter the screen session
317
+ await createOrEnterScreen(sessionName, command, fullArgs, autoTerminate);
318
+ }
319
+
320
+ // Run the main function
321
+ main().catch((error) => {
322
+ console.error('Unexpected error:', error);
323
+ process.exit(1);
324
+ });
package/src/task.mjs ADDED
@@ -0,0 +1,308 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Early exit paths - handle these before loading all modules to speed up testing
4
+ const earlyArgs = process.argv.slice(2);
5
+
6
+ if (earlyArgs.includes('--version')) {
7
+ const { getVersion } = await import('./version.lib.mjs');
8
+ try {
9
+ const version = await getVersion();
10
+ console.log(version);
11
+ } catch {
12
+ console.error('Error: Unable to determine version');
13
+ process.exit(1);
14
+ }
15
+ process.exit(0);
16
+ }
17
+
18
+ if (earlyArgs.includes('--help') || earlyArgs.includes('-h')) {
19
+ // Show help and exit
20
+ console.log('Usage: task.mjs <task-description> [options]');
21
+ console.log('\nOptions:');
22
+ console.log(' --version Show version number');
23
+ console.log(' --help, -h Show help');
24
+ console.log(' --clarify Enable clarification mode [default: true]');
25
+ console.log(' --decompose Enable decomposition mode [default: true]');
26
+ console.log(' --only-clarify Only run clarification mode');
27
+ console.log(' --only-decompose Only run decomposition mode');
28
+ console.log(' --model, -m Model to use (opus, sonnet, or full model ID) [default: sonnet]');
29
+ console.log(' --verbose, -v Enable verbose logging');
30
+ console.log(' --output-format Output format (text or json) [default: text]');
31
+ process.exit(0);
32
+ }
33
+
34
+ // 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
+
37
+ const yargs = (await use('yargs@latest')).default;
38
+ const path = (await use('path')).default;
39
+ const fs = (await use('fs')).promises;
40
+ const { spawn } = (await use('child_process')).default;
41
+
42
+ // Import Claude execution functions
43
+ import { mapModelToId } from './claude.lib.mjs';
44
+
45
+ // Global log file reference
46
+ let logFile = null;
47
+
48
+ // Helper function to log to both console and file
49
+ const log = async (message, options = {}) => {
50
+ const { level = 'info', verbose = false } = options;
51
+
52
+ // Skip verbose logs unless --verbose is enabled
53
+ if (verbose && !global.verboseMode) {
54
+ return;
55
+ }
56
+
57
+ // Write to file if log file is set
58
+ if (logFile) {
59
+ const logMessage = `[${new Date().toISOString()}] [${level.toUpperCase()}] ${message}`;
60
+ await fs.appendFile(logFile, logMessage + '\n').catch(() => {});
61
+ }
62
+
63
+ // Write to console based on level
64
+ switch (level) {
65
+ case 'error':
66
+ console.error(message);
67
+ break;
68
+ case 'warning':
69
+ case 'warn':
70
+ console.warn(message);
71
+ break;
72
+ case 'info':
73
+ default:
74
+ console.log(message);
75
+ break;
76
+ }
77
+ };
78
+
79
+ // Configure command line arguments - task description as positional argument
80
+ // Use yargs().parse(args) instead of yargs(args).argv to ensure .strict() mode works
81
+ const argv = yargs()
82
+ .usage('Usage: $0 <task-description> [options]')
83
+ .positional('task-description', {
84
+ type: 'string',
85
+ description: 'The task to clarify and decompose'
86
+ })
87
+ .option('clarify', {
88
+ type: 'boolean',
89
+ description: 'Enable clarification mode (asks clarifying questions about the task)',
90
+ default: true
91
+ })
92
+ .option('decompose', {
93
+ type: 'boolean',
94
+ description: 'Enable decomposition mode (breaks down the task into subtasks)',
95
+ default: true
96
+ })
97
+ .option('only-clarify', {
98
+ type: 'boolean',
99
+ description: 'Only run clarification mode, skip decomposition',
100
+ default: false
101
+ })
102
+ .option('only-decompose', {
103
+ type: 'boolean',
104
+ description: 'Only run decomposition mode, skip clarification',
105
+ default: false
106
+ })
107
+ .option('model', {
108
+ type: 'string',
109
+ description: 'Model to use (opus, sonnet, or full model ID like claude-sonnet-4-5-20250929)',
110
+ alias: 'm',
111
+ default: 'sonnet',
112
+ choices: ['opus', 'sonnet', 'claude-sonnet-4-5-20250929', 'claude-opus-4-5-20251101']
113
+ })
114
+ .option('verbose', {
115
+ type: 'boolean',
116
+ description: 'Enable verbose logging for debugging',
117
+ alias: 'v',
118
+ default: false
119
+ })
120
+ .option('output-format', {
121
+ type: 'string',
122
+ description: 'Output format (text or json)',
123
+ alias: 'o',
124
+ default: 'text',
125
+ choices: ['text', 'json']
126
+ })
127
+ .check((argv) => {
128
+ if (!argv['task-description'] && !argv._[0]) {
129
+ throw new Error('Please provide a task description');
130
+ }
131
+
132
+ // Handle mutual exclusivity of only-clarify and only-decompose
133
+ if (argv['only-clarify'] && argv['only-decompose']) {
134
+ throw new Error('Cannot use both --only-clarify and --only-decompose at the same time');
135
+ }
136
+
137
+ // If only-clarify is set, disable decompose
138
+ if (argv['only-clarify']) {
139
+ argv.decompose = false;
140
+ }
141
+
142
+ // If only-decompose is set, disable clarify
143
+ if (argv['only-decompose']) {
144
+ argv.clarify = false;
145
+ }
146
+
147
+ return true;
148
+ })
149
+ .parserConfiguration({
150
+ 'boolean-negation': true
151
+ })
152
+ .help()
153
+ .alias('h', 'help')
154
+ // Use yargs built-in strict mode to reject unrecognized options
155
+ // This prevents issues like #453 and #482 where unknown options are silently ignored
156
+ .strict()
157
+ .parse(process.argv.slice(2));
158
+
159
+ const taskDescription = argv['task-description'] || argv._[0];
160
+
161
+ // Set global verbose mode for log function
162
+ global.verboseMode = argv.verbose;
163
+
164
+ // Create permanent log file immediately with timestamp
165
+ const scriptDir = path.dirname(process.argv[1]);
166
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
167
+ logFile = path.join(scriptDir, `task-${timestamp}.log`);
168
+
169
+ // Create the log file immediately
170
+ await fs.writeFile(logFile, `# Task.mjs Log - ${new Date().toISOString()}\n\n`);
171
+ await log(`šŸ“ Log file: ${logFile}`);
172
+ await log(' (All output will be logged here)');
173
+
174
+ // Helper function to format aligned console output
175
+ const formatAligned = (icon, label, value, indent = 0) => {
176
+ const spaces = ' '.repeat(indent);
177
+ const labelWidth = 25 - indent;
178
+ const paddedLabel = label.padEnd(labelWidth, ' ');
179
+ return `${spaces}${icon} ${paddedLabel} ${value || ''}`;
180
+ };
181
+
182
+ await log('\nšŸŽÆ Task Processing Started');
183
+ await log(formatAligned('šŸ“', 'Task description:', taskDescription));
184
+ await log(formatAligned('šŸ¤–', 'Model:', argv.model));
185
+ await log(formatAligned('šŸ’”', 'Clarify mode:', argv.clarify ? 'enabled' : 'disabled'));
186
+ await log(formatAligned('šŸ”', 'Decompose mode:', argv.decompose ? 'enabled' : 'disabled'));
187
+ await log(formatAligned('šŸ“„', 'Output format:', argv.outputFormat));
188
+
189
+ const claudePath = process.env.CLAUDE_PATH || 'claude';
190
+
191
+ // Helper function to execute Claude command
192
+ const executeClaude = (prompt, model) => {
193
+ return new Promise((resolve, reject) => {
194
+ // Map model alias to full ID
195
+ const mappedModel = mapModelToId(model);
196
+
197
+ const args = [
198
+ '-p', prompt,
199
+ '--output-format', 'text',
200
+ '--dangerously-skip-permissions',
201
+ '--model', mappedModel
202
+ ];
203
+
204
+ const child = spawn(claudePath, args, {
205
+ stdio: ['ignore', 'pipe', 'pipe'],
206
+ env: process.env
207
+ });
208
+
209
+ let stdout = '';
210
+ let stderr = '';
211
+
212
+ child.stdout.on('data', (data) => {
213
+ stdout += data.toString();
214
+ });
215
+
216
+ child.stderr.on('data', (data) => {
217
+ stderr += data.toString();
218
+ });
219
+
220
+ child.on('close', (code) => {
221
+ if (code === 0) {
222
+ resolve(stdout.trim());
223
+ } else {
224
+ reject(new Error(`Claude exited with code ${code}: ${stderr}`));
225
+ }
226
+ });
227
+
228
+ child.on('error', (error) => {
229
+ reject(error);
230
+ });
231
+ });
232
+ };
233
+
234
+ try {
235
+ const results = {
236
+ task: taskDescription,
237
+ timestamp: new Date().toISOString(),
238
+ clarification: null,
239
+ decomposition: null
240
+ };
241
+
242
+ // Phase 1: Clarification
243
+ if (argv.clarify) {
244
+ await log('\nšŸ¤” Phase 1: Task Clarification');
245
+ await log(' Analyzing task and generating clarifying questions...');
246
+
247
+ const clarifyPrompt = `Task: "${taskDescription}"
248
+
249
+ Please help clarify this task by:
250
+ 1. Identifying any ambiguous aspects of the task
251
+ 2. Asking 3-5 specific clarifying questions that would help someone implement this task more effectively
252
+ 3. Suggesting potential assumptions that could be made if these questions aren't answered
253
+ 4. Identifying any missing context or requirements
254
+
255
+ Provide your response in a clear, structured format that helps refine the task understanding.`;
256
+
257
+ const clarificationOutput = await executeClaude(clarifyPrompt, argv.model);
258
+ if (!argv.verbose) {
259
+ console.log('\nšŸ“ Clarification Results:');
260
+ console.log(clarificationOutput);
261
+ }
262
+
263
+ results.clarification = clarificationOutput;
264
+ await log('\nāœ… Clarification phase completed');
265
+ }
266
+
267
+ // Phase 2: Decomposition
268
+ if (argv.decompose) {
269
+ await log('\nšŸ” Phase 2: Task Decomposition');
270
+ await log(' Breaking down task into manageable subtasks...');
271
+
272
+ let decomposePrompt = `Task: "${taskDescription}"`;
273
+
274
+ if (results.clarification) {
275
+ decomposePrompt += `\n\nClarification analysis:\n${results.clarification}`;
276
+ }
277
+
278
+ decomposePrompt += `\n\nPlease decompose this task by:
279
+ 1. Breaking it down into 3-8 specific, actionable subtasks
280
+ 2. Ordering the subtasks logically (dependencies and sequence)
281
+ 3. Estimating relative complexity/effort for each subtask (simple/medium/complex)
282
+ 4. Identifying any potential risks or challenges for each subtask
283
+ 5. Suggesting success criteria for each subtask
284
+
285
+ Provide your response as a structured breakdown that someone could use as a implementation roadmap.`;
286
+
287
+ const decompositionOutput = await executeClaude(decomposePrompt, argv.model);
288
+ if (!argv.verbose) {
289
+ console.log('\nšŸ” Decomposition Results:');
290
+ console.log(decompositionOutput);
291
+ }
292
+
293
+ results.decomposition = decompositionOutput;
294
+ await log('\nāœ… Decomposition phase completed');
295
+ }
296
+
297
+ // Output results
298
+ if (argv.outputFormat === 'json') {
299
+ console.log('\n' + JSON.stringify(results, null, 2));
300
+ }
301
+
302
+ await log('\nšŸŽ‰ Task processing completed successfully');
303
+ await log(`šŸ’” Review the session log for details: ${logFile}`);
304
+
305
+ } catch (error) {
306
+ await log(`āŒ Error processing task: ${error.message}`, { level: 'error' });
307
+ process.exit(1);
308
+ }