stigmergy 1.2.12 → 1.3.2-beta.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 (48) hide show
  1. package/README.md +39 -3
  2. package/STIGMERGY.md +3 -0
  3. package/config/enhanced-cli-config.json +438 -0
  4. package/docs/CLI_TOOLS_AGENT_SKILL_ANALYSIS.md +463 -0
  5. package/docs/ENHANCED_CLI_AGENT_SKILL_CONFIG.md +285 -0
  6. package/docs/INSTALLER_ARCHITECTURE.md +257 -0
  7. package/docs/SUDO_PROBLEM_AND_SOLUTION.md +529 -0
  8. package/package.json +14 -5
  9. package/scripts/analyze-router.js +168 -0
  10. package/scripts/test-runner.js +344 -0
  11. package/src/cli/commands/autoinstall.js +158 -0
  12. package/src/cli/commands/errors.js +190 -0
  13. package/src/cli/commands/install.js +142 -0
  14. package/src/cli/commands/permissions.js +108 -0
  15. package/src/cli/commands/project.js +449 -0
  16. package/src/cli/commands/resume.js +136 -0
  17. package/src/cli/commands/scan.js +97 -0
  18. package/src/cli/commands/skills.js +158 -0
  19. package/src/cli/commands/status.js +106 -0
  20. package/src/cli/commands/system.js +301 -0
  21. package/src/cli/router-beta.js +477 -0
  22. package/src/cli/utils/environment.js +75 -0
  23. package/src/cli/utils/formatters.js +47 -0
  24. package/src/cli/utils/skills_cache.js +92 -0
  25. package/src/core/cache_cleaner.js +1 -0
  26. package/src/core/cli_adapters.js +345 -0
  27. package/src/core/cli_help_analyzer.js +473 -1
  28. package/src/core/cli_path_detector.js +2 -1
  29. package/src/core/cli_tools.js +107 -0
  30. package/src/core/coordination/nodejs/HookDeploymentManager.js +185 -422
  31. package/src/core/coordination/nodejs/HookDeploymentManager.refactored.js +323 -0
  32. package/src/core/coordination/nodejs/generators/CLIAdapterGenerator.js +363 -0
  33. package/src/core/coordination/nodejs/generators/ResumeSessionGenerator.js +701 -0
  34. package/src/core/coordination/nodejs/generators/SkillsIntegrationGenerator.js +1210 -0
  35. package/src/core/coordination/nodejs/generators/index.js +12 -0
  36. package/src/core/enhanced_cli_installer.js +220 -30
  37. package/src/core/enhanced_cli_parameter_handler.js +395 -0
  38. package/src/core/execution_mode_detector.js +222 -0
  39. package/src/core/installer.js +51 -70
  40. package/src/core/local_skill_scanner.js +732 -0
  41. package/src/core/multilingual/language-pattern-manager.js +1 -1
  42. package/src/core/skills/StigmergySkillManager.js +26 -8
  43. package/src/core/smart_router.js +279 -2
  44. package/src/index.js +10 -4
  45. package/test/cli-integration.test.js +304 -0
  46. package/test/enhanced-cli-agent-skill-test.js +485 -0
  47. package/test/specific-cli-agent-skill-analysis.js +385 -0
  48. package/src/cli/router.js +0 -1737
@@ -0,0 +1,395 @@
1
+ /**
2
+ * Enhanced CLI Parameter Handler with Agent/Skill Support and Multi-Mode Retry
3
+ *
4
+ * This module extends the basic CLI parameter handling with:
5
+ * 1. TWO-STAGE agent and skill detection (fast pre-check + detailed matching)
6
+ * 2. Multiple parameter format support with retry mechanism
7
+ * 3. Fallback strategies for different CLI tools
8
+ *
9
+ * OPTIMIZATION: Stage 1 (quickDetectMention) avoids cache I/O if no keywords found
10
+ */
11
+
12
+ const CLIHelpAnalyzer = require('./cli_help_analyzer');
13
+ const { CLI_TOOLS } = require('./cli_tools');
14
+ const LocalSkillScanner = require('./local_skill_scanner');
15
+
16
+ class EnhancedCLIParameterHandler {
17
+ constructor() {
18
+ this.analyzer = new CLIHelpAnalyzer();
19
+ this.skillScanner = new LocalSkillScanner();
20
+ this.retryHistory = new Map(); // Track failed parameter formats
21
+ }
22
+
23
+ /**
24
+ * Initialize the analyzer
25
+ */
26
+ async initialize() {
27
+ await this.analyzer.initialize();
28
+ // Note: skillScanner is initialized lazily (only when needed)
29
+ }
30
+
31
+ /**
32
+ * Generate optimized arguments with agent/skill support and retry capability
33
+ *
34
+ * OPTIMIZED FLOW:
35
+ * 1. Stage 1: Quick keyword detection (<1ms, no I/O)
36
+ * - No keywords? Skip all skill/agent processing
37
+ * - Has keywords? Proceed to Stage 2
38
+ * 2. Stage 2: Load cache and detailed matching
39
+ * - Load skills/agents from cache
40
+ * - Perform precise matching
41
+ * 3. Generate parameters based on CLI type
42
+ *
43
+ * @param {string} toolName - Name of the CLI tool
44
+ * @param {string} prompt - User prompt
45
+ * @param {Object} options - Options
46
+ * @param {number} options.maxRetries - Maximum number of format retries (default: 3)
47
+ * @param {boolean} options.enableAgentSkillOptimization - Enable agent/skill optimization (default: true)
48
+ * @param {Array} options.preferredFormats - Preferred parameter formats to try first
49
+ * @returns {Promise<Object>} Result with arguments, format used, and optimization info
50
+ */
51
+ async generateArgumentsWithRetry(toolName, prompt, options = {}) {
52
+ const {
53
+ maxRetries = 3,
54
+ enableAgentSkillOptimization = true,
55
+ preferredFormats = null
56
+ } = options;
57
+
58
+ await this.initialize();
59
+
60
+ // === STAGE 1: Quick Pre-Check (Fast, No I/O) ===
61
+ let detectedMentions = { hasAgent: false, hasSkill: false, confidence: 0 };
62
+ let optimizationApplied = false;
63
+ let optimizedPrompt = prompt;
64
+ let skillMatches = [];
65
+ let agentMatches = [];
66
+
67
+ if (enableAgentSkillOptimization) {
68
+ // Fast keyword detection (<1ms)
69
+ const quickDetection = this.skillScanner.quickDetectMention(prompt);
70
+
71
+ if (!quickDetection.shouldLoadCache) {
72
+ // No agent/skill keywords detected - skip all skill/agent processing
73
+ if (process.env.DEBUG === 'true') {
74
+ console.log(`[AGENT/SKILL] No keywords detected, skipping skill/agent processing`);
75
+ }
76
+ } else {
77
+ // Keywords detected - proceed to Stage 2
78
+ if (process.env.DEBUG === 'true') {
79
+ console.log(`[AGENT/SKILL] Stage 1: Keywords detected (agent=${quickDetection.hasAgentKeyword}, skill=${quickDetection.hasSkillKeyword})`);
80
+ }
81
+
82
+ // === STAGE 2: Load Cache and Detailed Matching ===
83
+ await this.skillScanner.initialize(); // Load cache
84
+
85
+ // Get detailed matches using CLIHelpAnalyzer
86
+ detectedMentions = this.analyzer.detectAgentSkillMentions(prompt, toolName);
87
+
88
+ // Get local skill/agent matches
89
+ skillMatches = this.skillScanner.matchSkills(prompt, toolName);
90
+ agentMatches = this.skillScanner.matchAgents(prompt, toolName);
91
+
92
+ // Combine detection results
93
+ if (skillMatches.length > 0 || agentMatches.length > 0) {
94
+ detectedMentions.hasSkill = detectedMentions.hasSkill || skillMatches.length > 0;
95
+ detectedMentions.hasAgent = detectedMentions.hasAgent || agentMatches.length > 0;
96
+ detectedMentions.confidence = Math.min(1.0, detectedMentions.confidence + 0.2);
97
+ }
98
+
99
+ // Optimize prompt if matches found
100
+ if (detectedMentions.hasAgent || detectedMentions.hasSkill) {
101
+ optimizedPrompt = this.analyzer.optimizePromptForCLI(prompt, toolName, detectedMentions);
102
+ optimizationApplied = (optimizedPrompt !== prompt);
103
+
104
+ if (process.env.DEBUG === 'true') {
105
+ console.log(`[AGENT/SKILL] Stage 2: Detailed matching complete`);
106
+ console.log(`[AGENT/SKILL] Skill matches: ${skillMatches.length}, Agent matches: ${agentMatches.length}`);
107
+ if (optimizationApplied) {
108
+ console.log(`[AGENT/SKILL] Original: ${prompt}`);
109
+ console.log(`[AGENT/SKILL] Optimized: ${optimizedPrompt}`);
110
+ }
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ // Step 3: Get parameter formats to try (in priority order)
117
+ const formatsToTry = await this.getParameterFormats(toolName, preferredFormats, detectedMentions);
118
+
119
+ // Step 4: Try each format until one succeeds (or return first for non-execution context)
120
+ const results = {
121
+ toolName,
122
+ originalPrompt: prompt,
123
+ optimizedPrompt,
124
+ optimizationApplied,
125
+ detectedMentions,
126
+ formats: []
127
+ };
128
+
129
+ for (let i = 0; i < Math.min(formatsToTry.length, maxRetries); i++) {
130
+ const format = formatsToTry[i];
131
+ const args = this.generateArgumentsForFormat(toolName, optimizedPrompt, format);
132
+
133
+ results.formats.push({
134
+ format: format.name,
135
+ args,
136
+ priority: format.priority,
137
+ attempted: true
138
+ });
139
+
140
+ // For non-execution context, return all formats
141
+ // For execution, the caller should handle retry
142
+ }
143
+
144
+ // Select the best format based on priority and detection
145
+ const selectedFormat = this.selectBestFormat(results.formats, detectedMentions);
146
+ results.selectedFormat = selectedFormat;
147
+ results.arguments = selectedFormat.args;
148
+
149
+ return results;
150
+ }
151
+
152
+ /**
153
+ * Get parameter formats to try for a specific tool
154
+ *
155
+ * @param {string} toolName - Name of the CLI tool
156
+ * @param {Array} preferredFormats - User-preferred formats
157
+ * @param {Object} detectedMentions - Detected agent/skill mentions
158
+ * @returns {Promise<Array>} Array of format objects to try
159
+ */
160
+ async getParameterFormats(toolName, preferredFormats, detectedMentions) {
161
+ const formats = [];
162
+
163
+ // Get CLI pattern from analyzer
164
+ const cliPattern = await this.analyzer.getCLIPattern(toolName);
165
+ const enhancedPattern = this.analyzer.enhancedPatterns[toolName];
166
+
167
+ if (process.env.DEBUG === 'true') {
168
+ console.log(`[PARAM_FORMAT] Getting formats for ${toolName}`);
169
+ console.log(`[PARAM_FORMAT] cliPattern exists: ${!!cliPattern}`);
170
+ if (cliPattern && cliPattern.commandStructure) {
171
+ console.log(`[PARAM_FORMAT] executionPattern: ${cliPattern.commandStructure.executionPattern}`);
172
+ console.log(`[PARAM_FORMAT] nonInteractiveFlag: ${cliPattern.commandStructure.nonInteractiveFlag}`);
173
+ }
174
+ }
175
+
176
+ // Define format priority based on tool characteristics
177
+ if (enhancedPattern) {
178
+ // Use enhanced pattern information
179
+ if (enhancedPattern.positionalArgs) {
180
+ // Check if CLI has flag-based non-interactive execution
181
+ // For tools like Kode that have flag-based execution, don't use positional args
182
+ const hasFlagBasedExecution = cliPattern?.commandStructure?.executionPattern === 'flag-based' &&
183
+ cliPattern?.commandStructure?.nonInteractiveFlag;
184
+
185
+ if (!hasFlagBasedExecution) {
186
+ // Qwen, Copilot, etc. support positional arguments
187
+ formats.push({
188
+ name: 'positional',
189
+ priority: enhancedPattern.positionalArgs ? 10 : 5,
190
+ description: 'Positional arguments (natural language)',
191
+ template: (prompt) => [prompt]
192
+ });
193
+ }
194
+ }
195
+
196
+ if (enhancedPattern.naturalLanguageSupport) {
197
+ // CLIs with good natural language support
198
+ formats.push({
199
+ name: 'prompt-flag',
200
+ priority: 8,
201
+ description: 'Standard -p flag',
202
+ template: (prompt) => ['-p', `"${prompt}"`]
203
+ });
204
+ }
205
+
206
+ if (enhancedPattern.skillPrefixRequired) {
207
+ // CodeBuddy requires skill: prefix
208
+ formats.push({
209
+ name: 'skill-prefix',
210
+ priority: 9,
211
+ description: 'Skill prefix with -y flag',
212
+ template: (prompt) => ['-y', '-p', `"${prompt}"`]
213
+ });
214
+ }
215
+ }
216
+
217
+ // Add standard formats based on CLI pattern analysis
218
+ if (cliPattern && cliPattern.commandStructure) {
219
+ const structure = cliPattern.commandStructure;
220
+
221
+ if (structure.promptFlag) {
222
+ formats.push({
223
+ name: 'detected-prompt-flag',
224
+ priority: 7,
225
+ description: `Detected prompt flag: ${structure.promptFlag}`,
226
+ template: (prompt) => [structure.promptFlag, `"${prompt}"`]
227
+ });
228
+ }
229
+
230
+ if (structure.executionPattern === 'argument-based') {
231
+ formats.push({
232
+ name: 'argument-based',
233
+ priority: 6,
234
+ description: 'Argument-based execution',
235
+ template: (prompt) => [`"${prompt}"`]
236
+ });
237
+ }
238
+
239
+ if (structure.executionPattern === 'subcommand-based') {
240
+ // For tools like Codex that need subcommand
241
+ formats.push({
242
+ name: 'subcommand-exec',
243
+ priority: 7,
244
+ description: 'Subcommand-based with exec',
245
+ template: (prompt) => ['exec', '-p', `"${prompt}"`]
246
+ });
247
+ }
248
+
249
+ if (structure.executionPattern === 'flag-based') {
250
+ // For tools like Kode that use flag-based execution (e.g., --print)
251
+ if (structure.nonInteractiveFlag) {
252
+ formats.push({
253
+ name: 'flag-based-non-interactive',
254
+ priority: 9,
255
+ description: `Flag-based non-interactive: ${structure.nonInteractiveFlag}`,
256
+ template: (prompt) => [structure.nonInteractiveFlag, `"${prompt}"`]
257
+ });
258
+ }
259
+ }
260
+ }
261
+
262
+ // Add fallback formats
263
+ formats.push({
264
+ name: 'standard-p-flag',
265
+ priority: 4,
266
+ description: 'Standard -p flag (fallback)',
267
+ template: (prompt) => ['-p', `"${prompt}"`]
268
+ });
269
+
270
+ // Filter out failed formats from retry history
271
+ const failedFormats = this.retryHistory.get(toolName) || new Set();
272
+ const validFormats = formats.filter(f => !failedFormats.has(f.name));
273
+
274
+ if (process.env.DEBUG === 'true') {
275
+ console.log(`[PARAM_FORMAT] Total formats: ${formats.length}`);
276
+ formats.forEach(f => {
277
+ console.log(`[PARAM_FORMAT] - ${f.name} (priority: ${f.priority}): ${f.description}`);
278
+ });
279
+ console.log(`[PARAM_FORMAT] Valid formats: ${validFormats.length}`);
280
+ }
281
+
282
+ // If all formats failed, clear history and try again
283
+ if (validFormats.length === 0) {
284
+ if (process.env.DEBUG === 'true') {
285
+ console.log(`[RETRY] All formats failed for ${toolName}, clearing retry history`);
286
+ }
287
+ this.retryHistory.delete(toolName);
288
+ return formats;
289
+ }
290
+
291
+ // Sort by priority (descending)
292
+ return validFormats.sort((a, b) => b.priority - a.priority);
293
+ }
294
+
295
+ /**
296
+ * Generate arguments for a specific format
297
+ *
298
+ * @param {string} toolName - Name of the CLI tool
299
+ * @param {string} prompt - Optimized prompt
300
+ * @param {Object} format - Format object
301
+ * @returns {Array} Arguments array
302
+ */
303
+ generateArgumentsForFormat(toolName, prompt, format) {
304
+ try {
305
+ return format.template(prompt);
306
+ } catch (error) {
307
+ if (process.env.DEBUG === 'true') {
308
+ console.log(`[ERROR] Failed to generate args for format ${format.name}:`, error.message);
309
+ }
310
+ // Fallback to standard format
311
+ return ['-p', `"${prompt}"`];
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Select the best format from available options
317
+ *
318
+ * @param {Array} formats - Available formats
319
+ * @param {Object} detectedMentions - Detected agent/skill mentions
320
+ * @returns {Object} Selected format
321
+ */
322
+ selectBestFormat(formats, detectedMentions) {
323
+ // Prioritize formats based on detection
324
+ if (detectedMentions.hasSkill || detectedMentions.hasAgent) {
325
+ // For agent/skill requests, prefer formats with higher priority
326
+ const skillOptimizedFormats = formats.filter(f => f.priority >= 8);
327
+ if (skillOptimizedFormats.length > 0) {
328
+ return skillOptimizedFormats[0];
329
+ }
330
+ }
331
+
332
+ // Return highest priority format
333
+ return formats[0] || {
334
+ name: 'fallback',
335
+ priority: 1,
336
+ args: ['-p', '"{prompt}"'],
337
+ description: 'Fallback format'
338
+ };
339
+ }
340
+
341
+ /**
342
+ * Record a failed format for a tool
343
+ *
344
+ * @param {string} toolName - Name of the CLI tool
345
+ * @param {string} formatName - Name of the failed format
346
+ */
347
+ recordFailedFormat(toolName, formatName) {
348
+ if (!this.retryHistory.has(toolName)) {
349
+ this.retryHistory.set(toolName, new Set());
350
+ }
351
+ this.retryHistory.get(toolName).add(formatName);
352
+
353
+ if (process.env.DEBUG === 'true') {
354
+ console.log(`[RETRY] Recorded failed format ${formatName} for ${toolName}`);
355
+ console.log(`[RETRY] Failed formats:`, Array.from(this.retryHistory.get(toolName)));
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Clear retry history for a tool
361
+ *
362
+ * @param {string} toolName - Name of the CLI tool
363
+ */
364
+ clearRetryHistory(toolName) {
365
+ this.retryHistory.delete(toolName);
366
+
367
+ if (process.env.DEBUG === 'true') {
368
+ console.log(`[RETRY] Cleared retry history for ${toolName}`);
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Get compatibility score for a tool with the given prompt
374
+ *
375
+ * @param {string} toolName - Name of the CLI tool
376
+ * @param {string} prompt - User prompt
377
+ * @returns {Object} Compatibility score and reasons
378
+ */
379
+ getCompatibilityScore(toolName, prompt) {
380
+ return this.analyzer.getAgentSkillCompatibilityScore(toolName, prompt);
381
+ }
382
+
383
+ /**
384
+ * Generate optimized call command for a tool
385
+ *
386
+ * @param {string} toolName - Name of the CLI tool
387
+ * @param {string} prompt - User prompt
388
+ * @returns {Object|null} Optimized call command
389
+ */
390
+ generateOptimizedCall(toolName, prompt) {
391
+ return this.analyzer.generateOptimizedCall(toolName, prompt);
392
+ }
393
+ }
394
+
395
+ module.exports = EnhancedCLIParameterHandler;
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Execution Mode Detector
3
+ * Intelligently detects whether to run in interactive or one-time mode
4
+ * based on execution context and user preferences
5
+ *
6
+ * Modes:
7
+ * - 'interactive': Keep CLI tool running for continuous conversation
8
+ * - 'one-time': Execute prompt and exit (return control to caller)
9
+ */
10
+
11
+ class ExecutionModeDetector {
12
+ constructor() {
13
+ // Cached detection result
14
+ this._cachedMode = null;
15
+ }
16
+
17
+ /**
18
+ * Detect the appropriate execution mode
19
+ * @param {Object} options - Detection options
20
+ * @param {boolean} options.interactive - Force interactive mode
21
+ * @param {boolean} options.print - Force one-time (print) mode
22
+ * @param {boolean} options.verbose - Enable verbose output
23
+ * @returns {string} 'interactive' or 'one-time'
24
+ */
25
+ detect(options = {}) {
26
+ // Return cached result if available
27
+ if (this._cachedMode && !options.interactive && !options.print) {
28
+ return this._cachedMode;
29
+ }
30
+
31
+ const mode = this._detectMode(options);
32
+
33
+ // Cache the result if not forced
34
+ if (!options.interactive && !options.print) {
35
+ this._cachedMode = mode;
36
+ }
37
+
38
+ return mode;
39
+ }
40
+
41
+ /**
42
+ * Internal detection logic
43
+ * @private
44
+ */
45
+ _detectMode(options) {
46
+ const verbose = options.verbose || process.env.DEBUG === 'true';
47
+
48
+ // Priority 1: Explicit user flags (highest priority)
49
+ if (options.interactive) {
50
+ if (verbose) {
51
+ console.log('[MODE] Interactive mode forced by --interactive flag');
52
+ }
53
+ return 'interactive';
54
+ }
55
+
56
+ if (options.print) {
57
+ if (verbose) {
58
+ console.log('[MODE] One-time mode forced by --print flag');
59
+ }
60
+ return 'one-time';
61
+ }
62
+
63
+ // Priority 2: Environment variable
64
+ const envMode = process.env.STIGMERGY_MODE;
65
+ if (envMode) {
66
+ if (envMode === 'interactive') {
67
+ if (verbose) {
68
+ console.log('[MODE] Interactive mode from STIGMERGY_MODE environment variable');
69
+ }
70
+ return 'interactive';
71
+ }
72
+ if (envMode === 'one-time' || envMode === 'onetime') {
73
+ if (verbose) {
74
+ console.log('[MODE] One-time mode from STIGMERGY_MODE environment variable');
75
+ }
76
+ return 'one-time';
77
+ }
78
+ }
79
+
80
+ // Priority 3: Intelligent context detection
81
+ const context = this._analyzeContext();
82
+
83
+ if (verbose) {
84
+ console.log('[MODE] Context Analysis:');
85
+ console.log(` - stdin.isTTY: ${context.stdinIsTTY}`);
86
+ console.log(` - stdout.isTTY: ${context.stdoutIsTTY}`);
87
+ console.log(` - hasPipedInput: ${context.hasPipedInput}`);
88
+ console.log(` - isFromCI: ${context.isFromCI}`);
89
+ console.log(` - isFromScript: ${context.isFromScript}`);
90
+ }
91
+
92
+ // Decision logic
93
+ const isInteractiveTerminal = context.stdinIsTTY && context.stdoutIsTTY;
94
+
95
+ if (isInteractiveTerminal && !context.isFromCI && !context.isFromScript) {
96
+ if (verbose) {
97
+ console.log('[MODE] Detected interactive terminal → Interactive mode');
98
+ }
99
+ return 'interactive';
100
+ }
101
+
102
+ // All other cases: use one-time mode
103
+ if (verbose) {
104
+ console.log('[MODE] Detected non-interactive context → One-time mode');
105
+ }
106
+ return 'one-time';
107
+ }
108
+
109
+ /**
110
+ * Analyze execution context
111
+ * @private
112
+ */
113
+ _analyzeContext() {
114
+ const context = {
115
+ // Check if stdin is a terminal (interactive input)
116
+ stdinIsTTY: process.stdin.isTTY,
117
+
118
+ // Check if stdout is a terminal (interactive output)
119
+ stdoutIsTTY: process.stdout.isTTY,
120
+
121
+ // Check if there's piped input
122
+ hasPipedInput: !process.stdin.isTTY,
123
+
124
+ // Check if running in CI environment
125
+ isFromCI: this._isCIEnvironment(),
126
+
127
+ // Check if running from a script
128
+ isFromScript: this._isRunningFromScript(),
129
+
130
+ // Get parent process name
131
+ parentProcess: this._getParentProcessName()
132
+ };
133
+
134
+ return context;
135
+ }
136
+
137
+ /**
138
+ * Check if running in CI environment
139
+ * @private
140
+ */
141
+ _isCIEnvironment() {
142
+ // Common CI environment variables
143
+ const ciVars = [
144
+ 'CI',
145
+ 'CONTINUOUS_INTEGRATION',
146
+ 'JENKINS_URL',
147
+ 'BUILD_NUMBER',
148
+ 'GITHUB_ACTIONS',
149
+ 'GITLAB_CI',
150
+ 'TRAVIS',
151
+ 'CIRCLECI',
152
+ 'APPVEYOR',
153
+ 'TEAMCITY_VERSION'
154
+ ];
155
+
156
+ return ciVars.some(varName => process.env[varName]);
157
+ }
158
+
159
+ /**
160
+ * Check if running from a script
161
+ * @private
162
+ */
163
+ _isRunningFromScript() {
164
+ // Check if called from node script
165
+ const invokedAsScript = process.argv[1] &&
166
+ !process.argv[1].endsWith('stigmergy') &&
167
+ (process.argv[1].endsWith('.js') ||
168
+ process.argv[1].endsWith('.cjs') ||
169
+ process.argv[1].endsWith('.mjs'));
170
+
171
+ return invokedAsScript;
172
+ }
173
+
174
+ /**
175
+ * Get parent process name (if available)
176
+ * @private
177
+ */
178
+ _getParentProcessName() {
179
+ try {
180
+ // Try to get parent process info
181
+ if (process.ppid) {
182
+ return `PID:${process.ppid}`;
183
+ }
184
+ return null;
185
+ } catch (error) {
186
+ return null;
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Clear cached detection result
192
+ * Call this if execution context changes
193
+ */
194
+ clearCache() {
195
+ this._cachedMode = null;
196
+ }
197
+
198
+ /**
199
+ * Get human-readable mode description
200
+ * @param {string} mode - 'interactive' or 'one-time'
201
+ * @returns {string} Description
202
+ */
203
+ getModeDescription(mode) {
204
+ const descriptions = {
205
+ 'interactive': 'Continuous conversation mode (CLI tool stays running)',
206
+ 'one-time': 'Execute and exit mode (returns control after completion)'
207
+ };
208
+
209
+ return descriptions[mode] || 'Unknown mode';
210
+ }
211
+
212
+ /**
213
+ * Check if a specific mode is forced by user
214
+ * @param {Object} options - Detection options
215
+ * @returns {boolean} True if mode is forced
216
+ */
217
+ isModeForced(options) {
218
+ return !!(options.interactive || options.print);
219
+ }
220
+ }
221
+
222
+ module.exports = ExecutionModeDetector;