sumulige-claude 1.1.0 → 1.1.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/hooks/pre-commit.cjs +86 -0
- package/.claude/hooks/pre-push.cjs +103 -0
- package/.claude/quality-gate.json +61 -0
- package/.claude/settings.local.json +2 -1
- package/cli.js +28 -0
- package/config/quality-gate.json +61 -0
- package/lib/commands.js +208 -0
- package/lib/config-manager.js +441 -0
- package/lib/config-schema.js +408 -0
- package/lib/config-validator.js +330 -0
- package/lib/config.js +52 -1
- package/lib/errors.js +305 -0
- package/lib/quality-gate.js +431 -0
- package/lib/quality-rules.js +373 -0
- package/package.json +5 -1
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Validator
|
|
3
|
+
*
|
|
4
|
+
* AJV-based configuration validation with detailed error reporting.
|
|
5
|
+
* Provides structured error messages, severity levels, and auto-fix suggestions.
|
|
6
|
+
*
|
|
7
|
+
* @module lib/config-validator
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { CONFIG_SCHEMA, SETTINGS_SCHEMA, QUALITY_GATE_SCHEMA } = require('./config-schema');
|
|
13
|
+
const { ConfigError, parseAJVErrors } = require('./errors');
|
|
14
|
+
|
|
15
|
+
// Try to load AJV, provide fallback if not available
|
|
16
|
+
let Ajv = null;
|
|
17
|
+
let addFormats = null;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
Ajv = require('ajv');
|
|
21
|
+
addFormats = require('ajv-formats');
|
|
22
|
+
} catch {
|
|
23
|
+
// AJV not installed - will use basic validation
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Configuration Validator class
|
|
28
|
+
*/
|
|
29
|
+
class ConfigValidator {
|
|
30
|
+
/**
|
|
31
|
+
* @param {Object} options - Validator options
|
|
32
|
+
* @param {boolean} options.strict - Strict mode (default: false)
|
|
33
|
+
* @param {boolean} options.allErrors - Collect all errors (default: true)
|
|
34
|
+
* @param {boolean} options.coerceTypes - Coerce types (default: true)
|
|
35
|
+
* @param {boolean} options.useDefaults - Use default values (default: false)
|
|
36
|
+
*/
|
|
37
|
+
constructor(options = {}) {
|
|
38
|
+
this.strict = options.strict !== false;
|
|
39
|
+
this.allErrors = options.allErrors !== false;
|
|
40
|
+
this.coerceTypes = options.coerceTypes !== false;
|
|
41
|
+
this.useDefaults = options.useDefaults || false;
|
|
42
|
+
|
|
43
|
+
// Initialize AJV if available
|
|
44
|
+
if (Ajv) {
|
|
45
|
+
this.ajv = new Ajv({
|
|
46
|
+
allErrors: this.allErrors,
|
|
47
|
+
verbose: true,
|
|
48
|
+
coerceTypes: this.coerceTypes,
|
|
49
|
+
useDefaults: this.useDefaults,
|
|
50
|
+
allowUnionTypes: true,
|
|
51
|
+
strict: false,
|
|
52
|
+
removeAdditional: false // Keep additional properties
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Add formats if available
|
|
56
|
+
if (addFormats) {
|
|
57
|
+
addFormats(this.ajv);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Compile schemas
|
|
61
|
+
this.configValidate = this.ajv.compile(CONFIG_SCHEMA);
|
|
62
|
+
this.settingsValidate = this.ajv.compile(SETTINGS_SCHEMA);
|
|
63
|
+
this.qualityGateValidate = this.ajv.compile(QUALITY_GATE_SCHEMA);
|
|
64
|
+
} else {
|
|
65
|
+
// Fallback: basic validation without AJV
|
|
66
|
+
this.configValidate = null;
|
|
67
|
+
this.settingsValidate = null;
|
|
68
|
+
this.qualityGateValidate = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validate configuration object
|
|
74
|
+
* @param {Object} config - Configuration to validate
|
|
75
|
+
* @param {string} schemaName - Schema name ('config' | 'settings' | 'quality-gate')
|
|
76
|
+
* @returns {Object} Validation result
|
|
77
|
+
*/
|
|
78
|
+
validate(config, schemaName = 'config') {
|
|
79
|
+
// If AJV not available, do basic validation
|
|
80
|
+
if (!this.ajv) {
|
|
81
|
+
return this._basicValidate(config, schemaName);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const validate = this._getValidator(schemaName);
|
|
85
|
+
|
|
86
|
+
if (!validate) {
|
|
87
|
+
return {
|
|
88
|
+
valid: false,
|
|
89
|
+
errors: [{
|
|
90
|
+
path: 'schema',
|
|
91
|
+
message: `Unknown schema: ${schemaName}`,
|
|
92
|
+
severity: 'critical',
|
|
93
|
+
fix: `Use valid schema name: config, settings, quality-gate`
|
|
94
|
+
}],
|
|
95
|
+
warnings: [],
|
|
96
|
+
fixes: []
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const valid = validate(config);
|
|
101
|
+
|
|
102
|
+
if (valid) {
|
|
103
|
+
return { valid: true, errors: [], warnings: [], fixes: [] };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Process AJV errors
|
|
107
|
+
const result = {
|
|
108
|
+
valid: false,
|
|
109
|
+
errors: [],
|
|
110
|
+
warnings: [],
|
|
111
|
+
fixes: []
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const processedErrors = parseAJVErrors(validate.errors);
|
|
115
|
+
|
|
116
|
+
for (const error of processedErrors) {
|
|
117
|
+
if (error.severity === 'warn' || error.severity === 'info') {
|
|
118
|
+
result.warnings.push(error);
|
|
119
|
+
} else {
|
|
120
|
+
result.errors.push(error);
|
|
121
|
+
}
|
|
122
|
+
if (error.fix) {
|
|
123
|
+
result.fixes.push(error.fix);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Validate configuration file
|
|
132
|
+
* @param {string} configPath - Path to config file
|
|
133
|
+
* @param {string} schemaName - Schema name to use
|
|
134
|
+
* @returns {Object} Validation result
|
|
135
|
+
*/
|
|
136
|
+
validateFile(configPath, schemaName = null) {
|
|
137
|
+
// Auto-detect schema from filename if not provided
|
|
138
|
+
if (!schemaName) {
|
|
139
|
+
const filename = path.basename(configPath);
|
|
140
|
+
if (filename === 'config.json') {
|
|
141
|
+
schemaName = 'config';
|
|
142
|
+
} else if (filename === 'settings.json' || filename === 'settings.local.json') {
|
|
143
|
+
schemaName = 'settings';
|
|
144
|
+
} else if (filename === 'quality-gate.json') {
|
|
145
|
+
schemaName = 'quality-gate';
|
|
146
|
+
} else {
|
|
147
|
+
schemaName = 'config';
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!fs.existsSync(configPath)) {
|
|
152
|
+
return {
|
|
153
|
+
valid: false,
|
|
154
|
+
errors: [{
|
|
155
|
+
path: configPath,
|
|
156
|
+
message: 'Configuration file not found',
|
|
157
|
+
severity: 'critical',
|
|
158
|
+
fix: `Create config at: ${configPath}`
|
|
159
|
+
}],
|
|
160
|
+
warnings: [],
|
|
161
|
+
fixes: []
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
167
|
+
const config = JSON.parse(content);
|
|
168
|
+
return this.validate(config, schemaName);
|
|
169
|
+
} catch (e) {
|
|
170
|
+
if (e instanceof SyntaxError) {
|
|
171
|
+
return {
|
|
172
|
+
valid: false,
|
|
173
|
+
errors: [{
|
|
174
|
+
path: configPath,
|
|
175
|
+
message: `JSON parse error: ${e.message}`,
|
|
176
|
+
severity: 'critical',
|
|
177
|
+
fix: this._suggestJsonFix(e, configPath)
|
|
178
|
+
}],
|
|
179
|
+
warnings: [],
|
|
180
|
+
fixes: []
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
throw e;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Validate with error throwing
|
|
189
|
+
* @param {Object} config - Configuration to validate
|
|
190
|
+
* @param {string} schemaName - Schema name
|
|
191
|
+
* @throws {ConfigError} If validation fails
|
|
192
|
+
*/
|
|
193
|
+
validateOrThrow(config, schemaName = 'config') {
|
|
194
|
+
const result = this.validate(config, schemaName);
|
|
195
|
+
if (!result.valid) {
|
|
196
|
+
throw new ConfigError(
|
|
197
|
+
'Configuration validation failed',
|
|
198
|
+
result.errors,
|
|
199
|
+
result.fixes
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
return config;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get validator for schema name
|
|
207
|
+
* @param {string} schemaName - Schema name
|
|
208
|
+
* @returns {Function|null} Validator function
|
|
209
|
+
*/
|
|
210
|
+
_getValidator(schemaName) {
|
|
211
|
+
const validators = {
|
|
212
|
+
config: this.configValidate,
|
|
213
|
+
settings: this.settingsValidate,
|
|
214
|
+
'quality-gate': this.qualityGateValidate
|
|
215
|
+
};
|
|
216
|
+
return validators[schemaName] || null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Basic validation without AJV
|
|
221
|
+
* @param {Object} config - Configuration to validate
|
|
222
|
+
* @param {string} schemaName - Schema name
|
|
223
|
+
* @returns {Object} Validation result
|
|
224
|
+
*/
|
|
225
|
+
_basicValidate(config, schemaName) {
|
|
226
|
+
const result = {
|
|
227
|
+
valid: true,
|
|
228
|
+
errors: [],
|
|
229
|
+
warnings: [],
|
|
230
|
+
fixes: []
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Basic type check
|
|
234
|
+
if (!config || typeof config !== 'object') {
|
|
235
|
+
result.valid = false;
|
|
236
|
+
result.errors.push({
|
|
237
|
+
path: 'root',
|
|
238
|
+
message: 'Configuration must be an object',
|
|
239
|
+
severity: 'critical',
|
|
240
|
+
fix: 'Ensure config is valid JSON object'
|
|
241
|
+
});
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Schema-specific basic validation
|
|
246
|
+
if (schemaName === 'config') {
|
|
247
|
+
if (!config.version) {
|
|
248
|
+
result.valid = false;
|
|
249
|
+
result.errors.push({
|
|
250
|
+
path: 'version',
|
|
251
|
+
message: 'Missing required field: version',
|
|
252
|
+
severity: 'critical',
|
|
253
|
+
fix: 'Add "version": "1.0.0" to config'
|
|
254
|
+
});
|
|
255
|
+
} else if (typeof config.version === 'string' &&
|
|
256
|
+
!/^\d+\.\d+\.\d+/.test(config.version)) {
|
|
257
|
+
result.valid = false;
|
|
258
|
+
result.errors.push({
|
|
259
|
+
path: 'version',
|
|
260
|
+
message: 'Invalid version format',
|
|
261
|
+
severity: 'error',
|
|
262
|
+
expected: 'X.Y.Z',
|
|
263
|
+
actual: config.version,
|
|
264
|
+
fix: 'Use semantic version format (e.g., 1.0.0)'
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Suggest fix for JSON parsing errors
|
|
274
|
+
* @param {Error} error - JSON parse error
|
|
275
|
+
* @param {string} filePath - Path to file
|
|
276
|
+
* @returns {string} Fix suggestion
|
|
277
|
+
*/
|
|
278
|
+
_suggestJsonFix(error, filePath) {
|
|
279
|
+
const match = error.message.match(/position (\d+)/);
|
|
280
|
+
if (match) {
|
|
281
|
+
const pos = parseInt(match[1]);
|
|
282
|
+
try {
|
|
283
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
284
|
+
const line = content.substring(0, pos).split('\n').length;
|
|
285
|
+
const col = pos - content.lastIndexOf('\n', pos - 1);
|
|
286
|
+
return `Check line ${line}, column ${col} for syntax errors (missing comma, quote, bracket, etc.)`;
|
|
287
|
+
} catch {
|
|
288
|
+
return `Check around position ${pos} for syntax errors`;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return 'Verify JSON syntax (commas, quotes, brackets are properly closed)';
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Check if AJV is available
|
|
296
|
+
* @returns {boolean}
|
|
297
|
+
*/
|
|
298
|
+
static isAJVAvailable() {
|
|
299
|
+
return Ajv !== null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Create a default validator instance
|
|
305
|
+
*/
|
|
306
|
+
const defaultValidator = new ConfigValidator();
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Convenience functions using default validator
|
|
310
|
+
*/
|
|
311
|
+
function validate(config, schemaName) {
|
|
312
|
+
return defaultValidator.validate(config, schemaName);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function validateFile(configPath, schemaName) {
|
|
316
|
+
return defaultValidator.validateFile(configPath, schemaName);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function validateOrThrow(config, schemaName) {
|
|
320
|
+
return defaultValidator.validateOrThrow(config, schemaName);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
module.exports = {
|
|
324
|
+
ConfigValidator,
|
|
325
|
+
defaultValidator,
|
|
326
|
+
validate,
|
|
327
|
+
validateFile,
|
|
328
|
+
validateOrThrow,
|
|
329
|
+
isAJVAvailable: ConfigValidator.isAJVAvailable
|
|
330
|
+
};
|
package/lib/config.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* Config - Configuration management
|
|
3
3
|
*
|
|
4
4
|
* Loads default config and merges with user config from ~/.claude/config.json
|
|
5
|
+
*
|
|
6
|
+
* v2.0: Supports new ConfigManager with validation, backup, and rollback.
|
|
7
|
+
* Enable with SMC_USE_NEW_CONFIG=1 environment variable.
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
10
|
const fs = require('fs');
|
|
@@ -11,6 +14,25 @@ const defaults = require('../config/defaults.json');
|
|
|
11
14
|
const CONFIG_DIR = path.join(process.env.HOME, '.claude');
|
|
12
15
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
13
16
|
|
|
17
|
+
// Try to load new ConfigManager (v2.0)
|
|
18
|
+
let ConfigManager = null;
|
|
19
|
+
let newManager = null;
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
({ ConfigManager } = require('./config-manager'));
|
|
23
|
+
} catch {
|
|
24
|
+
// New system not available, use legacy
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if new config system should be used
|
|
29
|
+
*/
|
|
30
|
+
function useNewSystem() {
|
|
31
|
+
return process.env.SMC_USE_NEW_CONFIG === '1' ||
|
|
32
|
+
process.env.SMC_CONFIG_V2 === '1' ||
|
|
33
|
+
process.env.SMC_STRICT_CONFIG === '1';
|
|
34
|
+
}
|
|
35
|
+
|
|
14
36
|
/**
|
|
15
37
|
* Deep merge two objects
|
|
16
38
|
*/
|
|
@@ -31,6 +53,24 @@ function deepMerge(target, source) {
|
|
|
31
53
|
* @returns {Object} Merged configuration
|
|
32
54
|
*/
|
|
33
55
|
exports.loadConfig = function() {
|
|
56
|
+
// Use new system if enabled and available
|
|
57
|
+
if (useNewSystem() && ConfigManager) {
|
|
58
|
+
if (!newManager) {
|
|
59
|
+
newManager = new ConfigManager();
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
return newManager.load({ expandEnv: true });
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.warn(`[Config] ${e.message}`);
|
|
65
|
+
if (process.env.SMC_STRICT_CONFIG === '1') {
|
|
66
|
+
throw e;
|
|
67
|
+
}
|
|
68
|
+
// Fall back to legacy on validation error
|
|
69
|
+
console.warn('[Config] Falling back to legacy config system');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Legacy implementation
|
|
34
74
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
35
75
|
try {
|
|
36
76
|
const userConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
@@ -47,10 +87,21 @@ exports.loadConfig = function() {
|
|
|
47
87
|
/**
|
|
48
88
|
* Save configuration to file
|
|
49
89
|
* @param {Object} config - Configuration to save
|
|
90
|
+
* @param {Object} options - Save options
|
|
50
91
|
*/
|
|
51
|
-
exports.saveConfig = function(config) {
|
|
92
|
+
exports.saveConfig = function(config, options = {}) {
|
|
93
|
+
// Use new system if enabled and available
|
|
94
|
+
if (useNewSystem() && ConfigManager) {
|
|
95
|
+
if (!newManager) {
|
|
96
|
+
newManager = new ConfigManager();
|
|
97
|
+
}
|
|
98
|
+
return newManager.save(config, options);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Legacy implementation
|
|
52
102
|
exports.ensureDir(CONFIG_DIR);
|
|
53
103
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
104
|
+
return { success: true };
|
|
54
105
|
};
|
|
55
106
|
|
|
56
107
|
/**
|
package/lib/errors.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured Error Types
|
|
3
|
+
*
|
|
4
|
+
* Provides structured error information with recovery hints.
|
|
5
|
+
* All errors include:
|
|
6
|
+
* - Error code for programmatic handling
|
|
7
|
+
* - Severity level (info/warn/error/critical)
|
|
8
|
+
* - Details object with context
|
|
9
|
+
* - Hints array with recovery suggestions
|
|
10
|
+
* - Optional documentation URL
|
|
11
|
+
*
|
|
12
|
+
* @module lib/errors
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Base SMC Error class
|
|
17
|
+
*/
|
|
18
|
+
class SMCError extends Error {
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} message - Error message
|
|
21
|
+
* @param {Object} options - Error options
|
|
22
|
+
* @param {string} options.code - Error code (e.g., 'ERR_CONFIG')
|
|
23
|
+
* @param {string} options.severity - Severity level: 'info' | 'warn' | 'error' | 'critical'
|
|
24
|
+
* @param {Object} options.details - Additional error details
|
|
25
|
+
* @param {string[]} options.hints - Recovery hints
|
|
26
|
+
* @param {string} options.docUrl - Documentation URL
|
|
27
|
+
*/
|
|
28
|
+
constructor(message, options = {}) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = this.constructor.name;
|
|
31
|
+
this.code = options.code || 'ERR_UNKNOWN';
|
|
32
|
+
this.severity = options.severity || 'error';
|
|
33
|
+
this.details = options.details || {};
|
|
34
|
+
this.hints = options.hints || [];
|
|
35
|
+
this.docUrl = options.docUrl;
|
|
36
|
+
|
|
37
|
+
// Maintain proper stack trace
|
|
38
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Convert error to JSON-serializable object
|
|
43
|
+
*/
|
|
44
|
+
toJSON() {
|
|
45
|
+
return {
|
|
46
|
+
name: this.name,
|
|
47
|
+
code: this.code,
|
|
48
|
+
message: this.message,
|
|
49
|
+
severity: this.severity,
|
|
50
|
+
details: this.details,
|
|
51
|
+
hints: this.hints,
|
|
52
|
+
docUrl: this.docUrl
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Format error for console output
|
|
58
|
+
*/
|
|
59
|
+
toString() {
|
|
60
|
+
let output = `[${this.code}] ${this.message}`;
|
|
61
|
+
if (this.hints.length > 0) {
|
|
62
|
+
output += '\n\nSuggestions:\n';
|
|
63
|
+
this.hints.forEach((h, i) => {
|
|
64
|
+
output += ` ${i + 1}. ${h}\n`;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (this.docUrl) {
|
|
68
|
+
output += `\nDocs: ${this.docUrl}\n`;
|
|
69
|
+
}
|
|
70
|
+
return output;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Check if this error has a specific severity level or higher
|
|
75
|
+
* @param {string} minSeverity - Minimum severity to check against
|
|
76
|
+
* @returns {boolean}
|
|
77
|
+
*/
|
|
78
|
+
hasSeverity(minSeverity) {
|
|
79
|
+
const levels = { info: 0, warn: 1, error: 2, critical: 3 };
|
|
80
|
+
return levels[this.severity] >= levels[minSeverity];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Configuration-related errors
|
|
86
|
+
* Used for config file parsing, validation, and loading issues
|
|
87
|
+
*/
|
|
88
|
+
class ConfigError extends SMCError {
|
|
89
|
+
/**
|
|
90
|
+
* @param {string} message - Error message
|
|
91
|
+
* @param {Object[]} errors - Array of validation errors
|
|
92
|
+
* @param {string[]} fixes - Array of auto-fix suggestions
|
|
93
|
+
* @param {Object} options - Additional options passed to SMCError
|
|
94
|
+
*/
|
|
95
|
+
constructor(message, errors = [], fixes = [], options = {}) {
|
|
96
|
+
super(message, {
|
|
97
|
+
code: 'ERR_CONFIG',
|
|
98
|
+
severity: 'critical',
|
|
99
|
+
...options
|
|
100
|
+
});
|
|
101
|
+
this.errors = errors;
|
|
102
|
+
this.fixes = fixes;
|
|
103
|
+
this.details = {
|
|
104
|
+
errorCount: errors.length,
|
|
105
|
+
fixCount: fixes.length,
|
|
106
|
+
criticalCount: errors.filter(e => e.severity === 'critical').length,
|
|
107
|
+
errorCount: errors.length,
|
|
108
|
+
fixCount: fixes.length,
|
|
109
|
+
criticalCount: errors.filter(e => e.severity === 'critical').length
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Format config error for console output
|
|
115
|
+
*/
|
|
116
|
+
toString() {
|
|
117
|
+
let output = `ConfigError: ${this.message}\n`;
|
|
118
|
+
|
|
119
|
+
if (this.details.errorCount > 0) {
|
|
120
|
+
output += `\nErrors (${this.details.errorCount}):\n`;
|
|
121
|
+
this.errors.forEach(e => {
|
|
122
|
+
const icon = e.severity === 'critical' ? 'X' : e.severity === 'error' ? 'E' : 'W';
|
|
123
|
+
output += ` [${icon}] ${e.path}: ${e.message}\n`;
|
|
124
|
+
if (e.fix) {
|
|
125
|
+
output += ` Fix: ${e.fix}\n`;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (this.details.fixCount > 0) {
|
|
131
|
+
output += `\nSuggested fixes:\n`;
|
|
132
|
+
this.fixes.forEach((f, i) => {
|
|
133
|
+
output += ` ${i + 1}. ${f}\n`;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return output;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Validation errors
|
|
143
|
+
* Used when data validation fails
|
|
144
|
+
*/
|
|
145
|
+
class ValidationError extends SMCError {
|
|
146
|
+
constructor(message, options = {}) {
|
|
147
|
+
super(message, {
|
|
148
|
+
code: 'ERR_VALIDATION',
|
|
149
|
+
severity: 'error',
|
|
150
|
+
...options
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Quality gate errors
|
|
157
|
+
* Used when quality checks fail
|
|
158
|
+
*/
|
|
159
|
+
class QualityGateError extends SMCError {
|
|
160
|
+
/**
|
|
161
|
+
* @param {string} message - Error message
|
|
162
|
+
* @param {Object} results - Quality check results
|
|
163
|
+
* @param {Object} options - Additional options
|
|
164
|
+
*/
|
|
165
|
+
constructor(message, results = {}, options = {}) {
|
|
166
|
+
super(message, {
|
|
167
|
+
code: 'ERR_QUALITY_GATE',
|
|
168
|
+
severity: 'error',
|
|
169
|
+
...options
|
|
170
|
+
});
|
|
171
|
+
this.results = results;
|
|
172
|
+
this.details = {
|
|
173
|
+
passed: results.passed || false,
|
|
174
|
+
total: results.summary?.total || 0,
|
|
175
|
+
critical: results.summary?.critical || 0,
|
|
176
|
+
error: results.summary?.error || 0,
|
|
177
|
+
warn: results.summary?.warn || 0
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Migration errors
|
|
184
|
+
* Used during config migration
|
|
185
|
+
*/
|
|
186
|
+
class MigrationError extends SMCError {
|
|
187
|
+
constructor(message, options = {}) {
|
|
188
|
+
super(message, {
|
|
189
|
+
code: 'ERR_MIGRATION',
|
|
190
|
+
severity: 'critical',
|
|
191
|
+
...options
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* File operation errors
|
|
198
|
+
* Used for file read/write issues
|
|
199
|
+
*/
|
|
200
|
+
class FileError extends SMCError {
|
|
201
|
+
constructor(message, filePath, options = {}) {
|
|
202
|
+
super(message, {
|
|
203
|
+
code: 'ERR_FILE',
|
|
204
|
+
severity: 'error',
|
|
205
|
+
...options
|
|
206
|
+
});
|
|
207
|
+
this.filePath = filePath;
|
|
208
|
+
this.details.file = filePath;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Rule execution errors
|
|
214
|
+
* Used when a quality rule fails to execute
|
|
215
|
+
*/
|
|
216
|
+
class RuleError extends SMCError {
|
|
217
|
+
constructor(message, ruleId, options = {}) {
|
|
218
|
+
super(message, {
|
|
219
|
+
code: 'ERR_RULE',
|
|
220
|
+
severity: 'warn',
|
|
221
|
+
...options
|
|
222
|
+
});
|
|
223
|
+
this.ruleId = ruleId;
|
|
224
|
+
this.details.rule = ruleId;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Parse error details from AJV validation output
|
|
230
|
+
* @param {Object[]} ajvErrors - AJV validation errors
|
|
231
|
+
* @returns {Object[]} Parsed error objects
|
|
232
|
+
*/
|
|
233
|
+
function parseAJVErrors(ajvErrors) {
|
|
234
|
+
return ajvErrors.map(error => {
|
|
235
|
+
const path = error.instancePath || 'root';
|
|
236
|
+
const message = error.message || 'Validation failed';
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
path,
|
|
240
|
+
message,
|
|
241
|
+
severity: getSeverityFromKeyword(error.keyword),
|
|
242
|
+
expected: error.schema?.type || error.schema?.enum?.join('|'),
|
|
243
|
+
actual: error.data,
|
|
244
|
+
keyword: error.keyword,
|
|
245
|
+
fix: generateFixFromError(error)
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Map AJV keyword to severity level
|
|
252
|
+
* @param {string} keyword - AJV error keyword
|
|
253
|
+
* @returns {string} Severity level
|
|
254
|
+
*/
|
|
255
|
+
function getSeverityFromKeyword(keyword) {
|
|
256
|
+
const severityMap = {
|
|
257
|
+
required: 'critical',
|
|
258
|
+
type: 'error',
|
|
259
|
+
enum: 'error',
|
|
260
|
+
pattern: 'warn',
|
|
261
|
+
format: 'warn',
|
|
262
|
+
minimum: 'warn',
|
|
263
|
+
maximum: 'warn',
|
|
264
|
+
minLength: 'warn',
|
|
265
|
+
maxLength: 'warn'
|
|
266
|
+
};
|
|
267
|
+
return severityMap[keyword] || 'warn';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Generate auto-fix suggestion from AJV error
|
|
272
|
+
* @param {Object} error - AJV error object
|
|
273
|
+
* @returns {string|null} Fix suggestion
|
|
274
|
+
*/
|
|
275
|
+
function generateFixFromError(error) {
|
|
276
|
+
const fixes = {
|
|
277
|
+
required: `Add missing field: ${error.params?.missingProperty}`,
|
|
278
|
+
pattern: `Value must match pattern: ${error.schema?.pattern}`,
|
|
279
|
+
enum: `Value must be one of: ${error.schema?.enum?.join(', ')}`,
|
|
280
|
+
type: `Change type to: ${error.schema?.type}`,
|
|
281
|
+
minimum: `Value must be >= ${error.schema?.minimum}`,
|
|
282
|
+
maximum: `Value must be <= ${error.schema?.maximum}`,
|
|
283
|
+
minLength: `Length must be >= ${error.schema?.minLength}`,
|
|
284
|
+
maxLength: `Length must be <= ${error.schema?.maxLength}`
|
|
285
|
+
};
|
|
286
|
+
return fixes[error.keyword] || null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
module.exports = {
|
|
290
|
+
// Base class
|
|
291
|
+
SMCError,
|
|
292
|
+
|
|
293
|
+
// Specific error types
|
|
294
|
+
ConfigError,
|
|
295
|
+
ValidationError,
|
|
296
|
+
QualityGateError,
|
|
297
|
+
MigrationError,
|
|
298
|
+
FileError,
|
|
299
|
+
RuleError,
|
|
300
|
+
|
|
301
|
+
// Utility functions
|
|
302
|
+
parseAJVErrors,
|
|
303
|
+
getSeverityFromKeyword,
|
|
304
|
+
generateFixFromError
|
|
305
|
+
};
|