stigmergy 1.2.13 → 1.3.1

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 (88) hide show
  1. package/README.md +39 -3
  2. package/STIGMERGY.md +3 -0
  3. package/config/builtin-skills.json +43 -0
  4. package/config/enhanced-cli-config.json +438 -0
  5. package/docs/CLI_TOOLS_AGENT_SKILL_ANALYSIS.md +463 -0
  6. package/docs/DESIGN_CLI_HELP_ANALYZER_REFACTOR.md +726 -0
  7. package/docs/ENHANCED_CLI_AGENT_SKILL_CONFIG.md +285 -0
  8. package/docs/IMPLEMENTATION_CHECKLIST_CLI_HELP_ANALYZER_REFACTOR.md +1268 -0
  9. package/docs/INSTALLER_ARCHITECTURE.md +257 -0
  10. package/docs/LESSONS_LEARNED.md +252 -0
  11. package/docs/SPECS_CLI_HELP_ANALYZER_REFACTOR.md +287 -0
  12. package/docs/SUDO_PROBLEM_AND_SOLUTION.md +529 -0
  13. package/docs/correct-skillsio-implementation.md +368 -0
  14. package/docs/development_guidelines.md +276 -0
  15. package/docs/independent-resume-implementation.md +198 -0
  16. package/docs/resumesession-final-implementation.md +195 -0
  17. package/docs/resumesession-usage.md +87 -0
  18. package/package.json +146 -136
  19. package/scripts/analyze-router.js +168 -0
  20. package/scripts/run-comprehensive-tests.js +230 -0
  21. package/scripts/run-quick-tests.js +90 -0
  22. package/scripts/test-runner.js +344 -0
  23. package/skills/resumesession/INDEPENDENT_SKILL.md +403 -0
  24. package/skills/resumesession/README.md +381 -0
  25. package/skills/resumesession/SKILL.md +211 -0
  26. package/skills/resumesession/__init__.py +33 -0
  27. package/skills/resumesession/implementations/simple-resume.js +13 -0
  28. package/skills/resumesession/independent-resume.js +750 -0
  29. package/skills/resumesession/package.json +1 -0
  30. package/skills/resumesession/skill.json +1 -0
  31. package/src/adapters/claude/install_claude_integration.js +9 -1
  32. package/src/adapters/codebuddy/install_codebuddy_integration.js +3 -1
  33. package/src/adapters/codex/install_codex_integration.js +15 -5
  34. package/src/adapters/gemini/install_gemini_integration.js +3 -1
  35. package/src/adapters/qwen/install_qwen_integration.js +3 -1
  36. package/src/cli/commands/autoinstall.js +65 -0
  37. package/src/cli/commands/errors.js +190 -0
  38. package/src/cli/commands/independent-resume.js +395 -0
  39. package/src/cli/commands/install.js +179 -0
  40. package/src/cli/commands/permissions.js +108 -0
  41. package/src/cli/commands/project.js +485 -0
  42. package/src/cli/commands/scan.js +97 -0
  43. package/src/cli/commands/simple-resume.js +377 -0
  44. package/src/cli/commands/skills.js +158 -0
  45. package/src/cli/commands/status.js +113 -0
  46. package/src/cli/commands/stigmergy-resume.js +775 -0
  47. package/src/cli/commands/system.js +301 -0
  48. package/src/cli/commands/universal-resume.js +394 -0
  49. package/src/cli/router-beta.js +471 -0
  50. package/src/cli/utils/environment.js +75 -0
  51. package/src/cli/utils/formatters.js +47 -0
  52. package/src/cli/utils/skills_cache.js +92 -0
  53. package/src/core/cache_cleaner.js +1 -0
  54. package/src/core/cli_adapters.js +345 -0
  55. package/src/core/cli_help_analyzer.js +1236 -680
  56. package/src/core/cli_path_detector.js +702 -709
  57. package/src/core/cli_tools.js +515 -160
  58. package/src/core/coordination/nodejs/CLIIntegrationManager.js +18 -0
  59. package/src/core/coordination/nodejs/HookDeploymentManager.js +242 -412
  60. package/src/core/coordination/nodejs/HookDeploymentManager.refactored.js +323 -0
  61. package/src/core/coordination/nodejs/generators/CLIAdapterGenerator.js +363 -0
  62. package/src/core/coordination/nodejs/generators/ResumeSessionGenerator.js +932 -0
  63. package/src/core/coordination/nodejs/generators/SkillsIntegrationGenerator.js +1395 -0
  64. package/src/core/coordination/nodejs/generators/index.js +12 -0
  65. package/src/core/enhanced_cli_installer.js +1208 -608
  66. package/src/core/enhanced_cli_parameter_handler.js +402 -0
  67. package/src/core/execution_mode_detector.js +222 -0
  68. package/src/core/installer.js +151 -106
  69. package/src/core/local_skill_scanner.js +732 -0
  70. package/src/core/multilingual/language-pattern-manager.js +1 -1
  71. package/src/core/skills/BuiltinSkillsDeployer.js +188 -0
  72. package/src/core/skills/StigmergySkillManager.js +123 -16
  73. package/src/core/skills/embedded-openskills/SkillParser.js +7 -3
  74. package/src/core/smart_router.js +550 -261
  75. package/src/index.js +10 -4
  76. package/src/utils.js +66 -7
  77. package/test/cli-integration.test.js +304 -0
  78. package/test/direct_smart_router_test.js +88 -0
  79. package/test/enhanced-cli-agent-skill-test.js +485 -0
  80. package/test/simple_test.js +82 -0
  81. package/test/smart_router_test_runner.js +123 -0
  82. package/test/smart_routing_edge_cases.test.js +284 -0
  83. package/test/smart_routing_simple_verification.js +139 -0
  84. package/test/smart_routing_verification.test.js +346 -0
  85. package/test/specific-cli-agent-skill-analysis.js +385 -0
  86. package/test/unit/smart_router.test.js +295 -0
  87. package/test/very_simple_test.js +54 -0
  88. package/src/cli/router.js +0 -1783
@@ -0,0 +1,402 @@
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
+ * - CLI Help Analyzer 重构:REFACTORING_CLI_HELP_ANALYZER.md
13
+ *
14
+ * 🔗 依赖关系:
15
+ * - 依赖 CLIHelpAnalyzer 的 getCLIPattern() 方法
16
+ * - 重构后调用方式保持不变,完全向后兼容
17
+ */
18
+
19
+ const CLIHelpAnalyzer = require('./cli_help_analyzer');
20
+ const { CLI_TOOLS } = require('./cli_tools');
21
+ const LocalSkillScanner = require('./local_skill_scanner');
22
+
23
+ class EnhancedCLIParameterHandler {
24
+ constructor() {
25
+ this.analyzer = new CLIHelpAnalyzer();
26
+ this.skillScanner = new LocalSkillScanner();
27
+ this.retryHistory = new Map(); // Track failed parameter formats
28
+ }
29
+
30
+ /**
31
+ * Initialize the analyzer
32
+ */
33
+ async initialize() {
34
+ await this.analyzer.initialize();
35
+ // Note: skillScanner is initialized lazily (only when needed)
36
+ }
37
+
38
+ /**
39
+ * Generate optimized arguments with agent/skill support and retry capability
40
+ *
41
+ * OPTIMIZED FLOW:
42
+ * 1. Stage 1: Quick keyword detection (<1ms, no I/O)
43
+ * - No keywords? Skip all skill/agent processing
44
+ * - Has keywords? Proceed to Stage 2
45
+ * 2. Stage 2: Load cache and detailed matching
46
+ * - Load skills/agents from cache
47
+ * - Perform precise matching
48
+ * 3. Generate parameters based on CLI type
49
+ *
50
+ * @param {string} toolName - Name of the CLI tool
51
+ * @param {string} prompt - User prompt
52
+ * @param {Object} options - Options
53
+ * @param {number} options.maxRetries - Maximum number of format retries (default: 3)
54
+ * @param {boolean} options.enableAgentSkillOptimization - Enable agent/skill optimization (default: true)
55
+ * @param {Array} options.preferredFormats - Preferred parameter formats to try first
56
+ * @returns {Promise<Object>} Result with arguments, format used, and optimization info
57
+ */
58
+ async generateArgumentsWithRetry(toolName, prompt, options = {}) {
59
+ const {
60
+ maxRetries = 3,
61
+ enableAgentSkillOptimization = true,
62
+ preferredFormats = null
63
+ } = options;
64
+
65
+ await this.initialize();
66
+
67
+ // === STAGE 1: Quick Pre-Check (Fast, No I/O) ===
68
+ let detectedMentions = { hasAgent: false, hasSkill: false, confidence: 0 };
69
+ let optimizationApplied = false;
70
+ let optimizedPrompt = prompt;
71
+ let skillMatches = [];
72
+ let agentMatches = [];
73
+
74
+ if (enableAgentSkillOptimization) {
75
+ // Fast keyword detection (<1ms)
76
+ const quickDetection = this.skillScanner.quickDetectMention(prompt);
77
+
78
+ if (!quickDetection.shouldLoadCache) {
79
+ // No agent/skill keywords detected - skip all skill/agent processing
80
+ if (process.env.DEBUG === 'true') {
81
+ console.log(`[AGENT/SKILL] No keywords detected, skipping skill/agent processing`);
82
+ }
83
+ } else {
84
+ // Keywords detected - proceed to Stage 2
85
+ if (process.env.DEBUG === 'true') {
86
+ console.log(`[AGENT/SKILL] Stage 1: Keywords detected (agent=${quickDetection.hasAgentKeyword}, skill=${quickDetection.hasSkillKeyword})`);
87
+ }
88
+
89
+ // === STAGE 2: Load Cache and Detailed Matching ===
90
+ await this.skillScanner.initialize(); // Load cache
91
+
92
+ // Get detailed matches using CLIHelpAnalyzer
93
+ detectedMentions = this.analyzer.detectAgentSkillMentions(prompt, toolName);
94
+
95
+ // Get local skill/agent matches
96
+ skillMatches = this.skillScanner.matchSkills(prompt, toolName);
97
+ agentMatches = this.skillScanner.matchAgents(prompt, toolName);
98
+
99
+ // Combine detection results
100
+ if (skillMatches.length > 0 || agentMatches.length > 0) {
101
+ detectedMentions.hasSkill = detectedMentions.hasSkill || skillMatches.length > 0;
102
+ detectedMentions.hasAgent = detectedMentions.hasAgent || agentMatches.length > 0;
103
+ detectedMentions.confidence = Math.min(1.0, detectedMentions.confidence + 0.2);
104
+ }
105
+
106
+ // Optimize prompt if matches found
107
+ if (detectedMentions.hasAgent || detectedMentions.hasSkill) {
108
+ optimizedPrompt = this.analyzer.optimizePromptForCLI(prompt, toolName, detectedMentions);
109
+ optimizationApplied = (optimizedPrompt !== prompt);
110
+
111
+ if (process.env.DEBUG === 'true') {
112
+ console.log(`[AGENT/SKILL] Stage 2: Detailed matching complete`);
113
+ console.log(`[AGENT/SKILL] Skill matches: ${skillMatches.length}, Agent matches: ${agentMatches.length}`);
114
+ if (optimizationApplied) {
115
+ console.log(`[AGENT/SKILL] Original: ${prompt}`);
116
+ console.log(`[AGENT/SKILL] Optimized: ${optimizedPrompt}`);
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ // Step 3: Get parameter formats to try (in priority order)
124
+ const formatsToTry = await this.getParameterFormats(toolName, preferredFormats, detectedMentions);
125
+
126
+ // Step 4: Try each format until one succeeds (or return first for non-execution context)
127
+ const results = {
128
+ toolName,
129
+ originalPrompt: prompt,
130
+ optimizedPrompt,
131
+ optimizationApplied,
132
+ detectedMentions,
133
+ formats: []
134
+ };
135
+
136
+ for (let i = 0; i < Math.min(formatsToTry.length, maxRetries); i++) {
137
+ const format = formatsToTry[i];
138
+ const args = this.generateArgumentsForFormat(toolName, optimizedPrompt, format);
139
+
140
+ results.formats.push({
141
+ format: format.name,
142
+ args,
143
+ priority: format.priority,
144
+ attempted: true
145
+ });
146
+
147
+ // For non-execution context, return all formats
148
+ // For execution, the caller should handle retry
149
+ }
150
+
151
+ // Select the best format based on priority and detection
152
+ const selectedFormat = this.selectBestFormat(results.formats, detectedMentions);
153
+ results.selectedFormat = selectedFormat;
154
+ results.arguments = selectedFormat.args;
155
+
156
+ return results;
157
+ }
158
+
159
+ /**
160
+ * Get parameter formats to try for a specific tool
161
+ *
162
+ * @param {string} toolName - Name of the CLI tool
163
+ * @param {Array} preferredFormats - User-preferred formats
164
+ * @param {Object} detectedMentions - Detected agent/skill mentions
165
+ * @returns {Promise<Array>} Array of format objects to try
166
+ */
167
+ async getParameterFormats(toolName, preferredFormats, detectedMentions) {
168
+ const formats = [];
169
+
170
+ // Get CLI pattern from analyzer
171
+ const cliPattern = await this.analyzer.getCLIPattern(toolName);
172
+ const enhancedPattern = this.analyzer.enhancedPatterns[toolName];
173
+
174
+ if (process.env.DEBUG === 'true') {
175
+ console.log(`[PARAM_FORMAT] Getting formats for ${toolName}`);
176
+ console.log(`[PARAM_FORMAT] cliPattern exists: ${!!cliPattern}`);
177
+ if (cliPattern && cliPattern.commandStructure) {
178
+ console.log(`[PARAM_FORMAT] executionPattern: ${cliPattern.commandStructure.executionPattern}`);
179
+ console.log(`[PARAM_FORMAT] nonInteractiveFlag: ${cliPattern.commandStructure.nonInteractiveFlag}`);
180
+ }
181
+ }
182
+
183
+ // Define format priority based on tool characteristics
184
+ if (enhancedPattern) {
185
+ // Use enhanced pattern information
186
+ if (enhancedPattern.positionalArgs) {
187
+ // Check if CLI has flag-based non-interactive execution
188
+ // For tools like Kode that have flag-based execution, don't use positional args
189
+ const hasFlagBasedExecution = cliPattern?.commandStructure?.executionPattern === 'flag-based' &&
190
+ cliPattern?.commandStructure?.nonInteractiveFlag;
191
+
192
+ if (!hasFlagBasedExecution) {
193
+ // Qwen, Copilot, etc. support positional arguments
194
+ formats.push({
195
+ name: 'positional',
196
+ priority: enhancedPattern.positionalArgs ? 10 : 5,
197
+ description: 'Positional arguments (natural language)',
198
+ template: (prompt) => [prompt]
199
+ });
200
+ }
201
+ }
202
+
203
+ if (enhancedPattern.naturalLanguageSupport) {
204
+ // CLIs with good natural language support
205
+ formats.push({
206
+ name: 'prompt-flag',
207
+ priority: 8,
208
+ description: 'Standard -p flag',
209
+ template: (prompt) => ['-p', `"${prompt}"`]
210
+ });
211
+ }
212
+
213
+ if (enhancedPattern.skillPrefixRequired) {
214
+ // CodeBuddy requires skill: prefix
215
+ formats.push({
216
+ name: 'skill-prefix',
217
+ priority: 9,
218
+ description: 'Skill prefix with -y flag',
219
+ template: (prompt) => ['-y', '-p', `"${prompt}"`]
220
+ });
221
+ }
222
+ }
223
+
224
+ // Add standard formats based on CLI pattern analysis
225
+ if (cliPattern && cliPattern.commandStructure) {
226
+ const structure = cliPattern.commandStructure;
227
+
228
+ if (structure.promptFlag) {
229
+ formats.push({
230
+ name: 'detected-prompt-flag',
231
+ priority: 7,
232
+ description: `Detected prompt flag: ${structure.promptFlag}`,
233
+ template: (prompt) => [structure.promptFlag, `"${prompt}"`]
234
+ });
235
+ }
236
+
237
+ if (structure.executionPattern === 'argument-based') {
238
+ formats.push({
239
+ name: 'argument-based',
240
+ priority: 6,
241
+ description: 'Argument-based execution',
242
+ template: (prompt) => [`"${prompt}"`]
243
+ });
244
+ }
245
+
246
+ if (structure.executionPattern === 'subcommand-based') {
247
+ // For tools like Codex that need subcommand
248
+ formats.push({
249
+ name: 'subcommand-exec',
250
+ priority: 7,
251
+ description: 'Subcommand-based with exec',
252
+ template: (prompt) => ['exec', '-p', `"${prompt}"`]
253
+ });
254
+ }
255
+
256
+ if (structure.executionPattern === 'flag-based') {
257
+ // For tools like Kode that use flag-based execution (e.g., --print)
258
+ if (structure.nonInteractiveFlag) {
259
+ formats.push({
260
+ name: 'flag-based-non-interactive',
261
+ priority: 9,
262
+ description: `Flag-based non-interactive: ${structure.nonInteractiveFlag}`,
263
+ template: (prompt) => [structure.nonInteractiveFlag, `"${prompt}"`]
264
+ });
265
+ }
266
+ }
267
+ }
268
+
269
+ // Add fallback formats
270
+ formats.push({
271
+ name: 'standard-p-flag',
272
+ priority: 4,
273
+ description: 'Standard -p flag (fallback)',
274
+ template: (prompt) => ['-p', `"${prompt}"`]
275
+ });
276
+
277
+ // Filter out failed formats from retry history
278
+ const failedFormats = this.retryHistory.get(toolName) || new Set();
279
+ const validFormats = formats.filter(f => !failedFormats.has(f.name));
280
+
281
+ if (process.env.DEBUG === 'true') {
282
+ console.log(`[PARAM_FORMAT] Total formats: ${formats.length}`);
283
+ formats.forEach(f => {
284
+ console.log(`[PARAM_FORMAT] - ${f.name} (priority: ${f.priority}): ${f.description}`);
285
+ });
286
+ console.log(`[PARAM_FORMAT] Valid formats: ${validFormats.length}`);
287
+ }
288
+
289
+ // If all formats failed, clear history and try again
290
+ if (validFormats.length === 0) {
291
+ if (process.env.DEBUG === 'true') {
292
+ console.log(`[RETRY] All formats failed for ${toolName}, clearing retry history`);
293
+ }
294
+ this.retryHistory.delete(toolName);
295
+ return formats;
296
+ }
297
+
298
+ // Sort by priority (descending)
299
+ return validFormats.sort((a, b) => b.priority - a.priority);
300
+ }
301
+
302
+ /**
303
+ * Generate arguments for a specific format
304
+ *
305
+ * @param {string} toolName - Name of the CLI tool
306
+ * @param {string} prompt - Optimized prompt
307
+ * @param {Object} format - Format object
308
+ * @returns {Array} Arguments array
309
+ */
310
+ generateArgumentsForFormat(toolName, prompt, format) {
311
+ try {
312
+ return format.template(prompt);
313
+ } catch (error) {
314
+ if (process.env.DEBUG === 'true') {
315
+ console.log(`[ERROR] Failed to generate args for format ${format.name}:`, error.message);
316
+ }
317
+ // Fallback to standard format
318
+ return ['-p', `"${prompt}"`];
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Select the best format from available options
324
+ *
325
+ * @param {Array} formats - Available formats
326
+ * @param {Object} detectedMentions - Detected agent/skill mentions
327
+ * @returns {Object} Selected format
328
+ */
329
+ selectBestFormat(formats, detectedMentions) {
330
+ // Prioritize formats based on detection
331
+ if (detectedMentions.hasSkill || detectedMentions.hasAgent) {
332
+ // For agent/skill requests, prefer formats with higher priority
333
+ const skillOptimizedFormats = formats.filter(f => f.priority >= 8);
334
+ if (skillOptimizedFormats.length > 0) {
335
+ return skillOptimizedFormats[0];
336
+ }
337
+ }
338
+
339
+ // Return highest priority format
340
+ return formats[0] || {
341
+ name: 'fallback',
342
+ priority: 1,
343
+ args: ['-p', '"{prompt}"'],
344
+ description: 'Fallback format'
345
+ };
346
+ }
347
+
348
+ /**
349
+ * Record a failed format for a tool
350
+ *
351
+ * @param {string} toolName - Name of the CLI tool
352
+ * @param {string} formatName - Name of the failed format
353
+ */
354
+ recordFailedFormat(toolName, formatName) {
355
+ if (!this.retryHistory.has(toolName)) {
356
+ this.retryHistory.set(toolName, new Set());
357
+ }
358
+ this.retryHistory.get(toolName).add(formatName);
359
+
360
+ if (process.env.DEBUG === 'true') {
361
+ console.log(`[RETRY] Recorded failed format ${formatName} for ${toolName}`);
362
+ console.log(`[RETRY] Failed formats:`, Array.from(this.retryHistory.get(toolName)));
363
+ }
364
+ }
365
+
366
+ /**
367
+ * Clear retry history for a tool
368
+ *
369
+ * @param {string} toolName - Name of the CLI tool
370
+ */
371
+ clearRetryHistory(toolName) {
372
+ this.retryHistory.delete(toolName);
373
+
374
+ if (process.env.DEBUG === 'true') {
375
+ console.log(`[RETRY] Cleared retry history for ${toolName}`);
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Get compatibility score for a tool with the given prompt
381
+ *
382
+ * @param {string} toolName - Name of the CLI tool
383
+ * @param {string} prompt - User prompt
384
+ * @returns {Object} Compatibility score and reasons
385
+ */
386
+ getCompatibilityScore(toolName, prompt) {
387
+ return this.analyzer.getAgentSkillCompatibilityScore(toolName, prompt);
388
+ }
389
+
390
+ /**
391
+ * Generate optimized call command for a tool
392
+ *
393
+ * @param {string} toolName - Name of the CLI tool
394
+ * @param {string} prompt - User prompt
395
+ * @returns {Object|null} Optimized call command
396
+ */
397
+ generateOptimizedCall(toolName, prompt) {
398
+ return this.analyzer.generateOptimizedCall(toolName, prompt);
399
+ }
400
+ }
401
+
402
+ 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;