musubi-sdd 3.0.1 → 3.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/bin/musubi-change.js +623 -10
- package/bin/musubi-orchestrate.js +456 -0
- package/bin/musubi-trace.js +393 -0
- package/package.json +3 -2
- package/src/analyzers/impact-analyzer.js +682 -0
- package/src/integrations/cicd.js +782 -0
- package/src/integrations/documentation.js +740 -0
- package/src/integrations/examples.js +789 -0
- package/src/integrations/index.js +23 -0
- package/src/integrations/platforms.js +929 -0
- package/src/managers/delta-spec.js +484 -0
- package/src/monitoring/incident-manager.js +890 -0
- package/src/monitoring/index.js +633 -0
- package/src/monitoring/observability.js +938 -0
- package/src/monitoring/release-manager.js +622 -0
- package/src/orchestration/index.js +168 -0
- package/src/orchestration/orchestration-engine.js +409 -0
- package/src/orchestration/pattern-registry.js +319 -0
- package/src/orchestration/patterns/auto.js +386 -0
- package/src/orchestration/patterns/group-chat.js +395 -0
- package/src/orchestration/patterns/human-in-loop.js +506 -0
- package/src/orchestration/patterns/nested.js +322 -0
- package/src/orchestration/patterns/sequential.js +278 -0
- package/src/orchestration/patterns/swarm.js +395 -0
- package/src/orchestration/workflow-orchestrator.js +738 -0
- package/src/reporters/coverage-report.js +452 -0
- package/src/reporters/traceability-matrix-report.js +684 -0
- package/src/steering/advanced-validation.js +812 -0
- package/src/steering/auto-updater.js +670 -0
- package/src/steering/index.js +119 -0
- package/src/steering/quality-metrics.js +650 -0
- package/src/steering/template-constraints.js +789 -0
- package/src/templates/agents/claude-code/skills/agent-assistant/SKILL.md +22 -0
- package/src/templates/agents/claude-code/skills/issue-resolver/SKILL.md +21 -0
- package/src/templates/agents/claude-code/skills/orchestrator/SKILL.md +90 -28
- package/src/templates/agents/claude-code/skills/project-manager/SKILL.md +32 -0
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/SKILL.md +27 -0
- package/src/templates/agents/claude-code/skills/steering/SKILL.md +30 -0
- package/src/templates/agents/claude-code/skills/test-engineer/SKILL.md +21 -0
- package/src/templates/agents/claude-code/skills/ui-ux-designer/SKILL.md +27 -0
- package/src/templates/agents/codex/AGENTS.md +36 -1
- package/src/templates/agents/cursor/AGENTS.md +36 -1
- package/src/templates/agents/gemini-cli/GEMINI.md +36 -1
- package/src/templates/agents/github-copilot/AGENTS.md +65 -1
- package/src/templates/agents/qwen-code/QWEN.md +36 -1
- package/src/templates/agents/windsurf/AGENTS.md +36 -1
- package/src/templates/shared/delta-spec-template.md +246 -0
- package/src/validators/delta-format.js +474 -0
- package/src/validators/traceability-validator.js +561 -0
|
@@ -0,0 +1,812 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Validation Module
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - Cross-artifact consistency validation
|
|
6
|
+
* - Gap detection between requirements and implementation
|
|
7
|
+
* - Completeness checks
|
|
8
|
+
* - Dependency validation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const EventEmitter = require('events');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
// Validation Types
|
|
15
|
+
const ValidationType = {
|
|
16
|
+
CONSISTENCY: 'consistency',
|
|
17
|
+
COMPLETENESS: 'completeness',
|
|
18
|
+
GAP: 'gap',
|
|
19
|
+
DEPENDENCY: 'dependency',
|
|
20
|
+
REFERENCE: 'reference',
|
|
21
|
+
CUSTOM: 'custom'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Severity Levels
|
|
25
|
+
const Severity = {
|
|
26
|
+
CRITICAL: 'critical',
|
|
27
|
+
ERROR: 'error',
|
|
28
|
+
WARNING: 'warning',
|
|
29
|
+
INFO: 'info'
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Artifact Types
|
|
33
|
+
const ArtifactType = {
|
|
34
|
+
REQUIREMENT: 'requirement',
|
|
35
|
+
DESIGN: 'design',
|
|
36
|
+
IMPLEMENTATION: 'implementation',
|
|
37
|
+
TEST: 'test',
|
|
38
|
+
DOCUMENTATION: 'documentation',
|
|
39
|
+
STEERING: 'steering'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Validation Issue
|
|
44
|
+
*/
|
|
45
|
+
class ValidationIssue {
|
|
46
|
+
constructor(options = {}) {
|
|
47
|
+
this.id = options.id || `issue-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
48
|
+
this.type = options.type || ValidationType.CONSISTENCY;
|
|
49
|
+
this.severity = options.severity || Severity.ERROR;
|
|
50
|
+
this.message = options.message || 'Validation issue';
|
|
51
|
+
this.artifact = options.artifact || null;
|
|
52
|
+
this.location = options.location || null;
|
|
53
|
+
this.suggestion = options.suggestion || null;
|
|
54
|
+
this.relatedArtifacts = options.relatedArtifacts || [];
|
|
55
|
+
this.metadata = options.metadata || {};
|
|
56
|
+
this.timestamp = new Date();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
toJSON() {
|
|
60
|
+
return {
|
|
61
|
+
id: this.id,
|
|
62
|
+
type: this.type,
|
|
63
|
+
severity: this.severity,
|
|
64
|
+
message: this.message,
|
|
65
|
+
artifact: this.artifact,
|
|
66
|
+
location: this.location,
|
|
67
|
+
suggestion: this.suggestion,
|
|
68
|
+
relatedArtifacts: this.relatedArtifacts,
|
|
69
|
+
timestamp: this.timestamp.toISOString()
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Artifact Reference
|
|
76
|
+
*/
|
|
77
|
+
class ArtifactReference {
|
|
78
|
+
constructor(type, id, options = {}) {
|
|
79
|
+
this.type = type;
|
|
80
|
+
this.id = id;
|
|
81
|
+
this.name = options.name || id;
|
|
82
|
+
this.path = options.path || null;
|
|
83
|
+
this.version = options.version || null;
|
|
84
|
+
this.dependencies = options.dependencies || [];
|
|
85
|
+
this.references = options.references || [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
addDependency(ref) {
|
|
89
|
+
this.dependencies.push(ref);
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
addReference(ref) {
|
|
94
|
+
this.references.push(ref);
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
toJSON() {
|
|
99
|
+
return {
|
|
100
|
+
type: this.type,
|
|
101
|
+
id: this.id,
|
|
102
|
+
name: this.name,
|
|
103
|
+
path: this.path,
|
|
104
|
+
version: this.version,
|
|
105
|
+
dependencies: this.dependencies.map(d => typeof d === 'string' ? d : d.id),
|
|
106
|
+
references: this.references.map(r => typeof r === 'string' ? r : r.id)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Consistency Checker
|
|
113
|
+
*/
|
|
114
|
+
class ConsistencyChecker {
|
|
115
|
+
constructor(options = {}) {
|
|
116
|
+
this.rules = [];
|
|
117
|
+
this.strictMode = options.strict || false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
addRule(rule) {
|
|
121
|
+
this.rules.push(rule);
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
check(artifacts) {
|
|
126
|
+
const issues = [];
|
|
127
|
+
|
|
128
|
+
for (const rule of this.rules) {
|
|
129
|
+
try {
|
|
130
|
+
const ruleIssues = rule.check(artifacts);
|
|
131
|
+
issues.push(...ruleIssues);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
issues.push(new ValidationIssue({
|
|
134
|
+
type: ValidationType.CONSISTENCY,
|
|
135
|
+
severity: Severity.ERROR,
|
|
136
|
+
message: `Rule check failed: ${error.message}`,
|
|
137
|
+
metadata: { rule: rule.name, error: error.message }
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
valid: !issues.some(i => i.severity === Severity.CRITICAL || i.severity === Severity.ERROR),
|
|
144
|
+
issues,
|
|
145
|
+
rulesChecked: this.rules.length
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Built-in rules
|
|
150
|
+
static createNamingConsistencyRule() {
|
|
151
|
+
return {
|
|
152
|
+
name: 'naming-consistency',
|
|
153
|
+
check: (artifacts) => {
|
|
154
|
+
const issues = [];
|
|
155
|
+
const names = new Map();
|
|
156
|
+
|
|
157
|
+
for (const artifact of artifacts) {
|
|
158
|
+
const normalized = artifact.name?.toLowerCase().replace(/[-_\s]/g, '');
|
|
159
|
+
if (normalized && names.has(normalized)) {
|
|
160
|
+
const existing = names.get(normalized);
|
|
161
|
+
issues.push(new ValidationIssue({
|
|
162
|
+
type: ValidationType.CONSISTENCY,
|
|
163
|
+
severity: Severity.WARNING,
|
|
164
|
+
message: `Similar names detected: "${artifact.name}" and "${existing.name}"`,
|
|
165
|
+
artifact: artifact.id,
|
|
166
|
+
relatedArtifacts: [existing.id]
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
if (normalized) {
|
|
170
|
+
names.set(normalized, artifact);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return issues;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
static createVersionConsistencyRule() {
|
|
180
|
+
return {
|
|
181
|
+
name: 'version-consistency',
|
|
182
|
+
check: (artifacts) => {
|
|
183
|
+
const issues = [];
|
|
184
|
+
const versions = new Map();
|
|
185
|
+
|
|
186
|
+
for (const artifact of artifacts) {
|
|
187
|
+
if (artifact.version) {
|
|
188
|
+
if (!versions.has(artifact.type)) {
|
|
189
|
+
versions.set(artifact.type, []);
|
|
190
|
+
}
|
|
191
|
+
versions.get(artifact.type).push(artifact);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
for (const [type, typeArtifacts] of versions) {
|
|
196
|
+
const uniqueVersions = new Set(typeArtifacts.map(a => a.version));
|
|
197
|
+
if (uniqueVersions.size > 1) {
|
|
198
|
+
issues.push(new ValidationIssue({
|
|
199
|
+
type: ValidationType.CONSISTENCY,
|
|
200
|
+
severity: Severity.WARNING,
|
|
201
|
+
message: `Multiple versions found for ${type}: ${[...uniqueVersions].join(', ')}`,
|
|
202
|
+
metadata: { type, versions: [...uniqueVersions] }
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return issues;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Gap Detector
|
|
215
|
+
*/
|
|
216
|
+
class GapDetector {
|
|
217
|
+
constructor(options = {}) {
|
|
218
|
+
this.traceabilityMatrix = new Map();
|
|
219
|
+
this.requiredLinks = options.requiredLinks || {
|
|
220
|
+
[ArtifactType.REQUIREMENT]: [ArtifactType.DESIGN, ArtifactType.TEST],
|
|
221
|
+
[ArtifactType.DESIGN]: [ArtifactType.IMPLEMENTATION],
|
|
222
|
+
[ArtifactType.IMPLEMENTATION]: [ArtifactType.TEST]
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
addLink(source, target) {
|
|
227
|
+
if (!this.traceabilityMatrix.has(source)) {
|
|
228
|
+
this.traceabilityMatrix.set(source, new Set());
|
|
229
|
+
}
|
|
230
|
+
this.traceabilityMatrix.get(source).add(target);
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
detectGaps(artifacts) {
|
|
235
|
+
const issues = [];
|
|
236
|
+
const artifactMap = new Map(artifacts.map(a => [a.id, a]));
|
|
237
|
+
|
|
238
|
+
for (const artifact of artifacts) {
|
|
239
|
+
const requiredTargets = this.requiredLinks[artifact.type] || [];
|
|
240
|
+
const actualLinks = this.traceabilityMatrix.get(artifact.id) || new Set();
|
|
241
|
+
|
|
242
|
+
for (const requiredType of requiredTargets) {
|
|
243
|
+
const hasLink = [...actualLinks].some(linkId => {
|
|
244
|
+
const linked = artifactMap.get(linkId);
|
|
245
|
+
return linked && linked.type === requiredType;
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
if (!hasLink) {
|
|
249
|
+
issues.push(new ValidationIssue({
|
|
250
|
+
type: ValidationType.GAP,
|
|
251
|
+
severity: Severity.WARNING,
|
|
252
|
+
message: `${artifact.type} "${artifact.name}" has no linked ${requiredType}`,
|
|
253
|
+
artifact: artifact.id,
|
|
254
|
+
suggestion: `Create or link a ${requiredType} for this ${artifact.type}`
|
|
255
|
+
}));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
gaps: issues,
|
|
262
|
+
coverage: this.calculateCoverage(artifacts, issues)
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
calculateCoverage(artifacts, gaps) {
|
|
267
|
+
const totalRequired = artifacts.reduce((acc, a) => {
|
|
268
|
+
return acc + (this.requiredLinks[a.type]?.length || 0);
|
|
269
|
+
}, 0);
|
|
270
|
+
|
|
271
|
+
if (totalRequired === 0) return 100;
|
|
272
|
+
|
|
273
|
+
const gapCount = gaps.length;
|
|
274
|
+
return ((totalRequired - gapCount) / totalRequired) * 100;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
getTraceabilityReport() {
|
|
278
|
+
const report = [];
|
|
279
|
+
|
|
280
|
+
for (const [source, targets] of this.traceabilityMatrix) {
|
|
281
|
+
report.push({
|
|
282
|
+
source,
|
|
283
|
+
targets: [...targets],
|
|
284
|
+
count: targets.size
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return report;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Completeness Checker
|
|
294
|
+
*/
|
|
295
|
+
class CompletenessChecker {
|
|
296
|
+
constructor(options = {}) {
|
|
297
|
+
this.requiredFields = options.requiredFields || {};
|
|
298
|
+
this.requiredSections = options.requiredSections || {};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
setRequiredFields(type, fields) {
|
|
302
|
+
this.requiredFields[type] = fields;
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
setRequiredSections(type, sections) {
|
|
307
|
+
this.requiredSections[type] = sections;
|
|
308
|
+
return this;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
checkArtifact(artifact) {
|
|
312
|
+
const issues = [];
|
|
313
|
+
|
|
314
|
+
// Check required fields
|
|
315
|
+
const requiredFields = this.requiredFields[artifact.type] || [];
|
|
316
|
+
for (const field of requiredFields) {
|
|
317
|
+
const value = artifact[field] || artifact.metadata?.[field];
|
|
318
|
+
if (value === undefined || value === null || value === '') {
|
|
319
|
+
issues.push(new ValidationIssue({
|
|
320
|
+
type: ValidationType.COMPLETENESS,
|
|
321
|
+
severity: Severity.ERROR,
|
|
322
|
+
message: `Missing required field "${field}" in ${artifact.type} "${artifact.name}"`,
|
|
323
|
+
artifact: artifact.id,
|
|
324
|
+
location: field
|
|
325
|
+
}));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Check required sections (for document-like artifacts)
|
|
330
|
+
const requiredSections = this.requiredSections[artifact.type] || [];
|
|
331
|
+
const content = artifact.content || '';
|
|
332
|
+
|
|
333
|
+
for (const section of requiredSections) {
|
|
334
|
+
const sectionPattern = new RegExp(`^##?\\s+${section}`, 'im');
|
|
335
|
+
if (!sectionPattern.test(content)) {
|
|
336
|
+
issues.push(new ValidationIssue({
|
|
337
|
+
type: ValidationType.COMPLETENESS,
|
|
338
|
+
severity: Severity.WARNING,
|
|
339
|
+
message: `Missing section "${section}" in ${artifact.type} "${artifact.name}"`,
|
|
340
|
+
artifact: artifact.id,
|
|
341
|
+
suggestion: `Add a "## ${section}" section`
|
|
342
|
+
}));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
complete: issues.filter(i => i.severity === Severity.ERROR).length === 0,
|
|
348
|
+
issues,
|
|
349
|
+
artifact: artifact.id
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
checkAll(artifacts) {
|
|
354
|
+
const results = [];
|
|
355
|
+
|
|
356
|
+
for (const artifact of artifacts) {
|
|
357
|
+
results.push(this.checkArtifact(artifact));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const allIssues = results.flatMap(r => r.issues);
|
|
361
|
+
const completeCount = results.filter(r => r.complete).length;
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
valid: allIssues.filter(i => i.severity === Severity.ERROR).length === 0,
|
|
365
|
+
completeness: (completeCount / artifacts.length) * 100,
|
|
366
|
+
results,
|
|
367
|
+
issues: allIssues
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Dependency Validator
|
|
374
|
+
*/
|
|
375
|
+
class DependencyValidator {
|
|
376
|
+
constructor(options = {}) {
|
|
377
|
+
this.dependencies = new Map();
|
|
378
|
+
this.allowCycles = options.allowCycles || false;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
addDependency(from, to) {
|
|
382
|
+
if (!this.dependencies.has(from)) {
|
|
383
|
+
this.dependencies.set(from, new Set());
|
|
384
|
+
}
|
|
385
|
+
this.dependencies.get(from).add(to);
|
|
386
|
+
return this;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
removeDependency(from, to) {
|
|
390
|
+
const deps = this.dependencies.get(from);
|
|
391
|
+
if (deps) {
|
|
392
|
+
deps.delete(to);
|
|
393
|
+
}
|
|
394
|
+
return this;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
getDependencies(id) {
|
|
398
|
+
return [...(this.dependencies.get(id) || [])];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
getDependents(id) {
|
|
402
|
+
const dependents = [];
|
|
403
|
+
for (const [from, tos] of this.dependencies) {
|
|
404
|
+
if (tos.has(id)) {
|
|
405
|
+
dependents.push(from);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return dependents;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
detectCycles() {
|
|
412
|
+
const cycles = [];
|
|
413
|
+
const visited = new Set();
|
|
414
|
+
const stack = new Set();
|
|
415
|
+
|
|
416
|
+
const dfs = (node, path = []) => {
|
|
417
|
+
if (stack.has(node)) {
|
|
418
|
+
const cycleStart = path.indexOf(node);
|
|
419
|
+
cycles.push(path.slice(cycleStart).concat(node));
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (visited.has(node)) return;
|
|
424
|
+
|
|
425
|
+
visited.add(node);
|
|
426
|
+
stack.add(node);
|
|
427
|
+
path.push(node);
|
|
428
|
+
|
|
429
|
+
const deps = this.dependencies.get(node) || new Set();
|
|
430
|
+
for (const dep of deps) {
|
|
431
|
+
dfs(dep, [...path]);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
stack.delete(node);
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
for (const node of this.dependencies.keys()) {
|
|
438
|
+
dfs(node);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return cycles;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
validate(artifacts) {
|
|
445
|
+
const issues = [];
|
|
446
|
+
const artifactIds = new Set(artifacts.map(a => a.id));
|
|
447
|
+
|
|
448
|
+
// Check for missing dependencies
|
|
449
|
+
for (const [from, tos] of this.dependencies) {
|
|
450
|
+
if (!artifactIds.has(from)) {
|
|
451
|
+
issues.push(new ValidationIssue({
|
|
452
|
+
type: ValidationType.DEPENDENCY,
|
|
453
|
+
severity: Severity.ERROR,
|
|
454
|
+
message: `Dependency source "${from}" not found in artifacts`,
|
|
455
|
+
artifact: from
|
|
456
|
+
}));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
for (const to of tos) {
|
|
460
|
+
if (!artifactIds.has(to)) {
|
|
461
|
+
issues.push(new ValidationIssue({
|
|
462
|
+
type: ValidationType.DEPENDENCY,
|
|
463
|
+
severity: Severity.ERROR,
|
|
464
|
+
message: `Dependency target "${to}" not found (required by "${from}")`,
|
|
465
|
+
artifact: from,
|
|
466
|
+
relatedArtifacts: [to]
|
|
467
|
+
}));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Check for cycles
|
|
473
|
+
if (!this.allowCycles) {
|
|
474
|
+
const cycles = this.detectCycles();
|
|
475
|
+
for (const cycle of cycles) {
|
|
476
|
+
issues.push(new ValidationIssue({
|
|
477
|
+
type: ValidationType.DEPENDENCY,
|
|
478
|
+
severity: Severity.ERROR,
|
|
479
|
+
message: `Circular dependency detected: ${cycle.join(' → ')}`,
|
|
480
|
+
metadata: { cycle }
|
|
481
|
+
}));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
valid: issues.length === 0,
|
|
487
|
+
issues,
|
|
488
|
+
cycles: this.detectCycles()
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
getTopologicalOrder() {
|
|
493
|
+
const sorted = [];
|
|
494
|
+
const visited = new Set();
|
|
495
|
+
const temp = new Set();
|
|
496
|
+
|
|
497
|
+
const visit = (node) => {
|
|
498
|
+
if (temp.has(node)) return false; // cycle
|
|
499
|
+
if (visited.has(node)) return true;
|
|
500
|
+
|
|
501
|
+
temp.add(node);
|
|
502
|
+
const deps = this.dependencies.get(node) || new Set();
|
|
503
|
+
|
|
504
|
+
for (const dep of deps) {
|
|
505
|
+
if (!visit(dep)) return false;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
temp.delete(node);
|
|
509
|
+
visited.add(node);
|
|
510
|
+
sorted.unshift(node);
|
|
511
|
+
return true;
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
for (const node of this.dependencies.keys()) {
|
|
515
|
+
if (!visit(node)) return null; // cycle detected
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return sorted;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Reference Validator
|
|
524
|
+
*/
|
|
525
|
+
class ReferenceValidator {
|
|
526
|
+
constructor(options = {}) {
|
|
527
|
+
this.references = new Map();
|
|
528
|
+
this.patterns = options.patterns || {
|
|
529
|
+
requirement: /REQ-\d+/g,
|
|
530
|
+
design: /DES-\d+/g,
|
|
531
|
+
test: /TEST-\d+/g,
|
|
532
|
+
issue: /#\d+/g
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
registerReference(id, artifact) {
|
|
537
|
+
this.references.set(id, artifact);
|
|
538
|
+
return this;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
extractReferences(content) {
|
|
542
|
+
const refs = [];
|
|
543
|
+
|
|
544
|
+
for (const [type, pattern] of Object.entries(this.patterns)) {
|
|
545
|
+
const matches = content.match(pattern) || [];
|
|
546
|
+
for (const match of matches) {
|
|
547
|
+
refs.push({ type, id: match });
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return refs;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
validate(artifacts) {
|
|
555
|
+
const issues = [];
|
|
556
|
+
|
|
557
|
+
for (const artifact of artifacts) {
|
|
558
|
+
const content = artifact.content || '';
|
|
559
|
+
const refs = this.extractReferences(content);
|
|
560
|
+
|
|
561
|
+
for (const ref of refs) {
|
|
562
|
+
if (!this.references.has(ref.id)) {
|
|
563
|
+
issues.push(new ValidationIssue({
|
|
564
|
+
type: ValidationType.REFERENCE,
|
|
565
|
+
severity: Severity.WARNING,
|
|
566
|
+
message: `Reference "${ref.id}" not found`,
|
|
567
|
+
artifact: artifact.id,
|
|
568
|
+
metadata: { referenceType: ref.type, referenceId: ref.id }
|
|
569
|
+
}));
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return {
|
|
575
|
+
valid: issues.length === 0,
|
|
576
|
+
issues,
|
|
577
|
+
totalReferences: this.references.size
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Advanced Validator (Main Class)
|
|
584
|
+
*/
|
|
585
|
+
class AdvancedValidator extends EventEmitter {
|
|
586
|
+
constructor(options = {}) {
|
|
587
|
+
super();
|
|
588
|
+
this.consistencyChecker = new ConsistencyChecker(options.consistency);
|
|
589
|
+
this.gapDetector = new GapDetector(options.gaps);
|
|
590
|
+
this.completenessChecker = new CompletenessChecker(options.completeness);
|
|
591
|
+
this.dependencyValidator = new DependencyValidator(options.dependencies);
|
|
592
|
+
this.referenceValidator = new ReferenceValidator(options.references);
|
|
593
|
+
this.artifacts = new Map();
|
|
594
|
+
this.validationHistory = [];
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Artifact Management
|
|
598
|
+
registerArtifact(artifact) {
|
|
599
|
+
if (!(artifact instanceof ArtifactReference)) {
|
|
600
|
+
artifact = new ArtifactReference(
|
|
601
|
+
artifact.type,
|
|
602
|
+
artifact.id,
|
|
603
|
+
artifact
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
this.artifacts.set(artifact.id, artifact);
|
|
607
|
+
this.referenceValidator.registerReference(artifact.id, artifact);
|
|
608
|
+
this.emit('artifact:registered', artifact);
|
|
609
|
+
return this;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
getArtifact(id) {
|
|
613
|
+
return this.artifacts.get(id);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
getAllArtifacts() {
|
|
617
|
+
return [...this.artifacts.values()];
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Link Management
|
|
621
|
+
addLink(sourceId, targetId) {
|
|
622
|
+
this.gapDetector.addLink(sourceId, targetId);
|
|
623
|
+
this.dependencyValidator.addDependency(sourceId, targetId);
|
|
624
|
+
return this;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Validation Methods
|
|
628
|
+
validateConsistency() {
|
|
629
|
+
const artifacts = this.getAllArtifacts();
|
|
630
|
+
const result = this.consistencyChecker.check(artifacts);
|
|
631
|
+
this.recordValidation('consistency', result);
|
|
632
|
+
return result;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
validateGaps() {
|
|
636
|
+
const artifacts = this.getAllArtifacts();
|
|
637
|
+
const result = this.gapDetector.detectGaps(artifacts);
|
|
638
|
+
this.recordValidation('gaps', result);
|
|
639
|
+
return result;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
validateCompleteness() {
|
|
643
|
+
const artifacts = this.getAllArtifacts();
|
|
644
|
+
const result = this.completenessChecker.checkAll(artifacts);
|
|
645
|
+
this.recordValidation('completeness', result);
|
|
646
|
+
return result;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
validateDependencies() {
|
|
650
|
+
const artifacts = this.getAllArtifacts();
|
|
651
|
+
const result = this.dependencyValidator.validate(artifacts);
|
|
652
|
+
this.recordValidation('dependencies', result);
|
|
653
|
+
return result;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
validateReferences() {
|
|
657
|
+
const artifacts = this.getAllArtifacts();
|
|
658
|
+
const result = this.referenceValidator.validate(artifacts);
|
|
659
|
+
this.recordValidation('references', result);
|
|
660
|
+
return result;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
validateAll() {
|
|
664
|
+
const results = {
|
|
665
|
+
consistency: this.validateConsistency(),
|
|
666
|
+
gaps: this.validateGaps(),
|
|
667
|
+
completeness: this.validateCompleteness(),
|
|
668
|
+
dependencies: this.validateDependencies(),
|
|
669
|
+
references: this.validateReferences()
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
const allIssues = [
|
|
673
|
+
...results.consistency.issues,
|
|
674
|
+
...results.gaps.gaps,
|
|
675
|
+
...results.completeness.issues,
|
|
676
|
+
...results.dependencies.issues,
|
|
677
|
+
...results.references.issues
|
|
678
|
+
];
|
|
679
|
+
|
|
680
|
+
const valid = !allIssues.some(i =>
|
|
681
|
+
i.severity === Severity.CRITICAL || i.severity === Severity.ERROR
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
this.emit('validation:complete', { results, valid, issues: allIssues });
|
|
685
|
+
|
|
686
|
+
return {
|
|
687
|
+
valid,
|
|
688
|
+
results,
|
|
689
|
+
issues: allIssues,
|
|
690
|
+
summary: {
|
|
691
|
+
totalArtifacts: this.artifacts.size,
|
|
692
|
+
totalIssues: allIssues.length,
|
|
693
|
+
criticalIssues: allIssues.filter(i => i.severity === Severity.CRITICAL).length,
|
|
694
|
+
errorIssues: allIssues.filter(i => i.severity === Severity.ERROR).length,
|
|
695
|
+
warningIssues: allIssues.filter(i => i.severity === Severity.WARNING).length,
|
|
696
|
+
gapCoverage: results.gaps.coverage,
|
|
697
|
+
completeness: results.completeness.completeness
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
recordValidation(type, result) {
|
|
703
|
+
this.validationHistory.push({
|
|
704
|
+
type,
|
|
705
|
+
result,
|
|
706
|
+
timestamp: new Date()
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
// Keep last 100 validations
|
|
710
|
+
if (this.validationHistory.length > 100) {
|
|
711
|
+
this.validationHistory = this.validationHistory.slice(-100);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
getValidationHistory(type = null) {
|
|
716
|
+
if (type) {
|
|
717
|
+
return this.validationHistory.filter(v => v.type === type);
|
|
718
|
+
}
|
|
719
|
+
return this.validationHistory;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Report Generation
|
|
723
|
+
generateReport() {
|
|
724
|
+
const validation = this.validateAll();
|
|
725
|
+
const lines = [
|
|
726
|
+
'# Validation Report',
|
|
727
|
+
'',
|
|
728
|
+
`Generated: ${new Date().toISOString()}`,
|
|
729
|
+
'',
|
|
730
|
+
'## Summary',
|
|
731
|
+
'',
|
|
732
|
+
`- **Status**: ${validation.valid ? '✅ Valid' : '❌ Invalid'}`,
|
|
733
|
+
`- **Total Artifacts**: ${validation.summary.totalArtifacts}`,
|
|
734
|
+
`- **Total Issues**: ${validation.summary.totalIssues}`,
|
|
735
|
+
`- **Gap Coverage**: ${validation.summary.gapCoverage.toFixed(1)}%`,
|
|
736
|
+
`- **Completeness**: ${validation.summary.completeness.toFixed(1)}%`,
|
|
737
|
+
'',
|
|
738
|
+
'## Issues by Severity',
|
|
739
|
+
'',
|
|
740
|
+
`- Critical: ${validation.summary.criticalIssues}`,
|
|
741
|
+
`- Error: ${validation.summary.errorIssues}`,
|
|
742
|
+
`- Warning: ${validation.summary.warningIssues}`,
|
|
743
|
+
''
|
|
744
|
+
];
|
|
745
|
+
|
|
746
|
+
if (validation.issues.length > 0) {
|
|
747
|
+
lines.push('## Issues', '');
|
|
748
|
+
|
|
749
|
+
for (const issue of validation.issues) {
|
|
750
|
+
const icon = issue.severity === 'critical' ? '🔴' :
|
|
751
|
+
issue.severity === 'error' ? '🟠' : '🟡';
|
|
752
|
+
lines.push(`### ${icon} ${issue.message}`);
|
|
753
|
+
lines.push('');
|
|
754
|
+
lines.push(`- Type: ${issue.type}`);
|
|
755
|
+
lines.push(`- Severity: ${issue.severity}`);
|
|
756
|
+
if (issue.artifact) lines.push(`- Artifact: ${issue.artifact}`);
|
|
757
|
+
if (issue.suggestion) lines.push(`- Suggestion: ${issue.suggestion}`);
|
|
758
|
+
lines.push('');
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return lines.join('\n');
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Factory function
|
|
768
|
+
*/
|
|
769
|
+
function createAdvancedValidator(options = {}) {
|
|
770
|
+
const validator = new AdvancedValidator(options);
|
|
771
|
+
|
|
772
|
+
// Add default consistency rules
|
|
773
|
+
validator.consistencyChecker.addRule(
|
|
774
|
+
ConsistencyChecker.createNamingConsistencyRule()
|
|
775
|
+
);
|
|
776
|
+
validator.consistencyChecker.addRule(
|
|
777
|
+
ConsistencyChecker.createVersionConsistencyRule()
|
|
778
|
+
);
|
|
779
|
+
|
|
780
|
+
// Set default completeness requirements
|
|
781
|
+
validator.completenessChecker.setRequiredFields(ArtifactType.REQUIREMENT, [
|
|
782
|
+
'name', 'description'
|
|
783
|
+
]);
|
|
784
|
+
validator.completenessChecker.setRequiredFields(ArtifactType.DESIGN, [
|
|
785
|
+
'name', 'description'
|
|
786
|
+
]);
|
|
787
|
+
validator.completenessChecker.setRequiredSections(ArtifactType.STEERING, [
|
|
788
|
+
'Overview', 'Purpose'
|
|
789
|
+
]);
|
|
790
|
+
|
|
791
|
+
return validator;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
module.exports = {
|
|
795
|
+
// Constants
|
|
796
|
+
ValidationType,
|
|
797
|
+
Severity,
|
|
798
|
+
ArtifactType,
|
|
799
|
+
|
|
800
|
+
// Classes
|
|
801
|
+
ValidationIssue,
|
|
802
|
+
ArtifactReference,
|
|
803
|
+
ConsistencyChecker,
|
|
804
|
+
GapDetector,
|
|
805
|
+
CompletenessChecker,
|
|
806
|
+
DependencyValidator,
|
|
807
|
+
ReferenceValidator,
|
|
808
|
+
AdvancedValidator,
|
|
809
|
+
|
|
810
|
+
// Factory
|
|
811
|
+
createAdvancedValidator
|
|
812
|
+
};
|