musubi-sdd 3.10.0 → 5.1.0
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/README.md +24 -19
- package/package.json +1 -1
- package/src/agents/agent-loop.js +532 -0
- package/src/agents/agentic/code-generator.js +767 -0
- package/src/agents/agentic/code-reviewer.js +698 -0
- package/src/agents/agentic/index.js +43 -0
- package/src/agents/function-tool.js +432 -0
- package/src/agents/index.js +45 -0
- package/src/agents/schema-generator.js +514 -0
- package/src/analyzers/ast-extractor.js +870 -0
- package/src/analyzers/context-optimizer.js +681 -0
- package/src/analyzers/repository-map.js +692 -0
- package/src/integrations/index.js +7 -1
- package/src/integrations/mcp/index.js +175 -0
- package/src/integrations/mcp/mcp-context-provider.js +472 -0
- package/src/integrations/mcp/mcp-discovery.js +436 -0
- package/src/integrations/mcp/mcp-tool-registry.js +467 -0
- package/src/integrations/mcp-connector.js +818 -0
- package/src/integrations/tool-discovery.js +589 -0
- package/src/managers/index.js +7 -0
- package/src/managers/skill-tools.js +565 -0
- package/src/monitoring/cost-tracker.js +7 -0
- package/src/monitoring/incident-manager.js +10 -0
- package/src/monitoring/observability.js +10 -0
- package/src/monitoring/quality-dashboard.js +491 -0
- package/src/monitoring/release-manager.js +10 -0
- package/src/orchestration/agent-skill-binding.js +655 -0
- package/src/orchestration/error-handler.js +827 -0
- package/src/orchestration/index.js +235 -1
- package/src/orchestration/mcp-tool-adapters.js +896 -0
- package/src/orchestration/reasoning/index.js +58 -0
- package/src/orchestration/reasoning/planning-engine.js +831 -0
- package/src/orchestration/reasoning/reasoning-engine.js +710 -0
- package/src/orchestration/reasoning/self-correction.js +751 -0
- package/src/orchestration/skill-executor.js +665 -0
- package/src/orchestration/skill-registry.js +650 -0
- package/src/orchestration/workflow-examples.js +1072 -0
- package/src/orchestration/workflow-executor.js +779 -0
- package/src/phase4-integration.js +248 -0
- package/src/phase5-integration.js +402 -0
- package/src/steering/steering-auto-update.js +572 -0
- package/src/steering/steering-validator.js +547 -0
- package/src/templates/template-constraints.js +646 -0
- package/src/validators/advanced-validation.js +580 -0
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file steering-validator.js
|
|
3
|
+
* @description Steering file validation and consistency checks
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*
|
|
6
|
+
* Part of MUSUBI v5.0.0 - Phase 5 Advanced Features
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { EventEmitter } = require('events');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validation severity levels
|
|
17
|
+
* @enum {string}
|
|
18
|
+
*/
|
|
19
|
+
const SEVERITY = {
|
|
20
|
+
INFO: 'info',
|
|
21
|
+
WARNING: 'warning',
|
|
22
|
+
ERROR: 'error',
|
|
23
|
+
CRITICAL: 'critical'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validation rule types
|
|
28
|
+
* @enum {string}
|
|
29
|
+
*/
|
|
30
|
+
const RULE_TYPE = {
|
|
31
|
+
REQUIRED: 'required',
|
|
32
|
+
FORMAT: 'format',
|
|
33
|
+
CONSISTENCY: 'consistency',
|
|
34
|
+
REFERENCE: 'reference',
|
|
35
|
+
COMPLETENESS: 'completeness'
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @typedef {Object} ValidationIssue
|
|
40
|
+
* @property {string} id - Issue identifier
|
|
41
|
+
* @property {string} file - File with issue
|
|
42
|
+
* @property {string} type - Issue type
|
|
43
|
+
* @property {string} severity - Issue severity
|
|
44
|
+
* @property {string} message - Issue message
|
|
45
|
+
* @property {number} [line] - Line number
|
|
46
|
+
* @property {string} [suggestion] - Fix suggestion
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @typedef {Object} ValidationResult
|
|
51
|
+
* @property {boolean} valid - Overall validity
|
|
52
|
+
* @property {number} score - Validation score (0-100)
|
|
53
|
+
* @property {ValidationIssue[]} issues - Found issues
|
|
54
|
+
* @property {Object} summary - Validation summary
|
|
55
|
+
* @property {number} timestamp - Validation timestamp
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Default validation rules
|
|
60
|
+
*/
|
|
61
|
+
const DEFAULT_VALIDATION_RULES = [
|
|
62
|
+
// Structure.md rules
|
|
63
|
+
{
|
|
64
|
+
id: 'structure-has-overview',
|
|
65
|
+
file: 'structure.md',
|
|
66
|
+
type: RULE_TYPE.REQUIRED,
|
|
67
|
+
severity: SEVERITY.ERROR,
|
|
68
|
+
check: (content) => content.includes('## Overview') || content.includes('# '),
|
|
69
|
+
message: 'structure.md must have an Overview section'
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'structure-has-directories',
|
|
73
|
+
file: 'structure.md',
|
|
74
|
+
type: RULE_TYPE.REQUIRED,
|
|
75
|
+
severity: SEVERITY.WARNING,
|
|
76
|
+
check: (content) => content.includes('src/') || content.includes('lib/'),
|
|
77
|
+
message: 'structure.md should document directory structure'
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// Tech.md rules
|
|
81
|
+
{
|
|
82
|
+
id: 'tech-has-stack',
|
|
83
|
+
file: 'tech.md',
|
|
84
|
+
type: RULE_TYPE.REQUIRED,
|
|
85
|
+
severity: SEVERITY.ERROR,
|
|
86
|
+
check: (content) => content.includes('stack') || content.includes('technologies'),
|
|
87
|
+
message: 'tech.md must document the technology stack',
|
|
88
|
+
suggestion: 'Add a "Technology Stack" section'
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: 'tech-has-dependencies',
|
|
92
|
+
file: 'tech.md',
|
|
93
|
+
type: RULE_TYPE.REQUIRED,
|
|
94
|
+
severity: SEVERITY.WARNING,
|
|
95
|
+
check: (content) => content.includes('dependencies') || content.includes('package'),
|
|
96
|
+
message: 'tech.md should document dependencies'
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
// Product.md rules
|
|
100
|
+
{
|
|
101
|
+
id: 'product-has-vision',
|
|
102
|
+
file: 'product.md',
|
|
103
|
+
type: RULE_TYPE.REQUIRED,
|
|
104
|
+
severity: SEVERITY.ERROR,
|
|
105
|
+
check: (content) => content.includes('vision') || content.includes('purpose') || content.includes('goal'),
|
|
106
|
+
message: 'product.md must have a vision/purpose section'
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: 'product-has-features',
|
|
110
|
+
file: 'product.md',
|
|
111
|
+
type: RULE_TYPE.REQUIRED,
|
|
112
|
+
severity: SEVERITY.WARNING,
|
|
113
|
+
check: (content) => content.includes('feature') || content.includes('capability'),
|
|
114
|
+
message: 'product.md should list features or capabilities'
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// Constitution rules
|
|
118
|
+
{
|
|
119
|
+
id: 'constitution-has-articles',
|
|
120
|
+
file: 'rules/constitution.md',
|
|
121
|
+
type: RULE_TYPE.REQUIRED,
|
|
122
|
+
severity: SEVERITY.ERROR,
|
|
123
|
+
check: (content) => content.includes('Article') || content.includes('Rule'),
|
|
124
|
+
message: 'constitution.md must define governance articles'
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
// Format rules
|
|
128
|
+
{
|
|
129
|
+
id: 'format-valid-markdown',
|
|
130
|
+
file: '*',
|
|
131
|
+
type: RULE_TYPE.FORMAT,
|
|
132
|
+
severity: SEVERITY.WARNING,
|
|
133
|
+
check: (content) => {
|
|
134
|
+
// Check for broken links format
|
|
135
|
+
const brokenLinks = content.match(/\[([^\]]*)\]\(\s*\)/g);
|
|
136
|
+
return !brokenLinks || brokenLinks.length === 0;
|
|
137
|
+
},
|
|
138
|
+
message: 'File contains empty markdown links'
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: 'format-no-todo-in-production',
|
|
142
|
+
file: '*',
|
|
143
|
+
type: RULE_TYPE.COMPLETENESS,
|
|
144
|
+
severity: SEVERITY.INFO,
|
|
145
|
+
check: (content) => !content.includes('[TODO]') && !content.includes('[TBD]'),
|
|
146
|
+
message: 'File contains TODO or TBD markers'
|
|
147
|
+
}
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Steering Validator class
|
|
152
|
+
* @extends EventEmitter
|
|
153
|
+
*/
|
|
154
|
+
class SteeringValidator extends EventEmitter {
|
|
155
|
+
/**
|
|
156
|
+
* Create steering validator
|
|
157
|
+
* @param {Object} [options={}] - Options
|
|
158
|
+
*/
|
|
159
|
+
constructor(options = {}) {
|
|
160
|
+
super();
|
|
161
|
+
|
|
162
|
+
this.steeringPath = options.steeringPath || 'steering';
|
|
163
|
+
this.rules = [...DEFAULT_VALIDATION_RULES, ...(options.rules || [])];
|
|
164
|
+
this.strictMode = options.strictMode ?? false;
|
|
165
|
+
|
|
166
|
+
// State
|
|
167
|
+
this.validations = new Map();
|
|
168
|
+
this.validationCounter = 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Validate all steering files
|
|
173
|
+
* @param {string} [basePath='.'] - Base path
|
|
174
|
+
* @returns {Promise<ValidationResult>}
|
|
175
|
+
*/
|
|
176
|
+
async validate(basePath = '.') {
|
|
177
|
+
const id = `validation-${++this.validationCounter}`;
|
|
178
|
+
this.emit('validation:start', { id, basePath });
|
|
179
|
+
|
|
180
|
+
const steeringDir = path.join(basePath, this.steeringPath);
|
|
181
|
+
const issues = [];
|
|
182
|
+
|
|
183
|
+
// Define files to check
|
|
184
|
+
const files = [
|
|
185
|
+
'structure.md',
|
|
186
|
+
'tech.md',
|
|
187
|
+
'product.md',
|
|
188
|
+
'rules/constitution.md'
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
// Check each file
|
|
192
|
+
for (const file of files) {
|
|
193
|
+
const filePath = path.join(steeringDir, file);
|
|
194
|
+
|
|
195
|
+
if (!fs.existsSync(filePath)) {
|
|
196
|
+
issues.push({
|
|
197
|
+
id: `issue-${issues.length + 1}`,
|
|
198
|
+
file,
|
|
199
|
+
type: RULE_TYPE.REQUIRED,
|
|
200
|
+
severity: SEVERITY.ERROR,
|
|
201
|
+
message: `Required steering file "${file}" not found`,
|
|
202
|
+
suggestion: `Create ${file} with appropriate content`
|
|
203
|
+
});
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
209
|
+
const fileIssues = await this.validateFile(file, content);
|
|
210
|
+
issues.push(...fileIssues);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
issues.push({
|
|
213
|
+
id: `issue-${issues.length + 1}`,
|
|
214
|
+
file,
|
|
215
|
+
type: RULE_TYPE.FORMAT,
|
|
216
|
+
severity: SEVERITY.ERROR,
|
|
217
|
+
message: `Error reading file: ${error.message}`
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Check custom steering files
|
|
223
|
+
const customDir = path.join(steeringDir, 'custom');
|
|
224
|
+
if (fs.existsSync(customDir)) {
|
|
225
|
+
const customFiles = fs.readdirSync(customDir).filter(f => f.endsWith('.md'));
|
|
226
|
+
for (const file of customFiles) {
|
|
227
|
+
const filePath = path.join(customDir, file);
|
|
228
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
229
|
+
const fileIssues = await this.validateFile(`custom/${file}`, content);
|
|
230
|
+
issues.push(...fileIssues);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Cross-file consistency checks
|
|
235
|
+
const consistencyIssues = await this.checkConsistency(basePath);
|
|
236
|
+
issues.push(...consistencyIssues);
|
|
237
|
+
|
|
238
|
+
// Calculate score
|
|
239
|
+
const score = this.calculateScore(issues);
|
|
240
|
+
|
|
241
|
+
// Create result
|
|
242
|
+
const result = {
|
|
243
|
+
id,
|
|
244
|
+
valid: issues.filter(i => i.severity === SEVERITY.ERROR || i.severity === SEVERITY.CRITICAL).length === 0,
|
|
245
|
+
score,
|
|
246
|
+
issues,
|
|
247
|
+
summary: this.createSummary(issues),
|
|
248
|
+
timestamp: Date.now()
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
this.validations.set(id, result);
|
|
252
|
+
this.emit('validation:complete', { result });
|
|
253
|
+
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Validate a single file
|
|
259
|
+
* @param {string} file - File name
|
|
260
|
+
* @param {string} content - File content
|
|
261
|
+
* @returns {ValidationIssue[]}
|
|
262
|
+
*/
|
|
263
|
+
async validateFile(file, content) {
|
|
264
|
+
const issues = [];
|
|
265
|
+
|
|
266
|
+
// Get applicable rules
|
|
267
|
+
const applicableRules = this.rules.filter(rule =>
|
|
268
|
+
rule.file === file || rule.file === '*'
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
for (const rule of applicableRules) {
|
|
272
|
+
try {
|
|
273
|
+
const passes = rule.check(content);
|
|
274
|
+
|
|
275
|
+
if (!passes) {
|
|
276
|
+
issues.push({
|
|
277
|
+
id: `issue-${Date.now()}-${Math.random().toString(36).substr(2, 4)}`,
|
|
278
|
+
file,
|
|
279
|
+
rule: rule.id,
|
|
280
|
+
type: rule.type,
|
|
281
|
+
severity: rule.severity,
|
|
282
|
+
message: rule.message,
|
|
283
|
+
suggestion: rule.suggestion
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
} catch (error) {
|
|
287
|
+
this.emit('rule:error', { rule: rule.id, error });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return issues;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Check cross-file consistency
|
|
296
|
+
* @param {string} basePath - Base path
|
|
297
|
+
* @returns {Promise<ValidationIssue[]>}
|
|
298
|
+
*/
|
|
299
|
+
async checkConsistency(basePath) {
|
|
300
|
+
const issues = [];
|
|
301
|
+
const steeringDir = path.join(basePath, this.steeringPath);
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
// Load files
|
|
305
|
+
const structurePath = path.join(steeringDir, 'structure.md');
|
|
306
|
+
const techPath = path.join(steeringDir, 'tech.md');
|
|
307
|
+
const productPath = path.join(steeringDir, 'product.md');
|
|
308
|
+
|
|
309
|
+
const files = {};
|
|
310
|
+
if (fs.existsSync(structurePath)) {
|
|
311
|
+
files.structure = fs.readFileSync(structurePath, 'utf8');
|
|
312
|
+
}
|
|
313
|
+
if (fs.existsSync(techPath)) {
|
|
314
|
+
files.tech = fs.readFileSync(techPath, 'utf8');
|
|
315
|
+
}
|
|
316
|
+
if (fs.existsSync(productPath)) {
|
|
317
|
+
files.product = fs.readFileSync(productPath, 'utf8');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Check project name consistency
|
|
321
|
+
const projectNames = this.extractProjectNames(files);
|
|
322
|
+
if (projectNames.size > 1) {
|
|
323
|
+
issues.push({
|
|
324
|
+
id: `consistency-project-name`,
|
|
325
|
+
file: 'multiple',
|
|
326
|
+
type: RULE_TYPE.CONSISTENCY,
|
|
327
|
+
severity: SEVERITY.WARNING,
|
|
328
|
+
message: `Inconsistent project names found: ${Array.from(projectNames).join(', ')}`,
|
|
329
|
+
suggestion: 'Use consistent project name across all steering files'
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Check language consistency
|
|
334
|
+
if (files.structure && files.tech) {
|
|
335
|
+
const structureLangs = this.extractLanguages(files.structure);
|
|
336
|
+
const techLangs = this.extractLanguages(files.tech);
|
|
337
|
+
|
|
338
|
+
const missingInTech = structureLangs.filter(l => !techLangs.includes(l));
|
|
339
|
+
if (missingInTech.length > 0) {
|
|
340
|
+
issues.push({
|
|
341
|
+
id: `consistency-languages`,
|
|
342
|
+
file: 'tech.md',
|
|
343
|
+
type: RULE_TYPE.CONSISTENCY,
|
|
344
|
+
severity: SEVERITY.INFO,
|
|
345
|
+
message: `Languages in structure.md not documented in tech.md: ${missingInTech.join(', ')}`
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
} catch (error) {
|
|
351
|
+
this.emit('consistency:error', { error });
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return issues;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Extract project names from files
|
|
359
|
+
* @private
|
|
360
|
+
*/
|
|
361
|
+
extractProjectNames(files) {
|
|
362
|
+
const names = new Set();
|
|
363
|
+
|
|
364
|
+
for (const [, content] of Object.entries(files)) {
|
|
365
|
+
// Look for project name patterns
|
|
366
|
+
const matches = content.match(/^#\s+([^\n]+)/m);
|
|
367
|
+
if (matches) {
|
|
368
|
+
names.add(matches[1].trim());
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return names;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Extract languages from content
|
|
377
|
+
* @private
|
|
378
|
+
*/
|
|
379
|
+
extractLanguages(content) {
|
|
380
|
+
const langs = [];
|
|
381
|
+
const patterns = [
|
|
382
|
+
/javascript/gi,
|
|
383
|
+
/typescript/gi,
|
|
384
|
+
/python/gi,
|
|
385
|
+
/java(?!script)/gi,
|
|
386
|
+
/go(?:lang)?/gi,
|
|
387
|
+
/rust/gi,
|
|
388
|
+
/ruby/gi
|
|
389
|
+
];
|
|
390
|
+
|
|
391
|
+
for (const pattern of patterns) {
|
|
392
|
+
if (pattern.test(content)) {
|
|
393
|
+
langs.push(pattern.source.replace(/[^\w]/g, '').toLowerCase());
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return [...new Set(langs)];
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Calculate validation score
|
|
402
|
+
* @private
|
|
403
|
+
*/
|
|
404
|
+
calculateScore(issues) {
|
|
405
|
+
let score = 100;
|
|
406
|
+
|
|
407
|
+
const penalties = {
|
|
408
|
+
[SEVERITY.INFO]: 1,
|
|
409
|
+
[SEVERITY.WARNING]: 5,
|
|
410
|
+
[SEVERITY.ERROR]: 15,
|
|
411
|
+
[SEVERITY.CRITICAL]: 30
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
for (const issue of issues) {
|
|
415
|
+
score -= penalties[issue.severity] || 0;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return Math.max(0, score);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Create validation summary
|
|
423
|
+
* @private
|
|
424
|
+
*/
|
|
425
|
+
createSummary(issues) {
|
|
426
|
+
const bySeverity = {};
|
|
427
|
+
const byType = {};
|
|
428
|
+
const byFile = {};
|
|
429
|
+
|
|
430
|
+
for (const issue of issues) {
|
|
431
|
+
bySeverity[issue.severity] = (bySeverity[issue.severity] || 0) + 1;
|
|
432
|
+
byType[issue.type] = (byType[issue.type] || 0) + 1;
|
|
433
|
+
byFile[issue.file] = (byFile[issue.file] || 0) + 1;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return {
|
|
437
|
+
totalIssues: issues.length,
|
|
438
|
+
bySeverity,
|
|
439
|
+
byType,
|
|
440
|
+
byFile
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Add custom rule
|
|
446
|
+
* @param {Object} rule - Validation rule
|
|
447
|
+
*/
|
|
448
|
+
addRule(rule) {
|
|
449
|
+
if (!rule.id || !rule.file || !rule.check) {
|
|
450
|
+
throw new Error('Rule must have id, file, and check function');
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
this.rules.push({
|
|
454
|
+
type: RULE_TYPE.COMPLETENESS,
|
|
455
|
+
severity: SEVERITY.WARNING,
|
|
456
|
+
...rule
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Get validation history
|
|
462
|
+
* @returns {ValidationResult[]}
|
|
463
|
+
*/
|
|
464
|
+
getHistory() {
|
|
465
|
+
return Array.from(this.validations.values())
|
|
466
|
+
.sort((a, b) => b.timestamp - a.timestamp);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Get statistics
|
|
471
|
+
* @returns {Object}
|
|
472
|
+
*/
|
|
473
|
+
getStats() {
|
|
474
|
+
const validations = Array.from(this.validations.values());
|
|
475
|
+
const scores = validations.map(v => v.score);
|
|
476
|
+
|
|
477
|
+
return {
|
|
478
|
+
totalValidations: validations.length,
|
|
479
|
+
averageScore: scores.length > 0
|
|
480
|
+
? scores.reduce((a, b) => a + b, 0) / scores.length
|
|
481
|
+
: 0,
|
|
482
|
+
passed: validations.filter(v => v.valid).length,
|
|
483
|
+
failed: validations.filter(v => !v.valid).length,
|
|
484
|
+
rulesCount: this.rules.length
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Export validation report
|
|
490
|
+
* @param {string} validationId - Validation ID
|
|
491
|
+
* @returns {string} Markdown report
|
|
492
|
+
*/
|
|
493
|
+
exportReport(validationId) {
|
|
494
|
+
const validation = this.validations.get(validationId);
|
|
495
|
+
if (!validation) return '';
|
|
496
|
+
|
|
497
|
+
let md = `# Steering Validation Report\n\n`;
|
|
498
|
+
md += `**Status:** ${validation.valid ? '✅ Valid' : '❌ Invalid'}\n`;
|
|
499
|
+
md += `**Score:** ${validation.score}/100\n`;
|
|
500
|
+
md += `**Date:** ${new Date(validation.timestamp).toISOString()}\n\n`;
|
|
501
|
+
|
|
502
|
+
md += `## Summary\n\n`;
|
|
503
|
+
md += `- Total Issues: ${validation.issues.length}\n`;
|
|
504
|
+
for (const [severity, count] of Object.entries(validation.summary.bySeverity)) {
|
|
505
|
+
md += `- ${severity}: ${count}\n`;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (validation.issues.length > 0) {
|
|
509
|
+
md += `\n## Issues\n\n`;
|
|
510
|
+
|
|
511
|
+
for (const issue of validation.issues) {
|
|
512
|
+
const icon = {
|
|
513
|
+
[SEVERITY.INFO]: 'ℹ️',
|
|
514
|
+
[SEVERITY.WARNING]: '⚠️',
|
|
515
|
+
[SEVERITY.ERROR]: '❌',
|
|
516
|
+
[SEVERITY.CRITICAL]: '🚨'
|
|
517
|
+
}[issue.severity];
|
|
518
|
+
|
|
519
|
+
md += `### ${icon} ${issue.message}\n\n`;
|
|
520
|
+
md += `- **File:** ${issue.file}\n`;
|
|
521
|
+
md += `- **Type:** ${issue.type}\n`;
|
|
522
|
+
md += `- **Severity:** ${issue.severity}\n`;
|
|
523
|
+
if (issue.suggestion) md += `- **Suggestion:** ${issue.suggestion}\n`;
|
|
524
|
+
md += `\n`;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return md;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Create steering validator
|
|
534
|
+
* @param {Object} [options={}] - Options
|
|
535
|
+
* @returns {SteeringValidator}
|
|
536
|
+
*/
|
|
537
|
+
function createSteeringValidator(options = {}) {
|
|
538
|
+
return new SteeringValidator(options);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
module.exports = {
|
|
542
|
+
SteeringValidator,
|
|
543
|
+
createSteeringValidator,
|
|
544
|
+
SEVERITY,
|
|
545
|
+
RULE_TYPE,
|
|
546
|
+
DEFAULT_VALIDATION_RULES
|
|
547
|
+
};
|