@pactosigna/records 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -876,7 +876,7 @@ ${probabilityLabels[labelIndex]}`,
876
876
  };
877
877
  }
878
878
 
879
- // ../schemas/dist/index.js
879
+ // ../../node_modules/.pnpm/@pactosigna+schemas@0.1.19/node_modules/@pactosigna/schemas/dist/index.js
880
880
  import { z as z2 } from "zod";
881
881
  import { z as z22 } from "zod";
882
882
  import { z as z3 } from "zod";
@@ -5351,11 +5351,257 @@ function generateSdd(input) {
5351
5351
  });
5352
5352
  }
5353
5353
 
5354
+ // src/generators/vv-generator.ts
5355
+ function generateVv(input) {
5356
+ const protocols = readDocuments(input.rootDir, "docs/test/protocols");
5357
+ const reports = readDocuments(input.rootDir, "docs/test/reports");
5358
+ const plans = readDocuments(input.rootDir, "docs/software/plans").filter(
5359
+ (d) => d.frontmatter.id.startsWith("STP-")
5360
+ );
5361
+ const docs = [...plans, ...protocols, ...reports];
5362
+ if (docs.length === 0) {
5363
+ console.warn("VV: no approved/effective V&V documents found \u2014 skipping");
5364
+ return null;
5365
+ }
5366
+ const content = [];
5367
+ content.push({ text: "Table of Contents", style: "h2", margin: [0, 0, 0, 10] });
5368
+ content.push({
5369
+ ol: docs.map((d) => ({
5370
+ text: `${d.frontmatter.id}: ${d.frontmatter.title}`,
5371
+ margin: [0, 2, 0, 2]
5372
+ }))
5373
+ });
5374
+ content.push({ text: "", pageBreak: "after" });
5375
+ for (const doc of docs) {
5376
+ content.push({
5377
+ text: `${doc.frontmatter.id}: ${doc.frontmatter.title}`,
5378
+ style: "h2",
5379
+ margin: [0, 10, 0, 6]
5380
+ });
5381
+ content.push(...markdownToPdfmake(doc.body));
5382
+ content.push({ text: "", margin: [0, 20, 0, 0] });
5383
+ }
5384
+ return buildDocumentDefinition({
5385
+ config: input.config,
5386
+ rootDir: input.rootDir,
5387
+ recordTitle: "Verification & Validation",
5388
+ recordId: "VV",
5389
+ version: input.version,
5390
+ date: input.date,
5391
+ revisionHistory: input.revisionHistory,
5392
+ content
5393
+ });
5394
+ }
5395
+
5396
+ // src/generators/uef-generator.ts
5397
+ function generateUef(input) {
5398
+ const plans = readDocuments(input.rootDir, "docs/usability");
5399
+ const useSpecs = readDocuments(input.rootDir, "docs/usability/use-specifications");
5400
+ const taskAnalyses = readDocuments(input.rootDir, "docs/usability/task-analyses");
5401
+ const evaluations = readDocuments(input.rootDir, "docs/usability/evaluations");
5402
+ const docs = [...plans, ...useSpecs, ...taskAnalyses, ...evaluations];
5403
+ if (docs.length === 0) {
5404
+ console.warn("UEF: no approved/effective usability documents found \u2014 skipping");
5405
+ return null;
5406
+ }
5407
+ const content = [];
5408
+ content.push({ text: "Table of Contents", style: "h2", margin: [0, 0, 0, 10] });
5409
+ content.push({
5410
+ ol: docs.map((d) => ({
5411
+ text: `${d.frontmatter.id}: ${d.frontmatter.title}`,
5412
+ margin: [0, 2, 0, 2]
5413
+ }))
5414
+ });
5415
+ content.push({ text: "", pageBreak: "after" });
5416
+ for (const doc of docs) {
5417
+ content.push({
5418
+ text: `${doc.frontmatter.id}: ${doc.frontmatter.title}`,
5419
+ style: "h2",
5420
+ margin: [0, 10, 0, 6]
5421
+ });
5422
+ content.push(...markdownToPdfmake(doc.body));
5423
+ content.push({ text: "", margin: [0, 20, 0, 0] });
5424
+ }
5425
+ return buildDocumentDefinition({
5426
+ config: input.config,
5427
+ rootDir: input.rootDir,
5428
+ recordTitle: "Usability Engineering File",
5429
+ recordId: "UEF",
5430
+ version: input.version,
5431
+ date: input.date,
5432
+ revisionHistory: input.revisionHistory,
5433
+ content
5434
+ });
5435
+ }
5436
+
5437
+ // src/generators/csf-generator.ts
5438
+ function generateCsf(input) {
5439
+ const plans = readDocuments(input.rootDir, "docs/cybersecurity/plans");
5440
+ const risks = readDocuments(input.rootDir, "docs/cybersecurity/risks");
5441
+ const sboms = readDocuments(input.rootDir, "docs/cybersecurity/sbom");
5442
+ const docs = [...plans, ...risks, ...sboms];
5443
+ if (docs.length === 0) {
5444
+ console.warn("CSF: no approved/effective cybersecurity documents found \u2014 skipping");
5445
+ return null;
5446
+ }
5447
+ const content = [];
5448
+ content.push({ text: "Table of Contents", style: "h2", margin: [0, 0, 0, 10] });
5449
+ content.push({
5450
+ ol: docs.map((d) => ({
5451
+ text: `${d.frontmatter.id}: ${d.frontmatter.title}`,
5452
+ margin: [0, 2, 0, 2]
5453
+ }))
5454
+ });
5455
+ content.push({ text: "", pageBreak: "after" });
5456
+ for (const doc of docs) {
5457
+ content.push({
5458
+ text: `${doc.frontmatter.id}: ${doc.frontmatter.title}`,
5459
+ style: "h2",
5460
+ margin: [0, 10, 0, 6]
5461
+ });
5462
+ content.push(...markdownToPdfmake(doc.body));
5463
+ content.push({ text: "", margin: [0, 20, 0, 0] });
5464
+ }
5465
+ return buildDocumentDefinition({
5466
+ config: input.config,
5467
+ rootDir: input.rootDir,
5468
+ recordTitle: "Cybersecurity File",
5469
+ recordId: "CSF",
5470
+ version: input.version,
5471
+ date: input.date,
5472
+ revisionHistory: input.revisionHistory,
5473
+ content
5474
+ });
5475
+ }
5476
+
5477
+ // src/generators/cer-generator.ts
5478
+ function generateCer(input) {
5479
+ const docs = readDocuments(input.rootDir, "docs/clinical");
5480
+ if (docs.length === 0) {
5481
+ console.warn("CER: no approved/effective clinical documents found \u2014 skipping");
5482
+ return null;
5483
+ }
5484
+ const content = [];
5485
+ content.push({ text: "Table of Contents", style: "h2", margin: [0, 0, 0, 10] });
5486
+ content.push({
5487
+ ol: docs.map((d) => ({
5488
+ text: `${d.frontmatter.id}: ${d.frontmatter.title}`,
5489
+ margin: [0, 2, 0, 2]
5490
+ }))
5491
+ });
5492
+ content.push({ text: "", pageBreak: "after" });
5493
+ for (const doc of docs) {
5494
+ content.push({
5495
+ text: `${doc.frontmatter.id}: ${doc.frontmatter.title}`,
5496
+ style: "h2",
5497
+ margin: [0, 10, 0, 6]
5498
+ });
5499
+ content.push(...markdownToPdfmake(doc.body));
5500
+ content.push({ text: "", margin: [0, 20, 0, 0] });
5501
+ }
5502
+ return buildDocumentDefinition({
5503
+ config: input.config,
5504
+ rootDir: input.rootDir,
5505
+ recordTitle: "Clinical Evaluation Report",
5506
+ recordId: "CER",
5507
+ version: input.version,
5508
+ date: input.date,
5509
+ revisionHistory: input.revisionHistory,
5510
+ content
5511
+ });
5512
+ }
5513
+
5354
5514
  // src/github/changelog.ts
5355
5515
  import { execFileSync } from "child_process";
5356
- import { resolve as resolve5 } from "path";
5357
- import { readFileSync as readFileSync5 } from "fs";
5516
+ import { resolve as resolve6 } from "path";
5517
+ import { readFileSync as readFileSync6 } from "fs";
5518
+ import matter4 from "gray-matter";
5519
+
5520
+ // src/github/drm-parser.ts
5521
+ import { readdirSync as readdirSync2, readFileSync as readFileSync5, existsSync as existsSync4 } from "fs";
5522
+ import { resolve as resolve5, join as join2 } from "path";
5358
5523
  import matter3 from "gray-matter";
5524
+ function parseDrmDocsTable(body) {
5525
+ const startTag = "<!-- DRM:DOCS_START -->";
5526
+ const endTag = "<!-- DRM:DOCS_END -->";
5527
+ const startIdx = body.indexOf(startTag);
5528
+ const endIdx = body.indexOf(endTag);
5529
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) return [];
5530
+ const block = body.slice(startIdx + startTag.length, endIdx).trim();
5531
+ const lines = block.split("\n");
5532
+ const entries = [];
5533
+ for (const line of lines) {
5534
+ if (!line.startsWith("|")) continue;
5535
+ const cells = line.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
5536
+ if (cells.length < 2) continue;
5537
+ const action = cells[0].toUpperCase();
5538
+ if (action === "ACTION" || /^-+$/.test(action) || action === "NONE") continue;
5539
+ if (action !== "CREATE" && action !== "UPDATE") continue;
5540
+ const target = cells[1];
5541
+ const reason = cells.length > 2 ? cells[cells.length - 1] : "";
5542
+ entries.push({
5543
+ action,
5544
+ target,
5545
+ reason
5546
+ });
5547
+ }
5548
+ return entries;
5549
+ }
5550
+ var DRM_FOLDERS = ["docs/change/drm", "docs/design-review"];
5551
+ function readDrmFiles(rootDir) {
5552
+ const drms = [];
5553
+ for (const folder of DRM_FOLDERS) {
5554
+ const fullPath = resolve5(rootDir, folder);
5555
+ if (!existsSync4(fullPath)) continue;
5556
+ let files;
5557
+ try {
5558
+ files = readdirSync2(fullPath).filter((f) => f.endsWith(".md"));
5559
+ } catch {
5560
+ continue;
5561
+ }
5562
+ for (const file of files) {
5563
+ const filePath = join2(folder, file);
5564
+ const raw = readFileSync5(resolve5(rootDir, filePath), "utf-8");
5565
+ const { data, content } = matter3(raw);
5566
+ if (!data.id || !data.title) continue;
5567
+ const docEntries = parseDrmDocsTable(content);
5568
+ drms.push({
5569
+ id: data.id,
5570
+ title: data.title,
5571
+ status: data.status ?? "unknown",
5572
+ releasePlan: data.release_plan,
5573
+ docEntries
5574
+ });
5575
+ }
5576
+ }
5577
+ return drms.sort((a, b) => a.id.localeCompare(b.id, void 0, { numeric: true }));
5578
+ }
5579
+ function extractDocIdFromPath(target) {
5580
+ const filename = target.split("/").pop() ?? target;
5581
+ return filename.replace(/\.md$/, "");
5582
+ }
5583
+
5584
+ // src/github/changelog.ts
5585
+ function buildSmartChangelog(rootDir, version, date) {
5586
+ const drms = readDrmFiles(rootDir);
5587
+ const entries = buildRevisionEntriesFromDrms(drms, version, date);
5588
+ if (entries.length === 0) {
5589
+ return buildChangelog(rootDir, version, date);
5590
+ }
5591
+ return entries;
5592
+ }
5593
+ function buildRevisionEntriesFromDrms(drms, version, date) {
5594
+ const withDocEntries = drms.filter((drm) => drm.docEntries.length > 0);
5595
+ if (withDocEntries.length === 0) return [];
5596
+ return withDocEntries.map((drm) => {
5597
+ const reasons = drm.docEntries.map((entry) => {
5598
+ const docId = extractDocIdFromPath(entry.target);
5599
+ return entry.reason ? `${docId}: ${entry.reason}` : docId;
5600
+ });
5601
+ const description = `[${drm.id}] ${reasons.join("; ")}`;
5602
+ return { revision: version, date, description };
5603
+ });
5604
+ }
5359
5605
  function buildChangelog(rootDir, version, date) {
5360
5606
  let previousTag = null;
5361
5607
  try {
@@ -5388,8 +5634,8 @@ function buildChangelog(rootDir, version, date) {
5388
5634
  const descriptions = [];
5389
5635
  for (const file of changedFiles.slice(0, 20)) {
5390
5636
  try {
5391
- const raw = readFileSync5(resolve5(rootDir, file), "utf-8");
5392
- const { data } = matter3(raw);
5637
+ const raw = readFileSync6(resolve6(rootDir, file), "utf-8");
5638
+ const { data } = matter4(raw);
5393
5639
  if (data.id && data.title) {
5394
5640
  descriptions.push(`${data.id}: ${data.title}`);
5395
5641
  }
@@ -5409,7 +5655,11 @@ var RECORD_GENERATORS = [
5409
5655
  { name: "ARC-Software-Architecture-Description", fn: generateArc },
5410
5656
  { name: "SDD-Software-Detailed-Design", fn: generateSdd },
5411
5657
  { name: "PTA-Product-Traceability-Analysis", fn: generatePta },
5412
- { name: "RMR-Risk-Management-Report", fn: generateRmr }
5658
+ { name: "RMR-Risk-Management-Report", fn: generateRmr },
5659
+ { name: "VV-Verification-And-Validation", fn: generateVv },
5660
+ { name: "UEF-Usability-Engineering-File", fn: generateUef },
5661
+ { name: "CSF-Cybersecurity-File", fn: generateCsf },
5662
+ { name: "CER-Clinical-Evaluation-Report", fn: generateCer }
5413
5663
  ];
5414
5664
  var noopUrlResolver = {
5415
5665
  resolve: () => {
@@ -5431,7 +5681,7 @@ async function generate(options) {
5431
5681
  const outDir = options.outputDir ?? pathResolve(options.rootDir, "out");
5432
5682
  mkdirSync(outDir, { recursive: true });
5433
5683
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
5434
- const revisionHistory = buildChangelog(options.rootDir, options.version, date);
5684
+ const revisionHistory = buildSmartChangelog(options.rootDir, options.version, date);
5435
5685
  const fonts = {
5436
5686
  Helvetica: {
5437
5687
  normal: "Helvetica",
@@ -5487,11 +5737,11 @@ function getDraftReleaseVersion() {
5487
5737
 
5488
5738
  // src/github/publish.ts
5489
5739
  import { execFileSync as execFileSync3 } from "child_process";
5490
- import { readdirSync as readdirSync2 } from "fs";
5491
- import { resolve as resolve6 } from "path";
5740
+ import { readdirSync as readdirSync3 } from "fs";
5741
+ import { resolve as resolve7 } from "path";
5492
5742
  function publishDraftRelease(version, outputDir) {
5493
5743
  const tag = `v${version}`;
5494
- const pdfFiles = readdirSync2(outputDir).filter((f) => f.endsWith(".pdf")).map((f) => resolve6(outputDir, f));
5744
+ const pdfFiles = readdirSync3(outputDir).filter((f) => f.endsWith(".pdf")).map((f) => resolve7(outputDir, f));
5495
5745
  if (pdfFiles.length === 0) {
5496
5746
  throw new Error("No PDF files found to upload");
5497
5747
  }
@@ -1 +1 @@
1
- {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkCD,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA8C1E"}
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsCD,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA8C1E"}
@@ -0,0 +1,10 @@
1
+ import type { TDocumentDefinitions } from 'pdfmake/interfaces.js';
2
+ import type { GeneratorInput } from './urs-generator.js';
3
+ /**
4
+ * Generate a Clinical Evaluation Report (CER) compiled PDF.
5
+ *
6
+ * Reads clinical evaluation plans and reports.
7
+ * Regulatory basis: MDR Annex XIV
8
+ */
9
+ export declare function generateCer(input: GeneratorInput): TDocumentDefinitions | null;
10
+ //# sourceMappingURL=cer-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cer-generator.d.ts","sourceRoot":"","sources":["../../src/generators/cer-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAI3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,oBAAoB,GAAG,IAAI,CAyC9E"}
@@ -0,0 +1,10 @@
1
+ import type { TDocumentDefinitions } from 'pdfmake/interfaces.js';
2
+ import type { GeneratorInput } from './urs-generator.js';
3
+ /**
4
+ * Generate a Cybersecurity File (CSF) compiled PDF.
5
+ *
6
+ * Reads cybersecurity plans, SBOMs, and security risk documents.
7
+ * Regulatory basis: IEC 81001-5-1
8
+ */
9
+ export declare function generateCsf(input: GeneratorInput): TDocumentDefinitions | null;
10
+ //# sourceMappingURL=csf-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csf-generator.d.ts","sourceRoot":"","sources":["../../src/generators/csf-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAI3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,oBAAoB,GAAG,IAAI,CA8C9E"}
@@ -0,0 +1,15 @@
1
+ import type { TDocumentDefinitions } from 'pdfmake/interfaces.js';
2
+ import type { GeneratorInput } from './urs-generator.js';
3
+ /**
4
+ * Generate a Usability Engineering File (UEF) compiled PDF.
5
+ *
6
+ * Reads usability plans, use specifications, task analyses, and evaluations.
7
+ * Regulatory basis: IEC 62366
8
+ *
9
+ * Note: docs/usability/risks (usability_risk type) is intentionally excluded.
10
+ * Per ISO 14971, usability-related risks belong in the Risk Management File (RMF/RMR),
11
+ * not the Usability Engineering File. IEC 62366 §5.9 references risk analysis but
12
+ * delegates the actual risk documentation to ISO 14971.
13
+ */
14
+ export declare function generateUef(input: GeneratorInput): TDocumentDefinitions | null;
15
+ //# sourceMappingURL=uef-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uef-generator.d.ts","sourceRoot":"","sources":["../../src/generators/uef-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAI3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,oBAAoB,GAAG,IAAI,CA6C9E"}
@@ -0,0 +1,10 @@
1
+ import type { TDocumentDefinitions } from 'pdfmake/interfaces.js';
2
+ import type { GeneratorInput } from './urs-generator.js';
3
+ /**
4
+ * Generate a Verification & Validation (V&V) compiled PDF.
5
+ *
6
+ * Reads test protocols, test reports, and software test plans.
7
+ * Regulatory basis: IEC 62304 Section 5.7
8
+ */
9
+ export declare function generateVv(input: GeneratorInput): TDocumentDefinitions | null;
10
+ //# sourceMappingURL=vv-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vv-generator.d.ts","sourceRoot":"","sources":["../../src/generators/vv-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAI3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,oBAAoB,GAAG,IAAI,CA8C7E"}
@@ -1,3 +1,26 @@
1
1
  import type { RevisionEntry } from '../pdf/layout.js';
2
+ import type { ParsedDrm } from './drm-parser.js';
3
+ /**
4
+ * Build revision entries from DRM (Design Review Meeting) files.
5
+ *
6
+ * Reads DRM markdown files from the repository, extracts document-to-reason
7
+ * mappings from DRM:DOCS tables, and produces meaningful revision descriptions.
8
+ * Falls back to `buildChangelog()` if no DRM files with doc entries are found.
9
+ *
10
+ * @param rootDir - Repository root directory
11
+ * @param version - Release version string (e.g., "1.2.0")
12
+ * @param date - Release date in ISO format (e.g., "2026-03-28")
13
+ * @returns Revision entries with DRM-sourced descriptions
14
+ */
15
+ export declare function buildSmartChangelog(rootDir: string, version: string, date: string): RevisionEntry[];
16
+ /**
17
+ * Build revision entries from parsed DRM data.
18
+ *
19
+ * Groups DRM doc entries by DRM ID and produces one revision entry per DRM,
20
+ * with a description summarizing the affected documents and reasons.
21
+ *
22
+ * Exported for unit testing without filesystem access.
23
+ */
24
+ export declare function buildRevisionEntriesFromDrms(drms: ParsedDrm[], version: string, date: string): RevisionEntry[];
2
25
  export declare function buildChangelog(rootDir: string, version: string, date: string): RevisionEntry[];
3
26
  //# sourceMappingURL=changelog.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../../src/github/changelog.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAkD9F"}
1
+ {"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../../src/github/changelog.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,aAAa,EAAE,CASjB;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,SAAS,EAAE,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,aAAa,EAAE,CAcjB;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAkD9F"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * DRM (Design Review Meeting) Parser
3
+ *
4
+ * Parses DRM markdown files from git repositories to extract
5
+ * document-to-reason mappings from DRM:DOCS tables.
6
+ *
7
+ * The DRM:DOCS table format uses HTML comment markers:
8
+ *
9
+ * ```markdown
10
+ * <!-- DRM:DOCS_START -->
11
+ * | Action | Target | Reason |
12
+ * |--------|--------|--------|
13
+ * | UPDATE | docs/software/requirements/SRS-001.md | Added validation gate |
14
+ * <!-- DRM:DOCS_END -->
15
+ * ```
16
+ *
17
+ * This module is a standalone copy of the parsing logic from
18
+ * scripts/traceability/drm-check.ts, kept independent to avoid
19
+ * adding cross-package dependencies to the offline records CLI.
20
+ */
21
+ export interface DrmDocEntry {
22
+ action: 'CREATE' | 'UPDATE';
23
+ target: string;
24
+ reason: string;
25
+ }
26
+ export interface ParsedDrm {
27
+ id: string;
28
+ title: string;
29
+ status: string;
30
+ releasePlan?: string;
31
+ docEntries: DrmDocEntry[];
32
+ }
33
+ /**
34
+ * Parses a DRM:DOCS marker block from a markdown body.
35
+ *
36
+ * Extracts rows from the table between `<!-- DRM:DOCS_START -->` and
37
+ * `<!-- DRM:DOCS_END -->` markers. Each row maps an action (CREATE/UPDATE)
38
+ * to a target file path and a human-readable reason.
39
+ */
40
+ export declare function parseDrmDocsTable(body: string): DrmDocEntry[];
41
+ /**
42
+ * Reads all DRM markdown files from the repository.
43
+ *
44
+ * Searches known DRM folders (`docs/change/drm`, `docs/design-review`)
45
+ * and parses each file's frontmatter and DRM:DOCS table.
46
+ *
47
+ * @param rootDir - Repository root directory
48
+ * @returns Parsed DRM entries, sorted by ID
49
+ */
50
+ export declare function readDrmFiles(rootDir: string): ParsedDrm[];
51
+ /**
52
+ * Extracts the document ID from a file path.
53
+ *
54
+ * @example
55
+ * extractDocIdFromPath('docs/software/requirements/SRS-001.md') => 'SRS-001'
56
+ */
57
+ export declare function extractDocIdFromPath(target: string): string;
58
+ //# sourceMappingURL=drm-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drm-parser.d.ts","sourceRoot":"","sources":["../../src/github/drm-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAUH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,WAAW,EAAE,CAAC;CAC3B;AAMD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAyC7D;AASD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,CAkCzD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAG3D"}
package/dist/index.d.ts CHANGED
@@ -6,6 +6,10 @@ export { generatePrs } from './generators/prs-generator.js';
6
6
  export { generateSrs } from './generators/srs-generator.js';
7
7
  export { generatePta, buildTraceabilityMatrix } from './generators/pta-generator.js';
8
8
  export { generateRmr, buildRiskGraph } from './generators/rmr-generator.js';
9
+ export { generateVv } from './generators/vv-generator.js';
10
+ export { generateUef } from './generators/uef-generator.js';
11
+ export { generateCsf } from './generators/csf-generator.js';
12
+ export { generateCer } from './generators/cer-generator.js';
9
13
  export { loadConfig, RecordsConfigSchema } from './config.js';
10
14
  export type { RecordsConfig } from './config.js';
11
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,YAAY,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACrF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC9D,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,YAAY,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACrF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC9D,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -1193,11 +1193,257 @@ function generateSdd(input) {
1193
1193
  });
1194
1194
  }
1195
1195
 
1196
+ // src/generators/vv-generator.ts
1197
+ function generateVv(input) {
1198
+ const protocols = readDocuments(input.rootDir, "docs/test/protocols");
1199
+ const reports = readDocuments(input.rootDir, "docs/test/reports");
1200
+ const plans = readDocuments(input.rootDir, "docs/software/plans").filter(
1201
+ (d) => d.frontmatter.id.startsWith("STP-")
1202
+ );
1203
+ const docs = [...plans, ...protocols, ...reports];
1204
+ if (docs.length === 0) {
1205
+ console.warn("VV: no approved/effective V&V documents found \u2014 skipping");
1206
+ return null;
1207
+ }
1208
+ const content = [];
1209
+ content.push({ text: "Table of Contents", style: "h2", margin: [0, 0, 0, 10] });
1210
+ content.push({
1211
+ ol: docs.map((d) => ({
1212
+ text: `${d.frontmatter.id}: ${d.frontmatter.title}`,
1213
+ margin: [0, 2, 0, 2]
1214
+ }))
1215
+ });
1216
+ content.push({ text: "", pageBreak: "after" });
1217
+ for (const doc of docs) {
1218
+ content.push({
1219
+ text: `${doc.frontmatter.id}: ${doc.frontmatter.title}`,
1220
+ style: "h2",
1221
+ margin: [0, 10, 0, 6]
1222
+ });
1223
+ content.push(...markdownToPdfmake(doc.body));
1224
+ content.push({ text: "", margin: [0, 20, 0, 0] });
1225
+ }
1226
+ return buildDocumentDefinition({
1227
+ config: input.config,
1228
+ rootDir: input.rootDir,
1229
+ recordTitle: "Verification & Validation",
1230
+ recordId: "VV",
1231
+ version: input.version,
1232
+ date: input.date,
1233
+ revisionHistory: input.revisionHistory,
1234
+ content
1235
+ });
1236
+ }
1237
+
1238
+ // src/generators/uef-generator.ts
1239
+ function generateUef(input) {
1240
+ const plans = readDocuments(input.rootDir, "docs/usability");
1241
+ const useSpecs = readDocuments(input.rootDir, "docs/usability/use-specifications");
1242
+ const taskAnalyses = readDocuments(input.rootDir, "docs/usability/task-analyses");
1243
+ const evaluations = readDocuments(input.rootDir, "docs/usability/evaluations");
1244
+ const docs = [...plans, ...useSpecs, ...taskAnalyses, ...evaluations];
1245
+ if (docs.length === 0) {
1246
+ console.warn("UEF: no approved/effective usability documents found \u2014 skipping");
1247
+ return null;
1248
+ }
1249
+ const content = [];
1250
+ content.push({ text: "Table of Contents", style: "h2", margin: [0, 0, 0, 10] });
1251
+ content.push({
1252
+ ol: docs.map((d) => ({
1253
+ text: `${d.frontmatter.id}: ${d.frontmatter.title}`,
1254
+ margin: [0, 2, 0, 2]
1255
+ }))
1256
+ });
1257
+ content.push({ text: "", pageBreak: "after" });
1258
+ for (const doc of docs) {
1259
+ content.push({
1260
+ text: `${doc.frontmatter.id}: ${doc.frontmatter.title}`,
1261
+ style: "h2",
1262
+ margin: [0, 10, 0, 6]
1263
+ });
1264
+ content.push(...markdownToPdfmake(doc.body));
1265
+ content.push({ text: "", margin: [0, 20, 0, 0] });
1266
+ }
1267
+ return buildDocumentDefinition({
1268
+ config: input.config,
1269
+ rootDir: input.rootDir,
1270
+ recordTitle: "Usability Engineering File",
1271
+ recordId: "UEF",
1272
+ version: input.version,
1273
+ date: input.date,
1274
+ revisionHistory: input.revisionHistory,
1275
+ content
1276
+ });
1277
+ }
1278
+
1279
+ // src/generators/csf-generator.ts
1280
+ function generateCsf(input) {
1281
+ const plans = readDocuments(input.rootDir, "docs/cybersecurity/plans");
1282
+ const risks = readDocuments(input.rootDir, "docs/cybersecurity/risks");
1283
+ const sboms = readDocuments(input.rootDir, "docs/cybersecurity/sbom");
1284
+ const docs = [...plans, ...risks, ...sboms];
1285
+ if (docs.length === 0) {
1286
+ console.warn("CSF: no approved/effective cybersecurity documents found \u2014 skipping");
1287
+ return null;
1288
+ }
1289
+ const content = [];
1290
+ content.push({ text: "Table of Contents", style: "h2", margin: [0, 0, 0, 10] });
1291
+ content.push({
1292
+ ol: docs.map((d) => ({
1293
+ text: `${d.frontmatter.id}: ${d.frontmatter.title}`,
1294
+ margin: [0, 2, 0, 2]
1295
+ }))
1296
+ });
1297
+ content.push({ text: "", pageBreak: "after" });
1298
+ for (const doc of docs) {
1299
+ content.push({
1300
+ text: `${doc.frontmatter.id}: ${doc.frontmatter.title}`,
1301
+ style: "h2",
1302
+ margin: [0, 10, 0, 6]
1303
+ });
1304
+ content.push(...markdownToPdfmake(doc.body));
1305
+ content.push({ text: "", margin: [0, 20, 0, 0] });
1306
+ }
1307
+ return buildDocumentDefinition({
1308
+ config: input.config,
1309
+ rootDir: input.rootDir,
1310
+ recordTitle: "Cybersecurity File",
1311
+ recordId: "CSF",
1312
+ version: input.version,
1313
+ date: input.date,
1314
+ revisionHistory: input.revisionHistory,
1315
+ content
1316
+ });
1317
+ }
1318
+
1319
+ // src/generators/cer-generator.ts
1320
+ function generateCer(input) {
1321
+ const docs = readDocuments(input.rootDir, "docs/clinical");
1322
+ if (docs.length === 0) {
1323
+ console.warn("CER: no approved/effective clinical documents found \u2014 skipping");
1324
+ return null;
1325
+ }
1326
+ const content = [];
1327
+ content.push({ text: "Table of Contents", style: "h2", margin: [0, 0, 0, 10] });
1328
+ content.push({
1329
+ ol: docs.map((d) => ({
1330
+ text: `${d.frontmatter.id}: ${d.frontmatter.title}`,
1331
+ margin: [0, 2, 0, 2]
1332
+ }))
1333
+ });
1334
+ content.push({ text: "", pageBreak: "after" });
1335
+ for (const doc of docs) {
1336
+ content.push({
1337
+ text: `${doc.frontmatter.id}: ${doc.frontmatter.title}`,
1338
+ style: "h2",
1339
+ margin: [0, 10, 0, 6]
1340
+ });
1341
+ content.push(...markdownToPdfmake(doc.body));
1342
+ content.push({ text: "", margin: [0, 20, 0, 0] });
1343
+ }
1344
+ return buildDocumentDefinition({
1345
+ config: input.config,
1346
+ rootDir: input.rootDir,
1347
+ recordTitle: "Clinical Evaluation Report",
1348
+ recordId: "CER",
1349
+ version: input.version,
1350
+ date: input.date,
1351
+ revisionHistory: input.revisionHistory,
1352
+ content
1353
+ });
1354
+ }
1355
+
1196
1356
  // src/github/changelog.ts
1197
1357
  import { execFileSync } from "child_process";
1198
- import { resolve as resolve5 } from "path";
1199
- import { readFileSync as readFileSync5 } from "fs";
1358
+ import { resolve as resolve6 } from "path";
1359
+ import { readFileSync as readFileSync6 } from "fs";
1360
+ import matter3 from "gray-matter";
1361
+
1362
+ // src/github/drm-parser.ts
1363
+ import { readdirSync as readdirSync2, readFileSync as readFileSync5, existsSync as existsSync4 } from "fs";
1364
+ import { resolve as resolve5, join as join2 } from "path";
1200
1365
  import matter2 from "gray-matter";
1366
+ function parseDrmDocsTable(body) {
1367
+ const startTag = "<!-- DRM:DOCS_START -->";
1368
+ const endTag = "<!-- DRM:DOCS_END -->";
1369
+ const startIdx = body.indexOf(startTag);
1370
+ const endIdx = body.indexOf(endTag);
1371
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) return [];
1372
+ const block = body.slice(startIdx + startTag.length, endIdx).trim();
1373
+ const lines = block.split("\n");
1374
+ const entries = [];
1375
+ for (const line of lines) {
1376
+ if (!line.startsWith("|")) continue;
1377
+ const cells = line.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
1378
+ if (cells.length < 2) continue;
1379
+ const action = cells[0].toUpperCase();
1380
+ if (action === "ACTION" || /^-+$/.test(action) || action === "NONE") continue;
1381
+ if (action !== "CREATE" && action !== "UPDATE") continue;
1382
+ const target = cells[1];
1383
+ const reason = cells.length > 2 ? cells[cells.length - 1] : "";
1384
+ entries.push({
1385
+ action,
1386
+ target,
1387
+ reason
1388
+ });
1389
+ }
1390
+ return entries;
1391
+ }
1392
+ var DRM_FOLDERS = ["docs/change/drm", "docs/design-review"];
1393
+ function readDrmFiles(rootDir) {
1394
+ const drms = [];
1395
+ for (const folder of DRM_FOLDERS) {
1396
+ const fullPath = resolve5(rootDir, folder);
1397
+ if (!existsSync4(fullPath)) continue;
1398
+ let files;
1399
+ try {
1400
+ files = readdirSync2(fullPath).filter((f) => f.endsWith(".md"));
1401
+ } catch {
1402
+ continue;
1403
+ }
1404
+ for (const file of files) {
1405
+ const filePath = join2(folder, file);
1406
+ const raw = readFileSync5(resolve5(rootDir, filePath), "utf-8");
1407
+ const { data, content } = matter2(raw);
1408
+ if (!data.id || !data.title) continue;
1409
+ const docEntries = parseDrmDocsTable(content);
1410
+ drms.push({
1411
+ id: data.id,
1412
+ title: data.title,
1413
+ status: data.status ?? "unknown",
1414
+ releasePlan: data.release_plan,
1415
+ docEntries
1416
+ });
1417
+ }
1418
+ }
1419
+ return drms.sort((a, b) => a.id.localeCompare(b.id, void 0, { numeric: true }));
1420
+ }
1421
+ function extractDocIdFromPath(target) {
1422
+ const filename = target.split("/").pop() ?? target;
1423
+ return filename.replace(/\.md$/, "");
1424
+ }
1425
+
1426
+ // src/github/changelog.ts
1427
+ function buildSmartChangelog(rootDir, version, date) {
1428
+ const drms = readDrmFiles(rootDir);
1429
+ const entries = buildRevisionEntriesFromDrms(drms, version, date);
1430
+ if (entries.length === 0) {
1431
+ return buildChangelog(rootDir, version, date);
1432
+ }
1433
+ return entries;
1434
+ }
1435
+ function buildRevisionEntriesFromDrms(drms, version, date) {
1436
+ const withDocEntries = drms.filter((drm) => drm.docEntries.length > 0);
1437
+ if (withDocEntries.length === 0) return [];
1438
+ return withDocEntries.map((drm) => {
1439
+ const reasons = drm.docEntries.map((entry) => {
1440
+ const docId = extractDocIdFromPath(entry.target);
1441
+ return entry.reason ? `${docId}: ${entry.reason}` : docId;
1442
+ });
1443
+ const description = `[${drm.id}] ${reasons.join("; ")}`;
1444
+ return { revision: version, date, description };
1445
+ });
1446
+ }
1201
1447
  function buildChangelog(rootDir, version, date) {
1202
1448
  let previousTag = null;
1203
1449
  try {
@@ -1230,8 +1476,8 @@ function buildChangelog(rootDir, version, date) {
1230
1476
  const descriptions = [];
1231
1477
  for (const file of changedFiles.slice(0, 20)) {
1232
1478
  try {
1233
- const raw = readFileSync5(resolve5(rootDir, file), "utf-8");
1234
- const { data } = matter2(raw);
1479
+ const raw = readFileSync6(resolve6(rootDir, file), "utf-8");
1480
+ const { data } = matter3(raw);
1235
1481
  if (data.id && data.title) {
1236
1482
  descriptions.push(`${data.id}: ${data.title}`);
1237
1483
  }
@@ -1251,7 +1497,11 @@ var RECORD_GENERATORS = [
1251
1497
  { name: "ARC-Software-Architecture-Description", fn: generateArc },
1252
1498
  { name: "SDD-Software-Detailed-Design", fn: generateSdd },
1253
1499
  { name: "PTA-Product-Traceability-Analysis", fn: generatePta },
1254
- { name: "RMR-Risk-Management-Report", fn: generateRmr }
1500
+ { name: "RMR-Risk-Management-Report", fn: generateRmr },
1501
+ { name: "VV-Verification-And-Validation", fn: generateVv },
1502
+ { name: "UEF-Usability-Engineering-File", fn: generateUef },
1503
+ { name: "CSF-Cybersecurity-File", fn: generateCsf },
1504
+ { name: "CER-Clinical-Evaluation-Report", fn: generateCer }
1255
1505
  ];
1256
1506
  var noopUrlResolver = {
1257
1507
  resolve: () => {
@@ -1273,7 +1523,7 @@ async function generate(options) {
1273
1523
  const outDir = options.outputDir ?? pathResolve(options.rootDir, "out");
1274
1524
  mkdirSync(outDir, { recursive: true });
1275
1525
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1276
- const revisionHistory = buildChangelog(options.rootDir, options.version, date);
1526
+ const revisionHistory = buildSmartChangelog(options.rootDir, options.version, date);
1277
1527
  const fonts = {
1278
1528
  Helvetica: {
1279
1529
  normal: "Helvetica",
@@ -1313,10 +1563,14 @@ export {
1313
1563
  buildRiskGraph,
1314
1564
  buildTraceabilityMatrix,
1315
1565
  generate,
1566
+ generateCer,
1567
+ generateCsf,
1316
1568
  generatePrs,
1317
1569
  generatePta,
1318
1570
  generateRmr,
1319
1571
  generateSrs,
1572
+ generateUef,
1320
1573
  generateUrs,
1574
+ generateVv,
1321
1575
  loadConfig
1322
1576
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pactosigna/records",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "description": "Generate audit-ready regulatory PDF records from PactoSigna-compliant git repositories",
6
6
  "publishConfig": {
@@ -41,7 +41,7 @@
41
41
  "typescript": "^5.7.2",
42
42
  "vitest": "^3.0.0",
43
43
  "zod": "^3.24.1",
44
- "@pactosigna/schemas": "0.1.0"
44
+ "@pactosigna/schemas": "0.1.19"
45
45
  },
46
46
  "repository": {
47
47
  "type": "git",