@mitre/hdf-generators 3.2.0 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { BaselineRequirement, Description, HdfBaseline, Reference } from "@mitre/hdf-schema";
1
+ import { BaselineRequirement, Description, HDFBaseline, Reference } from "@mitre/hdf-schema";
2
2
 
3
3
  //#region src/types.d.ts
4
4
  /**
@@ -114,7 +114,7 @@ interface DeltaStatistics {
114
114
  * Complete result of an upgrade/delta operation.
115
115
  */
116
116
  interface UpgradeResult {
117
- baseline: HdfBaseline;
117
+ baseline: HDFBaseline;
118
118
  profile?: InSpecProfile;
119
119
  linkRecords: LinkRecord[];
120
120
  statistics: DeltaStatistics;
@@ -186,7 +186,7 @@ declare function generateControlStub(req: BaselineRequirement): string;
186
186
  * Uses string interpolation — no YAML library needed since the structure
187
187
  * is fixed and shallow.
188
188
  */
189
- declare function generateInSpecYml(baseline: HdfBaseline, options?: GeneratorOptions): string;
189
+ declare function generateInSpecYml(baseline: HDFBaseline, options?: GeneratorOptions): string;
190
190
  //#endregion
191
191
  //#region src/profile-generator.d.ts
192
192
  /**
@@ -196,7 +196,7 @@ declare function generateInSpecYml(baseline: HdfBaseline, options?: GeneratorOpt
196
196
  * control filenames to Ruby source code. No file I/O — the CLI
197
197
  * is responsible for writing files to disk.
198
198
  */
199
- declare function generateInSpecProfile(baseline: HdfBaseline, options?: GeneratorOptions): InSpecProfile;
199
+ declare function generateInSpecProfile(baseline: HDFBaseline, options?: GeneratorOptions): InSpecProfile;
200
200
  //#endregion
201
201
  //#region src/delta.d.ts
202
202
  /**
@@ -210,13 +210,13 @@ declare function generateInSpecProfile(baseline: HdfBaseline, options?: Generato
210
210
  * (a control removed from upstream should not survive the upgrade).
211
211
  * Set opts.keepUnmatched to retain them instead.
212
212
  */
213
- declare function generateUpgrade(currentBaseline: HdfBaseline, upstreamBaseline: HdfBaseline, linkRecords: LinkRecord[], opts?: UpgradeOptions): UpgradeResult;
213
+ declare function generateUpgrade(currentBaseline: HDFBaseline, upstreamBaseline: HDFBaseline, linkRecords: LinkRecord[], opts?: UpgradeOptions): UpgradeResult;
214
214
  /**
215
215
  * Legacy entry point. Wraps generateUpgrade for backward compatibility.
216
216
  *
217
217
  * @deprecated Use generateUpgrade instead.
218
218
  */
219
- declare function generateDelta(newBaseline: HdfBaseline, linkRecords: LinkRecord[], oldCodeMap: Map<string, string>, opts?: DeltaOptions, oldControlCount?: number): DeltaResult;
219
+ declare function generateDelta(newBaseline: HDFBaseline, linkRecords: LinkRecord[], oldCodeMap: Map<string, string>, opts?: DeltaOptions, oldControlCount?: number): DeltaResult;
220
220
  //#endregion
221
221
  //#region src/delta-report.d.ts
222
222
  /**
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/ruby-escape.ts","../src/control-stub.ts","../src/inspec-yml.ts","../src/profile-generator.ts","../src/merge.ts","../src/delta.ts","../src/delta-report.ts"],"sourcesContent":["/**\n * Escape a string for use as a Ruby string literal.\n *\n * Strategy:\n * - If the string contains both single and double quotes → %q() wrapper\n * - If the string contains only single quotes → double-quoted with escaping\n * - Otherwise → single-quoted with escaping\n *\n * Ported from ts-inspec-objects escapeQuotes(), cross-referenced with\n * inspec-parser's RubyRebuilder.\n */\nexport function escapeQuotes(s: string): string {\n const hasSingle = s.includes(\"'\");\n const hasDouble = s.includes('\"');\n\n if (hasSingle && hasDouble) {\n // %q() — escape backslashes before ) so Ruby doesn't treat \\) as escaped delimiter\n return `%q(${s.replace(/\\\\\\)/g, '\\\\\\\\)')})`;\n }\n\n if (hasSingle) {\n // Double-quoted: escape backslashes, then double quotes\n return `\"${s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\"`;\n }\n\n // Single-quoted: escape backslashes, then single quotes\n return `'${s.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\")}'`;\n}\n","import type { BaselineRequirement } from '@mitre/hdf-schema';\nimport { escapeQuotes } from './ruby-escape.js';\n\n/**\n * Detects when a code string already starts with `control 'ID' do`,\n * meaning it's a complete InSpec control file — not a body fragment.\n */\nconst FULL_CONTROL_BLOCK = /^\\s*control\\s+['\"]([^'\"]+)['\"]\\s+do\\b/;\n\n/**\n * Generate a Ruby InSpec control stub from an HDF BaselineRequirement.\n *\n * Output follows the InSpec DSL ordering convention:\n * control 'ID' do\n * title ...\n * desc ...\n * desc 'check', ...\n * impact ...\n * tag key: value\n * <code or stub comment>\n * end\n */\nexport function generateControlStub(req: BaselineRequirement): string {\n // If code is already a complete `control 'ID' do ... end` block (e.g.\n // from `-c controls/` reading whole .rb files), emit it as-is — wrapping\n // it again would produce nested control blocks, which InSpec rejects.\n // When the inner ID differs from req.id (e.g. an upgrade rename match\n // where current's code was carried into a renamed requirement), rewrite\n // the wrapper to match req.id so the file remains valid InSpec.\n if (req.code) {\n const m = FULL_CONTROL_BLOCK.exec(req.code);\n if (m && m[1] !== undefined) {\n const innerID = m[1];\n if (innerID === req.id) {\n return req.code;\n }\n const start = m.index + m[0].indexOf(innerID);\n return req.code.slice(0, start) + req.id + req.code.slice(start + innerID.length);\n }\n }\n\n const lines: string[] = [];\n\n lines.push(`control '${req.id}' do`);\n\n // Title\n if (req.title) {\n lines.push(` title ${escapeQuotes(req.title)}`);\n }\n\n // Descriptions: default first (as bare `desc`), then labeled\n const defaultDesc = req.descriptions.find((d) => d.label === 'default');\n if (defaultDesc) {\n lines.push(` desc ${escapeQuotes(defaultDesc.data)}`);\n }\n\n const seenDefault = new Set<string>();\n for (const d of req.descriptions) {\n if (d.label === 'default') {\n if (seenDefault.has('default')) {\n continue; // skip duplicate default\n }\n seenDefault.add('default');\n continue; // already emitted above as bare desc\n }\n lines.push(` desc '${d.label}', ${escapeQuotes(d.data)}`);\n }\n\n // Impact — always render with at least one decimal place for 0 and whole numbers\n if (req.impact !== undefined) {\n const impactStr = Number.isInteger(req.impact)\n ? req.impact.toFixed(1)\n : String(req.impact);\n lines.push(` impact ${impactStr}`);\n }\n\n // Tags\n for (const [key, value] of Object.entries(req.tags)) {\n lines.push(` ${formatTag(key, value)}`);\n }\n\n // Code body or stub placeholder\n if (req.code) {\n lines.push('');\n lines.push(req.code);\n } else {\n lines.push('');\n lines.push(' # TODO: Add InSpec test code here');\n }\n\n lines.push('end');\n lines.push(''); // trailing newline\n\n return lines.join('\\n');\n}\n\n/** Format a single tag key-value pair as Ruby DSL. */\nfunction formatTag(key: string, value: unknown): string {\n if (value === null || value === undefined) {\n return `tag ${key}: nil`;\n }\n\n if (Array.isArray(value)) {\n // Ruby array syntax: tag key: ['val1', 'val2']\n const items = value.map((v: unknown) => `'${String(v)}'`).join(', ');\n return `tag ${key}: [${items}]`;\n }\n\n if (typeof value === 'boolean') {\n return `tag ${key}: ${value}`;\n }\n\n if (typeof value === 'string') {\n return `tag ${key}: ${escapeQuotes(value)}`;\n }\n\n // Fallback for other types (numbers, objects)\n return `tag ${key}: ${JSON.stringify(value)}`;\n}\n","import type { HdfBaseline } from '@mitre/hdf-schema';\nimport type { GeneratorOptions } from './types.js';\n\n/**\n * Generate an inspec.yml YAML string from an HDF Baseline and options.\n *\n * Uses string interpolation — no YAML library needed since the structure\n * is fixed and shallow.\n */\nexport function generateInSpecYml(\n baseline: HdfBaseline,\n options?: GeneratorOptions,\n): string {\n const lines: string[] = [];\n const meta = options?.metadata;\n\n // Required: name\n lines.push(`name: ${baseline.name}`);\n\n // Optional metadata fields\n if (baseline.title) {\n lines.push(`title: ${baseline.title}`);\n }\n\n const maintainer = meta?.maintainer ?? baseline.maintainer;\n if (maintainer) {\n lines.push(`maintainer: ${maintainer}`);\n }\n\n const copyright = meta?.copyright ?? baseline.copyright;\n if (copyright) {\n lines.push(`copyright: ${copyright}`);\n }\n\n const license = meta?.license ?? baseline.license;\n if (license) {\n lines.push(`license: ${license}`);\n }\n\n if (baseline.summary) {\n lines.push(`summary: ${baseline.summary}`);\n }\n\n // Version: metadata override takes priority\n const version = meta?.version ?? baseline.version;\n if (version) {\n lines.push(`version: '${version}'`);\n }\n\n // InSpec version constraint\n const inspecVersion = options?.inspecVersion ?? '~>6.0';\n lines.push(`inspec_version: '${inspecVersion}'`);\n\n // Supports array\n if (baseline.supports && baseline.supports.length > 0) {\n lines.push('supports:');\n for (const support of baseline.supports) {\n const entries: string[] = [];\n if (support.platformName) {\n entries.push(` platform-name: ${support.platformName}`);\n }\n if (support.platformFamily) {\n entries.push(` platform-family: ${support.platformFamily}`);\n }\n if (support.platform) {\n entries.push(` platform: ${support.platform}`);\n }\n if (support.release) {\n entries.push(` release: ${support.release}`);\n }\n if (entries.length > 0) {\n lines.push(`- ${entries[0]!.trimStart()}`);\n for (let i = 1; i < entries.length; i++) {\n lines.push(entries[i]!);\n }\n }\n }\n }\n\n // Depends array\n if (baseline.depends && baseline.depends.length > 0) {\n lines.push('depends:');\n for (const dep of baseline.depends) {\n const entries: string[] = [];\n if (dep.name) entries.push(`name: ${dep.name}`);\n if (dep.git) entries.push(`git: ${dep.git}`);\n if (dep.url) entries.push(`url: ${dep.url}`);\n if (dep.path) entries.push(`path: ${dep.path}`);\n if (dep.branch) entries.push(`branch: ${dep.branch}`);\n if (dep.compliance) entries.push(`compliance: ${dep.compliance}`);\n if (dep.supermarket) entries.push(`supermarket: ${dep.supermarket}`);\n if (entries.length > 0) {\n lines.push(`- ${entries[0]}`);\n for (let i = 1; i < entries.length; i++) {\n lines.push(` ${entries[i]}`);\n }\n }\n }\n }\n\n // Inputs array\n if (baseline.inputs && baseline.inputs.length > 0) {\n lines.push('inputs:');\n for (const input of baseline.inputs) {\n for (const [key, value] of Object.entries(input)) {\n lines.push(`- ${key}: ${formatYamlValue(value)}`);\n }\n }\n }\n\n lines.push(''); // trailing newline\n return lines.join('\\n');\n}\n\n/** Format a value for inline YAML output. */\nfunction formatYamlValue(value: unknown): string {\n if (typeof value === 'boolean') return String(value);\n if (typeof value === 'number') return String(value);\n if (typeof value === 'string') return value;\n return JSON.stringify(value);\n}\n","import type { HdfBaseline } from '@mitre/hdf-schema';\nimport type { GeneratorOptions, InSpecProfile } from './types.js';\nimport { generateControlStub } from './control-stub.js';\nimport { generateInSpecYml } from './inspec-yml.js';\n\n/**\n * Generate an in-memory InSpec profile from an HDF Baseline.\n *\n * Returns an InSpecProfile with inspec.yml content and a Map of\n * control filenames to Ruby source code. No file I/O — the CLI\n * is responsible for writing files to disk.\n */\nexport function generateInSpecProfile(\n baseline: HdfBaseline,\n options?: GeneratorOptions,\n): InSpecProfile {\n const inspecYml = generateInSpecYml(baseline, options);\n const controls = new Map<string, string>();\n\n if (baseline.requirements.length === 0) {\n return { inspecYml, controls };\n }\n\n if (options?.singleFile) {\n // All controls in a single file\n const stubs = baseline.requirements.map((req) => generateControlStub(req));\n controls.set('controls/controls.rb', stubs.join('\\n'));\n } else {\n // One file per control — sanitize ID for safe filenames\n for (const req of baseline.requirements) {\n const safeId = req.id.replace(/\\.\\./g, '').replace(/[/\\\\]/g, '') || 'unknown';\n const filename = `controls/${safeId}.rb`;\n controls.set(filename, generateControlStub(req));\n }\n }\n\n return { inspecYml, controls };\n}\n","import type { BaselineRequirement, Description, Reference } from '@mitre/hdf-schema';\n\nexport type PreferSide = 'current' | 'upstream' | undefined;\n\n/**\n * Smart-merge a current and upstream requirement.\n *\n * Default (no prefer):\n * - ID: always upstream\n * - Scalars (title, impact, severity): upstream wins\n * - Tags: union, upstream wins key conflicts\n * - Descriptions: union by label, upstream wins on same label\n * - Code: current (preserve tests)\n * - Refs: union (deduplicated)\n *\n * prefer \"current\": scalars from current, current wins tag/desc conflicts\n * prefer \"upstream\": everything from upstream (full replacement)\n */\nexport function mergeRequirement(\n current: BaselineRequirement,\n upstream: BaselineRequirement,\n prefer?: PreferSide,\n): BaselineRequirement {\n const merged: BaselineRequirement = {\n // ID always comes from upstream\n id: upstream.id,\n\n // Scalars\n title: prefer === 'current' ? current.title : upstream.title,\n impact: prefer === 'current' ? current.impact : upstream.impact,\n severity: prefer === 'current' ? current.severity : upstream.severity,\n\n // Collections\n tags: mergeTags(current.tags, upstream.tags, prefer),\n descriptions: mergeDescriptions(current.descriptions, upstream.descriptions, prefer),\n refs: mergeRefs(current.refs, upstream.refs, prefer),\n\n // Code: current by default, upstream only with --prefer upstream\n code: prefer === 'upstream'\n ? upstream.code\n : (current.code ?? upstream.code),\n\n // SourceLocation follows scalars\n sourceLocation: prefer === 'current' ? current.sourceLocation : upstream.sourceLocation,\n };\n\n // Clean up undefined optional fields\n if (merged.code === undefined) delete merged.code;\n if (merged.severity === undefined) delete merged.severity;\n if (merged.sourceLocation === undefined) delete merged.sourceLocation;\n if (merged.refs === undefined || merged.refs.length === 0) delete merged.refs;\n\n return merged;\n}\n\n/**\n * Merge two tag maps.\n *\n * Default: union of keys; upstream wins on key conflicts.\n * prefer \"current\": union; current wins on key conflicts.\n * prefer \"upstream\": upstream replaces all.\n */\nexport function mergeTags(\n current: Record<string, any>, // eslint-disable-line @typescript-eslint/no-explicit-any\n upstream: Record<string, any>, // eslint-disable-line @typescript-eslint/no-explicit-any\n prefer?: PreferSide,\n): Record<string, any> { // eslint-disable-line @typescript-eslint/no-explicit-any\n if (prefer === 'upstream') {\n return { ...upstream };\n }\n\n const merged: Record<string, any> = { ...current }; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n for (const [key, value] of Object.entries(upstream)) {\n if (prefer === 'current') {\n if (!(key in merged)) {\n merged[key] = value;\n }\n } else {\n // Default: upstream wins on conflict\n merged[key] = value;\n }\n }\n\n return merged;\n}\n\n/**\n * Merge two description arrays by label.\n *\n * Default: union by label; upstream wins on label conflicts.\n * prefer \"current\": union; current wins on label conflicts.\n * prefer \"upstream\": upstream replaces all.\n */\nexport function mergeDescriptions(\n current: Description[],\n upstream: Description[],\n prefer?: PreferSide,\n): Description[] {\n if (prefer === 'upstream') {\n return [...upstream];\n }\n\n const byLabel = new Map<string, Description>();\n const order: string[] = [];\n\n // Start with current\n for (const d of current) {\n byLabel.set(d.label, d);\n order.push(d.label);\n }\n\n // Apply upstream\n for (const d of upstream) {\n if (byLabel.has(d.label)) {\n if (prefer !== 'current') {\n // Default: upstream wins\n byLabel.set(d.label, d);\n }\n } else {\n byLabel.set(d.label, d);\n order.push(d.label);\n }\n }\n\n return order.map(label => byLabel.get(label)!);\n}\n\n/**\n * Merge two reference arrays.\n *\n * Default: union, deduplicated by JSON key.\n * prefer \"current\": current only.\n * prefer \"upstream\": upstream only.\n */\nexport function mergeRefs(\n current?: Reference[],\n upstream?: Reference[],\n prefer?: PreferSide,\n): Reference[] | undefined {\n if (prefer === 'current') {\n return current ? [...current] : undefined;\n }\n if (prefer === 'upstream') {\n return upstream ? [...upstream] : undefined;\n }\n\n // Default: union, deduplicated\n if (!current && !upstream) return undefined;\n\n const seen = new Set<string>();\n const result: Reference[] = [];\n\n const addRef = (r: Reference): void => {\n const key = JSON.stringify(r);\n if (!seen.has(key)) {\n seen.add(key);\n result.push(r);\n }\n };\n\n if (current) current.forEach(addRef);\n if (upstream) upstream.forEach(addRef);\n\n return result.length > 0 ? result : undefined;\n}\n","import type { HdfBaseline, BaselineRequirement } from '@mitre/hdf-schema';\nimport type { UpgradeResult, UpgradeOptions, DeltaResult, DeltaOptions, LinkRecord, DeltaStatistics } from './delta-types.js';\nimport type { InSpecProfile } from './types.js';\nimport { mergeRequirement } from './merge.js';\nimport { generateControlStub } from './control-stub.js';\nimport { generateInSpecYml } from './inspec-yml.js';\n\n/**\n * Generate an upgraded HDF Baseline by smart-merging current and upstream requirements.\n *\n * For each upstream requirement:\n * - If matched: smart-merge current + upstream fields per mergeRequirement semantics\n * - If unmatched: include upstream requirement as-is (new control)\n *\n * Current requirements with no upstream match are dropped by default\n * (a control removed from upstream should not survive the upgrade).\n * Set opts.keepUnmatched to retain them instead.\n */\nexport function generateUpgrade(\n currentBaseline: HdfBaseline,\n upstreamBaseline: HdfBaseline,\n linkRecords: LinkRecord[],\n opts?: UpgradeOptions,\n): UpgradeResult {\n const prefer = opts?.prefer;\n\n // Build lookup: newId -> LinkRecord (prefer primary over related)\n const linkByNewId = new Map<string, LinkRecord>();\n for (const lr of linkRecords) {\n const existing = linkByNewId.get(lr.newId);\n if (!existing || lr.relationship === 'primary') {\n linkByNewId.set(lr.newId, lr);\n }\n }\n\n // Build lookup: oldId -> current requirement\n const currentById = new Map<string, BaselineRequirement>();\n for (const req of currentBaseline.requirements) {\n currentById.set(req.id, req);\n }\n\n // Track which current IDs got matched\n const matchedCurrentIds = new Set<string>();\n\n // Merge upstream requirements\n const mergedReqs: BaselineRequirement[] = [];\n for (const upReq of upstreamBaseline.requirements) {\n const link = linkByNewId.get(upReq.id);\n if (link && link.oldId) {\n const curReq = currentById.get(link.oldId);\n if (curReq) {\n matchedCurrentIds.add(link.oldId);\n\n // Handle --noCode\n const effectiveCurrent = opts?.noCode\n ? { ...curReq, code: undefined }\n : curReq;\n\n mergedReqs.push(mergeRequirement(effectiveCurrent, upReq, prefer));\n continue;\n }\n }\n // Unmatched upstream: include as-is\n mergedReqs.push(upReq);\n }\n\n // Include unmatched current requirements only when explicitly opted in.\n // By default they're dropped — a control removed from upstream should\n // not survive the upgrade, matching SAF CLI delta semantics.\n if (opts?.keepUnmatched) {\n for (const curReq of currentBaseline.requirements) {\n if (!matchedCurrentIds.has(curReq.id)) {\n mergedReqs.push(curReq);\n }\n }\n }\n\n // Build upgraded baseline (use upstream metadata)\n const upgradedBaseline: HdfBaseline = {\n ...upstreamBaseline,\n requirements: mergedReqs,\n };\n\n // Compute statistics\n const statistics = computeStatistics(linkRecords, currentBaseline.requirements.length, upstreamBaseline.requirements.length);\n\n const result: UpgradeResult = {\n baseline: upgradedBaseline,\n linkRecords,\n statistics,\n };\n\n // Generate InSpec profile if requested\n const outputFormat = opts?.outputFormat ?? '';\n if (outputFormat === 'inspec' || outputFormat === 'both' || outputFormat === '') {\n result.profile = generateProfileFromBaseline(upgradedBaseline, opts);\n }\n\n return result;\n}\n\n/**\n * Legacy entry point. Wraps generateUpgrade for backward compatibility.\n *\n * @deprecated Use generateUpgrade instead.\n */\nexport function generateDelta(\n newBaseline: HdfBaseline,\n linkRecords: LinkRecord[],\n oldCodeMap: Map<string, string>,\n opts?: DeltaOptions,\n oldControlCount?: number,\n): DeltaResult {\n // Build synthetic current baseline from code map\n const currentReqs = buildCurrentReqsFromCodeMap(linkRecords, oldCodeMap);\n const currentBaseline: HdfBaseline = {\n name: 'current',\n requirements: currentReqs,\n groups: [],\n supports: [],\n };\n\n const result = generateUpgrade(currentBaseline, newBaseline, linkRecords, opts);\n\n // Patch statistics to use provided old control count\n result.statistics.oldControlsLength = oldControlCount ?? 0;\n\n return result;\n}\n\nfunction buildCurrentReqsFromCodeMap(\n linkRecords: LinkRecord[],\n oldCodeMap: Map<string, string>,\n): BaselineRequirement[] {\n const seen = new Set<string>();\n const reqs: BaselineRequirement[] = [];\n\n for (const lr of linkRecords) {\n if (!lr.oldId || seen.has(lr.oldId)) continue;\n seen.add(lr.oldId);\n\n const req: BaselineRequirement = {\n id: lr.oldId,\n impact: 0,\n tags: {},\n descriptions: [{ label: 'default', data: '' }],\n };\n const code = oldCodeMap.get(lr.oldId);\n if (code) {\n req.code = code;\n }\n reqs.push(req);\n }\n return reqs;\n}\n\nfunction generateProfileFromBaseline(baseline: HdfBaseline, opts?: UpgradeOptions): InSpecProfile {\n const controls = new Map<string, string>();\n const allStubs: string[] = [];\n\n for (const req of baseline.requirements) {\n const ruby = generateControlStub(req);\n\n if (opts?.singleFile) {\n allStubs.push(ruby);\n } else {\n const safeId = req.id.replace(/\\.\\./g, '').replace(/[/\\\\]/g, '') || 'unknown';\n controls.set(`controls/${safeId}.rb`, ruby);\n }\n }\n\n if (opts?.singleFile && allStubs.length > 0) {\n controls.set('controls/controls.rb', allStubs.join('\\n'));\n }\n\n const inspecYml = generateInSpecYml(baseline, {\n singleFile: opts?.singleFile,\n metadata: opts?.metadata,\n inspecVersion: opts?.inspecVersion,\n });\n\n return { inspecYml, controls };\n}\n\nfunction computeStatistics(linkRecords: LinkRecord[], oldControlCount: number, newControlCount: number): DeltaStatistics {\n let match = 0;\n let posMisMatch = 0;\n let dupMatch = 0;\n let noMatch = 0;\n\n for (const lr of linkRecords) {\n if (lr.relationship === 'related') {\n dupMatch++;\n } else if (lr.relationship === 'no-match') {\n noMatch++;\n } else if (lr.potentialMismatch) {\n posMisMatch++;\n } else {\n match++;\n }\n }\n\n return {\n oldControlsLength: oldControlCount,\n newControlsLength: newControlCount,\n totalMappedControls: match + posMisMatch + dupMatch,\n match,\n posMisMatch,\n dupMatch,\n noMatch,\n };\n}\n","import type { DeltaResult, LinkRecord } from './delta-types.js';\n\n/**\n * JSON report payload for delta operations.\n * SAF CLI-compatible: { links: LinkRecord[] }\n */\nexport interface DeltaJsonReport {\n links: LinkRecord[];\n}\n\n/**\n * Generate a structured JSON report from a delta result.\n * Matches SAF CLI's delta.json format: { ...diff, links }.\n */\nexport function generateDeltaJson(result: DeltaResult): DeltaJsonReport {\n return {\n links: result.linkRecords,\n };\n}\n\n/**\n * Format a per-link match method description matching SAF CLI's logMatchMethod.\n */\nfunction formatMatchMethod(lr: LinkRecord): string {\n const confidencePct = (lr.confidence * 100).toFixed(0) + '%';\n switch (lr.matchMethod) {\n case 'srgDeterministic':\n return `SRG deterministic (${lr.srg ?? '?'}) [${lr.relationship}]`;\n case 'srgCciTiebreak':\n return `SRG block + CCI tiebreak (Jaccard=${confidencePct}) [${lr.relationship}]`;\n case 'vendorFuzzyTitle':\n return `Vendor fuzzy title (confidence=${confidencePct}) [${lr.relationship}]`;\n case 'exactId':\n return `Exact ID [${lr.relationship}]`;\n case 'cciMatch':\n return `CCI match [${lr.relationship}]`;\n case 'fuzzyTitle':\n return `Fuzzy title (confidence=${confidencePct}) [${lr.relationship}]`;\n case 'none':\n return 'No match';\n default:\n return `${lr.matchMethod} [${lr.relationship}]`;\n }\n}\n\n/**\n * Generate a Markdown report from a delta result.\n * Matches SAF CLI's delta.md format: mapping table, control counts,\n * match statistics, and statistics validation.\n */\nexport function generateDeltaMarkdown(result: DeltaResult): string {\n const lines: string[] = [];\n const stats = result.statistics;\n\n // Mapping results — SAF CLI format: Old Control -> New Control\n if (result.linkRecords.length > 0) {\n lines.push('Mapping Results ===========================================================================');\n lines.push('\\tOld Control -> New Control');\n for (const lr of result.linkRecords) {\n if (lr.relationship !== 'no-match' && lr.oldId) {\n lines.push(`\\t ${lr.oldId} -> ${lr.newId}`);\n }\n }\n\n const totalMapped = stats.totalMappedControls;\n lines.push(`Total Mapped Controls: ${totalMapped}`);\n lines.push('');\n }\n\n // Control counts\n lines.push('Control Counts ===========================');\n lines.push(`Total Controls Available for Delta: ${stats.oldControlsLength}`);\n lines.push(` Total Controls Found on XCCDF: ${stats.newControlsLength}`);\n lines.push('');\n\n // Match statistics\n lines.push('Match Statistics =========================');\n lines.push(` Match Controls: ${stats.match}`);\n lines.push(` Possible Mismatch Controls: ${stats.posMisMatch}`);\n lines.push(` Related Match Controls: ${stats.dupMatch}`);\n lines.push(` No Match Controls: ${stats.noMatch}`);\n lines.push('');\n\n // Statistics validation\n lines.push('Statistics Validation =============================================');\n const totalMapped = stats.totalMappedControls;\n const matchMappedValid = (stats.match + stats.posMisMatch + stats.dupMatch) === totalMapped;\n const processedValid = (totalMapped + stats.noMatch) === stats.newControlsLength;\n lines.push(`Match + Mismatch + Related = Total Mapped Controls: (${stats.match}+${stats.posMisMatch}+${stats.dupMatch}=${totalMapped}) ${matchMappedValid}`);\n lines.push(` Total Processed = Total XCCDF Controls: (${totalMapped}+${stats.noMatch}=${totalMapped + stats.noMatch}) ${processedValid}`);\n lines.push('');\n\n // Per-control match method details\n if (result.linkRecords.length > 0) {\n lines.push('Match Details =============================================================');\n for (const lr of result.linkRecords) {\n if (lr.oldId) {\n lines.push(` ${lr.oldId} --> ${lr.newId}`);\n lines.push(` Match method: ${formatMatchMethod(lr)}`);\n } else {\n lines.push(` (none) --> ${lr.newId} [no match]`);\n }\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n"],"mappings":";;;;;;;;;;;;AAWA,SAAgB,aAAa,GAAmB;CAC9C,MAAM,YAAY,EAAE,SAAS,GAAG;CAChC,MAAM,YAAY,EAAE,SAAS,IAAG;CAEhC,IAAI,aAAa,WAEf,OAAO,MAAM,EAAE,QAAQ,SAAS,OAAO,EAAE;CAG3C,IAAI,WAEF,OAAO,IAAI,EAAE,QAAQ,OAAO,MAAM,EAAE,QAAQ,MAAM,MAAK,EAAE;CAI3D,OAAO,IAAI,EAAE,QAAQ,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAK,EAAE;AAC3D;;;;;;;ACpBA,MAAM,qBAAqB;;;;;;;;;;;;;;AAe3B,SAAgB,oBAAoB,KAAkC;CAOpE,IAAI,IAAI,MAAM;EACZ,MAAM,IAAI,mBAAmB,KAAK,IAAI,IAAI;EAC1C,IAAI,KAAK,EAAE,OAAO,KAAA,GAAW;GAC3B,MAAM,UAAU,EAAE;GAClB,IAAI,YAAY,IAAI,IAClB,OAAO,IAAI;GAEb,MAAM,QAAQ,EAAE,QAAQ,EAAE,GAAG,QAAQ,OAAO;GAC5C,OAAO,IAAI,KAAK,MAAM,GAAG,KAAK,IAAI,IAAI,KAAK,IAAI,KAAK,MAAM,QAAQ,QAAQ,MAAM;EAClF;CACF;CAEA,MAAM,QAAkB,CAAC;CAEzB,MAAM,KAAK,YAAY,IAAI,GAAG,KAAK;CAGnC,IAAI,IAAI,OACN,MAAM,KAAK,WAAW,aAAa,IAAI,KAAK,GAAG;CAIjD,MAAM,cAAc,IAAI,aAAa,MAAM,MAAM,EAAE,UAAU,SAAS;CACtE,IAAI,aACF,MAAM,KAAK,UAAU,aAAa,YAAY,IAAI,GAAG;CAGvD,MAAM,8BAAc,IAAI,IAAY;CACpC,KAAK,MAAM,KAAK,IAAI,cAAc;EAChC,IAAI,EAAE,UAAU,WAAW;GACzB,IAAI,YAAY,IAAI,SAAS,GAC3B;GAEF,YAAY,IAAI,SAAS;GACzB;EACF;EACA,MAAM,KAAK,WAAW,EAAE,MAAM,KAAK,aAAa,EAAE,IAAI,GAAG;CAC3D;CAGA,IAAI,IAAI,WAAW,KAAA,GAAW;EAC5B,MAAM,YAAY,OAAO,UAAU,IAAI,MAAM,IACzC,IAAI,OAAO,QAAQ,CAAC,IACpB,OAAO,IAAI,MAAM;EACrB,MAAM,KAAK,YAAY,WAAW;CACpC;CAGA,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,IAAI,GAChD,MAAM,KAAK,KAAK,UAAU,KAAK,KAAK,GAAG;CAIzC,IAAI,IAAI,MAAM;EACZ,MAAM,KAAK,EAAE;EACb,MAAM,KAAK,IAAI,IAAI;CACrB,OAAO;EACL,MAAM,KAAK,EAAE;EACb,MAAM,KAAK,qCAAqC;CAClD;CAEA,MAAM,KAAK,KAAK;CAChB,MAAM,KAAK,EAAE;CAEb,OAAO,MAAM,KAAK,IAAI;AACxB;;AAGA,SAAS,UAAU,KAAa,OAAwB;CACtD,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B,OAAO,OAAO,IAAI;CAGpB,IAAI,MAAM,QAAQ,KAAK,GAGrB,OAAO,OAAO,IAAI,KADJ,MAAM,KAAK,MAAe,IAAI,OAAO,CAAC,EAAE,EAAE,EAAE,KAAK,IACpC,EAAE;CAG/B,IAAI,OAAO,UAAU,WACnB,OAAO,OAAO,IAAI,IAAI;CAGxB,IAAI,OAAO,UAAU,UACnB,OAAO,OAAO,IAAI,IAAI,aAAa,KAAK;CAI1C,OAAO,OAAO,IAAI,IAAI,KAAK,UAAU,KAAK;AAC5C;;;;;;;;;AC7GA,SAAgB,kBACd,UACA,SACQ;CACR,MAAM,QAAkB,CAAC;CACzB,MAAM,OAAO,SAAS;CAGtB,MAAM,KAAK,SAAS,SAAS,MAAM;CAGnC,IAAI,SAAS,OACX,MAAM,KAAK,UAAU,SAAS,OAAO;CAGvC,MAAM,aAAa,MAAM,cAAc,SAAS;CAChD,IAAI,YACF,MAAM,KAAK,eAAe,YAAY;CAGxC,MAAM,YAAY,MAAM,aAAa,SAAS;CAC9C,IAAI,WACF,MAAM,KAAK,cAAc,WAAW;CAGtC,MAAM,UAAU,MAAM,WAAW,SAAS;CAC1C,IAAI,SACF,MAAM,KAAK,YAAY,SAAS;CAGlC,IAAI,SAAS,SACX,MAAM,KAAK,YAAY,SAAS,SAAS;CAI3C,MAAM,UAAU,MAAM,WAAW,SAAS;CAC1C,IAAI,SACF,MAAM,KAAK,aAAa,QAAQ,EAAE;CAIpC,MAAM,gBAAgB,SAAS,iBAAiB;CAChD,MAAM,KAAK,oBAAoB,cAAc,EAAE;CAG/C,IAAI,SAAS,YAAY,SAAS,SAAS,SAAS,GAAG;EACrD,MAAM,KAAK,WAAW;EACtB,KAAK,MAAM,WAAW,SAAS,UAAU;GACvC,MAAM,UAAoB,CAAC;GAC3B,IAAI,QAAQ,cACV,QAAQ,KAAK,oBAAoB,QAAQ,cAAc;GAEzD,IAAI,QAAQ,gBACV,QAAQ,KAAK,sBAAsB,QAAQ,gBAAgB;GAE7D,IAAI,QAAQ,UACV,QAAQ,KAAK,eAAe,QAAQ,UAAU;GAEhD,IAAI,QAAQ,SACV,QAAQ,KAAK,cAAc,QAAQ,SAAS;GAE9C,IAAI,QAAQ,SAAS,GAAG;IACtB,MAAM,KAAK,KAAK,QAAQ,GAAI,UAAU,GAAG;IACzC,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAClC,MAAM,KAAK,QAAQ,EAAG;GAE1B;EACF;CACF;CAGA,IAAI,SAAS,WAAW,SAAS,QAAQ,SAAS,GAAG;EACnD,MAAM,KAAK,UAAU;EACrB,KAAK,MAAM,OAAO,SAAS,SAAS;GAClC,MAAM,UAAoB,CAAC;GAC3B,IAAI,IAAI,MAAM,QAAQ,KAAK,SAAS,IAAI,MAAM;GAC9C,IAAI,IAAI,KAAK,QAAQ,KAAK,QAAQ,IAAI,KAAK;GAC3C,IAAI,IAAI,KAAK,QAAQ,KAAK,QAAQ,IAAI,KAAK;GAC3C,IAAI,IAAI,MAAM,QAAQ,KAAK,SAAS,IAAI,MAAM;GAC9C,IAAI,IAAI,QAAQ,QAAQ,KAAK,WAAW,IAAI,QAAQ;GACpD,IAAI,IAAI,YAAY,QAAQ,KAAK,eAAe,IAAI,YAAY;GAChE,IAAI,IAAI,aAAa,QAAQ,KAAK,gBAAgB,IAAI,aAAa;GACnE,IAAI,QAAQ,SAAS,GAAG;IACtB,MAAM,KAAK,KAAK,QAAQ,IAAI;IAC5B,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAClC,MAAM,KAAK,KAAK,QAAQ,IAAI;GAEhC;EACF;CACF;CAGA,IAAI,SAAS,UAAU,SAAS,OAAO,SAAS,GAAG;EACjD,MAAM,KAAK,SAAS;EACpB,KAAK,MAAM,SAAS,SAAS,QAC3B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAC7C,MAAM,KAAK,KAAK,IAAI,IAAI,gBAAgB,KAAK,GAAG;CAGtD;CAEA,MAAM,KAAK,EAAE;CACb,OAAO,MAAM,KAAK,IAAI;AACxB;;AAGA,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,OAAO,UAAU,WAAW,OAAO,OAAO,KAAK;CACnD,IAAI,OAAO,UAAU,UAAU,OAAO,OAAO,KAAK;CAClD,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,OAAO,KAAK,UAAU,KAAK;AAC7B;;;;;;;;;;AC5GA,SAAgB,sBACd,UACA,SACe;CACf,MAAM,YAAY,kBAAkB,UAAU,OAAO;CACrD,MAAM,2BAAW,IAAI,IAAoB;CAEzC,IAAI,SAAS,aAAa,WAAW,GACnC,OAAO;EAAE;EAAW;CAAS;CAG/B,IAAI,SAAS,YAAY;EAEvB,MAAM,QAAQ,SAAS,aAAa,KAAK,QAAQ,oBAAoB,GAAG,CAAC;EACzE,SAAS,IAAI,wBAAwB,MAAM,KAAK,IAAI,CAAC;CACvD,OAEE,KAAK,MAAM,OAAO,SAAS,cAAc;EAEvC,MAAM,WAAW,YADF,IAAI,GAAG,QAAQ,SAAS,EAAE,EAAE,QAAQ,UAAU,EAAE,KAAK,UAChC;EACpC,SAAS,IAAI,UAAU,oBAAoB,GAAG,CAAC;CACjD;CAGF,OAAO;EAAE;EAAW;CAAS;AAC/B;;;;;;;;;;;;;;;;;ACnBA,SAAgB,iBACd,SACA,UACA,QACqB;CACrB,MAAM,SAA8B;EAElC,IAAI,SAAS;EAGb,OAAO,WAAW,YAAY,QAAQ,QAAQ,SAAS;EACvD,QAAQ,WAAW,YAAY,QAAQ,SAAS,SAAS;EACzD,UAAU,WAAW,YAAY,QAAQ,WAAW,SAAS;EAG7D,MAAM,UAAU,QAAQ,MAAM,SAAS,MAAM,MAAM;EACnD,cAAc,kBAAkB,QAAQ,cAAc,SAAS,cAAc,MAAM;EACnF,MAAM,UAAU,QAAQ,MAAM,SAAS,MAAM,MAAM;EAGnD,MAAM,WAAW,aACb,SAAS,OACR,QAAQ,QAAQ,SAAS;EAG9B,gBAAgB,WAAW,YAAY,QAAQ,iBAAiB,SAAS;CAC3E;CAGA,IAAI,OAAO,SAAS,KAAA,GAAW,OAAO,OAAO;CAC7C,IAAI,OAAO,aAAa,KAAA,GAAW,OAAO,OAAO;CACjD,IAAI,OAAO,mBAAmB,KAAA,GAAW,OAAO,OAAO;CACvD,IAAI,OAAO,SAAS,KAAA,KAAa,OAAO,KAAK,WAAW,GAAG,OAAO,OAAO;CAEzE,OAAO;AACT;;;;;;;;AASA,SAAgB,UACd,SACA,UACA,QACqB;CACrB,IAAI,WAAW,YACb,OAAO,EAAE,GAAG,SAAS;CAGvB,MAAM,SAA8B,EAAE,GAAG,QAAQ;CAEjD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,GAChD,IAAI,WAAW;MACT,EAAE,OAAO,SACX,OAAO,OAAO;CAAA,OAIhB,OAAO,OAAO;CAIlB,OAAO;AACT;;;;;;;;AASA,SAAgB,kBACd,SACA,UACA,QACe;CACf,IAAI,WAAW,YACb,OAAO,CAAC,GAAG,QAAQ;CAGrB,MAAM,0BAAU,IAAI,IAAyB;CAC7C,MAAM,QAAkB,CAAC;CAGzB,KAAK,MAAM,KAAK,SAAS;EACvB,QAAQ,IAAI,EAAE,OAAO,CAAC;EACtB,MAAM,KAAK,EAAE,KAAK;CACpB;CAGA,KAAK,MAAM,KAAK,UACd,IAAI,QAAQ,IAAI,EAAE,KAAK;MACjB,WAAW,WAEb,QAAQ,IAAI,EAAE,OAAO,CAAC;CAAA,OAEnB;EACL,QAAQ,IAAI,EAAE,OAAO,CAAC;EACtB,MAAM,KAAK,EAAE,KAAK;CACpB;CAGF,OAAO,MAAM,KAAI,UAAS,QAAQ,IAAI,KAAK,CAAE;AAC/C;;;;;;;;AASA,SAAgB,UACd,SACA,UACA,QACyB;CACzB,IAAI,WAAW,WACb,OAAO,UAAU,CAAC,GAAG,OAAO,IAAI,KAAA;CAElC,IAAI,WAAW,YACb,OAAO,WAAW,CAAC,GAAG,QAAQ,IAAI,KAAA;CAIpC,IAAI,CAAC,WAAW,CAAC,UAAU,OAAO,KAAA;CAElC,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,SAAsB,CAAC;CAE7B,MAAM,UAAU,MAAuB;EACrC,MAAM,MAAM,KAAK,UAAU,CAAC;EAC5B,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG;GAClB,KAAK,IAAI,GAAG;GACZ,OAAO,KAAK,CAAC;EACf;CACF;CAEA,IAAI,SAAS,QAAQ,QAAQ,MAAM;CACnC,IAAI,UAAU,SAAS,QAAQ,MAAM;CAErC,OAAO,OAAO,SAAS,IAAI,SAAS,KAAA;AACtC;;;;;;;;;;;;;;ACnJA,SAAgB,gBACd,iBACA,kBACA,aACA,MACe;CACf,MAAM,SAAS,MAAM;CAGrB,MAAM,8BAAc,IAAI,IAAwB;CAChD,KAAK,MAAM,MAAM,aAEf,IAAI,CADa,YAAY,IAAI,GAAG,KACxB,KAAK,GAAG,iBAAiB,WACnC,YAAY,IAAI,GAAG,OAAO,EAAE;CAKhC,MAAM,8BAAc,IAAI,IAAiC;CACzD,KAAK,MAAM,OAAO,gBAAgB,cAChC,YAAY,IAAI,IAAI,IAAI,GAAG;CAI7B,MAAM,oCAAoB,IAAI,IAAY;CAG1C,MAAM,aAAoC,CAAC;CAC3C,KAAK,MAAM,SAAS,iBAAiB,cAAc;EACjD,MAAM,OAAO,YAAY,IAAI,MAAM,EAAE;EACrC,IAAI,QAAQ,KAAK,OAAO;GACtB,MAAM,SAAS,YAAY,IAAI,KAAK,KAAK;GACzC,IAAI,QAAQ;IACV,kBAAkB,IAAI,KAAK,KAAK;IAGhC,MAAM,mBAAmB,MAAM,SAC3B;KAAE,GAAG;KAAQ,MAAM,KAAA;IAAU,IAC7B;IAEJ,WAAW,KAAK,iBAAiB,kBAAkB,OAAO,MAAM,CAAC;IACjE;GACF;EACF;EAEA,WAAW,KAAK,KAAK;CACvB;CAKA,IAAI,MAAM;OACH,MAAM,UAAU,gBAAgB,cACnC,IAAI,CAAC,kBAAkB,IAAI,OAAO,EAAE,GAClC,WAAW,KAAK,MAAM;CAAA;CAM5B,MAAM,mBAAgC;EACpC,GAAG;EACH,cAAc;CAChB;CAKA,MAAM,SAAwB;EAC5B,UAAU;EACV;EACA,YALiB,kBAAkB,aAAa,gBAAgB,aAAa,QAAQ,iBAAiB,aAAa,MAK1G;CACX;CAGA,MAAM,eAAe,MAAM,gBAAgB;CAC3C,IAAI,iBAAiB,YAAY,iBAAiB,UAAU,iBAAiB,IAC3E,OAAO,UAAU,4BAA4B,kBAAkB,IAAI;CAGrE,OAAO;AACT;;;;;;AAOA,SAAgB,cACd,aACA,aACA,YACA,MACA,iBACa;CAUb,MAAM,SAAS,gBAAgB;EAN7B,MAAM;EACN,cAHkB,4BAA4B,aAAa,UAGnC;EACxB,QAAQ,CAAC;EACT,UAAU,CAAC;CAGgC,GAAG,aAAa,aAAa,IAAI;CAG9E,OAAO,WAAW,oBAAoB,mBAAmB;CAEzD,OAAO;AACT;AAEA,SAAS,4BACP,aACA,YACuB;CACvB,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,OAA8B,CAAC;CAErC,KAAK,MAAM,MAAM,aAAa;EAC5B,IAAI,CAAC,GAAG,SAAS,KAAK,IAAI,GAAG,KAAK,GAAG;EACrC,KAAK,IAAI,GAAG,KAAK;EAEjB,MAAM,MAA2B;GAC/B,IAAI,GAAG;GACP,QAAQ;GACR,MAAM,CAAC;GACP,cAAc,CAAC;IAAE,OAAO;IAAW,MAAM;GAAG,CAAC;EAC/C;EACA,MAAM,OAAO,WAAW,IAAI,GAAG,KAAK;EACpC,IAAI,MACF,IAAI,OAAO;EAEb,KAAK,KAAK,GAAG;CACf;CACA,OAAO;AACT;AAEA,SAAS,4BAA4B,UAAuB,MAAsC;CAChG,MAAM,2BAAW,IAAI,IAAoB;CACzC,MAAM,WAAqB,CAAC;CAE5B,KAAK,MAAM,OAAO,SAAS,cAAc;EACvC,MAAM,OAAO,oBAAoB,GAAG;EAEpC,IAAI,MAAM,YACR,SAAS,KAAK,IAAI;OACb;GACL,MAAM,SAAS,IAAI,GAAG,QAAQ,SAAS,EAAE,EAAE,QAAQ,UAAU,EAAE,KAAK;GACpE,SAAS,IAAI,YAAY,OAAO,MAAM,IAAI;EAC5C;CACF;CAEA,IAAI,MAAM,cAAc,SAAS,SAAS,GACxC,SAAS,IAAI,wBAAwB,SAAS,KAAK,IAAI,CAAC;CAS1D,OAAO;EAAE,WANS,kBAAkB,UAAU;GAC5C,YAAY,MAAM;GAClB,UAAU,MAAM;GAChB,eAAe,MAAM;EACvB,CAEiB;EAAG;CAAS;AAC/B;AAEA,SAAS,kBAAkB,aAA2B,iBAAyB,iBAA0C;CACvH,IAAI,QAAQ;CACZ,IAAI,cAAc;CAClB,IAAI,WAAW;CACf,IAAI,UAAU;CAEd,KAAK,MAAM,MAAM,aACf,IAAI,GAAG,iBAAiB,WACtB;MACK,IAAI,GAAG,iBAAiB,YAC7B;MACK,IAAI,GAAG,mBACZ;MAEA;CAIJ,OAAO;EACL,mBAAmB;EACnB,mBAAmB;EACnB,qBAAqB,QAAQ,cAAc;EAC3C;EACA;EACA;EACA;CACF;AACF;;;;;;;ACrMA,SAAgB,kBAAkB,QAAsC;CACtE,OAAO,EACL,OAAO,OAAO,YAChB;AACF;;;;AAKA,SAAS,kBAAkB,IAAwB;CACjD,MAAM,iBAAiB,GAAG,aAAa,KAAK,QAAQ,CAAC,IAAI;CACzD,QAAQ,GAAG,aAAX;EACE,KAAK,oBACH,OAAO,sBAAsB,GAAG,OAAO,IAAI,KAAK,GAAG,aAAa;EAClE,KAAK,kBACH,OAAO,qCAAqC,cAAc,KAAK,GAAG,aAAa;EACjF,KAAK,oBACH,OAAO,kCAAkC,cAAc,KAAK,GAAG,aAAa;EAC9E,KAAK,WACH,OAAO,aAAa,GAAG,aAAa;EACtC,KAAK,YACH,OAAO,cAAc,GAAG,aAAa;EACvC,KAAK,cACH,OAAO,2BAA2B,cAAc,KAAK,GAAG,aAAa;EACvE,KAAK,QACH,OAAO;EACT,SACE,OAAO,GAAG,GAAG,YAAY,IAAI,GAAG,aAAa;CACjD;AACF;;;;;;AAOA,SAAgB,sBAAsB,QAA6B;CACjE,MAAM,QAAkB,CAAC;CACzB,MAAM,QAAQ,OAAO;CAGrB,IAAI,OAAO,YAAY,SAAS,GAAG;EACjC,MAAM,KAAK,6FAA6F;EACxG,MAAM,KAAK,6BAA8B;EACzC,KAAK,MAAM,MAAM,OAAO,aACtB,IAAI,GAAG,iBAAiB,cAAc,GAAG,OACvC,MAAM,KAAK,QAAQ,GAAG,MAAM,MAAM,GAAG,OAAO;EAIhD,MAAM,cAAc,MAAM;EAC1B,MAAM,KAAK,2BAA2B,aAAa;EACnD,MAAM,KAAK,EAAE;CACf;CAGA,MAAM,KAAK,4CAA4C;CACvD,MAAM,KAAK,wCAAwC,MAAM,mBAAmB;CAC5E,MAAM,KAAK,wCAAwC,MAAM,mBAAmB;CAC5E,MAAM,KAAK,EAAE;CAGb,MAAM,KAAK,4CAA4C;CACvD,MAAM,KAAK,wCAAwC,MAAM,OAAO;CAChE,MAAM,KAAK,wCAAwC,MAAM,aAAa;CACtE,MAAM,KAAK,wCAAwC,MAAM,UAAU;CACnE,MAAM,KAAK,wCAAwC,MAAM,SAAS;CAClE,MAAM,KAAK,EAAE;CAGb,MAAM,KAAK,qEAAqE;CAChF,MAAM,cAAc,MAAM;CAC1B,MAAM,mBAAoB,MAAM,QAAQ,MAAM,cAAc,MAAM,aAAc;CAChF,MAAM,iBAAkB,cAAc,MAAM,YAAa,MAAM;CAC/D,MAAM,KAAK,yDAAyD,MAAM,MAAM,GAAG,MAAM,YAAY,GAAG,MAAM,SAAS,GAAG,YAAY,IAAI,kBAAkB;CAC5J,MAAM,KAAK,+CAA+C,YAAY,GAAG,MAAM,QAAQ,GAAG,cAAc,MAAM,QAAQ,IAAI,gBAAgB;CAC1I,MAAM,KAAK,EAAE;CAGb,IAAI,OAAO,YAAY,SAAS,GAAG;EACjC,MAAM,KAAK,6EAA6E;EACxF,KAAK,MAAM,MAAM,OAAO,aACtB,IAAI,GAAG,OAAO;GACZ,MAAM,KAAK,KAAK,GAAG,MAAM,OAAO,GAAG,OAAO;GAC1C,MAAM,KAAK,yBAAyB,kBAAkB,EAAE,GAAG;EAC7D,OACE,MAAM,KAAK,gBAAgB,GAAG,MAAM,aAAa;EAGrD,MAAM,KAAK,EAAE;CACf;CAEA,OAAO,MAAM,KAAK,IAAI;AACxB"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/ruby-escape.ts","../src/control-stub.ts","../src/inspec-yml.ts","../src/profile-generator.ts","../src/merge.ts","../src/delta.ts","../src/delta-report.ts"],"sourcesContent":["/**\n * Escape a string for use as a Ruby string literal.\n *\n * Strategy:\n * - If the string contains both single and double quotes → %q() wrapper\n * - If the string contains only single quotes → double-quoted with escaping\n * - Otherwise → single-quoted with escaping\n *\n * Ported from ts-inspec-objects escapeQuotes(), cross-referenced with\n * inspec-parser's RubyRebuilder.\n */\nexport function escapeQuotes(s: string): string {\n const hasSingle = s.includes(\"'\");\n const hasDouble = s.includes('\"');\n\n if (hasSingle && hasDouble) {\n // %q() — escape backslashes before ) so Ruby doesn't treat \\) as escaped delimiter\n return `%q(${s.replace(/\\\\\\)/g, '\\\\\\\\)')})`;\n }\n\n if (hasSingle) {\n // Double-quoted: escape backslashes, then double quotes\n return `\"${s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"')}\"`;\n }\n\n // Single-quoted: escape backslashes, then single quotes\n return `'${s.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\")}'`;\n}\n","import type { BaselineRequirement } from '@mitre/hdf-schema';\nimport { escapeQuotes } from './ruby-escape.js';\n\n/**\n * Detects when a code string already starts with `control 'ID' do`,\n * meaning it's a complete InSpec control file — not a body fragment.\n */\nconst FULL_CONTROL_BLOCK = /^\\s*control\\s+['\"]([^'\"]+)['\"]\\s+do\\b/;\n\n/**\n * Generate a Ruby InSpec control stub from an HDF BaselineRequirement.\n *\n * Output follows the InSpec DSL ordering convention:\n * control 'ID' do\n * title ...\n * desc ...\n * desc 'check', ...\n * impact ...\n * tag key: value\n * <code or stub comment>\n * end\n */\nexport function generateControlStub(req: BaselineRequirement): string {\n // If code is already a complete `control 'ID' do ... end` block (e.g.\n // from `-c controls/` reading whole .rb files), emit it as-is — wrapping\n // it again would produce nested control blocks, which InSpec rejects.\n // When the inner ID differs from req.id (e.g. an upgrade rename match\n // where current's code was carried into a renamed requirement), rewrite\n // the wrapper to match req.id so the file remains valid InSpec.\n if (req.code) {\n const m = FULL_CONTROL_BLOCK.exec(req.code);\n if (m && m[1] !== undefined) {\n const innerID = m[1];\n if (innerID === req.id) {\n return req.code;\n }\n const start = m.index + m[0].indexOf(innerID);\n return req.code.slice(0, start) + req.id + req.code.slice(start + innerID.length);\n }\n }\n\n const lines: string[] = [];\n\n lines.push(`control '${req.id}' do`);\n\n // Title\n if (req.title) {\n lines.push(` title ${escapeQuotes(req.title)}`);\n }\n\n // Descriptions: default first (as bare `desc`), then labeled\n const defaultDesc = req.descriptions.find((d) => d.label === 'default');\n if (defaultDesc) {\n lines.push(` desc ${escapeQuotes(defaultDesc.data)}`);\n }\n\n const seenDefault = new Set<string>();\n for (const d of req.descriptions) {\n if (d.label === 'default') {\n if (seenDefault.has('default')) {\n continue; // skip duplicate default\n }\n seenDefault.add('default');\n continue; // already emitted above as bare desc\n }\n lines.push(` desc '${d.label}', ${escapeQuotes(d.data)}`);\n }\n\n // Impact — always render with at least one decimal place for 0 and whole numbers\n if (req.impact !== undefined) {\n const impactStr = Number.isInteger(req.impact)\n ? req.impact.toFixed(1)\n : String(req.impact);\n lines.push(` impact ${impactStr}`);\n }\n\n // Tags\n for (const [key, value] of Object.entries(req.tags)) {\n lines.push(` ${formatTag(key, value)}`);\n }\n\n // Code body or stub placeholder\n if (req.code) {\n lines.push('');\n lines.push(req.code);\n } else {\n lines.push('');\n lines.push(' # TODO: Add InSpec test code here');\n }\n\n lines.push('end');\n lines.push(''); // trailing newline\n\n return lines.join('\\n');\n}\n\n/** Format a single tag key-value pair as Ruby DSL. */\nfunction formatTag(key: string, value: unknown): string {\n if (value === null || value === undefined) {\n return `tag ${key}: nil`;\n }\n\n if (Array.isArray(value)) {\n // Ruby array syntax: tag key: ['val1', 'val2']\n const items = value.map((v: unknown) => `'${String(v)}'`).join(', ');\n return `tag ${key}: [${items}]`;\n }\n\n if (typeof value === 'boolean') {\n return `tag ${key}: ${value}`;\n }\n\n if (typeof value === 'string') {\n return `tag ${key}: ${escapeQuotes(value)}`;\n }\n\n // Fallback for other types (numbers, objects)\n return `tag ${key}: ${JSON.stringify(value)}`;\n}\n","import type { HDFBaseline } from '@mitre/hdf-schema';\nimport type { GeneratorOptions } from './types.js';\n\n/**\n * Generate an inspec.yml YAML string from an HDF Baseline and options.\n *\n * Uses string interpolation — no YAML library needed since the structure\n * is fixed and shallow.\n */\nexport function generateInSpecYml(\n baseline: HDFBaseline,\n options?: GeneratorOptions,\n): string {\n const lines: string[] = [];\n const meta = options?.metadata;\n\n // Required: name\n lines.push(`name: ${baseline.name}`);\n\n // Optional metadata fields\n if (baseline.title) {\n lines.push(`title: ${baseline.title}`);\n }\n\n const maintainer = meta?.maintainer ?? baseline.maintainer;\n if (maintainer) {\n lines.push(`maintainer: ${maintainer}`);\n }\n\n const copyright = meta?.copyright ?? baseline.copyright;\n if (copyright) {\n lines.push(`copyright: ${copyright}`);\n }\n\n const license = meta?.license ?? baseline.license;\n if (license) {\n lines.push(`license: ${license}`);\n }\n\n if (baseline.summary) {\n lines.push(`summary: ${baseline.summary}`);\n }\n\n // Version: metadata override takes priority\n const version = meta?.version ?? baseline.version;\n if (version) {\n lines.push(`version: '${version}'`);\n }\n\n // InSpec version constraint\n const inspecVersion = options?.inspecVersion ?? '~>6.0';\n lines.push(`inspec_version: '${inspecVersion}'`);\n\n // Supports array\n if (baseline.supports && baseline.supports.length > 0) {\n lines.push('supports:');\n for (const support of baseline.supports) {\n const entries: string[] = [];\n if (support.platformName) {\n entries.push(` platform-name: ${support.platformName}`);\n }\n if (support.platformFamily) {\n entries.push(` platform-family: ${support.platformFamily}`);\n }\n if (support.platform) {\n entries.push(` platform: ${support.platform}`);\n }\n if (support.release) {\n entries.push(` release: ${support.release}`);\n }\n if (entries.length > 0) {\n lines.push(`- ${entries[0]!.trimStart()}`);\n for (let i = 1; i < entries.length; i++) {\n lines.push(entries[i]!);\n }\n }\n }\n }\n\n // Depends array\n if (baseline.depends && baseline.depends.length > 0) {\n lines.push('depends:');\n for (const dep of baseline.depends) {\n const entries: string[] = [];\n if (dep.name) entries.push(`name: ${dep.name}`);\n if (dep.git) entries.push(`git: ${dep.git}`);\n if (dep.url) entries.push(`url: ${dep.url}`);\n if (dep.path) entries.push(`path: ${dep.path}`);\n if (dep.branch) entries.push(`branch: ${dep.branch}`);\n if (dep.compliance) entries.push(`compliance: ${dep.compliance}`);\n if (dep.supermarket) entries.push(`supermarket: ${dep.supermarket}`);\n if (entries.length > 0) {\n lines.push(`- ${entries[0]}`);\n for (let i = 1; i < entries.length; i++) {\n lines.push(` ${entries[i]}`);\n }\n }\n }\n }\n\n // Inputs array\n if (baseline.inputs && baseline.inputs.length > 0) {\n lines.push('inputs:');\n for (const input of baseline.inputs) {\n for (const [key, value] of Object.entries(input)) {\n lines.push(`- ${key}: ${formatYamlValue(value)}`);\n }\n }\n }\n\n lines.push(''); // trailing newline\n return lines.join('\\n');\n}\n\n/** Format a value for inline YAML output. */\nfunction formatYamlValue(value: unknown): string {\n if (typeof value === 'boolean') return String(value);\n if (typeof value === 'number') return String(value);\n if (typeof value === 'string') return value;\n return JSON.stringify(value);\n}\n","import type { BaselineRequirement, HDFBaseline } from '@mitre/hdf-schema';\nimport type { GeneratorOptions, InSpecProfile } from './types.js';\nimport { generateControlStub } from './control-stub.js';\nimport { generateInSpecYml } from './inspec-yml.js';\n\n/**\n * Generate an in-memory InSpec profile from an HDF Baseline.\n *\n * Returns an InSpecProfile with inspec.yml content and a Map of\n * control filenames to Ruby source code. No file I/O — the CLI\n * is responsible for writing files to disk.\n */\nexport function generateInSpecProfile(\n baseline: HDFBaseline,\n options?: GeneratorOptions,\n): InSpecProfile {\n const inspecYml = generateInSpecYml(baseline, options);\n const controls = new Map<string, string>();\n\n if (baseline.requirements.length === 0) {\n return { inspecYml, controls };\n }\n\n if (options?.singleFile) {\n // All controls in a single file\n const stubs = baseline.requirements.map((req: BaselineRequirement) => generateControlStub(req));\n controls.set('controls/controls.rb', stubs.join('\\n'));\n } else {\n // One file per control — sanitize ID for safe filenames\n for (const req of baseline.requirements) {\n const safeId = req.id.replace(/\\.\\./g, '').replace(/[/\\\\]/g, '') || 'unknown';\n const filename = `controls/${safeId}.rb`;\n controls.set(filename, generateControlStub(req));\n }\n }\n\n return { inspecYml, controls };\n}\n","import type { BaselineRequirement, Description, Reference } from '@mitre/hdf-schema';\n\nexport type PreferSide = 'current' | 'upstream' | undefined;\n\n/**\n * Smart-merge a current and upstream requirement.\n *\n * Default (no prefer):\n * - ID: always upstream\n * - Scalars (title, impact, severity): upstream wins\n * - Tags: union, upstream wins key conflicts\n * - Descriptions: union by label, upstream wins on same label\n * - Code: current (preserve tests)\n * - Refs: union (deduplicated)\n *\n * prefer \"current\": scalars from current, current wins tag/desc conflicts\n * prefer \"upstream\": everything from upstream (full replacement)\n */\nexport function mergeRequirement(\n current: BaselineRequirement,\n upstream: BaselineRequirement,\n prefer?: PreferSide,\n): BaselineRequirement {\n const merged: BaselineRequirement = {\n // ID always comes from upstream\n id: upstream.id,\n\n // Scalars\n title: prefer === 'current' ? current.title : upstream.title,\n impact: prefer === 'current' ? current.impact : upstream.impact,\n severity: prefer === 'current' ? current.severity : upstream.severity,\n\n // Collections\n tags: mergeTags(current.tags, upstream.tags, prefer),\n descriptions: mergeDescriptions(current.descriptions, upstream.descriptions, prefer),\n refs: mergeRefs(current.refs, upstream.refs, prefer),\n\n // Code: current by default, upstream only with --prefer upstream\n code: prefer === 'upstream'\n ? upstream.code\n : (current.code ?? upstream.code),\n\n // SourceLocation follows scalars\n sourceLocation: prefer === 'current' ? current.sourceLocation : upstream.sourceLocation,\n };\n\n // Clean up undefined optional fields\n if (merged.code === undefined) delete merged.code;\n if (merged.severity === undefined) delete merged.severity;\n if (merged.sourceLocation === undefined) delete merged.sourceLocation;\n if (merged.refs === undefined || merged.refs.length === 0) delete merged.refs;\n\n return merged;\n}\n\n/**\n * Merge two tag maps.\n *\n * Default: union of keys; upstream wins on key conflicts.\n * prefer \"current\": union; current wins on key conflicts.\n * prefer \"upstream\": upstream replaces all.\n */\nexport function mergeTags(\n current: Record<string, any>, // eslint-disable-line @typescript-eslint/no-explicit-any\n upstream: Record<string, any>, // eslint-disable-line @typescript-eslint/no-explicit-any\n prefer?: PreferSide,\n): Record<string, any> { // eslint-disable-line @typescript-eslint/no-explicit-any\n if (prefer === 'upstream') {\n return { ...upstream };\n }\n\n const merged: Record<string, any> = { ...current }; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n for (const [key, value] of Object.entries(upstream)) {\n if (prefer === 'current') {\n if (!(key in merged)) {\n merged[key] = value;\n }\n } else {\n // Default: upstream wins on conflict\n merged[key] = value;\n }\n }\n\n return merged;\n}\n\n/**\n * Merge two description arrays by label.\n *\n * Default: union by label; upstream wins on label conflicts.\n * prefer \"current\": union; current wins on label conflicts.\n * prefer \"upstream\": upstream replaces all.\n */\nexport function mergeDescriptions(\n current: Description[],\n upstream: Description[],\n prefer?: PreferSide,\n): Description[] {\n if (prefer === 'upstream') {\n return [...upstream];\n }\n\n const byLabel = new Map<string, Description>();\n const order: string[] = [];\n\n // Start with current\n for (const d of current) {\n byLabel.set(d.label, d);\n order.push(d.label);\n }\n\n // Apply upstream\n for (const d of upstream) {\n if (byLabel.has(d.label)) {\n if (prefer !== 'current') {\n // Default: upstream wins\n byLabel.set(d.label, d);\n }\n } else {\n byLabel.set(d.label, d);\n order.push(d.label);\n }\n }\n\n return order.map(label => byLabel.get(label)!);\n}\n\n/**\n * Merge two reference arrays.\n *\n * Default: union, deduplicated by JSON key.\n * prefer \"current\": current only.\n * prefer \"upstream\": upstream only.\n */\nexport function mergeRefs(\n current?: Reference[],\n upstream?: Reference[],\n prefer?: PreferSide,\n): Reference[] | undefined {\n if (prefer === 'current') {\n return current ? [...current] : undefined;\n }\n if (prefer === 'upstream') {\n return upstream ? [...upstream] : undefined;\n }\n\n // Default: union, deduplicated\n if (!current && !upstream) return undefined;\n\n const seen = new Set<string>();\n const result: Reference[] = [];\n\n const addRef = (r: Reference): void => {\n const key = JSON.stringify(r);\n if (!seen.has(key)) {\n seen.add(key);\n result.push(r);\n }\n };\n\n if (current) current.forEach(addRef);\n if (upstream) upstream.forEach(addRef);\n\n return result.length > 0 ? result : undefined;\n}\n","import type { HDFBaseline, BaselineRequirement } from '@mitre/hdf-schema';\nimport type { UpgradeResult, UpgradeOptions, DeltaResult, DeltaOptions, LinkRecord, DeltaStatistics } from './delta-types.js';\nimport type { InSpecProfile } from './types.js';\nimport { mergeRequirement } from './merge.js';\nimport { generateControlStub } from './control-stub.js';\nimport { generateInSpecYml } from './inspec-yml.js';\n\n/**\n * Generate an upgraded HDF Baseline by smart-merging current and upstream requirements.\n *\n * For each upstream requirement:\n * - If matched: smart-merge current + upstream fields per mergeRequirement semantics\n * - If unmatched: include upstream requirement as-is (new control)\n *\n * Current requirements with no upstream match are dropped by default\n * (a control removed from upstream should not survive the upgrade).\n * Set opts.keepUnmatched to retain them instead.\n */\nexport function generateUpgrade(\n currentBaseline: HDFBaseline,\n upstreamBaseline: HDFBaseline,\n linkRecords: LinkRecord[],\n opts?: UpgradeOptions,\n): UpgradeResult {\n const prefer = opts?.prefer;\n\n // Build lookup: newId -> LinkRecord (prefer primary over related)\n const linkByNewId = new Map<string, LinkRecord>();\n for (const lr of linkRecords) {\n const existing = linkByNewId.get(lr.newId);\n if (!existing || lr.relationship === 'primary') {\n linkByNewId.set(lr.newId, lr);\n }\n }\n\n // Build lookup: oldId -> current requirement\n const currentById = new Map<string, BaselineRequirement>();\n for (const req of currentBaseline.requirements) {\n currentById.set(req.id, req);\n }\n\n // Track which current IDs got matched\n const matchedCurrentIds = new Set<string>();\n\n // Merge upstream requirements\n const mergedReqs: BaselineRequirement[] = [];\n for (const upReq of upstreamBaseline.requirements) {\n const link = linkByNewId.get(upReq.id);\n if (link && link.oldId) {\n const curReq = currentById.get(link.oldId);\n if (curReq) {\n matchedCurrentIds.add(link.oldId);\n\n // Handle --noCode\n const effectiveCurrent = opts?.noCode\n ? { ...curReq, code: undefined }\n : curReq;\n\n mergedReqs.push(mergeRequirement(effectiveCurrent, upReq, prefer));\n continue;\n }\n }\n // Unmatched upstream: include as-is\n mergedReqs.push(upReq);\n }\n\n // Include unmatched current requirements only when explicitly opted in.\n // By default they're dropped — a control removed from upstream should\n // not survive the upgrade, matching SAF CLI delta semantics.\n if (opts?.keepUnmatched) {\n for (const curReq of currentBaseline.requirements) {\n if (!matchedCurrentIds.has(curReq.id)) {\n mergedReqs.push(curReq);\n }\n }\n }\n\n // Build upgraded baseline (use upstream metadata)\n const upgradedBaseline: HDFBaseline = {\n ...upstreamBaseline,\n requirements: mergedReqs,\n };\n\n // Compute statistics\n const statistics = computeStatistics(linkRecords, currentBaseline.requirements.length, upstreamBaseline.requirements.length);\n\n const result: UpgradeResult = {\n baseline: upgradedBaseline,\n linkRecords,\n statistics,\n };\n\n // Generate InSpec profile if requested\n const outputFormat = opts?.outputFormat ?? '';\n if (outputFormat === 'inspec' || outputFormat === 'both' || outputFormat === '') {\n result.profile = generateProfileFromBaseline(upgradedBaseline, opts);\n }\n\n return result;\n}\n\n/**\n * Legacy entry point. Wraps generateUpgrade for backward compatibility.\n *\n * @deprecated Use generateUpgrade instead.\n */\nexport function generateDelta(\n newBaseline: HDFBaseline,\n linkRecords: LinkRecord[],\n oldCodeMap: Map<string, string>,\n opts?: DeltaOptions,\n oldControlCount?: number,\n): DeltaResult {\n // Build synthetic current baseline from code map\n const currentReqs = buildCurrentReqsFromCodeMap(linkRecords, oldCodeMap);\n const currentBaseline: HDFBaseline = {\n name: 'current',\n requirements: currentReqs,\n groups: [],\n supports: [],\n };\n\n const result = generateUpgrade(currentBaseline, newBaseline, linkRecords, opts);\n\n // Patch statistics to use provided old control count\n result.statistics.oldControlsLength = oldControlCount ?? 0;\n\n return result;\n}\n\nfunction buildCurrentReqsFromCodeMap(\n linkRecords: LinkRecord[],\n oldCodeMap: Map<string, string>,\n): BaselineRequirement[] {\n const seen = new Set<string>();\n const reqs: BaselineRequirement[] = [];\n\n for (const lr of linkRecords) {\n if (!lr.oldId || seen.has(lr.oldId)) continue;\n seen.add(lr.oldId);\n\n const req: BaselineRequirement = {\n id: lr.oldId,\n impact: 0,\n tags: {},\n descriptions: [{ label: 'default', data: '' }],\n };\n const code = oldCodeMap.get(lr.oldId);\n if (code) {\n req.code = code;\n }\n reqs.push(req);\n }\n return reqs;\n}\n\nfunction generateProfileFromBaseline(baseline: HDFBaseline, opts?: UpgradeOptions): InSpecProfile {\n const controls = new Map<string, string>();\n const allStubs: string[] = [];\n\n for (const req of baseline.requirements) {\n const ruby = generateControlStub(req);\n\n if (opts?.singleFile) {\n allStubs.push(ruby);\n } else {\n const safeId = req.id.replace(/\\.\\./g, '').replace(/[/\\\\]/g, '') || 'unknown';\n controls.set(`controls/${safeId}.rb`, ruby);\n }\n }\n\n if (opts?.singleFile && allStubs.length > 0) {\n controls.set('controls/controls.rb', allStubs.join('\\n'));\n }\n\n const inspecYml = generateInSpecYml(baseline, {\n singleFile: opts?.singleFile,\n metadata: opts?.metadata,\n inspecVersion: opts?.inspecVersion,\n });\n\n return { inspecYml, controls };\n}\n\nfunction computeStatistics(linkRecords: LinkRecord[], oldControlCount: number, newControlCount: number): DeltaStatistics {\n let match = 0;\n let posMisMatch = 0;\n let dupMatch = 0;\n let noMatch = 0;\n\n for (const lr of linkRecords) {\n if (lr.relationship === 'related') {\n dupMatch++;\n } else if (lr.relationship === 'no-match') {\n noMatch++;\n } else if (lr.potentialMismatch) {\n posMisMatch++;\n } else {\n match++;\n }\n }\n\n return {\n oldControlsLength: oldControlCount,\n newControlsLength: newControlCount,\n totalMappedControls: match + posMisMatch + dupMatch,\n match,\n posMisMatch,\n dupMatch,\n noMatch,\n };\n}\n","import type { DeltaResult, LinkRecord } from './delta-types.js';\n\n/**\n * JSON report payload for delta operations.\n * SAF CLI-compatible: { links: LinkRecord[] }\n */\nexport interface DeltaJsonReport {\n links: LinkRecord[];\n}\n\n/**\n * Generate a structured JSON report from a delta result.\n * Matches SAF CLI's delta.json format: { ...diff, links }.\n */\nexport function generateDeltaJson(result: DeltaResult): DeltaJsonReport {\n return {\n links: result.linkRecords,\n };\n}\n\n/**\n * Format a per-link match method description matching SAF CLI's logMatchMethod.\n */\nfunction formatMatchMethod(lr: LinkRecord): string {\n const confidencePct = (lr.confidence * 100).toFixed(0) + '%';\n switch (lr.matchMethod) {\n case 'srgDeterministic':\n return `SRG deterministic (${lr.srg ?? '?'}) [${lr.relationship}]`;\n case 'srgCciTiebreak':\n return `SRG block + CCI tiebreak (Jaccard=${confidencePct}) [${lr.relationship}]`;\n case 'vendorFuzzyTitle':\n return `Vendor fuzzy title (confidence=${confidencePct}) [${lr.relationship}]`;\n case 'exactId':\n return `Exact ID [${lr.relationship}]`;\n case 'cciMatch':\n return `CCI match [${lr.relationship}]`;\n case 'fuzzyTitle':\n return `Fuzzy title (confidence=${confidencePct}) [${lr.relationship}]`;\n case 'none':\n return 'No match';\n default:\n return `${lr.matchMethod} [${lr.relationship}]`;\n }\n}\n\n/**\n * Generate a Markdown report from a delta result.\n * Matches SAF CLI's delta.md format: mapping table, control counts,\n * match statistics, and statistics validation.\n */\nexport function generateDeltaMarkdown(result: DeltaResult): string {\n const lines: string[] = [];\n const stats = result.statistics;\n\n // Mapping results — SAF CLI format: Old Control -> New Control\n if (result.linkRecords.length > 0) {\n lines.push('Mapping Results ===========================================================================');\n lines.push('\\tOld Control -> New Control');\n for (const lr of result.linkRecords) {\n if (lr.relationship !== 'no-match' && lr.oldId) {\n lines.push(`\\t ${lr.oldId} -> ${lr.newId}`);\n }\n }\n\n const totalMapped = stats.totalMappedControls;\n lines.push(`Total Mapped Controls: ${totalMapped}`);\n lines.push('');\n }\n\n // Control counts\n lines.push('Control Counts ===========================');\n lines.push(`Total Controls Available for Delta: ${stats.oldControlsLength}`);\n lines.push(` Total Controls Found on XCCDF: ${stats.newControlsLength}`);\n lines.push('');\n\n // Match statistics\n lines.push('Match Statistics =========================');\n lines.push(` Match Controls: ${stats.match}`);\n lines.push(` Possible Mismatch Controls: ${stats.posMisMatch}`);\n lines.push(` Related Match Controls: ${stats.dupMatch}`);\n lines.push(` No Match Controls: ${stats.noMatch}`);\n lines.push('');\n\n // Statistics validation\n lines.push('Statistics Validation =============================================');\n const totalMapped = stats.totalMappedControls;\n const matchMappedValid = (stats.match + stats.posMisMatch + stats.dupMatch) === totalMapped;\n const processedValid = (totalMapped + stats.noMatch) === stats.newControlsLength;\n lines.push(`Match + Mismatch + Related = Total Mapped Controls: (${stats.match}+${stats.posMisMatch}+${stats.dupMatch}=${totalMapped}) ${matchMappedValid}`);\n lines.push(` Total Processed = Total XCCDF Controls: (${totalMapped}+${stats.noMatch}=${totalMapped + stats.noMatch}) ${processedValid}`);\n lines.push('');\n\n // Per-control match method details\n if (result.linkRecords.length > 0) {\n lines.push('Match Details =============================================================');\n for (const lr of result.linkRecords) {\n if (lr.oldId) {\n lines.push(` ${lr.oldId} --> ${lr.newId}`);\n lines.push(` Match method: ${formatMatchMethod(lr)}`);\n } else {\n lines.push(` (none) --> ${lr.newId} [no match]`);\n }\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mitre/hdf-generators",
3
- "version": "3.2.0",
3
+ "version": "3.3.1",
4
4
  "description": "Generate InSpec profile stubs from HDF Baseline definitions",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -25,7 +25,10 @@
25
25
  "author": "MITRE Corporation",
26
26
  "license": "Apache-2.0",
27
27
  "dependencies": {
28
- "@mitre/hdf-schema": "^3.2.0"
28
+ "@mitre/hdf-schema": "^3.3.1"
29
+ },
30
+ "devDependencies": {
31
+ "@mitre/hdf-fixtures": "^3.3.1"
29
32
  },
30
33
  "engines": {
31
34
  "node": ">=22.0.0"