@vfarcic/dot-ai 0.4.9 → 0.5.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.
- package/.claude/commands/context-load.md +11 -0
- package/.claude/commands/context-save.md +16 -0
- package/.claude/commands/prd-done.md +115 -0
- package/.claude/commands/prd-get.md +25 -0
- package/.claude/commands/prd-start.md +87 -0
- package/.claude/commands/task-done.md +77 -0
- package/.claude/commands/tests-reminder.md +32 -0
- package/.claude/settings.local.json +20 -0
- package/.eslintrc.json +25 -0
- package/.github/workflows/ci.yml +170 -0
- package/.prettierrc.json +10 -0
- package/.teller.yml +8 -0
- package/CLAUDE.md +162 -0
- package/assets/images/logo.png +0 -0
- package/bin/dot-ai.ts +47 -0
- package/destroy.sh +45 -0
- package/devbox.json +13 -0
- package/devbox.lock +225 -0
- package/docs/API.md +449 -0
- package/docs/CONTEXT.md +49 -0
- package/docs/DEVELOPMENT.md +203 -0
- package/docs/NEXT_STEPS.md +97 -0
- package/docs/STAGE_BASED_API.md +97 -0
- package/docs/cli-guide.md +798 -0
- package/docs/design.md +750 -0
- package/docs/discovery-engine.md +515 -0
- package/docs/error-handling.md +429 -0
- package/docs/function-registration.md +157 -0
- package/docs/mcp-guide.md +416 -0
- package/package.json +2 -123
- package/renovate.json +51 -0
- package/setup.sh +111 -0
- package/{dist/cli.js → src/cli.ts} +26 -19
- package/src/core/claude.ts +280 -0
- package/src/core/deploy-operation.ts +127 -0
- package/src/core/discovery.ts +900 -0
- package/src/core/error-handling.ts +562 -0
- package/src/core/index.ts +143 -0
- package/src/core/kubernetes-utils.ts +218 -0
- package/src/core/memory.ts +148 -0
- package/src/core/schema.ts +830 -0
- package/src/core/session-utils.ts +97 -0
- package/src/core/workflow.ts +234 -0
- package/src/index.ts +18 -0
- package/src/interfaces/cli.ts +872 -0
- package/src/interfaces/mcp.ts +183 -0
- package/src/mcp/server.ts +131 -0
- package/src/tools/answer-question.ts +807 -0
- package/src/tools/choose-solution.ts +169 -0
- package/src/tools/deploy-manifests.ts +94 -0
- package/src/tools/generate-manifests.ts +502 -0
- package/src/tools/index.ts +41 -0
- package/src/tools/recommend.ts +370 -0
- package/tests/__mocks__/@kubernetes/client-node.ts +106 -0
- package/tests/build-system.test.ts +345 -0
- package/tests/configuration.test.ts +226 -0
- package/tests/core/deploy-operation.test.ts +38 -0
- package/tests/core/discovery.test.ts +1648 -0
- package/tests/core/error-handling.test.ts +632 -0
- package/tests/core/schema.test.ts +1658 -0
- package/tests/core/session-utils.test.ts +245 -0
- package/tests/core.test.ts +439 -0
- package/tests/fixtures/configmap-no-labels.yaml +8 -0
- package/tests/fixtures/crossplane-app-configuration.yaml +6 -0
- package/tests/fixtures/crossplane-providers.yaml +45 -0
- package/tests/fixtures/crossplane-rbac.yaml +48 -0
- package/tests/fixtures/invalid-configmap.yaml +8 -0
- package/tests/fixtures/invalid-deployment.yaml +17 -0
- package/tests/fixtures/test-deployment.yaml +28 -0
- package/tests/fixtures/valid-configmap.yaml +15 -0
- package/tests/infrastructure.test.ts +426 -0
- package/tests/interfaces/cli.test.ts +1036 -0
- package/tests/interfaces/mcp.test.ts +139 -0
- package/tests/kubernetes-utils.test.ts +200 -0
- package/tests/mcp/server.test.ts +126 -0
- package/tests/setup.ts +31 -0
- package/tests/tools/answer-question.test.ts +367 -0
- package/tests/tools/choose-solution.test.ts +481 -0
- package/tests/tools/deploy-manifests.test.ts +185 -0
- package/tests/tools/generate-manifests.test.ts +441 -0
- package/tests/tools/index.test.ts +111 -0
- package/tests/tools/recommend.test.ts +180 -0
- package/tsconfig.json +34 -0
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/core/claude.d.ts +0 -42
- package/dist/core/claude.d.ts.map +0 -1
- package/dist/core/claude.js +0 -229
- package/dist/core/deploy-operation.d.ts +0 -38
- package/dist/core/deploy-operation.d.ts.map +0 -1
- package/dist/core/deploy-operation.js +0 -101
- package/dist/core/discovery.d.ts +0 -162
- package/dist/core/discovery.d.ts.map +0 -1
- package/dist/core/discovery.js +0 -758
- package/dist/core/error-handling.d.ts +0 -167
- package/dist/core/error-handling.d.ts.map +0 -1
- package/dist/core/error-handling.js +0 -399
- package/dist/core/index.d.ts +0 -42
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -123
- package/dist/core/kubernetes-utils.d.ts +0 -38
- package/dist/core/kubernetes-utils.d.ts.map +0 -1
- package/dist/core/kubernetes-utils.js +0 -177
- package/dist/core/memory.d.ts +0 -45
- package/dist/core/memory.d.ts.map +0 -1
- package/dist/core/memory.js +0 -113
- package/dist/core/schema.d.ts +0 -187
- package/dist/core/schema.d.ts.map +0 -1
- package/dist/core/schema.js +0 -655
- package/dist/core/session-utils.d.ts +0 -29
- package/dist/core/session-utils.d.ts.map +0 -1
- package/dist/core/session-utils.js +0 -121
- package/dist/core/workflow.d.ts +0 -70
- package/dist/core/workflow.d.ts.map +0 -1
- package/dist/core/workflow.js +0 -161
- package/dist/index.d.ts +0 -15
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -32
- package/dist/interfaces/cli.d.ts +0 -74
- package/dist/interfaces/cli.d.ts.map +0 -1
- package/dist/interfaces/cli.js +0 -769
- package/dist/interfaces/mcp.d.ts +0 -30
- package/dist/interfaces/mcp.d.ts.map +0 -1
- package/dist/interfaces/mcp.js +0 -105
- package/dist/mcp/server.d.ts +0 -9
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js +0 -151
- package/dist/tools/answer-question.d.ts +0 -27
- package/dist/tools/answer-question.d.ts.map +0 -1
- package/dist/tools/answer-question.js +0 -696
- package/dist/tools/choose-solution.d.ts +0 -23
- package/dist/tools/choose-solution.d.ts.map +0 -1
- package/dist/tools/choose-solution.js +0 -171
- package/dist/tools/deploy-manifests.d.ts +0 -25
- package/dist/tools/deploy-manifests.d.ts.map +0 -1
- package/dist/tools/deploy-manifests.js +0 -74
- package/dist/tools/generate-manifests.d.ts +0 -23
- package/dist/tools/generate-manifests.d.ts.map +0 -1
- package/dist/tools/generate-manifests.js +0 -424
- package/dist/tools/index.d.ts +0 -11
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js +0 -34
- package/dist/tools/recommend.d.ts +0 -23
- package/dist/tools/recommend.d.ts.map +0 -1
- package/dist/tools/recommend.js +0 -332
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive Error Handling System for DevOps AI Toolkit
|
|
3
|
+
*
|
|
4
|
+
* Provides centralized error handling, logging, and context management
|
|
5
|
+
* with support for MCP protocol, CLI operations, and core functionality.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Error categories for systematic error classification
|
|
12
|
+
*/
|
|
13
|
+
export enum ErrorCategory {
|
|
14
|
+
// Infrastructure errors
|
|
15
|
+
KUBERNETES = 'kubernetes',
|
|
16
|
+
NETWORK = 'network',
|
|
17
|
+
AUTHENTICATION = 'authentication',
|
|
18
|
+
AUTHORIZATION = 'authorization',
|
|
19
|
+
|
|
20
|
+
// Application errors
|
|
21
|
+
VALIDATION = 'validation',
|
|
22
|
+
CONFIGURATION = 'configuration',
|
|
23
|
+
OPERATION = 'operation',
|
|
24
|
+
|
|
25
|
+
// External service errors
|
|
26
|
+
AI_SERVICE = 'ai_service',
|
|
27
|
+
STORAGE = 'storage',
|
|
28
|
+
|
|
29
|
+
// Protocol errors
|
|
30
|
+
MCP_PROTOCOL = 'mcp_protocol',
|
|
31
|
+
CLI_INTERFACE = 'cli_interface',
|
|
32
|
+
|
|
33
|
+
// System errors
|
|
34
|
+
INTERNAL = 'internal',
|
|
35
|
+
UNKNOWN = 'unknown'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Error severity levels
|
|
40
|
+
*/
|
|
41
|
+
export enum ErrorSeverity {
|
|
42
|
+
LOW = 'low', // Non-critical, operation can continue
|
|
43
|
+
MEDIUM = 'medium', // Important but recoverable
|
|
44
|
+
HIGH = 'high', // Significant impact, requires attention
|
|
45
|
+
CRITICAL = 'critical' // System-threatening, immediate action required
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Error context interface for comprehensive error tracking
|
|
50
|
+
*/
|
|
51
|
+
export interface ErrorContext {
|
|
52
|
+
// Operation details
|
|
53
|
+
operation: string;
|
|
54
|
+
component: string;
|
|
55
|
+
|
|
56
|
+
// User context
|
|
57
|
+
userId?: string;
|
|
58
|
+
sessionId?: string;
|
|
59
|
+
requestId?: string;
|
|
60
|
+
|
|
61
|
+
// Technical context
|
|
62
|
+
timestamp: Date;
|
|
63
|
+
version: string;
|
|
64
|
+
|
|
65
|
+
// Input context
|
|
66
|
+
input?: any;
|
|
67
|
+
parameters?: Record<string, any>;
|
|
68
|
+
|
|
69
|
+
// Stack trace and debugging
|
|
70
|
+
originalError?: Error;
|
|
71
|
+
stackTrace?: string;
|
|
72
|
+
|
|
73
|
+
// Recovery information
|
|
74
|
+
suggestedActions?: string[];
|
|
75
|
+
isRetryable?: boolean;
|
|
76
|
+
retryCount?: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Structured error interface
|
|
81
|
+
*/
|
|
82
|
+
export interface AppError {
|
|
83
|
+
// Core identification
|
|
84
|
+
id: string;
|
|
85
|
+
code: string;
|
|
86
|
+
category: ErrorCategory;
|
|
87
|
+
severity: ErrorSeverity;
|
|
88
|
+
|
|
89
|
+
// User-facing information
|
|
90
|
+
message: string;
|
|
91
|
+
userMessage?: string;
|
|
92
|
+
technicalDetails?: string;
|
|
93
|
+
|
|
94
|
+
// Context and debugging
|
|
95
|
+
context: ErrorContext;
|
|
96
|
+
|
|
97
|
+
// Timing
|
|
98
|
+
timestamp: Date;
|
|
99
|
+
|
|
100
|
+
// Recovery guidance
|
|
101
|
+
suggestedActions: string[];
|
|
102
|
+
isRetryable: boolean;
|
|
103
|
+
|
|
104
|
+
// Chaining
|
|
105
|
+
cause?: AppError;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Log levels for structured logging
|
|
110
|
+
*/
|
|
111
|
+
export enum LogLevel {
|
|
112
|
+
DEBUG = 'debug',
|
|
113
|
+
INFO = 'info',
|
|
114
|
+
WARN = 'warn',
|
|
115
|
+
ERROR = 'error',
|
|
116
|
+
FATAL = 'fatal'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Structured log entry interface
|
|
121
|
+
*/
|
|
122
|
+
export interface LogEntry {
|
|
123
|
+
level: LogLevel;
|
|
124
|
+
timestamp: Date;
|
|
125
|
+
message: string;
|
|
126
|
+
component: string;
|
|
127
|
+
operation?: string;
|
|
128
|
+
requestId?: string;
|
|
129
|
+
sessionId?: string;
|
|
130
|
+
data?: any;
|
|
131
|
+
error?: AppError;
|
|
132
|
+
duration?: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Logger interface for dependency injection
|
|
137
|
+
*/
|
|
138
|
+
export interface Logger {
|
|
139
|
+
debug(message: string, data?: any): void;
|
|
140
|
+
info(message: string, data?: any): void;
|
|
141
|
+
warn(message: string, data?: any): void;
|
|
142
|
+
error(message: string, error?: Error | AppError, data?: any): void;
|
|
143
|
+
fatal(message: string, error?: Error | AppError, data?: any): void;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Default console logger implementation
|
|
148
|
+
*/
|
|
149
|
+
export class ConsoleLogger implements Logger {
|
|
150
|
+
private component: string;
|
|
151
|
+
private minLevel: LogLevel;
|
|
152
|
+
|
|
153
|
+
constructor(component: string, minLevel: LogLevel = LogLevel.INFO) {
|
|
154
|
+
this.component = component;
|
|
155
|
+
this.minLevel = minLevel;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private shouldLog(level: LogLevel): boolean {
|
|
159
|
+
const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL];
|
|
160
|
+
return levels.indexOf(level) >= levels.indexOf(this.minLevel);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private formatMessage(level: LogLevel, message: string, data?: any): string {
|
|
164
|
+
const timestamp = new Date().toISOString();
|
|
165
|
+
const baseMessage = `[${timestamp}] ${level.toUpperCase()} [${this.component}] ${message}`;
|
|
166
|
+
|
|
167
|
+
if (data) {
|
|
168
|
+
return `${baseMessage} ${JSON.stringify(data, null, 2)}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return baseMessage;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
debug(message: string, data?: any): void {
|
|
175
|
+
if (this.shouldLog(LogLevel.DEBUG)) {
|
|
176
|
+
console.debug(this.formatMessage(LogLevel.DEBUG, message, data));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
info(message: string, data?: any): void {
|
|
181
|
+
if (this.shouldLog(LogLevel.INFO)) {
|
|
182
|
+
console.info(this.formatMessage(LogLevel.INFO, message, data));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
warn(message: string, data?: any): void {
|
|
187
|
+
if (this.shouldLog(LogLevel.WARN)) {
|
|
188
|
+
console.warn(this.formatMessage(LogLevel.WARN, message, data));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
error(message: string, error?: Error | AppError, data?: any): void {
|
|
193
|
+
if (this.shouldLog(LogLevel.ERROR)) {
|
|
194
|
+
const errorData = error ? { error: this.serializeError(error), ...data } : data;
|
|
195
|
+
console.error(this.formatMessage(LogLevel.ERROR, message, errorData));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
fatal(message: string, error?: Error | AppError, data?: any): void {
|
|
200
|
+
if (this.shouldLog(LogLevel.FATAL)) {
|
|
201
|
+
const errorData = error ? { error: this.serializeError(error), ...data } : data;
|
|
202
|
+
console.error(this.formatMessage(LogLevel.FATAL, message, errorData));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private serializeError(error: Error | AppError): any {
|
|
207
|
+
if ('category' in error) {
|
|
208
|
+
// AppError
|
|
209
|
+
return {
|
|
210
|
+
id: error.id,
|
|
211
|
+
code: error.code,
|
|
212
|
+
category: error.category,
|
|
213
|
+
severity: error.severity,
|
|
214
|
+
message: error.message,
|
|
215
|
+
context: error.context
|
|
216
|
+
};
|
|
217
|
+
} else {
|
|
218
|
+
// Native Error
|
|
219
|
+
return {
|
|
220
|
+
name: error.name,
|
|
221
|
+
message: error.message,
|
|
222
|
+
stack: error.stack
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Error handler factory and utilities
|
|
230
|
+
*/
|
|
231
|
+
export class ErrorHandler {
|
|
232
|
+
private static requestIdCounter = 0;
|
|
233
|
+
private static logger: Logger = new ConsoleLogger('ErrorHandler');
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Set custom logger implementation
|
|
237
|
+
*/
|
|
238
|
+
static setLogger(logger: Logger): void {
|
|
239
|
+
this.logger = logger;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Generate unique request ID
|
|
244
|
+
*/
|
|
245
|
+
static generateRequestId(): string {
|
|
246
|
+
return `req_${Date.now()}_${++this.requestIdCounter}`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Create comprehensive AppError from various error sources
|
|
251
|
+
*/
|
|
252
|
+
static createError(
|
|
253
|
+
category: ErrorCategory,
|
|
254
|
+
severity: ErrorSeverity,
|
|
255
|
+
message: string,
|
|
256
|
+
context: Partial<ErrorContext>,
|
|
257
|
+
originalError?: Error
|
|
258
|
+
): AppError {
|
|
259
|
+
const errorId = `err_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
260
|
+
const timestamp = new Date();
|
|
261
|
+
|
|
262
|
+
const fullContext: ErrorContext = {
|
|
263
|
+
operation: context.operation || 'unknown',
|
|
264
|
+
component: context.component || 'unknown',
|
|
265
|
+
timestamp,
|
|
266
|
+
version: process.env.npm_package_version || '0.1.0',
|
|
267
|
+
originalError,
|
|
268
|
+
stackTrace: originalError?.stack || new Error().stack,
|
|
269
|
+
isRetryable: context.isRetryable || false,
|
|
270
|
+
retryCount: context.retryCount || 0,
|
|
271
|
+
...context
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const suggestedActions = context.suggestedActions || this.getDefaultSuggestedActions(category);
|
|
275
|
+
|
|
276
|
+
const appError: AppError = {
|
|
277
|
+
id: errorId,
|
|
278
|
+
code: this.generateErrorCode(category, severity),
|
|
279
|
+
category,
|
|
280
|
+
severity,
|
|
281
|
+
message,
|
|
282
|
+
userMessage: this.getUserFriendlyMessage(category),
|
|
283
|
+
technicalDetails: originalError?.message,
|
|
284
|
+
context: fullContext,
|
|
285
|
+
timestamp,
|
|
286
|
+
suggestedActions,
|
|
287
|
+
isRetryable: fullContext.isRetryable || false,
|
|
288
|
+
// Don't wrap the original error to prevent circular references
|
|
289
|
+
cause: undefined
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// Log the error
|
|
293
|
+
this.logger.error(`Error created: ${message}`, appError, {
|
|
294
|
+
category,
|
|
295
|
+
severity,
|
|
296
|
+
operation: fullContext.operation,
|
|
297
|
+
component: fullContext.component
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
return appError;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Convert AppError to McpError for MCP protocol
|
|
305
|
+
*/
|
|
306
|
+
static toMcpError(appError: AppError): McpError {
|
|
307
|
+
const errorCode = this.mapToMcpErrorCode(appError.category);
|
|
308
|
+
const message = `${appError.message}${appError.technicalDetails ? ` - ${appError.technicalDetails}` : ''}`;
|
|
309
|
+
|
|
310
|
+
return new McpError(errorCode, message);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Handle error with automatic logging and context enhancement
|
|
315
|
+
*/
|
|
316
|
+
static handleError(
|
|
317
|
+
error: Error | AppError,
|
|
318
|
+
context: Partial<ErrorContext>,
|
|
319
|
+
options: {
|
|
320
|
+
rethrow?: boolean;
|
|
321
|
+
convertToMcp?: boolean;
|
|
322
|
+
logLevel?: LogLevel;
|
|
323
|
+
} = {}
|
|
324
|
+
): AppError | McpError {
|
|
325
|
+
let appError: AppError;
|
|
326
|
+
|
|
327
|
+
if ('category' in error) {
|
|
328
|
+
// Already an AppError
|
|
329
|
+
appError = error;
|
|
330
|
+
} else {
|
|
331
|
+
// Convert native Error to AppError
|
|
332
|
+
appError = this.createError(
|
|
333
|
+
this.categorizeError(error),
|
|
334
|
+
this.assessSeverity(error),
|
|
335
|
+
error.message,
|
|
336
|
+
context,
|
|
337
|
+
error
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Log the handled error
|
|
342
|
+
const logLevel = options.logLevel || LogLevel.ERROR;
|
|
343
|
+
this.logger[logLevel](`Error handled in ${context.component || 'unknown'}`, appError);
|
|
344
|
+
|
|
345
|
+
if (options.convertToMcp) {
|
|
346
|
+
const mcpError = this.toMcpError(appError);
|
|
347
|
+
if (options.rethrow) {
|
|
348
|
+
throw mcpError;
|
|
349
|
+
}
|
|
350
|
+
return mcpError;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (options.rethrow) {
|
|
354
|
+
throw appError;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return appError;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Wrap operation with error handling
|
|
362
|
+
*/
|
|
363
|
+
static async withErrorHandling<T>(
|
|
364
|
+
operation: () => Promise<T>,
|
|
365
|
+
context: Partial<ErrorContext>,
|
|
366
|
+
options: {
|
|
367
|
+
retryCount?: number;
|
|
368
|
+
convertToMcp?: boolean;
|
|
369
|
+
} = {}
|
|
370
|
+
): Promise<T> {
|
|
371
|
+
const maxRetries = options.retryCount || 0;
|
|
372
|
+
let lastError: Error;
|
|
373
|
+
|
|
374
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
375
|
+
try {
|
|
376
|
+
this.logger.debug(`Executing operation: ${context.operation}`, {
|
|
377
|
+
attempt: attempt + 1,
|
|
378
|
+
maxRetries: maxRetries + 1
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
return await operation();
|
|
382
|
+
} catch (error) {
|
|
383
|
+
lastError = error as Error;
|
|
384
|
+
|
|
385
|
+
const enhancedContext = {
|
|
386
|
+
...context,
|
|
387
|
+
retryCount: attempt,
|
|
388
|
+
isRetryable: attempt < maxRetries
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const appError = this.handleError(lastError, enhancedContext, {
|
|
392
|
+
logLevel: attempt < maxRetries ? LogLevel.WARN : LogLevel.ERROR
|
|
393
|
+
}) as AppError;
|
|
394
|
+
|
|
395
|
+
// Retry if we haven't exceeded max retries and the error is retryable
|
|
396
|
+
// For retry logic, we consider errors retryable by default unless explicitly marked as not retryable
|
|
397
|
+
const shouldRetry = attempt < maxRetries && (appError.isRetryable || enhancedContext.isRetryable);
|
|
398
|
+
|
|
399
|
+
if (shouldRetry) {
|
|
400
|
+
this.logger.info(`Retrying operation: ${context.operation}`, {
|
|
401
|
+
attempt: attempt + 1,
|
|
402
|
+
maxRetries: maxRetries + 1,
|
|
403
|
+
reason: appError.message
|
|
404
|
+
});
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Final attempt failed or not retryable
|
|
409
|
+
if (options.convertToMcp) {
|
|
410
|
+
throw this.toMcpError(appError);
|
|
411
|
+
}
|
|
412
|
+
throw appError;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// This should never be reached, but TypeScript requires it
|
|
417
|
+
throw lastError!;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private static generateErrorCode(category: ErrorCategory, severity: ErrorSeverity): string {
|
|
421
|
+
const categoryCode = category.toUpperCase().replace('_', '');
|
|
422
|
+
const severityCode = severity.charAt(0).toUpperCase();
|
|
423
|
+
const timestamp = Date.now().toString().slice(-6);
|
|
424
|
+
const random = Math.random().toString(36).substring(2, 5);
|
|
425
|
+
return `${categoryCode}_${severityCode}_${timestamp}_${random}`;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private static mapToMcpErrorCode(category: ErrorCategory): ErrorCode {
|
|
429
|
+
switch (category) {
|
|
430
|
+
case ErrorCategory.VALIDATION:
|
|
431
|
+
return ErrorCode.InvalidParams;
|
|
432
|
+
case ErrorCategory.AUTHENTICATION:
|
|
433
|
+
case ErrorCategory.AUTHORIZATION:
|
|
434
|
+
return ErrorCode.InvalidParams;
|
|
435
|
+
case ErrorCategory.MCP_PROTOCOL:
|
|
436
|
+
return ErrorCode.MethodNotFound;
|
|
437
|
+
case ErrorCategory.OPERATION:
|
|
438
|
+
case ErrorCategory.CLI_INTERFACE:
|
|
439
|
+
return ErrorCode.InvalidRequest;
|
|
440
|
+
default:
|
|
441
|
+
return ErrorCode.InternalError;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private static categorizeError(error: Error): ErrorCategory {
|
|
446
|
+
const message = error.message.toLowerCase();
|
|
447
|
+
|
|
448
|
+
if (message.includes('kubeconfig') || message.includes('kubernetes')) {
|
|
449
|
+
return ErrorCategory.KUBERNETES;
|
|
450
|
+
}
|
|
451
|
+
if (message.includes('network') || message.includes('connection')) {
|
|
452
|
+
return ErrorCategory.NETWORK;
|
|
453
|
+
}
|
|
454
|
+
if (message.includes('authentication') || message.includes('unauthorized')) {
|
|
455
|
+
return ErrorCategory.AUTHENTICATION;
|
|
456
|
+
}
|
|
457
|
+
if (message.includes('anthropic') || message.includes('ai') || message.includes('claude') || message.includes('api key invalid')) {
|
|
458
|
+
return ErrorCategory.AI_SERVICE;
|
|
459
|
+
}
|
|
460
|
+
if (message.includes('validation') || message.includes('invalid')) {
|
|
461
|
+
return ErrorCategory.VALIDATION;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return ErrorCategory.UNKNOWN;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
private static assessSeverity(error: Error): ErrorSeverity {
|
|
468
|
+
const message = error.message.toLowerCase();
|
|
469
|
+
|
|
470
|
+
if (message.includes('critical') || message.includes('fatal')) {
|
|
471
|
+
return ErrorSeverity.CRITICAL;
|
|
472
|
+
}
|
|
473
|
+
if (message.includes('authentication') || message.includes('authorization')) {
|
|
474
|
+
return ErrorSeverity.HIGH;
|
|
475
|
+
}
|
|
476
|
+
if (message.includes('validation') || message.includes('invalid')) {
|
|
477
|
+
return ErrorSeverity.MEDIUM;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return ErrorSeverity.LOW;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
private static getUserFriendlyMessage(category: ErrorCategory): string {
|
|
484
|
+
switch (category) {
|
|
485
|
+
case ErrorCategory.KUBERNETES:
|
|
486
|
+
return 'Unable to connect to Kubernetes cluster. Please check your kubeconfig and cluster connectivity.';
|
|
487
|
+
case ErrorCategory.AUTHENTICATION:
|
|
488
|
+
return 'Authentication failed. Please verify your credentials.';
|
|
489
|
+
case ErrorCategory.VALIDATION:
|
|
490
|
+
return 'Input validation failed. Please check your parameters and try again.';
|
|
491
|
+
case ErrorCategory.AI_SERVICE:
|
|
492
|
+
return 'AI service is temporarily unavailable. Please try again later.';
|
|
493
|
+
default:
|
|
494
|
+
return 'An unexpected error occurred. Please try again or contact support.';
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
private static getDefaultSuggestedActions(category: ErrorCategory): string[] {
|
|
499
|
+
switch (category) {
|
|
500
|
+
case ErrorCategory.KUBERNETES:
|
|
501
|
+
return [
|
|
502
|
+
'Verify kubeconfig file exists and is valid',
|
|
503
|
+
'Check cluster connectivity with kubectl cluster-info',
|
|
504
|
+
'Ensure proper authentication credentials'
|
|
505
|
+
];
|
|
506
|
+
case ErrorCategory.VALIDATION:
|
|
507
|
+
return [
|
|
508
|
+
'Review input parameters for correct format',
|
|
509
|
+
'Check required fields are provided',
|
|
510
|
+
'Verify data types match expected schema'
|
|
511
|
+
];
|
|
512
|
+
case ErrorCategory.AI_SERVICE:
|
|
513
|
+
return [
|
|
514
|
+
'Check ANTHROPIC_API_KEY environment variable',
|
|
515
|
+
'Verify API key is valid and has sufficient credits',
|
|
516
|
+
'Try again after a short delay'
|
|
517
|
+
];
|
|
518
|
+
default:
|
|
519
|
+
return [
|
|
520
|
+
'Try the operation again',
|
|
521
|
+
'Check system logs for more details',
|
|
522
|
+
'Contact support if problem persists'
|
|
523
|
+
];
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
private static wrapNativeError(error: Error): AppError {
|
|
528
|
+
const errorId = `err_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
529
|
+
const timestamp = new Date();
|
|
530
|
+
const category = this.categorizeError(error);
|
|
531
|
+
const severity = this.assessSeverity(error);
|
|
532
|
+
|
|
533
|
+
const context: ErrorContext = {
|
|
534
|
+
operation: 'error_wrapping',
|
|
535
|
+
component: 'ErrorHandler',
|
|
536
|
+
timestamp,
|
|
537
|
+
version: process.env.npm_package_version || '0.1.0',
|
|
538
|
+
originalError: error,
|
|
539
|
+
stackTrace: error.stack,
|
|
540
|
+
isRetryable: false,
|
|
541
|
+
retryCount: 0
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
const appError: AppError = {
|
|
545
|
+
id: errorId,
|
|
546
|
+
code: this.generateErrorCode(category, severity),
|
|
547
|
+
category,
|
|
548
|
+
severity,
|
|
549
|
+
message: error.message,
|
|
550
|
+
userMessage: this.getUserFriendlyMessage(category),
|
|
551
|
+
technicalDetails: error.message,
|
|
552
|
+
context,
|
|
553
|
+
timestamp,
|
|
554
|
+
suggestedActions: this.getDefaultSuggestedActions(category),
|
|
555
|
+
isRetryable: false,
|
|
556
|
+
// No cause to prevent circular reference
|
|
557
|
+
cause: undefined
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
return appError;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Intelligence Module
|
|
3
|
+
*
|
|
4
|
+
* Shared intelligence for both CLI and MCP interfaces
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { KubernetesDiscovery } from './discovery';
|
|
8
|
+
import { MemorySystem } from './memory';
|
|
9
|
+
import { WorkflowEngine } from './workflow';
|
|
10
|
+
import { ClaudeIntegration } from './claude';
|
|
11
|
+
import { SchemaParser, ManifestValidator, ResourceRecommender } from './schema';
|
|
12
|
+
|
|
13
|
+
export interface CoreConfig {
|
|
14
|
+
kubernetesConfig?: string;
|
|
15
|
+
anthropicApiKey?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class DotAI {
|
|
19
|
+
private config: CoreConfig;
|
|
20
|
+
private initialized: boolean = false;
|
|
21
|
+
|
|
22
|
+
public readonly discovery: KubernetesDiscovery;
|
|
23
|
+
public readonly memory: MemorySystem;
|
|
24
|
+
public readonly workflow: WorkflowEngine;
|
|
25
|
+
public readonly claude: ClaudeIntegration;
|
|
26
|
+
public readonly schema: {
|
|
27
|
+
parser: SchemaParser;
|
|
28
|
+
validator: ManifestValidator;
|
|
29
|
+
ranker: ResourceRecommender | null;
|
|
30
|
+
parseResource: (resourceName: string) => Promise<any>;
|
|
31
|
+
rankResources: (intent: string) => Promise<any>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
constructor(config: CoreConfig = {}) {
|
|
35
|
+
this.validateConfig(config);
|
|
36
|
+
// Centralize environment variable reading
|
|
37
|
+
this.config = {
|
|
38
|
+
kubernetesConfig: config.kubernetesConfig || process.env.KUBECONFIG,
|
|
39
|
+
anthropicApiKey: config.anthropicApiKey || process.env.ANTHROPIC_API_KEY
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Initialize modules
|
|
43
|
+
this.discovery = new KubernetesDiscovery({
|
|
44
|
+
kubeconfigPath: this.config.kubernetesConfig
|
|
45
|
+
});
|
|
46
|
+
this.memory = new MemorySystem();
|
|
47
|
+
this.workflow = new WorkflowEngine();
|
|
48
|
+
this.claude = new ClaudeIntegration(this.config.anthropicApiKey || 'test-key');
|
|
49
|
+
|
|
50
|
+
// Initialize schema components
|
|
51
|
+
const parser = new SchemaParser();
|
|
52
|
+
const validator = new ManifestValidator();
|
|
53
|
+
const ranker = this.config.anthropicApiKey ?
|
|
54
|
+
new ResourceRecommender({ claudeApiKey: this.config.anthropicApiKey }) :
|
|
55
|
+
null;
|
|
56
|
+
|
|
57
|
+
this.schema = {
|
|
58
|
+
parser,
|
|
59
|
+
validator,
|
|
60
|
+
ranker,
|
|
61
|
+
parseResource: async (resourceName: string) => {
|
|
62
|
+
// Get raw resource explanation from discovery
|
|
63
|
+
const explanation = await this.discovery.explainResource(resourceName);
|
|
64
|
+
|
|
65
|
+
// Parse GROUP, KIND, VERSION from kubectl explain output
|
|
66
|
+
const lines = explanation.split('\n');
|
|
67
|
+
const groupLine = lines.find((line: string) => line.startsWith('GROUP:'));
|
|
68
|
+
const kindLine = lines.find((line: string) => line.startsWith('KIND:'));
|
|
69
|
+
const versionLine = lines.find((line: string) => line.startsWith('VERSION:'));
|
|
70
|
+
|
|
71
|
+
const group = groupLine ? groupLine.replace('GROUP:', '').trim() : '';
|
|
72
|
+
const kind = kindLine ? kindLine.replace('KIND:', '').trim() : resourceName;
|
|
73
|
+
const version = versionLine ? versionLine.replace('VERSION:', '').trim() : 'v1';
|
|
74
|
+
|
|
75
|
+
// Build apiVersion from group and version
|
|
76
|
+
const apiVersion = group ? `${group}/${version}` : version;
|
|
77
|
+
|
|
78
|
+
// Return raw explanation for AI processing
|
|
79
|
+
return {
|
|
80
|
+
kind: kind,
|
|
81
|
+
rawExplanation: explanation,
|
|
82
|
+
apiVersion: apiVersion,
|
|
83
|
+
group: group,
|
|
84
|
+
description: explanation.split('\n').find((line: string) => line.startsWith('DESCRIPTION:'))?.replace('DESCRIPTION:', '').trim() || '',
|
|
85
|
+
properties: new Map() // Raw explanation contains all field info for AI
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
rankResources: async (intent: string) => {
|
|
89
|
+
if (!ranker) {
|
|
90
|
+
throw new Error('ResourceRanker not available. ANTHROPIC_API_KEY is required for AI-powered ranking.');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Create discovery functions with proper binding
|
|
94
|
+
const discoverResourcesFn = async () => await this.discovery.discoverResources();
|
|
95
|
+
const explainResourceFn = async (resource: string) => await this.discovery.explainResource(resource);
|
|
96
|
+
|
|
97
|
+
return await ranker.findBestSolutions(intent, discoverResourcesFn, explainResourceFn);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private validateConfig(config: CoreConfig): void {
|
|
103
|
+
if (config.anthropicApiKey === '') {
|
|
104
|
+
throw new Error('Invalid configuration: Empty API key provided');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async initialize(): Promise<void> {
|
|
109
|
+
try {
|
|
110
|
+
// Initialize all modules
|
|
111
|
+
await this.discovery.connect();
|
|
112
|
+
await this.memory.initialize();
|
|
113
|
+
await this.workflow.initialize();
|
|
114
|
+
|
|
115
|
+
this.initialized = true;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
this.initialized = false;
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
isInitialized(): boolean {
|
|
123
|
+
return this.initialized;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getVersion(): string {
|
|
127
|
+
return '0.1.0';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
getAnthropicApiKey(): string | undefined {
|
|
131
|
+
return this.config.anthropicApiKey;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Re-export all modules for convenience
|
|
136
|
+
export { KubernetesDiscovery } from './discovery';
|
|
137
|
+
export { MemorySystem } from './memory';
|
|
138
|
+
export { WorkflowEngine } from './workflow';
|
|
139
|
+
export { ClaudeIntegration } from './claude';
|
|
140
|
+
export { SchemaParser, ManifestValidator, ResourceRecommender } from './schema';
|
|
141
|
+
|
|
142
|
+
// Default export
|
|
143
|
+
export default DotAI;
|