docguard-cli 0.7.1 → 0.7.2
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/cli/commands/trace.mjs
CHANGED
|
@@ -89,7 +89,12 @@ export function runTrace(projectDir, config, flags) {
|
|
|
89
89
|
console.log(`${c.dim} Directory: ${projectDir}${c.reset}`);
|
|
90
90
|
console.log(`${c.dim} Generating requirements traceability matrix...${c.reset}\n`);
|
|
91
91
|
|
|
92
|
-
// ── 1.
|
|
92
|
+
// ── 1. Build set of required doc basenames from config ──
|
|
93
|
+
const requiredDocs = new Set(
|
|
94
|
+
(config.requiredFiles?.canonical || []).map(f => basename(f))
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// ── 2. Inventory canonical docs ──
|
|
93
98
|
const docsDir = resolve(projectDir, 'docs-canonical');
|
|
94
99
|
const canonicalDocs = [];
|
|
95
100
|
if (existsSync(docsDir)) {
|
|
@@ -98,16 +103,26 @@ export function runTrace(projectDir, config, flags) {
|
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
105
|
|
|
101
|
-
// ──
|
|
106
|
+
// ── 3. Scan project files ──
|
|
102
107
|
const projectFiles = [];
|
|
103
108
|
scanDir(projectDir, projectDir, projectFiles);
|
|
104
109
|
|
|
105
|
-
// ──
|
|
110
|
+
// ── 4. Build traceability matrix (only required docs) ──
|
|
106
111
|
const matrix = [];
|
|
112
|
+
const orphanedDocs = [];
|
|
107
113
|
|
|
108
114
|
for (const [docName, traceInfo] of Object.entries(TRACE_MAP)) {
|
|
109
115
|
const docPath = resolve(docsDir, docName);
|
|
110
116
|
const docExists = existsSync(docPath);
|
|
117
|
+
|
|
118
|
+
// Check if this doc is excluded from config
|
|
119
|
+
if (!requiredDocs.has(docName)) {
|
|
120
|
+
if (docExists) {
|
|
121
|
+
orphanedDocs.push(docName);
|
|
122
|
+
}
|
|
123
|
+
continue; // Skip excluded docs from the matrix
|
|
124
|
+
}
|
|
125
|
+
|
|
111
126
|
let lastModified = null;
|
|
112
127
|
let docSize = 0;
|
|
113
128
|
|
|
@@ -152,15 +167,15 @@ export function runTrace(projectDir, config, flags) {
|
|
|
152
167
|
});
|
|
153
168
|
}
|
|
154
169
|
|
|
155
|
-
// ──
|
|
170
|
+
// ── 5. Output ──
|
|
156
171
|
if (flags.format === 'json') {
|
|
157
|
-
outputJSON(config.projectName, matrix);
|
|
172
|
+
outputJSON(config.projectName, matrix, orphanedDocs);
|
|
158
173
|
} else {
|
|
159
|
-
outputText(config.projectName, matrix, canonicalDocs);
|
|
174
|
+
outputText(config.projectName, matrix, canonicalDocs, orphanedDocs);
|
|
160
175
|
}
|
|
161
176
|
}
|
|
162
177
|
|
|
163
|
-
function outputJSON(projectName, matrix) {
|
|
178
|
+
function outputJSON(projectName, matrix, orphanedDocs) {
|
|
164
179
|
const result = {
|
|
165
180
|
project: projectName,
|
|
166
181
|
traceability: matrix.map(m => ({
|
|
@@ -173,19 +188,21 @@ function outputJSON(projectName, matrix) {
|
|
|
173
188
|
tests: m.relatedTests.length,
|
|
174
189
|
traces: m.traces,
|
|
175
190
|
})),
|
|
191
|
+
orphanedDocs,
|
|
176
192
|
summary: {
|
|
177
193
|
total: matrix.length,
|
|
178
194
|
traced: matrix.filter(m => m.coverageSignal === 'TRACED').length,
|
|
179
195
|
partial: matrix.filter(m => m.coverageSignal === 'PARTIAL').length,
|
|
180
196
|
unlinked: matrix.filter(m => m.coverageSignal === 'UNLINKED').length,
|
|
181
197
|
missing: matrix.filter(m => m.coverageSignal === 'MISSING').length,
|
|
198
|
+
orphaned: orphanedDocs.length,
|
|
182
199
|
},
|
|
183
200
|
timestamp: new Date().toISOString(),
|
|
184
201
|
};
|
|
185
202
|
console.log(JSON.stringify(result, null, 2));
|
|
186
203
|
}
|
|
187
204
|
|
|
188
|
-
function outputText(projectName, matrix, canonicalDocs) {
|
|
205
|
+
function outputText(projectName, matrix, canonicalDocs, orphanedDocs) {
|
|
189
206
|
// Header table
|
|
190
207
|
console.log(` ${c.bold}Traceability Matrix${c.reset}\n`);
|
|
191
208
|
console.log(` ${c.dim}${'Document'.padEnd(22)} ${'Standard'.padEnd(28)} ${'Status'.padEnd(10)} ${'Sources'.padEnd(9)} ${'Tests'.padEnd(7)} ${'Last Modified'}${c.reset}`);
|
|
@@ -257,6 +274,16 @@ function outputText(projectName, matrix, canonicalDocs) {
|
|
|
257
274
|
console.log(` ${c.dim}Run ${c.cyan}docguard diagnose${c.dim} to fix coverage gaps.${c.reset}`);
|
|
258
275
|
}
|
|
259
276
|
|
|
277
|
+
// ── Orphaned docs warning ──
|
|
278
|
+
if (orphanedDocs.length > 0) {
|
|
279
|
+
console.log(`\n ${c.yellow}⚠️ Orphaned Files (${orphanedDocs.length})${c.reset}`);
|
|
280
|
+
console.log(` ${c.dim}These files exist in docs-canonical/ but are excluded from your config:${c.reset}`);
|
|
281
|
+
for (const doc of orphanedDocs) {
|
|
282
|
+
console.log(` ${c.yellow}→${c.reset} ${doc}`);
|
|
283
|
+
}
|
|
284
|
+
console.log(` ${c.dim}Delete them or add to .docguard.json requiredFiles.canonical${c.reset}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
260
287
|
console.log(`\n ${c.dim}Traceability methodology: ISO/IEC/IEEE 29119 (Lopez et al., AITPG, IEEE TSE 2026)${c.reset}\n`);
|
|
261
288
|
}
|
|
262
289
|
|
|
@@ -67,6 +67,8 @@ const TRACE_MAP = {
|
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
69
|
* Validate traceability — ensures canonical docs have corresponding source artifacts.
|
|
70
|
+
* Respects config.requiredFiles.canonical — only checks docs the user requires.
|
|
71
|
+
* Also warns about orphaned files (exist but excluded from config).
|
|
70
72
|
* @returns {{ errors: string[], warnings: string[], passed: number, total: number }}
|
|
71
73
|
*/
|
|
72
74
|
export function validateTraceability(projectDir, config) {
|
|
@@ -81,23 +83,26 @@ export function validateTraceability(projectDir, config) {
|
|
|
81
83
|
return { errors, warnings, passed: 0, total: 0 };
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
// Build set of required doc basenames from config
|
|
87
|
+
const requiredDocs = new Set(
|
|
88
|
+
(config.requiredFiles?.canonical || []).map(f => basename(f))
|
|
89
|
+
);
|
|
90
|
+
|
|
84
91
|
// Scan project files once
|
|
85
92
|
const projectFiles = [];
|
|
86
93
|
scanDir(projectDir, projectDir, projectFiles);
|
|
87
94
|
|
|
95
|
+
// ── Check required docs for traceability ──
|
|
88
96
|
for (const [docName, traceInfo] of Object.entries(TRACE_MAP)) {
|
|
97
|
+
// Skip docs not in the user's required list
|
|
98
|
+
if (!requiredDocs.has(docName)) continue;
|
|
99
|
+
|
|
89
100
|
total++;
|
|
90
101
|
const docPath = resolve(docsDir, docName);
|
|
91
102
|
const docExists = existsSync(docPath);
|
|
92
103
|
|
|
93
104
|
if (!docExists) {
|
|
94
|
-
|
|
95
|
-
if (docName === 'API-REFERENCE.md') {
|
|
96
|
-
// Don't count API-REFERENCE as missing — it's optional
|
|
97
|
-
total--;
|
|
98
|
-
} else {
|
|
99
|
-
warnings.push(`${docName} — document missing, no traceability possible`);
|
|
100
|
-
}
|
|
105
|
+
warnings.push(`${docName} — required but missing, no traceability possible`);
|
|
101
106
|
continue;
|
|
102
107
|
}
|
|
103
108
|
|
|
@@ -115,6 +120,16 @@ export function validateTraceability(projectDir, config) {
|
|
|
115
120
|
}
|
|
116
121
|
}
|
|
117
122
|
|
|
123
|
+
// ── Detect orphaned files (exist but not required) ──
|
|
124
|
+
try {
|
|
125
|
+
const existingDocs = readdirSync(docsDir).filter(f => f.endsWith('.md'));
|
|
126
|
+
for (const docFile of existingDocs) {
|
|
127
|
+
if (!requiredDocs.has(docFile) && TRACE_MAP[docFile]) {
|
|
128
|
+
warnings.push(`${docFile} — file exists in docs-canonical/ but is not in your requiredFiles config. Consider deleting it or adding it to .docguard.json requiredFiles.canonical`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} catch { /* ignore */ }
|
|
132
|
+
|
|
118
133
|
return { errors, warnings, passed, total };
|
|
119
134
|
}
|
|
120
135
|
|