stigmergy 1.0.94 → 1.0.95
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/bin/stigmergy +26 -12
- package/docs/HASH_TABLE.md +83 -0
- package/docs/WEATHER_PROCESSOR_API.md +230 -0
- package/docs/best_practices.md +135 -0
- package/docs/development_guidelines.md +392 -0
- package/docs/requirements_specification.md +148 -0
- package/docs/system_design.md +314 -0
- package/docs/tdd_implementation_plan.md +384 -0
- package/docs/test_report.md +49 -0
- package/examples/calculator-example.js +72 -0
- package/examples/json-validation-example.js +64 -0
- package/examples/rest-client-example.js +52 -0
- package/package.json +21 -17
- package/scripts/post-deployment-config.js +9 -2
- package/src/auth.js +171 -0
- package/src/auth_command.js +195 -0
- package/src/calculator.js +220 -0
- package/src/core/cli_help_analyzer.js +625 -562
- package/src/core/cli_parameter_handler.js +121 -0
- package/src/core/cli_tools.js +89 -0
- package/src/core/error_handler.js +307 -0
- package/src/core/memory_manager.js +76 -0
- package/src/core/smart_router.js +133 -0
- package/src/deploy.js +50 -0
- package/src/main_english.js +642 -719
- package/src/main_fixed.js +1035 -0
- package/src/utils.js +529 -0
- package/src/weatherProcessor.js +205 -0
- package/test/calculator.test.js +215 -0
- package/test/collision-test.js +26 -0
- package/test/csv-processing-test.js +36 -0
- package/test/e2e/claude-cli-test.js +128 -0
- package/test/e2e/collaboration-test.js +75 -0
- package/test/e2e/comprehensive-test.js +431 -0
- package/test/e2e/error-handling-test.js +90 -0
- package/test/e2e/individual-tool-test.js +143 -0
- package/test/e2e/other-cli-test.js +130 -0
- package/test/e2e/qoder-cli-test.js +128 -0
- package/test/e2e/run-e2e-tests.js +73 -0
- package/test/e2e/test-data.js +88 -0
- package/test/e2e/test-utils.js +222 -0
- package/test/hash-table-demo.js +33 -0
- package/test/hash-table-test.js +26 -0
- package/test/json-validation-test.js +164 -0
- package/test/rest-client-test.js +56 -0
- package/test/unit/calculator-full.test.js +191 -0
- package/test/unit/calculator-simple.test.js +96 -0
- package/test/unit/calculator.test.js +97 -0
- package/test/unit/cli_parameter_handler.test.js +116 -0
- package/test/weather-processor.test.js +104 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Unified CLI Parameter Handler
|
|
2
|
+
const { spawn } = require('child_process');
|
|
3
|
+
|
|
4
|
+
class CLIParameterHandler {
|
|
5
|
+
/**
|
|
6
|
+
* Generate appropriate arguments for CLI execution based on tool patterns
|
|
7
|
+
* @param {string} toolName - Name of the CLI tool
|
|
8
|
+
* @param {string} prompt - User prompt
|
|
9
|
+
* @param {Object} cliPattern - CLI pattern information from analyzer
|
|
10
|
+
* @returns {Array} Arguments array for spawn
|
|
11
|
+
*/
|
|
12
|
+
static generateArguments(toolName, prompt, cliPattern) {
|
|
13
|
+
// Default arguments
|
|
14
|
+
let toolArgs = [];
|
|
15
|
+
|
|
16
|
+
// Special handling for Codex CLI which always needs 'exec' subcommand
|
|
17
|
+
if (toolName === 'codex') {
|
|
18
|
+
return ['exec', '-p', `"${prompt}"`];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Check if we have pattern information from CLI help analyzer
|
|
23
|
+
if (cliPattern && cliPattern.commandStructure) {
|
|
24
|
+
const commandStructure = cliPattern.commandStructure;
|
|
25
|
+
|
|
26
|
+
// Handle based on command structure and patterns from help analyzer
|
|
27
|
+
if (commandStructure.nonInteractiveSupport) {
|
|
28
|
+
// Use flag-based approach if available
|
|
29
|
+
if (commandStructure.promptFlag) {
|
|
30
|
+
toolArgs = [commandStructure.promptFlag, `"${prompt}"`];
|
|
31
|
+
} else if (commandStructure.nonInteractiveFlag) {
|
|
32
|
+
toolArgs = [commandStructure.nonInteractiveFlag, `"${prompt}"`];
|
|
33
|
+
} else {
|
|
34
|
+
// Fallback to standard -p flag
|
|
35
|
+
toolArgs = ['-p', `"${prompt}"`];
|
|
36
|
+
}
|
|
37
|
+
} else if (commandStructure.executionPattern === 'flag-based') {
|
|
38
|
+
// Explicitly flag-based tools
|
|
39
|
+
if (commandStructure.promptFlag) {
|
|
40
|
+
toolArgs = [commandStructure.promptFlag, `"${prompt}"`];
|
|
41
|
+
} else {
|
|
42
|
+
toolArgs = ['-p', `"${prompt}"`];
|
|
43
|
+
}
|
|
44
|
+
} else if (commandStructure.executionPattern === 'argument-based') {
|
|
45
|
+
// Argument-based tools
|
|
46
|
+
toolArgs = [`"${prompt}"`];
|
|
47
|
+
} else if (commandStructure.executionPattern === 'subcommand-based') {
|
|
48
|
+
// Subcommand-based tools
|
|
49
|
+
toolArgs = ['-p', `"${prompt}"`];
|
|
50
|
+
} else {
|
|
51
|
+
// Fallback to tool-specific handling
|
|
52
|
+
toolArgs = this.getToolSpecificArguments(toolName, prompt);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
// Fallback to tool-specific handling if no pattern information
|
|
56
|
+
toolArgs = this.getToolSpecificArguments(toolName, prompt);
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
// Final fallback to tool-specific handling
|
|
60
|
+
toolArgs = this.getToolSpecificArguments(toolName, prompt);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return toolArgs;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get tool-specific arguments based on known patterns
|
|
68
|
+
* @param {string} toolName - Name of the CLI tool
|
|
69
|
+
* @param {string} prompt - User prompt
|
|
70
|
+
* @returns {Array} Arguments array for spawn
|
|
71
|
+
*/
|
|
72
|
+
static getToolSpecificArguments(toolName, prompt) {
|
|
73
|
+
// Tool-specific argument handling
|
|
74
|
+
const toolSpecificArgs = {
|
|
75
|
+
'claude': ['-p', `"${prompt}"`],
|
|
76
|
+
'qodercli': ['-p', `"${prompt}"`],
|
|
77
|
+
'iflow': ['-p', `"${prompt}"`],
|
|
78
|
+
'codebuddy': ['-p', `"${prompt}"`],
|
|
79
|
+
'copilot': ['-p', `"${prompt}"`],
|
|
80
|
+
'codex': ['exec', '-p', `"${prompt}"`] // Codex needs 'exec' subcommand
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Return tool-specific arguments if available
|
|
84
|
+
if (toolSpecificArgs[toolName]) {
|
|
85
|
+
return toolSpecificArgs[toolName];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Default handling for other tools
|
|
89
|
+
// Check if the tool commonly uses -p flag
|
|
90
|
+
const toolsWithPFlag = ['claude', 'qodercli', 'iflow', 'codebuddy', 'copilot'];
|
|
91
|
+
if (toolsWithPFlag.includes(toolName)) {
|
|
92
|
+
return ['-p', `"${prompt}"`];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Default to argument-based approach
|
|
96
|
+
return [`"${prompt}"`];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Execute CLI tool with appropriate arguments
|
|
101
|
+
* @param {string} toolPath - Path to the CLI tool
|
|
102
|
+
* @param {string} toolName - Name of the CLI tool
|
|
103
|
+
* @param {string} prompt - User prompt
|
|
104
|
+
* @param {Object} cliPattern - CLI pattern information from analyzer
|
|
105
|
+
* @returns {Object} Child process object
|
|
106
|
+
*/
|
|
107
|
+
static executeCLI(toolPath, toolName, prompt, cliPattern) {
|
|
108
|
+
// Generate appropriate arguments
|
|
109
|
+
const toolArgs = this.generateArguments(toolName, prompt, cliPattern);
|
|
110
|
+
|
|
111
|
+
// Execute the tool
|
|
112
|
+
const child = spawn(toolPath, toolArgs, {
|
|
113
|
+
stdio: 'inherit',
|
|
114
|
+
shell: true
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return child;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = CLIParameterHandler;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const { errorHandler, ERROR_TYPES } = require('./error_handler');
|
|
4
|
+
|
|
5
|
+
// AI CLI Tools Configuration
|
|
6
|
+
const CLI_TOOLS = {
|
|
7
|
+
claude: {
|
|
8
|
+
name: 'Claude CLI',
|
|
9
|
+
version: 'claude --version',
|
|
10
|
+
install: 'npm install -g @anthropic-ai/claude-cli',
|
|
11
|
+
hooksDir: path.join(os.homedir(), '.claude', 'hooks'),
|
|
12
|
+
config: path.join(os.homedir(), '.claude', 'config.json')
|
|
13
|
+
},
|
|
14
|
+
gemini: {
|
|
15
|
+
name: 'Gemini CLI',
|
|
16
|
+
version: 'gemini --version',
|
|
17
|
+
install: 'npm install -g @google/generative-ai-cli',
|
|
18
|
+
hooksDir: path.join(os.homedir(), '.gemini', 'extensions'),
|
|
19
|
+
config: path.join(os.homedir(), '.gemini', 'config.json')
|
|
20
|
+
},
|
|
21
|
+
qwen: {
|
|
22
|
+
name: 'Qwen CLI',
|
|
23
|
+
version: 'qwen --version',
|
|
24
|
+
install: 'npm install -g @alibaba/qwen-cli',
|
|
25
|
+
hooksDir: path.join(os.homedir(), '.qwen', 'hooks'),
|
|
26
|
+
config: path.join(os.homedir(), '.qwen', 'config.json')
|
|
27
|
+
},
|
|
28
|
+
iflow: {
|
|
29
|
+
name: 'iFlow CLI',
|
|
30
|
+
version: 'iflow --version',
|
|
31
|
+
install: 'npm install -g iflow-cli',
|
|
32
|
+
hooksDir: path.join(os.homedir(), '.iflow', 'hooks'),
|
|
33
|
+
config: path.join(os.homedir(), '.iflow', 'config.json')
|
|
34
|
+
},
|
|
35
|
+
qodercli: {
|
|
36
|
+
name: 'Qoder CLI',
|
|
37
|
+
version: 'qodercli --version',
|
|
38
|
+
install: 'npm install -g @qoder-ai/qodercli',
|
|
39
|
+
hooksDir: path.join(os.homedir(), '.qoder', 'hooks'),
|
|
40
|
+
config: path.join(os.homedir(), '.qoder', 'config.json')
|
|
41
|
+
},
|
|
42
|
+
codebuddy: {
|
|
43
|
+
name: 'CodeBuddy CLI',
|
|
44
|
+
version: 'codebuddy --version',
|
|
45
|
+
install: 'npm install -g codebuddy-cli',
|
|
46
|
+
hooksDir: path.join(os.homedir(), '.codebuddy', 'hooks'),
|
|
47
|
+
config: path.join(os.homedir(), '.codebuddy', 'config.json')
|
|
48
|
+
},
|
|
49
|
+
copilot: {
|
|
50
|
+
name: 'GitHub Copilot CLI',
|
|
51
|
+
version: 'copilot --version',
|
|
52
|
+
install: 'npm install -g @github/copilot-cli',
|
|
53
|
+
hooksDir: path.join(os.homedir(), '.copilot', 'mcp'),
|
|
54
|
+
config: path.join(os.homedir(), '.copilot', 'config.json')
|
|
55
|
+
},
|
|
56
|
+
codex: {
|
|
57
|
+
name: 'OpenAI Codex CLI',
|
|
58
|
+
version: 'codex --version',
|
|
59
|
+
install: 'npm install -g openai-codex-cli',
|
|
60
|
+
hooksDir: path.join(os.homedir(), '.config', 'codex', 'slash_commands'),
|
|
61
|
+
config: path.join(os.homedir(), '.codex', 'config.json')
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Validate CLI tool configuration
|
|
67
|
+
* @param {string} toolName - Name of the tool to validate
|
|
68
|
+
* @throws {StigmergyError} If validation fails
|
|
69
|
+
*/
|
|
70
|
+
function validateCLITool(toolName) {
|
|
71
|
+
if (!CLI_TOOLS[toolName]) {
|
|
72
|
+
throw errorHandler.createError(
|
|
73
|
+
`CLI tool '${toolName}' is not configured`,
|
|
74
|
+
ERROR_TYPES.CONFIGURATION,
|
|
75
|
+
'INVALID_CLI_TOOL'
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const tool = CLI_TOOLS[toolName];
|
|
80
|
+
if (!tool.name || !tool.version || !tool.install) {
|
|
81
|
+
throw errorHandler.createError(
|
|
82
|
+
`CLI tool '${toolName}' has invalid configuration`,
|
|
83
|
+
ERROR_TYPES.CONFIGURATION,
|
|
84
|
+
'INCOMPLETE_CLI_CONFIG'
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { CLI_TOOLS, validateCLITool };
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized Error Handling Utility for Stigmergy CLI
|
|
3
|
+
* Provides consistent error handling, logging, and reporting across the application
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs/promises');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
|
|
10
|
+
// Error types enumeration
|
|
11
|
+
const ERROR_TYPES = {
|
|
12
|
+
VALIDATION: 'VALIDATION_ERROR',
|
|
13
|
+
NETWORK: 'NETWORK_ERROR',
|
|
14
|
+
FILE_SYSTEM: 'FILE_SYSTEM_ERROR',
|
|
15
|
+
CLI_TOOL: 'CLI_TOOL_ERROR',
|
|
16
|
+
CONFIGURATION: 'CONFIGURATION_ERROR',
|
|
17
|
+
PERMISSION: 'PERMISSION_ERROR',
|
|
18
|
+
UNKNOWN: 'UNKNOWN_ERROR'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Log levels enumeration
|
|
22
|
+
const LOG_LEVELS = {
|
|
23
|
+
ERROR: 'ERROR',
|
|
24
|
+
WARN: 'WARN',
|
|
25
|
+
INFO: 'INFO',
|
|
26
|
+
DEBUG: 'DEBUG'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
class StigmergyError extends Error {
|
|
30
|
+
constructor(message, type = ERROR_TYPES.UNKNOWN, code = null, details = null) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = 'StigmergyError';
|
|
33
|
+
this.type = type;
|
|
34
|
+
this.code = code;
|
|
35
|
+
this.details = details;
|
|
36
|
+
this.timestamp = new Date().toISOString();
|
|
37
|
+
|
|
38
|
+
// Ensure proper stack trace
|
|
39
|
+
if (Error.captureStackTrace) {
|
|
40
|
+
Error.captureStackTrace(this, StigmergyError);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class ErrorHandler {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.logFile = path.join(os.homedir(), '.stigmergy', 'error.log');
|
|
48
|
+
this.maxLogSize = 10 * 1024 * 1024; // 10MB
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create a standardized Stigmergy error
|
|
53
|
+
* @param {string} message - Error message
|
|
54
|
+
* @param {string} type - Error type from ERROR_TYPES
|
|
55
|
+
* @param {string|null} code - Error code
|
|
56
|
+
* @param {Object|null} details - Additional error details
|
|
57
|
+
* @returns {StigmergyError}
|
|
58
|
+
*/
|
|
59
|
+
createError(message, type = ERROR_TYPES.UNKNOWN, code = null, details = null) {
|
|
60
|
+
return new StigmergyError(message, type, code, details);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Log an error to console and file
|
|
65
|
+
* @param {Error|StigmergyError} error - The error to log
|
|
66
|
+
* @param {string} level - Log level (ERROR, WARN, INFO, DEBUG)
|
|
67
|
+
* @param {string|null} context - Context where error occurred
|
|
68
|
+
*/
|
|
69
|
+
async logError(error, level = LOG_LEVELS.ERROR, context = null) {
|
|
70
|
+
try {
|
|
71
|
+
const timestamp = new Date().toISOString();
|
|
72
|
+
const errorType = error.type || ERROR_TYPES.UNKNOWN;
|
|
73
|
+
const errorCode = error.code || 'NO_CODE';
|
|
74
|
+
const errorMessage = error.message || 'Unknown error';
|
|
75
|
+
|
|
76
|
+
// Format log entry
|
|
77
|
+
const logEntry = {
|
|
78
|
+
timestamp,
|
|
79
|
+
level,
|
|
80
|
+
type: errorType,
|
|
81
|
+
code: errorCode,
|
|
82
|
+
message: errorMessage,
|
|
83
|
+
context,
|
|
84
|
+
stack: error.stack || 'No stack trace available'
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Console output
|
|
88
|
+
const consoleMessage = `[${level}] [${errorType}] ${errorMessage}`;
|
|
89
|
+
switch (level) {
|
|
90
|
+
case LOG_LEVELS.ERROR:
|
|
91
|
+
console.error(consoleMessage);
|
|
92
|
+
break;
|
|
93
|
+
case LOG_LEVELS.WARN:
|
|
94
|
+
console.warn(consoleMessage);
|
|
95
|
+
break;
|
|
96
|
+
case LOG_LEVELS.INFO:
|
|
97
|
+
console.info(consoleMessage);
|
|
98
|
+
break;
|
|
99
|
+
case LOG_LEVELS.DEBUG:
|
|
100
|
+
console.debug(consoleMessage);
|
|
101
|
+
break;
|
|
102
|
+
default:
|
|
103
|
+
console.log(consoleMessage);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// File logging
|
|
107
|
+
await this.writeToFile(logEntry);
|
|
108
|
+
} catch (logError) {
|
|
109
|
+
console.error('[ERROR_HANDLER] Failed to log error:', logError.message);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Write error to log file
|
|
115
|
+
* @param {Object} logEntry - Formatted log entry
|
|
116
|
+
*/
|
|
117
|
+
async writeToFile(logEntry) {
|
|
118
|
+
try {
|
|
119
|
+
// Ensure log directory exists
|
|
120
|
+
const logDir = path.dirname(this.logFile);
|
|
121
|
+
await fs.mkdir(logDir, { recursive: true });
|
|
122
|
+
|
|
123
|
+
// Check file size and rotate if necessary
|
|
124
|
+
await this.rotateLogFile();
|
|
125
|
+
|
|
126
|
+
// Append to log file
|
|
127
|
+
const logLine = JSON.stringify(logEntry) + '\n';
|
|
128
|
+
await fs.appendFile(this.logFile, logLine, { encoding: 'utf8' });
|
|
129
|
+
} catch (error) {
|
|
130
|
+
// Silent fail to prevent infinite loop
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Rotate log file if it exceeds max size
|
|
136
|
+
*/
|
|
137
|
+
async rotateLogFile() {
|
|
138
|
+
try {
|
|
139
|
+
const stats = await fs.stat(this.logFile);
|
|
140
|
+
if (stats.size > this.maxLogSize) {
|
|
141
|
+
const backupFile = this.logFile + '.old';
|
|
142
|
+
await fs.rename(this.logFile, backupFile);
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
// File doesn't exist or other issue, continue
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Handle CLI tool execution errors
|
|
151
|
+
* @param {string} toolName - Name of the CLI tool
|
|
152
|
+
* @param {Error} error - Error that occurred
|
|
153
|
+
* @param {string|null} command - Command that failed
|
|
154
|
+
*/
|
|
155
|
+
async handleCLIError(toolName, error, command = null) {
|
|
156
|
+
const cliError = new StigmergyError(
|
|
157
|
+
`Failed to execute ${toolName}${command ? ` command: ${command}` : ''}`,
|
|
158
|
+
ERROR_TYPES.CLI_TOOL,
|
|
159
|
+
null,
|
|
160
|
+
{
|
|
161
|
+
tool: toolName,
|
|
162
|
+
command,
|
|
163
|
+
originalError: error.message,
|
|
164
|
+
stack: error.stack
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
await this.logError(cliError, LOG_LEVELS.ERROR, 'CLI_EXECUTION');
|
|
169
|
+
return cliError;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Handle file system errors
|
|
174
|
+
* @param {string} operation - File operation that failed
|
|
175
|
+
* @param {string} filePath - Path of file involved
|
|
176
|
+
* @param {Error} error - Original error
|
|
177
|
+
*/
|
|
178
|
+
async handleFileError(operation, filePath, error) {
|
|
179
|
+
const fileError = new StigmergyError(
|
|
180
|
+
`Failed to ${operation} file: ${filePath}`,
|
|
181
|
+
ERROR_TYPES.FILE_SYSTEM,
|
|
182
|
+
error.code,
|
|
183
|
+
{
|
|
184
|
+
operation,
|
|
185
|
+
filePath,
|
|
186
|
+
originalError: error.message,
|
|
187
|
+
stack: error.stack
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
await this.logError(fileError, LOG_LEVELS.ERROR, 'FILE_SYSTEM');
|
|
192
|
+
return fileError;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Handle network errors
|
|
197
|
+
* @param {string} operation - Network operation that failed
|
|
198
|
+
* @param {string} url - URL involved
|
|
199
|
+
* @param {Error} error - Original error
|
|
200
|
+
*/
|
|
201
|
+
async handleNetworkError(operation, url, error) {
|
|
202
|
+
const networkError = new StigmergyError(
|
|
203
|
+
`Network error during ${operation}: ${url}`,
|
|
204
|
+
ERROR_TYPES.NETWORK,
|
|
205
|
+
error.code,
|
|
206
|
+
{
|
|
207
|
+
operation,
|
|
208
|
+
url,
|
|
209
|
+
originalError: error.message,
|
|
210
|
+
stack: error.stack
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
await this.logError(networkError, LOG_LEVELS.ERROR, 'NETWORK');
|
|
215
|
+
return networkError;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Handle validation errors
|
|
220
|
+
* @param {string} field - Field that failed validation
|
|
221
|
+
* @param {string} value - Value that failed validation
|
|
222
|
+
* @param {string} reason - Reason for validation failure
|
|
223
|
+
*/
|
|
224
|
+
createValidationError(field, value, reason) {
|
|
225
|
+
const validationError = new StigmergyError(
|
|
226
|
+
`Validation failed for ${field}: ${reason}`,
|
|
227
|
+
ERROR_TYPES.VALIDATION,
|
|
228
|
+
'VALIDATION_FAILED',
|
|
229
|
+
{
|
|
230
|
+
field,
|
|
231
|
+
value,
|
|
232
|
+
reason
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
return validationError;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Wrap an async function with error handling
|
|
241
|
+
* @param {Function} fn - Async function to wrap
|
|
242
|
+
* @param {string} context - Context for error logging
|
|
243
|
+
* @returns {Function} Wrapped function
|
|
244
|
+
*/
|
|
245
|
+
wrapAsync(fn, context) {
|
|
246
|
+
return async (...args) => {
|
|
247
|
+
try {
|
|
248
|
+
return await fn(...args);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
if (error instanceof StigmergyError) {
|
|
251
|
+
await this.logError(error, LOG_LEVELS.ERROR, context);
|
|
252
|
+
throw error;
|
|
253
|
+
} else {
|
|
254
|
+
const wrappedError = new StigmergyError(
|
|
255
|
+
error.message || 'Unknown error occurred',
|
|
256
|
+
ERROR_TYPES.UNKNOWN,
|
|
257
|
+
null,
|
|
258
|
+
{ originalError: error.message, stack: error.stack }
|
|
259
|
+
);
|
|
260
|
+
await this.logError(wrappedError, LOG_LEVELS.ERROR, context);
|
|
261
|
+
throw wrappedError;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Get error statistics from log file
|
|
269
|
+
*/
|
|
270
|
+
async getErrorStats() {
|
|
271
|
+
try {
|
|
272
|
+
const data = await fs.readFile(this.logFile, 'utf8');
|
|
273
|
+
const lines = data.split('\n').filter(line => line.trim());
|
|
274
|
+
|
|
275
|
+
const stats = {
|
|
276
|
+
totalErrors: lines.length,
|
|
277
|
+
byType: {},
|
|
278
|
+
byLevel: {}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
for (const line of lines) {
|
|
282
|
+
try {
|
|
283
|
+
const entry = JSON.parse(line);
|
|
284
|
+
stats.byType[entry.type] = (stats.byType[entry.type] || 0) + 1;
|
|
285
|
+
stats.byLevel[entry.level] = (stats.byLevel[entry.level] || 0) + 1;
|
|
286
|
+
} catch (parseError) {
|
|
287
|
+
// Skip malformed entries
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return stats;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
return { totalErrors: 0, byType: {}, byLevel: {} };
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Export singleton instance
|
|
299
|
+
const errorHandler = new ErrorHandler();
|
|
300
|
+
|
|
301
|
+
module.exports = {
|
|
302
|
+
ErrorHandler,
|
|
303
|
+
errorHandler,
|
|
304
|
+
StigmergyError,
|
|
305
|
+
ERROR_TYPES,
|
|
306
|
+
LOG_LEVELS
|
|
307
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/core/memory_manager.js
|
|
2
|
+
|
|
3
|
+
const fs = require('fs/promises');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Memory Manager - Handles global memory and context sharing between CLI tools
|
|
9
|
+
*/
|
|
10
|
+
class MemoryManager {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.globalMemoryFile = path.join(os.homedir(), '.stigmergy', 'memory.json');
|
|
13
|
+
this.projectMemoryFile = path.join(process.cwd(), 'STIGMERGY.md');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get global memory data
|
|
18
|
+
* @returns {Promise<Object>} Memory data
|
|
19
|
+
*/
|
|
20
|
+
async getGlobalMemory() {
|
|
21
|
+
try {
|
|
22
|
+
const data = await fs.readFile(this.globalMemoryFile, 'utf8');
|
|
23
|
+
return JSON.parse(data);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
// Return default memory structure if file doesn't exist or is invalid
|
|
26
|
+
return {
|
|
27
|
+
version: '1.0.0',
|
|
28
|
+
lastUpdated: new Date().toISOString(),
|
|
29
|
+
interactions: [],
|
|
30
|
+
collaborations: []
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Update global memory data
|
|
37
|
+
* @param {Function} updateFn - Function that takes current memory and returns updated memory
|
|
38
|
+
* @returns {Promise<Object>} Updated memory data
|
|
39
|
+
*/
|
|
40
|
+
async updateGlobalMemory(updateFn) {
|
|
41
|
+
const memory = await this.getGlobalMemory();
|
|
42
|
+
const updatedMemory = updateFn(memory);
|
|
43
|
+
updatedMemory.lastUpdated = new Date().toISOString();
|
|
44
|
+
|
|
45
|
+
// Ensure directory exists
|
|
46
|
+
const dir = path.dirname(this.globalMemoryFile);
|
|
47
|
+
await fs.mkdir(dir, { recursive: true });
|
|
48
|
+
|
|
49
|
+
// Write updated memory
|
|
50
|
+
await fs.writeFile(this.globalMemoryFile, JSON.stringify(updatedMemory, null, 2));
|
|
51
|
+
return updatedMemory;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Add an interaction record to memory
|
|
56
|
+
* @param {string} tool - CLI tool name
|
|
57
|
+
* @param {string} prompt - User prompt
|
|
58
|
+
* @param {string} response - Tool response
|
|
59
|
+
* @param {number} duration - Execution duration in milliseconds
|
|
60
|
+
* @returns {Promise<void>}
|
|
61
|
+
*/
|
|
62
|
+
async addInteraction(tool, prompt, response, duration = 0) {
|
|
63
|
+
await this.updateGlobalMemory(memory => {
|
|
64
|
+
memory.interactions.push({
|
|
65
|
+
timestamp: new Date().toISOString(),
|
|
66
|
+
tool,
|
|
67
|
+
prompt,
|
|
68
|
+
response,
|
|
69
|
+
duration
|
|
70
|
+
});
|
|
71
|
+
return memory;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = MemoryManager;
|