musubi-sdd 6.2.0 → 6.2.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/README.ja.md +60 -1
- package/README.md +60 -1
- package/bin/musubi-dashboard.js +340 -0
- package/package.json +3 -2
- package/src/cli/dashboard-cli.js +536 -0
- package/src/constitutional/checker.js +633 -0
- package/src/constitutional/ci-reporter.js +336 -0
- package/src/constitutional/index.js +22 -0
- package/src/constitutional/phase-minus-one.js +404 -0
- package/src/constitutional/steering-sync.js +473 -0
- package/src/dashboard/index.js +20 -0
- package/src/dashboard/sprint-planner.js +361 -0
- package/src/dashboard/sprint-reporter.js +378 -0
- package/src/dashboard/transition-recorder.js +209 -0
- package/src/dashboard/workflow-dashboard.js +434 -0
- package/src/enterprise/error-recovery.js +524 -0
- package/src/enterprise/experiment-report.js +573 -0
- package/src/enterprise/index.js +57 -4
- package/src/enterprise/rollback-manager.js +584 -0
- package/src/enterprise/tech-article.js +509 -0
- package/src/traceability/extractor.js +294 -0
- package/src/traceability/gap-detector.js +230 -0
- package/src/traceability/index.js +15 -0
- package/src/traceability/matrix-storage.js +368 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TraceabilityExtractor Implementation
|
|
3
|
+
*
|
|
4
|
+
* Extracts requirement ID patterns from code, tests, commits, and documents.
|
|
5
|
+
*
|
|
6
|
+
* Requirement: IMP-6.2-004-01
|
|
7
|
+
* Design: Section 5.1
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs').promises;
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { exec } = require('child_process');
|
|
13
|
+
const { promisify } = require('util');
|
|
14
|
+
|
|
15
|
+
const execAsync = promisify(exec);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Requirement ID patterns
|
|
19
|
+
*/
|
|
20
|
+
const REQ_PATTERNS = [
|
|
21
|
+
/REQ-[A-Z0-9]+-\d{3}/g, // REQ-XXX-NNN
|
|
22
|
+
/IMP-\d+\.\d+-\d{3}(?:-\d{2})?/g, // IMP-6.2-001 or IMP-6.2-001-01
|
|
23
|
+
/FEAT-\d{3}/g, // FEAT-001
|
|
24
|
+
/TASK-\d{3}/g, // TASK-001
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Default configuration
|
|
29
|
+
*/
|
|
30
|
+
const DEFAULT_CONFIG = {
|
|
31
|
+
sourceDir: 'src',
|
|
32
|
+
testDir: 'tests',
|
|
33
|
+
docDir: 'docs',
|
|
34
|
+
includePatterns: ['*.js', '*.ts', '*.tsx', '*.jsx', '*.md'],
|
|
35
|
+
excludePatterns: ['node_modules/**', '*.test.js', '*.spec.js'],
|
|
36
|
+
scanCommits: false,
|
|
37
|
+
maxCommits: 100
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* TraceabilityExtractor
|
|
42
|
+
*
|
|
43
|
+
* Extracts requirement references from various sources.
|
|
44
|
+
*/
|
|
45
|
+
class TraceabilityExtractor {
|
|
46
|
+
/**
|
|
47
|
+
* @param {Object} config - Configuration options
|
|
48
|
+
*/
|
|
49
|
+
constructor(config = {}) {
|
|
50
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Extract references from a single file
|
|
55
|
+
* @param {string} filePath - Path to file
|
|
56
|
+
* @param {string} sourceType - Type of source (code|test|document)
|
|
57
|
+
* @returns {Promise<Array>} Requirement references
|
|
58
|
+
*/
|
|
59
|
+
async extractFromFile(filePath, sourceType) {
|
|
60
|
+
try {
|
|
61
|
+
await fs.access(filePath);
|
|
62
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
63
|
+
|
|
64
|
+
return this.extractFromContent(content, filePath, sourceType);
|
|
65
|
+
} catch {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Extract references from content string
|
|
72
|
+
* @param {string} content - File content
|
|
73
|
+
* @param {string} filePath - File path
|
|
74
|
+
* @param {string} sourceType - Source type
|
|
75
|
+
* @returns {Array} Requirement references
|
|
76
|
+
*/
|
|
77
|
+
extractFromContent(content, filePath, sourceType) {
|
|
78
|
+
const refs = [];
|
|
79
|
+
const lines = content.split('\n');
|
|
80
|
+
const now = new Date().toISOString();
|
|
81
|
+
|
|
82
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
83
|
+
const line = lines[lineNum];
|
|
84
|
+
|
|
85
|
+
for (const pattern of REQ_PATTERNS) {
|
|
86
|
+
// Reset regex state
|
|
87
|
+
pattern.lastIndex = 0;
|
|
88
|
+
|
|
89
|
+
let match;
|
|
90
|
+
while ((match = pattern.exec(line)) !== null) {
|
|
91
|
+
refs.push({
|
|
92
|
+
requirementId: match[0],
|
|
93
|
+
sourceType,
|
|
94
|
+
filePath,
|
|
95
|
+
lineNumber: lineNum + 1,
|
|
96
|
+
context: line.trim().substring(0, 100),
|
|
97
|
+
foundAt: now
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return refs;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Extract references from git commits
|
|
108
|
+
* @returns {Promise<Array>} Requirement references from commits
|
|
109
|
+
*/
|
|
110
|
+
async extractFromCommits() {
|
|
111
|
+
const refs = [];
|
|
112
|
+
const now = new Date().toISOString();
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const maxCommits = this.config.maxCommits || 100;
|
|
116
|
+
const { stdout } = await execAsync(
|
|
117
|
+
`git log -${maxCommits} --format="%H|%s|%ad|%ae" --date=short`
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const lines = stdout.trim().split('\n').filter(l => l.length > 0);
|
|
121
|
+
|
|
122
|
+
for (const line of lines) {
|
|
123
|
+
const [hash, message, date, author] = line.split('|');
|
|
124
|
+
|
|
125
|
+
for (const pattern of REQ_PATTERNS) {
|
|
126
|
+
pattern.lastIndex = 0;
|
|
127
|
+
|
|
128
|
+
let match;
|
|
129
|
+
while ((match = pattern.exec(message)) !== null) {
|
|
130
|
+
refs.push({
|
|
131
|
+
requirementId: match[0],
|
|
132
|
+
sourceType: 'commit',
|
|
133
|
+
commitHash: hash,
|
|
134
|
+
commitMessage: message,
|
|
135
|
+
context: message,
|
|
136
|
+
foundAt: now
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
// Git not available or not in a git repo
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return refs;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Scan a directory for references
|
|
150
|
+
* @param {string} dirPath - Directory path
|
|
151
|
+
* @param {string} sourceType - Source type
|
|
152
|
+
* @returns {Promise<Array>} Requirement references
|
|
153
|
+
*/
|
|
154
|
+
async scanDirectory(dirPath, sourceType) {
|
|
155
|
+
const refs = [];
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const entries = await fs.readdir(dirPath);
|
|
159
|
+
|
|
160
|
+
for (const entry of entries) {
|
|
161
|
+
const fullPath = path.join(dirPath, entry);
|
|
162
|
+
const stat = await fs.stat(fullPath);
|
|
163
|
+
|
|
164
|
+
if (stat.isDirectory()) {
|
|
165
|
+
// Skip excluded directories
|
|
166
|
+
if (this.isExcluded(entry)) continue;
|
|
167
|
+
|
|
168
|
+
const subRefs = await this.scanDirectory(fullPath, sourceType);
|
|
169
|
+
refs.push(...subRefs);
|
|
170
|
+
} else if (stat.isFile() && this.shouldInclude(entry)) {
|
|
171
|
+
const fileRefs = await this.extractFromFile(fullPath, sourceType);
|
|
172
|
+
refs.push(...fileRefs);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
// Directory not accessible
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return refs;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Extract from all configured sources
|
|
184
|
+
* @returns {Promise<Array>} All requirement references
|
|
185
|
+
*/
|
|
186
|
+
async extractAll() {
|
|
187
|
+
const refs = [];
|
|
188
|
+
|
|
189
|
+
// Extract from source code
|
|
190
|
+
if (this.config.sourceDir) {
|
|
191
|
+
const codeRefs = await this.scanDirectory(this.config.sourceDir, 'code');
|
|
192
|
+
refs.push(...codeRefs);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Extract from tests
|
|
196
|
+
if (this.config.testDir) {
|
|
197
|
+
const testRefs = await this.scanDirectory(this.config.testDir, 'test');
|
|
198
|
+
refs.push(...testRefs);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Extract from documents
|
|
202
|
+
if (this.config.docDir) {
|
|
203
|
+
const docRefs = await this.scanDirectory(this.config.docDir, 'document');
|
|
204
|
+
refs.push(...docRefs);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Extract from commits
|
|
208
|
+
if (this.config.scanCommits) {
|
|
209
|
+
const commitRefs = await this.extractFromCommits();
|
|
210
|
+
refs.push(...commitRefs);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return refs;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Group references by requirement ID
|
|
218
|
+
* @param {Array} refs - References to group
|
|
219
|
+
* @returns {Map<string, Array>} Grouped references
|
|
220
|
+
*/
|
|
221
|
+
groupByRequirement(refs) {
|
|
222
|
+
const grouped = new Map();
|
|
223
|
+
|
|
224
|
+
for (const ref of refs) {
|
|
225
|
+
const existing = grouped.get(ref.requirementId) || [];
|
|
226
|
+
existing.push(ref);
|
|
227
|
+
grouped.set(ref.requirementId, existing);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return grouped;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Check if file should be included
|
|
235
|
+
* @param {string} filename - Filename to check
|
|
236
|
+
* @returns {boolean} Should include
|
|
237
|
+
*/
|
|
238
|
+
shouldInclude(filename) {
|
|
239
|
+
const includePatterns = this.config.includePatterns || [];
|
|
240
|
+
const excludePatterns = this.config.excludePatterns || [];
|
|
241
|
+
|
|
242
|
+
// Check exclude patterns first
|
|
243
|
+
for (const pattern of excludePatterns) {
|
|
244
|
+
if (this.matchPattern(filename, pattern)) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check include patterns
|
|
250
|
+
for (const pattern of includePatterns) {
|
|
251
|
+
if (this.matchPattern(filename, pattern)) {
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return includePatterns.length === 0;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Check if directory/file should be excluded
|
|
261
|
+
* @param {string} name - Name to check
|
|
262
|
+
* @returns {boolean} Should exclude
|
|
263
|
+
*/
|
|
264
|
+
isExcluded(name) {
|
|
265
|
+
const excludePatterns = this.config.excludePatterns || [];
|
|
266
|
+
|
|
267
|
+
for (const pattern of excludePatterns) {
|
|
268
|
+
if (pattern.includes('**')) {
|
|
269
|
+
// Glob pattern with directory
|
|
270
|
+
const dirPattern = pattern.split('**')[0].replace(/\/$/, '');
|
|
271
|
+
if (name === dirPattern) return true;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return name === 'node_modules' || name.startsWith('.');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Simple glob pattern matching
|
|
280
|
+
* @param {string} filename - Filename to match
|
|
281
|
+
* @param {string} pattern - Pattern to match against
|
|
282
|
+
* @returns {boolean} Matches
|
|
283
|
+
*/
|
|
284
|
+
matchPattern(filename, pattern) {
|
|
285
|
+
// Convert glob pattern to regex
|
|
286
|
+
const regexPattern = pattern
|
|
287
|
+
.replace(/\./g, '\\.')
|
|
288
|
+
.replace(/\*/g, '.*');
|
|
289
|
+
|
|
290
|
+
return new RegExp(`^${regexPattern}$`).test(filename);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
module.exports = { TraceabilityExtractor };
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GapDetector Implementation
|
|
3
|
+
*
|
|
4
|
+
* Detects requirements without implementation or tests.
|
|
5
|
+
*
|
|
6
|
+
* Requirement: IMP-6.2-004-02
|
|
7
|
+
* Design: Section 5.2
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Severity levels for gaps
|
|
12
|
+
*/
|
|
13
|
+
const SEVERITY_MAP = {
|
|
14
|
+
'no-test': 'critical',
|
|
15
|
+
'no-code': 'high',
|
|
16
|
+
'no-design': 'medium',
|
|
17
|
+
'no-commit': 'low'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Severity ordering for sorting
|
|
22
|
+
*/
|
|
23
|
+
const SEVERITY_ORDER = {
|
|
24
|
+
'critical': 0,
|
|
25
|
+
'high': 1,
|
|
26
|
+
'medium': 2,
|
|
27
|
+
'low': 3
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Suggestions for each gap type
|
|
32
|
+
*/
|
|
33
|
+
const SUGGESTIONS = {
|
|
34
|
+
'no-test': 'この要件に対するテストを作成してください。Article III (Test-First) に従い、実装前にテストを書くことを推奨します。',
|
|
35
|
+
'no-code': '要件の実装が必要です。設計ドキュメントを参照して実装を開始してください。',
|
|
36
|
+
'no-design': '設計ドキュメントを作成してください。C4モデルに従い、コンポーネント図とADRを追加することを推奨します。',
|
|
37
|
+
'no-commit': 'この要件に関連するコミットがありません。コミットメッセージに要件IDを含めてください。'
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* GapDetector
|
|
42
|
+
*
|
|
43
|
+
* Detects gaps in traceability between requirements and artifacts.
|
|
44
|
+
*/
|
|
45
|
+
class GapDetector {
|
|
46
|
+
/**
|
|
47
|
+
* Detect all gaps in traceability links
|
|
48
|
+
* @param {Array} links - Traceability links
|
|
49
|
+
* @returns {Array} Detected gaps
|
|
50
|
+
*/
|
|
51
|
+
detectGaps(links) {
|
|
52
|
+
const gaps = [];
|
|
53
|
+
|
|
54
|
+
for (const link of links) {
|
|
55
|
+
// Check for design gap
|
|
56
|
+
if (link.design.length === 0) {
|
|
57
|
+
gaps.push(this.createGap(link.requirementId, 'no-design'));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check for code gap
|
|
61
|
+
if (link.code.length === 0) {
|
|
62
|
+
gaps.push(this.createGap(link.requirementId, 'no-code'));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check for test gap
|
|
66
|
+
if (link.tests.length === 0) {
|
|
67
|
+
gaps.push(this.createGap(link.requirementId, 'no-test'));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return gaps;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Create a gap entry
|
|
76
|
+
* @param {string} requirementId - Requirement ID
|
|
77
|
+
* @param {string} gapType - Gap type
|
|
78
|
+
* @returns {Object} Gap entry
|
|
79
|
+
*/
|
|
80
|
+
createGap(requirementId, gapType) {
|
|
81
|
+
return {
|
|
82
|
+
requirementId,
|
|
83
|
+
gapType,
|
|
84
|
+
severity: SEVERITY_MAP[gapType],
|
|
85
|
+
suggestion: SUGGESTIONS[gapType]
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Analyze a traceability matrix for gaps
|
|
91
|
+
* @param {Object} matrix - Traceability matrix
|
|
92
|
+
* @returns {Object} Gap analysis
|
|
93
|
+
*/
|
|
94
|
+
analyzeMatrix(matrix) {
|
|
95
|
+
const links = Object.values(matrix.requirements);
|
|
96
|
+
const gaps = this.detectGaps(links);
|
|
97
|
+
|
|
98
|
+
return this.calculateAnalysis(gaps);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Calculate gap analysis
|
|
103
|
+
* @param {Array} gaps - Detected gaps
|
|
104
|
+
* @returns {Object} Gap analysis
|
|
105
|
+
*/
|
|
106
|
+
calculateAnalysis(gaps) {
|
|
107
|
+
const gapsByType = {};
|
|
108
|
+
const gapsByRequirement = {};
|
|
109
|
+
|
|
110
|
+
let criticalGaps = 0;
|
|
111
|
+
let highGaps = 0;
|
|
112
|
+
let mediumGaps = 0;
|
|
113
|
+
let lowGaps = 0;
|
|
114
|
+
|
|
115
|
+
for (const gap of gaps) {
|
|
116
|
+
// Count by type
|
|
117
|
+
gapsByType[gap.gapType] = (gapsByType[gap.gapType] || 0) + 1;
|
|
118
|
+
|
|
119
|
+
// Count by requirement
|
|
120
|
+
gapsByRequirement[gap.requirementId] =
|
|
121
|
+
(gapsByRequirement[gap.requirementId] || 0) + 1;
|
|
122
|
+
|
|
123
|
+
// Count by severity
|
|
124
|
+
switch (gap.severity) {
|
|
125
|
+
case 'critical':
|
|
126
|
+
criticalGaps++;
|
|
127
|
+
break;
|
|
128
|
+
case 'high':
|
|
129
|
+
highGaps++;
|
|
130
|
+
break;
|
|
131
|
+
case 'medium':
|
|
132
|
+
mediumGaps++;
|
|
133
|
+
break;
|
|
134
|
+
case 'low':
|
|
135
|
+
lowGaps++;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
totalGaps: gaps.length,
|
|
142
|
+
criticalGaps,
|
|
143
|
+
highGaps,
|
|
144
|
+
mediumGaps,
|
|
145
|
+
lowGaps,
|
|
146
|
+
gapsByType,
|
|
147
|
+
gapsByRequirement
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Generate a gap report
|
|
153
|
+
* @param {Array} links - Traceability links
|
|
154
|
+
* @returns {Object} Gap report
|
|
155
|
+
*/
|
|
156
|
+
getGapReport(links) {
|
|
157
|
+
const gaps = this.detectGaps(links);
|
|
158
|
+
const sortedGaps = this.sortGapsBySeverity(gaps);
|
|
159
|
+
const summary = this.calculateAnalysis(gaps);
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
generatedAt: new Date().toISOString(),
|
|
163
|
+
gaps: sortedGaps,
|
|
164
|
+
summary
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Sort gaps by severity (critical first)
|
|
170
|
+
* @param {Array} gaps - Gaps to sort
|
|
171
|
+
* @returns {Array} Sorted gaps
|
|
172
|
+
*/
|
|
173
|
+
sortGapsBySeverity(gaps) {
|
|
174
|
+
return [...gaps].sort((a, b) => {
|
|
175
|
+
return SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity];
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Filter gaps by severity
|
|
181
|
+
* @param {Array} gaps - Gaps to filter
|
|
182
|
+
* @param {string} severity - Severity to filter by
|
|
183
|
+
* @returns {Array} Filtered gaps
|
|
184
|
+
*/
|
|
185
|
+
filterGapsBySeverity(gaps, severity) {
|
|
186
|
+
return gaps.filter(g => g.severity === severity);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get unique requirement IDs that have gaps
|
|
191
|
+
* @param {Array} links - Traceability links
|
|
192
|
+
* @returns {Array} Requirement IDs with gaps
|
|
193
|
+
*/
|
|
194
|
+
getRequirementsWithGaps(links) {
|
|
195
|
+
const gaps = this.detectGaps(links);
|
|
196
|
+
const reqIds = new Set();
|
|
197
|
+
|
|
198
|
+
for (const gap of gaps) {
|
|
199
|
+
reqIds.add(gap.requirementId);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return Array.from(reqIds);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Check if a specific requirement has critical gaps
|
|
207
|
+
* @param {Object} link - Traceability link
|
|
208
|
+
* @returns {boolean} Has critical gap
|
|
209
|
+
*/
|
|
210
|
+
hasCriticalGap(link) {
|
|
211
|
+
// No tests is critical
|
|
212
|
+
return link.tests.length === 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get gap coverage percentage
|
|
217
|
+
* @param {Array} links - Traceability links
|
|
218
|
+
* @returns {number} Coverage percentage
|
|
219
|
+
*/
|
|
220
|
+
getGapCoverage(links) {
|
|
221
|
+
if (links.length === 0) return 100;
|
|
222
|
+
|
|
223
|
+
const reqsWithGaps = this.getRequirementsWithGaps(links);
|
|
224
|
+
const covered = links.length - reqsWithGaps.length;
|
|
225
|
+
|
|
226
|
+
return Math.round((covered / links.length) * 100);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = { GapDetector };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Traceability Module
|
|
3
|
+
*
|
|
4
|
+
* Requirement: IMP-6.2-004
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { TraceabilityExtractor } = require('./extractor');
|
|
8
|
+
const { GapDetector } = require('./gap-detector');
|
|
9
|
+
const { MatrixStorage } = require('./matrix-storage');
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
TraceabilityExtractor,
|
|
13
|
+
GapDetector,
|
|
14
|
+
MatrixStorage
|
|
15
|
+
};
|