timonel 3.0.0-beta.1 → 3.1.0-beta.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 (52) hide show
  1. package/CHANGELOG.md +192 -0
  2. package/README.md +606 -119
  3. package/SECURITY.md +25 -11
  4. package/dist/cli.js +54 -15
  5. package/dist/index.d.ts +3 -0
  6. package/dist/index.js +2 -0
  7. package/dist/lib/helm.js +28 -1
  8. package/dist/lib/helmChartWriter.js +27 -8
  9. package/dist/lib/policy/configurationLoader.d.ts +46 -0
  10. package/dist/lib/policy/configurationLoader.js +251 -0
  11. package/dist/lib/policy/errorContextGenerator.d.ts +63 -0
  12. package/dist/lib/policy/errorContextGenerator.js +302 -0
  13. package/dist/lib/policy/errors.d.ts +40 -0
  14. package/dist/lib/policy/errors.js +109 -0
  15. package/dist/lib/policy/index.d.ts +11 -0
  16. package/dist/lib/policy/index.js +10 -0
  17. package/dist/lib/policy/parallelExecutor.d.ts +58 -0
  18. package/dist/lib/policy/parallelExecutor.js +215 -0
  19. package/dist/lib/policy/pluginLoader.d.ts +41 -0
  20. package/dist/lib/policy/pluginLoader.js +220 -0
  21. package/dist/lib/policy/pluginRegistry.d.ts +14 -0
  22. package/dist/lib/policy/pluginRegistry.js +69 -0
  23. package/dist/lib/policy/policyEngine.d.ts +39 -0
  24. package/dist/lib/policy/policyEngine.js +495 -0
  25. package/dist/lib/policy/resultAggregator.d.ts +8 -0
  26. package/dist/lib/policy/resultAggregator.js +138 -0
  27. package/dist/lib/policy/resultFormatter.d.ts +25 -0
  28. package/dist/lib/policy/resultFormatter.js +217 -0
  29. package/dist/lib/policy/types.d.ts +111 -0
  30. package/dist/lib/policy/types.js +1 -0
  31. package/dist/lib/policy/validationCache.d.ts +58 -0
  32. package/dist/lib/policy/validationCache.js +289 -0
  33. package/dist/lib/resources/baseResourceProvider.js +5 -0
  34. package/dist/lib/resources/cloud/aws/awsResources.js +2 -1
  35. package/dist/lib/resources/cloud/aws/karpenterResources.js +16 -2
  36. package/dist/lib/rutter.d.ts +7 -2
  37. package/dist/lib/rutter.js +177 -8
  38. package/dist/lib/security.js +4 -4
  39. package/dist/lib/templates/flexible-subchart.js +18 -7
  40. package/dist/lib/templates/umbrella-chart.js +29 -18
  41. package/dist/lib/umbrellaRutter.d.ts +1 -1
  42. package/dist/lib/umbrellaRutter.js +21 -9
  43. package/dist/lib/utils/envVarsLoader.js +13 -7
  44. package/dist/lib/utils/helmHelpers.js +10 -2
  45. package/dist/lib/utils/helmYamlSerializer.js +19 -9
  46. package/dist/lib/utils/logger.js +54 -39
  47. package/dist/lib/utils/valuesRef.js +9 -0
  48. package/dist/lib/validation/inputValidator.d.ts +26 -0
  49. package/dist/lib/validation/inputValidator.js +176 -0
  50. package/dist/types/index.d.ts +27 -0
  51. package/dist/types/index.js +1 -0
  52. package/package.json +21 -19
@@ -0,0 +1,495 @@
1
+ import { createLogger } from '../utils/logger.js';
2
+ import { InputValidator } from '../validation/inputValidator.js';
3
+ import { PluginRegistry } from './pluginRegistry.js';
4
+ import { PolicyEngineError, ValidationTimeoutError, PluginError, PluginRetryExhaustedError, PolicyValidationError, } from './errors.js';
5
+ import { generateResultSummary } from './resultAggregator.js';
6
+ import { DefaultResultFormatter } from './resultFormatter.js';
7
+ import { ErrorContextGenerator } from './errorContextGenerator.js';
8
+ import { ConfigurationLoader } from './configurationLoader.js';
9
+ import { ValidationCache, generateManifestHash, generatePluginHash } from './validationCache.js';
10
+ import { ParallelExecutor, calculateOptimalConcurrency, } from './parallelExecutor.js';
11
+ const UNKNOWN_ERROR_MESSAGE = 'Unknown error';
12
+ const DEFAULT_OPTIONS = {
13
+ timeout: 5000,
14
+ parallel: false,
15
+ failFast: false,
16
+ gracefulDegradation: true,
17
+ };
18
+ const DEFAULT_RETRY_CONFIG = {
19
+ maxAttempts: 3,
20
+ baseDelay: 1000,
21
+ backoffMultiplier: 2,
22
+ maxDelay: 10000,
23
+ retryOnTimeout: true,
24
+ retryOnPluginError: false,
25
+ };
26
+ const OPERATIONS = {
27
+ PLUGIN_REGISTRATION: 'plugin_registration',
28
+ VALIDATION_START: 'validation_start',
29
+ VALIDATION_COMPLETE: 'validation_complete',
30
+ PLUGIN_EXECUTION_ERROR: 'plugin_execution_error_isolated',
31
+ PLUGIN_EXECUTION_RETRY: 'plugin_execution_retry',
32
+ };
33
+ export class PolicyEngine {
34
+ constructor(options = {}) {
35
+ this.registry = new PluginRegistry();
36
+ this.options = { ...DEFAULT_OPTIONS, ...options };
37
+ this.logger = createLogger('policy-engine');
38
+ this.errorContextGenerator = new ErrorContextGenerator();
39
+ this.inputValidator = new InputValidator(options.validationOptions);
40
+ this.logger.debug('PolicyEngine initialized with lazy loading', {
41
+ options: this.options,
42
+ cacheEnabled: !!options.cacheOptions,
43
+ parallelEnabled: !!options.parallel,
44
+ operation: 'policy_engine_init',
45
+ });
46
+ }
47
+ get configurationLoader() {
48
+ if (!this._configurationLoader) {
49
+ this._configurationLoader = new ConfigurationLoader(this.options.configurationLoader);
50
+ this.logger.debug('ConfigurationLoader initialized lazily', {
51
+ operation: 'lazy_init_config_loader',
52
+ });
53
+ }
54
+ return this._configurationLoader;
55
+ }
56
+ get cache() {
57
+ if (!this._cache) {
58
+ this._cache = new ValidationCache(this.options.cacheOptions);
59
+ this.logger.debug('ValidationCache initialized lazily', {
60
+ cacheEnabled: !!this.options.cacheOptions,
61
+ operation: 'lazy_init_cache',
62
+ });
63
+ }
64
+ return this._cache;
65
+ }
66
+ get parallelExecutor() {
67
+ if (!this._parallelExecutor) {
68
+ const parallelOptions = {
69
+ maxConcurrency: this.options.parallelOptions?.maxConcurrency || calculateOptimalConcurrency(10),
70
+ pluginTimeout: this.options.timeout || DEFAULT_OPTIONS.timeout,
71
+ failFast: this.options.failFast || DEFAULT_OPTIONS.failFast,
72
+ ...this.options.parallelOptions,
73
+ };
74
+ this._parallelExecutor = new ParallelExecutor(parallelOptions);
75
+ this.logger.debug('ParallelExecutor initialized lazily', {
76
+ maxConcurrency: parallelOptions.maxConcurrency,
77
+ operation: 'lazy_init_parallel_executor',
78
+ });
79
+ }
80
+ return this._parallelExecutor;
81
+ }
82
+ async use(plugin) {
83
+ try {
84
+ if (!plugin || typeof plugin !== 'object') {
85
+ throw new PolicyValidationError('Plugin must be a non-null object');
86
+ }
87
+ if (!plugin.name || typeof plugin.name !== 'string') {
88
+ throw new PolicyValidationError('Plugin name must be a non-empty string');
89
+ }
90
+ if (!plugin.version || typeof plugin.version !== 'string') {
91
+ throw new PolicyValidationError('Plugin version must be a non-empty string');
92
+ }
93
+ if (typeof plugin.validate !== 'function') {
94
+ throw new PolicyValidationError('Plugin must have a validate method');
95
+ }
96
+ if (plugin.name.length > 100) {
97
+ throw new PolicyValidationError('Plugin name exceeds maximum length (100 characters)');
98
+ }
99
+ const sanitizedName = plugin.name
100
+ .replace(/[<>"'&]/g, '')
101
+ .trim();
102
+ if (sanitizedName !== plugin.name) {
103
+ this.logger.warn('Plugin name was sanitized', {
104
+ original: plugin.name,
105
+ sanitized: sanitizedName,
106
+ operation: 'plugin_name_sanitization',
107
+ });
108
+ }
109
+ const pluginConfiguration = await this.configurationLoader.loadPluginConfiguration(plugin, this.options.environment, this.options.pluginConfig?.[plugin.name]);
110
+ this.registry.register(plugin, pluginConfiguration.config);
111
+ this.logger.info('Plugin registered successfully', {
112
+ pluginName: plugin.name,
113
+ pluginVersion: plugin.version,
114
+ hasConfig: Object.keys(pluginConfiguration.config).length > 0,
115
+ configValidated: pluginConfiguration.validated,
116
+ configSources: pluginConfiguration.entries.map((e) => e.source),
117
+ operation: OPERATIONS.PLUGIN_REGISTRATION,
118
+ });
119
+ if (pluginConfiguration.validationErrors) {
120
+ this.logger.warn('Plugin configuration validation warnings', {
121
+ pluginName: plugin.name,
122
+ errors: pluginConfiguration.validationErrors,
123
+ operation: 'plugin_config_validation_warnings',
124
+ });
125
+ }
126
+ return this;
127
+ }
128
+ catch (error) {
129
+ this.logger.error('Plugin registration failed', {
130
+ pluginName: plugin?.name || 'unknown',
131
+ error: error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE,
132
+ operation: 'plugin_registration_error',
133
+ });
134
+ throw error;
135
+ }
136
+ }
137
+ async validate(manifests, chartMetadata) {
138
+ const startTime = Date.now();
139
+ if (!Array.isArray(manifests)) {
140
+ throw new PolicyValidationError('Manifests must be an array');
141
+ }
142
+ if (manifests.length > 1000) {
143
+ throw new PolicyValidationError('Too many manifests provided (maximum: 1000)');
144
+ }
145
+ manifests.forEach((manifest, index) => {
146
+ if (manifest === null || manifest === undefined) {
147
+ throw new PolicyValidationError(`Manifest at index ${index} cannot be null or undefined`);
148
+ }
149
+ const manifestStr = JSON.stringify(manifest);
150
+ if (manifestStr.length > 100000) {
151
+ throw new PolicyValidationError(`Manifest at index ${index} exceeds size limit (100KB)`);
152
+ }
153
+ });
154
+ if (!chartMetadata || typeof chartMetadata !== 'object') {
155
+ throw new PolicyValidationError('Chart metadata must be a non-null object');
156
+ }
157
+ const plugins = this.registry.getAllPlugins();
158
+ this.logger.info('Starting policy validation', {
159
+ manifestCount: manifests.length,
160
+ pluginCount: plugins.length,
161
+ operation: OPERATIONS.VALIDATION_START,
162
+ });
163
+ if (plugins.length === 0) {
164
+ const metadata = {
165
+ executionTime: Date.now() - startTime,
166
+ pluginCount: 0,
167
+ manifestCount: manifests.length,
168
+ startTime,
169
+ };
170
+ this.logger.debug('No plugins registered, validation successful', {
171
+ metadata,
172
+ operation: 'validation_no_plugins',
173
+ });
174
+ return {
175
+ valid: true,
176
+ violations: [],
177
+ warnings: [],
178
+ metadata,
179
+ summary: {
180
+ violationsBySeverity: { error: 0, warning: 0, info: 0 },
181
+ violationsByPlugin: {},
182
+ topViolationTypes: [],
183
+ },
184
+ };
185
+ }
186
+ const manifestHash = generateManifestHash(manifests);
187
+ const pluginNames = plugins.map((p) => p.name);
188
+ const pluginConfigs = Object.fromEntries(pluginNames.map((name) => [name, this.registry.getPluginConfig(name)]));
189
+ const pluginHash = generatePluginHash(pluginNames, pluginConfigs);
190
+ const cachedResult = this.cache.get(manifestHash, pluginHash);
191
+ if (cachedResult) {
192
+ this.logger.info('Returning cached validation result', {
193
+ manifestCount: manifests.length,
194
+ pluginCount: plugins.length,
195
+ cacheAge: Date.now() - (cachedResult.metadata.startTime || 0),
196
+ operation: 'validation_cache_hit',
197
+ });
198
+ return cachedResult;
199
+ }
200
+ const violations = [];
201
+ const warnings = [];
202
+ try {
203
+ if (this.options.parallel) {
204
+ await this.executeParallelOptimized(plugins, manifests, violations, warnings, chartMetadata);
205
+ }
206
+ else {
207
+ await this.executeSequential(plugins, manifests, violations, warnings, chartMetadata);
208
+ }
209
+ }
210
+ catch (error) {
211
+ const executionTime = Date.now() - startTime;
212
+ this.logger.error('Policy validation failed', {
213
+ error: error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE,
214
+ executionTime,
215
+ operation: 'validation_error',
216
+ });
217
+ throw new PolicyEngineError(`Validation failed: ${error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE}`);
218
+ }
219
+ const executionTime = Date.now() - startTime;
220
+ const metadata = {
221
+ executionTime,
222
+ pluginCount: plugins.length,
223
+ manifestCount: manifests.length,
224
+ startTime,
225
+ };
226
+ const result = {
227
+ valid: violations.length === 0,
228
+ violations,
229
+ warnings,
230
+ metadata,
231
+ summary: generateResultSummary(violations, warnings, plugins.map((p) => p.name)),
232
+ };
233
+ this.cache.set(manifestHash, pluginHash, result);
234
+ this.logger.info('Policy validation completed', {
235
+ valid: result.valid,
236
+ violationCount: violations.length,
237
+ warningCount: warnings.length,
238
+ executionTime,
239
+ cached: true,
240
+ operation: 'validation_complete',
241
+ });
242
+ return result;
243
+ }
244
+ configure(options) {
245
+ this.options = { ...this.options, ...options };
246
+ this.logger.debug('PolicyEngine configuration updated', {
247
+ options: this.options,
248
+ operation: 'policy_engine_configure',
249
+ });
250
+ return this;
251
+ }
252
+ formatResult(result) {
253
+ const formatter = this.options.formatter || new DefaultResultFormatter();
254
+ return formatter.format(result);
255
+ }
256
+ generateErrorContext(violation, validationContext, allViolations, executionTime) {
257
+ return this.errorContextGenerator.generateContext(violation, validationContext, allViolations, executionTime);
258
+ }
259
+ generateErrorReport(violation, validationContext, allViolations, executionTime) {
260
+ return this.errorContextGenerator.generateErrorReport(violation, validationContext, allViolations, executionTime);
261
+ }
262
+ getCacheStats() {
263
+ return this.cache.getStats();
264
+ }
265
+ invalidateCache(criteria) {
266
+ return this.cache.invalidate(criteria);
267
+ }
268
+ clearCacheStats() {
269
+ this.cache.clearStats();
270
+ }
271
+ async executeSequential(plugins, manifests, violations, warnings, chartMetadata) {
272
+ for (const plugin of plugins) {
273
+ try {
274
+ const pluginViolations = await this.executePlugin(plugin, manifests, chartMetadata);
275
+ for (const violation of pluginViolations) {
276
+ if (violation.severity === 'error') {
277
+ violations.push(violation);
278
+ }
279
+ else {
280
+ warnings.push(violation);
281
+ }
282
+ }
283
+ if (this.options.failFast && violations.length > 0) {
284
+ this.logger.debug('Fail fast enabled, stopping validation', {
285
+ pluginName: plugin.name,
286
+ violationCount: violations.length,
287
+ operation: 'validation_fail_fast',
288
+ });
289
+ break;
290
+ }
291
+ }
292
+ catch (error) {
293
+ if (this.options.gracefulDegradation) {
294
+ this.handlePluginError(plugin, error, violations);
295
+ }
296
+ else {
297
+ throw error;
298
+ }
299
+ }
300
+ }
301
+ }
302
+ async executeParallelOptimized(plugins, manifests, violations, warnings, chartMetadata) {
303
+ const validationContext = {
304
+ chart: chartMetadata,
305
+ environment: this.options.environment || 'development',
306
+ logger: this.logger,
307
+ };
308
+ const { results, stats } = await this.parallelExecutor.executePlugins(plugins, manifests, validationContext);
309
+ this.logger.info('Parallel execution completed with statistics', {
310
+ pluginCount: plugins.length,
311
+ totalExecutionTime: stats.totalExecutionTime,
312
+ averagePluginTime: stats.averagePluginTime,
313
+ concurrencyUtilization: stats.concurrencyUtilization,
314
+ peakConcurrentPlugins: stats.resourceUsage.peakConcurrentPlugins,
315
+ totalMemoryMB: stats.resourceUsage.totalMemoryMB,
316
+ operation: 'parallel_execution_stats',
317
+ });
318
+ for (const result of results) {
319
+ if (result.error) {
320
+ if (this.options.gracefulDegradation) {
321
+ this.handlePluginError(result.plugin, result.error, violations);
322
+ }
323
+ else {
324
+ throw result.error;
325
+ }
326
+ }
327
+ else {
328
+ for (const violation of result.violations) {
329
+ if (violation.severity === 'error') {
330
+ violations.push(violation);
331
+ }
332
+ else {
333
+ warnings.push(violation);
334
+ }
335
+ }
336
+ }
337
+ if (this.options.failFast && violations.length > 0) {
338
+ this.logger.debug('Fail fast enabled, stopping validation', {
339
+ pluginName: result.plugin.name,
340
+ violationCount: violations.length,
341
+ operation: 'validation_fail_fast',
342
+ });
343
+ break;
344
+ }
345
+ }
346
+ }
347
+ async executePlugin(plugin, manifests, chartMetadata) {
348
+ const retryConfig = { ...DEFAULT_RETRY_CONFIG, ...this.options.retryConfig };
349
+ let lastError = null;
350
+ for (let attempt = 1; attempt <= retryConfig.maxAttempts; attempt++) {
351
+ try {
352
+ return await this.executePluginAttempt(plugin, manifests, attempt, chartMetadata);
353
+ }
354
+ catch (error) {
355
+ lastError = error instanceof Error ? error : new Error(String(error));
356
+ this.logger.warn('Plugin execution attempt failed', {
357
+ pluginName: plugin.name,
358
+ attempt,
359
+ maxAttempts: retryConfig.maxAttempts,
360
+ error: lastError.message,
361
+ operation: 'plugin_execution_retry',
362
+ });
363
+ const shouldRetry = this.shouldRetryError(lastError, retryConfig);
364
+ if (!shouldRetry || attempt === retryConfig.maxAttempts) {
365
+ break;
366
+ }
367
+ const delay = Math.min(retryConfig.baseDelay * Math.pow(retryConfig.backoffMultiplier, attempt - 1), retryConfig.maxDelay);
368
+ this.logger.debug('Retrying plugin execution after delay', {
369
+ pluginName: plugin.name,
370
+ attempt: attempt + 1,
371
+ delay,
372
+ operation: 'plugin_execution_retry_delay',
373
+ });
374
+ await this.sleep(delay);
375
+ }
376
+ }
377
+ const finalError = lastError ||
378
+ new Error(`Plugin '${plugin.name}' failed with unknown error after ${retryConfig.maxAttempts} attempts`);
379
+ throw new PluginRetryExhaustedError(`Plugin '${plugin.name}' failed after ${retryConfig.maxAttempts} attempts`, plugin.name, retryConfig.maxAttempts, finalError);
380
+ }
381
+ async executePluginAttempt(plugin, manifests, attempt, chartMetadata) {
382
+ const timeout = this.options.timeout || DEFAULT_OPTIONS.timeout;
383
+ this.logger.debug('Executing plugin attempt', {
384
+ pluginName: plugin.name,
385
+ attempt,
386
+ timeout,
387
+ operation: 'plugin_execution_attempt_start',
388
+ });
389
+ const validationContext = {
390
+ chart: chartMetadata,
391
+ config: this.registry.getPluginConfig(plugin.name),
392
+ environment: this.options.environment || 'development',
393
+ logger: this.logger,
394
+ };
395
+ const timeoutPromise = this.createTimeoutPromise(timeout, plugin.name);
396
+ const validationPromise = plugin.validate(manifests, validationContext);
397
+ try {
398
+ const result = await Promise.race([validationPromise, timeoutPromise]);
399
+ this.logger.debug('Plugin execution attempt completed', {
400
+ pluginName: plugin.name,
401
+ attempt,
402
+ violationCount: result.length,
403
+ operation: 'plugin_execution_attempt_complete',
404
+ });
405
+ return result;
406
+ }
407
+ catch (error) {
408
+ if (error instanceof ValidationTimeoutError) {
409
+ this.logger.warn('Plugin execution attempt timed out', {
410
+ pluginName: plugin.name,
411
+ attempt,
412
+ timeout,
413
+ operation: 'plugin_execution_attempt_timeout',
414
+ });
415
+ }
416
+ else {
417
+ this.logger.warn('Plugin execution attempt failed', {
418
+ pluginName: plugin.name,
419
+ attempt,
420
+ error: error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE,
421
+ operation: 'plugin_execution_attempt_error',
422
+ });
423
+ }
424
+ throw error;
425
+ }
426
+ }
427
+ shouldRetryError(error, retryConfig) {
428
+ if (error instanceof ValidationTimeoutError) {
429
+ return retryConfig.retryOnTimeout;
430
+ }
431
+ if (error instanceof PluginError) {
432
+ return retryConfig.retryOnPluginError;
433
+ }
434
+ return retryConfig.retryOnPluginError;
435
+ }
436
+ sleep(ms) {
437
+ return new Promise((resolve) => globalThis.setTimeout(resolve, ms));
438
+ }
439
+ createTimeoutPromise(timeout, pluginName) {
440
+ return new Promise((_, reject) => {
441
+ globalThis.setTimeout(() => {
442
+ reject(new ValidationTimeoutError(pluginName, timeout));
443
+ }, timeout);
444
+ });
445
+ }
446
+ handlePluginError(plugin, error, violations) {
447
+ const errorMessage = error instanceof Error ? error.message : UNKNOWN_ERROR_MESSAGE;
448
+ const errorStack = error instanceof Error ? error.stack : String(error);
449
+ this.logger.warn('Plugin execution failed with error isolation', {
450
+ pluginName: plugin.name,
451
+ pluginVersion: plugin.version,
452
+ error: errorMessage,
453
+ errorType: error instanceof Error ? error.constructor.name : typeof error,
454
+ operation: 'plugin_execution_error_isolated',
455
+ });
456
+ const errorContext = {
457
+ error: errorStack,
458
+ pluginVersion: plugin.version,
459
+ pluginMetadata: plugin.metadata,
460
+ timestamp: new Date().toISOString(),
461
+ errorType: error instanceof Error ? error.constructor.name : typeof error,
462
+ isolated: true,
463
+ };
464
+ const errorViolation = {
465
+ plugin: plugin.name,
466
+ severity: 'error',
467
+ message: `Plugin execution failed: ${errorMessage}`,
468
+ resourcePath: 'plugin-execution',
469
+ field: 'validate',
470
+ suggestion: this.generateErrorSuggestion(error, plugin),
471
+ context: errorContext,
472
+ };
473
+ violations.push(errorViolation);
474
+ this.logger.debug('Plugin error context generated', {
475
+ pluginName: plugin.name,
476
+ errorContext,
477
+ operation: 'plugin_error_context',
478
+ });
479
+ }
480
+ generateErrorSuggestion(error, plugin) {
481
+ if (error instanceof ValidationTimeoutError) {
482
+ return `Consider increasing the timeout value or optimizing the plugin '${plugin.name}' for better performance.`;
483
+ }
484
+ if (error instanceof PluginRetryExhaustedError) {
485
+ return `Plugin '${plugin.name}' failed after multiple retry attempts. Check plugin configuration and dependencies.`;
486
+ }
487
+ if (error instanceof TypeError) {
488
+ return `Plugin '${plugin.name}' encountered a type error. Verify the plugin implementation and input validation.`;
489
+ }
490
+ if (error instanceof ReferenceError) {
491
+ return `Plugin '${plugin.name}' referenced an undefined variable or function. Check plugin dependencies and imports.`;
492
+ }
493
+ return `Review plugin '${plugin.name}' implementation and ensure it handles all edge cases properly.`;
494
+ }
495
+ }
@@ -0,0 +1,8 @@
1
+ import type { PolicyResult, PolicyViolation, PolicyWarning, ResultSummary } from './types.js';
2
+ export declare function aggregateResults(results: PolicyResult[]): PolicyResult;
3
+ export declare function generateResultSummary(violations: PolicyViolation[], warnings: PolicyWarning[], allPlugins?: string[]): ResultSummary;
4
+ export declare function filterViolationsBySeverity(violations: PolicyViolation[], severity: 'error' | 'warning' | 'info'): PolicyViolation[];
5
+ export declare function groupViolationsByPlugin(violations: PolicyViolation[]): Record<string, PolicyViolation[]>;
6
+ export declare function sortViolationsBySeverity(violations: PolicyViolation[]): PolicyViolation[];
7
+ export declare function createEmptyResult(): PolicyResult;
8
+ export declare function mergeViolationContexts(contexts: Array<Record<string, unknown> | undefined>): Record<string, unknown>;
@@ -0,0 +1,138 @@
1
+ export function aggregateResults(results) {
2
+ if (results.length === 0) {
3
+ return createEmptyResult();
4
+ }
5
+ if (results.length === 1) {
6
+ const result = results[0];
7
+ return result || createEmptyResult();
8
+ }
9
+ const violations = [];
10
+ const warnings = [];
11
+ let totalExecutionTime = 0;
12
+ let totalPluginCount = 0;
13
+ let totalManifestCount = 0;
14
+ let earliestStartTime;
15
+ for (const result of results) {
16
+ violations.push(...result.violations);
17
+ warnings.push(...result.warnings);
18
+ totalExecutionTime += result.metadata.executionTime;
19
+ totalPluginCount += result.metadata.pluginCount;
20
+ totalManifestCount = Math.max(totalManifestCount, result.metadata.manifestCount);
21
+ if (result.metadata.startTime &&
22
+ (!earliestStartTime || result.metadata.startTime < earliestStartTime)) {
23
+ earliestStartTime = result.metadata.startTime;
24
+ }
25
+ }
26
+ const metadata = {
27
+ executionTime: totalExecutionTime,
28
+ pluginCount: totalPluginCount,
29
+ manifestCount: totalManifestCount,
30
+ ...(earliestStartTime && { startTime: earliestStartTime }),
31
+ aggregatedResults: results.length,
32
+ };
33
+ const aggregatedResult = {
34
+ valid: violations.length === 0,
35
+ violations,
36
+ warnings,
37
+ metadata,
38
+ summary: generateResultSummary(violations, warnings),
39
+ };
40
+ return aggregatedResult;
41
+ }
42
+ export function generateResultSummary(violations, warnings, allPlugins) {
43
+ const allViolations = [...violations, ...warnings];
44
+ const violationsBySeverity = {
45
+ error: violations.filter((v) => v.severity === 'error').length,
46
+ warning: allViolations.filter((v) => v.severity === 'warning').length,
47
+ info: allViolations.filter((v) => v.severity === 'info').length,
48
+ };
49
+ const violationsByPlugin = Object.create(null);
50
+ if (allPlugins) {
51
+ for (const pluginName of allPlugins) {
52
+ violationsByPlugin[pluginName] = 0;
53
+ }
54
+ }
55
+ for (const violation of allViolations) {
56
+ const pluginName = violation.plugin;
57
+ violationsByPlugin[pluginName] = (violationsByPlugin[pluginName] || 0) + 1;
58
+ }
59
+ const violationTypeCount = Object.create(null);
60
+ for (const violation of allViolations) {
61
+ const type = extractViolationType(violation.message);
62
+ violationTypeCount[type] = (violationTypeCount[type] || 0) + 1;
63
+ }
64
+ const topViolationTypes = Object.entries(violationTypeCount)
65
+ .map(([type, count]) => ({ type, count }))
66
+ .sort((a, b) => b.count - a.count)
67
+ .slice(0, 5);
68
+ return {
69
+ violationsBySeverity,
70
+ violationsByPlugin,
71
+ topViolationTypes,
72
+ };
73
+ }
74
+ export function filterViolationsBySeverity(violations, severity) {
75
+ return violations.filter((v) => v.severity === severity);
76
+ }
77
+ export function groupViolationsByPlugin(violations) {
78
+ const grouped = Object.create(null);
79
+ for (const violation of violations) {
80
+ const pluginName = violation.plugin;
81
+ if (!grouped[pluginName]) {
82
+ grouped[pluginName] = [];
83
+ }
84
+ grouped[pluginName].push(violation);
85
+ }
86
+ return grouped;
87
+ }
88
+ export function sortViolationsBySeverity(violations) {
89
+ const severityOrder = { error: 0, warning: 1, info: 2 };
90
+ return [...violations].sort((a, b) => {
91
+ const orderA = severityOrder[a.severity];
92
+ const orderB = severityOrder[b.severity];
93
+ if (orderA !== orderB) {
94
+ return orderA - orderB;
95
+ }
96
+ if (a.plugin !== b.plugin) {
97
+ return a.plugin.localeCompare(b.plugin);
98
+ }
99
+ return a.message.localeCompare(b.message);
100
+ });
101
+ }
102
+ export function createEmptyResult() {
103
+ const metadata = {
104
+ executionTime: 0,
105
+ pluginCount: 0,
106
+ manifestCount: 0,
107
+ };
108
+ return {
109
+ valid: true,
110
+ violations: [],
111
+ warnings: [],
112
+ metadata,
113
+ summary: {
114
+ violationsBySeverity: { error: 0, warning: 0, info: 0 },
115
+ violationsByPlugin: {},
116
+ topViolationTypes: [],
117
+ },
118
+ };
119
+ }
120
+ function extractViolationType(message) {
121
+ const sentences = message.split('.');
122
+ const firstSentence = sentences.length > 0 ? sentences[0] : message;
123
+ if (firstSentence && firstSentence.length <= 50) {
124
+ return firstSentence.trim();
125
+ }
126
+ return message.substring(0, 50).trim() + '...';
127
+ }
128
+ export function mergeViolationContexts(contexts) {
129
+ const merged = Object.create(null);
130
+ for (const context of contexts) {
131
+ if (context && typeof context === 'object') {
132
+ for (const [key, value] of Object.entries(context)) {
133
+ merged[key] = value;
134
+ }
135
+ }
136
+ }
137
+ return merged;
138
+ }
@@ -0,0 +1,25 @@
1
+ import type { PolicyResult, ResultFormatter } from './types.js';
2
+ export declare class DefaultResultFormatter implements ResultFormatter {
3
+ format(result: PolicyResult): string;
4
+ private formatViolation;
5
+ private getSeverityIcon;
6
+ }
7
+ export declare class JsonResultFormatter implements ResultFormatter {
8
+ private indent;
9
+ constructor(indent?: number);
10
+ format(result: PolicyResult): string;
11
+ }
12
+ export declare class CompactResultFormatter implements ResultFormatter {
13
+ format(result: PolicyResult): string;
14
+ }
15
+ export declare class GitHubActionsResultFormatter implements ResultFormatter {
16
+ format(result: PolicyResult): string;
17
+ }
18
+ export declare class SarifResultFormatter implements ResultFormatter {
19
+ format(result: PolicyResult): string;
20
+ private convertViolationsToSarifResults;
21
+ private generateRuleId;
22
+ private mapSeverityToSarifLevel;
23
+ }
24
+ export declare function createFormatter(name: string, options?: Record<string, unknown>): ResultFormatter;
25
+ export declare function getAvailableFormatters(): string[];