musubi-sdd 3.0.1 → 3.6.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 +17 -3
- 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/llm-providers/anthropic-provider.js +175 -0
- package/src/llm-providers/base-provider.js +221 -0
- package/src/llm-providers/copilot-provider.js +262 -0
- package/src/llm-providers/index.js +214 -0
- package/src/llm-providers/openai-provider.js +205 -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 +193 -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 +502 -0
- package/src/orchestration/replanning/alternative-generator.js +508 -0
- package/src/orchestration/replanning/config.js +378 -0
- package/src/orchestration/replanning/index.js +40 -0
- package/src/orchestration/replanning/plan-evaluator.js +455 -0
- package/src/orchestration/replanning/plan-monitor.js +379 -0
- package/src/orchestration/replanning/replan-history.js +402 -0
- package/src/orchestration/replanning/replanning-engine.js +706 -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,474 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delta Format Validator
|
|
3
|
+
* Validates delta specification format compliance
|
|
4
|
+
*
|
|
5
|
+
* @module validators/delta-format
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { DeltaType } = require('../managers/delta-spec');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validation rules for delta specifications
|
|
14
|
+
*/
|
|
15
|
+
const ValidationRules = {
|
|
16
|
+
// ID format: PREFIX-IDENTIFIER (e.g., DELTA-AUTH-001, CHG-UI-002)
|
|
17
|
+
ID_PATTERN: /^[A-Z]+-[A-Z0-9]+-\d{3}$/,
|
|
18
|
+
|
|
19
|
+
// Delta marker in markdown: [TYPE] ID: Description
|
|
20
|
+
MARKER_PATTERN: /^\[([A-Z]+)\]\s+([A-Z]+-[A-Z0-9]+-\d{3}):\s+(.+)$/,
|
|
21
|
+
|
|
22
|
+
// Requirement reference: REQ-xxx-nnn
|
|
23
|
+
REQ_PATTERN: /REQ-[A-Z]+-\d{3}/g,
|
|
24
|
+
|
|
25
|
+
// Valid statuses
|
|
26
|
+
VALID_STATUSES: ['proposed', 'approved', 'rejected', 'implemented', 'archived'],
|
|
27
|
+
|
|
28
|
+
// Maximum description length
|
|
29
|
+
MAX_DESCRIPTION_LENGTH: 200,
|
|
30
|
+
|
|
31
|
+
// Required sections in delta markdown
|
|
32
|
+
REQUIRED_SECTIONS: ['Description']
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
class DeltaFormatValidator {
|
|
36
|
+
constructor(options = {}) {
|
|
37
|
+
this.strict = options.strict || false;
|
|
38
|
+
this.errors = [];
|
|
39
|
+
this.warnings = [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Validate a delta specification object
|
|
44
|
+
* @param {Object} delta - Delta spec to validate
|
|
45
|
+
* @returns {Object} Validation result
|
|
46
|
+
*/
|
|
47
|
+
validate(delta) {
|
|
48
|
+
this.errors = [];
|
|
49
|
+
this.warnings = [];
|
|
50
|
+
|
|
51
|
+
this.validateId(delta.id);
|
|
52
|
+
this.validateType(delta.type);
|
|
53
|
+
this.validateTarget(delta.target);
|
|
54
|
+
this.validateDescription(delta.description);
|
|
55
|
+
this.validateStatus(delta.status);
|
|
56
|
+
this.validateStateTransitions(delta);
|
|
57
|
+
this.validateImpactedAreas(delta.impactedAreas);
|
|
58
|
+
this.validateTimestamps(delta);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
valid: this.errors.length === 0,
|
|
62
|
+
errors: this.errors,
|
|
63
|
+
warnings: this.warnings,
|
|
64
|
+
delta
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Validate delta ID format
|
|
70
|
+
* @param {string} id - Delta ID
|
|
71
|
+
*/
|
|
72
|
+
validateId(id) {
|
|
73
|
+
if (!id) {
|
|
74
|
+
this.errors.push({
|
|
75
|
+
rule: 'required-id',
|
|
76
|
+
message: 'Delta ID is required'
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!ValidationRules.ID_PATTERN.test(id)) {
|
|
82
|
+
this.errors.push({
|
|
83
|
+
rule: 'id-format',
|
|
84
|
+
message: `Invalid ID format: "${id}". Expected: PREFIX-IDENTIFIER-NNN (e.g., DELTA-AUTH-001)`,
|
|
85
|
+
value: id
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Validate delta type
|
|
92
|
+
* @param {string} type - Delta type
|
|
93
|
+
*/
|
|
94
|
+
validateType(type) {
|
|
95
|
+
if (!type) {
|
|
96
|
+
this.errors.push({
|
|
97
|
+
rule: 'required-type',
|
|
98
|
+
message: 'Delta type is required'
|
|
99
|
+
});
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!Object.values(DeltaType).includes(type)) {
|
|
104
|
+
this.errors.push({
|
|
105
|
+
rule: 'invalid-type',
|
|
106
|
+
message: `Invalid delta type: "${type}". Must be one of: ${Object.values(DeltaType).join(', ')}`,
|
|
107
|
+
value: type
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Validate target reference
|
|
114
|
+
* @param {string} target - Target requirement/component
|
|
115
|
+
*/
|
|
116
|
+
validateTarget(target) {
|
|
117
|
+
if (!target) {
|
|
118
|
+
this.errors.push({
|
|
119
|
+
rule: 'required-target',
|
|
120
|
+
message: 'Target is required'
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check if target looks like a requirement ID
|
|
126
|
+
if (!ValidationRules.REQ_PATTERN.test(target) && !target.includes('/')) {
|
|
127
|
+
this.warnings.push({
|
|
128
|
+
rule: 'target-format',
|
|
129
|
+
message: `Target "${target}" doesn't match requirement ID format (REQ-XXX-NNN) or path format`,
|
|
130
|
+
value: target
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Validate description
|
|
137
|
+
* @param {string} description - Delta description
|
|
138
|
+
*/
|
|
139
|
+
validateDescription(description) {
|
|
140
|
+
if (!description) {
|
|
141
|
+
this.errors.push({
|
|
142
|
+
rule: 'required-description',
|
|
143
|
+
message: 'Description is required'
|
|
144
|
+
});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (description.length > ValidationRules.MAX_DESCRIPTION_LENGTH) {
|
|
149
|
+
this.warnings.push({
|
|
150
|
+
rule: 'description-length',
|
|
151
|
+
message: `Description exceeds ${ValidationRules.MAX_DESCRIPTION_LENGTH} characters`,
|
|
152
|
+
value: description.length
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Check for vague language
|
|
157
|
+
const vagueTerms = ['should', 'might', 'could', 'maybe', 'possibly'];
|
|
158
|
+
const foundVague = vagueTerms.filter(term =>
|
|
159
|
+
description.toLowerCase().includes(term)
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (foundVague.length > 0) {
|
|
163
|
+
this.warnings.push({
|
|
164
|
+
rule: 'vague-language',
|
|
165
|
+
message: `Description contains vague terms: ${foundVague.join(', ')}. Use definitive language.`,
|
|
166
|
+
value: foundVague
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Validate status
|
|
173
|
+
* @param {string} status - Delta status
|
|
174
|
+
*/
|
|
175
|
+
validateStatus(status) {
|
|
176
|
+
if (!status) {
|
|
177
|
+
this.warnings.push({
|
|
178
|
+
rule: 'missing-status',
|
|
179
|
+
message: 'Status not specified, defaulting to "proposed"'
|
|
180
|
+
});
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!ValidationRules.VALID_STATUSES.includes(status)) {
|
|
185
|
+
this.errors.push({
|
|
186
|
+
rule: 'invalid-status',
|
|
187
|
+
message: `Invalid status: "${status}". Must be one of: ${ValidationRules.VALID_STATUSES.join(', ')}`,
|
|
188
|
+
value: status
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Validate state transitions based on delta type
|
|
195
|
+
* @param {Object} delta - Delta spec
|
|
196
|
+
*/
|
|
197
|
+
validateStateTransitions(delta) {
|
|
198
|
+
const { type, before, after } = delta;
|
|
199
|
+
|
|
200
|
+
switch (type) {
|
|
201
|
+
case DeltaType.ADDED:
|
|
202
|
+
if (before) {
|
|
203
|
+
this.warnings.push({
|
|
204
|
+
rule: 'added-has-before',
|
|
205
|
+
message: 'ADDED delta should not have a "before" state'
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
if (!after && this.strict) {
|
|
209
|
+
this.errors.push({
|
|
210
|
+
rule: 'added-missing-after',
|
|
211
|
+
message: 'ADDED delta should specify the new state in "after"'
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
break;
|
|
215
|
+
|
|
216
|
+
case DeltaType.MODIFIED:
|
|
217
|
+
if (!before) {
|
|
218
|
+
this.warnings.push({
|
|
219
|
+
rule: 'modified-missing-before',
|
|
220
|
+
message: 'MODIFIED delta should have a "before" state for comparison'
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
if (!after) {
|
|
224
|
+
this.warnings.push({
|
|
225
|
+
rule: 'modified-missing-after',
|
|
226
|
+
message: 'MODIFIED delta should have an "after" state'
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
|
|
231
|
+
case DeltaType.REMOVED:
|
|
232
|
+
if (!before && this.strict) {
|
|
233
|
+
this.warnings.push({
|
|
234
|
+
rule: 'removed-missing-before',
|
|
235
|
+
message: 'REMOVED delta should document the removed state in "before"'
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (after) {
|
|
239
|
+
this.warnings.push({
|
|
240
|
+
rule: 'removed-has-after',
|
|
241
|
+
message: 'REMOVED delta should not have an "after" state'
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
break;
|
|
245
|
+
|
|
246
|
+
case DeltaType.RENAMED:
|
|
247
|
+
if (!before) {
|
|
248
|
+
this.errors.push({
|
|
249
|
+
rule: 'renamed-missing-before',
|
|
250
|
+
message: 'RENAMED delta requires "before" state (original name)'
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
if (!after) {
|
|
254
|
+
this.errors.push({
|
|
255
|
+
rule: 'renamed-missing-after',
|
|
256
|
+
message: 'RENAMED delta requires "after" state (new name)'
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Validate impacted areas
|
|
265
|
+
* @param {string[]} impactedAreas - List of impacted areas
|
|
266
|
+
*/
|
|
267
|
+
validateImpactedAreas(impactedAreas) {
|
|
268
|
+
if (!impactedAreas || impactedAreas.length === 0) {
|
|
269
|
+
this.warnings.push({
|
|
270
|
+
rule: 'missing-impact',
|
|
271
|
+
message: 'Impacted areas should be specified for change tracking'
|
|
272
|
+
});
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Known impact categories
|
|
277
|
+
const knownCategories = [
|
|
278
|
+
'api', 'database', 'ui', 'backend', 'frontend',
|
|
279
|
+
'security', 'performance', 'testing', 'documentation',
|
|
280
|
+
'infrastructure', 'configuration'
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
const unknown = impactedAreas.filter(area =>
|
|
284
|
+
!knownCategories.includes(area.toLowerCase())
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
if (unknown.length > 0 && this.strict) {
|
|
288
|
+
this.warnings.push({
|
|
289
|
+
rule: 'unknown-impact-area',
|
|
290
|
+
message: `Unknown impact areas: ${unknown.join(', ')}. Consider using: ${knownCategories.join(', ')}`,
|
|
291
|
+
value: unknown
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Validate timestamps
|
|
298
|
+
* @param {Object} delta - Delta spec
|
|
299
|
+
*/
|
|
300
|
+
validateTimestamps(delta) {
|
|
301
|
+
if (delta.createdAt) {
|
|
302
|
+
if (isNaN(Date.parse(delta.createdAt))) {
|
|
303
|
+
this.errors.push({
|
|
304
|
+
rule: 'invalid-created-timestamp',
|
|
305
|
+
message: 'Invalid createdAt timestamp format',
|
|
306
|
+
value: delta.createdAt
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (delta.updatedAt) {
|
|
312
|
+
if (isNaN(Date.parse(delta.updatedAt))) {
|
|
313
|
+
this.errors.push({
|
|
314
|
+
rule: 'invalid-updated-timestamp',
|
|
315
|
+
message: 'Invalid updatedAt timestamp format',
|
|
316
|
+
value: delta.updatedAt
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (delta.createdAt && new Date(delta.updatedAt) < new Date(delta.createdAt)) {
|
|
321
|
+
this.warnings.push({
|
|
322
|
+
rule: 'timestamp-order',
|
|
323
|
+
message: 'updatedAt is before createdAt'
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Validate markdown content for delta markers
|
|
331
|
+
* @param {string} content - Markdown content
|
|
332
|
+
* @returns {Object} Validation result
|
|
333
|
+
*/
|
|
334
|
+
validateMarkdown(content) {
|
|
335
|
+
this.errors = [];
|
|
336
|
+
this.warnings = [];
|
|
337
|
+
|
|
338
|
+
const lines = content.split('\n');
|
|
339
|
+
const deltas = [];
|
|
340
|
+
|
|
341
|
+
lines.forEach((line, index) => {
|
|
342
|
+
const match = line.match(ValidationRules.MARKER_PATTERN);
|
|
343
|
+
if (match) {
|
|
344
|
+
const [, type, id, description] = match;
|
|
345
|
+
|
|
346
|
+
const delta = {
|
|
347
|
+
type,
|
|
348
|
+
id,
|
|
349
|
+
description,
|
|
350
|
+
line: index + 1
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// Validate each found delta marker
|
|
354
|
+
this.validateType(type);
|
|
355
|
+
this.validateId(id);
|
|
356
|
+
this.validateDescription(description);
|
|
357
|
+
|
|
358
|
+
if (this.errors.length === 0) {
|
|
359
|
+
deltas.push(delta);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Check for duplicate IDs
|
|
365
|
+
const ids = deltas.map(d => d.id);
|
|
366
|
+
const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
|
|
367
|
+
|
|
368
|
+
if (duplicates.length > 0) {
|
|
369
|
+
this.errors.push({
|
|
370
|
+
rule: 'duplicate-id',
|
|
371
|
+
message: `Duplicate delta IDs found: ${[...new Set(duplicates)].join(', ')}`,
|
|
372
|
+
value: duplicates
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
valid: this.errors.length === 0,
|
|
378
|
+
errors: this.errors,
|
|
379
|
+
warnings: this.warnings,
|
|
380
|
+
deltas
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Validate a delta file
|
|
386
|
+
* @param {string} filePath - Path to delta file
|
|
387
|
+
* @returns {Object} Validation result
|
|
388
|
+
*/
|
|
389
|
+
validateFile(filePath) {
|
|
390
|
+
if (!fs.existsSync(filePath)) {
|
|
391
|
+
return {
|
|
392
|
+
valid: false,
|
|
393
|
+
errors: [{ rule: 'file-not-found', message: `File not found: ${filePath}` }],
|
|
394
|
+
warnings: []
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const ext = path.extname(filePath);
|
|
399
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
400
|
+
|
|
401
|
+
if (ext === '.json') {
|
|
402
|
+
try {
|
|
403
|
+
const delta = JSON.parse(content);
|
|
404
|
+
return this.validate(delta);
|
|
405
|
+
} catch (e) {
|
|
406
|
+
return {
|
|
407
|
+
valid: false,
|
|
408
|
+
errors: [{ rule: 'invalid-json', message: `Invalid JSON: ${e.message}` }],
|
|
409
|
+
warnings: []
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
} else if (ext === '.md') {
|
|
413
|
+
return this.validateMarkdown(content);
|
|
414
|
+
} else {
|
|
415
|
+
return {
|
|
416
|
+
valid: false,
|
|
417
|
+
errors: [{ rule: 'unsupported-format', message: `Unsupported file format: ${ext}` }],
|
|
418
|
+
warnings: []
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Validate all deltas in a directory
|
|
425
|
+
* @param {string} dirPath - Path to changes directory
|
|
426
|
+
* @returns {Object} Validation results
|
|
427
|
+
*/
|
|
428
|
+
validateDirectory(dirPath) {
|
|
429
|
+
if (!fs.existsSync(dirPath)) {
|
|
430
|
+
return {
|
|
431
|
+
valid: true,
|
|
432
|
+
results: [],
|
|
433
|
+
summary: { total: 0, valid: 0, invalid: 0 }
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
438
|
+
const results = [];
|
|
439
|
+
let validCount = 0;
|
|
440
|
+
let invalidCount = 0;
|
|
441
|
+
|
|
442
|
+
for (const entry of entries) {
|
|
443
|
+
if (entry.isDirectory()) {
|
|
444
|
+
const jsonPath = path.join(dirPath, entry.name, 'delta.json');
|
|
445
|
+
if (fs.existsSync(jsonPath)) {
|
|
446
|
+
const result = this.validateFile(jsonPath);
|
|
447
|
+
result.id = entry.name;
|
|
448
|
+
results.push(result);
|
|
449
|
+
|
|
450
|
+
if (result.valid) {
|
|
451
|
+
validCount++;
|
|
452
|
+
} else {
|
|
453
|
+
invalidCount++;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
valid: invalidCount === 0,
|
|
461
|
+
results,
|
|
462
|
+
summary: {
|
|
463
|
+
total: results.length,
|
|
464
|
+
valid: validCount,
|
|
465
|
+
invalid: invalidCount
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
module.exports = {
|
|
472
|
+
DeltaFormatValidator,
|
|
473
|
+
ValidationRules
|
|
474
|
+
};
|