@pactosigna/trace 0.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.
Files changed (101) hide show
  1. package/dist/cli.d.ts +8 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +7391 -0
  4. package/dist/commands/check.d.ts +11 -0
  5. package/dist/commands/check.d.ts.map +1 -0
  6. package/dist/commands/context.d.ts +9 -0
  7. package/dist/commands/context.d.ts.map +1 -0
  8. package/dist/commands/drm-check.d.ts +9 -0
  9. package/dist/commands/drm-check.d.ts.map +1 -0
  10. package/dist/commands/impact.d.ts +17 -0
  11. package/dist/commands/impact.d.ts.map +1 -0
  12. package/dist/commands/report.d.ts +8 -0
  13. package/dist/commands/report.d.ts.map +1 -0
  14. package/dist/commands/risk.d.ts +8 -0
  15. package/dist/commands/risk.d.ts.map +1 -0
  16. package/dist/commands/rtm.d.ts +8 -0
  17. package/dist/commands/rtm.d.ts.map +1 -0
  18. package/dist/commands/validate.d.ts +8 -0
  19. package/dist/commands/validate.d.ts.map +1 -0
  20. package/dist/commands/vsr.d.ts +9 -0
  21. package/dist/commands/vsr.d.ts.map +1 -0
  22. package/dist/config/load-config.d.ts +14 -0
  23. package/dist/config/load-config.d.ts.map +1 -0
  24. package/dist/config/types.d.ts +52 -0
  25. package/dist/config/types.d.ts.map +1 -0
  26. package/dist/formatters/format-check.d.ts +28 -0
  27. package/dist/formatters/format-check.d.ts.map +1 -0
  28. package/dist/formatters/format-impact.d.ts +37 -0
  29. package/dist/formatters/format-impact.d.ts.map +1 -0
  30. package/dist/formatters/format-risk.d.ts +21 -0
  31. package/dist/formatters/format-risk.d.ts.map +1 -0
  32. package/dist/impact/baseline-scan.d.ts +18 -0
  33. package/dist/impact/baseline-scan.d.ts.map +1 -0
  34. package/dist/impact/diff-gaps.d.ts +20 -0
  35. package/dist/impact/diff-gaps.d.ts.map +1 -0
  36. package/dist/index.d.ts +28 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +4794 -0
  39. package/dist/loaders/filesystem-document-loader.d.ts +40 -0
  40. package/dist/loaders/filesystem-document-loader.d.ts.map +1 -0
  41. package/dist/test-report/build-report.d.ts +6 -0
  42. package/dist/test-report/build-report.d.ts.map +1 -0
  43. package/dist/test-report/format-str.d.ts +6 -0
  44. package/dist/test-report/format-str.d.ts.map +1 -0
  45. package/dist/test-report/index.d.ts +24 -0
  46. package/dist/test-report/index.d.ts.map +1 -0
  47. package/dist/test-report/parse-results.d.ts +10 -0
  48. package/dist/test-report/parse-results.d.ts.map +1 -0
  49. package/dist/test-report/types.d.ts +112 -0
  50. package/dist/test-report/types.d.ts.map +1 -0
  51. package/dist/traceability/context.d.ts +24 -0
  52. package/dist/traceability/context.d.ts.map +1 -0
  53. package/dist/traceability/cross-reference.d.ts +15 -0
  54. package/dist/traceability/cross-reference.d.ts.map +1 -0
  55. package/dist/traceability/drm-check.d.ts +29 -0
  56. package/dist/traceability/drm-check.d.ts.map +1 -0
  57. package/dist/traceability/format-report.d.ts +6 -0
  58. package/dist/traceability/format-report.d.ts.map +1 -0
  59. package/dist/traceability/format-rtm.d.ts +7 -0
  60. package/dist/traceability/format-rtm.d.ts.map +1 -0
  61. package/dist/traceability/graph/build-graph.d.ts +10 -0
  62. package/dist/traceability/graph/build-graph.d.ts.map +1 -0
  63. package/dist/traceability/graph/coverage-query.d.ts +20 -0
  64. package/dist/traceability/graph/coverage-query.d.ts.map +1 -0
  65. package/dist/traceability/graph/format.d.ts +21 -0
  66. package/dist/traceability/graph/format.d.ts.map +1 -0
  67. package/dist/traceability/graph/parse-docs.d.ts +12 -0
  68. package/dist/traceability/graph/parse-docs.d.ts.map +1 -0
  69. package/dist/traceability/graph/parse-tests.d.ts +14 -0
  70. package/dist/traceability/graph/parse-tests.d.ts.map +1 -0
  71. package/dist/traceability/graph/resolve.d.ts +35 -0
  72. package/dist/traceability/graph/resolve.d.ts.map +1 -0
  73. package/dist/traceability/graph/types.d.ts +68 -0
  74. package/dist/traceability/graph/types.d.ts.map +1 -0
  75. package/dist/traceability/graph/walk.d.ts +10 -0
  76. package/dist/traceability/graph/walk.d.ts.map +1 -0
  77. package/dist/traceability/impact.d.ts +43 -0
  78. package/dist/traceability/impact.d.ts.map +1 -0
  79. package/dist/traceability/index.d.ts +10 -0
  80. package/dist/traceability/index.d.ts.map +1 -0
  81. package/dist/traceability/load-rules.d.ts +43 -0
  82. package/dist/traceability/load-rules.d.ts.map +1 -0
  83. package/dist/traceability/lookups.d.ts +25 -0
  84. package/dist/traceability/lookups.d.ts.map +1 -0
  85. package/dist/traceability/parse-compliance.d.ts +7 -0
  86. package/dist/traceability/parse-compliance.d.ts.map +1 -0
  87. package/dist/traceability/parse-frontmatter.d.ts +13 -0
  88. package/dist/traceability/parse-frontmatter.d.ts.map +1 -0
  89. package/dist/traceability/parse-requirements.d.ts +19 -0
  90. package/dist/traceability/parse-requirements.d.ts.map +1 -0
  91. package/dist/traceability/scan-e2e.d.ts +14 -0
  92. package/dist/traceability/scan-e2e.d.ts.map +1 -0
  93. package/dist/traceability/scan-unit.d.ts +7 -0
  94. package/dist/traceability/scan-unit.d.ts.map +1 -0
  95. package/dist/traceability/trace-cli.d.ts +20 -0
  96. package/dist/traceability/trace-cli.d.ts.map +1 -0
  97. package/dist/traceability/types.d.ts +50 -0
  98. package/dist/traceability/types.d.ts.map +1 -0
  99. package/dist/validation-report/index.d.ts +13 -0
  100. package/dist/validation-report/index.d.ts.map +1 -0
  101. package/package.json +48 -0
package/dist/index.js ADDED
@@ -0,0 +1,4794 @@
1
+ // src/config/load-config.ts
2
+ import { readFileSync } from "fs";
3
+ import { join } from "path";
4
+ import yaml from "js-yaml";
5
+ var CONFIG_FILENAME = "pactosigna-trace.yaml";
6
+ function defaultConfig() {
7
+ return {
8
+ version: 1,
9
+ deviceSafetyClass: void 0,
10
+ codeMap: [],
11
+ folderOverrides: [],
12
+ softwareItems: void 0,
13
+ impactRules: void 0
14
+ };
15
+ }
16
+ function isRecord(value) {
17
+ return typeof value === "object" && value !== null && !Array.isArray(value);
18
+ }
19
+ function parseCodeMap(raw) {
20
+ if (!Array.isArray(raw)) return [];
21
+ const entries = [];
22
+ for (const item of raw) {
23
+ if (!isRecord(item)) continue;
24
+ if (typeof item.path !== "string") continue;
25
+ const softwareItems = Array.isArray(item.software_items) ? item.software_items.filter((s) => typeof s === "string") : [];
26
+ entries.push({ path: item.path, softwareItems });
27
+ }
28
+ return entries;
29
+ }
30
+ function parseFolderOverrides(raw) {
31
+ if (!Array.isArray(raw)) return [];
32
+ const overrides = [];
33
+ for (const item of raw) {
34
+ if (!isRecord(item)) continue;
35
+ if (typeof item.pattern !== "string") continue;
36
+ if (typeof item.doc_type !== "string") continue;
37
+ const prefixes = Array.isArray(item.prefixes) ? item.prefixes.filter((s) => typeof s === "string") : void 0;
38
+ overrides.push({
39
+ pattern: item.pattern,
40
+ docType: item.doc_type,
41
+ prefixes
42
+ });
43
+ }
44
+ return overrides;
45
+ }
46
+ function parseSoftwareItems(raw) {
47
+ if (!isRecord(raw)) return void 0;
48
+ const result = {};
49
+ for (const [name, value] of Object.entries(raw)) {
50
+ if (!isRecord(value)) continue;
51
+ if (typeof value.domain !== "string") continue;
52
+ const codePaths = Array.isArray(value.code_paths) ? value.code_paths.filter((s) => typeof s === "string") : [];
53
+ const architecture = Array.isArray(value.architecture) ? value.architecture.filter((s) => typeof s === "string") : [];
54
+ result[name] = { domain: value.domain, codePaths, architecture };
55
+ }
56
+ return Object.keys(result).length > 0 ? result : void 0;
57
+ }
58
+ function parseImpactRules(raw) {
59
+ if (!isRecord(raw)) return void 0;
60
+ const domains = isRecord(raw.domains) ? raw.domains : {};
61
+ const rules = Array.isArray(raw.rules) ? raw.rules : [];
62
+ if (Object.keys(domains).length === 0 && rules.length === 0) {
63
+ return void 0;
64
+ }
65
+ return { domains, rules };
66
+ }
67
+ function loadTraceConfig(rootDir) {
68
+ const filePath = join(rootDir, CONFIG_FILENAME);
69
+ let raw;
70
+ try {
71
+ raw = readFileSync(filePath, "utf-8");
72
+ } catch {
73
+ return defaultConfig();
74
+ }
75
+ let parsed;
76
+ try {
77
+ parsed = yaml.load(raw);
78
+ } catch {
79
+ return defaultConfig();
80
+ }
81
+ if (!isRecord(parsed)) {
82
+ return defaultConfig();
83
+ }
84
+ const version = typeof parsed.version === "number" ? parsed.version : 1;
85
+ const deviceSafetyClass = typeof parsed.device_safety_class === "string" ? parsed.device_safety_class : void 0;
86
+ const codeMap = parseCodeMap(parsed.code_map);
87
+ const folderOverrides = parseFolderOverrides(parsed.folder_rules);
88
+ const softwareItems = parseSoftwareItems(parsed.software_items);
89
+ const impactRules = parseImpactRules(parsed.impact_rules);
90
+ return { version, deviceSafetyClass, codeMap, folderOverrides, softwareItems, impactRules };
91
+ }
92
+
93
+ // src/loaders/filesystem-document-loader.ts
94
+ import { readdirSync, readFileSync as readFileSync2, statSync } from "fs";
95
+ import { join as join2, relative } from "path";
96
+ import matter from "gray-matter";
97
+ import { minimatch } from "minimatch";
98
+
99
+ // ../schemas/dist/chunk-MFMBM63Y.js
100
+ var FOLDER_RULES = {
101
+ // Product (ISO 13485 §7.3)
102
+ "docs/product": {
103
+ prefixes: ["PDP-", "IU-"],
104
+ type: ["product_development_plan", "intended_use"]
105
+ },
106
+ "docs/product/requirements": { prefixes: ["PRS-"], type: "requirement", category: "product" },
107
+ "docs/product/user-needs": { prefixes: ["UN-"], type: "user_need" },
108
+ // Software requirements (moved under software discipline)
109
+ "docs/software/requirements": { prefixes: ["SRS-"], type: "requirement", category: "software" },
110
+ // Design (IEC 62304 — under software discipline)
111
+ // DDD prefix retained (#665) — existing DDD- docs still live in this folder.
112
+ "docs/software/architecture": { prefixes: ["HLD-", "SAD-", "DDD-"], type: "architecture" },
113
+ "docs/software/design": { prefixes: ["SDD-"], type: "detailed_design" },
114
+ // Test (protocols + reports)
115
+ "docs/test/protocols": { prefixes: ["TP-"], type: "test_protocol" },
116
+ "docs/test/reports": { prefixes: ["TR-"], type: "test_report" },
117
+ // Risk management — ISO 14971 (umbrella)
118
+ "docs/risk/plans": { prefixes: ["RMP-"], type: "risk_management_plan" },
119
+ "docs/risk/situations": { prefixes: ["HS-"], type: "hazardous_situation" },
120
+ "docs/risk/harms": { prefixes: ["HARM-"], type: "harm" },
121
+ "docs/risk/hazard-categories": { prefixes: ["HC-"], type: "hazard_category" },
122
+ // Software lifecycle — IEC 62304
123
+ "docs/software/plans": {
124
+ prefixes: ["DEV-PLAN-", "MAINT-PLAN-", "SDP-", "STP-"],
125
+ type: [
126
+ "software_development_plan",
127
+ "software_maintenance_plan",
128
+ "software_development_plan",
129
+ "software_test_plan"
130
+ ]
131
+ },
132
+ "docs/software/risks": {
133
+ prefixes: ["HAZ-SW-", "RISK-SW-"],
134
+ type: ["haz_soe_software", "software_risk"]
135
+ },
136
+ "docs/software/soup": { prefixes: ["SOUP-"], type: "soup_register" },
137
+ // Problem resolution — IEC 62304 §9
138
+ "docs/software/anomalies": { prefixes: ["ANOM-"], type: "anomaly" },
139
+ // Cybersecurity — IEC 81001-5-1
140
+ "docs/cybersecurity/plans": { prefixes: ["CSPLAN-"], type: "cybersecurity_plan" },
141
+ "docs/cybersecurity/risks": {
142
+ prefixes: ["HAZ-SEC-", "RISK-SEC-"],
143
+ type: ["haz_soe_security", "security_risk"]
144
+ },
145
+ "docs/cybersecurity/sbom": { prefixes: ["SBOM-"], type: "sbom" },
146
+ // Usability engineering — IEC 62366
147
+ "docs/usability": { prefixes: ["UEP-"], type: "usability_plan" },
148
+ "docs/usability/use-specifications": { prefixes: ["US-"], type: "use_specification" },
149
+ "docs/usability/task-analyses": { prefixes: ["TA-"], type: "task_analysis" },
150
+ "docs/usability/evaluations": {
151
+ prefixes: ["UE-", "SE-"],
152
+ type: ["usability_evaluation", "summative_evaluation"]
153
+ },
154
+ "docs/usability/risks": { prefixes: ["RISK-US-"], type: "usability_risk" },
155
+ // Clinical — MDR / FDA
156
+ "docs/clinical": {
157
+ prefixes: ["CEP-", "CER-"],
158
+ type: ["clinical_evaluation_plan", "clinical_evaluation_report"]
159
+ },
160
+ // Post-market surveillance — MDR Art. 83
161
+ "docs/post-market": { prefixes: ["PMS-"], type: "post_market_surveillance_plan" },
162
+ "docs/post-market/feedback": { prefixes: ["PMF-"], type: "post_market_feedback" },
163
+ // Labeling — MDR Annex I
164
+ "docs/labeling": { prefixes: ["LBL-"], type: "labeling" },
165
+ // QMS documents
166
+ "docs/policies": { prefixes: ["POL-"], type: "policy" },
167
+ "docs/sops": { prefixes: ["SOP-"], type: "sop" },
168
+ "docs/work-instructions": { prefixes: ["WI-"], type: "work_instruction" },
169
+ // Internal audit management (ISO 13485 §8.2.2)
170
+ "docs/audits/schedules": { prefixes: ["AUD-SCH-"], type: "audit_schedule" },
171
+ "docs/audits/reports": { prefixes: ["AUD-RPT-"], type: "audit_report" },
172
+ // Management review (ISO 13485 §5.6) — management reviews are in-app entities
173
+ // (qualityReviews collection), not git-native documents. Folder rule removed (#707).
174
+ // Supplier management (ISO 13485 §7.4)
175
+ "docs/suppliers": { prefixes: ["SUP-"], type: "supplier" },
176
+ // Personnel (ISO 13485 §5.5, §6.2)
177
+ "docs/personnel": { prefixes: ["JD-"], type: "job_description" }
178
+ };
179
+ function normalizePath(path) {
180
+ return path.toLowerCase().replace(/\\/g, "/");
181
+ }
182
+ var MONOREPO_QMS_PREFIX = "docs/qms/";
183
+ function normalizeMonorepoPath(filePath) {
184
+ const normalized = normalizePath(filePath);
185
+ if (normalized.startsWith(MONOREPO_QMS_PREFIX)) {
186
+ return "docs/" + normalized.slice(MONOREPO_QMS_PREFIX.length);
187
+ }
188
+ return normalized;
189
+ }
190
+ function extractFolder(filePath) {
191
+ const normalized = normalizeMonorepoPath(filePath);
192
+ const parts = normalized.split("/");
193
+ parts.pop();
194
+ return parts.join("/");
195
+ }
196
+ function findFolderRule(filePath) {
197
+ const folder = extractFolder(filePath);
198
+ if (FOLDER_RULES[folder]) {
199
+ return { rule: FOLDER_RULES[folder], folder };
200
+ }
201
+ const parts = folder.split("/");
202
+ while (parts.length > 0) {
203
+ const partial = parts.join("/");
204
+ if (FOLDER_RULES[partial]) {
205
+ return { rule: FOLDER_RULES[partial], folder: partial };
206
+ }
207
+ parts.pop();
208
+ }
209
+ return null;
210
+ }
211
+ function inferDocumentType(filePath, documentId) {
212
+ const match = findFolderRule(filePath);
213
+ if (!match) {
214
+ return null;
215
+ }
216
+ const { rule } = match;
217
+ if (typeof rule.type === "string") {
218
+ return {
219
+ docType: rule.type,
220
+ category: rule.category
221
+ };
222
+ }
223
+ const prefixIndex = rule.prefixes.findIndex(
224
+ (prefix) => documentId.toUpperCase().startsWith(prefix.toUpperCase())
225
+ );
226
+ if (prefixIndex === -1) {
227
+ return {
228
+ docType: rule.type[0],
229
+ category: rule.category
230
+ };
231
+ }
232
+ return {
233
+ docType: rule.type[prefixIndex],
234
+ category: rule.category
235
+ };
236
+ }
237
+
238
+ // src/loaders/filesystem-document-loader.ts
239
+ var SCAN_DIRECTORIES = ["docs", "compliance"];
240
+ var LINK_FIELD_MAP = {
241
+ traces_from: "derives_from",
242
+ derives_from: "derives_from",
243
+ derives: "derives_from",
244
+ traces_to: "implements",
245
+ implements: "implements",
246
+ leads_to: "leads_to",
247
+ results_in: "results_in",
248
+ analyzes: "analyzes",
249
+ verified_by: "verified_by",
250
+ mitigated_by: "mitigates"
251
+ };
252
+ var LINK_FIELDS = Object.keys(LINK_FIELD_MAP);
253
+ function collectMarkdownFiles(dir) {
254
+ const results = [];
255
+ let entries;
256
+ try {
257
+ entries = readdirSync(dir);
258
+ } catch {
259
+ return results;
260
+ }
261
+ for (const entry of entries) {
262
+ const fullPath = join2(dir, entry);
263
+ let stat;
264
+ try {
265
+ stat = statSync(fullPath);
266
+ } catch {
267
+ continue;
268
+ }
269
+ if (stat.isDirectory()) {
270
+ results.push(...collectMarkdownFiles(fullPath));
271
+ } else if (stat.isFile() && entry.endsWith(".md") && entry.toLowerCase() !== "readme.md") {
272
+ results.push(fullPath);
273
+ }
274
+ }
275
+ return results;
276
+ }
277
+ function matchFolderOverride(relPath, overrides) {
278
+ for (const override of overrides) {
279
+ if (minimatch(relPath, override.pattern)) {
280
+ return override;
281
+ }
282
+ }
283
+ return null;
284
+ }
285
+ function parseFile(absolutePath, rootDir, folderOverrides = []) {
286
+ let raw;
287
+ try {
288
+ raw = readFileSync2(absolutePath, "utf-8");
289
+ } catch {
290
+ return null;
291
+ }
292
+ if (!raw.trimStart().startsWith("---")) {
293
+ return null;
294
+ }
295
+ let parsed;
296
+ try {
297
+ parsed = matter(raw);
298
+ } catch {
299
+ return null;
300
+ }
301
+ const frontmatter = parsed.data;
302
+ const id = frontmatter.id;
303
+ if (!id || typeof id !== "string" || id.trim() === "") {
304
+ return null;
305
+ }
306
+ const documentId = id.trim();
307
+ const relPath = relative(rootDir, absolutePath).replace(/\\/g, "/");
308
+ const override = matchFolderOverride(relPath, folderOverrides);
309
+ let docType;
310
+ let category;
311
+ if (override) {
312
+ docType = override.docType;
313
+ category = void 0;
314
+ } else {
315
+ const typeResult = inferDocumentType(relPath, documentId);
316
+ docType = typeResult?.docType ?? "unknown";
317
+ category = typeResult?.category;
318
+ }
319
+ const title = typeof frontmatter.title === "string" && frontmatter.title.trim() !== "" ? frontmatter.title.trim() : documentId;
320
+ const metadata = {};
321
+ for (const [key, value] of Object.entries(frontmatter)) {
322
+ if (key !== "id" && key !== "title") {
323
+ metadata[key] = value;
324
+ }
325
+ }
326
+ if (category) {
327
+ metadata.category = category;
328
+ }
329
+ return {
330
+ documentId,
331
+ title,
332
+ docType,
333
+ category,
334
+ metadata,
335
+ body: parsed.content,
336
+ filePath: relPath
337
+ };
338
+ }
339
+ function extractLinks(file) {
340
+ const links = [];
341
+ for (const field of LINK_FIELDS) {
342
+ const value = file.metadata[field];
343
+ if (!value) continue;
344
+ const linkType = LINK_FIELD_MAP[field];
345
+ const targets = normalizeToArray(value);
346
+ for (const targetId of targets) {
347
+ if (typeof targetId === "string" && targetId.trim() !== "") {
348
+ links.push({
349
+ sourceDocumentId: file.documentId,
350
+ targetDocumentId: targetId.trim(),
351
+ linkType
352
+ });
353
+ }
354
+ }
355
+ }
356
+ return links;
357
+ }
358
+ function normalizeToArray(value) {
359
+ if (Array.isArray(value)) {
360
+ return value.filter((v) => typeof v === "string");
361
+ }
362
+ if (typeof value === "string") {
363
+ return [value];
364
+ }
365
+ return [];
366
+ }
367
+ var FilesystemDocumentLoader = class {
368
+ rootDir;
369
+ folderOverrides;
370
+ cachedFiles = null;
371
+ constructor(rootDir, config) {
372
+ this.rootDir = rootDir;
373
+ this.folderOverrides = config?.folderOverrides ?? [];
374
+ }
375
+ /**
376
+ * Load and return all valid DocumentRef[] from the repository.
377
+ */
378
+ loadDocuments() {
379
+ return this.getParsedFiles().map((file) => ({
380
+ documentId: file.documentId,
381
+ title: file.title,
382
+ docType: file.docType,
383
+ metadata: file.metadata
384
+ }));
385
+ }
386
+ /**
387
+ * Extract all LinkRef[] from frontmatter link fields across all documents.
388
+ */
389
+ loadLinks() {
390
+ const allLinks = [];
391
+ for (const file of this.getParsedFiles()) {
392
+ allLinks.push(...extractLinks(file));
393
+ }
394
+ return allLinks;
395
+ }
396
+ /**
397
+ * Return a Map of documentId to markdown body content (without frontmatter).
398
+ */
399
+ loadBodies() {
400
+ const bodies = /* @__PURE__ */ new Map();
401
+ for (const file of this.getParsedFiles()) {
402
+ bodies.set(file.documentId, file.body);
403
+ }
404
+ return bodies;
405
+ }
406
+ /**
407
+ * Parse all markdown files from scan directories. Results are cached
408
+ * so repeated calls to loadDocuments/loadLinks/loadBodies are cheap.
409
+ */
410
+ getParsedFiles() {
411
+ if (this.cachedFiles) {
412
+ return this.cachedFiles;
413
+ }
414
+ const files = [];
415
+ for (const dir of SCAN_DIRECTORIES) {
416
+ const absoluteDir = join2(this.rootDir, dir);
417
+ const mdFiles = collectMarkdownFiles(absoluteDir);
418
+ for (const filePath of mdFiles) {
419
+ const parsed = parseFile(filePath, this.rootDir, this.folderOverrides);
420
+ if (parsed) {
421
+ files.push(parsed);
422
+ }
423
+ }
424
+ }
425
+ this.cachedFiles = files;
426
+ return files;
427
+ }
428
+ };
429
+
430
+ // src/traceability/scan-e2e.ts
431
+ var REQ_TAG_REGEX = /@req-((?:PRS|SRS)-[A-Z]+-\d+\.\d+[a-z]?)/g;
432
+ var REQ_TAG_TEST_REGEX = /@req-((?:PRS|SRS)-[A-Z]+-\d+\.\d+[a-z]?)/;
433
+ function extractReqTagsFromFeature(content, filePath) {
434
+ const lines = content.split("\n");
435
+ const references = [];
436
+ let pendingTags = [];
437
+ for (const line of lines) {
438
+ const trimmed = line.trim();
439
+ if (trimmed.startsWith("@")) {
440
+ const tags = [...trimmed.matchAll(REQ_TAG_REGEX)].map((m) => m[1]);
441
+ pendingTags.push(...tags);
442
+ continue;
443
+ }
444
+ const scenarioMatch = trimmed.match(/^Scenario(?: Outline)?:\s*(.+)$/);
445
+ if (scenarioMatch && pendingTags.length > 0) {
446
+ const scenarioName = scenarioMatch[1].trim();
447
+ for (const reqId of pendingTags) {
448
+ references.push({
449
+ requirementId: reqId,
450
+ testType: "e2e",
451
+ file: filePath,
452
+ name: scenarioName
453
+ });
454
+ }
455
+ pendingTags = [];
456
+ continue;
457
+ }
458
+ pendingTags = [];
459
+ }
460
+ return references;
461
+ }
462
+ function findOrphanScenarios(content, filePath) {
463
+ const lines = content.split("\n");
464
+ const orphans = [];
465
+ let hasReqTag = false;
466
+ for (const line of lines) {
467
+ const trimmed = line.trim();
468
+ if (trimmed.startsWith("@")) {
469
+ if (REQ_TAG_TEST_REGEX.test(trimmed)) {
470
+ hasReqTag = true;
471
+ }
472
+ continue;
473
+ }
474
+ const scenarioMatch = trimmed.match(/^Scenario(?: Outline)?:\s*(.+)$/);
475
+ if (scenarioMatch) {
476
+ if (!hasReqTag) {
477
+ orphans.push({ file: filePath, name: scenarioMatch[1].trim() });
478
+ }
479
+ hasReqTag = false;
480
+ continue;
481
+ }
482
+ hasReqTag = false;
483
+ }
484
+ return orphans;
485
+ }
486
+
487
+ // src/traceability/scan-unit.ts
488
+ var REQ_MARKER_REGEX = /@req\s+((?:PRS|SRS)-[A-Z]+-\d+\.\d+[a-z]?)/g;
489
+ function extractReqMarkersFromTestFile(content, filePath) {
490
+ const references = [];
491
+ const itBlockRegex = /(?:describe|it|test)\s*\(\s*['"`]([^'"`]+)['"`]/g;
492
+ let match;
493
+ while ((match = itBlockRegex.exec(content)) !== null) {
494
+ const testName = match[1];
495
+ const reqMatches = [...testName.matchAll(REQ_MARKER_REGEX)];
496
+ for (const reqMatch of reqMatches) {
497
+ const reqId = reqMatch[1];
498
+ const cleanName = testName.replace(/@req\s+(?:PRS|SRS)-[A-Z]+-\d+\.\d+[a-z]?\s*—?\s*/g, "").trim();
499
+ references.push({
500
+ requirementId: reqId,
501
+ testType: "unit",
502
+ file: filePath,
503
+ name: cleanName || testName
504
+ });
505
+ }
506
+ }
507
+ return references;
508
+ }
509
+
510
+ // src/traceability/parse-requirements.ts
511
+ function parseRequirementsFromMarkdown(content, documentId, level) {
512
+ const domain = extractDomain(documentId);
513
+ const lines = content.split("\n");
514
+ const tableStartIdx = lines.findIndex(
515
+ (line) => line.includes("Requirement") && line.includes("Criteria") && line.includes("|") && (line.includes("Verification Method") || line.includes("Method"))
516
+ );
517
+ if (tableStartIdx === -1) return [];
518
+ const headerLine = lines[tableStartIdx];
519
+ const hasProtocolColumn = headerLine.includes("Protocol");
520
+ const dataStartIdx = tableStartIdx + 2;
521
+ const requirements = [];
522
+ for (let i = dataStartIdx; i < lines.length; i++) {
523
+ const line = lines[i].trim();
524
+ if (!line.startsWith("|")) break;
525
+ const cells = line.split("|").map((c) => c.trim()).filter(Boolean);
526
+ let reqId;
527
+ let verificationMethod;
528
+ let criteria;
529
+ if (hasProtocolColumn) {
530
+ if (cells.length < 4) continue;
531
+ reqId = cells[0];
532
+ verificationMethod = cells[2];
533
+ criteria = cells.slice(3).join(" | ");
534
+ } else {
535
+ if (cells.length < 3) continue;
536
+ reqId = cells[0];
537
+ verificationMethod = cells[1];
538
+ criteria = cells.slice(2).join(" | ");
539
+ }
540
+ requirements.push({
541
+ id: reqId,
542
+ document: documentId,
543
+ domain,
544
+ level,
545
+ verificationMethod,
546
+ criteria,
547
+ regulatoryCompliance: []
548
+ });
549
+ }
550
+ return requirements;
551
+ }
552
+ function extractDomain(documentId) {
553
+ const match = documentId.match(/^(?:PRS|SRS)-([A-Z]+)-\d+$/);
554
+ return match?.[1] ?? "UNKNOWN";
555
+ }
556
+
557
+ // src/traceability/cross-reference.ts
558
+ function buildTraceabilityReport(requirements, testRefs, compliance, orphanScenarios) {
559
+ const reqMap = new Map(requirements.map((r) => [r.id, r]));
560
+ const covered = /* @__PURE__ */ new Map();
561
+ const gaps = [];
562
+ for (const ref of testRefs) {
563
+ if (!reqMap.has(ref.requirementId)) {
564
+ gaps.push({
565
+ category: "INVALID_REQ_REFERENCE",
566
+ message: `Test "${ref.name}" in ${ref.file} references non-existent requirement ${ref.requirementId}`,
567
+ requirementId: ref.requirementId,
568
+ testFile: ref.file
569
+ });
570
+ continue;
571
+ }
572
+ const existing = covered.get(ref.requirementId) ?? [];
573
+ existing.push(ref);
574
+ covered.set(ref.requirementId, existing);
575
+ }
576
+ const uncovered = requirements.filter((req) => {
577
+ const method = req.verificationMethod;
578
+ const needsTest = method === "E2E Test" || method === "Unit Test";
579
+ return needsTest && !covered.has(req.id);
580
+ });
581
+ for (const req of uncovered) {
582
+ gaps.push({
583
+ category: "MISSING_TEST_COVERAGE",
584
+ message: `${req.id} (${req.criteria}) requires ${req.verificationMethod} but has no test`,
585
+ requirementId: req.id
586
+ });
587
+ }
588
+ for (const orphan of orphanScenarios) {
589
+ gaps.push({
590
+ category: "ORPHAN_TEST",
591
+ message: `Scenario "${orphan.name}" in ${orphan.file} has no @req tag`,
592
+ testFile: orphan.file
593
+ });
594
+ }
595
+ for (const entry of compliance) {
596
+ if (entry.status !== "Compliant") continue;
597
+ for (const reqId of entry.linkedRequirements) {
598
+ if (!covered.has(reqId)) {
599
+ gaps.push({
600
+ category: "COMPLIANCE_WITHOUT_EVIDENCE",
601
+ message: `Part 11 \xA7${entry.section} claims Compliant via ${reqId}, but ${reqId} has no test coverage`,
602
+ requirementId: reqId
603
+ });
604
+ }
605
+ }
606
+ }
607
+ return {
608
+ matrix: {
609
+ requirements,
610
+ testReferences: testRefs,
611
+ covered,
612
+ uncovered,
613
+ orphanTests: testRefs.filter((r) => !reqMap.has(r.requirementId))
614
+ },
615
+ compliance,
616
+ gaps
617
+ };
618
+ }
619
+ function buildDocumentChainGaps(documentLinks, _riskEntries, knownDocIds, _requirements) {
620
+ const gaps = [];
621
+ for (const link of documentLinks) {
622
+ if (!knownDocIds.has(link.targetId)) {
623
+ gaps.push({
624
+ category: "BROKEN_TRACE",
625
+ message: `${link.sourceId} has ${link.direction} \u2192 ${link.targetId}, but ${link.targetId} does not exist`,
626
+ requirementId: link.sourceId
627
+ });
628
+ }
629
+ }
630
+ const tracedToTargets = /* @__PURE__ */ new Set();
631
+ for (const link of documentLinks) {
632
+ if (link.direction === "derives" || link.direction === "traces_to") {
633
+ tracedToTargets.add(link.targetId);
634
+ }
635
+ if (link.direction === "traces_from") {
636
+ tracedToTargets.add(link.sourceId);
637
+ }
638
+ }
639
+ const prsDocIds = [...knownDocIds].filter((id) => id.startsWith("PRS-"));
640
+ const srsDocIds = [...knownDocIds].filter((id) => id.startsWith("SRS-"));
641
+ for (const prsId of prsDocIds) {
642
+ if (!tracedToTargets.has(prsId)) {
643
+ gaps.push({
644
+ category: "ORPHAN_REQUIREMENT",
645
+ message: `${prsId} is not traced from any upstream User Need document`,
646
+ requirementId: prsId
647
+ });
648
+ }
649
+ }
650
+ for (const srsId of srsDocIds) {
651
+ if (!tracedToTargets.has(srsId)) {
652
+ gaps.push({
653
+ category: "ORPHAN_REQUIREMENT",
654
+ message: `${srsId} is not traced from any upstream Product Requirement document`,
655
+ requirementId: srsId
656
+ });
657
+ }
658
+ }
659
+ return gaps;
660
+ }
661
+
662
+ // src/traceability/index.ts
663
+ import { readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
664
+ import { join as join4, relative as relative2 } from "path";
665
+
666
+ // src/traceability/parse-compliance.ts
667
+ var STATUS_VALUES = ["Compliant", "Partially Compliant", "Planned", "N/A"];
668
+ var REQ_ID_REGEX = /((?:PRS|SRS)-[A-Z]+-\d+\.\d+)/g;
669
+ function parseComplianceMatrix(content) {
670
+ const lines = content.split("\n");
671
+ const entries = [];
672
+ let inTable = false;
673
+ for (let i = 0; i < lines.length; i++) {
674
+ const line = lines[i].trim();
675
+ if (line.includes("| Section") && line.includes("Status") && line.includes("Evidence")) {
676
+ inTable = true;
677
+ i++;
678
+ continue;
679
+ }
680
+ if (inTable) {
681
+ if (!line.startsWith("|")) {
682
+ inTable = false;
683
+ continue;
684
+ }
685
+ const cells = line.split("|").map((c) => c.trim()).filter(Boolean);
686
+ if (cells.length < 4) continue;
687
+ const section = cells[0];
688
+ const statusRaw = cells[2].replace(/\*\*/g, "");
689
+ const status = STATUS_VALUES.find((s) => statusRaw === s);
690
+ if (!status) continue;
691
+ const evidence = cells[3] ?? "";
692
+ const linkedRequirements = [...evidence.matchAll(REQ_ID_REGEX)].map((m) => m[1]);
693
+ entries.push({
694
+ section,
695
+ status,
696
+ linkedRequirements
697
+ });
698
+ }
699
+ }
700
+ return entries;
701
+ }
702
+
703
+ // src/traceability/format-report.ts
704
+ function formatReport(report) {
705
+ const { matrix, gaps } = report;
706
+ const lines = [];
707
+ const testableReqs = matrix.requirements.filter(
708
+ (r) => r.verificationMethod === "E2E Test" || r.verificationMethod === "Unit Test"
709
+ );
710
+ const coveredCount = testableReqs.filter((r) => matrix.covered.has(r.id)).length;
711
+ const totalCount = testableReqs.length;
712
+ const pct = totalCount > 0 ? Math.round(coveredCount / totalCount * 100) : 0;
713
+ lines.push("# Requirements Traceability Report");
714
+ lines.push("");
715
+ lines.push(`**Coverage:** ${coveredCount}/${totalCount} testable requirements covered (${pct}%)`);
716
+ lines.push(`**Total requirements:** ${matrix.requirements.length}`);
717
+ lines.push(`**Test references:** ${matrix.testReferences.length}`);
718
+ lines.push(`**Gaps found:** ${gaps.length}`);
719
+ lines.push("");
720
+ const domains = [...new Set(matrix.requirements.map((r) => r.domain))].sort();
721
+ lines.push("## Coverage by Domain");
722
+ lines.push("");
723
+ lines.push("| Domain | Covered | Total | % |");
724
+ lines.push("| ------ | ------- | ----- | - |");
725
+ for (const domain of domains) {
726
+ const domainReqs = testableReqs.filter((r) => r.domain === domain);
727
+ const domainCovered = domainReqs.filter((r) => matrix.covered.has(r.id)).length;
728
+ const domainPct = domainReqs.length > 0 ? Math.round(domainCovered / domainReqs.length * 100) : 0;
729
+ lines.push(`| ${domain} | ${domainCovered} | ${domainReqs.length} | ${domainPct}% |`);
730
+ }
731
+ lines.push("");
732
+ if (gaps.length > 0) {
733
+ const categories = [...new Set(gaps.map((g) => g.category))];
734
+ for (const cat of categories) {
735
+ const catGaps = gaps.filter((g) => g.category === cat);
736
+ const heading = cat.replace(/_/g, " ");
737
+ lines.push(`## ${heading}`);
738
+ lines.push("");
739
+ for (const gap of catGaps) {
740
+ lines.push(`- ${gap.message}`);
741
+ }
742
+ lines.push("");
743
+ }
744
+ } else {
745
+ lines.push("## No gaps found");
746
+ lines.push("");
747
+ lines.push("All testable requirements have test coverage.");
748
+ }
749
+ return lines.join("\n");
750
+ }
751
+
752
+ // src/traceability/format-rtm.ts
753
+ import { readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
754
+ import { join as join3 } from "path";
755
+ function parseTraceLinks(rootDir) {
756
+ const links = /* @__PURE__ */ new Map();
757
+ const dirs = [
758
+ join3(rootDir, "docs/product/requirements"),
759
+ join3(rootDir, "docs/software/requirements")
760
+ ];
761
+ for (const dir of dirs) {
762
+ let files;
763
+ try {
764
+ files = readdirSync2(dir).filter((f) => f.endsWith(".md"));
765
+ } catch {
766
+ continue;
767
+ }
768
+ for (const file of files) {
769
+ const docId = file.replace(".md", "");
770
+ const content = readFileSync3(join3(dir, file), "utf-8");
771
+ const lines = content.split("\n");
772
+ const tracesFrom = [];
773
+ const tracesTo = [];
774
+ const traceTableIdx = lines.findIndex(
775
+ (line) => line.includes("Direction") && line.includes("Document") && line.includes("|")
776
+ );
777
+ if (traceTableIdx === -1) continue;
778
+ for (let i = traceTableIdx + 2; i < lines.length; i++) {
779
+ const line = lines[i].trim();
780
+ if (!line.startsWith("|")) break;
781
+ const cells = line.split("|").map((c) => c.trim()).filter(Boolean);
782
+ if (cells.length < 2) continue;
783
+ const direction = cells[0].toLowerCase();
784
+ const targetDoc = cells[1];
785
+ if (direction.includes("from")) {
786
+ tracesFrom.push(targetDoc);
787
+ } else if (direction.includes("to")) {
788
+ tracesTo.push(targetDoc);
789
+ }
790
+ }
791
+ links.set(docId, { tracesFrom, tracesTo });
792
+ }
793
+ }
794
+ return links;
795
+ }
796
+ function formatRTM(report, rootDir) {
797
+ const traceLinks = parseTraceLinks(rootDir);
798
+ const lines = [];
799
+ lines.push("# Requirements Traceability Matrix (RTM)");
800
+ lines.push("");
801
+ lines.push(`**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}`);
802
+ lines.push(`**Standard:** IEC 62304:2015, ISO 13485:2016 \xA77.3.3`);
803
+ lines.push("");
804
+ const entries = [];
805
+ for (const req of report.matrix.requirements) {
806
+ const docLinks = traceLinks.get(req.document);
807
+ const tests = report.matrix.covered.get(req.id) ?? [];
808
+ const needsTest = req.verificationMethod === "E2E Test" || req.verificationMethod === "Unit Test";
809
+ let status;
810
+ if (!needsTest) {
811
+ status = "N/A";
812
+ } else if (tests.length > 0) {
813
+ status = "Verified";
814
+ } else {
815
+ status = "Not Verified";
816
+ }
817
+ entries.push({
818
+ requirementId: req.id,
819
+ domain: req.domain,
820
+ level: req.level,
821
+ criteria: req.criteria,
822
+ verificationMethod: req.verificationMethod,
823
+ tracesFrom: docLinks?.tracesFrom ?? [],
824
+ tracesTo: docLinks?.tracesTo ?? [],
825
+ tests: tests.map((t) => ({ file: t.file, name: t.name, type: t.testType })),
826
+ status
827
+ });
828
+ }
829
+ const domains = [...new Set(entries.map((e) => e.domain))].sort();
830
+ for (const domain of domains) {
831
+ const domainEntries = entries.filter((e) => e.domain === domain);
832
+ const verified = domainEntries.filter((e) => e.status === "Verified").length;
833
+ const notVerified = domainEntries.filter((e) => e.status === "Not Verified").length;
834
+ const na = domainEntries.filter((e) => e.status === "N/A").length;
835
+ lines.push(`## ${domain} Domain`);
836
+ lines.push("");
837
+ lines.push(`**Verified:** ${verified} | **Not Verified:** ${notVerified} | **N/A:** ${na}`);
838
+ lines.push("");
839
+ lines.push("| Requirement | Traces From | Traces To | Verification | Tests | Status |");
840
+ lines.push("| ----------- | ----------- | --------- | ------------ | ----- | ------ |");
841
+ for (const entry of domainEntries) {
842
+ const from = entry.tracesFrom.length > 0 ? entry.tracesFrom.join(", ") : "\u2014";
843
+ const to = entry.tracesTo.length > 0 ? entry.tracesTo.join(", ") : "\u2014";
844
+ const testCount = entry.tests.length > 0 ? `${entry.tests.length} test(s)` : "\u2014";
845
+ lines.push(
846
+ `| ${entry.requirementId} | ${from} | ${to} | ${entry.verificationMethod} | ${testCount} | ${entry.status} |`
847
+ );
848
+ }
849
+ lines.push("");
850
+ }
851
+ const totalVerified = entries.filter((e) => e.status === "Verified").length;
852
+ const totalNotVerified = entries.filter((e) => e.status === "Not Verified").length;
853
+ const totalNA = entries.filter((e) => e.status === "N/A").length;
854
+ const total = entries.length;
855
+ lines.push("## Summary");
856
+ lines.push("");
857
+ lines.push("| Metric | Count |");
858
+ lines.push("| ------ | ----- |");
859
+ lines.push(`| Total Requirements | ${total} |`);
860
+ lines.push(`| Verified | ${totalVerified} |`);
861
+ lines.push(`| Not Verified | ${totalNotVerified} |`);
862
+ lines.push(`| N/A (non-test verification) | ${totalNA} |`);
863
+ lines.push("");
864
+ return lines.join("\n");
865
+ }
866
+
867
+ // src/traceability/index.ts
868
+ function globFiles(dir, pattern) {
869
+ const results = [];
870
+ try {
871
+ const entries = readdirSync3(dir, { withFileTypes: true, recursive: true });
872
+ for (const entry of entries) {
873
+ if (entry.isFile() && pattern.test(entry.name)) {
874
+ const fullPath = join4(entry.parentPath, entry.name);
875
+ results.push(fullPath);
876
+ }
877
+ }
878
+ } catch {
879
+ }
880
+ return results;
881
+ }
882
+ function runLegacyTraceability(options) {
883
+ const { rootDir, mode } = options;
884
+ const prsDir = join4(rootDir, "docs/product/requirements");
885
+ const prsFiles = globFiles(prsDir, /^PRS-.*\.md$/);
886
+ const requirements = [];
887
+ for (const file of prsFiles) {
888
+ const content = readFileSync4(file, "utf-8");
889
+ const docId = file.match(/(PRS-[A-Z]+-\d+)\.md$/)?.[1];
890
+ if (!docId) continue;
891
+ requirements.push(...parseRequirementsFromMarkdown(content, docId, "PRS"));
892
+ }
893
+ const srsDir = join4(rootDir, "docs/software/requirements");
894
+ const srsFiles = globFiles(srsDir, /^SRS-.*\.md$/);
895
+ for (const file of srsFiles) {
896
+ const content = readFileSync4(file, "utf-8");
897
+ const docId = file.match(/(SRS-[A-Z]+-\d+)\.md$/)?.[1];
898
+ if (!docId) continue;
899
+ requirements.push(...parseRequirementsFromMarkdown(content, docId, "SRS"));
900
+ }
901
+ const e2eDir = join4(rootDir, "e2e/features");
902
+ const featureFiles = globFiles(e2eDir, /\.feature$/);
903
+ const testRefs = [];
904
+ const allOrphans = [];
905
+ for (const file of featureFiles) {
906
+ const content = readFileSync4(file, "utf-8");
907
+ const relPath = relative2(rootDir, file);
908
+ testRefs.push(...extractReqTagsFromFeature(content, relPath));
909
+ allOrphans.push(...findOrphanScenarios(content, relPath));
910
+ }
911
+ const testDirs = [join4(rootDir, "packages"), join4(rootDir, "apps"), join4(rootDir, "scripts")];
912
+ for (const dir of testDirs) {
913
+ const testFiles = globFiles(dir, /\.(test|spec)\.tsx?$/);
914
+ for (const file of testFiles) {
915
+ const content = readFileSync4(file, "utf-8");
916
+ const relPath = relative2(rootDir, file);
917
+ testRefs.push(...extractReqMarkersFromTestFile(content, relPath));
918
+ }
919
+ }
920
+ const compliancePath = join4(rootDir, "compliance/part11-compliance-matrix.md");
921
+ let compliance;
922
+ try {
923
+ const complianceContent = readFileSync4(compliancePath, "utf-8");
924
+ compliance = parseComplianceMatrix(complianceContent);
925
+ } catch {
926
+ compliance = [];
927
+ }
928
+ const report = buildTraceabilityReport(requirements, testRefs, compliance, allOrphans);
929
+ if (mode === "rtm") {
930
+ const rtmOutput = formatRTM(report, rootDir);
931
+ return { output: rtmOutput, gapCount: report.gaps.length };
932
+ }
933
+ const output = formatReport(report);
934
+ const lines = [output];
935
+ if (mode === "check" && report.gaps.length > 0) {
936
+ lines.push(`
937
+ Traceability check FAILED: ${report.gaps.length} gap(s) found.`);
938
+ }
939
+ if (mode === "check" && report.gaps.length === 0) {
940
+ lines.push("\nTraceability check PASSED: all requirements have test coverage.");
941
+ }
942
+ return { output: lines.join("\n"), gapCount: report.gaps.length };
943
+ }
944
+
945
+ // src/impact/diff-gaps.ts
946
+ function gapKey(gap) {
947
+ return `${gap.code}::${gap.documentId}`;
948
+ }
949
+ function diffGaps(baseGaps, headGaps) {
950
+ const baseKeys = new Set(baseGaps.map(gapKey));
951
+ const headKeys = new Set(headGaps.map(gapKey));
952
+ const newGaps = headGaps.filter((g) => !baseKeys.has(gapKey(g)));
953
+ const resolvedGaps = baseGaps.filter((g) => !headKeys.has(gapKey(g)));
954
+ let unchangedCount = 0;
955
+ for (const key of headKeys) {
956
+ if (baseKeys.has(key)) {
957
+ unchangedCount++;
958
+ }
959
+ }
960
+ return { newGaps, resolvedGaps, unchangedCount };
961
+ }
962
+
963
+ // src/impact/baseline-scan.ts
964
+ import { execSync } from "child_process";
965
+ import { mkdtempSync, rmSync } from "fs";
966
+ import { join as join5 } from "path";
967
+ import { tmpdir } from "os";
968
+
969
+ // ../domain/dist/index.js
970
+ import { z as z2 } from "zod";
971
+ import { z } from "zod";
972
+ import { z as z3 } from "zod";
973
+ import { z as z4 } from "zod";
974
+ import { z as z5 } from "zod";
975
+ import { z as z7 } from "zod";
976
+ import { z as z6 } from "zod";
977
+ import { z as z8 } from "zod";
978
+ import { z as z9 } from "zod";
979
+ import { z as z10 } from "zod";
980
+ import { z as z11 } from "zod";
981
+ import { z as z12 } from "zod";
982
+ import { z as z13 } from "zod";
983
+ import { z as z14 } from "zod";
984
+ import { z as z15 } from "zod";
985
+ import { z as z16 } from "zod";
986
+ import { z as z18 } from "zod";
987
+ import { z as z17 } from "zod";
988
+ import { z as z20 } from "zod";
989
+ import { z as z19 } from "zod";
990
+ import { z as z21 } from "zod";
991
+ import { z as z22 } from "zod";
992
+ import { z as z23 } from "zod";
993
+ import { z as z24 } from "zod";
994
+ import { z as z25 } from "zod";
995
+ import { z as z26 } from "zod";
996
+ import { z as z27 } from "zod";
997
+ import { z as z28 } from "zod";
998
+ import { z as z29 } from "zod";
999
+ import { z as z30 } from "zod";
1000
+ import { z as z31 } from "zod";
1001
+ var REQUIRED_SECTIONS = {
1002
+ user_need: ["User Story", "Validation Criteria"],
1003
+ architecture: ["Purpose", "Architecture Overview", "Interfaces"],
1004
+ detailed_design: ["Purpose", "Detailed Design", "Interfaces"],
1005
+ audit_schedule: ["Scope", "Audit Criteria"],
1006
+ audit_report: ["Scope", "Methodology", "Findings", "Conclusion"],
1007
+ // Deprecated: management reviews are in-app entities, not git-native documents (#707)
1008
+ management_review: ["Review Inputs", "Review Outputs", "Action Items", "Decisions"],
1009
+ hazard_category: ["Description", "Examples", "Applicable Standards"],
1010
+ // Risk document body sections use BODY_FORMAT heading_pattern (## HARM-XXX via HS-XXX),
1011
+ // not a literal "Harm Assessment" heading. Validation is via BODY_FORMAT, not here.
1012
+ haz_soe_software: ["Intended Function", "Failure Cause", "Failure Mode", "Failure Effect"],
1013
+ haz_soe_security: [
1014
+ "STRIDE Category & Threat",
1015
+ "Asset",
1016
+ "Vulnerability",
1017
+ "Actor & Attack Vector",
1018
+ "Adverse Impact"
1019
+ ],
1020
+ // Personnel (ISO 13485 §5.5, §6.2)
1021
+ job_description: [
1022
+ "Purpose",
1023
+ "Responsibilities",
1024
+ "Qualifications",
1025
+ "Authorities",
1026
+ "Reporting Structure",
1027
+ "Training Requirements"
1028
+ ]
1029
+ };
1030
+ var RiskDocumentStatusSchema = z.enum([
1031
+ "draft",
1032
+ "in_review",
1033
+ "approved",
1034
+ "effective",
1035
+ "archived",
1036
+ "example"
1037
+ ]);
1038
+ var IsoCategorySchema = z.enum([
1039
+ "safe_design",
1040
+ "protective_measure",
1041
+ "safety_information"
1042
+ ]);
1043
+ var ReducesTargetSchema = z.enum(["p1_sequence", "p2_harm", "severity"]);
1044
+ var RiskGapCodeSchema = z.enum([
1045
+ "hazard_no_situation",
1046
+ "situation_no_harm",
1047
+ "hazard_not_analyzed",
1048
+ "missing_mitigation",
1049
+ "broken_mitigation_link",
1050
+ "control_not_approved",
1051
+ "missing_risk_benefit",
1052
+ "risk_missing_safety_chain",
1053
+ "risk_broken_safety_chain",
1054
+ "wrong_probability_dimension",
1055
+ "missing_frontmatter",
1056
+ "requirement_no_architecture",
1057
+ "architecture_no_segregation",
1058
+ "architecture_no_parent",
1059
+ "haz_missing_category",
1060
+ "haz_invalid_category",
1061
+ "category_not_approved",
1062
+ "missing_iso_category",
1063
+ "missing_risk_acceptable",
1064
+ "unacceptable_no_benefit",
1065
+ "preliminary_not_analyzed",
1066
+ "missing_body_rationale",
1067
+ "orphaned_body_section",
1068
+ "architecture_no_risk_analysis",
1069
+ "security_hazard_no_asset_ref",
1070
+ "high_cia_no_security_hazard",
1071
+ "hazard_no_software_item",
1072
+ "detailed_design_missing_for_unit",
1073
+ "unit_no_verification",
1074
+ "risk_control_no_verification",
1075
+ "architecture_no_asset_types"
1076
+ ]);
1077
+ var RiskGapSeveritySchema = z.enum(["error", "warning"]);
1078
+ var MitigationSchema = z.object({
1079
+ control: z.string().min(1),
1080
+ iso_category: IsoCategorySchema,
1081
+ reduces: ReducesTargetSchema
1082
+ });
1083
+ var HarmAssessmentSchema = z.object({
1084
+ harm: z.string().min(1),
1085
+ inherent_probability: z.number().int().min(1).max(5).optional(),
1086
+ inherent_exploitability: z.number().int().min(1).max(5).optional(),
1087
+ residual_probability: z.number().int().min(1).max(5).optional(),
1088
+ residual_exploitability: z.number().int().min(1).max(5).optional(),
1089
+ harm_severity_override: z.number().int().min(1).max(5).optional(),
1090
+ risk_acceptable: z.boolean(),
1091
+ benefit_outweighs_risk: z.boolean().optional()
1092
+ });
1093
+ var HazardousSituationAssessmentSchema = z.object({
1094
+ hazardous_situation: z.string().min(1),
1095
+ mitigations: z.array(MitigationSchema).optional(),
1096
+ harms: z.array(HarmAssessmentSchema).min(1)
1097
+ });
1098
+ var RiskEntryFrontmatterSchema = z.object({
1099
+ type: z.enum(["software_risk", "usability_risk", "security_risk"]),
1100
+ id: z.string().min(1),
1101
+ title: z.string().min(1),
1102
+ status: RiskDocumentStatusSchema,
1103
+ author: z.string().min(1),
1104
+ reviewers: z.array(z.string()).optional(),
1105
+ approvers: z.array(z.string()).optional(),
1106
+ analyzes: z.string().min(1),
1107
+ mitigations: z.array(MitigationSchema).optional(),
1108
+ hazardous_situation_assessments: z.array(HazardousSituationAssessmentSchema).min(1),
1109
+ cvss_score: z.number().min(0).max(10).optional(),
1110
+ cvss_vector: z.string().regex(
1111
+ /^CVSS:3\.[01]\/AV:[NALP]\/AC:[LH]\/PR:[NLH]\/UI:[NR]\/S:[UC]\/C:[NLH]\/I:[NLH]\/A:[NLH]$/
1112
+ ).optional()
1113
+ }).refine(
1114
+ (data) => {
1115
+ const allHarms = data.hazardous_situation_assessments.flatMap((hsa) => hsa.harms);
1116
+ if (data.type === "security_risk") {
1117
+ return allHarms.every((ha) => ha.inherent_exploitability != null);
1118
+ }
1119
+ return allHarms.every((ha) => ha.inherent_probability != null);
1120
+ },
1121
+ {
1122
+ message: "Security risks must use inherent_exploitability; software/usability risks must use inherent_probability"
1123
+ }
1124
+ ).refine(
1125
+ (data) => {
1126
+ const allHarms = data.hazardous_situation_assessments.flatMap((hsa) => hsa.harms);
1127
+ return allHarms.every((ha) => ha.risk_acceptable || ha.benefit_outweighs_risk != null);
1128
+ },
1129
+ {
1130
+ message: "benefit_outweighs_risk required when risk_acceptable is false"
1131
+ }
1132
+ );
1133
+ var HazardSoftwareFrontmatterSchema = z.object({
1134
+ type: z.literal("haz_soe_software"),
1135
+ id: z.string().min(1),
1136
+ title: z.string().min(1),
1137
+ status: RiskDocumentStatusSchema,
1138
+ author: z.string().min(1),
1139
+ reviewers: z.array(z.string()).optional(),
1140
+ approvers: z.array(z.string()).optional(),
1141
+ preliminary: z.boolean().default(false),
1142
+ leads_to: z.array(z.string()).optional(),
1143
+ hazard_category: z.string().optional(),
1144
+ detection_score: z.number().int().min(1).max(5).optional(),
1145
+ detection_method: z.string().optional(),
1146
+ /** Reference to the HLD/SDD software item this hazard applies to (IEC 62304 §7.1) */
1147
+ software_item: z.string().min(1).optional()
1148
+ });
1149
+ var HazardSecurityFrontmatterSchema = z.object({
1150
+ type: z.literal("haz_soe_security"),
1151
+ id: z.string().min(1),
1152
+ title: z.string().min(1),
1153
+ status: RiskDocumentStatusSchema,
1154
+ author: z.string().min(1),
1155
+ reviewers: z.array(z.string()).optional(),
1156
+ approvers: z.array(z.string()).optional(),
1157
+ preliminary: z.boolean().default(false),
1158
+ leads_to: z.array(z.string()).optional(),
1159
+ hazard_category: z.string().optional(),
1160
+ /** Reference to the HLD/SDD software item this security hazard applies to (IEC 62304 §7.1, IEC 81001-5-1) */
1161
+ software_item: z.string().min(1).optional()
1162
+ });
1163
+ var HazardFrontmatterSchema = z.discriminatedUnion("type", [
1164
+ HazardSoftwareFrontmatterSchema,
1165
+ HazardSecurityFrontmatterSchema
1166
+ ]);
1167
+ var HazardCategoryFrontmatterSchema = z.object({
1168
+ type: z.literal("hazard_category"),
1169
+ id: z.string().min(1),
1170
+ title: z.string().min(1),
1171
+ status: RiskDocumentStatusSchema,
1172
+ source: z.string().optional()
1173
+ });
1174
+ var HazardousSituationFrontmatterSchema = z.object({
1175
+ type: z.literal("hazardous_situation"),
1176
+ id: z.string().min(1),
1177
+ title: z.string().min(1),
1178
+ status: RiskDocumentStatusSchema,
1179
+ results_in: z.array(z.string()).optional()
1180
+ });
1181
+ var HarmFrontmatterSchema = z.object({
1182
+ type: z.literal("harm"),
1183
+ id: z.string().min(1),
1184
+ title: z.string().min(1),
1185
+ status: RiskDocumentStatusSchema,
1186
+ severity: z.number().int().min(1).max(5),
1187
+ category: z.string().optional()
1188
+ });
1189
+ var RiskMatrixConfigSchema = z.object({
1190
+ version: z.number(),
1191
+ labels: z.object({
1192
+ severity: z.array(z.string()).length(5),
1193
+ probability: z.array(z.string()).length(5),
1194
+ exploitability: z.array(z.string()).length(5).optional()
1195
+ }),
1196
+ acceptability: z.object({
1197
+ unacceptable: z.array(z.tuple([z.number(), z.number()])),
1198
+ review_required: z.array(z.tuple([z.number(), z.number()])).optional()
1199
+ }),
1200
+ overrides: z.record(
1201
+ z.object({
1202
+ unacceptable: z.array(z.tuple([z.number(), z.number()])),
1203
+ review_required: z.array(z.tuple([z.number(), z.number()])).optional()
1204
+ })
1205
+ ).optional()
1206
+ });
1207
+ var AnomalyCategorySchema = z2.enum([
1208
+ "bug",
1209
+ "security_vulnerability",
1210
+ "regression",
1211
+ "performance"
1212
+ ]);
1213
+ var AnomalySeveritySchema = z2.enum(["critical", "major", "minor"]);
1214
+ var AnomalyDispositionSchema = z2.enum([
1215
+ "open",
1216
+ "investigating",
1217
+ "resolved",
1218
+ "deferred",
1219
+ "will_not_fix"
1220
+ ]);
1221
+ var AnomalyFrontmatterSchema = z2.object({
1222
+ type: z2.literal("anomaly"),
1223
+ id: z2.string().min(1),
1224
+ title: z2.string().min(1),
1225
+ status: RiskDocumentStatusSchema,
1226
+ category: AnomalyCategorySchema,
1227
+ severity: AnomalySeveritySchema,
1228
+ disposition: AnomalyDispositionSchema,
1229
+ affected_version: z2.string().optional(),
1230
+ author: z2.string().optional(),
1231
+ reviewers: z2.array(z2.string()).optional(),
1232
+ approvers: z2.array(z2.string()).optional()
1233
+ });
1234
+ var AuditFindingClassificationSchema = z3.enum(["observation", "minor_nc", "major_nc"]);
1235
+ var AuditStatusSchema = z3.enum(["planned", "in_progress", "completed", "cancelled"]);
1236
+ var PlannedAuditEntrySchema = z3.object({
1237
+ audit_id: z3.string().min(1),
1238
+ process_area: z3.string().min(1),
1239
+ clause: z3.string().optional(),
1240
+ planned_date: z3.string().min(1),
1241
+ auditor: z3.string().min(1),
1242
+ status: AuditStatusSchema
1243
+ });
1244
+ var AuditScheduleFrontmatterSchema = z3.object({
1245
+ id: z3.string().min(1),
1246
+ title: z3.string().min(1),
1247
+ type: z3.literal("audit_schedule").optional(),
1248
+ status: z3.string().optional(),
1249
+ author: z3.string().optional(),
1250
+ reviewers: z3.array(z3.string()).optional(),
1251
+ approvers: z3.array(z3.string()).optional(),
1252
+ cycle_year: z3.number().int().min(2e3).max(2100),
1253
+ audits: z3.array(PlannedAuditEntrySchema).min(1)
1254
+ });
1255
+ var AuditFindingSchema = z3.object({
1256
+ finding_id: z3.string().min(1),
1257
+ classification: AuditFindingClassificationSchema,
1258
+ description: z3.string().min(1),
1259
+ capa_id: z3.string().optional()
1260
+ });
1261
+ var AuditReportFrontmatterSchema = z3.object({
1262
+ id: z3.string().min(1),
1263
+ title: z3.string().min(1),
1264
+ type: z3.literal("audit_report").optional(),
1265
+ status: z3.string().optional(),
1266
+ author: z3.string().optional(),
1267
+ reviewers: z3.array(z3.string()).optional(),
1268
+ approvers: z3.array(z3.string()).optional(),
1269
+ audit_date: z3.string().min(1),
1270
+ audit_id: z3.string().optional(),
1271
+ process_area: z3.string().min(1),
1272
+ clause: z3.string().optional(),
1273
+ auditor: z3.string().min(1),
1274
+ findings: z3.array(AuditFindingSchema),
1275
+ findings_major: z3.number().int().min(0).optional(),
1276
+ findings_minor: z3.number().int().min(0).optional(),
1277
+ findings_observations: z3.number().int().min(0).optional()
1278
+ });
1279
+ var ClinicalEvaluationPlanFrontmatterSchema = z4.object({
1280
+ type: z4.literal("clinical_evaluation_plan"),
1281
+ id: z4.string().min(1),
1282
+ title: z4.string().min(1),
1283
+ status: RiskDocumentStatusSchema,
1284
+ author: z4.string().optional(),
1285
+ reviewers: z4.array(z4.string()).optional(),
1286
+ approvers: z4.array(z4.string()).optional()
1287
+ });
1288
+ var ClinicalEvaluationReportFrontmatterSchema = z4.object({
1289
+ type: z4.literal("clinical_evaluation_report"),
1290
+ id: z4.string().min(1),
1291
+ title: z4.string().min(1),
1292
+ status: RiskDocumentStatusSchema,
1293
+ author: z4.string().optional(),
1294
+ reviewers: z4.array(z4.string()).optional(),
1295
+ approvers: z4.array(z4.string()).optional()
1296
+ });
1297
+ var CybersecurityPlanFrontmatterSchema = z5.object({
1298
+ type: z5.literal("cybersecurity_plan"),
1299
+ id: z5.string().min(1),
1300
+ title: z5.string().min(1),
1301
+ status: RiskDocumentStatusSchema,
1302
+ author: z5.string().optional(),
1303
+ reviewers: z5.array(z5.string()).optional(),
1304
+ approvers: z5.array(z5.string()).optional()
1305
+ });
1306
+ var SbomFrontmatterSchema = z5.object({
1307
+ type: z5.literal("sbom"),
1308
+ id: z5.string().min(1),
1309
+ title: z5.string().min(1),
1310
+ status: RiskDocumentStatusSchema,
1311
+ author: z5.string().optional(),
1312
+ reviewers: z5.array(z5.string()).optional(),
1313
+ approvers: z5.array(z5.string()).optional()
1314
+ });
1315
+ var RegulatoryFrameworkSchema = z6.enum([
1316
+ "ISO_13485",
1317
+ "IEC_62304",
1318
+ "FDA_21_CFR_820",
1319
+ "QMSR",
1320
+ "EU_MDR",
1321
+ "ISO_14971",
1322
+ "AI_ACT"
1323
+ ]);
1324
+ var SafetyClassSchema = z6.enum(["A", "B", "C"]);
1325
+ var DocumentTypeSchema = z6.enum([
1326
+ "user_need",
1327
+ "requirement",
1328
+ "architecture",
1329
+ "detailed_design",
1330
+ "test_protocol",
1331
+ "test_report",
1332
+ "sop",
1333
+ "work_instruction",
1334
+ "policy",
1335
+ "usability_risk",
1336
+ "software_risk",
1337
+ "security_risk",
1338
+ // Risk document types
1339
+ "haz_soe_software",
1340
+ "haz_soe_security",
1341
+ "hazardous_situation",
1342
+ "harm",
1343
+ "hazard_category",
1344
+ // Usability engineering (IEC 62366)
1345
+ "usability_plan",
1346
+ "use_specification",
1347
+ "task_analysis",
1348
+ "usability_evaluation",
1349
+ "summative_evaluation",
1350
+ // Risk management (ISO 14971)
1351
+ "risk_management_plan",
1352
+ // Software lifecycle (IEC 62304)
1353
+ "software_development_plan",
1354
+ "software_maintenance_plan",
1355
+ "soup_register",
1356
+ // Problem resolution (IEC 62304 §9)
1357
+ "anomaly",
1358
+ // Cybersecurity (IEC 81001-5-1)
1359
+ "cybersecurity_plan",
1360
+ "sbom",
1361
+ // Clinical (MDR / FDA)
1362
+ "clinical_evaluation_plan",
1363
+ "clinical_evaluation_report",
1364
+ // Post-market surveillance (MDR Art. 83)
1365
+ "post_market_surveillance_plan",
1366
+ "post_market_feedback",
1367
+ // Labeling (MDR Annex I)
1368
+ "labeling",
1369
+ // Product (ISO 13485 §7.3)
1370
+ "product_development_plan",
1371
+ "intended_use",
1372
+ // Supplier management (ISO 13485 §7.4)
1373
+ "supplier",
1374
+ // Internal audit management (ISO 13485 §8.2.2)
1375
+ "audit_schedule",
1376
+ "audit_report",
1377
+ // Management review (ISO 13485 §5.6)
1378
+ "management_review",
1379
+ // Software test plan (IEC 62304 §5.7)
1380
+ "software_test_plan",
1381
+ // Personnel (ISO 13485 §5.5, §6.2)
1382
+ "job_description"
1383
+ ]);
1384
+ var DocumentStatusSchema = z6.enum([
1385
+ "draft",
1386
+ "in_review",
1387
+ "approved",
1388
+ "obsolete",
1389
+ "example"
1390
+ ]);
1391
+ var LinkTypeSchema = z6.enum([
1392
+ "derives_from",
1393
+ "implements",
1394
+ "verified_by",
1395
+ "mitigates",
1396
+ "parent_of",
1397
+ "related_to",
1398
+ // New risk link types
1399
+ "leads_to",
1400
+ "results_in",
1401
+ "analyzes"
1402
+ ]);
1403
+ var AcceptabilityStatusSchema = z6.enum(["acceptable", "review_required", "unacceptable"]);
1404
+ var DepartmentRoleSchema = z6.enum(["manager", "member"]);
1405
+ var JobDescriptionFrontmatterSchema = z7.object({
1406
+ type: z7.literal("job_description").optional(),
1407
+ id: z7.string().min(1),
1408
+ title: z7.string().min(1),
1409
+ version: z7.string().optional(),
1410
+ status: DocumentStatusSchema.optional(),
1411
+ department: z7.string().min(1),
1412
+ department_role: DepartmentRoleSchema.optional(),
1413
+ author: z7.string().optional(),
1414
+ reviewers: z7.array(z7.string()).optional(),
1415
+ approvers: z7.array(z7.string()).optional(),
1416
+ effective_date: z7.string().optional()
1417
+ });
1418
+ var LabelingFrontmatterSchema = z8.object({
1419
+ type: z8.literal("labeling"),
1420
+ id: z8.string().min(1),
1421
+ title: z8.string().min(1),
1422
+ status: RiskDocumentStatusSchema,
1423
+ label_type: z8.enum(["ifu", "product_label", "packaging_label"]).optional(),
1424
+ author: z8.string().optional(),
1425
+ reviewers: z8.array(z8.string()).optional(),
1426
+ approvers: z8.array(z8.string()).optional()
1427
+ });
1428
+ var ManagementReviewAttendeeSchema = z9.object({
1429
+ name: z9.string().min(1),
1430
+ role: z9.string().min(1)
1431
+ });
1432
+ var ManagementReviewFrontmatterSchema = z9.object({
1433
+ id: z9.string().min(1),
1434
+ title: z9.string().min(1),
1435
+ type: z9.literal("management_review").optional(),
1436
+ version: z9.string().optional(),
1437
+ status: z9.enum(["draft", "scheduled", "completed"]).optional(),
1438
+ review_date: z9.string().min(1),
1439
+ review_period: z9.object({
1440
+ from: z9.string().min(1),
1441
+ to: z9.string().min(1)
1442
+ }),
1443
+ attendees: z9.array(ManagementReviewAttendeeSchema).min(1),
1444
+ data_snapshot_date: z9.string().optional(),
1445
+ next_review_date: z9.string().optional()
1446
+ });
1447
+ var PostMarketSurveillancePlanFrontmatterSchema = z10.object({
1448
+ type: z10.literal("post_market_surveillance_plan"),
1449
+ id: z10.string().min(1),
1450
+ title: z10.string().min(1),
1451
+ status: RiskDocumentStatusSchema,
1452
+ author: z10.string().optional(),
1453
+ reviewers: z10.array(z10.string()).optional(),
1454
+ approvers: z10.array(z10.string()).optional()
1455
+ });
1456
+ var PostMarketFeedbackCategorySchema = z10.enum([
1457
+ "complaint",
1458
+ "field_observation",
1459
+ "clinical_followup",
1460
+ "trend_report"
1461
+ ]);
1462
+ var PostMarketFeedbackSeveritySchema = z10.enum(["low", "medium", "high", "critical"]);
1463
+ var PostMarketFeedbackFrontmatterSchema = z10.object({
1464
+ type: z10.literal("post_market_feedback"),
1465
+ id: z10.string().min(1),
1466
+ title: z10.string().min(1),
1467
+ status: RiskDocumentStatusSchema,
1468
+ category: PostMarketFeedbackCategorySchema,
1469
+ severity: PostMarketFeedbackSeveritySchema,
1470
+ device: z10.string().optional(),
1471
+ reporting_period: z10.string().optional(),
1472
+ author: z10.string().optional(),
1473
+ reviewers: z10.array(z10.string()).optional(),
1474
+ approvers: z10.array(z10.string()).optional()
1475
+ });
1476
+ var ProductDevelopmentPlanFrontmatterSchema = z11.object({
1477
+ type: z11.literal("product_development_plan"),
1478
+ id: z11.string().min(1),
1479
+ title: z11.string().min(1),
1480
+ status: RiskDocumentStatusSchema,
1481
+ author: z11.string().optional(),
1482
+ reviewers: z11.array(z11.string()).optional(),
1483
+ approvers: z11.array(z11.string()).optional()
1484
+ });
1485
+ var IntendedUseFrontmatterSchema = z11.object({
1486
+ type: z11.literal("intended_use"),
1487
+ id: z11.string().min(1),
1488
+ title: z11.string().min(1),
1489
+ status: RiskDocumentStatusSchema,
1490
+ author: z11.string().optional(),
1491
+ reviewers: z11.array(z11.string()).optional(),
1492
+ approvers: z11.array(z11.string()).optional()
1493
+ });
1494
+ var CompiledDocumentPresetSchema = z12.enum([
1495
+ "user_needs",
1496
+ "srs",
1497
+ "prs",
1498
+ "dmr",
1499
+ "risk",
1500
+ "hld",
1501
+ "sdd",
1502
+ "vv",
1503
+ "uef",
1504
+ "csf",
1505
+ "cer",
1506
+ "smp",
1507
+ "soup",
1508
+ "pms",
1509
+ "anomaly"
1510
+ ]);
1511
+ var COMPILED_PRESET_DOC_TYPES = {
1512
+ user_needs: ["user_need"],
1513
+ srs: ["requirement"],
1514
+ prs: ["requirement"],
1515
+ dmr: [
1516
+ "intended_use",
1517
+ "product_development_plan",
1518
+ "software_development_plan",
1519
+ "architecture",
1520
+ "detailed_design",
1521
+ "requirement",
1522
+ "labeling",
1523
+ "user_need"
1524
+ ],
1525
+ risk: [
1526
+ "risk_management_plan",
1527
+ "software_risk",
1528
+ "security_risk",
1529
+ "usability_risk",
1530
+ "haz_soe_software",
1531
+ "haz_soe_security",
1532
+ "hazardous_situation",
1533
+ "harm",
1534
+ "hazard_category"
1535
+ ],
1536
+ hld: ["architecture"],
1537
+ sdd: ["detailed_design"],
1538
+ vv: ["test_protocol", "test_report", "software_test_plan"],
1539
+ uef: [
1540
+ "usability_plan",
1541
+ "use_specification",
1542
+ "task_analysis",
1543
+ "usability_evaluation",
1544
+ "summative_evaluation"
1545
+ ],
1546
+ csf: ["cybersecurity_plan", "sbom", "security_risk"],
1547
+ cer: ["clinical_evaluation_plan", "clinical_evaluation_report"],
1548
+ smp: ["software_maintenance_plan"],
1549
+ soup: ["soup_register"],
1550
+ pms: ["post_market_surveillance_plan", "post_market_feedback"],
1551
+ anomaly: ["anomaly"]
1552
+ };
1553
+ var COMPILED_PRESET_CATEGORIES = {
1554
+ srs: ["software"],
1555
+ prs: ["product"]
1556
+ };
1557
+ var PRESET_TITLES = {
1558
+ user_needs: "User Needs Specification",
1559
+ srs: "Software Requirements Specification",
1560
+ prs: "Product Requirements Specification",
1561
+ dmr: "Device Master Record",
1562
+ risk: "Risk Management File",
1563
+ hld: "Software Architecture Description",
1564
+ sdd: "Software Detailed Design",
1565
+ vv: "Verification & Validation",
1566
+ uef: "Usability Engineering File",
1567
+ csf: "Cybersecurity File",
1568
+ cer: "Clinical Evaluation Report",
1569
+ smp: "Software Maintenance Plan",
1570
+ soup: "SOUP Register",
1571
+ pms: "Post-Market Surveillance File",
1572
+ anomaly: "Anomaly Register"
1573
+ };
1574
+ var COMPILATION_PRESETS = Object.fromEntries(
1575
+ Object.entries(COMPILED_PRESET_DOC_TYPES).map(([key, docTypes]) => [
1576
+ key,
1577
+ {
1578
+ title: PRESET_TITLES[key] ?? key,
1579
+ docTypes: [...docTypes],
1580
+ ...COMPILED_PRESET_CATEGORIES[key] ? {
1581
+ categories: [
1582
+ ...COMPILED_PRESET_CATEGORIES[key]
1583
+ ]
1584
+ } : {}
1585
+ }
1586
+ ])
1587
+ );
1588
+ var FrameworkScopeSchema = z13.enum(["qms", "device", "both"]);
1589
+ var MappingRelationshipSchema = z13.enum(["equivalent", "partial", "related", "supersedes"]);
1590
+ var FrameworkDefinitionSeedSchema = z13.object({
1591
+ id: RegulatoryFrameworkSchema,
1592
+ name: z13.string().min(1),
1593
+ version: z13.string().min(1),
1594
+ scope: FrameworkScopeSchema,
1595
+ url: z13.string().url().nullable(),
1596
+ active: z13.boolean().default(true)
1597
+ });
1598
+ var ClauseBase = z13.object({
1599
+ clauseNumber: z13.string().min(1),
1600
+ title: z13.string().min(1),
1601
+ /** Clause-level scope override. When omitted, inherits from parent framework. */
1602
+ scope: FrameworkScopeSchema.optional()
1603
+ });
1604
+ function buildClauseSchema(remainingDepth) {
1605
+ if (remainingDepth <= 0) {
1606
+ return ClauseBase.strict();
1607
+ }
1608
+ return ClauseBase.extend({
1609
+ children: z13.array(z13.lazy(() => buildClauseSchema(remainingDepth - 1))).optional()
1610
+ });
1611
+ }
1612
+ var FrameworkClauseSeedSchema = buildClauseSchema(5);
1613
+ var FrameworkMappingSeedSchema = z13.object({
1614
+ sourceClause: z13.string().min(1),
1615
+ targetFramework: RegulatoryFrameworkSchema,
1616
+ targetClause: z13.string().min(1),
1617
+ relationship: MappingRelationshipSchema,
1618
+ notes: z13.string().nullable().default(null)
1619
+ });
1620
+ var MappingFileSeedSchema = z13.object({
1621
+ sourceFramework: RegulatoryFrameworkSchema,
1622
+ mappings: z13.array(FrameworkMappingSeedSchema)
1623
+ });
1624
+ var EvidenceSourceSchema = z13.enum(["documents", "training_records"]);
1625
+ var EvidenceRuleConditionsSeedSchema = z13.object({
1626
+ evidenceSource: EvidenceSourceSchema.optional(),
1627
+ documentType: DocumentTypeSchema.optional(),
1628
+ linkType: LinkTypeSchema.optional(),
1629
+ status: z13.string().optional(),
1630
+ minCount: z13.number().int().positive().optional(),
1631
+ safetyClass: SafetyClassSchema.optional(),
1632
+ metadata: z13.record(z13.string(), z13.string()).optional()
1633
+ });
1634
+ var EvidenceRuleSeedSchema = z13.object({
1635
+ clauseId: z13.string().min(1),
1636
+ description: z13.string().min(1),
1637
+ conditions: EvidenceRuleConditionsSeedSchema,
1638
+ weight: z13.number().min(0).max(1)
1639
+ });
1640
+ var EvidenceRulesFileSeedSchema = z13.object({
1641
+ frameworkId: RegulatoryFrameworkSchema,
1642
+ rules: z13.array(EvidenceRuleSeedSchema)
1643
+ });
1644
+ var FrameworkFileSeedSchema = FrameworkDefinitionSeedSchema.extend({
1645
+ clauses: z13.array(FrameworkClauseSeedSchema)
1646
+ });
1647
+ var SAFETY_CLASS_RANK = { A: 1, B: 2, C: 3 };
1648
+ var ARCHITECTURE_DOC_TYPES = ["architecture", "detailed_design"];
1649
+ function isArchitectureDoc(docType) {
1650
+ return ARCHITECTURE_DOC_TYPES.includes(docType);
1651
+ }
1652
+ function detectRequirementNoArchitecture(documents, links) {
1653
+ const archDocTypes = new Set(ARCHITECTURE_DOC_TYPES);
1654
+ const docTypeById = new Map(documents.map((d) => [d.documentId, d.docType]));
1655
+ const softwareRequirements = documents.filter(
1656
+ (d) => d.docType === "requirement" && d.metadata.category === "software"
1657
+ );
1658
+ return softwareRequirements.filter((req) => {
1659
+ const hasOutgoingArchLink = links.some(
1660
+ (l) => l.sourceDocumentId === req.documentId && l.linkType === "implements" && archDocTypes.has(docTypeById.get(l.targetDocumentId) ?? "")
1661
+ );
1662
+ const hasIncomingArchLink = links.some(
1663
+ (l) => l.targetDocumentId === req.documentId && l.linkType === "implements" && archDocTypes.has(docTypeById.get(l.sourceDocumentId) ?? "")
1664
+ );
1665
+ return !hasOutgoingArchLink && !hasIncomingArchLink;
1666
+ }).map((req) => ({
1667
+ code: "requirement_no_architecture",
1668
+ severity: "warning",
1669
+ documentId: req.documentId,
1670
+ documentTitle: req.title,
1671
+ message: `Software requirement ${req.documentId} has no implements link to an architecture or detailed design document (IEC 62304 \xA75.3.1).`
1672
+ }));
1673
+ }
1674
+ function detectArchitectureNoSegregation(documents, deviceSafetyClass) {
1675
+ if (!deviceSafetyClass) return [];
1676
+ const deviceRank = SAFETY_CLASS_RANK[deviceSafetyClass];
1677
+ if (deviceRank == null) return [];
1678
+ return documents.filter((d) => {
1679
+ if (!isArchitectureDoc(d.docType)) return false;
1680
+ const docClass = d.metadata.safety_class;
1681
+ if (!docClass) return false;
1682
+ const docRank = SAFETY_CLASS_RANK[docClass];
1683
+ if (docRank == null) return false;
1684
+ return docRank < deviceRank && !d.metadata.segregation;
1685
+ }).map((d) => ({
1686
+ code: "architecture_no_segregation",
1687
+ severity: "warning",
1688
+ documentId: d.documentId,
1689
+ documentTitle: d.title,
1690
+ message: `${d.documentId} has safety_class '${d.metadata.safety_class}' (device default: '${deviceSafetyClass}') but no segregation strategy documented (IEC 62304 \xA75.3.5).`
1691
+ }));
1692
+ }
1693
+ var APPROVED_STATUSES = /* @__PURE__ */ new Set(["approved", "effective"]);
1694
+ var HAZARD_DOC_TYPES = /* @__PURE__ */ new Set(["haz_soe_software", "haz_soe_security"]);
1695
+ function detectArchitectureNoRiskAnalysis(documents, links) {
1696
+ const analyzedByHazard = /* @__PURE__ */ new Set();
1697
+ for (const doc of documents) {
1698
+ if (!HAZARD_DOC_TYPES.has(doc.docType)) continue;
1699
+ const softwareItem = doc.metadata.software_item;
1700
+ if (softwareItem) {
1701
+ analyzedByHazard.add(softwareItem);
1702
+ }
1703
+ }
1704
+ for (const link of links) {
1705
+ if (link.linkType === "analyzes") {
1706
+ analyzedByHazard.add(link.targetDocumentId);
1707
+ }
1708
+ }
1709
+ return documents.filter((d) => {
1710
+ if (!isArchitectureDoc(d.docType)) return false;
1711
+ if (!APPROVED_STATUSES.has(d.metadata.status)) return false;
1712
+ return !analyzedByHazard.has(d.documentId);
1713
+ }).map((d) => ({
1714
+ code: "architecture_no_risk_analysis",
1715
+ severity: "warning",
1716
+ documentId: d.documentId,
1717
+ documentTitle: d.title,
1718
+ message: `${d.documentId} is an approved ${d.docType} with no hazard analysis. Every software item must be evaluated for contribution to hazardous situations (IEC 62304 \xA77.1).`
1719
+ }));
1720
+ }
1721
+ function detectArchitectureNoParent(documents) {
1722
+ const needsParent = /* @__PURE__ */ new Set(["subsystem", "component", "unit"]);
1723
+ return documents.filter((d) => {
1724
+ if (!isArchitectureDoc(d.docType)) return false;
1725
+ const itemType = d.metadata.software_item_type;
1726
+ if (!itemType || !needsParent.has(itemType)) return false;
1727
+ return !d.metadata.parent_item;
1728
+ }).map((d) => ({
1729
+ code: "architecture_no_parent",
1730
+ severity: "warning",
1731
+ documentId: d.documentId,
1732
+ documentTitle: d.title,
1733
+ message: `${d.documentId} is a ${d.metadata.software_item_type} but has no parent_item. Non-system software items must reference their parent (IEC 62304 \xA75.3.2).`
1734
+ }));
1735
+ }
1736
+ function isApprovedOrEffective(doc) {
1737
+ return doc.status === "approved" || doc.status === "effective";
1738
+ }
1739
+ function buildGap(doc, code, severity, message) {
1740
+ return {
1741
+ code,
1742
+ severity,
1743
+ documentId: doc.id,
1744
+ documentTitle: doc.title,
1745
+ message,
1746
+ ...doc.repositoryId && { repositoryId: doc.repositoryId },
1747
+ ...doc.repositoryName && { repositoryName: doc.repositoryName }
1748
+ };
1749
+ }
1750
+ var HAZARD_TYPES = /* @__PURE__ */ new Set(["haz_soe_software", "haz_soe_security"]);
1751
+ var ARCHITECTURE_TYPES = /* @__PURE__ */ new Set(["architecture", "detailed_design"]);
1752
+ var RISK_ENTRY_TYPES = /* @__PURE__ */ new Set(["software_risk", "usability_risk", "security_risk"]);
1753
+ var NEEDS_DETAILED_DESIGN = /* @__PURE__ */ new Set(["unit", "component"]);
1754
+ var HIGH_CIA = "high";
1755
+ function buildVerifiedDocIds(links) {
1756
+ const ids = /* @__PURE__ */ new Set();
1757
+ for (const link of links) {
1758
+ if (link.type === "verified_by") {
1759
+ ids.add(link.sourceId);
1760
+ ids.add(link.targetId);
1761
+ }
1762
+ }
1763
+ return ids;
1764
+ }
1765
+ function classSeverity(deviceSafetyClass) {
1766
+ return deviceSafetyClass === "C" ? "error" : "warning";
1767
+ }
1768
+ function detectHazardNoSoftwareItem(documents, deviceSafetyClass) {
1769
+ const severity = classSeverity(deviceSafetyClass);
1770
+ return documents.filter((d) => {
1771
+ if (!HAZARD_TYPES.has(d.type)) return false;
1772
+ if (!isApprovedOrEffective(d)) return false;
1773
+ return !d.frontmatter.software_item;
1774
+ }).map(
1775
+ (d) => buildGap(
1776
+ d,
1777
+ "hazard_no_software_item",
1778
+ severity,
1779
+ `Hazard "${d.title}" has no software_item reference. Every hazard must be traceable to a software item (IEC 62304 \xA77.1).`
1780
+ )
1781
+ );
1782
+ }
1783
+ function detectSecurityHazardNoAssetRef(documents) {
1784
+ const docById = new Map(documents.map((d) => [d.id, d]));
1785
+ const gaps = [];
1786
+ for (const doc of documents) {
1787
+ if (doc.type !== "haz_soe_security") continue;
1788
+ if (!isApprovedOrEffective(doc)) continue;
1789
+ const targetId = doc.frontmatter.software_item;
1790
+ if (!targetId) continue;
1791
+ const targetDoc = docById.get(targetId);
1792
+ if (!targetDoc || !ARCHITECTURE_TYPES.has(targetDoc.type)) continue;
1793
+ const assetTypes = targetDoc.frontmatter.asset_types;
1794
+ if (!assetTypes || assetTypes.length === 0) {
1795
+ gaps.push(
1796
+ buildGap(
1797
+ doc,
1798
+ "security_hazard_no_asset_ref",
1799
+ "warning",
1800
+ `Security hazard "${doc.title}" targets "${targetId}" which has no asset_types. Structured asset identification is required (IEC 81001-5-1).`
1801
+ )
1802
+ );
1803
+ }
1804
+ }
1805
+ return gaps;
1806
+ }
1807
+ function detectHighCiaNoSecurityHazard(documents) {
1808
+ const analyzedBySecurityHazard = /* @__PURE__ */ new Set();
1809
+ for (const doc of documents) {
1810
+ if (doc.type !== "haz_soe_security") continue;
1811
+ const targetId = doc.frontmatter.software_item;
1812
+ if (targetId) {
1813
+ analyzedBySecurityHazard.add(targetId);
1814
+ }
1815
+ }
1816
+ return documents.filter((d) => {
1817
+ if (!ARCHITECTURE_TYPES.has(d.type)) return false;
1818
+ if (!isApprovedOrEffective(d)) return false;
1819
+ const cia = d.frontmatter.cia_impact;
1820
+ if (!cia) return false;
1821
+ const hasHighCia = cia.confidentiality === HIGH_CIA || cia.integrity === HIGH_CIA || cia.availability === HIGH_CIA;
1822
+ if (!hasHighCia) return false;
1823
+ return !analyzedBySecurityHazard.has(d.id);
1824
+ }).map(
1825
+ (d) => buildGap(
1826
+ d,
1827
+ "high_cia_no_security_hazard",
1828
+ "warning",
1829
+ `Architecture doc "${d.title}" has high CIA impact but no security hazard analysis. High-value assets require security risk analysis (IEC 81001-5-1).`
1830
+ )
1831
+ );
1832
+ }
1833
+ function detectDetailedDesignMissingForUnit(documents, deviceSafetyClass) {
1834
+ const severity = classSeverity(deviceSafetyClass);
1835
+ const hasDetailedDesign = /* @__PURE__ */ new Set();
1836
+ for (const doc of documents) {
1837
+ if (doc.type !== "detailed_design") continue;
1838
+ const parentItem = doc.frontmatter.parent_item;
1839
+ if (parentItem) {
1840
+ hasDetailedDesign.add(parentItem);
1841
+ }
1842
+ }
1843
+ return documents.filter((d) => {
1844
+ if (!ARCHITECTURE_TYPES.has(d.type)) return false;
1845
+ if (!isApprovedOrEffective(d)) return false;
1846
+ const itemType = d.frontmatter.software_item_type;
1847
+ if (!itemType || !NEEDS_DETAILED_DESIGN.has(itemType)) return false;
1848
+ if (d.type === "detailed_design" && itemType === "unit") return false;
1849
+ return !hasDetailedDesign.has(d.id);
1850
+ }).map(
1851
+ (d) => buildGap(
1852
+ d,
1853
+ "detailed_design_missing_for_unit",
1854
+ severity,
1855
+ `Architecture ${d.frontmatter.software_item_type} "${d.title}" has no detailed design document. IEC 62304 \xA75.4.2 requires detailed design for units and components.`
1856
+ )
1857
+ );
1858
+ }
1859
+ function detectUnitNoVerification(documents, links, deviceSafetyClass) {
1860
+ const severity = classSeverity(deviceSafetyClass);
1861
+ const verifiedDocIds = buildVerifiedDocIds(links);
1862
+ return documents.filter((d) => {
1863
+ if (d.type !== "detailed_design") return false;
1864
+ if (!isApprovedOrEffective(d)) return false;
1865
+ return !verifiedDocIds.has(d.id);
1866
+ }).map(
1867
+ (d) => buildGap(
1868
+ d,
1869
+ "unit_no_verification",
1870
+ severity,
1871
+ `Detailed design "${d.title}" has no verified_by link to a test protocol or test report. IEC 62304 \xA75.5.5 requires unit verification.`
1872
+ )
1873
+ );
1874
+ }
1875
+ function detectRiskControlNoVerification(documents, links) {
1876
+ const verifiedDocIds = buildVerifiedDocIds(links);
1877
+ const gaps = [];
1878
+ for (const doc of documents) {
1879
+ if (!RISK_ENTRY_TYPES.has(doc.type)) continue;
1880
+ if (!isApprovedOrEffective(doc)) continue;
1881
+ const controls = /* @__PURE__ */ new Set();
1882
+ const topLevel = doc.frontmatter.mitigations ?? [];
1883
+ for (const m of topLevel) {
1884
+ controls.add(m.control);
1885
+ }
1886
+ const perHS = doc.frontmatter.hazardous_situation_assessments ?? [];
1887
+ for (const hsa of perHS) {
1888
+ for (const m of hsa.mitigations ?? []) {
1889
+ controls.add(m.control);
1890
+ }
1891
+ }
1892
+ for (const control of controls) {
1893
+ if (!verifiedDocIds.has(control)) {
1894
+ gaps.push(
1895
+ buildGap(
1896
+ doc,
1897
+ "risk_control_no_verification",
1898
+ "warning",
1899
+ `Risk "${doc.title}" has mitigation control "${control}" with no verified_by link to a test document. Risk controls must be verified (IEC 62304 \xA77.3).`
1900
+ )
1901
+ );
1902
+ }
1903
+ }
1904
+ }
1905
+ return gaps;
1906
+ }
1907
+ function detectArchitectureNoAssetTypes(documents, deviceSafetyClass) {
1908
+ if (deviceSafetyClass !== "C") return [];
1909
+ return documents.filter((d) => {
1910
+ if (!ARCHITECTURE_TYPES.has(d.type)) return false;
1911
+ if (!isApprovedOrEffective(d)) return false;
1912
+ const assetTypes = d.frontmatter.asset_types;
1913
+ return !assetTypes || assetTypes.length === 0;
1914
+ }).map(
1915
+ (d) => buildGap(
1916
+ d,
1917
+ "architecture_no_asset_types",
1918
+ "warning",
1919
+ `Architecture doc "${d.title}" has no asset_types. Class C devices require structured asset identification for cybersecurity risk analysis (IEC 81001-5-1).`
1920
+ )
1921
+ );
1922
+ }
1923
+ function getAcceptabilityStatus(severity, probability, config) {
1924
+ const coord = [severity, probability];
1925
+ if (config.unacceptable.some(([s, p]) => s === coord[0] && p === coord[1])) {
1926
+ return "unacceptable";
1927
+ }
1928
+ if (config.review_required?.some(([s, p]) => s === coord[0] && p === coord[1])) {
1929
+ return "review_required";
1930
+ }
1931
+ return "acceptable";
1932
+ }
1933
+ function collectAllMitigations(doc) {
1934
+ const topLevel = doc.frontmatter.mitigations ?? [];
1935
+ const perHS = (doc.frontmatter.hazardous_situation_assessments ?? []).flatMap(
1936
+ (hsa) => hsa.mitigations ?? []
1937
+ );
1938
+ return [...topLevel, ...perHS];
1939
+ }
1940
+ function collectAllHarms(doc) {
1941
+ return (doc.frontmatter.hazardous_situation_assessments ?? []).flatMap((hsa) => hsa.harms);
1942
+ }
1943
+ function detectMitigationGaps(doc, docById, mitigations) {
1944
+ const gaps = [];
1945
+ for (const mitigation of mitigations) {
1946
+ const controlDoc = docById.get(mitigation.control);
1947
+ if (!controlDoc) {
1948
+ gaps.push(
1949
+ buildGap(
1950
+ doc,
1951
+ "broken_mitigation_link",
1952
+ "error",
1953
+ `Risk "${doc.title}" references non-existent control "${mitigation.control}"`
1954
+ )
1955
+ );
1956
+ } else if (controlDoc.status && controlDoc.status !== "approved" && controlDoc.status !== "effective") {
1957
+ gaps.push(
1958
+ buildGap(
1959
+ doc,
1960
+ "control_not_approved",
1961
+ "warning",
1962
+ `Risk "${doc.title}" mitigated by control "${mitigation.control}" which is not approved`
1963
+ )
1964
+ );
1965
+ }
1966
+ }
1967
+ return gaps;
1968
+ }
1969
+ function detectBrokenReferences(doc, assessments, docById) {
1970
+ const gaps = [];
1971
+ for (const hsa of assessments) {
1972
+ if (hsa.hazardous_situation && !docById.has(hsa.hazardous_situation)) {
1973
+ gaps.push(
1974
+ buildGap(
1975
+ doc,
1976
+ "risk_broken_safety_chain",
1977
+ "error",
1978
+ `Risk "${doc.title}" references non-existent hazardous situation "${hsa.hazardous_situation}"`
1979
+ )
1980
+ );
1981
+ }
1982
+ for (const harm of hsa.harms) {
1983
+ if (harm.harm && !docById.has(harm.harm)) {
1984
+ gaps.push(
1985
+ buildGap(
1986
+ doc,
1987
+ "risk_broken_safety_chain",
1988
+ "error",
1989
+ `Risk "${doc.title}" references non-existent harm "${harm.harm}"`
1990
+ )
1991
+ );
1992
+ }
1993
+ }
1994
+ }
1995
+ return gaps;
1996
+ }
1997
+ function detectSafetyChainGaps(doc, docById) {
1998
+ if (!isApprovedOrEffective(doc)) return [];
1999
+ const gaps = [];
2000
+ const assessments = doc.frontmatter.hazardous_situation_assessments;
2001
+ const allHarms = collectAllHarms(doc);
2002
+ if (!assessments || assessments.length === 0 || allHarms.length === 0) {
2003
+ const missingParts = [];
2004
+ if (!assessments || assessments.length === 0)
2005
+ missingParts.push("hazardous situation reference");
2006
+ if (allHarms.length === 0) missingParts.push("harm assessments");
2007
+ gaps.push(
2008
+ buildGap(
2009
+ doc,
2010
+ "risk_missing_safety_chain",
2011
+ "error",
2012
+ `Risk "${doc.title}" missing ${missingParts.join(" and ")}`
2013
+ )
2014
+ );
2015
+ }
2016
+ if (assessments) {
2017
+ gaps.push(...detectBrokenReferences(doc, assessments, docById));
2018
+ }
2019
+ return gaps;
2020
+ }
2021
+ function detectWrongProbabilityDimension(doc) {
2022
+ if (!isApprovedOrEffective(doc)) return [];
2023
+ const allHarms = collectAllHarms(doc);
2024
+ if (allHarms.length === 0) return [];
2025
+ const isSecurityRisk = doc.type === "security_risk";
2026
+ const gaps = [];
2027
+ for (const assessment of allHarms) {
2028
+ if (isSecurityRisk) {
2029
+ if (assessment.inherent_probability != null && assessment.inherent_exploitability == null) {
2030
+ gaps.push(
2031
+ buildGap(
2032
+ doc,
2033
+ "wrong_probability_dimension",
2034
+ "error",
2035
+ `Security risk "${doc.title}" uses inherent_probability instead of inherent_exploitability for harm "${assessment.harm}"`
2036
+ )
2037
+ );
2038
+ }
2039
+ } else {
2040
+ if (assessment.inherent_exploitability != null && assessment.inherent_probability == null) {
2041
+ gaps.push(
2042
+ buildGap(
2043
+ doc,
2044
+ "wrong_probability_dimension",
2045
+ "error",
2046
+ `Risk "${doc.title}" uses inherent_exploitability instead of inherent_probability for harm "${assessment.harm}"`
2047
+ )
2048
+ );
2049
+ }
2050
+ }
2051
+ }
2052
+ return gaps;
2053
+ }
2054
+ function checkAssessmentAcceptability(allHarms, docById, config) {
2055
+ let anyInherentUnacceptable = false;
2056
+ let anyResidualUnacceptable = false;
2057
+ for (const assessment of allHarms) {
2058
+ const harmDoc = docById.get(assessment.harm);
2059
+ const severity = harmDoc?.frontmatter?.severity ?? 1;
2060
+ const probability = assessment.inherent_probability ?? assessment.inherent_exploitability ?? 1;
2061
+ if (getAcceptabilityStatus(severity, probability, config) === "unacceptable") {
2062
+ anyInherentUnacceptable = true;
2063
+ }
2064
+ const residualSeverity = assessment.harm_severity_override ?? severity;
2065
+ const residualProbability = assessment.residual_probability ?? assessment.residual_exploitability ?? probability;
2066
+ if (getAcceptabilityStatus(residualSeverity, residualProbability, config) === "unacceptable") {
2067
+ anyResidualUnacceptable = true;
2068
+ }
2069
+ }
2070
+ return { anyInherentUnacceptable, anyResidualUnacceptable };
2071
+ }
2072
+ function detectIsoCategoryGaps(doc) {
2073
+ if (!isApprovedOrEffective(doc)) return [];
2074
+ const allMitigations = collectAllMitigations(doc);
2075
+ const gaps = [];
2076
+ for (const mitigation of allMitigations) {
2077
+ if (!mitigation.iso_category) {
2078
+ gaps.push(
2079
+ buildGap(
2080
+ doc,
2081
+ "missing_iso_category",
2082
+ "error",
2083
+ `Risk "${doc.title}" has mitigation "${mitigation.control}" without iso_category`
2084
+ )
2085
+ );
2086
+ }
2087
+ }
2088
+ return gaps;
2089
+ }
2090
+ function detectRiskAcceptableGaps(doc) {
2091
+ if (!isApprovedOrEffective(doc)) return [];
2092
+ const allHarms = collectAllHarms(doc);
2093
+ const gaps = [];
2094
+ for (const harm of allHarms) {
2095
+ if (harm.risk_acceptable == null) {
2096
+ gaps.push(
2097
+ buildGap(
2098
+ doc,
2099
+ "missing_risk_acceptable",
2100
+ "error",
2101
+ `Risk "${doc.title}" has harm "${harm.harm}" without risk_acceptable field`
2102
+ )
2103
+ );
2104
+ }
2105
+ }
2106
+ return gaps;
2107
+ }
2108
+ function detectBenefitGaps(doc) {
2109
+ if (!isApprovedOrEffective(doc)) return [];
2110
+ const allHarms = collectAllHarms(doc);
2111
+ const gaps = [];
2112
+ for (const harm of allHarms) {
2113
+ if (harm.risk_acceptable === false && harm.benefit_outweighs_risk == null) {
2114
+ gaps.push(
2115
+ buildGap(
2116
+ doc,
2117
+ "unacceptable_no_benefit",
2118
+ "error",
2119
+ `Risk "${doc.title}" has unacceptable harm "${harm.harm}" without benefit_outweighs_risk`
2120
+ )
2121
+ );
2122
+ }
2123
+ }
2124
+ return gaps;
2125
+ }
2126
+ function detectMissingBodyRationale(doc) {
2127
+ if (!isApprovedOrEffective(doc)) return [];
2128
+ const assessments = doc.frontmatter.hazardous_situation_assessments;
2129
+ if (!assessments || assessments.length === 0) return [];
2130
+ if (!doc.bodySectionHeadings || doc.bodySectionHeadings.length === 0) return [];
2131
+ const gaps = [];
2132
+ for (const hsa of assessments) {
2133
+ for (const harm of hsa.harms) {
2134
+ const expectedHeading = `${harm.harm} via ${hsa.hazardous_situation}`;
2135
+ if (!doc.bodySectionHeadings.some(
2136
+ (h) => h.toLowerCase().trim() === expectedHeading.toLowerCase().trim()
2137
+ )) {
2138
+ gaps.push(
2139
+ buildGap(
2140
+ doc,
2141
+ "missing_body_rationale",
2142
+ "error",
2143
+ `Risk "${doc.title}" is missing body section "## ${expectedHeading}"`
2144
+ )
2145
+ );
2146
+ }
2147
+ }
2148
+ }
2149
+ return gaps;
2150
+ }
2151
+ function detectOrphanedBodySections(doc) {
2152
+ const assessments = doc.frontmatter.hazardous_situation_assessments;
2153
+ if (!assessments || assessments.length === 0) return [];
2154
+ if (!doc.bodySectionHeadings || doc.bodySectionHeadings.length === 0) return [];
2155
+ const expectedHeadings = /* @__PURE__ */ new Set();
2156
+ for (const hsa of assessments) {
2157
+ for (const harm of hsa.harms) {
2158
+ expectedHeadings.add(`${harm.harm} via ${hsa.hazardous_situation}`.toLowerCase().trim());
2159
+ }
2160
+ }
2161
+ const optionalSections = /* @__PURE__ */ new Set(["risk-benefit analysis", "harm assessment"]);
2162
+ const gaps = [];
2163
+ for (const heading of doc.bodySectionHeadings) {
2164
+ const normalized = heading.toLowerCase().trim();
2165
+ if (!expectedHeadings.has(normalized) && !optionalSections.has(normalized)) {
2166
+ gaps.push(
2167
+ buildGap(
2168
+ doc,
2169
+ "orphaned_body_section",
2170
+ "warning",
2171
+ `Risk "${doc.title}" has body section "## ${heading}" that does not match any frontmatter assessment`
2172
+ )
2173
+ );
2174
+ }
2175
+ }
2176
+ return gaps;
2177
+ }
2178
+ function detectRiskEntryGaps(doc, docById, config) {
2179
+ const gaps = [];
2180
+ gaps.push(...detectSafetyChainGaps(doc, docById));
2181
+ gaps.push(...detectWrongProbabilityDimension(doc));
2182
+ const allHarms = collectAllHarms(doc);
2183
+ const allMitigations = collectAllMitigations(doc);
2184
+ const { anyInherentUnacceptable, anyResidualUnacceptable } = checkAssessmentAcceptability(
2185
+ allHarms,
2186
+ docById,
2187
+ config
2188
+ );
2189
+ if (anyInherentUnacceptable && allMitigations.length === 0) {
2190
+ gaps.push(
2191
+ buildGap(
2192
+ doc,
2193
+ "missing_mitigation",
2194
+ "error",
2195
+ `Risk "${doc.title}" has unacceptable inherent risk but no mitigations`
2196
+ )
2197
+ );
2198
+ }
2199
+ gaps.push(...detectMitigationGaps(doc, docById, allMitigations));
2200
+ gaps.push(...detectRiskAcceptableGaps(doc));
2201
+ gaps.push(...detectBenefitGaps(doc));
2202
+ if (anyResidualUnacceptable && !doc.hasRiskBenefitSection) {
2203
+ gaps.push(
2204
+ buildGap(
2205
+ doc,
2206
+ "missing_risk_benefit",
2207
+ "error",
2208
+ `Risk "${doc.title}" has unacceptable residual risk but no risk-benefit analysis`
2209
+ )
2210
+ );
2211
+ }
2212
+ gaps.push(...detectIsoCategoryGaps(doc));
2213
+ gaps.push(...detectMissingBodyRationale(doc));
2214
+ gaps.push(...detectOrphanedBodySections(doc));
2215
+ return gaps;
2216
+ }
2217
+ var HAZARD_TYPES2 = ["haz_soe_software", "haz_soe_security"];
2218
+ var RISK_TYPES = ["software_risk", "usability_risk", "security_risk"];
2219
+ function detectHazardGaps(doc, leadsToLinks, analyzesLinks, allDocuments) {
2220
+ const gaps = [];
2221
+ const hasLeadsTo = doc.frontmatter.leads_to?.length || leadsToLinks.some((l) => l.sourceId === doc.id);
2222
+ if (!hasLeadsTo) {
2223
+ gaps.push(
2224
+ buildGap(
2225
+ doc,
2226
+ "hazard_no_situation",
2227
+ "error",
2228
+ `Hazard "${doc.title}" has no leads_to links to hazardous situations`
2229
+ )
2230
+ );
2231
+ }
2232
+ const isAnalyzed = analyzesLinks.some((l) => l.targetId === doc.id) || allDocuments.some((d) => RISK_TYPES.includes(d.type) && d.frontmatter.analyzes === doc.id);
2233
+ if (!isAnalyzed) {
2234
+ gaps.push(
2235
+ buildGap(
2236
+ doc,
2237
+ "hazard_not_analyzed",
2238
+ "warning",
2239
+ `Hazard "${doc.title}" is not analyzed by any risk entry`
2240
+ )
2241
+ );
2242
+ }
2243
+ return gaps;
2244
+ }
2245
+ function detectHazardCategoryGaps(doc, docById) {
2246
+ if (!isApprovedOrEffective(doc)) return [];
2247
+ const gaps = [];
2248
+ const categoryRef = doc.frontmatter.hazard_category;
2249
+ if (!categoryRef) {
2250
+ gaps.push(
2251
+ buildGap(
2252
+ doc,
2253
+ "haz_missing_category",
2254
+ "warning",
2255
+ `Hazard "${doc.title}" has no hazard_category reference`
2256
+ )
2257
+ );
2258
+ return gaps;
2259
+ }
2260
+ const categoryDoc = docById.get(categoryRef);
2261
+ if (!categoryDoc) {
2262
+ gaps.push(
2263
+ buildGap(
2264
+ doc,
2265
+ "haz_invalid_category",
2266
+ "error",
2267
+ `Hazard "${doc.title}" references non-existent category "${categoryRef}"`
2268
+ )
2269
+ );
2270
+ return gaps;
2271
+ }
2272
+ if (categoryDoc.status !== "approved" && categoryDoc.status !== "effective") {
2273
+ gaps.push(
2274
+ buildGap(
2275
+ doc,
2276
+ "category_not_approved",
2277
+ "warning",
2278
+ `Hazard "${doc.title}" references category "${categoryRef}" which is not approved`
2279
+ )
2280
+ );
2281
+ }
2282
+ return gaps;
2283
+ }
2284
+ function detectPreliminaryGaps(doc, allDocuments) {
2285
+ if (!doc.frontmatter.preliminary) return [];
2286
+ const isAnalyzedByRisk = allDocuments.some(
2287
+ (d) => RISK_TYPES.includes(d.type) && d.frontmatter.analyzes === doc.id
2288
+ );
2289
+ if (!isAnalyzedByRisk) {
2290
+ return [
2291
+ buildGap(
2292
+ doc,
2293
+ "preliminary_not_analyzed",
2294
+ "warning",
2295
+ `Preliminary hazard "${doc.title}" is not analyzed by any risk entry`
2296
+ )
2297
+ ];
2298
+ }
2299
+ return [];
2300
+ }
2301
+ function detectRiskGaps(documents, links, config, _deviceSafetyClass) {
2302
+ const gaps = [];
2303
+ const docById = new Map(documents.map((d) => [d.id, d]));
2304
+ const leadsToLinks = links.filter((l) => l.type === "leads_to");
2305
+ const resultsInLinks = links.filter((l) => l.type === "results_in");
2306
+ const analyzesLinks = links.filter((l) => l.type === "analyzes");
2307
+ for (const doc of documents) {
2308
+ if (HAZARD_TYPES2.includes(doc.type)) {
2309
+ gaps.push(...detectHazardGaps(doc, leadsToLinks, analyzesLinks, documents));
2310
+ gaps.push(...detectHazardCategoryGaps(doc, docById));
2311
+ gaps.push(...detectPreliminaryGaps(doc, documents));
2312
+ }
2313
+ if (doc.type === "hazardous_situation") {
2314
+ const hasResultsIn = doc.frontmatter.results_in?.length || resultsInLinks.some((l) => l.sourceId === doc.id);
2315
+ if (!hasResultsIn) {
2316
+ gaps.push(
2317
+ buildGap(
2318
+ doc,
2319
+ "situation_no_harm",
2320
+ "error",
2321
+ `Hazardous situation "${doc.title}" has no results_in links to harms`
2322
+ )
2323
+ );
2324
+ }
2325
+ }
2326
+ if (RISK_TYPES.includes(doc.type)) {
2327
+ gaps.push(...detectRiskEntryGaps(doc, docById, config));
2328
+ }
2329
+ }
2330
+ return gaps;
2331
+ }
2332
+ function detectBrokenLinks(documents, links) {
2333
+ const gaps = [];
2334
+ const documentIds = new Set(documents.map((d) => d.documentId));
2335
+ for (const link of links) {
2336
+ if (!documentIds.has(link.targetDocumentId)) {
2337
+ const sourceDoc = documents.find((d) => d.documentId === link.sourceDocumentId);
2338
+ gaps.push({
2339
+ code: "broken_link",
2340
+ severity: "error",
2341
+ documentId: link.sourceDocumentId,
2342
+ documentTitle: sourceDoc?.title || link.sourceDocumentId,
2343
+ message: `Link references non-existent document: ${link.targetDocumentId}`,
2344
+ metadata: { targetId: link.targetDocumentId, linkType: link.linkType }
2345
+ });
2346
+ }
2347
+ }
2348
+ return gaps;
2349
+ }
2350
+ function detectOrphanedPrs(documents, links) {
2351
+ const gaps = [];
2352
+ const productRequirements = documents.filter(
2353
+ (d) => d.docType === "requirement" && d.metadata.category === "product"
2354
+ );
2355
+ for (const req of productRequirements) {
2356
+ const hasDerivesFrom = links.some(
2357
+ (l) => l.sourceDocumentId === req.documentId && l.linkType === "derives_from"
2358
+ );
2359
+ if (!hasDerivesFrom) {
2360
+ gaps.push({
2361
+ code: "prs_no_upstream",
2362
+ severity: "warning",
2363
+ documentId: req.documentId,
2364
+ documentTitle: req.title,
2365
+ message: `Product requirement has no upstream link (derives_from). Should derive from a User Need, Risk, or regulatory requirement.`
2366
+ });
2367
+ }
2368
+ }
2369
+ return gaps;
2370
+ }
2371
+ function detectOrphanedPrsFulfillment(documents, links) {
2372
+ const FULFILLMENT_TARGET = {
2373
+ labeling: "labeling",
2374
+ clinical: "clinical_evaluation_report",
2375
+ usability: "summative_evaluation"
2376
+ };
2377
+ const gaps = [];
2378
+ const docTypeById = new Map(documents.map((d) => [d.documentId, d.docType]));
2379
+ const productRequirements = documents.filter(
2380
+ (d) => d.docType === "requirement" && d.metadata.category === "product"
2381
+ );
2382
+ for (const req of productRequirements) {
2383
+ const fulfillmentType = req.metadata.fulfillment_type;
2384
+ const expectedDocType = fulfillmentType ? FULFILLMENT_TARGET[fulfillmentType] : void 0;
2385
+ if (!expectedDocType) continue;
2386
+ const hasLink = links.some(
2387
+ (l) => l.sourceDocumentId === req.documentId && l.linkType === "implements" && docTypeById.get(l.targetDocumentId) === expectedDocType
2388
+ );
2389
+ if (!hasLink) {
2390
+ gaps.push({
2391
+ code: "prs_no_fulfillment",
2392
+ severity: "warning",
2393
+ documentId: req.documentId,
2394
+ documentTitle: req.title,
2395
+ message: `${req.documentId} has fulfillment_type '${fulfillmentType}' but no implements link to a ${expectedDocType.replace(/_/g, " ")} document`
2396
+ });
2397
+ }
2398
+ }
2399
+ return gaps;
2400
+ }
2401
+ function detectSrsMissingReqType(documents) {
2402
+ const gaps = [];
2403
+ const softwareRequirements = documents.filter(
2404
+ (d) => d.docType === "requirement" && d.metadata.category === "software"
2405
+ );
2406
+ for (const req of softwareRequirements) {
2407
+ if (!req.metadata.req_type) {
2408
+ gaps.push({
2409
+ code: "srs_missing_req_type",
2410
+ severity: "warning",
2411
+ documentId: req.documentId,
2412
+ documentTitle: req.title,
2413
+ message: `Software requirement missing req_type classification (IEC 62304 \xA75.2.2). Add req_type: functional|interface|performance|security|usability|safety|regulatory to frontmatter.`
2414
+ });
2415
+ }
2416
+ }
2417
+ return gaps;
2418
+ }
2419
+ function extractH2Headings(body) {
2420
+ const headings = [];
2421
+ for (const line of body.split("\n")) {
2422
+ const match = line.match(/^##\s+(.+)/);
2423
+ if (match) {
2424
+ headings.push(match[1].trim());
2425
+ }
2426
+ }
2427
+ return headings;
2428
+ }
2429
+ function checkRequiredHeadings(doc, body, requiredSections) {
2430
+ const headingsLower = extractH2Headings(body).map((h) => h.toLowerCase());
2431
+ return requiredSections.filter((section) => !headingsLower.some((h) => h.includes(section.toLowerCase()))).map((section) => ({
2432
+ code: "missing_section",
2433
+ severity: "warning",
2434
+ documentId: doc.documentId,
2435
+ documentTitle: doc.title,
2436
+ message: `Missing required section: "${section}"`
2437
+ }));
2438
+ }
2439
+ function detectMissingSections(documents, bodies) {
2440
+ const gaps = [];
2441
+ for (const doc of documents) {
2442
+ const requiredSections = REQUIRED_SECTIONS[doc.docType];
2443
+ if (!requiredSections) continue;
2444
+ const body = bodies.get(doc.documentId);
2445
+ if (!body) continue;
2446
+ gaps.push(...checkRequiredHeadings(doc, body, requiredSections));
2447
+ }
2448
+ return gaps;
2449
+ }
2450
+ var STANDARD_REQUIRED_SECTIONS = ["Requirement", "Verification Criteria"];
2451
+ var USER_STORY_REQUIRED_SECTIONS = ["User Story", "Verification Criteria"];
2452
+ function detectRequirementFormatSections(documents, bodies) {
2453
+ const gaps = [];
2454
+ const requirements = documents.filter((d) => d.docType === "requirement");
2455
+ for (const req of requirements) {
2456
+ const body = bodies.get(req.documentId);
2457
+ if (!body) continue;
2458
+ const format = req.metadata.format;
2459
+ const requiredSections = format === "user_story" ? USER_STORY_REQUIRED_SECTIONS : STANDARD_REQUIRED_SECTIONS;
2460
+ gaps.push(...checkRequiredHeadings(req, body, requiredSections));
2461
+ }
2462
+ return gaps;
2463
+ }
2464
+ function detectUnlinkedMajorNc(documents) {
2465
+ const gaps = [];
2466
+ const auditReports = documents.filter((d) => d.docType === "audit_report");
2467
+ for (const doc of auditReports) {
2468
+ const findings = doc.metadata.findings ?? [];
2469
+ for (const finding of findings) {
2470
+ if (finding.classification === "major_nc" && !finding.capa_id) {
2471
+ gaps.push({
2472
+ code: "unlinked_major_nc",
2473
+ severity: "error",
2474
+ documentId: doc.documentId,
2475
+ documentTitle: doc.title,
2476
+ message: `Finding ${finding.finding_id} is classified as major nonconformity but has no linked CAPA. Major NCs require corrective action per ISO 13485 \xA78.5.2.`,
2477
+ metadata: { findingId: finding.finding_id, classification: finding.classification }
2478
+ });
2479
+ }
2480
+ }
2481
+ }
2482
+ return gaps;
2483
+ }
2484
+ function detectOverdueAudits(documents) {
2485
+ const gaps = [];
2486
+ const schedules = documents.filter((d) => d.docType === "audit_schedule");
2487
+ const now = /* @__PURE__ */ new Date();
2488
+ for (const doc of schedules) {
2489
+ const audits = doc.metadata.audits ?? [];
2490
+ for (const audit of audits) {
2491
+ if (audit.status === "planned" && new Date(audit.planned_date) < now) {
2492
+ gaps.push({
2493
+ code: "overdue_audit",
2494
+ severity: "warning",
2495
+ documentId: doc.documentId,
2496
+ documentTitle: doc.title,
2497
+ message: `Planned audit "${audit.audit_id}" (${audit.process_area}) was due ${audit.planned_date} but is still in planned status.`,
2498
+ metadata: {
2499
+ auditId: audit.audit_id,
2500
+ processArea: audit.process_area,
2501
+ plannedDate: audit.planned_date
2502
+ }
2503
+ });
2504
+ }
2505
+ }
2506
+ }
2507
+ return gaps;
2508
+ }
2509
+ function toDocumentForGaps(doc) {
2510
+ const m = doc.metadata;
2511
+ return {
2512
+ id: doc.documentId,
2513
+ type: doc.docType,
2514
+ title: doc.title,
2515
+ status: m.status,
2516
+ frontmatter: {
2517
+ leads_to: m.leads_to,
2518
+ results_in: m.results_in,
2519
+ analyzes: m.analyzes,
2520
+ preliminary: m.preliminary,
2521
+ hazard_category: m.hazard_category,
2522
+ software_item: m.software_item,
2523
+ software_item_type: m.software_item_type,
2524
+ parent_item: m.parent_item,
2525
+ asset_types: m.asset_types,
2526
+ severity: m.severity,
2527
+ cia_impact: m.cia_impact,
2528
+ mitigations: m.mitigations,
2529
+ hazardous_situation_assessments: m.hazardous_situation_assessments
2530
+ }
2531
+ };
2532
+ }
2533
+ function toLinkForGaps(link) {
2534
+ return {
2535
+ sourceId: link.sourceDocumentId,
2536
+ targetId: link.targetDocumentId,
2537
+ type: link.linkType
2538
+ };
2539
+ }
2540
+ function riskGapToGap(rg) {
2541
+ return {
2542
+ code: rg.code,
2543
+ severity: rg.severity,
2544
+ documentId: rg.documentId,
2545
+ documentTitle: rg.documentTitle,
2546
+ message: rg.message
2547
+ };
2548
+ }
2549
+ function detectRiskSecurityGaps(docsForGaps, linksForGaps, deviceSafetyClass) {
2550
+ return [
2551
+ ...detectHazardNoSoftwareItem(docsForGaps, deviceSafetyClass),
2552
+ ...detectSecurityHazardNoAssetRef(docsForGaps),
2553
+ ...detectHighCiaNoSecurityHazard(docsForGaps),
2554
+ ...detectDetailedDesignMissingForUnit(docsForGaps, deviceSafetyClass),
2555
+ ...detectUnitNoVerification(docsForGaps, linksForGaps, deviceSafetyClass),
2556
+ ...detectRiskControlNoVerification(docsForGaps, linksForGaps),
2557
+ ...detectArchitectureNoAssetTypes(docsForGaps, deviceSafetyClass)
2558
+ ].map(riskGapToGap);
2559
+ }
2560
+ function buildSummary(gaps) {
2561
+ let errorCount = 0;
2562
+ let warningCount = 0;
2563
+ const byCode = {};
2564
+ for (const gap of gaps) {
2565
+ if (gap.severity === "error") errorCount++;
2566
+ else warningCount++;
2567
+ byCode[gap.code] = (byCode[gap.code] || 0) + 1;
2568
+ }
2569
+ return { errorCount, warningCount, byCode };
2570
+ }
2571
+ function detectAllGaps(documents, links, context) {
2572
+ const { deviceSafetyClass, bodies, acceptabilityConfig } = context ?? {};
2573
+ const gaps = [];
2574
+ gaps.push(...detectBrokenLinks(documents, links));
2575
+ gaps.push(...detectOrphanedPrs(documents, links));
2576
+ gaps.push(...detectOrphanedPrsFulfillment(documents, links));
2577
+ gaps.push(...detectSrsMissingReqType(documents));
2578
+ if (bodies) {
2579
+ gaps.push(...detectMissingSections(documents, bodies));
2580
+ gaps.push(...detectRequirementFormatSections(documents, bodies));
2581
+ }
2582
+ gaps.push(...detectUnlinkedMajorNc(documents));
2583
+ gaps.push(...detectOverdueAudits(documents));
2584
+ gaps.push(...detectRequirementNoArchitecture(documents, links));
2585
+ gaps.push(...detectArchitectureNoSegregation(documents, deviceSafetyClass));
2586
+ gaps.push(...detectArchitectureNoParent(documents));
2587
+ gaps.push(...detectArchitectureNoRiskAnalysis(documents, links));
2588
+ const docsForGaps = documents.map(toDocumentForGaps);
2589
+ const linksForGaps = links.map(toLinkForGaps);
2590
+ gaps.push(...detectRiskSecurityGaps(docsForGaps, linksForGaps, deviceSafetyClass));
2591
+ if (acceptabilityConfig) {
2592
+ const riskGaps = detectRiskGaps(
2593
+ docsForGaps,
2594
+ linksForGaps,
2595
+ acceptabilityConfig,
2596
+ deviceSafetyClass
2597
+ );
2598
+ gaps.push(...riskGaps.map(riskGapToGap));
2599
+ }
2600
+ return { gaps, summary: buildSummary(gaps) };
2601
+ }
2602
+ var RetentionPolicySchema = z14.object({
2603
+ /** Default retention period in years for all record types */
2604
+ defaultPeriodYears: z14.number().int().min(1).max(30).default(10),
2605
+ /**
2606
+ * Optional per-record-type overrides (e.g., { "release": 15, "signature": 20 }).
2607
+ * Keys are record type identifiers; values are retention periods in years.
2608
+ */
2609
+ byRecordType: z14.record(z14.string(), z14.number().int().min(1).max(30)).optional()
2610
+ });
2611
+ var MemberPermissionsSchema = z15.object({
2612
+ canSign: z15.boolean(),
2613
+ canApprove: z15.boolean(),
2614
+ canCreateRelease: z15.boolean(),
2615
+ canPublishRelease: z15.boolean(),
2616
+ canApproveTransfer: z15.boolean(),
2617
+ canManageMembers: z15.boolean(),
2618
+ canViewAuditLog: z15.boolean(),
2619
+ canExport: z15.boolean()
2620
+ });
2621
+ var SoftwareItemTypeSchema = z16.enum(["system", "subsystem", "component", "unit"]);
2622
+ var AssetTypeSchema = z16.enum([
2623
+ "data_store",
2624
+ "api_endpoint",
2625
+ "background_worker",
2626
+ "auth_provider",
2627
+ "external_service",
2628
+ "user_interface",
2629
+ "message_queue",
2630
+ "network_boundary"
2631
+ ]);
2632
+ var CiaImpactLevelSchema = z16.enum(["low", "medium", "high"]);
2633
+ var CiaImpactSchema = z16.object({
2634
+ confidentiality: CiaImpactLevelSchema.optional(),
2635
+ integrity: CiaImpactLevelSchema.optional(),
2636
+ availability: CiaImpactLevelSchema.optional()
2637
+ });
2638
+ var SegregationSchema = z16.object({
2639
+ mechanism: z16.string().min(1),
2640
+ rationale: z16.string().min(1)
2641
+ });
2642
+ var ArchitectureFrontmatterSchema = z16.object({
2643
+ id: z16.string().min(1),
2644
+ title: z16.string().min(1),
2645
+ /** Required — cannot be inferred from folder alone since architecture/ and design/ share parent */
2646
+ type: z16.literal("architecture"),
2647
+ status: RiskDocumentStatusSchema,
2648
+ /** IEC 62304 §5.3 — C4/IEC mapping: system, subsystem, component, unit (required) */
2649
+ software_item_type: SoftwareItemTypeSchema,
2650
+ /** Parent HLD doc ID (optional for system-level, recommended for subsystem/component) */
2651
+ parent_item: z16.string().optional(),
2652
+ safety_class: SafetyClassSchema.optional(),
2653
+ segregation: SegregationSchema.optional(),
2654
+ /** Document author — required for all regulated document types */
2655
+ author: z16.string().min(1),
2656
+ reviewers: z16.array(z16.string()).optional(),
2657
+ /** Approver list — required for all regulated document types */
2658
+ approvers: z16.array(z16.string()).min(1),
2659
+ /** SRS requirement IDs this architecture item implements (IEC 62304 §5.3.1) */
2660
+ implements: z16.array(z16.string().min(1)).optional(),
2661
+ /** IEC 81001-5-1 asset classification for this software item (optional, enables security gap detection) */
2662
+ asset_types: z16.array(AssetTypeSchema).optional(),
2663
+ /** CIA impact assessment — enables automated detection of high-value assets missing security analysis */
2664
+ cia_impact: CiaImpactSchema.optional()
2665
+ });
2666
+ var DetailedDesignFrontmatterSchema = z16.object({
2667
+ id: z16.string().min(1),
2668
+ title: z16.string().min(1),
2669
+ /** Required — cannot be inferred from folder alone since architecture/ and design/ share parent */
2670
+ type: z16.literal("detailed_design"),
2671
+ status: RiskDocumentStatusSchema,
2672
+ /** IEC 62304 §5.4 — typically component or unit (required) */
2673
+ software_item_type: SoftwareItemTypeSchema,
2674
+ /** Parent HLD doc ID — required for SDD (must reference parent architecture) */
2675
+ parent_item: z16.string().min(1),
2676
+ safety_class: SafetyClassSchema.optional(),
2677
+ segregation: SegregationSchema.optional(),
2678
+ /** Document author — required for all regulated document types */
2679
+ author: z16.string().min(1),
2680
+ reviewers: z16.array(z16.string()).optional(),
2681
+ /** Approver list — required for all regulated document types */
2682
+ approvers: z16.array(z16.string()).min(1),
2683
+ /** SRS requirement IDs this design item implements (IEC 62304 §5.4.2) */
2684
+ implements: z16.array(z16.string().min(1)).optional(),
2685
+ /** IEC 81001-5-1 asset classification for this software item (optional, enables security gap detection) */
2686
+ asset_types: z16.array(AssetTypeSchema).optional(),
2687
+ /** CIA impact assessment — enables automated detection of high-value assets missing security analysis */
2688
+ cia_impact: CiaImpactSchema.optional()
2689
+ });
2690
+ var RequirementTypeSchema = z17.enum([
2691
+ "functional",
2692
+ "interface",
2693
+ "performance",
2694
+ "security",
2695
+ "usability",
2696
+ "safety",
2697
+ "regulatory"
2698
+ ]);
2699
+ var RequirementFormatSchema = z17.enum(["standard", "user_story"]);
2700
+ var RequirementFulfillmentTypeSchema = z17.enum([
2701
+ "software",
2702
+ "labeling",
2703
+ "clinical",
2704
+ "usability",
2705
+ "regulatory_doc",
2706
+ "process"
2707
+ ]);
2708
+ var RequirementFrontmatterSchema = z17.object({
2709
+ id: z17.string().min(1),
2710
+ title: z17.string().min(1),
2711
+ status: RiskDocumentStatusSchema,
2712
+ /** IEC 62304 §5.2.2 — requirement classification (required for SRS, optional for PRS) */
2713
+ req_type: RequirementTypeSchema.optional(),
2714
+ /** Authoring convention — controls which required sections are checked */
2715
+ format: RequirementFormatSchema.optional(),
2716
+ /** ISO 13485 §7.3.3 — how this PRS design input is fulfilled (PRS only, defaults to 'software') */
2717
+ fulfillment_type: RequirementFulfillmentTypeSchema.optional(),
2718
+ author: z17.string().optional(),
2719
+ reviewers: z17.array(z17.string()).optional(),
2720
+ approvers: z17.array(z17.string()).optional(),
2721
+ /** Upstream traceability — IDs of documents this requirement traces from (e.g., UN for PRS, PRS for SRS) */
2722
+ traces_from: z17.array(z17.string()).optional(),
2723
+ /** Downstream traceability — IDs of documents this requirement traces to (e.g., SRS for PRS) */
2724
+ traces_to: z17.array(z17.string()).optional()
2725
+ });
2726
+ var ProductRequirementFrontmatterSchema = z18.object({
2727
+ id: z18.string().min(1),
2728
+ title: z18.string().min(1),
2729
+ status: RiskDocumentStatusSchema,
2730
+ /** IEC 62304 §5.2.2 — requirement classification (optional for PRS) */
2731
+ req_type: RequirementTypeSchema.optional(),
2732
+ /** Authoring convention — controls which required sections are checked */
2733
+ format: RequirementFormatSchema.optional(),
2734
+ /** ISO 13485 §7.3.3 — how this PRS design input is fulfilled (defaults to 'software') */
2735
+ fulfillment_type: RequirementFulfillmentTypeSchema.optional(),
2736
+ /** Document author — required for all regulated document types */
2737
+ author: z18.string().min(1),
2738
+ reviewers: z18.array(z18.string()).optional(),
2739
+ /** Approver list — required for all regulated document types */
2740
+ approvers: z18.array(z18.string()).min(1),
2741
+ /** Upstream traceability — IDs of User Need documents this PRS traces from (required) */
2742
+ traces_from: z18.array(z18.string()).min(1),
2743
+ /** Downstream traceability — IDs of SRS documents this PRS traces to */
2744
+ traces_to: z18.array(z18.string()).optional()
2745
+ });
2746
+ var DeliverableVersionTypeSchema = z19.enum(["initial", "major", "minor", "patch"]);
2747
+ var DeliverableSafetyClassSchema = z19.enum(["A", "B", "C"]);
2748
+ var PlanDeliverableSchema = z19.object({
2749
+ /** Unique ID within the plan (e.g., "DEL-SDP-001") */
2750
+ id: z19.string().min(1),
2751
+ /** Human-readable label for the checklist item */
2752
+ label: z19.string().min(1),
2753
+ /** Which version types this applies to (initial, major, minor, patch) */
2754
+ version_types: z19.array(DeliverableVersionTypeSchema).optional(),
2755
+ /** Which safety classes this applies to (A, B, C) */
2756
+ safety_classes: z19.array(DeliverableSafetyClassSchema).optional(),
2757
+ /** Whether this deliverable can be deferred with rationale */
2758
+ deferrable: z19.boolean().optional()
2759
+ });
2760
+ var PlanDeliverablesFieldSchema = z19.array(PlanDeliverableSchema).optional();
2761
+ var StrictPlanDeliverablesFieldSchema = z19.array(PlanDeliverableSchema).min(1);
2762
+ var SoftwareDevelopmentPlanFrontmatterSchema = z20.object({
2763
+ type: z20.literal("software_development_plan"),
2764
+ id: z20.string().min(1),
2765
+ title: z20.string().min(1),
2766
+ status: RiskDocumentStatusSchema,
2767
+ author: z20.string().optional(),
2768
+ reviewers: z20.array(z20.string()).optional(),
2769
+ approvers: z20.array(z20.string()).optional(),
2770
+ deliverables: PlanDeliverablesFieldSchema
2771
+ });
2772
+ var SoftwareMaintenancePlanFrontmatterSchema = z20.object({
2773
+ type: z20.literal("software_maintenance_plan"),
2774
+ id: z20.string().min(1),
2775
+ title: z20.string().min(1),
2776
+ status: RiskDocumentStatusSchema,
2777
+ author: z20.string().optional(),
2778
+ reviewers: z20.array(z20.string()).optional(),
2779
+ approvers: z20.array(z20.string()).optional()
2780
+ });
2781
+ var SoupRegisterFrontmatterSchema = z20.object({
2782
+ type: z20.literal("soup_register"),
2783
+ id: z20.string().min(1),
2784
+ title: z20.string().min(1),
2785
+ status: RiskDocumentStatusSchema,
2786
+ author: z20.string().optional(),
2787
+ reviewers: z20.array(z20.string()).optional(),
2788
+ approvers: z20.array(z20.string()).optional()
2789
+ });
2790
+ var SoftwareTestPlanFrontmatterSchema = z20.object({
2791
+ type: z20.literal("software_test_plan"),
2792
+ id: z20.string().min(1),
2793
+ title: z20.string().min(1),
2794
+ status: RiskDocumentStatusSchema,
2795
+ author: z20.string().optional(),
2796
+ reviewers: z20.array(z20.string()).optional(),
2797
+ approvers: z20.array(z20.string()).optional(),
2798
+ deliverables: PlanDeliverablesFieldSchema
2799
+ });
2800
+ var SoftwareRequirementFrontmatterSchema = z21.object({
2801
+ id: z21.string().min(1),
2802
+ title: z21.string().min(1),
2803
+ status: RiskDocumentStatusSchema,
2804
+ /** IEC 62304 §5.2.2 — requirement classification (recommended for SRS) */
2805
+ req_type: RequirementTypeSchema.optional(),
2806
+ /** Authoring convention — controls which required sections are checked */
2807
+ format: RequirementFormatSchema.optional(),
2808
+ /** Document author — required for all regulated document types */
2809
+ author: z21.string().min(1),
2810
+ reviewers: z21.array(z21.string()).optional(),
2811
+ /** Approver list — required for all regulated document types */
2812
+ approvers: z21.array(z21.string()).min(1),
2813
+ /** Upstream traceability — IDs of PRS documents this SRS traces from (required) */
2814
+ traces_from: z21.array(z21.string()).min(1),
2815
+ /** Downstream traceability — IDs of documents this SRS traces to */
2816
+ traces_to: z21.array(z21.string()).optional(),
2817
+ /**
2818
+ * @deprecated Use `implements` on HLD/SDD documents instead.
2819
+ * Architecture documents should declare which requirements they implement (IEC 62304 §5.3.1).
2820
+ * Kept for backwards compatibility — the sync engine does not create links from this field.
2821
+ */
2822
+ implemented_in: z21.string().optional()
2823
+ });
2824
+ var ExecutionMethodSchema = z22.enum(["manual", "automated"]);
2825
+ var TestProtocolFrontmatterSchema = z22.object({
2826
+ type: z22.literal("test_protocol"),
2827
+ id: z22.string().min(1),
2828
+ title: z22.string().min(1),
2829
+ status: RiskDocumentStatusSchema,
2830
+ execution_method: ExecutionMethodSchema.optional(),
2831
+ // lenient: optional for existing docs
2832
+ author: z22.string().optional(),
2833
+ reviewers: z22.array(z22.string()).optional(),
2834
+ approvers: z22.array(z22.string()).optional()
2835
+ });
2836
+ var TestPhaseSchema = z22.enum(["verification", "production"]);
2837
+ var TestReportFrontmatterSchema = z22.object({
2838
+ type: z22.literal("test_report"),
2839
+ id: z22.string().min(1),
2840
+ title: z22.string().min(1),
2841
+ status: RiskDocumentStatusSchema,
2842
+ protocol_id: z22.string().min(1).optional(),
2843
+ // lenient: optional for existing docs
2844
+ author: z22.string().optional(),
2845
+ reviewers: z22.array(z22.string()).optional(),
2846
+ approvers: z22.array(z22.string()).optional(),
2847
+ release_version: z22.string().optional(),
2848
+ test_phase: TestPhaseSchema.optional()
2849
+ });
2850
+ var UsabilityPlanFrontmatterSchema = z23.object({
2851
+ type: z23.literal("usability_plan"),
2852
+ id: z23.string().min(1),
2853
+ title: z23.string().min(1),
2854
+ status: RiskDocumentStatusSchema,
2855
+ author: z23.string().optional(),
2856
+ reviewers: z23.array(z23.string()).optional(),
2857
+ approvers: z23.array(z23.string()).optional(),
2858
+ deliverables: PlanDeliverablesFieldSchema
2859
+ });
2860
+ var UseSpecificationFrontmatterSchema = z23.object({
2861
+ type: z23.literal("use_specification"),
2862
+ id: z23.string().min(1),
2863
+ title: z23.string().min(1),
2864
+ status: RiskDocumentStatusSchema,
2865
+ user_group: z23.string().min(1),
2866
+ author: z23.string().optional(),
2867
+ reviewers: z23.array(z23.string()).optional(),
2868
+ approvers: z23.array(z23.string()).optional()
2869
+ });
2870
+ var TaskAnalysisFrontmatterSchema = z23.object({
2871
+ type: z23.literal("task_analysis"),
2872
+ id: z23.string().min(1),
2873
+ title: z23.string().min(1),
2874
+ status: RiskDocumentStatusSchema,
2875
+ user_group: z23.string().min(1),
2876
+ critical_task: z23.boolean(),
2877
+ author: z23.string().optional(),
2878
+ reviewers: z23.array(z23.string()).optional(),
2879
+ approvers: z23.array(z23.string()).optional()
2880
+ });
2881
+ var UsabilityEvaluationFrontmatterSchema = z23.object({
2882
+ type: z23.literal("usability_evaluation"),
2883
+ id: z23.string().min(1),
2884
+ title: z23.string().min(1),
2885
+ status: RiskDocumentStatusSchema,
2886
+ result: z23.enum(["pass", "fail", "pass_with_findings"]).optional(),
2887
+ author: z23.string().optional(),
2888
+ reviewers: z23.array(z23.string()).optional(),
2889
+ approvers: z23.array(z23.string()).optional()
2890
+ });
2891
+ var SummativeEvaluationFrontmatterSchema = z23.object({
2892
+ type: z23.literal("summative_evaluation"),
2893
+ id: z23.string().min(1),
2894
+ title: z23.string().min(1),
2895
+ status: RiskDocumentStatusSchema,
2896
+ result: z23.enum(["pass", "fail", "pass_with_findings"]).optional(),
2897
+ author: z23.string().optional(),
2898
+ reviewers: z23.array(z23.string()).optional(),
2899
+ approvers: z23.array(z23.string()).optional()
2900
+ });
2901
+ var UserNeedPrioritySchema = z24.enum(["must_have", "should_have", "nice_to_have"]);
2902
+ var UserNeedFrontmatterSchema = z24.object({
2903
+ id: z24.string().min(1),
2904
+ title: z24.string().min(1),
2905
+ status: RiskDocumentStatusSchema,
2906
+ /** Validated if present — ensures frontmatter doesn't misidentify the document type */
2907
+ type: z24.literal("user_need").optional(),
2908
+ /** The user role or stakeholder (e.g., "Quality Manager", "Developer") — required per ISO 13485 §7.3.2 */
2909
+ stakeholder: z24.string().min(1),
2910
+ /** MoSCoW priority classification */
2911
+ priority: UserNeedPrioritySchema.optional(),
2912
+ /** Where this need originated (e.g., "ISO 13485 §7.3", "user interview") */
2913
+ source: z24.string().optional(),
2914
+ /** IDs of product requirements derived from this need */
2915
+ derives: z24.array(z24.string()).optional(),
2916
+ /** Document author — required for all regulated document types */
2917
+ author: z24.string().min(1),
2918
+ reviewers: z24.array(z24.string()).optional(),
2919
+ /** Approver list — required for all regulated document types */
2920
+ approvers: z24.array(z24.string()).min(1),
2921
+ /** Optional reference to a Use Specification persona (US-xxx) per IEC 62366 */
2922
+ use_specification: z24.string().optional()
2923
+ });
2924
+ var DEFAULT_ACCEPTABILITY_CONFIG = {
2925
+ unacceptable: [
2926
+ [5, 5],
2927
+ // Catastrophic + Frequent
2928
+ [5, 4],
2929
+ // Catastrophic + Likely
2930
+ [5, 3],
2931
+ // Catastrophic + Possible
2932
+ [4, 5],
2933
+ // Major + Frequent
2934
+ [4, 4],
2935
+ // Major + Likely
2936
+ [3, 5]
2937
+ // Moderate + Frequent
2938
+ ],
2939
+ review_required: [
2940
+ [5, 2],
2941
+ // Catastrophic + Unlikely
2942
+ [4, 3],
2943
+ // Major + Possible
2944
+ [3, 4],
2945
+ // Moderate + Likely
2946
+ [3, 3],
2947
+ // Moderate + Possible
2948
+ [2, 5]
2949
+ // Minor + Frequent
2950
+ ]
2951
+ };
2952
+ var RISK_DOCUMENT_TYPES = [
2953
+ "software_risk",
2954
+ "usability_risk",
2955
+ "security_risk"
2956
+ ];
2957
+ var HAZARD_DOCUMENT_TYPES = ["haz_soe_software", "haz_soe_security"];
2958
+ var ALL_RISK_RELATED_DOCUMENT_TYPES = [
2959
+ ...RISK_DOCUMENT_TYPES,
2960
+ ...HAZARD_DOCUMENT_TYPES,
2961
+ "hazardous_situation",
2962
+ "harm"
2963
+ ];
2964
+ var RiskManagementPlanFrontmatterSchema = z25.object({
2965
+ type: z25.literal("risk_management_plan"),
2966
+ id: z25.string().min(1),
2967
+ title: z25.string().min(1),
2968
+ status: RiskDocumentStatusSchema,
2969
+ author: z25.string().optional(),
2970
+ reviewers: z25.array(z25.string()).optional(),
2971
+ approvers: z25.array(z25.string()).optional(),
2972
+ deliverables: PlanDeliverablesFieldSchema
2973
+ });
2974
+ var EvidenceResultStatusSchema = z26.enum(["pass", "fail", "skip"]);
2975
+ var EvidenceResultSchema = z26.object({
2976
+ test_id: z26.string().min(1),
2977
+ req_id: z26.string().min(1),
2978
+ status: EvidenceResultStatusSchema,
2979
+ message: z26.string().optional(),
2980
+ duration_ms: z26.number().nonnegative().optional()
2981
+ });
2982
+ var EvidenceSummarySchema = z26.object({
2983
+ total: z26.number().int().nonnegative(),
2984
+ passed: z26.number().int().nonnegative(),
2985
+ failed: z26.number().int().nonnegative(),
2986
+ skipped: z26.number().int().nonnegative()
2987
+ }).refine((s) => s.total === s.passed + s.failed + s.skipped, {
2988
+ message: "total must equal passed + failed + skipped",
2989
+ path: ["total"]
2990
+ });
2991
+ var AutomatedTestEvidenceSchema = z26.object({
2992
+ protocol_id: z26.string().min(1),
2993
+ results: z26.array(EvidenceResultSchema).min(1),
2994
+ summary: EvidenceSummarySchema,
2995
+ environment: z26.record(z26.string()).optional(),
2996
+ timestamp: z26.string().datetime()
2997
+ }).refine((e) => e.results.length === e.summary.total, {
2998
+ message: "results.length must equal summary.total",
2999
+ path: ["summary", "total"]
3000
+ });
3001
+ var ManualStepResultSchema = z26.object({
3002
+ step_id: z26.string().min(1),
3003
+ status: EvidenceResultStatusSchema,
3004
+ notes: z26.string().optional(),
3005
+ attachment_path: z26.string().optional()
3006
+ });
3007
+ var ManualTestEvidenceSchema = z26.object({
3008
+ protocol_id: z26.string().min(1),
3009
+ results: z26.array(ManualStepResultSchema).min(1),
3010
+ summary: EvidenceSummarySchema,
3011
+ executor: z26.string().email(),
3012
+ executed_at: z26.string().datetime()
3013
+ }).refine((e) => e.results.length === e.summary.total, {
3014
+ message: "results.length must equal summary.total",
3015
+ path: ["summary", "total"]
3016
+ });
3017
+ var LegalDocumentTypeSchema = z27.enum(["tos", "privacy-policy", "billing-terms"]);
3018
+ var LegalDocumentVersionSchema = z27.string().regex(/^\d{4}-\d{2}-\d{2}$/, {
3019
+ message: "Version must be in YYYY-MM-DD format"
3020
+ });
3021
+ var OwnerTypeSchema = z28.enum(["qms", "device"]);
3022
+ var DocumentSnapshotSchema = z28.object({
3023
+ documentId: z28.string(),
3024
+ commitSha: z28.string(),
3025
+ documentPath: z28.string(),
3026
+ documentTitle: z28.string(),
3027
+ documentType: z28.string(),
3028
+ previousReleasedCommitSha: z28.string().optional(),
3029
+ changeType: z28.enum(["added", "modified", "unchanged"]),
3030
+ changeDescription: z28.string().optional()
3031
+ });
3032
+ var QmsDocumentTypeSchema = z28.enum([
3033
+ "sop",
3034
+ "policy",
3035
+ "work_instruction",
3036
+ "supplier",
3037
+ "audit_schedule",
3038
+ "audit_report",
3039
+ "management_review",
3040
+ "job_description"
3041
+ ]);
3042
+ var DeviceDocumentTypeSchema = z28.enum([
3043
+ "user_need",
3044
+ "requirement",
3045
+ "architecture",
3046
+ "detailed_design",
3047
+ "test_protocol",
3048
+ "test_report",
3049
+ "usability_risk",
3050
+ "software_risk",
3051
+ "security_risk",
3052
+ "hazardous_situation",
3053
+ "harm",
3054
+ "haz_soe_software",
3055
+ "haz_soe_security",
3056
+ "hazard_category",
3057
+ // Usability engineering (IEC 62366)
3058
+ "usability_plan",
3059
+ "use_specification",
3060
+ "task_analysis",
3061
+ "usability_evaluation",
3062
+ "summative_evaluation",
3063
+ // Risk management (ISO 14971)
3064
+ "risk_management_plan",
3065
+ // Software lifecycle (IEC 62304)
3066
+ "software_development_plan",
3067
+ "software_maintenance_plan",
3068
+ "soup_register",
3069
+ // Problem resolution (IEC 62304 §9)
3070
+ "anomaly",
3071
+ // Cybersecurity (IEC 81001-5-1)
3072
+ "cybersecurity_plan",
3073
+ "sbom",
3074
+ // Clinical (MDR / FDA)
3075
+ "clinical_evaluation_plan",
3076
+ "clinical_evaluation_report",
3077
+ // Post-market surveillance (MDR Art. 83)
3078
+ "post_market_surveillance_plan",
3079
+ "post_market_feedback",
3080
+ // Labeling (MDR Annex I)
3081
+ "labeling",
3082
+ // Product (ISO 13485 §7.3)
3083
+ "product_development_plan",
3084
+ "intended_use",
3085
+ // Software test plan (IEC 62304 §5.7)
3086
+ "software_test_plan"
3087
+ ]);
3088
+ var ReleaseReviewConfigSchema = z29.object({
3089
+ required_departments: z29.array(z29.string().min(1)).min(1),
3090
+ final_approver: z29.string().min(1)
3091
+ });
3092
+ var RepoConfigSchema = z29.object({
3093
+ monorepo: z29.boolean().optional(),
3094
+ device: z29.object({
3095
+ name: z29.string().min(1),
3096
+ safety_class: z29.enum(["A", "B", "C"]),
3097
+ classification: z29.object({
3098
+ eu: z29.string().optional(),
3099
+ us: z29.string().optional()
3100
+ }).optional(),
3101
+ udi_device_identifier: z29.string().optional(),
3102
+ deploy_sources: z29.array(
3103
+ z29.object({
3104
+ identifier: z29.string().min(1),
3105
+ label: z29.string().min(1),
3106
+ provider: z29.enum(["github"]),
3107
+ connection_ref: z29.string().min(1)
3108
+ })
3109
+ ).optional()
3110
+ }).optional(),
3111
+ release_review: ReleaseReviewConfigSchema.optional()
3112
+ });
3113
+ var GateOverrideCategorySchema = z31.enum([
3114
+ "unverified_requirements",
3115
+ "unanalyzed_requirements",
3116
+ "error_risk_gaps",
3117
+ "missing_risk_management_plan_reference",
3118
+ "missing_plan_risk_management",
3119
+ "missing_plan_software_development",
3120
+ "missing_plan_cybersecurity",
3121
+ "missing_plan_usability",
3122
+ "missing_plan_clinical_evaluation",
3123
+ "missing_plan_post_market_surveillance",
3124
+ "missing_artifact_soup_register",
3125
+ "missing_artifact_sbom",
3126
+ "missing_artifact_summative_evaluation",
3127
+ "missing_deployment_references",
3128
+ "missing_smoke_test_evidence"
3129
+ ]);
3130
+ var MemberRoleSchema = z31.enum(["admin", "member"]);
3131
+ var MemberStatusSchema = z31.enum(["active", "suspended", "removed"]);
3132
+ var EmailAddressSchema = z31.object({
3133
+ value: z31.string().email(),
3134
+ verified: z31.boolean()
3135
+ });
3136
+ var SyncStatusSchema = z31.enum(["active", "paused", "error"]);
3137
+ var AuditActionSchema = z31.enum([
3138
+ // Organization
3139
+ "organization.created",
3140
+ "organization.settings_updated",
3141
+ "organization.checklist_template_updated",
3142
+ // Members
3143
+ "member.invited",
3144
+ "member.invite_declined",
3145
+ "member.joined",
3146
+ "member.role_changed",
3147
+ "member.removed",
3148
+ "member.password_reset_forced",
3149
+ // Repositories
3150
+ "repository.connected",
3151
+ "repository.disconnected",
3152
+ "repository.sync_triggered",
3153
+ "repository.updated",
3154
+ "repository.assigned",
3155
+ "repository.unassigned",
3156
+ // Documents
3157
+ "document.synced",
3158
+ "document.removed",
3159
+ "document.viewed",
3160
+ // Releases
3161
+ "release.created",
3162
+ "release.updated",
3163
+ "release.deleted",
3164
+ "release.withdrawn",
3165
+ "release.submitted_for_review",
3166
+ "release.signature_added",
3167
+ "release.approved",
3168
+ "release.transferred_to_transfer",
3169
+ "release.transfer_approved",
3170
+ "release.published",
3171
+ "release.checklist_updated",
3172
+ "release.checklist_item_added",
3173
+ "release.checklist_item_deferred",
3174
+ "release.checklist_reordered",
3175
+ "release.checklist_retrigger_confirmed",
3176
+ "release.signing_obligations_rederived",
3177
+ "release.gate_override_added",
3178
+ "release.gate_override_removed",
3179
+ "release.gate_override_invalidated",
3180
+ "release.viewed",
3181
+ "release.artifact_uploaded",
3182
+ "release.evidence_pulled",
3183
+ "release.manual_test_executed",
3184
+ "release.attributions_derived",
3185
+ "release.attribution_dismissed",
3186
+ "release.attribution_restored",
3187
+ // Signatures
3188
+ "signature.created",
3189
+ "signature.attempt_failed",
3190
+ "signature.lockout_triggered",
3191
+ // Signing Requests
3192
+ "signing_request.created",
3193
+ "signing_request.sent",
3194
+ "signing_request.viewed",
3195
+ "signing_request.signed",
3196
+ "signing_request.declined",
3197
+ "signing_request.cancelled",
3198
+ "signing_request.expired",
3199
+ "signing_request.reminded",
3200
+ "signing_request.document_accessed",
3201
+ // Training
3202
+ "training.assigned",
3203
+ "training.completed",
3204
+ "training.started",
3205
+ "training.settings_updated",
3206
+ "training.quiz_submitted",
3207
+ "training.quiz_passed",
3208
+ "training.quiz_failed",
3209
+ "training.overdue_notified",
3210
+ "training.acknowledged",
3211
+ "training.signature_added",
3212
+ "training.instructor_signoff",
3213
+ "training.task_created",
3214
+ "training.viewed",
3215
+ // QMS
3216
+ "qms.created",
3217
+ "qms.updated",
3218
+ "qms.deleted",
3219
+ // Devices
3220
+ "device.created",
3221
+ "device.updated",
3222
+ "device.deleted",
3223
+ // DCOs
3224
+ "dco.created",
3225
+ "dco.updated",
3226
+ "dco.deleted",
3227
+ "dco.submitted",
3228
+ "dco.obligation_fulfilled",
3229
+ "dco.approved",
3230
+ "dco.effective",
3231
+ // CAPAs
3232
+ "capa.created",
3233
+ "capa.updated",
3234
+ "capa.deleted",
3235
+ "capa.investigation_started",
3236
+ "capa.implementation_started",
3237
+ "capa.verification_started",
3238
+ "capa.closed",
3239
+ "capa.cancelled",
3240
+ "capa.action_added",
3241
+ "capa.action_updated",
3242
+ "capa.action_removed",
3243
+ // Complaints
3244
+ "complaint.created",
3245
+ "complaint.updated",
3246
+ "complaint.deleted",
3247
+ "complaint.investigation_started",
3248
+ "complaint.resolved",
3249
+ "complaint.closed",
3250
+ "complaint.cancelled",
3251
+ "complaint.escalated_to_capa",
3252
+ // Nonconformances
3253
+ "nc.created",
3254
+ "nc.updated",
3255
+ "nc.deleted",
3256
+ "nc.investigation_started",
3257
+ "nc.disposition_set",
3258
+ "nc.concession_approved",
3259
+ "nc.closed",
3260
+ "nc.cancelled",
3261
+ "nc.escalated_to_capa",
3262
+ // Actions
3263
+ "action.created",
3264
+ "action.updated",
3265
+ "action.completed",
3266
+ "action.cancelled",
3267
+ "action.deleted",
3268
+ // Legal
3269
+ "billing_terms.accepted",
3270
+ "legal_document.accepted",
3271
+ // Billing
3272
+ "billing.checkout_created",
3273
+ "billing.portal_accessed",
3274
+ "billing.subscription_activated",
3275
+ "billing.subscription_updated",
3276
+ "billing.subscription_canceled",
3277
+ // API Keys
3278
+ "api_key.created",
3279
+ "api_key.revoked",
3280
+ // Risk Management
3281
+ "risk.viewed",
3282
+ // Compliance
3283
+ "compliance_package.requested",
3284
+ "compliance_package.downloaded",
3285
+ "validation_package.downloaded",
3286
+ // ERSD
3287
+ "ersd.created",
3288
+ "ersd.auto_seeded",
3289
+ // Quality Objectives
3290
+ "quality_objective.created",
3291
+ "quality_objective.updated",
3292
+ "quality_objective.value_recorded",
3293
+ "quality_objective.deactivated",
3294
+ // Quality Reviews
3295
+ "quality_review.created",
3296
+ "quality_review.updated",
3297
+ "quality_review.started",
3298
+ "quality_review.completed",
3299
+ "quality_review.cancelled",
3300
+ // Review Types
3301
+ "review_type.created",
3302
+ "review_type.updated",
3303
+ // Components (SOUP/SBOM enrichment)
3304
+ "component.enriched",
3305
+ // Audit
3306
+ "audit_log.exported",
3307
+ // Compiled Record Export
3308
+ "compiled_record_export.requested",
3309
+ "compiled_record_export.completed",
3310
+ "compiled_record_export.failed",
3311
+ "compiled_record_export.downloaded"
3312
+ ]);
3313
+ var ResourceTypeSchema = z31.enum([
3314
+ "organization",
3315
+ "member",
3316
+ "invite",
3317
+ "repository",
3318
+ "document",
3319
+ "release",
3320
+ "signature",
3321
+ "training",
3322
+ "training_task",
3323
+ "training_settings",
3324
+ "qms",
3325
+ "device",
3326
+ "dco",
3327
+ "signing_request",
3328
+ "capa",
3329
+ "complaint",
3330
+ "nonconformance",
3331
+ "action",
3332
+ "legal_acceptance",
3333
+ "subscription",
3334
+ "api_key",
3335
+ "compliance_package",
3336
+ "validation_package",
3337
+ "ersd",
3338
+ "qualityObjective",
3339
+ "qualityReview",
3340
+ "reviewType",
3341
+ "audit_log",
3342
+ "risk_matrix",
3343
+ "component",
3344
+ "compiled_record_export"
3345
+ ]);
3346
+
3347
+ // src/impact/baseline-scan.ts
3348
+ function scanBaselineGaps(rootDir, baseBranch, config) {
3349
+ const worktreeDir = mkdtempSync(join5(tmpdir(), "trace-baseline-"));
3350
+ try {
3351
+ const mergeBase = execSync(`git merge-base HEAD ${baseBranch}`, {
3352
+ cwd: rootDir,
3353
+ encoding: "utf-8",
3354
+ stdio: ["pipe", "pipe", "pipe"]
3355
+ }).trim();
3356
+ if (!mergeBase) {
3357
+ return [];
3358
+ }
3359
+ execSync(`git worktree add --detach ${worktreeDir} ${mergeBase}`, {
3360
+ cwd: rootDir,
3361
+ stdio: "pipe"
3362
+ });
3363
+ const loader = new FilesystemDocumentLoader(worktreeDir, config);
3364
+ const docs = loader.loadDocuments();
3365
+ const links = loader.loadLinks();
3366
+ const bodies = loader.loadBodies();
3367
+ const result = detectAllGaps(docs, links, {
3368
+ bodies,
3369
+ deviceSafetyClass: config.deviceSafetyClass,
3370
+ acceptabilityConfig: DEFAULT_ACCEPTABILITY_CONFIG
3371
+ });
3372
+ return result.gaps;
3373
+ } catch {
3374
+ return [];
3375
+ } finally {
3376
+ try {
3377
+ execSync(`git worktree remove --force ${worktreeDir}`, {
3378
+ cwd: rootDir,
3379
+ stdio: "pipe"
3380
+ });
3381
+ } catch {
3382
+ rmSync(worktreeDir, { recursive: true, force: true });
3383
+ }
3384
+ }
3385
+ }
3386
+
3387
+ // src/traceability/trace-cli.ts
3388
+ import { readFileSync as readFileSync8 } from "fs";
3389
+
3390
+ // src/traceability/graph/parse-docs.ts
3391
+ import { readFileSync as readFileSync6 } from "fs";
3392
+ import { join as join7 } from "path";
3393
+ import yaml3 from "js-yaml";
3394
+
3395
+ // src/traceability/parse-frontmatter.ts
3396
+ import { readFileSync as readFileSync5, readdirSync as readdirSync4 } from "fs";
3397
+ import { join as join6 } from "path";
3398
+ import yaml2 from "js-yaml";
3399
+ var TYPE_PREFIXES = {
3400
+ UN: "UN",
3401
+ PRS: "PRS",
3402
+ SRS: "SRS",
3403
+ HAZ: "HAZ",
3404
+ RISK: "RISK",
3405
+ HLD: "HLD",
3406
+ SDD: "SDD",
3407
+ DDD: "DDD",
3408
+ RA: "RISK"
3409
+ };
3410
+ function parseFrontmatter(filePath) {
3411
+ const content = readFileSync5(filePath, "utf-8");
3412
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
3413
+ if (!fmMatch) {
3414
+ throw new Error(`No frontmatter found in ${filePath}`);
3415
+ }
3416
+ const fields = yaml2.load(fmMatch[1]);
3417
+ const id = fields.id;
3418
+ if (!id) throw new Error(`No 'id' field in frontmatter of ${filePath}`);
3419
+ const type = inferType(id, fields.type);
3420
+ return {
3421
+ id,
3422
+ title: fields.title ?? id,
3423
+ type,
3424
+ derives: parseStringArray(fields.derives),
3425
+ traces_from: parseStringArray(fields.traces_from),
3426
+ traces_to: parseStringArray(fields.traces_to),
3427
+ analyzes: parseStringArray(fields.analyzes),
3428
+ leads_to: parseStringArray(fields.leads_to)
3429
+ };
3430
+ }
3431
+ function parseAllDocsInDir(dir, pattern) {
3432
+ const results = [];
3433
+ try {
3434
+ const entries = readdirSync4(dir, { withFileTypes: true, recursive: true });
3435
+ for (const entry of entries) {
3436
+ if (entry.isFile() && pattern.test(entry.name)) {
3437
+ try {
3438
+ const fullPath = join6(entry.parentPath, entry.name);
3439
+ results.push(parseFrontmatter(fullPath));
3440
+ } catch {
3441
+ }
3442
+ }
3443
+ }
3444
+ } catch {
3445
+ }
3446
+ return results;
3447
+ }
3448
+ function inferType(id, explicitType) {
3449
+ if (explicitType) {
3450
+ const normalized = explicitType.toLowerCase();
3451
+ if (normalized.includes("risk")) return "RISK";
3452
+ if (normalized.includes("hazard")) return "HAZ";
3453
+ }
3454
+ const prefix = id.split("-")[0];
3455
+ return TYPE_PREFIXES[prefix] ?? "SDD";
3456
+ }
3457
+ function parseStringArray(value) {
3458
+ if (!value) return void 0;
3459
+ if (Array.isArray(value)) return value.map(String);
3460
+ if (typeof value === "string") return [value];
3461
+ return void 0;
3462
+ }
3463
+
3464
+ // src/traceability/graph/types.ts
3465
+ function createDocumentNode(props) {
3466
+ return { nodeKind: "document", ...props };
3467
+ }
3468
+ function createRequirementNode(props) {
3469
+ return { nodeKind: "requirement", ...props };
3470
+ }
3471
+ function createTestRefNode(props) {
3472
+ return {
3473
+ nodeKind: "test_ref",
3474
+ id: `test:${props.file}:${props.requirementId}:${props.name}`,
3475
+ ...props
3476
+ };
3477
+ }
3478
+ function createEdge(props) {
3479
+ return { ...props };
3480
+ }
3481
+
3482
+ // src/traceability/graph/parse-docs.ts
3483
+ var DIR_SCANS = [
3484
+ { subdir: "docs/product/user-needs", pattern: /^UN-.*\.md$/ },
3485
+ { subdir: "docs/product/requirements", pattern: /^PRS-.*\.md$/ },
3486
+ { subdir: "docs/software/requirements", pattern: /^SRS-.*\.md$/ },
3487
+ { subdir: "docs/software/architecture", pattern: /^(?:HLD|DDD)-.*\.md$/ },
3488
+ { subdir: "docs/software/design", pattern: /^SDD-.*\.md$/ },
3489
+ { subdir: "docs/usability/risks", pattern: /^RISK-.*\.md$/ },
3490
+ { subdir: "docs/risk/analysis", pattern: /^(?:RA|RISK)-.*\.md$/ },
3491
+ { subdir: "docs/usability/task-analyses", pattern: /^TA-.*\.md$/ }
3492
+ ];
3493
+ function parseAllDocuments(rootDir) {
3494
+ const documentNodes = scanDocumentNodes(rootDir);
3495
+ const requirementNodes = extractRequirementNodes(rootDir, documentNodes);
3496
+ const edges = extractAllEdges(rootDir, documentNodes);
3497
+ return { documentNodes, requirementNodes, edges };
3498
+ }
3499
+ function scanDocumentNodes(rootDir) {
3500
+ const nodeMap = /* @__PURE__ */ new Map();
3501
+ for (const { subdir, pattern } of DIR_SCANS) {
3502
+ const dir = join7(rootDir, subdir);
3503
+ const docs = parseAllDocsInDir(dir, pattern);
3504
+ for (const doc of docs) {
3505
+ if (nodeMap.has(doc.id)) continue;
3506
+ const filePath = resolveFilePath(rootDir, subdir, doc);
3507
+ const frontmatter = readRawFrontmatter(join7(rootDir, filePath));
3508
+ nodeMap.set(
3509
+ doc.id,
3510
+ createDocumentNode({
3511
+ id: doc.id,
3512
+ type: doc.type,
3513
+ title: doc.title,
3514
+ filePath,
3515
+ frontmatter: frontmatter ?? { id: doc.id }
3516
+ })
3517
+ );
3518
+ }
3519
+ }
3520
+ return [...nodeMap.values()];
3521
+ }
3522
+ function resolveFilePath(_rootDir, subdir, doc) {
3523
+ return `${subdir}/${doc.id}.md`;
3524
+ }
3525
+ function readRawFrontmatter(filePath) {
3526
+ try {
3527
+ const content = readFileSync6(filePath, "utf-8");
3528
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
3529
+ if (!match) return null;
3530
+ return yaml3.load(match[1]) ?? null;
3531
+ } catch {
3532
+ return null;
3533
+ }
3534
+ }
3535
+ function extractRequirementNodes(rootDir, documentNodes) {
3536
+ const reqNodes = [];
3537
+ const prsAndSrs = documentNodes.filter((n) => n.type === "PRS" || n.type === "SRS");
3538
+ for (const doc of prsAndSrs) {
3539
+ const level = doc.type;
3540
+ const content = readFileContent(join7(rootDir, doc.filePath));
3541
+ if (!content) continue;
3542
+ const reqs = parseRequirementsFromMarkdown(content, doc.id, level);
3543
+ for (const req of reqs) {
3544
+ reqNodes.push(
3545
+ createRequirementNode({
3546
+ id: req.id,
3547
+ documentId: req.document,
3548
+ verificationMethod: req.verificationMethod,
3549
+ criteria: req.criteria
3550
+ })
3551
+ );
3552
+ }
3553
+ }
3554
+ return reqNodes;
3555
+ }
3556
+ function readFileContent(filePath) {
3557
+ try {
3558
+ return readFileSync6(filePath, "utf-8");
3559
+ } catch {
3560
+ return null;
3561
+ }
3562
+ }
3563
+ function extractAllEdges(_rootDir, documentNodes) {
3564
+ const edgeSet = new EdgeDeduplicator();
3565
+ extractFrontmatterEdges(documentNodes, edgeSet);
3566
+ extractArchitectureEdges(documentNodes, edgeSet);
3567
+ return edgeSet.toArray();
3568
+ }
3569
+ function extractFrontmatterEdges(nodes, edgeSet) {
3570
+ for (const node of nodes) {
3571
+ const fm = node.frontmatter;
3572
+ addEdgesFromField(node.id, fm.derives, "derives_from", edgeSet);
3573
+ addEdgesFromField(node.id, fm.traces_from, "traces_from", edgeSet);
3574
+ addEdgesFromField(node.id, fm.traces_to, "traces_to", edgeSet);
3575
+ addEdgesFromField(node.id, fm.analyzes, "analyzes", edgeSet);
3576
+ addEdgesFromField(node.id, fm.leads_to, "leads_to", edgeSet);
3577
+ }
3578
+ }
3579
+ function addEdgesFromField(sourceId, field, linkType, edgeSet) {
3580
+ const targets = toStringArray(field);
3581
+ for (const targetId of targets) {
3582
+ edgeSet.add(createEdge({ from: sourceId, to: targetId, linkType }));
3583
+ }
3584
+ }
3585
+ function extractArchitectureEdges(nodes, edgeSet) {
3586
+ for (const node of nodes) {
3587
+ const fm = node.frontmatter;
3588
+ const implementsList = toStringArray(fm.implements);
3589
+ for (const targetId of implementsList) {
3590
+ edgeSet.add(createEdge({ from: node.id, to: targetId, linkType: "implements" }));
3591
+ }
3592
+ if (typeof fm.parent_item === "string" && fm.parent_item) {
3593
+ edgeSet.add(createEdge({ from: fm.parent_item, to: node.id, linkType: "parent_of" }));
3594
+ }
3595
+ if (typeof fm.software_item === "string" && fm.software_item) {
3596
+ edgeSet.add(createEdge({ from: node.id, to: fm.software_item, linkType: "software_item" }));
3597
+ }
3598
+ }
3599
+ }
3600
+ function toStringArray(value) {
3601
+ if (!value) return [];
3602
+ if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
3603
+ if (typeof value === "string") return [value];
3604
+ return [];
3605
+ }
3606
+ var EdgeDeduplicator = class {
3607
+ seen = /* @__PURE__ */ new Set();
3608
+ edges = [];
3609
+ add(edge) {
3610
+ const key = `${edge.from}|${edge.to}|${edge.linkType}`;
3611
+ if (this.seen.has(key)) return;
3612
+ this.seen.add(key);
3613
+ this.edges.push(edge);
3614
+ }
3615
+ toArray() {
3616
+ return [...this.edges];
3617
+ }
3618
+ };
3619
+
3620
+ // src/traceability/graph/parse-tests.ts
3621
+ import { readFileSync as readFileSync7, readdirSync as readdirSync5 } from "fs";
3622
+ import { join as join8, relative as relative3 } from "path";
3623
+ function parseAllTests(rootDir) {
3624
+ const testRefNodes = [];
3625
+ const orphanScenarios = [];
3626
+ scanE2eFeatures(rootDir, testRefNodes, orphanScenarios);
3627
+ scanUnitTests(rootDir, testRefNodes);
3628
+ return { testRefNodes, orphanScenarios };
3629
+ }
3630
+ function scanE2eFeatures(rootDir, nodes, orphans) {
3631
+ const featureDir = join8(rootDir, "e2e/features");
3632
+ const featureFiles = globFiles2(featureDir, /\.feature$/);
3633
+ for (const file of featureFiles) {
3634
+ const content = readFileSync7(file, "utf-8");
3635
+ const relPath = relative3(rootDir, file);
3636
+ const refs = extractReqTagsFromFeature(content, relPath);
3637
+ for (const ref of refs) {
3638
+ nodes.push(
3639
+ createTestRefNode({
3640
+ requirementId: ref.requirementId,
3641
+ testType: ref.testType,
3642
+ file: ref.file,
3643
+ name: ref.name
3644
+ })
3645
+ );
3646
+ }
3647
+ orphans.push(...findOrphanScenarios(content, relPath));
3648
+ }
3649
+ }
3650
+ function scanUnitTests(rootDir, nodes) {
3651
+ const testDirs = [join8(rootDir, "packages"), join8(rootDir, "apps"), join8(rootDir, "scripts")];
3652
+ const testPattern = /\.(test|spec)\.tsx?$/;
3653
+ for (const dir of testDirs) {
3654
+ const testFiles = globFiles2(dir, testPattern);
3655
+ for (const file of testFiles) {
3656
+ const content = readFileSync7(file, "utf-8");
3657
+ const relPath = relative3(rootDir, file);
3658
+ const refs = extractReqMarkersFromTestFile(content, relPath);
3659
+ for (const ref of refs) {
3660
+ nodes.push(
3661
+ createTestRefNode({
3662
+ requirementId: ref.requirementId,
3663
+ testType: ref.testType,
3664
+ file: ref.file,
3665
+ name: ref.name
3666
+ })
3667
+ );
3668
+ }
3669
+ }
3670
+ }
3671
+ }
3672
+ function globFiles2(dir, pattern) {
3673
+ const results = [];
3674
+ try {
3675
+ const entries = readdirSync5(dir, { withFileTypes: true, recursive: true });
3676
+ for (const entry of entries) {
3677
+ if (entry.isFile() && pattern.test(entry.name)) {
3678
+ const fullPath = join8(entry.parentPath, entry.name);
3679
+ results.push(fullPath);
3680
+ }
3681
+ }
3682
+ } catch {
3683
+ }
3684
+ return results;
3685
+ }
3686
+
3687
+ // src/traceability/graph/build-graph.ts
3688
+ function buildGraph(nodes, edges) {
3689
+ const nodeMap = buildNodeMap(nodes);
3690
+ const outgoing = /* @__PURE__ */ new Map();
3691
+ const incoming = /* @__PURE__ */ new Map();
3692
+ initializeAdjacencyEntries(nodeMap, outgoing, incoming);
3693
+ populateAdjacencyLists(edges, outgoing, incoming);
3694
+ return { nodes: nodeMap, edges, outgoing, incoming };
3695
+ }
3696
+ function buildNodeMap(nodes) {
3697
+ const map = /* @__PURE__ */ new Map();
3698
+ for (const node of nodes) {
3699
+ map.set(node.id, node);
3700
+ }
3701
+ return map;
3702
+ }
3703
+ function initializeAdjacencyEntries(nodeMap, outgoing, incoming) {
3704
+ for (const id of nodeMap.keys()) {
3705
+ outgoing.set(id, []);
3706
+ incoming.set(id, []);
3707
+ }
3708
+ }
3709
+ function populateAdjacencyLists(edges, outgoing, incoming) {
3710
+ for (const edge of edges) {
3711
+ appendToList(outgoing, edge.from, edge);
3712
+ appendToList(incoming, edge.to, edge);
3713
+ }
3714
+ }
3715
+ function appendToList(map, key, edge) {
3716
+ const list = map.get(key);
3717
+ if (list) {
3718
+ list.push(edge);
3719
+ } else {
3720
+ map.set(key, [edge]);
3721
+ }
3722
+ }
3723
+
3724
+ // src/traceability/graph/walk.ts
3725
+ var strategies = /* @__PURE__ */ new Map();
3726
+ function registerStrategy(name, fn) {
3727
+ strategies.set(name, fn);
3728
+ }
3729
+ function walkStrategy(name, graph, entryNode) {
3730
+ const fn = strategies.get(name);
3731
+ if (!fn) {
3732
+ throw new Error(`Unknown strategy: "${name}"`);
3733
+ }
3734
+ return fn(graph, entryNode);
3735
+ }
3736
+ function walkStrategies(names, graph, entryNode) {
3737
+ const seen = /* @__PURE__ */ new Set();
3738
+ const result = [];
3739
+ for (const name of names) {
3740
+ for (const node of walkStrategy(name, graph, entryNode)) {
3741
+ if (!seen.has(node.id)) {
3742
+ seen.add(node.id);
3743
+ result.push(node);
3744
+ }
3745
+ }
3746
+ }
3747
+ return result;
3748
+ }
3749
+ function followOutgoing(graph, sourceIds, linkTypes, visited) {
3750
+ const result = [];
3751
+ const typeSet = new Set(linkTypes);
3752
+ for (const sourceId of sourceIds) {
3753
+ const edges = graph.outgoing.get(sourceId) ?? [];
3754
+ for (const edge of edges) {
3755
+ if (typeSet.has(edge.linkType) && !visited.has(edge.to)) {
3756
+ const node = graph.nodes.get(edge.to);
3757
+ if (node) {
3758
+ visited.add(edge.to);
3759
+ result.push(node);
3760
+ }
3761
+ }
3762
+ }
3763
+ }
3764
+ return result;
3765
+ }
3766
+ function followIncoming(graph, targetIds, linkTypes, visited) {
3767
+ const result = [];
3768
+ const typeSet = new Set(linkTypes);
3769
+ for (const targetId of targetIds) {
3770
+ const edges = graph.incoming.get(targetId) ?? [];
3771
+ for (const edge of edges) {
3772
+ if (typeSet.has(edge.linkType) && !visited.has(edge.from)) {
3773
+ const node = graph.nodes.get(edge.from);
3774
+ if (node) {
3775
+ visited.add(edge.from);
3776
+ result.push(node);
3777
+ }
3778
+ }
3779
+ }
3780
+ }
3781
+ return result;
3782
+ }
3783
+ function onlyDocumentNodes(nodes) {
3784
+ return nodes.filter((n) => n.nodeKind === "document");
3785
+ }
3786
+ var ARCH_DOC_TYPES = /* @__PURE__ */ new Set(["HLD", "SDD", "DDD"]);
3787
+ function isArchDocType(type) {
3788
+ return ARCH_DOC_TYPES.has(type);
3789
+ }
3790
+ function requirementsChain(graph, entryNode) {
3791
+ const visited = /* @__PURE__ */ new Set([entryNode.id]);
3792
+ const result = [];
3793
+ const srsNodes = followOutgoing(graph, [entryNode.id], ["implements"], visited);
3794
+ result.push(...onlyDocumentNodes(srsNodes));
3795
+ const srsIds = srsNodes.map((n) => n.id);
3796
+ const prsNodes = followOutgoing(graph, srsIds, ["traces_from", "derives_from"], visited);
3797
+ result.push(...onlyDocumentNodes(prsNodes));
3798
+ const prsIds = prsNodes.map((n) => n.id);
3799
+ const unNodes = followOutgoing(graph, prsIds, ["traces_from", "derives_from"], visited);
3800
+ result.push(...onlyDocumentNodes(unNodes));
3801
+ return result;
3802
+ }
3803
+ function riskChain(graph, entryNode) {
3804
+ const visited = /* @__PURE__ */ new Set([entryNode.id]);
3805
+ const result = [];
3806
+ const hazNodes = followIncoming(graph, [entryNode.id], ["software_item"], visited);
3807
+ result.push(...onlyDocumentNodes(hazNodes));
3808
+ const hazIds = hazNodes.map((n) => n.id);
3809
+ const hsNodes = followOutgoing(graph, hazIds, ["leads_to"], visited);
3810
+ result.push(...onlyDocumentNodes(hsNodes));
3811
+ const hsIds = hsNodes.map((n) => n.id);
3812
+ const harmNodes = followOutgoing(graph, hsIds, ["results_in"], visited);
3813
+ result.push(...onlyDocumentNodes(harmNodes));
3814
+ const riskNodes = followIncoming(graph, [entryNode.id], ["analyzes"], visited);
3815
+ result.push(...onlyDocumentNodes(riskNodes));
3816
+ return result;
3817
+ }
3818
+ function architectureDocs(graph, entryNode) {
3819
+ const visited = /* @__PURE__ */ new Set([entryNode.id]);
3820
+ const collected = [];
3821
+ const fromImplements = followIncoming(graph, [entryNode.id], ["implements"], visited);
3822
+ collected.push(...onlyDocumentNodes(fromImplements));
3823
+ const toImplements = followOutgoing(graph, [entryNode.id], ["implements"], visited);
3824
+ collected.push(...onlyDocumentNodes(toImplements));
3825
+ const fromParent = followIncoming(graph, [entryNode.id], ["parent_of"], visited);
3826
+ collected.push(...onlyDocumentNodes(fromParent));
3827
+ const toParent = followOutgoing(graph, [entryNode.id], ["parent_of"], visited);
3828
+ collected.push(...onlyDocumentNodes(toParent));
3829
+ return collected.filter((n) => isArchDocType(n.type));
3830
+ }
3831
+ function riskNeighbors(graph, entryNode) {
3832
+ const visited = /* @__PURE__ */ new Set([entryNode.id]);
3833
+ const riskNodes = followIncoming(graph, [entryNode.id], ["analyzes"], visited);
3834
+ return onlyDocumentNodes(riskNodes);
3835
+ }
3836
+ function fullGraphValidation(graph, _entryNode) {
3837
+ const brokenNodeIds = /* @__PURE__ */ new Set();
3838
+ for (const edge of graph.edges) {
3839
+ const fromExists = graph.nodes.has(edge.from);
3840
+ const toExists = graph.nodes.has(edge.to);
3841
+ if (!fromExists || !toExists) {
3842
+ if (fromExists) brokenNodeIds.add(edge.from);
3843
+ if (toExists) brokenNodeIds.add(edge.to);
3844
+ }
3845
+ }
3846
+ const result = [];
3847
+ for (const id of brokenNodeIds) {
3848
+ const node = graph.nodes.get(id);
3849
+ if (node) result.push(node);
3850
+ }
3851
+ return result;
3852
+ }
3853
+ registerStrategy("requirements_chain", requirementsChain);
3854
+ registerStrategy("risk_chain", riskChain);
3855
+ registerStrategy("architecture_docs", architectureDocs);
3856
+ registerStrategy("risk_neighbors", riskNeighbors);
3857
+ registerStrategy("full_graph_validation", fullGraphValidation);
3858
+
3859
+ // src/traceability/graph/coverage-query.ts
3860
+ function queryCoverage(graph, documentId) {
3861
+ const testableRequirements = findTestableRequirements(graph, documentId);
3862
+ const coveredIds = findCoveredRequirementIds(graph);
3863
+ const covered = [];
3864
+ const uncovered = [];
3865
+ for (const req of testableRequirements) {
3866
+ if (coveredIds.has(req.id)) {
3867
+ covered.push(req.id);
3868
+ } else {
3869
+ uncovered.push(req.id);
3870
+ }
3871
+ }
3872
+ return { covered, uncovered, total: testableRequirements.length };
3873
+ }
3874
+ function isTestableMethod(verificationMethod) {
3875
+ return verificationMethod.includes("Test");
3876
+ }
3877
+ function findTestableRequirements(graph, documentId) {
3878
+ const results = [];
3879
+ for (const node of graph.nodes.values()) {
3880
+ if (node.nodeKind === "requirement" && node.documentId === documentId && isTestableMethod(node.verificationMethod)) {
3881
+ results.push(node);
3882
+ }
3883
+ }
3884
+ return results;
3885
+ }
3886
+ function findCoveredRequirementIds(graph) {
3887
+ const ids = /* @__PURE__ */ new Set();
3888
+ for (const node of graph.nodes.values()) {
3889
+ if (node.nodeKind === "test_ref") {
3890
+ ids.add(node.requirementId);
3891
+ }
3892
+ }
3893
+ return ids;
3894
+ }
3895
+
3896
+ // src/traceability/graph/resolve.ts
3897
+ import { execSync as execSync2 } from "child_process";
3898
+ import { minimatch as minimatch2 } from "minimatch";
3899
+ function resolveChangedFiles(changedFiles, config) {
3900
+ if (changedFiles.length === 0) return [];
3901
+ const matched = /* @__PURE__ */ new Map();
3902
+ for (const file of changedFiles) {
3903
+ matchFileToSoftwareItems(file, config.software_items, matched);
3904
+ }
3905
+ return Array.from(matched.values());
3906
+ }
3907
+ function matchFileToSoftwareItems(file, items, matched) {
3908
+ for (const [name, entry] of Object.entries(items)) {
3909
+ if (matched.has(name)) continue;
3910
+ if (!isFileInEntry(file, entry)) continue;
3911
+ matched.set(name, {
3912
+ softwareItem: name,
3913
+ domain: entry.domain,
3914
+ architecture: entry.architecture
3915
+ });
3916
+ }
3917
+ }
3918
+ function isFileInEntry(file, entry) {
3919
+ return entry.code_paths.some((pattern) => minimatch2(file, pattern));
3920
+ }
3921
+ function getChangedFilesFromGit() {
3922
+ const branch = execSync2("git branch --show-current", { encoding: "utf-8" }).trim();
3923
+ const command = isOnMain(branch) ? "git diff --name-only HEAD~1" : "git diff --name-only main...HEAD";
3924
+ const output = execSync2(command, { encoding: "utf-8" }).trim();
3925
+ if (!output) return [];
3926
+ return output.split("\n");
3927
+ }
3928
+ function isOnMain(branch) {
3929
+ return branch === "main" || branch === "";
3930
+ }
3931
+
3932
+ // src/traceability/graph/format.ts
3933
+ var PHASE_RULES = {
3934
+ design: {
3935
+ description: "Understanding the domain before designing",
3936
+ strategies: ["requirements_chain", "risk_chain", "test_coverage"],
3937
+ output: "context",
3938
+ prompts: [
3939
+ "Has a task analysis been done for this area?",
3940
+ "Are existing risk controls still valid for this change?",
3941
+ "Which user needs drive this domain?"
3942
+ ]
3943
+ },
3944
+ assess: {
3945
+ description: "Predicting impact of a planned change",
3946
+ strategies: ["requirements_chain", "risk_chain", "test_coverage"],
3947
+ output: "impact_table",
3948
+ checks: [
3949
+ "Every touched SRS must have risk coverage",
3950
+ "Every new SRS requirement needs a verification method"
3951
+ ]
3952
+ },
3953
+ implement: {
3954
+ description: "What docs are stale given code changes",
3955
+ strategies: ["requirements_chain", "architecture_docs", "risk_neighbors"],
3956
+ output: "update_list"
3957
+ },
3958
+ verify: {
3959
+ description: "CI gate -- are all links intact?",
3960
+ strategies: ["full_graph_validation"],
3961
+ output: "gap_report",
3962
+ checks: [
3963
+ "Every SRS with verification_method has @req coverage",
3964
+ "Every SRS has risk analysis",
3965
+ "Every test has @req tag",
3966
+ "No broken references"
3967
+ ]
3968
+ }
3969
+ };
3970
+ var NODE_GROUP_ORDER = [
3971
+ { key: "un", label: "User Needs", types: ["UN"] },
3972
+ { key: "prs", label: "Product Requirements", types: ["PRS"] },
3973
+ { key: "srs", label: "Software Requirements", types: ["SRS"] },
3974
+ { key: "risk", label: "Risk Analysis", types: ["RISK", "HAZ-SW", "HAZ", "HS", "HARM"] },
3975
+ { key: "arch", label: "Architecture", types: ["HLD", "SDD", "DDD"] }
3976
+ ];
3977
+ function onlyDocumentNodes2(nodes) {
3978
+ return nodes.filter((n) => n.nodeKind === "document");
3979
+ }
3980
+ function groupByType(docNodes) {
3981
+ const groups = /* @__PURE__ */ new Map();
3982
+ for (const group of NODE_GROUP_ORDER) {
3983
+ const matching = docNodes.filter((n) => group.types.includes(n.type));
3984
+ if (matching.length > 0) {
3985
+ groups.set(group.key, matching);
3986
+ }
3987
+ }
3988
+ return groups;
3989
+ }
3990
+ function findGroupLabel(key) {
3991
+ return NODE_GROUP_ORDER.find((g) => g.key === key)?.label ?? key;
3992
+ }
3993
+ function getSrsCoverage(graph, srsNodes) {
3994
+ return srsNodes.map((srs) => {
3995
+ const cov = queryCoverage(graph, srs.id);
3996
+ return {
3997
+ documentId: srs.id,
3998
+ covered: cov.covered.length,
3999
+ total: cov.total,
4000
+ uncovered: cov.uncovered
4001
+ };
4002
+ });
4003
+ }
4004
+ function formatContext(input) {
4005
+ const { graph, resolvedItems, walkedNodes, domain } = input;
4006
+ const lines = [];
4007
+ const domainLabel = domain ?? "unknown";
4008
+ lines.push(`=== Traceability Context: ${domainLabel} ===`);
4009
+ lines.push("");
4010
+ appendSoftwareItemsSummary(lines, resolvedItems);
4011
+ const docNodes = onlyDocumentNodes2(walkedNodes);
4012
+ if (docNodes.length === 0) {
4013
+ lines.push("No traceability nodes found.");
4014
+ lines.push("");
4015
+ appendDesignPrompts(lines);
4016
+ return lines.join("\n");
4017
+ }
4018
+ const grouped = groupByType(docNodes);
4019
+ for (const [key, docs] of grouped) {
4020
+ if (key === "srs") {
4021
+ appendSrsGroup(lines, graph, docs);
4022
+ } else {
4023
+ appendDocGroup(lines, key, docs);
4024
+ }
4025
+ }
4026
+ appendDesignPrompts(lines);
4027
+ appendContextGaps(lines, graph, grouped);
4028
+ return lines.join("\n");
4029
+ }
4030
+ function appendSoftwareItemsSummary(lines, items) {
4031
+ if (items.length === 0) return;
4032
+ const itemNames = items.map((i) => i.softwareItem).join(", ");
4033
+ lines.push(`Software Items: ${itemNames}`);
4034
+ const archIds = [...new Set(items.flatMap((i) => i.architecture))];
4035
+ if (archIds.length > 0) {
4036
+ lines.push(`Architecture: ${archIds.join(", ")}`);
4037
+ }
4038
+ lines.push("");
4039
+ }
4040
+ function appendDocGroup(lines, key, docs) {
4041
+ const label = findGroupLabel(key);
4042
+ lines.push(`${label} (${docs.length}):`);
4043
+ for (const doc of docs) {
4044
+ lines.push(` ${doc.id} ${doc.title}`);
4045
+ }
4046
+ lines.push("");
4047
+ }
4048
+ function appendSrsGroup(lines, graph, docs) {
4049
+ const coverages = getSrsCoverage(graph, docs);
4050
+ lines.push(`Software Requirements (${docs.length}):`);
4051
+ for (let i = 0; i < docs.length; i++) {
4052
+ const doc = docs[i];
4053
+ const cov = coverages[i];
4054
+ const covLabel = cov.total > 0 ? ` test_coverage: ${cov.covered}/${cov.total}` : "";
4055
+ const check = cov.uncovered.length === 0 && cov.total > 0 ? " \u2713" : "";
4056
+ lines.push(` ${doc.id} ${doc.title}${covLabel}${check}`);
4057
+ }
4058
+ lines.push("");
4059
+ }
4060
+ function appendDesignPrompts(lines) {
4061
+ const designPhase = PHASE_RULES["design"];
4062
+ if (!designPhase?.prompts) return;
4063
+ lines.push("Prompts:");
4064
+ for (const prompt of designPhase.prompts) {
4065
+ lines.push(` \u26A0 ${prompt}`);
4066
+ }
4067
+ lines.push("");
4068
+ }
4069
+ function appendContextGaps(lines, graph, grouped) {
4070
+ const srsNodes = grouped.get("srs") ?? [];
4071
+ const coverages = getSrsCoverage(graph, srsNodes);
4072
+ const gapEntries = [];
4073
+ for (const cov of coverages) {
4074
+ for (const uncoveredId of cov.uncovered) {
4075
+ gapEntries.push(` MISSING_TEST_COVERAGE ${uncoveredId}`);
4076
+ }
4077
+ }
4078
+ if (gapEntries.length > 0) {
4079
+ lines.push(`Gaps (${gapEntries.length}):`);
4080
+ lines.push(...gapEntries);
4081
+ lines.push("");
4082
+ }
4083
+ }
4084
+ function formatUpdateList(input) {
4085
+ const { resolvedItems, walkedNodes } = input;
4086
+ const lines = [];
4087
+ const docNodes = onlyDocumentNodes2(walkedNodes);
4088
+ lines.push(`=== Trace Impact: ${docNodes.length} docs may need updates ===`);
4089
+ lines.push("");
4090
+ if (docNodes.length === 0) {
4091
+ return lines.join("\n");
4092
+ }
4093
+ lines.push("| Document | Type | Reason |");
4094
+ lines.push("|----------|------|--------|");
4095
+ for (const doc of docNodes) {
4096
+ const reason = buildUpdateReason(doc, resolvedItems);
4097
+ lines.push(`| ${doc.id} | ${doc.type} | ${reason} |`);
4098
+ }
4099
+ lines.push("");
4100
+ if (resolvedItems.length > 0) {
4101
+ const itemNames = resolvedItems.map((i) => i.softwareItem).join(", ");
4102
+ lines.push(`Software items touched: ${itemNames}`);
4103
+ lines.push("");
4104
+ }
4105
+ return lines.join("\n");
4106
+ }
4107
+ function buildUpdateReason(doc, items) {
4108
+ const itemNames = items.map((i) => i.softwareItem);
4109
+ const itemLabel = itemNames.length > 0 ? itemNames.join(", ") : "affected scope";
4110
+ switch (doc.type) {
4111
+ case "SRS":
4112
+ return `implements ${itemLabel}`;
4113
+ case "HLD":
4114
+ return `architecture for ${itemLabel}`;
4115
+ case "SDD":
4116
+ return `design for ${itemLabel}`;
4117
+ case "RISK":
4118
+ return `analyzes requirements -- review if risk posture changed`;
4119
+ default:
4120
+ return `related to ${itemLabel}`;
4121
+ }
4122
+ }
4123
+ function formatImpactTable(input) {
4124
+ const { graph, resolvedItems, walkedNodes } = input;
4125
+ const lines = [];
4126
+ const docNodes = onlyDocumentNodes2(walkedNodes);
4127
+ lines.push("## DRM Assessment (auto-generated)");
4128
+ lines.push("");
4129
+ appendArchSection(lines, docNodes, resolvedItems);
4130
+ appendReqSection(lines, graph, docNodes);
4131
+ return lines.join("\n");
4132
+ }
4133
+ function appendArchSection(lines, docNodes, items) {
4134
+ const archDocs = docNodes.filter((n) => ["HLD", "SDD", "DDD"].includes(n.type));
4135
+ const itemLabel = items.map((i) => i.softwareItem).join(", ");
4136
+ lines.push("<!-- DRM:ARCH_START -->");
4137
+ if (archDocs.length === 0) {
4138
+ lines.push("No architecture documents affected.");
4139
+ } else {
4140
+ lines.push("| Action | Document | Section | Description |");
4141
+ lines.push("|--------|----------|---------|-------------|");
4142
+ for (const doc of archDocs) {
4143
+ const description = itemLabel ? `${doc.title} for ${itemLabel}` : doc.title;
4144
+ lines.push(`| UPDATE | ${doc.filePath} | - | ${description} |`);
4145
+ }
4146
+ }
4147
+ lines.push("<!-- DRM:ARCH_END -->");
4148
+ lines.push("");
4149
+ }
4150
+ function appendReqSection(lines, graph, docNodes) {
4151
+ const srsNodes = docNodes.filter((n) => n.type === "SRS");
4152
+ lines.push("<!-- DRM:REQ_START -->");
4153
+ if (srsNodes.length === 0) {
4154
+ lines.push("No requirements affected.");
4155
+ } else {
4156
+ lines.push("| Document | Coverage | Gaps |");
4157
+ lines.push("|----------|----------|------|");
4158
+ for (const srs of srsNodes) {
4159
+ const cov = queryCoverage(graph, srs.id);
4160
+ const covLabel = `${cov.covered.length}/${cov.total}`;
4161
+ const gapLabel = cov.uncovered.length > 0 ? cov.uncovered.join(", ") : "-";
4162
+ lines.push(`| ${srs.id} | ${covLabel} | ${gapLabel} |`);
4163
+ }
4164
+ }
4165
+ lines.push("<!-- DRM:REQ_END -->");
4166
+ lines.push("");
4167
+ }
4168
+ function formatGapReport(input) {
4169
+ const { gaps } = input;
4170
+ const lines = [];
4171
+ lines.push("# Requirements Traceability Report");
4172
+ lines.push("");
4173
+ lines.push(`**Gaps found:** ${gaps.length}`);
4174
+ lines.push("");
4175
+ if (gaps.length === 0) {
4176
+ lines.push("## No gaps found");
4177
+ lines.push("");
4178
+ lines.push("All traceability links are intact.");
4179
+ return lines.join("\n");
4180
+ }
4181
+ const categories = [...new Set(gaps.map((g) => g.category))];
4182
+ for (const cat of categories) {
4183
+ const catGaps = gaps.filter((g) => g.category === cat);
4184
+ const heading = cat.replace(/_/g, " ");
4185
+ lines.push(`## ${heading}`);
4186
+ lines.push("");
4187
+ for (const gap of catGaps) {
4188
+ lines.push(`- ${gap.message}`);
4189
+ }
4190
+ lines.push("");
4191
+ }
4192
+ return lines.join("\n");
4193
+ }
4194
+
4195
+ // src/traceability/trace-cli.ts
4196
+ function constructGraph(rootDir) {
4197
+ const docResult = parseAllDocuments(rootDir);
4198
+ const testResult = parseAllTests(rootDir);
4199
+ const allNodes = [
4200
+ ...docResult.documentNodes,
4201
+ ...docResult.requirementNodes,
4202
+ ...testResult.testRefNodes
4203
+ ];
4204
+ return buildGraph(allNodes, docResult.edges);
4205
+ }
4206
+ function configToSoftwareItemsMap(items) {
4207
+ const result = {};
4208
+ for (const [name, config] of Object.entries(items)) {
4209
+ result[name] = {
4210
+ code_paths: config.codePaths,
4211
+ architecture: config.architecture,
4212
+ domain: config.domain
4213
+ };
4214
+ }
4215
+ return { software_items: result };
4216
+ }
4217
+ function requireSoftwareItems(items) {
4218
+ if (!items || Object.keys(items).length === 0) {
4219
+ throw new Error(
4220
+ "No software_items configuration found. Create a pactosigna-trace.yaml with a software_items section."
4221
+ );
4222
+ }
4223
+ return configToSoftwareItemsMap(items);
4224
+ }
4225
+ function findArchitectureNodesForDomain(graph, domain, config) {
4226
+ const resolvedItems = [];
4227
+ const archIdSet = /* @__PURE__ */ new Set();
4228
+ for (const [name, entry] of Object.entries(config.software_items)) {
4229
+ if (entry.domain === domain) {
4230
+ resolvedItems.push({
4231
+ softwareItem: name,
4232
+ domain: entry.domain,
4233
+ architecture: entry.architecture
4234
+ });
4235
+ for (const archId of entry.architecture) {
4236
+ archIdSet.add(archId);
4237
+ }
4238
+ }
4239
+ }
4240
+ const archNodes = [];
4241
+ for (const archId of archIdSet) {
4242
+ const node = graph.nodes.get(archId);
4243
+ if (node && node.nodeKind === "document") {
4244
+ archNodes.push(node);
4245
+ }
4246
+ }
4247
+ return { archNodes, resolvedItems };
4248
+ }
4249
+ function runTraceContext(rootDir, domain, softwareItems) {
4250
+ const config = requireSoftwareItems(softwareItems);
4251
+ const graph = constructGraph(rootDir);
4252
+ const { archNodes, resolvedItems } = findArchitectureNodesForDomain(graph, domain, config);
4253
+ if (archNodes.length === 0) {
4254
+ const domains = [...new Set(Object.values(config.software_items).map((e) => e.domain))];
4255
+ const lines = [
4256
+ `No architecture nodes found for domain "${domain}".`,
4257
+ "Available domains:",
4258
+ ...domains.map((d) => ` ${d}`)
4259
+ ];
4260
+ return lines.join("\n");
4261
+ }
4262
+ const walkedNodes = walkArchNodes(graph, archNodes, ["requirements_chain", "risk_chain"]);
4263
+ return formatContext({ graph, resolvedItems, walkedNodes, domain });
4264
+ }
4265
+ function runTraceImpact(rootDir, softwareItems) {
4266
+ const config = requireSoftwareItems(softwareItems);
4267
+ const graph = constructGraph(rootDir);
4268
+ const changedFiles = getChangedFilesFromGit();
4269
+ if (changedFiles.length === 0) {
4270
+ return "No changed files detected.";
4271
+ }
4272
+ const resolvedItems = resolveChangedFiles(changedFiles, config);
4273
+ if (resolvedItems.length === 0) {
4274
+ return "Changed files do not map to any known software items.";
4275
+ }
4276
+ const archNodes = collectArchNodes(graph, resolvedItems);
4277
+ const walkedNodes = walkArchNodes(graph, archNodes, [
4278
+ "requirements_chain",
4279
+ "architecture_docs",
4280
+ "risk_neighbors"
4281
+ ]);
4282
+ return formatUpdateList({ graph, resolvedItems, walkedNodes });
4283
+ }
4284
+ function runTraceAssess(rootDir, planFile, softwareItems) {
4285
+ const config = requireSoftwareItems(softwareItems);
4286
+ const graph = constructGraph(rootDir);
4287
+ let planContent;
4288
+ try {
4289
+ planContent = readFileSync8(planFile, "utf-8");
4290
+ } catch {
4291
+ return `Could not read plan file: ${planFile}`;
4292
+ }
4293
+ const resolvedItems = matchPlanToSoftwareItems(planContent, config);
4294
+ if (resolvedItems.length === 0) {
4295
+ return "No software items matched from the plan file.";
4296
+ }
4297
+ const archNodes = collectArchNodes(graph, resolvedItems);
4298
+ const walkedNodes = walkArchNodes(graph, archNodes, ["requirements_chain", "risk_chain"]);
4299
+ return formatImpactTable({ graph, resolvedItems, walkedNodes });
4300
+ }
4301
+ function collectCoverageGaps(graph, srsDocNodes) {
4302
+ const gaps = [];
4303
+ let totalReqs = 0;
4304
+ let totalCovered = 0;
4305
+ for (const srs of srsDocNodes) {
4306
+ const cov = queryCoverage(graph, srs.id);
4307
+ totalReqs += cov.total;
4308
+ totalCovered += cov.covered.length;
4309
+ for (const uncoveredId of cov.uncovered) {
4310
+ gaps.push({
4311
+ category: "MISSING_TEST_COVERAGE",
4312
+ message: `${uncoveredId} \u2014 requires test but has none`,
4313
+ requirementId: uncoveredId
4314
+ });
4315
+ }
4316
+ }
4317
+ return { gaps, totalReqs, totalCovered };
4318
+ }
4319
+ function collectOrphanTestGaps(orphanScenarios) {
4320
+ return orphanScenarios.map((orphan) => ({
4321
+ category: "ORPHAN_TEST",
4322
+ message: `${orphan.file}: "${orphan.name}" \u2014 scenario has no @req tag`,
4323
+ testFile: orphan.file
4324
+ }));
4325
+ }
4326
+ function collectInvalidRefGaps(graph) {
4327
+ const gaps = [];
4328
+ for (const node of graph.nodes.values()) {
4329
+ if (node.nodeKind === "test_ref") {
4330
+ const reqExists = graph.nodes.has(node.requirementId);
4331
+ if (!reqExists) {
4332
+ gaps.push({
4333
+ category: "INVALID_REQ_REFERENCE",
4334
+ message: `${node.file}: @req(${node.requirementId}) \u2014 requirement does not exist`,
4335
+ requirementId: node.requirementId,
4336
+ testFile: node.file
4337
+ });
4338
+ }
4339
+ }
4340
+ }
4341
+ return gaps;
4342
+ }
4343
+ function collectBrokenTraceGaps(graph) {
4344
+ const gaps = [];
4345
+ for (const edge of graph.edges) {
4346
+ const fromExists = graph.nodes.has(edge.from);
4347
+ const toExists = graph.nodes.has(edge.to);
4348
+ if (!fromExists || !toExists) {
4349
+ const missingId = !toExists ? edge.to : edge.from;
4350
+ const existingId = !toExists ? edge.from : edge.to;
4351
+ gaps.push({
4352
+ category: "BROKEN_TRACE",
4353
+ message: `${existingId} --(${edge.linkType})--> ${missingId} \u2014 target does not exist`
4354
+ });
4355
+ }
4356
+ }
4357
+ return gaps;
4358
+ }
4359
+ function runTraceCheck(rootDir) {
4360
+ const graph = constructGraph(rootDir);
4361
+ const testResult = parseAllTests(rootDir);
4362
+ const srsDocNodes = findSrsDocumentNodes(graph);
4363
+ const coverage = collectCoverageGaps(graph, srsDocNodes);
4364
+ const gaps = [
4365
+ ...coverage.gaps,
4366
+ ...collectOrphanTestGaps(testResult.orphanScenarios),
4367
+ ...collectInvalidRefGaps(graph),
4368
+ ...collectBrokenTraceGaps(graph)
4369
+ ];
4370
+ const pct = coverage.totalReqs > 0 ? (coverage.totalCovered / coverage.totalReqs * 100).toFixed(1) : "0.0";
4371
+ const lines = [];
4372
+ lines.push(
4373
+ `Test coverage: ${coverage.totalCovered}/${coverage.totalReqs} testable requirements (${pct}%)`
4374
+ );
4375
+ lines.push(`SRS documents: ${srsDocNodes.length}`);
4376
+ lines.push("");
4377
+ const gapOutput = formatGapReport({
4378
+ graph,
4379
+ resolvedItems: [],
4380
+ walkedNodes: [],
4381
+ gaps
4382
+ });
4383
+ lines.push(gapOutput);
4384
+ const passed = gaps.length === 0;
4385
+ if (!passed) {
4386
+ lines.push(`
4387
+ Trace check FAILED: ${gaps.length} gap(s) found.`);
4388
+ } else {
4389
+ lines.push("\nTrace check PASSED: all traceability links are intact.");
4390
+ }
4391
+ return {
4392
+ output: lines.join("\n"),
4393
+ gapCount: gaps.length,
4394
+ passed
4395
+ };
4396
+ }
4397
+ function collectArchNodes(graph, items) {
4398
+ const archIds = new Set(items.flatMap((i) => i.architecture));
4399
+ const archNodes = [];
4400
+ for (const archId of archIds) {
4401
+ const node = graph.nodes.get(archId);
4402
+ if (node && node.nodeKind === "document") {
4403
+ archNodes.push(node);
4404
+ }
4405
+ }
4406
+ return archNodes;
4407
+ }
4408
+ function walkArchNodes(graph, archNodes, strategies2) {
4409
+ const seen = /* @__PURE__ */ new Set();
4410
+ const result = [];
4411
+ for (const archNode of archNodes) {
4412
+ const walked = walkStrategies(strategies2, graph, archNode);
4413
+ for (const node of walked) {
4414
+ if (!seen.has(node.id)) {
4415
+ seen.add(node.id);
4416
+ result.push(node);
4417
+ }
4418
+ }
4419
+ }
4420
+ return result;
4421
+ }
4422
+ function findSrsDocumentNodes(graph) {
4423
+ const result = [];
4424
+ for (const node of graph.nodes.values()) {
4425
+ if (node.nodeKind === "document" && node.type === "SRS") {
4426
+ result.push(node);
4427
+ }
4428
+ }
4429
+ return result;
4430
+ }
4431
+ function matchPlanToSoftwareItems(planContent, config) {
4432
+ const contentLower = planContent.toLowerCase();
4433
+ const matched = [];
4434
+ for (const [name, entry] of Object.entries(config.software_items)) {
4435
+ const keywords = [name, entry.domain, ...name.split("-")].filter((k) => k.length > 2);
4436
+ const isMatch = keywords.some((kw) => contentLower.includes(kw.toLowerCase()));
4437
+ if (isMatch) {
4438
+ matched.push({
4439
+ softwareItem: name,
4440
+ domain: entry.domain,
4441
+ architecture: entry.architecture
4442
+ });
4443
+ }
4444
+ }
4445
+ return matched;
4446
+ }
4447
+
4448
+ // src/test-report/index.ts
4449
+ import { existsSync } from "fs";
4450
+ import { join as join9 } from "path";
4451
+
4452
+ // src/test-report/parse-results.ts
4453
+ import { readFileSync as readFileSync9 } from "fs";
4454
+ function extractReqIds(text) {
4455
+ return [...text.matchAll(/@req[\s-]((?:PRS|SRS)-[A-Z]+-\d+\.\d+)/g)].map((m) => m[1]).filter((id) => id !== void 0);
4456
+ }
4457
+ function parseVitestResults(jsonPath) {
4458
+ const raw = readFileSync9(jsonPath, "utf-8");
4459
+ const data = JSON.parse(raw);
4460
+ const suites = [];
4461
+ for (const fileResult of data.testResults) {
4462
+ const tests = [];
4463
+ for (const assertion of fileResult.assertionResults) {
4464
+ const fullName = [...assertion.ancestorTitles, assertion.title].join(" > ");
4465
+ const reqIds = extractReqIds(fullName);
4466
+ tests.push({
4467
+ name: fullName,
4468
+ file: fileResult.name,
4469
+ status: assertion.status === "pending" ? "skipped" : assertion.status,
4470
+ duration: assertion.duration ?? 0,
4471
+ errorMessage: assertion.failureMessages.length > 0 ? assertion.failureMessages[0] : void 0,
4472
+ requirementIds: reqIds
4473
+ });
4474
+ }
4475
+ suites.push({
4476
+ name: fileResult.name,
4477
+ file: fileResult.name,
4478
+ tests,
4479
+ duration: fileResult.endTime - fileResult.startTime
4480
+ });
4481
+ }
4482
+ return suites;
4483
+ }
4484
+ function flattenSuites(suite, parentFile) {
4485
+ const results = [];
4486
+ const file = suite.file || parentFile;
4487
+ for (const spec of suite.specs) {
4488
+ const reqIds = spec.tags.flatMap(
4489
+ (t) => [...t.matchAll(/@req-((?:PRS|SRS)-[A-Z]+-\d+\.\d+)/g)].map((m) => m[1]).filter((id) => id !== void 0)
4490
+ );
4491
+ for (const test of spec.tests) {
4492
+ const lastResult = test.results[test.results.length - 1];
4493
+ let status;
4494
+ if (test.status === "expected") status = "passed";
4495
+ else if (test.status === "skipped") status = "skipped";
4496
+ else status = "failed";
4497
+ results.push({
4498
+ name: `${suite.title} > ${spec.title}`,
4499
+ file,
4500
+ status,
4501
+ duration: lastResult?.duration ?? 0,
4502
+ errorMessage: lastResult?.error?.message,
4503
+ requirementIds: reqIds
4504
+ });
4505
+ }
4506
+ }
4507
+ if (suite.suites) {
4508
+ for (const child of suite.suites) {
4509
+ results.push(...flattenSuites(child, file));
4510
+ }
4511
+ }
4512
+ return results;
4513
+ }
4514
+ function parsePlaywrightResults(jsonPath) {
4515
+ const raw = readFileSync9(jsonPath, "utf-8");
4516
+ const data = JSON.parse(raw);
4517
+ const suites = [];
4518
+ for (const suite of data.suites) {
4519
+ const tests = flattenSuites(suite, suite.file);
4520
+ const duration = tests.reduce((sum, t) => sum + t.duration, 0);
4521
+ suites.push({
4522
+ name: suite.title || suite.file,
4523
+ file: suite.file,
4524
+ tests,
4525
+ duration
4526
+ });
4527
+ }
4528
+ return suites;
4529
+ }
4530
+
4531
+ // src/test-report/build-report.ts
4532
+ import { execSync as execSync3 } from "child_process";
4533
+ function buildSTRReport(unitSuites, e2eSuites) {
4534
+ const allSuites = [...unitSuites, ...e2eSuites];
4535
+ const allTests = allSuites.flatMap((s) => s.tests);
4536
+ const totalDuration = allSuites.reduce((sum, s) => sum + s.duration, 0);
4537
+ let passed = 0;
4538
+ let failed = 0;
4539
+ let skipped = 0;
4540
+ const failures = [];
4541
+ const reqMap = /* @__PURE__ */ new Map();
4542
+ for (const test of allTests) {
4543
+ if (test.status === "passed") passed++;
4544
+ else if (test.status === "failed") {
4545
+ failed++;
4546
+ failures.push(test);
4547
+ } else skipped++;
4548
+ for (const reqId of test.requirementIds) {
4549
+ const existing = reqMap.get(reqId) ?? [];
4550
+ existing.push({ name: test.name, file: test.file, status: test.status });
4551
+ reqMap.set(reqId, existing);
4552
+ }
4553
+ }
4554
+ const total = allTests.length;
4555
+ const unitTestCount = unitSuites.reduce((n, s) => n + s.tests.length, 0);
4556
+ const e2eTestCount = e2eSuites.reduce((n, s) => n + s.tests.length, 0);
4557
+ const requirementCoverage = [...reqMap.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([requirementId, tests]) => ({
4558
+ requirementId,
4559
+ tests,
4560
+ allPassed: tests.every((t) => t.status === "passed")
4561
+ }));
4562
+ let gitCommit = "unknown";
4563
+ let gitBranch = "unknown";
4564
+ try {
4565
+ gitCommit = execSync3("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
4566
+ gitBranch = execSync3("git branch --show-current", { encoding: "utf-8" }).trim();
4567
+ } catch {
4568
+ }
4569
+ return {
4570
+ title: "Software Test Report (STR)",
4571
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4572
+ environment: {
4573
+ nodeVersion: process.version,
4574
+ platform: process.platform,
4575
+ gitCommit,
4576
+ gitBranch
4577
+ },
4578
+ summary: {
4579
+ totalTests: total,
4580
+ passed,
4581
+ failed,
4582
+ skipped,
4583
+ passRate: total > 0 ? Math.round(passed / total * 100) : 0,
4584
+ totalDuration,
4585
+ unitTestCount,
4586
+ e2eTestCount
4587
+ },
4588
+ suites: allSuites,
4589
+ requirementCoverage,
4590
+ failures
4591
+ };
4592
+ }
4593
+
4594
+ // src/test-report/format-str.ts
4595
+ function formatSTR(report) {
4596
+ const lines = [];
4597
+ lines.push(`# ${report.title}`);
4598
+ lines.push("");
4599
+ lines.push("| Field | Value |");
4600
+ lines.push("| ----- | ----- |");
4601
+ lines.push(`| Generated | ${report.generatedAt} |`);
4602
+ lines.push(`| Standard | IEC 62304:2015 \xA75.7 |`);
4603
+ lines.push(`| Node.js | ${report.environment.nodeVersion} |`);
4604
+ lines.push(`| Platform | ${report.environment.platform} |`);
4605
+ lines.push(`| Git Commit | ${report.environment.gitCommit} |`);
4606
+ lines.push(`| Git Branch | ${report.environment.gitBranch} |`);
4607
+ lines.push("");
4608
+ lines.push("## 1. Test Summary");
4609
+ lines.push("");
4610
+ lines.push("| Metric | Count |");
4611
+ lines.push("| ------ | ----- |");
4612
+ lines.push(`| Total Tests | ${report.summary.totalTests} |`);
4613
+ lines.push(`| Passed | ${report.summary.passed} |`);
4614
+ lines.push(`| Failed | ${report.summary.failed} |`);
4615
+ lines.push(`| Skipped | ${report.summary.skipped} |`);
4616
+ lines.push(`| Pass Rate | ${report.summary.passRate}% |`);
4617
+ lines.push(`| Unit Tests | ${report.summary.unitTestCount} |`);
4618
+ lines.push(`| E2E Tests | ${report.summary.e2eTestCount} |`);
4619
+ lines.push(`| Total Duration | ${formatDuration(report.summary.totalDuration)} |`);
4620
+ lines.push("");
4621
+ lines.push("## 2. Test Results by Suite");
4622
+ lines.push("");
4623
+ for (const suite of report.suites) {
4624
+ const suitePassed = suite.tests.filter((t) => t.status === "passed").length;
4625
+ const suiteTotal = suite.tests.length;
4626
+ lines.push(`### ${suite.name}`);
4627
+ lines.push("");
4628
+ lines.push(`**${suitePassed}/${suiteTotal} passed** (${formatDuration(suite.duration)})`);
4629
+ lines.push("");
4630
+ lines.push("| Test | Status | Duration | Requirements |");
4631
+ lines.push("| ---- | ------ | -------- | ------------ |");
4632
+ for (const test of suite.tests) {
4633
+ const statusIcon = test.status === "passed" ? "PASS" : test.status === "failed" ? "FAIL" : "SKIP";
4634
+ const reqs = test.requirementIds.length > 0 ? test.requirementIds.join(", ") : "\u2014";
4635
+ lines.push(
4636
+ `| ${escapeMarkdown(test.name)} | ${statusIcon} | ${formatDuration(test.duration)} | ${reqs} |`
4637
+ );
4638
+ }
4639
+ lines.push("");
4640
+ }
4641
+ lines.push("## 3. Requirement Verification Results");
4642
+ lines.push("");
4643
+ if (report.requirementCoverage.length === 0) {
4644
+ lines.push("No test-to-requirement traceability tags found.");
4645
+ } else {
4646
+ lines.push("| Requirement | Tests | Result |");
4647
+ lines.push("| ----------- | ----- | ------ |");
4648
+ for (const entry of report.requirementCoverage) {
4649
+ const testCount = entry.tests.length;
4650
+ const result = entry.allPassed ? "VERIFIED" : "FAILED";
4651
+ lines.push(`| ${entry.requirementId} | ${testCount} test(s) | ${result} |`);
4652
+ }
4653
+ }
4654
+ lines.push("");
4655
+ lines.push("## 4. Failures and Anomalies");
4656
+ lines.push("");
4657
+ if (report.failures.length === 0) {
4658
+ lines.push("No test failures recorded.");
4659
+ } else {
4660
+ for (const failure of report.failures) {
4661
+ lines.push(`### ${escapeMarkdown(failure.name)}`);
4662
+ lines.push("");
4663
+ lines.push(`- **File:** ${failure.file}`);
4664
+ lines.push(`- **Requirements:** ${failure.requirementIds.join(", ") || "\u2014"}`);
4665
+ if (failure.errorMessage) {
4666
+ lines.push("- **Error:**");
4667
+ lines.push("```");
4668
+ lines.push(failure.errorMessage.slice(0, 500));
4669
+ lines.push("```");
4670
+ }
4671
+ lines.push("");
4672
+ }
4673
+ }
4674
+ lines.push("## 5. Conclusion");
4675
+ lines.push("");
4676
+ if (report.summary.failed === 0) {
4677
+ lines.push(
4678
+ "All tests passed. The software meets its verification criteria as defined in the Software Test Plan (STP-001)."
4679
+ );
4680
+ } else {
4681
+ lines.push(
4682
+ `**${report.summary.failed} test(s) failed.** The software does NOT meet all verification criteria. Failed tests must be investigated and resolved before release.`
4683
+ );
4684
+ }
4685
+ lines.push("");
4686
+ return lines.join("\n");
4687
+ }
4688
+ function formatDuration(ms) {
4689
+ if (ms < 1e3) return `${ms}ms`;
4690
+ const seconds = (ms / 1e3).toFixed(1);
4691
+ return `${seconds}s`;
4692
+ }
4693
+ function escapeMarkdown(text) {
4694
+ return text.replace(/\|/g, "\\|").replace(/\n/g, " ");
4695
+ }
4696
+
4697
+ // src/test-report/index.ts
4698
+ function generateTestReport(options = {}) {
4699
+ const root = options.rootDir ?? process.cwd();
4700
+ const vitestFile = options.vitestJsonPath ?? join9(root, "test-results", "vitest-results.json");
4701
+ const playwrightFile = options.playwrightJsonPath ?? join9(root, "test-results", "playwright-results.json");
4702
+ let unitSuites = [];
4703
+ let e2eSuites = [];
4704
+ if (existsSync(vitestFile)) {
4705
+ unitSuites = parseVitestResults(vitestFile);
4706
+ }
4707
+ if (existsSync(playwrightFile)) {
4708
+ e2eSuites = parsePlaywrightResults(playwrightFile);
4709
+ }
4710
+ if (unitSuites.length === 0 && e2eSuites.length === 0) {
4711
+ return null;
4712
+ }
4713
+ const report = buildSTRReport(unitSuites, e2eSuites);
4714
+ return formatSTR(report);
4715
+ }
4716
+
4717
+ // src/validation-report/index.ts
4718
+ import { execSync as execSync4 } from "child_process";
4719
+ function resolveGitContext(rootDir) {
4720
+ try {
4721
+ return {
4722
+ commit: execSync4("git rev-parse --short HEAD", { cwd: rootDir, encoding: "utf-8" }).trim(),
4723
+ branch: execSync4("git branch --show-current", { cwd: rootDir, encoding: "utf-8" }).trim(),
4724
+ tag: execSync4('git describe --tags --abbrev=0 2>/dev/null || echo "untagged"', {
4725
+ cwd: rootDir,
4726
+ encoding: "utf-8"
4727
+ }).trim()
4728
+ };
4729
+ } catch {
4730
+ return { commit: "unknown", branch: "unknown", tag: "untagged" };
4731
+ }
4732
+ }
4733
+ function generateVSR(options) {
4734
+ const { rootDir } = options;
4735
+ const git = options.gitContext ?? resolveGitContext(rootDir);
4736
+ const rtmResult = runLegacyTraceability({ rootDir, mode: "rtm" });
4737
+ const strOutput = generateTestReport({
4738
+ rootDir,
4739
+ vitestJsonPath: options.vitestJsonPath,
4740
+ playwrightJsonPath: options.playwrightJsonPath
4741
+ });
4742
+ return [
4743
+ "# Validation Summary Report",
4744
+ "",
4745
+ `**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}`,
4746
+ `**Commit:** ${git.commit} (${git.branch})`,
4747
+ `**Tag:** ${git.tag}`,
4748
+ "",
4749
+ "## \xA75.8.1 Scope",
4750
+ "",
4751
+ "This Validation Summary Report (VSR) provides evidence that the software",
4752
+ "has been validated per IEC 62304:2006+A1:2015 \xA75.8.",
4753
+ "",
4754
+ "## \xA75.8.2 Requirements Traceability Matrix",
4755
+ "",
4756
+ rtmResult.output,
4757
+ "",
4758
+ "## \xA75.8.3 Software Test Report",
4759
+ "",
4760
+ strOutput ?? "_No test results available._",
4761
+ "",
4762
+ "## \xA75.8.4 Risk Management",
4763
+ "",
4764
+ "Risk analysis is maintained in `docs/risk/analysis/RA-001.md`.",
4765
+ "STRIDE security risk analysis is maintained in `docs/cybersecurity/risks/`.",
4766
+ "",
4767
+ "## \xA75.8.5 Conclusion",
4768
+ "",
4769
+ rtmResult.gapCount === 0 ? "All testable requirements have been verified. The software meets its specified requirements." : `**${rtmResult.gapCount} gap(s) remain.** Review the RTM section above for details.`
4770
+ ].join("\n");
4771
+ }
4772
+ export {
4773
+ FilesystemDocumentLoader,
4774
+ buildDocumentChainGaps,
4775
+ buildSTRReport,
4776
+ buildTraceabilityReport,
4777
+ diffGaps,
4778
+ extractReqMarkersFromTestFile,
4779
+ extractReqTagsFromFeature,
4780
+ findOrphanScenarios,
4781
+ formatSTR,
4782
+ generateTestReport,
4783
+ generateVSR,
4784
+ loadTraceConfig,
4785
+ parsePlaywrightResults,
4786
+ parseRequirementsFromMarkdown,
4787
+ parseVitestResults,
4788
+ runLegacyTraceability,
4789
+ runTraceAssess,
4790
+ runTraceCheck,
4791
+ runTraceContext,
4792
+ runTraceImpact,
4793
+ scanBaselineGaps
4794
+ };