@mitre/hdf-diff
Advanced tools
+55
| # License | ||
| Copyright © 2025 The MITRE Corporation. | ||
| Approved for Public Release; Distribution Unlimited. Case Number 18-3678. | ||
| Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
| not use this file except in compliance with the License. You may obtain a | ||
| copy of the License at | ||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
| WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
| License for the specific language governing permissions and limitations | ||
| under the License. | ||
| ## Redistribution Terms | ||
| Redistribution and use in source and binary forms, with or without | ||
| modification, are permitted provided that the following conditions are | ||
| met: | ||
| - Redistributions of source code must retain the above copyright/digital | ||
| rights legend, this list of conditions and the following Notice. | ||
| - Redistributions in binary form must reproduce the above | ||
| copyright/digital rights legend, this list of conditions and the | ||
| following Notice in the documentation and/or other materials provided | ||
| with the distribution. | ||
| - Neither the name of The MITRE Corporation nor the names of its contributors | ||
| may be used to endorse or promote products derived from this software | ||
| without specific prior written permission. | ||
| ## Notice | ||
| The MITRE Corporation grants permission to reproduce, distribute, modify, and | ||
| otherwise use this software to the extent permitted by the licensed terms | ||
| provided in the LICENSE file included with this project. | ||
| This software was produced by The MITRE Corporation for the U.S. Government | ||
| under contract. As such the U.S. Government has certain use and data | ||
| rights in this software. No use other than those granted to the U.S. | ||
| Government, or to those acting on behalf of the U.S. Government, under | ||
| these contract arrangements is authorized without the express written | ||
| permission of The MITRE Corporation. | ||
| Some files in this codebase were generated by generative AI, under the | ||
| direction and review of The MITRE Corporation employees, for the purpose of | ||
| development efficiency. All AI-generated code functionality was validated | ||
| by standard quality and assurance testing. | ||
| For further information, please contact The MITRE Corporation, | ||
| Contracts Management Office, 7515 Colshire Drive, McLean, VA 22102-7539, | ||
| (703) 983-6000. |
+622
-16
@@ -1,17 +0,623 @@ | ||
| export type { ChangeReason, RequirementState, FieldChange, RequirementDiff, ComponentDiff, ComparisonSummary, BaselineDiff, Source, Annotation, MatchingConfig, HdfComparison, } from './types.js'; | ||
| export type { DiffStatus, DiffSummary, HdfDiff, } from './types.js'; | ||
| export { diffHdf, diffBaselines, diffSystems } from './diff.js'; | ||
| export type { DiffOptions } from './diff.js'; | ||
| export { computeEffectiveStatus, classifyChangeReasons, classifyDiffStatus, } from './status.js'; | ||
| export { computeSummary } from './summary.js'; | ||
| export { isV1Format, normalizeToV2 } from './normalize.js'; | ||
| export { matchRequirements, createExactIdStrategy, createMappedIdStrategy, createCciMatchStrategy, createFuzzyTitleStrategy, tokenize, jaccardSimilarity, } from './matching/index.js'; | ||
| export type { MatchResult, MatchPair, MatchStrategy, MatchOptions, } from './matching/index.js'; | ||
| export { validateComparison } from './validate.js'; | ||
| export type { ValidationResult } from './validate.js'; | ||
| export { EXIT_IDENTICAL, EXIT_DIFFERENCES, EXIT_ERROR, EXIT_DETAILED_IDENTICAL, EXIT_DETAILED_ERROR, EXIT_DETAILED_FIXES_ONLY, EXIT_DETAILED_REGRESSIONS_ONLY, EXIT_DETAILED_MIXED, EXIT_DETAILED_BASELINE_CHANGED, EXIT_DETAILED_DRIFT_ONLY, computeExitCode, computeDetailedExitCode, } from './exit-codes.js'; | ||
| export { render, renderJson, renderMarkdown, renderTerminal, renderCsv } from './renderers/index.js'; | ||
| export type { DetailLevel, RenderOptions } from './renderers/types.js'; | ||
| export { diffSboms } from './sbom.js'; | ||
| export type { PackageDiff, SbomDiffResult } from './sbom.js'; | ||
| //#region src/sbom.d.ts | ||
| /** | ||
| * SBOM comparison for the HDF diff engine. | ||
| * | ||
| * Compares two SBOM documents (CycloneDX or SPDX JSON) and produces | ||
| * package-level diffs: added, removed, updated, unchanged packages. | ||
| * | ||
| * Format auto-detection: | ||
| * - CycloneDX: has `bomFormat: "CycloneDX"`, packages in `components[]` | ||
| * - SPDX: has `spdxVersion`, packages in `packages[]` | ||
| */ | ||
| /** | ||
| * The comparison result for a single package between two SBOMs. | ||
| */ | ||
| interface PackageDiff { | ||
| purl: string; | ||
| name: string; | ||
| state: 'added' | 'removed' | 'updated' | 'unchanged'; | ||
| oldVersion?: string; | ||
| newVersion?: string; | ||
| licenses?: string[]; | ||
| } | ||
| /** | ||
| * The complete result of comparing two SBOM documents. | ||
| */ | ||
| interface SbomDiffResult { | ||
| packageDiffs: PackageDiff[]; | ||
| added: number; | ||
| removed: number; | ||
| updated: number; | ||
| unchanged: number; | ||
| } | ||
| /** | ||
| * Compare two SBOM documents (CycloneDX or SPDX JSON strings) and return | ||
| * package-level diffs. | ||
| * | ||
| * @param oldJson - JSON string of the old SBOM document | ||
| * @param newJson - JSON string of the new SBOM document | ||
| * @returns Structured diff result with per-package state and aggregate counts | ||
| * @throws Error if either input is not valid JSON or not a recognized SBOM format | ||
| */ | ||
| declare function diffSboms(oldJson: string, newJson: string): SbomDiffResult; | ||
| //#endregion | ||
| //#region src/types.d.ts | ||
| /** | ||
| * Why a requirement's effective status changed between two evaluations. | ||
| * | ||
| * Aligned with the hdf-comparison schema's Change_Reason enum: | ||
| * - resultChanged: The underlying test results differ (e.g., a fix was deployed) | ||
| * - overrideAdded: A new statusOverride (waiver/attestation) was added | ||
| * - overrideExpired: A statusOverride present in the old scan has expired by the new scan's timestamp | ||
| * - overrideRemoved: A statusOverride was removed between scans | ||
| * - overrideModified: An existing statusOverride was modified (reserved for future use) | ||
| * - impactChanged: The impact score changed (e.g., from 0.7 to 0.0 means N/A) | ||
| * - baselineUpgraded: The baseline version changed (reserved for future use) | ||
| * - controlMapped: A control was mapped to a different framework (reserved for future use) | ||
| * - scannerChanged: A different scanner was used (reserved for future use) | ||
| * - targetChanged: The scan target changed (reserved for future use) | ||
| * - configChanged: The scan configuration changed (reserved for future use) | ||
| * - metadataChanged: Non-impact baseline metadata changed (tags, descriptions, title, etc.) | ||
| */ | ||
| type ChangeReason = 'resultChanged' | 'overrideAdded' | 'overrideExpired' | 'overrideRemoved' | 'overrideModified' | 'impactChanged' | 'baselineUpgraded' | 'controlMapped' | 'scannerChanged' | 'targetChanged' | 'configChanged' | 'metadataChanged' | 'dispositionChanged' | 'effectiveImpactChanged'; | ||
| /** | ||
| * Classification of how a requirement's state changed between evaluations. | ||
| * Uses SARIF-inspired vocabulary. | ||
| * | ||
| * - new: Requirement exists only in the new evaluation (was "added") | ||
| * - absent: Requirement exists only in the old evaluation (was "removed") | ||
| * - unchanged: Same effective status in both evaluations | ||
| * - updated: Status changed but doesn't fit fixed/regressed (was "changed") | ||
| * - fixed: Was failing/error, now passing | ||
| * - regressed: Was passing, now failing/error | ||
| * - moved: Requirement ID changed but content is the same (future use) | ||
| * - split: One requirement became multiple (future use) | ||
| * - merged: Multiple requirements became one (future use) | ||
| */ | ||
| type RequirementState = 'new' | 'absent' | 'unchanged' | 'updated' | 'fixed' | 'regressed' | 'moved' | 'split' | 'merged'; | ||
| /** | ||
| * A field-level difference on a requirement, following JSON Patch-like conventions. | ||
| */ | ||
| interface FieldChange { | ||
| /** The operation type: add, remove, or replace */ | ||
| op: 'add' | 'remove' | 'replace'; | ||
| /** Dot-notation path to the changed field (e.g., 'impact', 'tags.cci') */ | ||
| path: string; | ||
| /** Value in the old evaluation (undefined for 'add' operations) */ | ||
| oldValue?: unknown; | ||
| /** Value in the new evaluation (undefined for 'remove' operations) */ | ||
| newValue?: unknown; | ||
| } | ||
| /** | ||
| * The diff for a single requirement across two evaluations. | ||
| */ | ||
| interface RequirementDiff { | ||
| /** The requirement ID (e.g., 'SV-238196') */ | ||
| id: string; | ||
| /** Classification of the change */ | ||
| state: RequirementState; | ||
| /** Why the status changed — empty array if unchanged */ | ||
| changeReasons: ChangeReason[]; | ||
| /** Full snapshot of the requirement from the old evaluation (null when state = 'new') */ | ||
| before: Record<string, unknown> | null; | ||
| /** Full snapshot of the requirement from the new evaluation (null when state = 'absent') */ | ||
| after: Record<string, unknown> | null; | ||
| /** The requirement title (from whichever evaluation has it) */ | ||
| title?: string; | ||
| /** Effective status in the old evaluation (undefined if new) */ | ||
| oldEffectiveStatus?: string; | ||
| /** Effective status in the new evaluation (undefined if absent) */ | ||
| newEffectiveStatus?: string; | ||
| /** Impact in the old evaluation */ | ||
| oldImpact?: number; | ||
| /** Impact in the new evaluation */ | ||
| newImpact?: number; | ||
| /** Field-level diffs for non-status fields */ | ||
| fieldChanges: FieldChange[]; | ||
| /** The matching strategy that paired this requirement (e.g., 'exactId') */ | ||
| matchStrategy?: string; | ||
| /** Confidence of the match (0.0-1.0) */ | ||
| matchConfidence?: number; | ||
| /** Index into the sources array for fleet mode */ | ||
| sourceIndex?: number; | ||
| } | ||
| /** | ||
| * Summary counts for the comparison. | ||
| */ | ||
| interface ComparisonSummary { | ||
| /** Requirements that went from failing/error to passing */ | ||
| fixed: number; | ||
| /** Requirements that went from passing to failing/error */ | ||
| regressed: number; | ||
| /** Requirements present only in the new evaluation (was "added") */ | ||
| new: number; | ||
| /** Requirements present only in the old evaluation (was "removed") */ | ||
| absent: number; | ||
| /** Requirements with the same effective status */ | ||
| unchanged: number; | ||
| /** Requirements whose status changed in a way other than fixed/regressed (was "changed") */ | ||
| updated: number; | ||
| /** Total unique requirements across both evaluations */ | ||
| total: number; | ||
| /** Number of requirements matched between old and new */ | ||
| matchedCount: number; | ||
| /** Number of requirements only in old (unmatched) */ | ||
| unmatchedOldCount: number; | ||
| /** Number of requirements only in new (unmatched) */ | ||
| unmatchedNewCount: number; | ||
| } | ||
| /** | ||
| * The diff for a single component across two system documents. | ||
| * Used in systemDrift comparison mode. | ||
| */ | ||
| interface ComponentDiff { | ||
| /** Component name */ | ||
| name: string; | ||
| /** Classification of the change: new, absent, unchanged, or updated */ | ||
| state: 'new' | 'absent' | 'unchanged' | 'updated'; | ||
| /** Component snapshot from the old system (null when state = 'new') */ | ||
| before: Record<string, unknown> | null; | ||
| /** Component snapshot from the new system (null when state = 'absent') */ | ||
| after: Record<string, unknown> | null; | ||
| /** Field-level diffs between old and new component */ | ||
| fieldChanges: FieldChange[]; | ||
| } | ||
| /** | ||
| * The diff for a single baseline across two evaluations. | ||
| */ | ||
| interface BaselineDiff { | ||
| /** Baseline name */ | ||
| name: string; | ||
| /** Version in the old evaluation */ | ||
| oldVersion?: string; | ||
| /** Version in the new evaluation */ | ||
| newVersion?: string; | ||
| /** Whether this baseline was new, absent, updated, or unchanged */ | ||
| state: 'new' | 'absent' | 'updated' | 'unchanged'; | ||
| } | ||
| /** | ||
| * Metadata about a source document used in the comparison. | ||
| */ | ||
| interface Source { | ||
| /** Role of the source in the comparison */ | ||
| role: 'old' | 'new' | 'golden' | 'reference' | 'system'; | ||
| /** Human-readable label */ | ||
| label: string; | ||
| /** URI to the source document */ | ||
| uri?: string; | ||
| /** Original format of the source (e.g., 'hdf-results-v2', 'inspec-exec-json-v1') */ | ||
| originalFormat?: string; | ||
| /** Assessment timestamp from the source document */ | ||
| assessmentTimestamp?: string; | ||
| } | ||
| /** | ||
| * An annotation attached to a requirement diff. | ||
| */ | ||
| interface Annotation { | ||
| /** Human-readable label */ | ||
| label: string; | ||
| /** Description of the annotation */ | ||
| text: string; | ||
| /** When the annotation was created */ | ||
| timestamp?: string; | ||
| } | ||
| /** | ||
| * The top-level comparison result comparing two or more HDF evaluations. | ||
| */ | ||
| interface HdfComparison { | ||
| /** Schema version for the comparison format */ | ||
| formatVersion: '1.0.0'; | ||
| /** The mode of comparison */ | ||
| comparisonMode: 'temporal' | 'baseline' | 'fleet' | 'multiSource' | 'baselineEvolution' | 'systemDrift'; | ||
| /** When the comparison was generated */ | ||
| timestamp?: string; | ||
| /** Source documents used in the comparison */ | ||
| sources: Source[]; | ||
| /** Matching configuration used */ | ||
| matching?: MatchingConfig; | ||
| /** Aggregate counts */ | ||
| summary: ComparisonSummary; | ||
| /** Per-baseline diffs */ | ||
| baselineDiffs: BaselineDiff[]; | ||
| /** Per-requirement diffs, sorted by id */ | ||
| requirementDiffs: RequirementDiff[]; | ||
| /** Per-component diffs (systemDrift mode only) */ | ||
| componentDiffs?: ComponentDiff[]; | ||
| /** Per-package diffs from embedded SBOM comparison (systemDrift mode only) */ | ||
| packageDiffs?: PackageDiff[]; | ||
| /** URI identifying the system being compared (systemDrift mode only) */ | ||
| systemRef?: string; | ||
| /** Requirements that drifted from a golden baseline (future use) */ | ||
| drift?: RequirementDiff[]; | ||
| /** Annotations keyed by requirement ID */ | ||
| annotations?: Record<string, Annotation>; | ||
| /** Extension data for custom integrations */ | ||
| extensions?: Record<string, unknown>; | ||
| } | ||
| /** | ||
| * Configuration for how requirements are matched between evaluations. | ||
| */ | ||
| interface MatchingConfig { | ||
| /** The primary strategy used for matching requirements across sources */ | ||
| primaryStrategy: string; | ||
| /** Minimum confidence threshold for a match */ | ||
| confidenceThreshold?: number; | ||
| } | ||
| /** @deprecated Use RequirementState instead */ | ||
| type DiffStatus = RequirementState; | ||
| /** @deprecated Use ComparisonSummary instead */ | ||
| type DiffSummary = ComparisonSummary; | ||
| /** @deprecated Use HdfComparison instead */ | ||
| type HdfDiff = HdfComparison; | ||
| //#endregion | ||
| //#region src/diff.d.ts | ||
| /** | ||
| * Options for configuring the diff behavior. | ||
| */ | ||
| interface DiffOptions { | ||
| /** Fields to track for field-level diffs (default: ['impact', 'severity', 'tags']) */ | ||
| trackedFields?: string[]; | ||
| /** Comparison mode (default: 'temporal') */ | ||
| comparisonMode?: 'temporal' | 'baseline' | 'fleet' | 'multiSource' | 'baselineEvolution' | 'systemDrift'; | ||
| /** Primary matching strategy name (default: 'exactId') */ | ||
| matchStrategy?: string; | ||
| /** Fallback strategy names, applied in order to remaining unmatched requirements */ | ||
| fallbackStrategies?: string[]; | ||
| /** Mapping table for the 'mappedId' strategy (old ID -> new ID) */ | ||
| mappingTable?: Record<string, string>; | ||
| /** Minimum confidence threshold for fuzzy matching (default: 0.6) */ | ||
| minConfidence?: number; | ||
| /** Validate output against hdf-comparison schema. Default: false (performance). */ | ||
| validateOutput?: boolean; | ||
| } | ||
| /** | ||
| * Compare two HDF results documents and produce a structured comparison. | ||
| * | ||
| * Requirements are matched using a configurable matching strategy (default: exact ID). | ||
| * Baselines are matched by `name`. | ||
| * | ||
| * For fleet mode, `newResults` can be an array of documents, each compared | ||
| * pairwise against `oldResults` (the reference). | ||
| */ | ||
| declare function diffHdf(oldResults: Record<string, unknown>, newResults: Record<string, unknown> | Record<string, unknown>[], options?: DiffOptions): HdfComparison; | ||
| /** | ||
| * Compare two HDF baseline documents and produce a structured comparison | ||
| * showing requirement changes between baseline versions. | ||
| * | ||
| * Unlike diffHdf (which compares results/evaluations), this compares baseline | ||
| * definitions — requirements without results. There is no status-based classification | ||
| * (fixed/regressed); only metadata changes (title, impact, descriptions, tags) are tracked. | ||
| */ | ||
| declare function diffBaselines(oldBaseline: Record<string, unknown>, newBaseline: Record<string, unknown>, options?: DiffOptions): HdfComparison; | ||
| /** | ||
| * Compare two HDF system documents and produce a structured comparison | ||
| * showing component-level changes between system versions. | ||
| * | ||
| * Components are matched by componentId (UUID) when available, falling back | ||
| * to exact name matching. Top-level system fields, data flows, and embedded | ||
| * SBOMs are also compared. | ||
| */ | ||
| declare function diffSystems(oldSystem: Record<string, unknown>, newSystem: Record<string, unknown>, options?: DiffOptions): HdfComparison; | ||
| //#endregion | ||
| //#region src/status.d.ts | ||
| /** | ||
| * Determine the effective status of a requirement from its results and overrides. | ||
| * | ||
| * Priority: | ||
| * 1. impact === 0 → notApplicable (regardless of results) | ||
| * 2. effectiveStatus field set (and no statusOverrides) → use it | ||
| * 3. Non-expired statusOverrides → use first non-expired | ||
| * 4. Aggregate results using worst-wins | ||
| * 5. Empty results → notReviewed | ||
| */ | ||
| declare function computeEffectiveStatus(requirement: Record<string, unknown>, referenceTimestamp?: string): string; | ||
| /** | ||
| * Classify why the status changed between two requirements. | ||
| * Returns an array of change reasons (a status change can have multiple causes). | ||
| */ | ||
| declare function classifyChangeReasons(oldReq: Record<string, unknown>, newReq: Record<string, unknown>, oldTimestamp?: string, newTimestamp?: string): ChangeReason[]; | ||
| /** | ||
| * Classify the overall diff status based on old and new effective statuses. | ||
| * | ||
| * - If old is failing and new is passing → 'fixed' | ||
| * - If old is passing and new is failing → 'regressed' | ||
| * - If statuses are equal → 'unchanged' | ||
| * - Otherwise → 'updated' | ||
| */ | ||
| declare function classifyDiffStatus(oldEffectiveStatus: string, newEffectiveStatus: string): RequirementState; | ||
| //#endregion | ||
| //#region src/summary.d.ts | ||
| /** | ||
| * Compute summary counts from an array of RequirementDiff entries. | ||
| */ | ||
| declare function computeSummary(requirements: RequirementDiff[]): ComparisonSummary; | ||
| //#endregion | ||
| //#region src/normalize.d.ts | ||
| /** | ||
| * Normalize InSpec exec-json v1 format to HDF v2 structure for diffing. | ||
| * | ||
| * V1 uses: profiles[].controls[].results[].code_desc, source_location, start_time | ||
| * V2 uses: baselines[].requirements[].results[].codeDesc, sourceLocation, startTime | ||
| * | ||
| * This module detects v1 documents and converts them in-memory to v2 shape | ||
| * so the diff engine only needs to handle one format. | ||
| */ | ||
| /** | ||
| * Detect whether a document is v1 (InSpec exec-json) format. | ||
| * V1 has `profiles` at the top level; v2 has `baselines`. | ||
| */ | ||
| declare function isV1Format(doc: Record<string, unknown>): boolean; | ||
| /** | ||
| * Normalize a document to v2-like structure. If already v2, returns as-is. | ||
| * If v1, converts profiles→baselines, controls→requirements, and snake_case→camelCase. | ||
| */ | ||
| declare function normalizeToV2(doc: Record<string, unknown>): Record<string, unknown>; | ||
| //#endregion | ||
| //#region src/matching/types.d.ts | ||
| /** | ||
| * Result of matching requirements between two evaluations. | ||
| */ | ||
| interface MatchResult { | ||
| /** Paired requirements with their match metadata */ | ||
| matched: MatchPair[]; | ||
| /** Requirements only in the old evaluation (unmatched) */ | ||
| unmatchedOld: Record<string, unknown>[]; | ||
| /** Requirements only in the new evaluation (unmatched) */ | ||
| unmatchedNew: Record<string, unknown>[]; | ||
| } | ||
| /** | ||
| * A single matched pair of requirements. | ||
| */ | ||
| interface MatchPair { | ||
| /** The requirement from the old evaluation */ | ||
| oldReq: Record<string, unknown>; | ||
| /** The requirement from the new evaluation */ | ||
| newReq: Record<string, unknown>; | ||
| /** Name of the strategy that produced this match */ | ||
| strategy: string; | ||
| /** Confidence score for the match (0.0 - 1.0) */ | ||
| confidence: number; | ||
| } | ||
| /** | ||
| * A pluggable strategy for matching requirements between evaluations. | ||
| */ | ||
| interface MatchStrategy { | ||
| /** Unique name for this strategy */ | ||
| name: string; | ||
| /** Match old requirements against new requirements */ | ||
| match(oldReqs: Record<string, unknown>[], newReqs: Record<string, unknown>[]): MatchResult; | ||
| } | ||
| //#endregion | ||
| //#region src/matching/exact-id.d.ts | ||
| /** | ||
| * Create an exact ID matching strategy. | ||
| * | ||
| * Matches requirements by their `id` field with exact string equality. | ||
| * Confidence is always 1.0 for matches. | ||
| */ | ||
| declare function createExactIdStrategy(): MatchStrategy; | ||
| //#endregion | ||
| //#region src/matching/mapped-id.d.ts | ||
| /** | ||
| * Create a mapped ID matching strategy. | ||
| * | ||
| * Translates old requirement IDs using a mapping table before matching. | ||
| * Only matches requirements whose old ID appears in the mapping table and | ||
| * whose mapped new ID exists in the new requirements. | ||
| * | ||
| * Confidence is 0.95 for mapped matches (slightly less than exact ID | ||
| * because the match depends on the accuracy of the mapping table). | ||
| */ | ||
| declare function createMappedIdStrategy(mapping: Record<string, string>): MatchStrategy; | ||
| //#endregion | ||
| //#region src/matching/cci-match.d.ts | ||
| /** | ||
| * Create a CCI-based matching strategy. | ||
| * | ||
| * Matches requirements that share the same CCI identifier in `tags.cci`. | ||
| * Only produces a match when exactly one old requirement and exactly one | ||
| * new requirement share a given CCI (unambiguous). Ambiguous CCIs (shared | ||
| * by multiple old or multiple new requirements) are skipped, and those | ||
| * requirements are left unmatched. | ||
| * | ||
| * Confidence is 0.8 for unambiguous matches. | ||
| */ | ||
| declare function createCciMatchStrategy(): MatchStrategy; | ||
| //#endregion | ||
| //#region src/matching/fuzzy-match.d.ts | ||
| /** | ||
| * Tokenize a title string into a set of meaningful tokens. | ||
| * | ||
| * - Lowercases the string | ||
| * - Splits on whitespace and punctuation | ||
| * - Filters out common stop words | ||
| * - Returns unique tokens as a Set | ||
| */ | ||
| declare function tokenize(text: string): Set<string>; | ||
| /** | ||
| * Compute Jaccard similarity between two sets. | ||
| * Returns |intersection| / |union|, or 0 if both sets are empty. | ||
| */ | ||
| declare function jaccardSimilarity(a: Set<string>, b: Set<string>): number; | ||
| /** | ||
| * Create a fuzzy title matching strategy. | ||
| * | ||
| * Matches requirements by token-based Jaccard similarity on the `title` field. | ||
| * Uses greedy best-match: for each unmatched old requirement, finds the | ||
| * best-matching unmatched new requirement above the confidence threshold. | ||
| * | ||
| * @param minConfidence Minimum Jaccard similarity to accept a match (default: 0.6) | ||
| */ | ||
| declare function createFuzzyTitleStrategy(minConfidence?: number): MatchStrategy; | ||
| //#endregion | ||
| //#region src/matching/index.d.ts | ||
| /** | ||
| * Options for configuring requirement matching. | ||
| */ | ||
| interface MatchOptions { | ||
| /** Primary matching strategy name (default: 'exactId') */ | ||
| strategy?: string; | ||
| /** Fallback strategy names, applied in order to remaining unmatched requirements */ | ||
| fallbackStrategies?: string[]; | ||
| /** Mapping table for the 'mappedId' strategy (old ID -> new ID) */ | ||
| mappingTable?: Record<string, string>; | ||
| /** Minimum confidence threshold for fuzzy matching (default: 0.6) */ | ||
| minConfidence?: number; | ||
| } | ||
| /** | ||
| * Match requirements between two evaluations using a primary strategy | ||
| * and optional fallback strategies. | ||
| * | ||
| * The registry applies strategies in order: | ||
| * 1. Primary strategy matches what it can | ||
| * 2. Unmatched requirements pass to the next fallback strategy | ||
| * 3. Process continues until all strategies are exhausted or all | ||
| * requirements are matched | ||
| * | ||
| * @param oldReqs Requirements from the old evaluation | ||
| * @param newReqs Requirements from the new evaluation | ||
| * @param options Matching configuration | ||
| * @returns Combined match result from all strategies | ||
| */ | ||
| declare function matchRequirements(oldReqs: Record<string, unknown>[], newReqs: Record<string, unknown>[], options?: MatchOptions): MatchResult; | ||
| //#endregion | ||
| //#region src/validate.d.ts | ||
| /** | ||
| * Result of validating a document against the hdf-comparison schema. | ||
| */ | ||
| interface ValidationResult { | ||
| /** Whether the document conforms to the schema */ | ||
| valid: boolean; | ||
| /** Human-readable error messages (only present when valid is false) */ | ||
| errors?: string[]; | ||
| } | ||
| /** | ||
| * Validate a document against the hdf-comparison schema. | ||
| * | ||
| * Delegates to @mitre/hdf-validators which loads schemas from embedded | ||
| * bundled JSON (no filesystem access, no hardcoded version URLs). | ||
| * | ||
| * @param doc - The document to validate (typically the output of `diffHdf()`) | ||
| * @returns Validation result with `valid` boolean and optional `errors` array | ||
| */ | ||
| declare function validateComparison(doc: unknown): ValidationResult; | ||
| //#endregion | ||
| //#region src/exit-codes.d.ts | ||
| /** GNU diff compatible exit codes (--exit-code mode) */ | ||
| declare const EXIT_IDENTICAL = 0; | ||
| declare const EXIT_DIFFERENCES = 1; | ||
| declare const EXIT_ERROR = 2; | ||
| /** Detailed exit codes (--detailed-exitcode mode) */ | ||
| declare const EXIT_DETAILED_IDENTICAL = 0; | ||
| declare const EXIT_DETAILED_ERROR = 1; | ||
| declare const EXIT_DETAILED_FIXES_ONLY = 10; | ||
| declare const EXIT_DETAILED_REGRESSIONS_ONLY = 11; | ||
| declare const EXIT_DETAILED_MIXED = 12; | ||
| declare const EXIT_DETAILED_BASELINE_CHANGED = 13; | ||
| declare const EXIT_DETAILED_DRIFT_ONLY = 14; | ||
| /** | ||
| * Compute the GNU diff compatible exit code from a comparison summary. | ||
| * | ||
| * Returns: | ||
| * 0 = identical (no differences found) | ||
| * 1 = differences found (any kind) | ||
| * | ||
| * Note: error (exit code 2) is not computed from the summary — callers | ||
| * should return EXIT_ERROR directly when I/O or parse errors occur. | ||
| */ | ||
| declare function computeExitCode(summary: ComparisonSummary): number; | ||
| /** | ||
| * Compute the detailed exit code from a comparison summary. | ||
| * | ||
| * Returns: | ||
| * 0 = identical (no differences found) | ||
| * 10 = differences found, fixes only (security posture improved) | ||
| * 11 = differences found, regressions only (security posture degraded) | ||
| * 12 = differences found, mixed fixes and regressions | ||
| * 13 = differences found, only new/absent controls (baseline changed) | ||
| * 14 = differences found, only metadata drift (no status changes) | ||
| * | ||
| * Note: error (exit code 1) is not computed from the summary — callers | ||
| * should return EXIT_DETAILED_ERROR directly when I/O or parse errors occur. | ||
| * | ||
| * Priority order: mixed(12) > regressions(11) > fixes(10) > baseline(13) > drift(14) | ||
| */ | ||
| declare function computeDetailedExitCode(summary: ComparisonSummary): number; | ||
| //#endregion | ||
| //#region src/renderers/types.d.ts | ||
| type DetailLevel = 'summary' | 'control' | 'full'; | ||
| interface RenderOptions { | ||
| /** What detail to show. Default: 'control' */ | ||
| detail?: DetailLevel; | ||
| /** Only show requirements matching these states */ | ||
| filterStates?: string[]; | ||
| /** Only show requirements matching this severity */ | ||
| filterSeverity?: string; | ||
| /** Use color codes (for terminal). Default: true */ | ||
| color?: boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/renderers/json.d.ts | ||
| /** | ||
| * Render an HdfComparison as a JSON string. | ||
| * | ||
| * - `detail: 'summary'` -- only `{ formatVersion, comparisonMode, summary }` | ||
| * - `detail: 'control'` -- full document but `before`/`after` stripped from requirementDiffs | ||
| * - `detail: 'full'` -- `JSON.stringify(comparison, null, 2)` (the complete document) | ||
| * | ||
| * Default detail level: `'control'`. | ||
| */ | ||
| declare function renderJson(comparison: HdfComparison, options?: RenderOptions): string; | ||
| //#endregion | ||
| //#region src/renderers/markdown.d.ts | ||
| /** | ||
| * Render an HdfComparison as a Markdown string. | ||
| * | ||
| * - `detail: 'summary'` -- summary table only | ||
| * - `detail: 'control'` -- summary + per-requirement tables by state | ||
| * - `detail: 'full'` -- summary + per-requirement tables with changeReasons and fieldChanges | ||
| * | ||
| * Default detail level: `'control'`. | ||
| */ | ||
| declare function renderMarkdown(comparison: HdfComparison, options?: RenderOptions): string; | ||
| //#endregion | ||
| //#region src/renderers/terminal.d.ts | ||
| /** | ||
| * Render an HdfComparison for terminal display with optional ANSI colors. | ||
| * | ||
| * - `detail: 'summary'` -- just the summary line | ||
| * - `detail: 'control'` -- requirement list + summary (excludes unchanged) | ||
| * - `detail: 'full'` -- all requirements including unchanged, with changeReasons and fieldChanges | ||
| * | ||
| * When `color: false`, no ANSI escape codes are emitted. | ||
| * Default detail level: `'control'`. Default color: `true`. | ||
| */ | ||
| declare function renderTerminal(comparison: HdfComparison, options?: RenderOptions): string; | ||
| //#endregion | ||
| //#region src/renderers/csv.d.ts | ||
| /** | ||
| * Render an HdfComparison as a CSV string. | ||
| * | ||
| * One row per requirement. Columns: | ||
| * - ID, Title, State, Old Status, New Status, Impact (Old), Impact (New), Change Reasons | ||
| * | ||
| * When `detail: 'full'`, an additional Field Changes column is included. | ||
| * | ||
| * Header row is always included. Standard CSV escaping (RFC 4180). | ||
| * Default detail level: `'control'`. | ||
| */ | ||
| declare function renderCsv(comparison: HdfComparison, options?: RenderOptions): string; | ||
| //#endregion | ||
| //#region src/renderers/index.d.ts | ||
| /** | ||
| * Convenience function to render a comparison in any supported format. | ||
| * | ||
| * @param comparison - The HdfComparison document to render | ||
| * @param format - Output format: 'json', 'markdown', 'terminal', or 'csv' | ||
| * @param options - Rendering options (detail level, filters, color) | ||
| * @returns The rendered string | ||
| */ | ||
| declare function render(comparison: HdfComparison, format: 'json' | 'markdown' | 'terminal' | 'csv', options?: RenderOptions): string; | ||
| //#endregion | ||
| export { type Annotation, type BaselineDiff, type ChangeReason, type ComparisonSummary, type ComponentDiff, type DetailLevel, type DiffOptions, type DiffStatus, type DiffSummary, EXIT_DETAILED_BASELINE_CHANGED, EXIT_DETAILED_DRIFT_ONLY, EXIT_DETAILED_ERROR, EXIT_DETAILED_FIXES_ONLY, EXIT_DETAILED_IDENTICAL, EXIT_DETAILED_MIXED, EXIT_DETAILED_REGRESSIONS_ONLY, EXIT_DIFFERENCES, EXIT_ERROR, EXIT_IDENTICAL, type FieldChange, type HdfComparison, type HdfDiff, type MatchOptions, type MatchPair, type MatchResult, type MatchStrategy, type MatchingConfig, type PackageDiff, type RenderOptions, type RequirementDiff, type RequirementState, type SbomDiffResult, type Source, type ValidationResult, classifyChangeReasons, classifyDiffStatus, computeDetailedExitCode, computeEffectiveStatus, computeExitCode, computeSummary, createCciMatchStrategy, createExactIdStrategy, createFuzzyTitleStrategy, createMappedIdStrategy, diffBaselines, diffHdf, diffSboms, diffSystems, isV1Format, jaccardSimilarity, matchRequirements, normalizeToV2, render, renderCsv, renderJson, renderMarkdown, renderTerminal, tokenize, validateComparison }; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,MAAM,EACN,UAAU,EACV,cAAc,EACd,aAAa,GACd,MAAM,YAAY,CAAC;AAGpB,YAAY,EACV,UAAU,EACV,WAAW,EACX,OAAO,GACR,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAChE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG7C,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9C,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG3D,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,EACxB,QAAQ,EACR,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,WAAW,EACX,SAAS,EACT,aAAa,EACb,YAAY,GACb,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,uBAAuB,EACvB,mBAAmB,EACnB,wBAAwB,EACxB,8BAA8B,EAC9B,mBAAmB,EACnB,8BAA8B,EAC9B,wBAAwB,EACxB,eAAe,EACf,uBAAuB,GACxB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACrG,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGvE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"} | ||
| {"version":3,"file":"index.d.ts","names":[],"sources":["../src/sbom.ts","../src/types.ts","../src/diff.ts","../src/status.ts","../src/summary.ts","../src/normalize.ts","../src/matching/types.ts","../src/matching/exact-id.ts","../src/matching/mapped-id.ts","../src/matching/cci-match.ts","../src/matching/fuzzy-match.ts","../src/matching/index.ts","../src/validate.ts","../src/exit-codes.ts","../src/renderers/types.ts","../src/renderers/json.ts","../src/renderers/markdown.ts","../src/renderers/terminal.ts","../src/renderers/csv.ts","../src/renderers/index.ts"],"mappings":"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"} |
+19
-19
| { | ||
| "name": "@mitre/hdf-diff", | ||
| "version": "3.0.1", | ||
| "version": "3.1.0-rc.1", | ||
| "description": "Structured comparison of HDF evaluation results — tracks what changed, why, and by how much", | ||
@@ -13,4 +13,4 @@ "publishConfig": { | ||
| ".": { | ||
| "import": "./dist/index.js", | ||
| "types": "./dist/index.d.ts" | ||
| "types": "./dist/index.d.ts", | ||
| "import": "./dist/index.js" | ||
| } | ||
@@ -21,14 +21,2 @@ }, | ||
| ], | ||
| "scripts": { | ||
| "build": "pnpm clean && tsc", | ||
| "clean": "rimraf dist", | ||
| "test": "pnpm run test:ts", | ||
| "test:ts": "vitest run", | ||
| "test:go": "echo 'No Go tests yet in hdf-diff'", | ||
| "test:watch": "vitest", | ||
| "test:coverage": "vitest run --coverage", | ||
| "type-check": "tsc --noEmit", | ||
| "lint": "eslint src test", | ||
| "lint:fix": "eslint src test --fix" | ||
| }, | ||
| "repository": { | ||
@@ -42,6 +30,6 @@ "type": "git", | ||
| "dependencies": { | ||
| "@mitre/hdf-validators": "workspace:*" | ||
| "@mitre/hdf-validators": "^3.1.0-rc.1" | ||
| }, | ||
| "engines": { | ||
| "node": ">=20.0.0" | ||
| "node": ">=22.0.0" | ||
| }, | ||
@@ -55,3 +43,15 @@ "keywords": [ | ||
| "compliance" | ||
| ] | ||
| } | ||
| ], | ||
| "scripts": { | ||
| "build": "tsdown", | ||
| "clean": "rimraf dist", | ||
| "test": "pnpm run test:ts", | ||
| "test:ts": "vitest run", | ||
| "test:go": "echo 'No Go tests yet in hdf-diff'", | ||
| "test:watch": "vitest", | ||
| "test:coverage": "vitest run --coverage", | ||
| "type-check": "tsc --noEmit", | ||
| "lint": "eslint src test", | ||
| "lint:fix": "eslint src test --fix" | ||
| } | ||
| } |
+49
-17
| # @mitre/hdf-diff | ||
| Structured comparison of HDF evaluation results — tracks what changed, why, and by how much. | ||
| Structured comparison of HDF documents — tracks what changed, why, and by how much. | ||
| ## What it does | ||
| Compares two HDF results documents and produces a structured diff showing: | ||
| - Requirements added, removed, or changed between evaluations | ||
| - Status transitions (passed→failed, failed→passed, etc.) with change reasons | ||
| - Field-level changes (impact, title, severity) | ||
| - Per-baseline and per-component compliance summaries | ||
| - SBOM (CycloneDX/SPDX) package-level diffs | ||
| Compares HDF documents (results, baselines, or system documents) and produces a structured diff: | ||
| Output formats: JSON, Markdown, CSV, terminal (ANSI-colored). | ||
| - **Results comparison** (`diffHdf`) — requirements added, removed, or changed between evaluations; status transitions with change reasons; field-level changes (impact, severity, disposition, effectiveImpact); per-baseline compliance summaries | ||
| - **Baseline evolution** (`diffBaselines`) — track how a baseline's requirements change across versions (IDs added/removed, impact/severity/title changes) | ||
| - **System drift** (`diffSystems`) — compare two HDF system documents for component, data flow, and configuration changes | ||
| - **SBOM comparison** (`diffSboms`) — CycloneDX/SPDX package-level diffs (added, removed, updated) | ||
| - **Amendment operations** (Go only, `amend` subpackage) — merge overrides into results, verify amendment chains, compute effectiveStatus/effectiveImpact/disposition | ||
| Additional capabilities: | ||
| - Multiple comparison modes: temporal, baseline, fleet, multi-source | ||
| - Format normalization: legacy InSpec exec-json (v1) to current HDF | ||
| - Output formats: JSON, Markdown, CSV, terminal (ANSI-colored) | ||
| - CI exit codes: GNU diff-compatible (0/1/2) and detailed (10-14) | ||
| ## Relationship to other packages | ||
@@ -20,6 +25,5 @@ | ||
| |---------|-------------| | ||
| | **hdf-schema** | Provides the `HDFResults` types that hdf-diff consumes | | ||
| | **hdf-schema** | Provides `HDFResults`, `HDFBaseline`, and system types that hdf-diff consumes | | ||
| | **hdf-validators** | Used to validate comparison output against the HDF comparison schema | | ||
| | **hdf-cli** | `hdf diff` command wraps this library for CLI use | | ||
| | **hdf-parsers** | Not used — hdf-diff operates on typed structs, not raw JSON | | ||
| | **hdf-cli** | `hdf diff` and `hdf amend` commands wrap this library for CLI use | | ||
@@ -35,13 +39,17 @@ ## Installation | ||
| ```typescript | ||
| import { diffHdf, render } from '@mitre/hdf-diff'; | ||
| import { diffHdf, diffBaselines, diffSystems, render } from '@mitre/hdf-diff'; | ||
| // Compare two evaluation results | ||
| // Compare two evaluation results (temporal mode) | ||
| const comparison = diffHdf(oldResults, newResults); | ||
| // Render as markdown | ||
| const md = render(comparison, { format: 'markdown', detail: 'full' }); | ||
| // Compare baseline evolution (track requirement changes across versions) | ||
| const baselineDiff = diffBaselines(oldBaseline, newBaseline); | ||
| // Render as JSON | ||
| const json = render(comparison, { format: 'json' }); | ||
| // Compare system documents (component/data-flow drift) | ||
| const systemDiff = diffSystems(oldSystem, newSystem); | ||
| // Render as markdown, JSON, CSV, or terminal | ||
| const md = render(comparison, 'markdown', { detail: 'full' }); | ||
| const json = render(comparison, 'json'); | ||
| // Check exit codes for CI | ||
@@ -81,10 +89,34 @@ import { computeExitCode, EXIT_IDENTICAL } from '@mitre/hdf-diff'; | ||
| ```bash | ||
| # Results comparison | ||
| hdf diff old-results.json new-results.json | ||
| hdf diff old-results.json new-results.json --format markdown | ||
| hdf diff old-results.json new-results.json --json | ||
| # System drift detection | ||
| hdf diff old-system.json new-system.json | ||
| # SBOM comparison | ||
| hdf diff --sbom old-sbom.json new-sbom.json | ||
| # Baseline mode (golden baseline vs current scan) | ||
| hdf diff --mode baseline golden.json current.json | ||
| ``` | ||
| ## Go usage | ||
| The diff engine is also available as a Go module: | ||
| ```go | ||
| import diff "github.com/mitre/hdf-libs/hdf-diff/go" | ||
| ``` | ||
| See the [hdf-diff/go](https://github.com/mitre/hdf-libs/tree/main/hdf-diff/go) directory for the Go API. | ||
| ## Schema documentation | ||
| The HDF Comparison schema that hdf-diff produces is documented at | ||
| <https://mitre.github.io/hdf-libs/schemas/>. | ||
| ## License | ||
| Apache-2.0 © MITRE Corporation |
| import type { HdfComparison } from './types.js'; | ||
| /** | ||
| * Options for configuring the diff behavior. | ||
| */ | ||
| export interface DiffOptions { | ||
| /** Fields to track for field-level diffs (default: ['impact', 'severity', 'tags']) */ | ||
| trackedFields?: string[]; | ||
| /** Comparison mode (default: 'temporal') */ | ||
| comparisonMode?: 'temporal' | 'baseline' | 'fleet' | 'multiSource' | 'baselineEvolution' | 'systemDrift'; | ||
| /** Primary matching strategy name (default: 'exactId') */ | ||
| matchStrategy?: string; | ||
| /** Fallback strategy names, applied in order to remaining unmatched requirements */ | ||
| fallbackStrategies?: string[]; | ||
| /** Mapping table for the 'mappedId' strategy (old ID -> new ID) */ | ||
| mappingTable?: Record<string, string>; | ||
| /** Minimum confidence threshold for fuzzy matching (default: 0.6) */ | ||
| minConfidence?: number; | ||
| /** Validate output against hdf-comparison schema. Default: false (performance). */ | ||
| validateOutput?: boolean; | ||
| } | ||
| /** | ||
| * Compare two HDF results documents and produce a structured comparison. | ||
| * | ||
| * Requirements are matched using a configurable matching strategy (default: exact ID). | ||
| * Baselines are matched by `name`. | ||
| * | ||
| * For fleet mode, `newResults` can be an array of documents, each compared | ||
| * pairwise against `oldResults` (the reference). | ||
| */ | ||
| export declare function diffHdf(oldResults: Record<string, unknown>, newResults: Record<string, unknown> | Record<string, unknown>[], options?: DiffOptions): HdfComparison; | ||
| /** | ||
| * Compare two HDF baseline documents and produce a structured comparison | ||
| * showing requirement changes between baseline versions. | ||
| * | ||
| * Unlike diffHdf (which compares results/evaluations), this compares baseline | ||
| * definitions — requirements without results. There is no status-based classification | ||
| * (fixed/regressed); only metadata changes (title, impact, descriptions, tags) are tracked. | ||
| */ | ||
| export declare function diffBaselines(oldBaseline: Record<string, unknown>, newBaseline: Record<string, unknown>, options?: DiffOptions): HdfComparison; | ||
| /** | ||
| * Compare two HDF system documents and produce a structured comparison | ||
| * showing component-level changes between system versions. | ||
| * | ||
| * Components are matched by componentId (UUID) when available, falling back | ||
| * to exact name matching. Top-level system fields, data flows, and embedded | ||
| * SBOMs are also compared. | ||
| */ | ||
| export declare function diffSystems(oldSystem: Record<string, unknown>, newSystem: Record<string, unknown>, options?: DiffOptions): HdfComparison; | ||
| //# sourceMappingURL=diff.d.ts.map |
| {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EAMd,MAAM,YAAY,CAAC;AAUpB;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,sFAAsF;IACtF,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,4CAA4C;IAC5C,cAAc,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,OAAO,GAAG,aAAa,GAAG,mBAAmB,GAAG,aAAa,CAAC;IACzG,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oFAAoF;IACpF,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mFAAmF;IACnF,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAgDD;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CACrB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC/D,OAAO,CAAC,EAAE,WAAW,GACpB,aAAa,CA8Ff;AA4WD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,OAAO,CAAC,EAAE,WAAW,GACpB,aAAa,CA2If;AAsBD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,OAAO,CAAC,EAAE,WAAW,GACpB,aAAa,CA6Ff"} |
-791
| import { computeEffectiveStatus, classifyChangeReasons, classifyDiffStatus } from './status.js'; | ||
| import { computeSummary } from './summary.js'; | ||
| import { normalizeToV2 } from './normalize.js'; | ||
| import { matchRequirements } from './matching/index.js'; | ||
| import { validateComparison } from './validate.js'; | ||
| import { diffSboms } from './sbom.js'; | ||
| const DEFAULT_TRACKED_FIELDS = ['impact', 'severity', 'tags']; | ||
| /** | ||
| * Extract a dataSource label from a document, if available. | ||
| * Looks for `dataSource.name` in the raw (pre-normalized) document. | ||
| */ | ||
| function extractDataSourceLabel(doc) { | ||
| const ds = doc['dataSource']; | ||
| if (ds && typeof ds['name'] === 'string') { | ||
| return ds['name']; | ||
| } | ||
| return undefined; | ||
| } | ||
| /** | ||
| * Build MatchOptions from DiffOptions, extracting the matching-related fields. | ||
| */ | ||
| function buildMatchOptions(options) { | ||
| return { | ||
| strategy: options?.matchStrategy, | ||
| fallbackStrategies: options?.fallbackStrategies, | ||
| mappingTable: options?.mappingTable, | ||
| minConfidence: options?.minConfidence, | ||
| }; | ||
| } | ||
| /** | ||
| * Determine the primary strategy name for the matching config output. | ||
| */ | ||
| function resolveStrategyName(options) { | ||
| return options?.matchStrategy ?? 'exactId'; | ||
| } | ||
| /** | ||
| * Compare two HDF results documents and produce a structured comparison. | ||
| * | ||
| * Requirements are matched using a configurable matching strategy (default: exact ID). | ||
| * Baselines are matched by `name`. | ||
| * | ||
| * For fleet mode, `newResults` can be an array of documents, each compared | ||
| * pairwise against `oldResults` (the reference). | ||
| */ | ||
| export function diffHdf(oldResults, newResults, options) { | ||
| const trackedFields = options?.trackedFields ?? DEFAULT_TRACKED_FIELDS; | ||
| let comparisonMode = options?.comparisonMode ?? 'temporal'; | ||
| const matchOpts = buildMatchOptions(options); | ||
| // Auto-detect baseline evolution mode when both inputs are baseline documents | ||
| // (have requirements[] but no baselines[], targets[], or statistics[]) | ||
| if (!options?.comparisonMode && !Array.isArray(newResults)) { | ||
| if (isBaselineDocument(oldResults) && isBaselineDocument(newResults)) { | ||
| comparisonMode = 'baselineEvolution'; | ||
| } | ||
| else if (isSystemDocument(oldResults) && isSystemDocument(newResults)) { | ||
| comparisonMode = 'systemDrift'; | ||
| } | ||
| } | ||
| // Validate array inputs up front (applies to all modes) | ||
| if (Array.isArray(newResults)) { | ||
| if (newResults.length === 0) { | ||
| throw new Error('newResults array must not be empty'); | ||
| } | ||
| if (comparisonMode !== 'fleet' && newResults.length > 1) { | ||
| throw new Error(`Mode '${comparisonMode}' expects a single document, got ${newResults.length}. Use 'fleet' mode for multiple documents.`); | ||
| } | ||
| } | ||
| // Baseline evolution mode: compare two baseline documents | ||
| if (comparisonMode === 'baselineEvolution') { | ||
| const newDoc = Array.isArray(newResults) ? newResults[0] : newResults; | ||
| return diffBaselines(oldResults, newDoc, options); | ||
| } | ||
| // System drift mode: compare two system documents | ||
| if (comparisonMode === 'systemDrift') { | ||
| const newDoc = Array.isArray(newResults) ? newResults[0] : newResults; | ||
| return diffSystems(oldResults, newDoc, options); | ||
| } | ||
| // Fleet mode: compare reference against each system | ||
| if (comparisonMode === 'fleet') { | ||
| return diffFleet(oldResults, newResults, trackedFields, matchOpts); | ||
| } | ||
| // Non-fleet modes: newResults must be a single document | ||
| const newDoc = Array.isArray(newResults) ? newResults[0] : newResults; | ||
| // Normalize v1 (InSpec exec-json) to v2 if needed | ||
| const oldNormalized = normalizeToV2(oldResults); | ||
| const newNormalized = normalizeToV2(newDoc); | ||
| const oldTimestamp = oldNormalized['timestamp']; | ||
| const newTimestamp = newNormalized['timestamp']; | ||
| // Build sources metadata based on mode | ||
| const sources = buildSources(comparisonMode, oldResults, newDoc, oldTimestamp, newTimestamp); | ||
| // Compute baseline and requirement diffs | ||
| const { baselineDiffs, requirementDiffs } = comparePair(oldNormalized, newNormalized, oldTimestamp, newTimestamp, trackedFields, matchOpts); | ||
| // Sort by id | ||
| requirementDiffs.sort((a, b) => a.id.localeCompare(b.id)); | ||
| // Extract drift: unchanged requirements with metadata changes | ||
| const drift = extractDrift(requirementDiffs); | ||
| const comparison = { | ||
| formatVersion: '1.0.0', | ||
| comparisonMode, | ||
| timestamp: new Date().toISOString(), | ||
| sources, | ||
| matching: { | ||
| primaryStrategy: resolveStrategyName(options), | ||
| }, | ||
| summary: computeSummary(requirementDiffs), | ||
| baselineDiffs, | ||
| requirementDiffs, | ||
| drift, | ||
| }; | ||
| if (options?.validateOutput) { | ||
| const result = validateComparison(comparison); | ||
| // Safety net: diffHdf always produces valid output by construction. | ||
| // This branch is unreachable in normal operation but guards against future regressions. | ||
| /* c8 ignore start */ | ||
| if (!result.valid) { | ||
| throw new Error(`Output validation failed: ${result.errors?.join(', ')}`); | ||
| } | ||
| /* c8 ignore stop */ | ||
| } | ||
| return comparison; | ||
| } | ||
| /** | ||
| * Build source metadata entries based on comparison mode. | ||
| */ | ||
| function buildSources(mode, oldDoc, newDoc, oldTimestamp, newTimestamp) { | ||
| switch (mode) { | ||
| case 'baseline': | ||
| return [ | ||
| { role: 'golden', label: 'Golden baseline', assessmentTimestamp: oldTimestamp }, | ||
| { role: 'new', label: 'Current scan', assessmentTimestamp: newTimestamp }, | ||
| ]; | ||
| case 'multiSource': { | ||
| const oldLabel = extractDataSourceLabel(oldDoc) ?? 'Old evaluation'; | ||
| const newLabel = extractDataSourceLabel(newDoc) ?? 'New evaluation'; | ||
| return [ | ||
| { role: 'old', label: oldLabel, assessmentTimestamp: oldTimestamp }, | ||
| { role: 'new', label: newLabel, assessmentTimestamp: newTimestamp }, | ||
| ]; | ||
| } | ||
| case 'temporal': | ||
| default: | ||
| return [ | ||
| { role: 'old', label: 'Old evaluation', assessmentTimestamp: oldTimestamp }, | ||
| { role: 'new', label: 'New evaluation', assessmentTimestamp: newTimestamp }, | ||
| ]; | ||
| } | ||
| } | ||
| /** | ||
| * Fleet mode: compare a reference document against one or more system documents. | ||
| * Each system is compared pairwise against the reference; all diffs are collected | ||
| * into a single result with `sourceIndex` set on each RequirementDiff. | ||
| */ | ||
| function diffFleet(referenceDoc, newResults, trackedFields, matchOpts) { | ||
| const systems = Array.isArray(newResults) ? newResults : [newResults]; | ||
| const refNormalized = normalizeToV2(referenceDoc); | ||
| const refTimestamp = refNormalized['timestamp']; | ||
| // Build sources, compare each system against the reference, and collect diffs | ||
| // in a single pass — each system document is normalized exactly once. | ||
| const sources = [ | ||
| { role: 'reference', label: 'Reference', assessmentTimestamp: refTimestamp }, | ||
| ]; | ||
| const allRequirementDiffs = []; | ||
| const allBaselineDiffs = []; | ||
| // Deduplicate baseline diffs by name: first-wins. In fleet mode, every system | ||
| // is compared against the same reference, so the first occurrence of a | ||
| // BaselineDiff for a given baseline name is representative. BaselineDiff does | ||
| // not carry a sourceIndex in v1 of the schema, so per-system baseline tracking | ||
| // is deferred to a future version. | ||
| const seenBaselineNames = new Set(); | ||
| for (let i = 0; i < systems.length; i++) { | ||
| const sysNormalized = normalizeToV2(systems[i]); | ||
| const sysTimestamp = sysNormalized['timestamp']; | ||
| const sourceIndex = i + 1; | ||
| // Build the source entry for this system | ||
| sources.push({ | ||
| role: 'system', | ||
| label: `System ${sourceIndex}`, | ||
| assessmentTimestamp: sysTimestamp, | ||
| }); | ||
| const { baselineDiffs, requirementDiffs } = comparePair(refNormalized, sysNormalized, refTimestamp, sysTimestamp, trackedFields, matchOpts); | ||
| // Tag each requirement diff with its source index | ||
| for (const rd of requirementDiffs) { | ||
| rd.sourceIndex = sourceIndex; | ||
| allRequirementDiffs.push(rd); | ||
| } | ||
| // Collect baseline diffs (first-wins dedup; see comment above) | ||
| for (const bd of baselineDiffs) { | ||
| if (!seenBaselineNames.has(bd.name)) { | ||
| seenBaselineNames.add(bd.name); | ||
| allBaselineDiffs.push(bd); | ||
| } | ||
| } | ||
| } | ||
| // Sort by id, then by sourceIndex | ||
| allRequirementDiffs.sort((a, b) => { | ||
| const idCmp = a.id.localeCompare(b.id); | ||
| if (idCmp !== 0) | ||
| return idCmp; | ||
| /* c8 ignore next -- sourceIndex is always set in fleet mode; ?? 0 is defensive typing */ | ||
| return (a.sourceIndex ?? 0) - (b.sourceIndex ?? 0); | ||
| }); | ||
| // Extract drift: unchanged requirements with metadata changes | ||
| const drift = extractDrift(allRequirementDiffs); | ||
| return { | ||
| formatVersion: '1.0.0', | ||
| comparisonMode: 'fleet', | ||
| timestamp: new Date().toISOString(), | ||
| sources, | ||
| matching: { | ||
| primaryStrategy: matchOpts.strategy ?? 'exactId', | ||
| }, | ||
| summary: computeSummary(allRequirementDiffs), | ||
| baselineDiffs: allBaselineDiffs, | ||
| requirementDiffs: allRequirementDiffs, | ||
| drift, | ||
| }; | ||
| } | ||
| /** | ||
| * Extract drift entries from requirement diffs. | ||
| * | ||
| * Drift = requirements whose effective status is unchanged but whose metadata | ||
| * changed (tags, descriptions, impact score, etc.). These are the "silent" | ||
| * changes that don't affect pass/fail outcome but still matter for auditing. | ||
| * | ||
| * Returns shallow copies so drift entries are independent of requirementDiffs. | ||
| */ | ||
| function extractDrift(requirementDiffs) { | ||
| return requirementDiffs | ||
| .filter(r => r.state === 'unchanged' && r.changeReasons.length > 0) | ||
| .map(r => ({ ...r })); | ||
| } | ||
| /** | ||
| * Core pairwise comparison between two normalized HDF documents. | ||
| * Returns baseline diffs and requirement diffs without sorting or wrapping. | ||
| * | ||
| * Uses the pluggable matching system to pair requirements between evaluations. | ||
| */ | ||
| function comparePair(oldNormalized, newNormalized, oldTimestamp, newTimestamp, trackedFields, matchOpts) { | ||
| const oldBaselines = oldNormalized['baselines'] ?? []; | ||
| const newBaselines = newNormalized['baselines'] ?? []; | ||
| // Build baseline maps by name | ||
| const oldBaselineMap = new Map(); | ||
| for (const b of oldBaselines) { | ||
| oldBaselineMap.set(b.name, b); | ||
| } | ||
| const newBaselineMap = new Map(); | ||
| for (const b of newBaselines) { | ||
| newBaselineMap.set(b.name, b); | ||
| } | ||
| // Compute baseline diffs | ||
| const allBaselineNames = new Set([...oldBaselineMap.keys(), ...newBaselineMap.keys()]); | ||
| const baselineDiffs = []; | ||
| for (const name of allBaselineNames) { | ||
| const oldB = oldBaselineMap.get(name); | ||
| const newB = newBaselineMap.get(name); | ||
| if (oldB && newB) { | ||
| const versionChanged = oldB.version !== newB.version; | ||
| baselineDiffs.push({ | ||
| name, | ||
| oldVersion: oldB.version, | ||
| newVersion: newB.version, | ||
| state: versionChanged ? 'updated' : 'unchanged', | ||
| }); | ||
| } | ||
| else if (oldB && !newB) { | ||
| baselineDiffs.push({ | ||
| name, | ||
| oldVersion: oldB.version, | ||
| state: 'absent', | ||
| }); | ||
| } | ||
| else if (!oldB && newB) { | ||
| baselineDiffs.push({ | ||
| name, | ||
| newVersion: newB.version, | ||
| state: 'new', | ||
| }); | ||
| } | ||
| } | ||
| // Collect all requirements across all baselines | ||
| const oldReqs = []; | ||
| for (const baseline of oldBaselines) { | ||
| for (const req of baseline.requirements) { | ||
| oldReqs.push(req); | ||
| } | ||
| } | ||
| const newReqs = []; | ||
| for (const baseline of newBaselines) { | ||
| for (const req of baseline.requirements) { | ||
| newReqs.push(req); | ||
| } | ||
| } | ||
| // Use the matching system to pair requirements | ||
| const matchResult = matchRequirements(oldReqs, newReqs, matchOpts); | ||
| // Build requirement diffs from match results | ||
| const requirementDiffs = []; | ||
| // Matched pairs | ||
| for (const pair of matchResult.matched) { | ||
| const oldReq = pair.oldReq; | ||
| const newReq = pair.newReq; | ||
| /* c8 ignore next -- id is always a string on RequirementLike; ?? is defensive typing */ | ||
| const id = (newReq.id ?? oldReq.id); | ||
| const oldStatus = computeEffectiveStatus(oldReq, oldTimestamp); | ||
| const newStatus = computeEffectiveStatus(newReq, newTimestamp); | ||
| const diffState = classifyDiffStatus(oldStatus, newStatus); | ||
| const changeReasons = classifyChangeReasons(oldReq, newReq, oldTimestamp, newTimestamp); | ||
| const fieldChanges = computeFieldChanges(oldReq, newReq, trackedFields); | ||
| requirementDiffs.push({ | ||
| id, | ||
| title: newReq.title ?? oldReq.title, | ||
| state: diffState, | ||
| oldEffectiveStatus: oldStatus, | ||
| newEffectiveStatus: newStatus, | ||
| changeReasons, | ||
| oldImpact: oldReq.impact, | ||
| newImpact: newReq.impact, | ||
| fieldChanges, | ||
| before: oldReq, | ||
| after: newReq, | ||
| matchStrategy: pair.strategy, | ||
| matchConfidence: pair.confidence, | ||
| }); | ||
| } | ||
| // Unmatched old requirements (absent) | ||
| for (const req of matchResult.unmatchedOld) { | ||
| const oldReq = req; | ||
| const id = oldReq.id; | ||
| const oldStatus = computeEffectiveStatus(oldReq, oldTimestamp); | ||
| requirementDiffs.push({ | ||
| id, | ||
| title: oldReq.title, | ||
| state: 'absent', | ||
| oldEffectiveStatus: oldStatus, | ||
| changeReasons: [], | ||
| oldImpact: oldReq.impact, | ||
| fieldChanges: [], | ||
| before: oldReq, | ||
| after: null, | ||
| }); | ||
| } | ||
| // Unmatched new requirements (new) | ||
| for (const req of matchResult.unmatchedNew) { | ||
| const newReq = req; | ||
| const id = newReq.id; | ||
| const newStatus = computeEffectiveStatus(newReq, newTimestamp); | ||
| requirementDiffs.push({ | ||
| id, | ||
| title: newReq.title, | ||
| state: 'new', | ||
| newEffectiveStatus: newStatus, | ||
| changeReasons: [], | ||
| newImpact: newReq.impact, | ||
| fieldChanges: [], | ||
| before: null, | ||
| after: newReq, | ||
| }); | ||
| } | ||
| return { baselineDiffs, requirementDiffs }; | ||
| } | ||
| /** | ||
| * Compute field-level changes for tracked fields between two requirements. | ||
| * Uses JSON Patch-like op/path format. | ||
| */ | ||
| function computeFieldChanges(oldReq, newReq, trackedFields) { | ||
| const changes = []; | ||
| for (const field of trackedFields) { | ||
| const oldVal = oldReq[field]; | ||
| const newVal = newReq[field]; | ||
| // Deep comparison for objects/arrays | ||
| if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) { | ||
| if (oldVal === undefined && newVal !== undefined) { | ||
| changes.push({ | ||
| op: 'add', | ||
| path: field, | ||
| newValue: newVal, | ||
| }); | ||
| } | ||
| else if (oldVal !== undefined && newVal === undefined) { | ||
| changes.push({ | ||
| op: 'remove', | ||
| path: field, | ||
| oldValue: oldVal, | ||
| }); | ||
| } | ||
| else { | ||
| changes.push({ | ||
| op: 'replace', | ||
| path: field, | ||
| oldValue: oldVal, | ||
| newValue: newVal, | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| return changes; | ||
| } | ||
| /** | ||
| * Detect whether a document is a baseline (not results). | ||
| * Baselines have `requirements` at the top level and lack `baselines`, `targets`, and `statistics`. | ||
| */ | ||
| function isBaselineDocument(doc) { | ||
| return (Array.isArray(doc['requirements']) && | ||
| !Array.isArray(doc['baselines']) && | ||
| !Array.isArray(doc['targets']) && | ||
| doc['statistics'] === undefined); | ||
| } | ||
| /** Default tracked fields for baseline evolution comparisons. */ | ||
| const BASELINE_TRACKED_FIELDS = ['title', 'impact', 'descriptions', 'tags']; | ||
| /** | ||
| * Compare two HDF baseline documents and produce a structured comparison | ||
| * showing requirement changes between baseline versions. | ||
| * | ||
| * Unlike diffHdf (which compares results/evaluations), this compares baseline | ||
| * definitions — requirements without results. There is no status-based classification | ||
| * (fixed/regressed); only metadata changes (title, impact, descriptions, tags) are tracked. | ||
| */ | ||
| export function diffBaselines(oldBaseline, newBaseline, options) { | ||
| const trackedFields = options?.trackedFields ?? BASELINE_TRACKED_FIELDS; | ||
| const matchOpts = buildMatchOptions(options); | ||
| // Extract requirements from baseline documents | ||
| const oldReqs = oldBaseline['requirements'] ?? []; | ||
| const newReqs = newBaseline['requirements'] ?? []; | ||
| // Use the matching system to pair requirements | ||
| const matchResult = matchRequirements(oldReqs, newReqs, matchOpts); | ||
| // Build requirement diffs from match results | ||
| const requirementDiffs = []; | ||
| // Matched pairs | ||
| for (const pair of matchResult.matched) { | ||
| const oldReq = pair.oldReq; | ||
| const newReq = pair.newReq; | ||
| const id = (newReq.id ?? oldReq.id); | ||
| const fieldChanges = computeFieldChanges(oldReq, newReq, trackedFields); | ||
| // For baseline evolution, state is determined by metadata changes only | ||
| const state = fieldChanges.length > 0 ? 'updated' : 'unchanged'; | ||
| // Determine change reasons for baseline evolution | ||
| const changeReasons = []; | ||
| if (oldReq.impact !== newReq.impact) { | ||
| changeReasons.push('impactChanged'); | ||
| } | ||
| const oldTags = JSON.stringify(oldReq['tags'] ?? {}); | ||
| const newTags = JSON.stringify(newReq['tags'] ?? {}); | ||
| const oldDescs = JSON.stringify(oldReq['descriptions'] ?? []); | ||
| const newDescs = JSON.stringify(newReq['descriptions'] ?? []); | ||
| const oldTitle = oldReq['title']; | ||
| const newTitle = newReq['title']; | ||
| if (oldTags !== newTags || oldDescs !== newDescs || oldTitle !== newTitle) { | ||
| changeReasons.push('metadataChanged'); | ||
| } | ||
| requirementDiffs.push({ | ||
| id, | ||
| title: newReq.title ?? oldReq.title, | ||
| state, | ||
| changeReasons, | ||
| oldImpact: oldReq.impact, | ||
| newImpact: newReq.impact, | ||
| fieldChanges, | ||
| before: oldReq, | ||
| after: newReq, | ||
| matchStrategy: pair.strategy, | ||
| matchConfidence: pair.confidence, | ||
| }); | ||
| } | ||
| // Unmatched old requirements (absent) | ||
| for (const req of matchResult.unmatchedOld) { | ||
| const oldReq = req; | ||
| requirementDiffs.push({ | ||
| id: oldReq.id, | ||
| title: oldReq.title, | ||
| state: 'absent', | ||
| changeReasons: [], | ||
| oldImpact: oldReq.impact, | ||
| fieldChanges: [], | ||
| before: oldReq, | ||
| after: null, | ||
| }); | ||
| } | ||
| // Unmatched new requirements (new) | ||
| for (const req of matchResult.unmatchedNew) { | ||
| const newReq = req; | ||
| requirementDiffs.push({ | ||
| id: newReq.id, | ||
| title: newReq.title, | ||
| state: 'new', | ||
| changeReasons: [], | ||
| newImpact: newReq.impact, | ||
| fieldChanges: [], | ||
| before: null, | ||
| after: newReq, | ||
| }); | ||
| } | ||
| // Sort by id | ||
| requirementDiffs.sort((a, b) => a.id.localeCompare(b.id)); | ||
| // Build baseline diff from top-level baseline metadata | ||
| const oldName = oldBaseline['name'] ?? ''; | ||
| const newName = newBaseline['name'] ?? ''; | ||
| const oldVersion = oldBaseline['version']; | ||
| const newVersion = newBaseline['version']; | ||
| const baselineDiffs = []; | ||
| const baselineName = newName || oldName; | ||
| if (baselineName) { | ||
| const versionChanged = oldVersion !== newVersion; | ||
| baselineDiffs.push({ | ||
| name: baselineName, | ||
| oldVersion, | ||
| newVersion, | ||
| state: versionChanged ? 'updated' : 'unchanged', | ||
| }); | ||
| } | ||
| // Build sources | ||
| const sources = [ | ||
| { role: 'old', label: oldVersion ? `${baselineName} ${oldVersion}` : baselineName || 'Old baseline' }, | ||
| { role: 'new', label: newVersion ? `${baselineName} ${newVersion}` : baselineName || 'New baseline' }, | ||
| ]; | ||
| const comparison = { | ||
| formatVersion: '1.0.0', | ||
| comparisonMode: 'baselineEvolution', | ||
| timestamp: new Date().toISOString(), | ||
| sources, | ||
| matching: { | ||
| primaryStrategy: resolveStrategyName(options), | ||
| }, | ||
| summary: computeSummary(requirementDiffs), | ||
| baselineDiffs, | ||
| requirementDiffs, | ||
| }; | ||
| if (options?.validateOutput) { | ||
| const result = validateComparison(comparison); | ||
| /* c8 ignore start */ | ||
| if (!result.valid) { | ||
| throw new Error(`Output validation failed: ${result.errors?.join(', ')}`); | ||
| } | ||
| /* c8 ignore stop */ | ||
| } | ||
| return comparison; | ||
| } | ||
| /** | ||
| * Detect whether a document is a system document (has components[] but no baselines/requirements). | ||
| */ | ||
| function isSystemDocument(doc) { | ||
| return (Array.isArray(doc['components']) && | ||
| !Array.isArray(doc['baselines']) && | ||
| !Array.isArray(doc['requirements']) && | ||
| doc['statistics'] === undefined); | ||
| } | ||
| /** Fields tracked for system-level field changes. */ | ||
| const SYSTEM_TOP_LEVEL_FIELDS = ['authorizationStatus', 'categorizationLevel', 'description']; | ||
| /** Fields tracked for component-level field changes. */ | ||
| const COMPONENT_TRACKED_FIELDS = [ | ||
| 'type', 'description', 'baselineRefs', 'inputOverrides', 'sbomRef', 'targetSelector', | ||
| ]; | ||
| /** | ||
| * Compare two HDF system documents and produce a structured comparison | ||
| * showing component-level changes between system versions. | ||
| * | ||
| * Components are matched by componentId (UUID) when available, falling back | ||
| * to exact name matching. Top-level system fields, data flows, and embedded | ||
| * SBOMs are also compared. | ||
| */ | ||
| export function diffSystems(oldSystem, newSystem, options) { | ||
| const oldComponents = oldSystem['components'] ?? []; | ||
| const newComponents = newSystem['components'] ?? []; | ||
| // Match components: prefer componentId, fall back to name | ||
| const pairs = matchComponents(oldComponents, newComponents); | ||
| const componentDiffs = []; | ||
| for (const { oldComp, newComp, name } of pairs) { | ||
| if (oldComp && newComp) { | ||
| const fieldChanges = computeComponentFieldChanges(oldComp, newComp, COMPONENT_TRACKED_FIELDS); | ||
| const state = fieldChanges.length > 0 ? 'updated' : 'unchanged'; | ||
| componentDiffs.push({ name, state, before: oldComp, after: newComp, fieldChanges }); | ||
| } | ||
| else if (oldComp && !newComp) { | ||
| componentDiffs.push({ name, state: 'absent', before: oldComp, after: null, fieldChanges: [] }); | ||
| } | ||
| else if (!oldComp && newComp) { | ||
| componentDiffs.push({ name, state: 'new', before: null, after: newComp, fieldChanges: [] }); | ||
| } | ||
| } | ||
| componentDiffs.sort((a, b) => a.name.localeCompare(b.name)); | ||
| // Compare top-level system fields | ||
| const systemFieldChanges = computeComponentFieldChanges(oldSystem, newSystem, SYSTEM_TOP_LEVEL_FIELDS); | ||
| // Build summary counts based on component diffs | ||
| const counts = { new: 0, absent: 0, unchanged: 0, updated: 0 }; | ||
| for (const cd of componentDiffs) { | ||
| counts[cd.state]++; | ||
| } | ||
| const oldName = oldSystem['name'] ?? ''; | ||
| const newName = newSystem['name'] ?? ''; | ||
| const systemName = newName || oldName; | ||
| const sources = [ | ||
| { role: 'old', label: systemName ? `${systemName} (old)` : 'Old system' }, | ||
| { role: 'new', label: systemName ? `${systemName} (new)` : 'New system' }, | ||
| ]; | ||
| const comparison = { | ||
| formatVersion: '1.0.0', | ||
| comparisonMode: 'systemDrift', | ||
| timestamp: new Date().toISOString(), | ||
| sources, | ||
| summary: { | ||
| total: pairs.length, | ||
| matchedCount: counts.unchanged + counts.updated, | ||
| unmatchedOldCount: counts.absent, | ||
| unmatchedNewCount: counts.new, | ||
| new: counts.new, | ||
| absent: counts.absent, | ||
| unchanged: counts.unchanged, | ||
| updated: counts.updated, | ||
| fixed: 0, | ||
| regressed: 0, | ||
| }, | ||
| baselineDiffs: [], | ||
| requirementDiffs: [], | ||
| componentDiffs, | ||
| }; | ||
| // Extensions: system field changes + data flow changes | ||
| const extensions = {}; | ||
| if (systemFieldChanges.length > 0) { | ||
| extensions['systemFieldChanges'] = systemFieldChanges; | ||
| } | ||
| const dataFlowChanges = diffDataFlows(oldSystem, newSystem); | ||
| if (dataFlowChanges.length > 0) { | ||
| extensions['dataFlowChanges'] = dataFlowChanges; | ||
| } | ||
| if (Object.keys(extensions).length > 0) { | ||
| comparison.extensions = extensions; | ||
| } | ||
| // Diff embedded SBOMs across matched components | ||
| const allPackageDiffs = diffEmbeddedSboms(pairs); | ||
| if (allPackageDiffs.length > 0) { | ||
| comparison.packageDiffs = allPackageDiffs; | ||
| } | ||
| if (options?.validateOutput) { | ||
| const result = validateComparison(comparison); | ||
| /* c8 ignore start */ | ||
| if (!result.valid) { | ||
| throw new Error(`Output validation failed: ${result.errors?.join(', ')}`); | ||
| } | ||
| /* c8 ignore stop */ | ||
| } | ||
| return comparison; | ||
| } | ||
| /** | ||
| * Match old and new components by componentId (when available) or name. | ||
| */ | ||
| function matchComponents(oldComponents, newComponents) { | ||
| const matched = new Set(); // indices of matched new components | ||
| const pairs = []; | ||
| // First pass: match by componentId | ||
| const newById = new Map(); | ||
| for (let i = 0; i < newComponents.length; i++) { | ||
| const id = newComponents[i]['componentId']; | ||
| if (id) | ||
| newById.set(id, i); | ||
| } | ||
| const oldMatched = new Set(); | ||
| for (let i = 0; i < oldComponents.length; i++) { | ||
| const oldId = oldComponents[i]['componentId']; | ||
| if (oldId && newById.has(oldId)) { | ||
| const ni = newById.get(oldId); | ||
| const newComp = newComponents[ni]; | ||
| pairs.push({ | ||
| name: newComp['name'] || oldComponents[i]['name'] || oldId, | ||
| oldComp: oldComponents[i], | ||
| newComp, | ||
| }); | ||
| matched.add(ni); | ||
| oldMatched.add(i); | ||
| } | ||
| } | ||
| // Second pass: match remaining by name | ||
| const newByName = new Map(); | ||
| for (let i = 0; i < newComponents.length; i++) { | ||
| if (matched.has(i)) | ||
| continue; | ||
| const name = newComponents[i]['name']; | ||
| if (name) | ||
| newByName.set(name, i); | ||
| } | ||
| for (let i = 0; i < oldComponents.length; i++) { | ||
| if (oldMatched.has(i)) | ||
| continue; | ||
| const name = oldComponents[i]['name']; | ||
| if (name && newByName.has(name)) { | ||
| const ni = newByName.get(name); | ||
| pairs.push({ name, oldComp: oldComponents[i], newComp: newComponents[ni] }); | ||
| matched.add(ni); | ||
| oldMatched.add(i); | ||
| newByName.delete(name); | ||
| } | ||
| } | ||
| // Unmatched old → absent | ||
| for (let i = 0; i < oldComponents.length; i++) { | ||
| if (oldMatched.has(i)) | ||
| continue; | ||
| const name = oldComponents[i]['name'] || `component-${i}`; | ||
| pairs.push({ name, oldComp: oldComponents[i], newComp: undefined }); | ||
| } | ||
| // Unmatched new → new | ||
| for (let i = 0; i < newComponents.length; i++) { | ||
| if (matched.has(i)) | ||
| continue; | ||
| const name = newComponents[i]['name'] || `component-${i}`; | ||
| pairs.push({ name, oldComp: undefined, newComp: newComponents[i] }); | ||
| } | ||
| return pairs; | ||
| } | ||
| /** | ||
| * Diff data flows between two system documents. Flows are keyed by from+to. | ||
| */ | ||
| function diffDataFlows(oldSystem, newSystem) { | ||
| const oldFlows = oldSystem['dataFlows'] ?? []; | ||
| const newFlows = newSystem['dataFlows'] ?? []; | ||
| if (oldFlows.length === 0 && newFlows.length === 0) | ||
| return []; | ||
| const flowKey = (f) => { | ||
| const from = f['from'] ?? ''; | ||
| const to = typeof f['to'] === 'string' ? f['to'] : JSON.stringify(f['to']); | ||
| return `${from}→${to}`; | ||
| }; | ||
| const oldMap = new Map(); | ||
| for (const f of oldFlows) | ||
| oldMap.set(flowKey(f), f); | ||
| const newMap = new Map(); | ||
| for (const f of newFlows) | ||
| newMap.set(flowKey(f), f); | ||
| const allKeys = new Set([...oldMap.keys(), ...newMap.keys()]); | ||
| const changes = []; | ||
| for (const key of allKeys) { | ||
| const oldF = oldMap.get(key); | ||
| const newF = newMap.get(key); | ||
| if (oldF && newF) { | ||
| if (JSON.stringify(oldF) !== JSON.stringify(newF)) { | ||
| changes.push({ state: 'updated', flow: newF }); | ||
| } | ||
| } | ||
| else if (oldF && !newF) { | ||
| changes.push({ state: 'removed', flow: oldF }); | ||
| } | ||
| else if (!oldF && newF) { | ||
| changes.push({ state: 'added', flow: newF }); | ||
| } | ||
| } | ||
| return changes; | ||
| } | ||
| /** | ||
| * Diff embedded SBOMs across matched component pairs. | ||
| */ | ||
| function diffEmbeddedSboms(pairs) { | ||
| const allDiffs = []; | ||
| for (const { oldComp, newComp } of pairs) { | ||
| if (!oldComp || !newComp) | ||
| continue; | ||
| const oldSbom = oldComp['sbom']; | ||
| const newSbom = newComp['sbom']; | ||
| if (!oldSbom || !newSbom) | ||
| continue; | ||
| if (typeof oldSbom !== 'object' || typeof newSbom !== 'object') | ||
| continue; | ||
| try { | ||
| const result = diffSboms(JSON.stringify(oldSbom), JSON.stringify(newSbom)); | ||
| allDiffs.push(...result.packageDiffs); | ||
| } | ||
| catch { | ||
| // Skip SBOM diff if formats are incompatible | ||
| } | ||
| } | ||
| return allDiffs; | ||
| } | ||
| /** | ||
| * Compute field-level changes for component or system fields. | ||
| * Uses JSON Patch-like op/path format. | ||
| */ | ||
| function computeComponentFieldChanges(oldObj, newObj, trackedFields) { | ||
| const changes = []; | ||
| for (const field of trackedFields) { | ||
| const oldVal = oldObj[field]; | ||
| const newVal = newObj[field]; | ||
| if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) { | ||
| if (oldVal === undefined && newVal !== undefined) { | ||
| changes.push({ op: 'add', path: field, newValue: newVal }); | ||
| } | ||
| else if (oldVal !== undefined && newVal === undefined) { | ||
| changes.push({ op: 'remove', path: field, oldValue: oldVal }); | ||
| } | ||
| else { | ||
| changes.push({ op: 'replace', path: field, oldValue: oldVal, newValue: newVal }); | ||
| } | ||
| } | ||
| } | ||
| return changes; | ||
| } | ||
| //# sourceMappingURL=diff.js.map |
| {"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"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"} |
| import type { ComparisonSummary } from './types.js'; | ||
| /** GNU diff compatible exit codes (--exit-code mode) */ | ||
| export declare const EXIT_IDENTICAL = 0; | ||
| export declare const EXIT_DIFFERENCES = 1; | ||
| export declare const EXIT_ERROR = 2; | ||
| /** Detailed exit codes (--detailed-exitcode mode) */ | ||
| export declare const EXIT_DETAILED_IDENTICAL = 0; | ||
| export declare const EXIT_DETAILED_ERROR = 1; | ||
| export declare const EXIT_DETAILED_FIXES_ONLY = 10; | ||
| export declare const EXIT_DETAILED_REGRESSIONS_ONLY = 11; | ||
| export declare const EXIT_DETAILED_MIXED = 12; | ||
| export declare const EXIT_DETAILED_BASELINE_CHANGED = 13; | ||
| export declare const EXIT_DETAILED_DRIFT_ONLY = 14; | ||
| /** | ||
| * Compute the GNU diff compatible exit code from a comparison summary. | ||
| * | ||
| * Returns: | ||
| * 0 = identical (no differences found) | ||
| * 1 = differences found (any kind) | ||
| * | ||
| * Note: error (exit code 2) is not computed from the summary — callers | ||
| * should return EXIT_ERROR directly when I/O or parse errors occur. | ||
| */ | ||
| export declare function computeExitCode(summary: ComparisonSummary): number; | ||
| /** | ||
| * Compute the detailed exit code from a comparison summary. | ||
| * | ||
| * Returns: | ||
| * 0 = identical (no differences found) | ||
| * 10 = differences found, fixes only (security posture improved) | ||
| * 11 = differences found, regressions only (security posture degraded) | ||
| * 12 = differences found, mixed fixes and regressions | ||
| * 13 = differences found, only new/absent controls (baseline changed) | ||
| * 14 = differences found, only metadata drift (no status changes) | ||
| * | ||
| * Note: error (exit code 1) is not computed from the summary — callers | ||
| * should return EXIT_DETAILED_ERROR directly when I/O or parse errors occur. | ||
| * | ||
| * Priority order: mixed(12) > regressions(11) > fixes(10) > baseline(13) > drift(14) | ||
| */ | ||
| export declare function computeDetailedExitCode(summary: ComparisonSummary): number; | ||
| //# sourceMappingURL=exit-codes.d.ts.map |
| {"version":3,"file":"exit-codes.d.ts","sourceRoot":"","sources":["../src/exit-codes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,wDAAwD;AACxD,eAAO,MAAM,cAAc,IAAI,CAAC;AAChC,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,UAAU,IAAI,CAAC;AAE5B,qDAAqD;AACrD,eAAO,MAAM,uBAAuB,IAAI,CAAC;AACzC,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAC3C,eAAO,MAAM,8BAA8B,KAAK,CAAC;AACjD,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,eAAO,MAAM,8BAA8B,KAAK,CAAC;AACjD,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAKlE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CA2B1E"} |
| /** GNU diff compatible exit codes (--exit-code mode) */ | ||
| export const EXIT_IDENTICAL = 0; | ||
| export const EXIT_DIFFERENCES = 1; | ||
| export const EXIT_ERROR = 2; | ||
| /** Detailed exit codes (--detailed-exitcode mode) */ | ||
| export const EXIT_DETAILED_IDENTICAL = 0; | ||
| export const EXIT_DETAILED_ERROR = 1; | ||
| export const EXIT_DETAILED_FIXES_ONLY = 10; | ||
| export const EXIT_DETAILED_REGRESSIONS_ONLY = 11; | ||
| export const EXIT_DETAILED_MIXED = 12; | ||
| export const EXIT_DETAILED_BASELINE_CHANGED = 13; | ||
| export const EXIT_DETAILED_DRIFT_ONLY = 14; | ||
| /** | ||
| * Compute the GNU diff compatible exit code from a comparison summary. | ||
| * | ||
| * Returns: | ||
| * 0 = identical (no differences found) | ||
| * 1 = differences found (any kind) | ||
| * | ||
| * Note: error (exit code 2) is not computed from the summary — callers | ||
| * should return EXIT_ERROR directly when I/O or parse errors occur. | ||
| */ | ||
| export function computeExitCode(summary) { | ||
| if (hasDifferences(summary)) { | ||
| return EXIT_DIFFERENCES; | ||
| } | ||
| return EXIT_IDENTICAL; | ||
| } | ||
| /** | ||
| * Compute the detailed exit code from a comparison summary. | ||
| * | ||
| * Returns: | ||
| * 0 = identical (no differences found) | ||
| * 10 = differences found, fixes only (security posture improved) | ||
| * 11 = differences found, regressions only (security posture degraded) | ||
| * 12 = differences found, mixed fixes and regressions | ||
| * 13 = differences found, only new/absent controls (baseline changed) | ||
| * 14 = differences found, only metadata drift (no status changes) | ||
| * | ||
| * Note: error (exit code 1) is not computed from the summary — callers | ||
| * should return EXIT_DETAILED_ERROR directly when I/O or parse errors occur. | ||
| * | ||
| * Priority order: mixed(12) > regressions(11) > fixes(10) > baseline(13) > drift(14) | ||
| */ | ||
| export function computeDetailedExitCode(summary) { | ||
| if (!hasDifferences(summary)) { | ||
| return EXIT_DETAILED_IDENTICAL; | ||
| } | ||
| // Mixed: both fixes and regressions | ||
| if (summary.regressed > 0 && summary.fixed > 0) { | ||
| return EXIT_DETAILED_MIXED; | ||
| } | ||
| // Regressions only (no fixes) | ||
| if (summary.regressed > 0) { | ||
| return EXIT_DETAILED_REGRESSIONS_ONLY; | ||
| } | ||
| // Fixes only (no regressions) | ||
| if (summary.fixed > 0) { | ||
| return EXIT_DETAILED_FIXES_ONLY; | ||
| } | ||
| // Baseline changes: new or absent controls (but no status changes) | ||
| if (summary.new > 0 || summary.absent > 0) { | ||
| return EXIT_DETAILED_BASELINE_CHANGED; | ||
| } | ||
| // Everything else is metadata drift (updated tags, descriptions, etc.) | ||
| return EXIT_DETAILED_DRIFT_ONLY; | ||
| } | ||
| /** | ||
| * Returns true if the summary indicates any differences at all. | ||
| */ | ||
| function hasDifferences(summary) { | ||
| return summary.total !== summary.unchanged; | ||
| } | ||
| //# sourceMappingURL=exit-codes.js.map |
| {"version":3,"file":"exit-codes.js","sourceRoot":"","sources":["../src/exit-codes.ts"],"names":[],"mappings":"AAEA,wDAAwD;AACxD,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAChC,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAClC,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC;AAE5B,qDAAqD;AACrD,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC;AACzC,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AACrC,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAC3C,MAAM,CAAC,MAAM,8BAA8B,GAAG,EAAE,CAAC;AACjD,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AACtC,MAAM,CAAC,MAAM,8BAA8B,GAAG,EAAE,CAAC;AACjD,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAE3C;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,OAA0B;IACxD,IAAI,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,OAAO,gBAAgB,CAAC;IAC1B,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAA0B;IAChE,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7B,OAAO,uBAAuB,CAAC;IACjC,CAAC;IAED,oCAAoC;IACpC,IAAI,OAAO,CAAC,SAAS,GAAG,CAAC,IAAI,OAAO,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;QAC/C,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IAED,8BAA8B;IAC9B,IAAI,OAAO,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,8BAA8B,CAAC;IACxC,CAAC;IAED,8BAA8B;IAC9B,IAAI,OAAO,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,wBAAwB,CAAC;IAClC,CAAC;IAED,mEAAmE;IACnE,IAAI,OAAO,CAAC,GAAG,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1C,OAAO,8BAA8B,CAAC;IACxC,CAAC;IAED,uEAAuE;IACvE,OAAO,wBAAwB,CAAC;AAClC,CAAC;AAED;;GAEG;AACH,SAAS,cAAc,CAAC,OAA0B;IAChD,OAAO,OAAO,CAAC,KAAK,KAAK,OAAO,CAAC,SAAS,CAAC;AAC7C,CAAC"} |
| import type { MatchStrategy } from './types.js'; | ||
| /** | ||
| * Create a CCI-based matching strategy. | ||
| * | ||
| * Matches requirements that share the same CCI identifier in `tags.cci`. | ||
| * Only produces a match when exactly one old requirement and exactly one | ||
| * new requirement share a given CCI (unambiguous). Ambiguous CCIs (shared | ||
| * by multiple old or multiple new requirements) are skipped, and those | ||
| * requirements are left unmatched. | ||
| * | ||
| * Confidence is 0.8 for unambiguous matches. | ||
| */ | ||
| export declare function createCciMatchStrategy(): MatchStrategy; | ||
| //# sourceMappingURL=cci-match.d.ts.map |
| {"version":3,"file":"cci-match.d.ts","sourceRoot":"","sources":["../../src/matching/cci-match.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,YAAY,CAAC;AAc7D;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,IAAI,aAAa,CAqFtD"} |
| /** | ||
| * Extract CCI identifiers from a requirement's tags. | ||
| * Looks for `tags.cci` as an array of strings. | ||
| */ | ||
| function extractCcis(req) { | ||
| const tags = req['tags']; | ||
| if (!tags) | ||
| return []; | ||
| const cci = tags['cci']; | ||
| if (!Array.isArray(cci)) | ||
| return []; | ||
| return cci.filter((c) => typeof c === 'string'); | ||
| } | ||
| /** | ||
| * Create a CCI-based matching strategy. | ||
| * | ||
| * Matches requirements that share the same CCI identifier in `tags.cci`. | ||
| * Only produces a match when exactly one old requirement and exactly one | ||
| * new requirement share a given CCI (unambiguous). Ambiguous CCIs (shared | ||
| * by multiple old or multiple new requirements) are skipped, and those | ||
| * requirements are left unmatched. | ||
| * | ||
| * Confidence is 0.8 for unambiguous matches. | ||
| */ | ||
| export function createCciMatchStrategy() { | ||
| return { | ||
| name: 'cciMatch', | ||
| match(oldReqs, newReqs) { | ||
| const result = { | ||
| matched: [], | ||
| unmatchedOld: [], | ||
| unmatchedNew: [], | ||
| }; | ||
| // Build CCI -> requirement indices for old and new | ||
| const oldCciMap = new Map(); | ||
| for (let i = 0; i < oldReqs.length; i++) { | ||
| for (const cci of extractCcis(oldReqs[i])) { | ||
| const list = oldCciMap.get(cci); | ||
| if (list) { | ||
| list.push(i); | ||
| } | ||
| else { | ||
| oldCciMap.set(cci, [i]); | ||
| } | ||
| } | ||
| } | ||
| const newCciMap = new Map(); | ||
| for (let i = 0; i < newReqs.length; i++) { | ||
| for (const cci of extractCcis(newReqs[i])) { | ||
| const list = newCciMap.get(cci); | ||
| if (list) { | ||
| list.push(i); | ||
| } | ||
| else { | ||
| newCciMap.set(cci, [i]); | ||
| } | ||
| } | ||
| } | ||
| // Track which indices have been matched | ||
| const matchedOldIndices = new Set(); | ||
| const matchedNewIndices = new Set(); | ||
| // Find unambiguous CCI matches: exactly 1 old and 1 new share the CCI | ||
| // Collect all CCIs from both maps | ||
| const allCcis = new Set([...oldCciMap.keys(), ...newCciMap.keys()]); | ||
| for (const cci of allCcis) { | ||
| const oldIndices = oldCciMap.get(cci) ?? []; | ||
| const newIndices = newCciMap.get(cci) ?? []; | ||
| if (oldIndices.length !== 1 || newIndices.length !== 1) { | ||
| // Ambiguous or one-sided — skip | ||
| continue; | ||
| } | ||
| const oldIdx = oldIndices[0]; | ||
| const newIdx = newIndices[0]; | ||
| // Don't double-match | ||
| if (matchedOldIndices.has(oldIdx) || matchedNewIndices.has(newIdx)) { | ||
| continue; | ||
| } | ||
| result.matched.push({ | ||
| oldReq: oldReqs[oldIdx], | ||
| newReq: newReqs[newIdx], | ||
| strategy: 'cciMatch', | ||
| confidence: 0.8, | ||
| }); | ||
| matchedOldIndices.add(oldIdx); | ||
| matchedNewIndices.add(newIdx); | ||
| } | ||
| // Collect unmatched | ||
| for (let i = 0; i < oldReqs.length; i++) { | ||
| if (!matchedOldIndices.has(i)) { | ||
| result.unmatchedOld.push(oldReqs[i]); | ||
| } | ||
| } | ||
| for (let i = 0; i < newReqs.length; i++) { | ||
| if (!matchedNewIndices.has(i)) { | ||
| result.unmatchedNew.push(newReqs[i]); | ||
| } | ||
| } | ||
| return result; | ||
| }, | ||
| }; | ||
| } | ||
| //# sourceMappingURL=cci-match.js.map |
| {"version":3,"file":"cci-match.js","sourceRoot":"","sources":["../../src/matching/cci-match.ts"],"names":[],"mappings":"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"} |
| import type { MatchStrategy } from './types.js'; | ||
| /** | ||
| * Create an exact ID matching strategy. | ||
| * | ||
| * Matches requirements by their `id` field with exact string equality. | ||
| * Confidence is always 1.0 for matches. | ||
| */ | ||
| export declare function createExactIdStrategy(): MatchStrategy; | ||
| //# sourceMappingURL=exact-id.d.ts.map |
| {"version":3,"file":"exact-id.d.ts","sourceRoot":"","sources":["../../src/matching/exact-id.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,YAAY,CAAC;AAE7D;;;;;GAKG;AACH,wBAAgB,qBAAqB,IAAI,aAAa,CAmFrD"} |
| /** | ||
| * Create an exact ID matching strategy. | ||
| * | ||
| * Matches requirements by their `id` field with exact string equality. | ||
| * Confidence is always 1.0 for matches. | ||
| */ | ||
| export function createExactIdStrategy() { | ||
| return { | ||
| name: 'exactId', | ||
| match(oldReqs, newReqs) { | ||
| const result = { | ||
| matched: [], | ||
| unmatchedOld: [], | ||
| unmatchedNew: [], | ||
| }; | ||
| // Build a map of new requirements by id, detecting duplicates. | ||
| // When multiple requirements share the same ID, the ID is ambiguous | ||
| // and cannot be used for matching — all duplicates go to unmatchedNew. | ||
| const newById = new Map(); | ||
| const duplicateNewIds = new Set(); | ||
| for (const req of newReqs) { | ||
| const id = req['id']; | ||
| if (typeof id === 'string') { | ||
| if (newById.has(id)) { | ||
| // Mark this ID as a duplicate — remove it from the map | ||
| duplicateNewIds.add(id); | ||
| newById.delete(id); | ||
| } | ||
| else if (!duplicateNewIds.has(id)) { | ||
| newById.set(id, req); | ||
| } | ||
| } | ||
| } | ||
| // Build a map of old requirements by id, detecting duplicates. | ||
| const oldById = new Map(); | ||
| const duplicateOldIds = new Set(); | ||
| for (const req of oldReqs) { | ||
| const id = req['id']; | ||
| if (typeof id === 'string') { | ||
| if (oldById.has(id)) { | ||
| duplicateOldIds.add(id); | ||
| oldById.delete(id); | ||
| } | ||
| else if (!duplicateOldIds.has(id)) { | ||
| oldById.set(id, req); | ||
| } | ||
| } | ||
| } | ||
| // Track which new reqs have been matched | ||
| const matchedNewIds = new Set(); | ||
| // Match old requirements against new by id (skip duplicate old IDs) | ||
| for (const oldReq of oldReqs) { | ||
| const id = oldReq['id']; | ||
| if (typeof id !== 'string' || duplicateOldIds.has(id)) { | ||
| result.unmatchedOld.push(oldReq); | ||
| continue; | ||
| } | ||
| const newReq = newById.get(id); | ||
| if (newReq) { | ||
| result.matched.push({ | ||
| oldReq, | ||
| newReq, | ||
| strategy: 'exactId', | ||
| confidence: 1.0, | ||
| }); | ||
| matchedNewIds.add(id); | ||
| } | ||
| else { | ||
| result.unmatchedOld.push(oldReq); | ||
| } | ||
| } | ||
| // Collect unmatched new requirements (including all duplicates) | ||
| for (const req of newReqs) { | ||
| const id = req['id']; | ||
| if (typeof id === 'string') { | ||
| if (duplicateNewIds.has(id) || !matchedNewIds.has(id)) { | ||
| result.unmatchedNew.push(req); | ||
| } | ||
| } | ||
| else { | ||
| result.unmatchedNew.push(req); | ||
| } | ||
| } | ||
| return result; | ||
| }, | ||
| }; | ||
| } | ||
| //# sourceMappingURL=exact-id.js.map |
| {"version":3,"file":"exact-id.js","sourceRoot":"","sources":["../../src/matching/exact-id.ts"],"names":[],"mappings":"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"} |
| import type { MatchStrategy } from './types.js'; | ||
| /** | ||
| * Tokenize a title string into a set of meaningful tokens. | ||
| * | ||
| * - Lowercases the string | ||
| * - Splits on whitespace and punctuation | ||
| * - Filters out common stop words | ||
| * - Returns unique tokens as a Set | ||
| */ | ||
| export declare function tokenize(text: string): Set<string>; | ||
| /** | ||
| * Compute Jaccard similarity between two sets. | ||
| * Returns |intersection| / |union|, or 0 if both sets are empty. | ||
| */ | ||
| export declare function jaccardSimilarity(a: Set<string>, b: Set<string>): number; | ||
| /** | ||
| * Create a fuzzy title matching strategy. | ||
| * | ||
| * Matches requirements by token-based Jaccard similarity on the `title` field. | ||
| * Uses greedy best-match: for each unmatched old requirement, finds the | ||
| * best-matching unmatched new requirement above the confidence threshold. | ||
| * | ||
| * @param minConfidence Minimum Jaccard similarity to accept a match (default: 0.6) | ||
| */ | ||
| export declare function createFuzzyTitleStrategy(minConfidence?: number): MatchStrategy; | ||
| //# sourceMappingURL=fuzzy-match.d.ts.map |
| {"version":3,"file":"fuzzy-match.d.ts","sourceRoot":"","sources":["../../src/matching/fuzzy-match.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,YAAY,CAAC;AAe7D;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CASlD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAmBxE;AAOD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,aAAa,CA6F9E"} |
| /** Common English stop words to exclude from tokenization */ | ||
| const STOP_WORDS = new Set([ | ||
| 'a', 'an', 'the', 'is', 'are', 'was', 'were', 'be', 'been', 'being', | ||
| 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could', | ||
| 'should', 'may', 'might', 'shall', 'can', 'to', 'of', 'in', 'for', | ||
| 'on', 'with', 'at', 'by', 'from', 'as', 'into', 'through', 'during', | ||
| 'before', 'after', 'above', 'below', 'between', 'out', 'off', 'over', | ||
| 'under', 'again', 'further', 'then', 'once', 'and', 'but', 'or', 'nor', | ||
| 'not', 'no', 'so', 'if', 'it', 'its', 'that', 'this', 'these', 'those', | ||
| 'each', 'every', 'all', 'both', 'few', 'more', 'most', 'other', 'some', | ||
| 'such', 'than', 'too', 'very', 'just', 'about', | ||
| ]); | ||
| /** | ||
| * Tokenize a title string into a set of meaningful tokens. | ||
| * | ||
| * - Lowercases the string | ||
| * - Splits on whitespace and punctuation | ||
| * - Filters out common stop words | ||
| * - Returns unique tokens as a Set | ||
| */ | ||
| export function tokenize(text) { | ||
| if (!text) | ||
| return new Set(); | ||
| const tokens = text | ||
| .toLowerCase() | ||
| .split(/[\s\-_/\\,.;:!?()[\]{}"']+/) | ||
| .filter((t) => t.length > 0 && !STOP_WORDS.has(t)); | ||
| return new Set(tokens); | ||
| } | ||
| /** | ||
| * Compute Jaccard similarity between two sets. | ||
| * Returns |intersection| / |union|, or 0 if both sets are empty. | ||
| */ | ||
| export function jaccardSimilarity(a, b) { | ||
| if (a.size === 0 && b.size === 0) | ||
| return 0.0; | ||
| let intersectionSize = 0; | ||
| // Iterate over the smaller set for efficiency | ||
| const [smaller, larger] = a.size <= b.size ? [a, b] : [b, a]; | ||
| for (const item of smaller) { | ||
| if (larger.has(item)) { | ||
| intersectionSize++; | ||
| } | ||
| } | ||
| const unionSize = a.size + b.size - intersectionSize; | ||
| // Unreachable: if both sets are empty, we return above. If either is non-empty, unionSize > 0. | ||
| // Guard kept for mathematical correctness. | ||
| /* c8 ignore next */ | ||
| if (unionSize === 0) | ||
| return 0.0; | ||
| return intersectionSize / unionSize; | ||
| } | ||
| /** | ||
| * Default minimum confidence threshold for fuzzy title matching. | ||
| */ | ||
| const DEFAULT_MIN_CONFIDENCE = 0.6; | ||
| /** | ||
| * Create a fuzzy title matching strategy. | ||
| * | ||
| * Matches requirements by token-based Jaccard similarity on the `title` field. | ||
| * Uses greedy best-match: for each unmatched old requirement, finds the | ||
| * best-matching unmatched new requirement above the confidence threshold. | ||
| * | ||
| * @param minConfidence Minimum Jaccard similarity to accept a match (default: 0.6) | ||
| */ | ||
| export function createFuzzyTitleStrategy(minConfidence) { | ||
| const threshold = minConfidence ?? DEFAULT_MIN_CONFIDENCE; | ||
| return { | ||
| name: 'fuzzyTitle', | ||
| match(oldReqs, newReqs) { | ||
| const result = { | ||
| matched: [], | ||
| unmatchedOld: [], | ||
| unmatchedNew: [], | ||
| }; | ||
| // Pre-tokenize all titles | ||
| const oldTokens = []; | ||
| for (let i = 0; i < oldReqs.length; i++) { | ||
| const title = oldReqs[i]['title']; | ||
| if (typeof title === 'string' && title.length > 0) { | ||
| const tokens = tokenize(title); | ||
| if (tokens.size > 0) { | ||
| oldTokens.push({ idx: i, tokens }); | ||
| } | ||
| } | ||
| } | ||
| const newTokens = []; | ||
| for (let i = 0; i < newReqs.length; i++) { | ||
| const title = newReqs[i]['title']; | ||
| if (typeof title === 'string' && title.length > 0) { | ||
| const tokens = tokenize(title); | ||
| if (tokens.size > 0) { | ||
| newTokens.push({ idx: i, tokens }); | ||
| } | ||
| } | ||
| } | ||
| // Track matched indices | ||
| const matchedOldIndices = new Set(); | ||
| const matchedNewIndices = new Set(); | ||
| // Build a list of all potential matches with similarity scores | ||
| const candidates = []; | ||
| for (const old of oldTokens) { | ||
| for (const nw of newTokens) { | ||
| const sim = jaccardSimilarity(old.tokens, nw.tokens); | ||
| if (sim >= threshold) { | ||
| candidates.push({ | ||
| oldIdx: old.idx, | ||
| newIdx: nw.idx, | ||
| similarity: sim, | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| // Sort by similarity descending (greedy best-match) | ||
| candidates.sort((a, b) => b.similarity - a.similarity); | ||
| // Greedily assign matches | ||
| for (const candidate of candidates) { | ||
| if (matchedOldIndices.has(candidate.oldIdx) || matchedNewIndices.has(candidate.newIdx)) { | ||
| continue; | ||
| } | ||
| result.matched.push({ | ||
| oldReq: oldReqs[candidate.oldIdx], | ||
| newReq: newReqs[candidate.newIdx], | ||
| strategy: 'fuzzyTitle', | ||
| confidence: candidate.similarity, | ||
| }); | ||
| matchedOldIndices.add(candidate.oldIdx); | ||
| matchedNewIndices.add(candidate.newIdx); | ||
| } | ||
| // Collect unmatched | ||
| for (let i = 0; i < oldReqs.length; i++) { | ||
| if (!matchedOldIndices.has(i)) { | ||
| result.unmatchedOld.push(oldReqs[i]); | ||
| } | ||
| } | ||
| for (let i = 0; i < newReqs.length; i++) { | ||
| if (!matchedNewIndices.has(i)) { | ||
| result.unmatchedNew.push(newReqs[i]); | ||
| } | ||
| } | ||
| return result; | ||
| }, | ||
| }; | ||
| } | ||
| //# sourceMappingURL=fuzzy-match.js.map |
| {"version":3,"file":"fuzzy-match.js","sourceRoot":"","sources":["../../src/matching/fuzzy-match.ts"],"names":[],"mappings":"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"} |
| import type { MatchResult } from './types.js'; | ||
| export type { MatchResult, MatchPair, MatchStrategy } from './types.js'; | ||
| export { createExactIdStrategy } from './exact-id.js'; | ||
| export { createMappedIdStrategy } from './mapped-id.js'; | ||
| export { createCciMatchStrategy } from './cci-match.js'; | ||
| export { createFuzzyTitleStrategy, tokenize, jaccardSimilarity } from './fuzzy-match.js'; | ||
| /** | ||
| * Options for configuring requirement matching. | ||
| */ | ||
| export interface MatchOptions { | ||
| /** Primary matching strategy name (default: 'exactId') */ | ||
| strategy?: string; | ||
| /** Fallback strategy names, applied in order to remaining unmatched requirements */ | ||
| fallbackStrategies?: string[]; | ||
| /** Mapping table for the 'mappedId' strategy (old ID -> new ID) */ | ||
| mappingTable?: Record<string, string>; | ||
| /** Minimum confidence threshold for fuzzy matching (default: 0.6) */ | ||
| minConfidence?: number; | ||
| } | ||
| /** | ||
| * Match requirements between two evaluations using a primary strategy | ||
| * and optional fallback strategies. | ||
| * | ||
| * The registry applies strategies in order: | ||
| * 1. Primary strategy matches what it can | ||
| * 2. Unmatched requirements pass to the next fallback strategy | ||
| * 3. Process continues until all strategies are exhausted or all | ||
| * requirements are matched | ||
| * | ||
| * @param oldReqs Requirements from the old evaluation | ||
| * @param newReqs Requirements from the new evaluation | ||
| * @param options Matching configuration | ||
| * @returns Combined match result from all strategies | ||
| */ | ||
| export declare function matchRequirements(oldReqs: Record<string, unknown>[], newReqs: Record<string, unknown>[], options?: MatchOptions): MatchResult; | ||
| //# sourceMappingURL=index.d.ts.map |
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/matching/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,YAAY,CAAC;AAO7D,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEzF;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oFAAoF;IACpF,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAqBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAClC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAClC,OAAO,CAAC,EAAE,YAAY,GACrB,WAAW,CAmCb"} |
| import { createExactIdStrategy } from './exact-id.js'; | ||
| import { createMappedIdStrategy } from './mapped-id.js'; | ||
| import { createCciMatchStrategy } from './cci-match.js'; | ||
| import { createFuzzyTitleStrategy } from './fuzzy-match.js'; | ||
| export { createExactIdStrategy } from './exact-id.js'; | ||
| export { createMappedIdStrategy } from './mapped-id.js'; | ||
| export { createCciMatchStrategy } from './cci-match.js'; | ||
| export { createFuzzyTitleStrategy, tokenize, jaccardSimilarity } from './fuzzy-match.js'; | ||
| /** | ||
| * Create a strategy instance by name, using the provided options for | ||
| * strategies that require configuration. | ||
| */ | ||
| function createStrategy(name, options) { | ||
| switch (name) { | ||
| case 'exactId': | ||
| return createExactIdStrategy(); | ||
| case 'mappedId': | ||
| return createMappedIdStrategy(options.mappingTable ?? {}); | ||
| case 'cciMatch': | ||
| return createCciMatchStrategy(); | ||
| case 'fuzzyTitle': | ||
| return createFuzzyTitleStrategy(options.minConfidence); | ||
| default: | ||
| throw new Error(`Unknown matching strategy: '${name}'`); | ||
| } | ||
| } | ||
| /** | ||
| * Match requirements between two evaluations using a primary strategy | ||
| * and optional fallback strategies. | ||
| * | ||
| * The registry applies strategies in order: | ||
| * 1. Primary strategy matches what it can | ||
| * 2. Unmatched requirements pass to the next fallback strategy | ||
| * 3. Process continues until all strategies are exhausted or all | ||
| * requirements are matched | ||
| * | ||
| * @param oldReqs Requirements from the old evaluation | ||
| * @param newReqs Requirements from the new evaluation | ||
| * @param options Matching configuration | ||
| * @returns Combined match result from all strategies | ||
| */ | ||
| export function matchRequirements(oldReqs, newReqs, options) { | ||
| const opts = options ?? {}; | ||
| const primaryName = opts.strategy ?? 'exactId'; | ||
| const fallbackNames = opts.fallbackStrategies ?? []; | ||
| // Build all strategy instances once up front (also validates names — | ||
| // createStrategy throws for unknown strategy names). | ||
| const allNames = [primaryName, ...fallbackNames]; | ||
| const strategies = allNames.map((name) => createStrategy(name, opts)); | ||
| // Accumulate all matched pairs | ||
| const allMatched = []; | ||
| // Start with all requirements unmatched | ||
| let currentUnmatchedOld = oldReqs; | ||
| let currentUnmatchedNew = newReqs; | ||
| // Apply strategies in order | ||
| for (const strategy of strategies) { | ||
| if (currentUnmatchedOld.length === 0 || currentUnmatchedNew.length === 0) { | ||
| break; | ||
| } | ||
| const result = strategy.match(currentUnmatchedOld, currentUnmatchedNew); | ||
| allMatched.push(...result.matched); | ||
| currentUnmatchedOld = result.unmatchedOld; | ||
| currentUnmatchedNew = result.unmatchedNew; | ||
| } | ||
| return { | ||
| matched: allMatched, | ||
| unmatchedOld: currentUnmatchedOld, | ||
| unmatchedNew: currentUnmatchedNew, | ||
| }; | ||
| } | ||
| //# sourceMappingURL=index.js.map |
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/matching/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAI5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAgBzF;;;GAGG;AACH,SAAS,cAAc,CAAC,IAAY,EAAE,OAAqB;IACzD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,SAAS;YACZ,OAAO,qBAAqB,EAAE,CAAC;QACjC,KAAK,UAAU;YACb,OAAO,sBAAsB,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;QAC5D,KAAK,UAAU;YACb,OAAO,sBAAsB,EAAE,CAAC;QAClC,KAAK,YAAY;YACf,OAAO,wBAAwB,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;QACzD;YACE,MAAM,IAAI,KAAK,CAAC,+BAA+B,IAAI,GAAG,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAkC,EAClC,OAAkC,EAClC,OAAsB;IAEtB,MAAM,IAAI,GAAG,OAAO,IAAI,EAAE,CAAC;IAC3B,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,IAAI,SAAS,CAAC;IAC/C,MAAM,aAAa,GAAG,IAAI,CAAC,kBAAkB,IAAI,EAAE,CAAC;IAEpD,qEAAqE;IACrE,qDAAqD;IACrD,MAAM,QAAQ,GAAG,CAAC,WAAW,EAAE,GAAG,aAAa,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAEtE,+BAA+B;IAC/B,MAAM,UAAU,GAA2B,EAAE,CAAC;IAE9C,wCAAwC;IACxC,IAAI,mBAAmB,GAAG,OAAO,CAAC;IAClC,IAAI,mBAAmB,GAAG,OAAO,CAAC;IAElC,4BAA4B;IAC5B,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;QAClC,IAAI,mBAAmB,CAAC,MAAM,KAAK,CAAC,IAAI,mBAAmB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzE,MAAM;QACR,CAAC;QAED,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,mBAAmB,EAAE,mBAAmB,CAAC,CAAC;QAExE,UAAU,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;QACnC,mBAAmB,GAAG,MAAM,CAAC,YAAY,CAAC;QAC1C,mBAAmB,GAAG,MAAM,CAAC,YAAY,CAAC;IAC5C,CAAC;IAED,OAAO;QACL,OAAO,EAAE,UAAU;QACnB,YAAY,EAAE,mBAAmB;QACjC,YAAY,EAAE,mBAAmB;KAClC,CAAC;AACJ,CAAC"} |
| import type { MatchStrategy } from './types.js'; | ||
| /** | ||
| * Create a mapped ID matching strategy. | ||
| * | ||
| * Translates old requirement IDs using a mapping table before matching. | ||
| * Only matches requirements whose old ID appears in the mapping table and | ||
| * whose mapped new ID exists in the new requirements. | ||
| * | ||
| * Confidence is 0.95 for mapped matches (slightly less than exact ID | ||
| * because the match depends on the accuracy of the mapping table). | ||
| */ | ||
| export declare function createMappedIdStrategy(mapping: Record<string, string>): MatchStrategy; | ||
| //# sourceMappingURL=mapped-id.d.ts.map |
| {"version":3,"file":"mapped-id.d.ts","sourceRoot":"","sources":["../../src/matching/mapped-id.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,YAAY,CAAC;AAE7D;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,aAAa,CA8FrF"} |
| /** | ||
| * Create a mapped ID matching strategy. | ||
| * | ||
| * Translates old requirement IDs using a mapping table before matching. | ||
| * Only matches requirements whose old ID appears in the mapping table and | ||
| * whose mapped new ID exists in the new requirements. | ||
| * | ||
| * Confidence is 0.95 for mapped matches (slightly less than exact ID | ||
| * because the match depends on the accuracy of the mapping table). | ||
| */ | ||
| export function createMappedIdStrategy(mapping) { | ||
| return { | ||
| name: 'mappedId', | ||
| match(oldReqs, newReqs) { | ||
| const result = { | ||
| matched: [], | ||
| unmatchedOld: [], | ||
| unmatchedNew: [], | ||
| }; | ||
| // Build a map of new requirements by id, detecting duplicates. | ||
| // When multiple requirements share the same ID, the ID is ambiguous | ||
| // and cannot be used for matching — all duplicates go to unmatchedNew. | ||
| const newById = new Map(); | ||
| const duplicateNewIds = new Set(); | ||
| for (const req of newReqs) { | ||
| const id = req['id']; | ||
| if (typeof id === 'string') { | ||
| if (newById.has(id)) { | ||
| duplicateNewIds.add(id); | ||
| newById.delete(id); | ||
| } | ||
| else if (!duplicateNewIds.has(id)) { | ||
| newById.set(id, req); | ||
| } | ||
| } | ||
| } | ||
| // Build a map of old requirements by id, detecting duplicates. | ||
| const oldById = new Map(); | ||
| const duplicateOldIds = new Set(); | ||
| for (const req of oldReqs) { | ||
| const id = req['id']; | ||
| if (typeof id === 'string') { | ||
| if (oldById.has(id)) { | ||
| duplicateOldIds.add(id); | ||
| oldById.delete(id); | ||
| } | ||
| else if (!duplicateOldIds.has(id)) { | ||
| oldById.set(id, req); | ||
| } | ||
| } | ||
| } | ||
| // Track which new reqs have been matched | ||
| const matchedNewIds = new Set(); | ||
| // Try to match old requirements using the mapping table (skip duplicate old IDs) | ||
| for (const oldReq of oldReqs) { | ||
| const oldId = oldReq['id']; | ||
| if (typeof oldId !== 'string' || duplicateOldIds.has(oldId)) { | ||
| result.unmatchedOld.push(oldReq); | ||
| continue; | ||
| } | ||
| const mappedNewId = mapping[oldId]; | ||
| if (mappedNewId === undefined) { | ||
| result.unmatchedOld.push(oldReq); | ||
| continue; | ||
| } | ||
| // Skip if the mapped new ID is a duplicate | ||
| if (duplicateNewIds.has(mappedNewId)) { | ||
| result.unmatchedOld.push(oldReq); | ||
| continue; | ||
| } | ||
| const newReq = newById.get(mappedNewId); | ||
| if (newReq && !matchedNewIds.has(mappedNewId)) { | ||
| result.matched.push({ | ||
| oldReq, | ||
| newReq, | ||
| strategy: 'mappedId', | ||
| confidence: 0.95, | ||
| }); | ||
| matchedNewIds.add(mappedNewId); | ||
| } | ||
| else { | ||
| result.unmatchedOld.push(oldReq); | ||
| } | ||
| } | ||
| // Collect unmatched new requirements (including all duplicates) | ||
| for (const req of newReqs) { | ||
| const id = req['id']; | ||
| if (typeof id === 'string') { | ||
| if (duplicateNewIds.has(id) || !matchedNewIds.has(id)) { | ||
| result.unmatchedNew.push(req); | ||
| } | ||
| } | ||
| else { | ||
| result.unmatchedNew.push(req); | ||
| } | ||
| } | ||
| return result; | ||
| }, | ||
| }; | ||
| } | ||
| //# sourceMappingURL=mapped-id.js.map |
| {"version":3,"file":"mapped-id.js","sourceRoot":"","sources":["../../src/matching/mapped-id.ts"],"names":[],"mappings":"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"} |
| /** | ||
| * Result of matching requirements between two evaluations. | ||
| */ | ||
| export interface MatchResult { | ||
| /** Paired requirements with their match metadata */ | ||
| matched: MatchPair[]; | ||
| /** Requirements only in the old evaluation (unmatched) */ | ||
| unmatchedOld: Record<string, unknown>[]; | ||
| /** Requirements only in the new evaluation (unmatched) */ | ||
| unmatchedNew: Record<string, unknown>[]; | ||
| } | ||
| /** | ||
| * A single matched pair of requirements. | ||
| */ | ||
| export interface MatchPair { | ||
| /** The requirement from the old evaluation */ | ||
| oldReq: Record<string, unknown>; | ||
| /** The requirement from the new evaluation */ | ||
| newReq: Record<string, unknown>; | ||
| /** Name of the strategy that produced this match */ | ||
| strategy: string; | ||
| /** Confidence score for the match (0.0 - 1.0) */ | ||
| confidence: number; | ||
| } | ||
| /** | ||
| * A pluggable strategy for matching requirements between evaluations. | ||
| */ | ||
| export interface MatchStrategy { | ||
| /** Unique name for this strategy */ | ||
| name: string; | ||
| /** Match old requirements against new requirements */ | ||
| match(oldReqs: Record<string, unknown>[], newReqs: Record<string, unknown>[]): MatchResult; | ||
| } | ||
| //# sourceMappingURL=types.d.ts.map |
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/matching/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,oDAAoD;IACpD,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,0DAA0D;IAC1D,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACxC,0DAA0D;IAC1D,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,WAAW,CAAC;CAC5F"} |
| export {}; | ||
| //# sourceMappingURL=types.js.map |
| {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/matching/types.ts"],"names":[],"mappings":""} |
| /** | ||
| * Normalize InSpec exec-json v1 format to HDF v2 structure for diffing. | ||
| * | ||
| * V1 uses: profiles[].controls[].results[].code_desc, source_location, start_time | ||
| * V2 uses: baselines[].requirements[].results[].codeDesc, sourceLocation, startTime | ||
| * | ||
| * This module detects v1 documents and converts them in-memory to v2 shape | ||
| * so the diff engine only needs to handle one format. | ||
| */ | ||
| /** | ||
| * Detect whether a document is v1 (InSpec exec-json) format. | ||
| * V1 has `profiles` at the top level; v2 has `baselines`. | ||
| */ | ||
| export declare function isV1Format(doc: Record<string, unknown>): boolean; | ||
| /** | ||
| * Normalize a document to v2-like structure. If already v2, returns as-is. | ||
| * If v1, converts profiles→baselines, controls→requirements, and snake_case→camelCase. | ||
| */ | ||
| export declare function normalizeToV2(doc: Record<string, unknown>): Record<string, unknown>; | ||
| //# sourceMappingURL=normalize.d.ts.map |
| {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAwCH;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAEhE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgBnF"} |
| /** | ||
| * Normalize InSpec exec-json v1 format to HDF v2 structure for diffing. | ||
| * | ||
| * V1 uses: profiles[].controls[].results[].code_desc, source_location, start_time | ||
| * V2 uses: baselines[].requirements[].results[].codeDesc, sourceLocation, startTime | ||
| * | ||
| * This module detects v1 documents and converts them in-memory to v2 shape | ||
| * so the diff engine only needs to handle one format. | ||
| */ | ||
| /** | ||
| * Detect whether a document is v1 (InSpec exec-json) format. | ||
| * V1 has `profiles` at the top level; v2 has `baselines`. | ||
| */ | ||
| export function isV1Format(doc) { | ||
| return Array.isArray(doc['profiles']) && !Array.isArray(doc['baselines']); | ||
| } | ||
| /** | ||
| * Normalize a document to v2-like structure. If already v2, returns as-is. | ||
| * If v1, converts profiles→baselines, controls→requirements, and snake_case→camelCase. | ||
| */ | ||
| export function normalizeToV2(doc) { | ||
| if (!isV1Format(doc)) { | ||
| return doc; | ||
| } | ||
| const profiles = doc['profiles']; | ||
| const baselines = profiles.map(normalizeProfile); | ||
| // Preserve timestamp from statistics if available | ||
| const statistics = doc['statistics']; | ||
| return { | ||
| baselines, | ||
| statistics, | ||
| timestamp: doc['timestamp'], | ||
| }; | ||
| } | ||
| function normalizeProfile(profile) { | ||
| const controls = profile.controls ?? []; | ||
| const requirements = controls.map(normalizeControl); | ||
| return { | ||
| name: profile.name, | ||
| title: profile.title, | ||
| version: profile.version, | ||
| checksum: profile.sha256 ? { algorithm: 'sha256', value: profile.sha256 } : undefined, | ||
| groups: profile.groups ?? [], | ||
| supports: profile.supports ?? [], | ||
| inputs: profile.attributes ?? [], | ||
| requirements, | ||
| }; | ||
| } | ||
| function normalizeControl(control) { | ||
| const v1Results = control.results ?? []; | ||
| const results = v1Results.map(normalizeResult); | ||
| return { | ||
| id: control.id, | ||
| title: control.title, | ||
| descriptions: control.desc | ||
| ? [{ label: 'default', data: control.desc }] | ||
| : [], | ||
| impact: control.impact, | ||
| tags: control.tags ?? {}, | ||
| refs: control.refs ?? [], | ||
| code: control.code, | ||
| sourceLocation: control.source_location ?? control.sourceLocation, | ||
| results, | ||
| }; | ||
| } | ||
| /** | ||
| * Map InSpec v1 result status values to HDF v2 Result_Status enum values. | ||
| * | ||
| * InSpec v1 uses: "passed", "failed", "skipped", "error" | ||
| * HDF v2 uses: "passed", "failed", "notApplicable", "notReviewed", "error" | ||
| * | ||
| * "skipped" in InSpec means the test was not executed (typically because a | ||
| * `describe.one_of` condition was not met, or `only_if` excluded it). | ||
| * This maps to "notReviewed" in HDF v2 — the requirement was not assessed. | ||
| */ | ||
| function normalizeResultStatus(v1Status) { | ||
| switch (v1Status) { | ||
| case 'skipped': | ||
| return 'notReviewed'; | ||
| default: | ||
| return v1Status; | ||
| } | ||
| } | ||
| /** | ||
| * Normalize a timestamp string to RFC 3339 / ISO 8601 date-time format. | ||
| * | ||
| * InSpec v1 uses formats like "2017-09-22 14:12:15 -0400" which are not valid | ||
| * RFC 3339. This function attempts to parse and re-format such timestamps. | ||
| * If parsing fails, the original string is returned as-is. | ||
| */ | ||
| function normalizeTimestamp(timestamp) { | ||
| // Already valid ISO 8601 (contains 'T') | ||
| if (timestamp.includes('T')) { | ||
| return timestamp; | ||
| } | ||
| // Try to parse InSpec format: "YYYY-MM-DD HH:MM:SS +HHMM" | ||
| const parsed = new Date(timestamp); | ||
| if (!isNaN(parsed.getTime())) { | ||
| return parsed.toISOString(); | ||
| } | ||
| return timestamp; | ||
| } | ||
| function normalizeResult(result) { | ||
| const rawStartTime = result.start_time ?? result.startTime ?? ''; | ||
| const normalized = { | ||
| status: normalizeResultStatus(result.status), | ||
| codeDesc: result.code_desc ?? result.codeDesc ?? '', | ||
| startTime: rawStartTime ? normalizeTimestamp(rawStartTime) : rawStartTime, | ||
| }; | ||
| // Only include optional fields when they have values | ||
| const runTime = result.run_time ?? result.runTime; | ||
| if (runTime !== undefined) { | ||
| normalized['runTime'] = runTime; | ||
| } | ||
| if (result.message !== undefined) { | ||
| normalized['message'] = result.message; | ||
| } | ||
| return normalized; | ||
| } | ||
| //# sourceMappingURL=normalize.js.map |
| {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"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"} |
| import type { HdfComparison } from '../types.js'; | ||
| import type { RenderOptions } from './types.js'; | ||
| /** | ||
| * Render an HdfComparison as a CSV string. | ||
| * | ||
| * One row per requirement. Columns: | ||
| * - ID, Title, State, Old Status, New Status, Impact (Old), Impact (New), Change Reasons | ||
| * | ||
| * When `detail: 'full'`, an additional Field Changes column is included. | ||
| * | ||
| * Header row is always included. Standard CSV escaping (RFC 4180). | ||
| * Default detail level: `'control'`. | ||
| */ | ||
| export declare function renderCsv(comparison: HdfComparison, options?: RenderOptions): string; | ||
| //# sourceMappingURL=csv.d.ts.map |
| {"version":3,"file":"csv.d.ts","sourceRoot":"","sources":["../../src/renderers/csv.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA+BhD;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CACvB,UAAU,EAAE,aAAa,EACzB,OAAO,CAAC,EAAE,aAAa,GACtB,MAAM,CAyCR"} |
| import { filterRequirements } from './filter.js'; | ||
| /** | ||
| * Escape a value for CSV output. | ||
| * | ||
| * Per RFC 4180: | ||
| * - Fields containing commas, double quotes, or newlines are enclosed in double quotes. | ||
| * - Double quotes within a field are escaped by doubling them. | ||
| */ | ||
| function escapeCsvField(value) { | ||
| if (value.includes(',') || value.includes('"') || value.includes('\n') || value.includes('\r')) { | ||
| return `"${value.replace(/"/g, '""')}"`; | ||
| } | ||
| return value; | ||
| } | ||
| /** | ||
| * Format field changes as a human-readable string. | ||
| */ | ||
| function formatFieldChanges(req) { | ||
| if (req.fieldChanges.length === 0) | ||
| return ''; | ||
| return req.fieldChanges | ||
| .map((fc) => { | ||
| if (fc.op === 'add') | ||
| return `+${fc.path}: ${JSON.stringify(fc.newValue)}`; | ||
| if (fc.op === 'remove') | ||
| return `-${fc.path}: ${JSON.stringify(fc.oldValue)}`; | ||
| return `${fc.path}: ${JSON.stringify(fc.oldValue)} -> ${JSON.stringify(fc.newValue)}`; | ||
| }) | ||
| .join('; '); | ||
| } | ||
| /** | ||
| * Render an HdfComparison as a CSV string. | ||
| * | ||
| * One row per requirement. Columns: | ||
| * - ID, Title, State, Old Status, New Status, Impact (Old), Impact (New), Change Reasons | ||
| * | ||
| * When `detail: 'full'`, an additional Field Changes column is included. | ||
| * | ||
| * Header row is always included. Standard CSV escaping (RFC 4180). | ||
| * Default detail level: `'control'`. | ||
| */ | ||
| export function renderCsv(comparison, options) { | ||
| const detail = options?.detail ?? 'control'; | ||
| const filtered = filterRequirements(comparison.requirementDiffs, options); | ||
| const headers = [ | ||
| 'ID', | ||
| 'Title', | ||
| 'State', | ||
| 'Old Status', | ||
| 'New Status', | ||
| 'Impact (Old)', | ||
| 'Impact (New)', | ||
| 'Change Reasons', | ||
| ]; | ||
| if (detail === 'full') { | ||
| headers.push('Field Changes'); | ||
| } | ||
| const lines = [headers.join(',')]; | ||
| for (const req of filtered) { | ||
| const row = [ | ||
| escapeCsvField(req.id), | ||
| escapeCsvField(req.title ?? ''), | ||
| escapeCsvField(req.state), | ||
| escapeCsvField(req.oldEffectiveStatus ?? ''), | ||
| escapeCsvField(req.newEffectiveStatus ?? ''), | ||
| escapeCsvField(req.oldImpact !== undefined ? String(req.oldImpact) : ''), | ||
| escapeCsvField(req.newImpact !== undefined ? String(req.newImpact) : ''), | ||
| escapeCsvField(req.changeReasons.join(', ')), | ||
| ]; | ||
| if (detail === 'full') { | ||
| row.push(escapeCsvField(formatFieldChanges(req))); | ||
| } | ||
| lines.push(row.join(',')); | ||
| } | ||
| return lines.join('\n'); | ||
| } | ||
| //# sourceMappingURL=csv.js.map |
| {"version":3,"file":"csv.js","sourceRoot":"","sources":["../../src/renderers/csv.ts"],"names":[],"mappings":"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"} |
| import type { RequirementDiff } from '../types.js'; | ||
| import type { RenderOptions } from './types.js'; | ||
| /** | ||
| * Filter requirement diffs based on render options. | ||
| * | ||
| * Supports filtering by: | ||
| * - `filterStates`: Only include requirements matching the given states | ||
| * - `filterSeverity`: Only include requirements matching the given severity tag | ||
| */ | ||
| export declare function filterRequirements(diffs: RequirementDiff[], options?: RenderOptions): RequirementDiff[]; | ||
| //# sourceMappingURL=filter.d.ts.map |
| {"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../../src/renderers/filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,eAAe,EAAE,EACxB,OAAO,CAAC,EAAE,aAAa,GACtB,eAAe,EAAE,CAyBnB"} |
| /** | ||
| * Filter requirement diffs based on render options. | ||
| * | ||
| * Supports filtering by: | ||
| * - `filterStates`: Only include requirements matching the given states | ||
| * - `filterSeverity`: Only include requirements matching the given severity tag | ||
| */ | ||
| export function filterRequirements(diffs, options) { | ||
| let filtered = diffs; | ||
| if (options?.filterStates && options.filterStates.length > 0) { | ||
| const states = new Set(options.filterStates); | ||
| filtered = filtered.filter((r) => states.has(r.state)); | ||
| } | ||
| if (options?.filterSeverity) { | ||
| const severity = options.filterSeverity.toLowerCase(); | ||
| filtered = filtered.filter((r) => { | ||
| const before = r.before; | ||
| const after = r.after; | ||
| const tags = after?.['tags'] ?? | ||
| before?.['tags']; | ||
| if (!tags) | ||
| return false; | ||
| return (typeof tags['severity'] === 'string' && | ||
| tags['severity'].toLowerCase() === severity); | ||
| }); | ||
| } | ||
| return filtered; | ||
| } | ||
| //# sourceMappingURL=filter.js.map |
| {"version":3,"file":"filter.js","sourceRoot":"","sources":["../../src/renderers/filter.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAwB,EACxB,OAAuB;IAEvB,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,IAAI,OAAO,EAAE,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAC7C,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,IAAI,OAAO,EAAE,cAAc,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,EAAE,CAAC;QACtD,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;YAC/B,MAAM,MAAM,GAAG,CAAC,CAAC,MAAwC,CAAC;YAC1D,MAAM,KAAK,GAAG,CAAC,CAAC,KAAuC,CAAC;YACxD,MAAM,IAAI,GACP,KAAK,EAAE,CAAC,MAAM,CAAyC;gBACvD,MAAM,EAAE,CAAC,MAAM,CAAyC,CAAC;YAC5D,IAAI,CAAC,IAAI;gBAAE,OAAO,KAAK,CAAC;YACxB,OAAO,CACL,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,QAAQ;gBACpC,IAAI,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE,KAAK,QAAQ,CAC5C,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"} |
| export { renderJson } from './json.js'; | ||
| export { renderMarkdown } from './markdown.js'; | ||
| export { renderTerminal } from './terminal.js'; | ||
| export { renderCsv } from './csv.js'; | ||
| export type { DetailLevel, RenderOptions } from './types.js'; | ||
| import type { HdfComparison } from '../types.js'; | ||
| import type { RenderOptions } from './types.js'; | ||
| /** | ||
| * Convenience function to render a comparison in any supported format. | ||
| * | ||
| * @param comparison - The HdfComparison document to render | ||
| * @param format - Output format: 'json', 'markdown', 'terminal', or 'csv' | ||
| * @param options - Rendering options (detail level, filters, color) | ||
| * @returns The rendered string | ||
| */ | ||
| export declare function render(comparison: HdfComparison, format: 'json' | 'markdown' | 'terminal' | 'csv', options?: RenderOptions): string; | ||
| //# sourceMappingURL=index.d.ts.map |
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/renderers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAMhD;;;;;;;GAOG;AACH,wBAAgB,MAAM,CACpB,UAAU,EAAE,aAAa,EACzB,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,KAAK,EAChD,OAAO,CAAC,EAAE,aAAa,GACtB,MAAM,CAWR"} |
| export { renderJson } from './json.js'; | ||
| export { renderMarkdown } from './markdown.js'; | ||
| export { renderTerminal } from './terminal.js'; | ||
| export { renderCsv } from './csv.js'; | ||
| import { renderJson } from './json.js'; | ||
| import { renderMarkdown } from './markdown.js'; | ||
| import { renderTerminal } from './terminal.js'; | ||
| import { renderCsv } from './csv.js'; | ||
| /** | ||
| * Convenience function to render a comparison in any supported format. | ||
| * | ||
| * @param comparison - The HdfComparison document to render | ||
| * @param format - Output format: 'json', 'markdown', 'terminal', or 'csv' | ||
| * @param options - Rendering options (detail level, filters, color) | ||
| * @returns The rendered string | ||
| */ | ||
| export function render(comparison, format, options) { | ||
| switch (format) { | ||
| case 'json': | ||
| return renderJson(comparison, options); | ||
| case 'markdown': | ||
| return renderMarkdown(comparison, options); | ||
| case 'terminal': | ||
| return renderTerminal(comparison, options); | ||
| case 'csv': | ||
| return renderCsv(comparison, options); | ||
| } | ||
| } | ||
| //# sourceMappingURL=index.js.map |
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/renderers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAKrC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC;;;;;;;GAOG;AACH,MAAM,UAAU,MAAM,CACpB,UAAyB,EACzB,MAAgD,EAChD,OAAuB;IAEvB,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,MAAM;YACT,OAAO,UAAU,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACzC,KAAK,UAAU;YACb,OAAO,cAAc,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC7C,KAAK,UAAU;YACb,OAAO,cAAc,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC7C,KAAK,KAAK;YACR,OAAO,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC"} |
| import type { HdfComparison } from '../types.js'; | ||
| import type { RenderOptions } from './types.js'; | ||
| /** | ||
| * Render an HdfComparison as a JSON string. | ||
| * | ||
| * - `detail: 'summary'` -- only `{ formatVersion, comparisonMode, summary }` | ||
| * - `detail: 'control'` -- full document but `before`/`after` stripped from requirementDiffs | ||
| * - `detail: 'full'` -- `JSON.stringify(comparison, null, 2)` (the complete document) | ||
| * | ||
| * Default detail level: `'control'`. | ||
| */ | ||
| export declare function renderJson(comparison: HdfComparison, options?: RenderOptions): string; | ||
| //# sourceMappingURL=json.d.ts.map |
| {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/renderers/json.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAehD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,UAAU,EAAE,aAAa,EACzB,OAAO,CAAC,EAAE,aAAa,GACtB,MAAM,CA0CR"} |
| import { filterRequirements } from './filter.js'; | ||
| /** | ||
| * Strip `before` and `after` from a RequirementDiff, keeping only | ||
| * the summary fields (id, state, title, statuses, changeReasons, fieldChanges). | ||
| */ | ||
| function stripSnapshots(req) { | ||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
| const { before, after, ...rest } = req; | ||
| return rest; | ||
| } | ||
| /** | ||
| * Render an HdfComparison as a JSON string. | ||
| * | ||
| * - `detail: 'summary'` -- only `{ formatVersion, comparisonMode, summary }` | ||
| * - `detail: 'control'` -- full document but `before`/`after` stripped from requirementDiffs | ||
| * - `detail: 'full'` -- `JSON.stringify(comparison, null, 2)` (the complete document) | ||
| * | ||
| * Default detail level: `'control'`. | ||
| */ | ||
| export function renderJson(comparison, options) { | ||
| const detail = options?.detail ?? 'control'; | ||
| if (detail === 'summary') { | ||
| return JSON.stringify({ | ||
| formatVersion: comparison.formatVersion, | ||
| comparisonMode: comparison.comparisonMode, | ||
| summary: comparison.summary, | ||
| }, null, 2); | ||
| } | ||
| if (detail === 'full') { | ||
| const filtered = filterRequirements(comparison.requirementDiffs, options); | ||
| if (filtered.length !== comparison.requirementDiffs.length) { | ||
| return JSON.stringify({ ...comparison, requirementDiffs: filtered }, null, 2); | ||
| } | ||
| return JSON.stringify(comparison, null, 2); | ||
| } | ||
| // detail === 'control' (default) | ||
| const filtered = filterRequirements(comparison.requirementDiffs, options); | ||
| const strippedDiffs = filtered.map(stripSnapshots); | ||
| return JSON.stringify({ ...comparison, requirementDiffs: strippedDiffs }, null, 2); | ||
| } | ||
| //# sourceMappingURL=json.js.map |
| {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/renderers/json.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD;;;GAGG;AACH,SAAS,cAAc,CACrB,GAAoB;IAEpB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,CAAC;IACvC,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CACxB,UAAyB,EACzB,OAAuB;IAEvB,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,SAAS,CAAC;IAE5C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC,SAAS,CACnB;YACE,aAAa,EAAE,UAAU,CAAC,aAAa;YACvC,cAAc,EAAE,UAAU,CAAC,cAAc;YACzC,OAAO,EAAE,UAAU,CAAC,OAAO;SAC5B,EACD,IAAI,EACJ,CAAC,CACF,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,kBAAkB,CACjC,UAAU,CAAC,gBAAgB,EAC3B,OAAO,CACR,CAAC;QACF,IAAI,QAAQ,CAAC,MAAM,KAAK,UAAU,CAAC,gBAAgB,CAAC,MAAM,EAAE,CAAC;YAC3D,OAAO,IAAI,CAAC,SAAS,CACnB,EAAE,GAAG,UAAU,EAAE,gBAAgB,EAAE,QAAQ,EAAE,EAC7C,IAAI,EACJ,CAAC,CACF,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7C,CAAC;IAED,iCAAiC;IACjC,MAAM,QAAQ,GAAG,kBAAkB,CACjC,UAAU,CAAC,gBAAgB,EAC3B,OAAO,CACR,CAAC;IACF,MAAM,aAAa,GAAG,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEnD,OAAO,IAAI,CAAC,SAAS,CACnB,EAAE,GAAG,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,EAClD,IAAI,EACJ,CAAC,CACF,CAAC;AACJ,CAAC"} |
| import type { HdfComparison } from '../types.js'; | ||
| import type { RenderOptions } from './types.js'; | ||
| /** | ||
| * Render an HdfComparison as a Markdown string. | ||
| * | ||
| * - `detail: 'summary'` -- summary table only | ||
| * - `detail: 'control'` -- summary + per-requirement tables by state | ||
| * - `detail: 'full'` -- summary + per-requirement tables with changeReasons and fieldChanges | ||
| * | ||
| * Default detail level: `'control'`. | ||
| */ | ||
| export declare function renderMarkdown(comparison: HdfComparison, options?: RenderOptions): string; | ||
| //# sourceMappingURL=markdown.d.ts.map |
| {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/renderers/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAoHhD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,aAAa,EACzB,OAAO,CAAC,EAAE,aAAa,GACtB,MAAM,CAqCR"} |
| import { filterRequirements } from './filter.js'; | ||
| /** | ||
| * Render the summary table in markdown format. | ||
| */ | ||
| function renderSummaryTable(comparison) { | ||
| const { summary } = comparison; | ||
| const lines = [ | ||
| '## HDF Comparison Summary', | ||
| '', | ||
| '| Metric | Count |', | ||
| '|--------|-------|', | ||
| `| Fixed | ${summary.fixed} |`, | ||
| `| Regressed | ${summary.regressed} |`, | ||
| `| New | ${summary.new} |`, | ||
| `| Absent | ${summary.absent} |`, | ||
| `| Unchanged | ${summary.unchanged} |`, | ||
| `| Updated | ${summary.updated} |`, | ||
| `| **Total** | **${summary.total}** |`, | ||
| ]; | ||
| return lines.join('\n'); | ||
| } | ||
| /** | ||
| * Group requirement diffs by state. | ||
| */ | ||
| function groupByState(diffs) { | ||
| const groups = new Map(); | ||
| for (const diff of diffs) { | ||
| const existing = groups.get(diff.state); | ||
| if (existing) { | ||
| existing.push(diff); | ||
| } | ||
| else { | ||
| groups.set(diff.state, [diff]); | ||
| } | ||
| } | ||
| return groups; | ||
| } | ||
| /** | ||
| * Escape a value for use in a markdown table cell. | ||
| * Replaces pipe characters with their HTML entity. | ||
| */ | ||
| function escapeCell(value) { | ||
| return value.replace(/\|/g, '|'); | ||
| } | ||
| /** | ||
| * Format field changes for a markdown cell. | ||
| */ | ||
| function formatFieldChanges(req) { | ||
| if (req.fieldChanges.length === 0) | ||
| return ''; | ||
| return req.fieldChanges | ||
| .map((fc) => { | ||
| if (fc.op === 'add') | ||
| return `+${fc.path}: ${JSON.stringify(fc.newValue)}`; | ||
| if (fc.op === 'remove') | ||
| return `-${fc.path}: ${JSON.stringify(fc.oldValue)}`; | ||
| return `${fc.path}: ${JSON.stringify(fc.oldValue)} -> ${JSON.stringify(fc.newValue)}`; | ||
| }) | ||
| .join('; '); | ||
| } | ||
| /** | ||
| * Render a section for a single state group. | ||
| */ | ||
| function renderStateSection(state, diffs, detail) { | ||
| const label = state.charAt(0).toUpperCase() + state.slice(1); | ||
| const lines = [`### ${label} (${diffs.length})`]; | ||
| if (diffs.length === 0) { | ||
| lines.push('', '(none)'); | ||
| return lines.join('\n'); | ||
| } | ||
| lines.push(''); | ||
| if (detail === 'full') { | ||
| lines.push('| ID | Title | Old Status | New Status | Change Reasons | Field Changes |', '|----|-------|------------|------------|----------------|---------------|'); | ||
| for (const req of diffs) { | ||
| const id = escapeCell(req.id); | ||
| const title = escapeCell(req.title ?? ''); | ||
| const oldStatus = escapeCell(req.oldEffectiveStatus ?? ''); | ||
| const newStatus = escapeCell(req.newEffectiveStatus ?? ''); | ||
| const reasons = escapeCell(req.changeReasons.join(', ')); | ||
| const fieldChanges = escapeCell(formatFieldChanges(req)); | ||
| lines.push(`| ${id} | ${title} | ${oldStatus} | ${newStatus} | ${reasons} | ${fieldChanges} |`); | ||
| } | ||
| } | ||
| else { | ||
| lines.push('| ID | Title | Old Status | New Status |', '|----|-------|------------|------------|'); | ||
| for (const req of diffs) { | ||
| const id = escapeCell(req.id); | ||
| const title = escapeCell(req.title ?? ''); | ||
| const oldStatus = escapeCell(req.oldEffectiveStatus ?? ''); | ||
| const newStatus = escapeCell(req.newEffectiveStatus ?? ''); | ||
| lines.push(`| ${id} | ${title} | ${oldStatus} | ${newStatus} |`); | ||
| } | ||
| } | ||
| return lines.join('\n'); | ||
| } | ||
| /** | ||
| * Render an HdfComparison as a Markdown string. | ||
| * | ||
| * - `detail: 'summary'` -- summary table only | ||
| * - `detail: 'control'` -- summary + per-requirement tables by state | ||
| * - `detail: 'full'` -- summary + per-requirement tables with changeReasons and fieldChanges | ||
| * | ||
| * Default detail level: `'control'`. | ||
| */ | ||
| export function renderMarkdown(comparison, options) { | ||
| const detail = options?.detail ?? 'control'; | ||
| const parts = [renderSummaryTable(comparison)]; | ||
| if (detail === 'summary') { | ||
| return parts.join('\n'); | ||
| } | ||
| // Determine which states to display | ||
| const stateOrder = [ | ||
| 'fixed', | ||
| 'regressed', | ||
| 'new', | ||
| 'absent', | ||
| 'updated', | ||
| 'unchanged', | ||
| ]; | ||
| const filtered = filterRequirements(comparison.requirementDiffs, options); | ||
| const grouped = groupByState(filtered); | ||
| // If filtering by states, only show those specific states | ||
| const statesToShow = options?.filterStates && options.filterStates.length > 0 | ||
| ? stateOrder.filter((s) => options.filterStates.includes(s)) | ||
| : stateOrder; | ||
| for (const state of statesToShow) { | ||
| parts.push(''); | ||
| const diffs = grouped.get(state) ?? []; | ||
| parts.push(renderStateSection(state, diffs, detail)); | ||
| } | ||
| return parts.join('\n'); | ||
| } | ||
| //# sourceMappingURL=markdown.js.map |
| {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/renderers/markdown.ts"],"names":[],"mappings":"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"} |
| import type { HdfComparison } from '../types.js'; | ||
| import type { RenderOptions } from './types.js'; | ||
| /** | ||
| * Render an HdfComparison for terminal display with optional ANSI colors. | ||
| * | ||
| * - `detail: 'summary'` -- just the summary line | ||
| * - `detail: 'control'` -- requirement list + summary (excludes unchanged) | ||
| * - `detail: 'full'` -- all requirements including unchanged, with changeReasons and fieldChanges | ||
| * | ||
| * When `color: false`, no ANSI escape codes are emitted. | ||
| * Default detail level: `'control'`. Default color: `true`. | ||
| */ | ||
| export declare function renderTerminal(comparison: HdfComparison, options?: RenderOptions): string; | ||
| //# sourceMappingURL=terminal.d.ts.map |
| {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/renderers/terminal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAwKhD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,aAAa,EACzB,OAAO,CAAC,EAAE,aAAa,GACtB,MAAM,CA8BR"} |
| import { filterRequirements } from './filter.js'; | ||
| // ANSI color codes | ||
| const RESET = '\x1b[0m'; | ||
| const GREEN = '\x1b[32m'; | ||
| const RED = '\x1b[31m'; | ||
| const YELLOW = '\x1b[33m'; | ||
| const BOLD = '\x1b[1m'; | ||
| const DIM = '\x1b[2m'; | ||
| /** | ||
| * Get the symbol and color for a requirement state. | ||
| */ | ||
| function getSymbolAndColor(state, useColor) { | ||
| const identity = (s) => s; | ||
| switch (state) { | ||
| case 'fixed': | ||
| case 'new': | ||
| return { | ||
| symbol: '+', | ||
| colorFn: useColor ? (s) => `${GREEN}${s}${RESET}` : identity, | ||
| }; | ||
| case 'regressed': | ||
| case 'absent': | ||
| return { | ||
| symbol: '-', | ||
| colorFn: useColor ? (s) => `${RED}${s}${RESET}` : identity, | ||
| }; | ||
| case 'updated': | ||
| return { | ||
| symbol: '~', | ||
| colorFn: useColor ? (s) => `${YELLOW}${s}${RESET}` : identity, | ||
| }; | ||
| case 'unchanged': | ||
| default: | ||
| return { | ||
| symbol: ' ', | ||
| colorFn: useColor ? (s) => `${DIM}${s}${RESET}` : identity, | ||
| }; | ||
| } | ||
| } | ||
| /** | ||
| * Format the status transition for a requirement. | ||
| */ | ||
| function formatStatusTransition(req) { | ||
| if (req.state === 'new') { | ||
| return '(new)'; | ||
| } | ||
| if (req.state === 'absent') { | ||
| return '(absent)'; | ||
| } | ||
| const parts = []; | ||
| // Show status change if both exist | ||
| if (req.oldEffectiveStatus && req.newEffectiveStatus) { | ||
| parts.push(`${req.oldEffectiveStatus} → ${req.newEffectiveStatus}`); | ||
| } | ||
| // Add state label for non-obvious transitions | ||
| if (req.state !== 'unchanged') { | ||
| parts.push(`(${req.state})`); | ||
| } | ||
| return parts.join(' '); | ||
| } | ||
| /** | ||
| * Format field changes for full detail. | ||
| */ | ||
| function formatFieldChangesForTerminal(req) { | ||
| if (req.fieldChanges.length === 0) | ||
| return ''; | ||
| const parts = req.fieldChanges.map((fc) => { | ||
| if (fc.op === 'add') | ||
| return `+${fc.path}: ${JSON.stringify(fc.newValue)}`; | ||
| if (fc.op === 'remove') | ||
| return `-${fc.path}: ${JSON.stringify(fc.oldValue)}`; | ||
| return `${fc.path}: ${JSON.stringify(fc.oldValue)} → ${JSON.stringify(fc.newValue)}`; | ||
| }); | ||
| return parts.join('; '); | ||
| } | ||
| /** | ||
| * Render a single requirement line. | ||
| */ | ||
| function renderRequirementLine(req, detail, useColor) { | ||
| const { symbol, colorFn } = getSymbolAndColor(req.state, useColor); | ||
| const title = req.title ?? ''; | ||
| const transition = formatStatusTransition(req); | ||
| let line = ` ${symbol} ${req.id} ${title} ${transition}`; | ||
| if (detail === 'full') { | ||
| // Add change reasons | ||
| if (req.changeReasons.length > 0) { | ||
| line += ` [${req.changeReasons.join(', ')}]`; | ||
| } | ||
| // Add field changes | ||
| const fieldChanges = formatFieldChangesForTerminal(req); | ||
| if (fieldChanges) { | ||
| line += ` ${fieldChanges}`; | ||
| } | ||
| } | ||
| return colorFn(line); | ||
| } | ||
| /** | ||
| * Build the summary line. | ||
| */ | ||
| function buildSummaryLine(comparison, useColor) { | ||
| const { summary } = comparison; | ||
| const parts = [ | ||
| `${summary.fixed} fixed`, | ||
| `${summary.regressed} regressed`, | ||
| `${summary.new} new`, | ||
| `${summary.absent} absent`, | ||
| `${summary.unchanged} unchanged`, | ||
| `${summary.updated} updated`, | ||
| `(${summary.total} total)`, | ||
| ]; | ||
| const line = `Summary: ${parts.join(', ')}`; | ||
| if (useColor) { | ||
| return `${BOLD}${line}${RESET}`; | ||
| } | ||
| return line; | ||
| } | ||
| /** | ||
| * Build the header line. | ||
| */ | ||
| function buildHeaderLine(comparison, useColor) { | ||
| const mode = comparison.comparisonMode; | ||
| const oldSource = comparison.sources.find((s) => s.role === 'old' || s.role === 'golden' || s.role === 'reference'); | ||
| const newSource = comparison.sources.find((s) => s.role === 'new' || s.role === 'system'); | ||
| let header = `HDF Comparison: ${mode}`; | ||
| const oldTimestamp = oldSource?.assessmentTimestamp; | ||
| const newTimestamp = newSource?.assessmentTimestamp; | ||
| if (oldTimestamp && newTimestamp) { | ||
| // Format as date-only if possible, otherwise use full timestamp | ||
| // split() always returns at least one element, so [0] is safe | ||
| const oldDate = oldTimestamp.split('T')[0]; | ||
| const newDate = newTimestamp.split('T')[0]; | ||
| header += ` (${oldDate} → ${newDate})`; | ||
| } | ||
| if (useColor) { | ||
| return `${BOLD}${header}${RESET}`; | ||
| } | ||
| return header; | ||
| } | ||
| /** | ||
| * Render an HdfComparison for terminal display with optional ANSI colors. | ||
| * | ||
| * - `detail: 'summary'` -- just the summary line | ||
| * - `detail: 'control'` -- requirement list + summary (excludes unchanged) | ||
| * - `detail: 'full'` -- all requirements including unchanged, with changeReasons and fieldChanges | ||
| * | ||
| * When `color: false`, no ANSI escape codes are emitted. | ||
| * Default detail level: `'control'`. Default color: `true`. | ||
| */ | ||
| export function renderTerminal(comparison, options) { | ||
| const detail = options?.detail ?? 'control'; | ||
| const useColor = options?.color ?? true; | ||
| const lines = []; | ||
| // Header | ||
| lines.push(buildHeaderLine(comparison, useColor)); | ||
| lines.push(''); | ||
| if (detail === 'summary') { | ||
| lines.push(buildSummaryLine(comparison, useColor)); | ||
| return lines.join('\n'); | ||
| } | ||
| // Requirement lines | ||
| const filtered = filterRequirements(comparison.requirementDiffs, options); | ||
| for (const req of filtered) { | ||
| // In 'control' mode, skip unchanged requirements | ||
| if (detail === 'control' && req.state === 'unchanged') { | ||
| continue; | ||
| } | ||
| lines.push(renderRequirementLine(req, detail, useColor)); | ||
| } | ||
| lines.push(''); | ||
| lines.push(buildSummaryLine(comparison, useColor)); | ||
| return lines.join('\n'); | ||
| } | ||
| //# sourceMappingURL=terminal.js.map |
| {"version":3,"file":"terminal.js","sourceRoot":"","sources":["../../src/renderers/terminal.ts"],"names":[],"mappings":"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"} |
| export type DetailLevel = 'summary' | 'control' | 'full'; | ||
| export interface RenderOptions { | ||
| /** What detail to show. Default: 'control' */ | ||
| detail?: DetailLevel; | ||
| /** Only show requirements matching these states */ | ||
| filterStates?: string[]; | ||
| /** Only show requirements matching this severity */ | ||
| filterSeverity?: string; | ||
| /** Use color codes (for terminal). Default: true */ | ||
| color?: boolean; | ||
| } | ||
| //# sourceMappingURL=types.d.ts.map |
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/renderers/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC5B,8CAA8C;IAC9C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB"} |
| export {}; | ||
| //# sourceMappingURL=types.js.map |
| {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/renderers/types.ts"],"names":[],"mappings":""} |
| /** | ||
| * SBOM comparison for the HDF diff engine. | ||
| * | ||
| * Compares two SBOM documents (CycloneDX or SPDX JSON) and produces | ||
| * package-level diffs: added, removed, updated, unchanged packages. | ||
| * | ||
| * Format auto-detection: | ||
| * - CycloneDX: has `bomFormat: "CycloneDX"`, packages in `components[]` | ||
| * - SPDX: has `spdxVersion`, packages in `packages[]` | ||
| */ | ||
| /** | ||
| * The comparison result for a single package between two SBOMs. | ||
| */ | ||
| export interface PackageDiff { | ||
| purl: string; | ||
| name: string; | ||
| state: 'added' | 'removed' | 'updated' | 'unchanged'; | ||
| oldVersion?: string; | ||
| newVersion?: string; | ||
| licenses?: string[]; | ||
| } | ||
| /** | ||
| * The complete result of comparing two SBOM documents. | ||
| */ | ||
| export interface SbomDiffResult { | ||
| packageDiffs: PackageDiff[]; | ||
| added: number; | ||
| removed: number; | ||
| updated: number; | ||
| unchanged: number; | ||
| } | ||
| /** | ||
| * Compare two SBOM documents (CycloneDX or SPDX JSON strings) and return | ||
| * package-level diffs. | ||
| * | ||
| * @param oldJson - JSON string of the old SBOM document | ||
| * @param newJson - JSON string of the new SBOM document | ||
| * @returns Structured diff result with per-package state and aggregate counts | ||
| * @throws Error if either input is not valid JSON or not a recognized SBOM format | ||
| */ | ||
| export declare function diffSboms(oldJson: string, newJson: string): SbomDiffResult; | ||
| //# sourceMappingURL=sbom.d.ts.map |
| {"version":3,"file":"sbom.d.ts","sourceRoot":"","sources":["../src/sbom.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAYD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,CA2E1E"} |
-203
| /** | ||
| * SBOM comparison for the HDF diff engine. | ||
| * | ||
| * Compares two SBOM documents (CycloneDX or SPDX JSON) and produces | ||
| * package-level diffs: added, removed, updated, unchanged packages. | ||
| * | ||
| * Format auto-detection: | ||
| * - CycloneDX: has `bomFormat: "CycloneDX"`, packages in `components[]` | ||
| * - SPDX: has `spdxVersion`, packages in `packages[]` | ||
| */ | ||
| /** | ||
| * Compare two SBOM documents (CycloneDX or SPDX JSON strings) and return | ||
| * package-level diffs. | ||
| * | ||
| * @param oldJson - JSON string of the old SBOM document | ||
| * @param newJson - JSON string of the new SBOM document | ||
| * @returns Structured diff result with per-package state and aggregate counts | ||
| * @throws Error if either input is not valid JSON or not a recognized SBOM format | ||
| */ | ||
| export function diffSboms(oldJson, newJson) { | ||
| const oldParsed = JSON.parse(oldJson); | ||
| const newParsed = JSON.parse(newJson); | ||
| const oldPackages = extractPackages(oldParsed); | ||
| const newPackages = extractPackages(newParsed); | ||
| const oldMap = buildPackageMap(oldPackages); | ||
| const newMap = buildPackageMap(newPackages); | ||
| const diffs = []; | ||
| const seen = new Set(); | ||
| // Check old packages against new | ||
| for (const [key, oldPkg] of oldMap) { | ||
| seen.add(key); | ||
| const newPkg = newMap.get(key); | ||
| if (newPkg) { | ||
| if (oldPkg.version !== newPkg.version) { | ||
| diffs.push({ | ||
| purl: newPkg.purl || newPkg.name, | ||
| name: newPkg.name, | ||
| state: 'updated', | ||
| oldVersion: oldPkg.version, | ||
| newVersion: newPkg.version, | ||
| licenses: newPkg.licenses.length > 0 ? newPkg.licenses : undefined, | ||
| }); | ||
| } | ||
| else { | ||
| diffs.push({ | ||
| purl: oldPkg.purl || oldPkg.name, | ||
| name: oldPkg.name, | ||
| state: 'unchanged', | ||
| }); | ||
| } | ||
| } | ||
| else { | ||
| diffs.push({ | ||
| purl: oldPkg.purl || oldPkg.name, | ||
| name: oldPkg.name, | ||
| state: 'removed', | ||
| oldVersion: oldPkg.version, | ||
| }); | ||
| } | ||
| } | ||
| // Check for added packages | ||
| for (const [key, newPkg] of newMap) { | ||
| if (!seen.has(key)) { | ||
| diffs.push({ | ||
| purl: newPkg.purl || newPkg.name, | ||
| name: newPkg.name, | ||
| state: 'added', | ||
| newVersion: newPkg.version, | ||
| licenses: newPkg.licenses.length > 0 ? newPkg.licenses : undefined, | ||
| }); | ||
| } | ||
| } | ||
| // Sort by name for deterministic output | ||
| diffs.sort((a, b) => a.name.localeCompare(b.name)); | ||
| // Count states | ||
| let added = 0; | ||
| let removed = 0; | ||
| let updated = 0; | ||
| let unchanged = 0; | ||
| for (const d of diffs) { | ||
| switch (d.state) { | ||
| case 'added': | ||
| added++; | ||
| break; | ||
| case 'removed': | ||
| removed++; | ||
| break; | ||
| case 'updated': | ||
| updated++; | ||
| break; | ||
| case 'unchanged': | ||
| unchanged++; | ||
| break; | ||
| } | ||
| } | ||
| return { packageDiffs: diffs, added, removed, updated, unchanged }; | ||
| } | ||
| /** | ||
| * Extract packages from an SBOM document, auto-detecting the format. | ||
| */ | ||
| function extractPackages(doc) { | ||
| if (doc['bomFormat'] === 'CycloneDX') { | ||
| return extractCycloneDXPackages(doc); | ||
| } | ||
| if (typeof doc['spdxVersion'] === 'string') { | ||
| return extractSPDXPackages(doc); | ||
| } | ||
| throw new Error('Unrecognized SBOM format: expected CycloneDX (bomFormat) or SPDX (spdxVersion)'); | ||
| } | ||
| /** | ||
| * Extract packages from a CycloneDX document. | ||
| * Reads components[] with purl, name, version fields. | ||
| */ | ||
| function extractCycloneDXPackages(doc) { | ||
| const components = doc['components']; | ||
| if (!Array.isArray(components)) { | ||
| return []; | ||
| } | ||
| return components.map((comp) => { | ||
| const licenses = extractCycloneDXLicenses(comp); | ||
| return { | ||
| purl: comp['purl'] ?? '', | ||
| name: comp['name'] ?? '', | ||
| version: comp['version'] ?? '', | ||
| licenses, | ||
| }; | ||
| }); | ||
| } | ||
| /** | ||
| * Extract license strings from a CycloneDX component. | ||
| * CycloneDX licenses can be in `licenses[].license.id` or `licenses[].license.name`. | ||
| */ | ||
| function extractCycloneDXLicenses(comp) { | ||
| const licensesArr = comp['licenses']; | ||
| if (!Array.isArray(licensesArr)) { | ||
| return []; | ||
| } | ||
| const result = []; | ||
| for (const entry of licensesArr) { | ||
| const license = entry['license']; | ||
| if (license) { | ||
| const id = license['id']; | ||
| const name = license['name']; | ||
| if (id) { | ||
| result.push(id); | ||
| } | ||
| else if (name) { | ||
| result.push(name); | ||
| } | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| /** | ||
| * Extract packages from an SPDX document. | ||
| * Reads packages[] with name, versionInfo, and externalRefs (for PURL). | ||
| */ | ||
| function extractSPDXPackages(doc) { | ||
| const packages = doc['packages']; | ||
| if (!Array.isArray(packages)) { | ||
| return []; | ||
| } | ||
| return packages.map((pkg) => { | ||
| const purl = extractSPDXPurl(pkg); | ||
| const license = pkg['licenseConcluded'] ?? pkg['licenseDeclared'] ?? ''; | ||
| return { | ||
| purl, | ||
| name: pkg['name'] ?? '', | ||
| version: pkg['versionInfo'] ?? '', | ||
| licenses: license && license !== 'NOASSERTION' ? [license] : [], | ||
| }; | ||
| }); | ||
| } | ||
| /** | ||
| * Extract a PURL from an SPDX package's externalRefs array. | ||
| */ | ||
| function extractSPDXPurl(pkg) { | ||
| const refs = pkg['externalRefs']; | ||
| if (!Array.isArray(refs)) { | ||
| return ''; | ||
| } | ||
| for (const ref of refs) { | ||
| const refType = ref['referenceType']; | ||
| if (refType === 'purl') { | ||
| return ref['referenceLocator'] ?? ''; | ||
| } | ||
| } | ||
| return ''; | ||
| } | ||
| /** | ||
| * Build a lookup map of packages indexed by name (version-independent key). | ||
| */ | ||
| function buildPackageMap(packages) { | ||
| const map = new Map(); | ||
| for (const pkg of packages) { | ||
| const key = pkg.name || pkg.purl; | ||
| if (key) { | ||
| map.set(key, pkg); | ||
| } | ||
| } | ||
| return map; | ||
| } | ||
| //# sourceMappingURL=sbom.js.map |
| {"version":3,"file":"sbom.js","sourceRoot":"","sources":["../src/sbom.ts"],"names":[],"mappings":"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"} |
| import type { ChangeReason, RequirementState } from './types.js'; | ||
| /** | ||
| * Determine the effective status of a requirement from its results and overrides. | ||
| * | ||
| * Priority: | ||
| * 1. impact === 0 → notApplicable (regardless of results) | ||
| * 2. effectiveStatus field set (and no statusOverrides) → use it | ||
| * 3. Non-expired statusOverrides → use first non-expired | ||
| * 4. Aggregate results using worst-wins | ||
| * 5. Empty results → notReviewed | ||
| */ | ||
| export declare function computeEffectiveStatus(requirement: Record<string, unknown>, referenceTimestamp?: string): string; | ||
| /** | ||
| * Classify why the status changed between two requirements. | ||
| * Returns an array of change reasons (a status change can have multiple causes). | ||
| */ | ||
| export declare function classifyChangeReasons(oldReq: Record<string, unknown>, newReq: Record<string, unknown>, oldTimestamp?: string, newTimestamp?: string): ChangeReason[]; | ||
| /** | ||
| * Classify the overall diff status based on old and new effective statuses. | ||
| * | ||
| * - If old is failing and new is passing → 'fixed' | ||
| * - If old is passing and new is failing → 'regressed' | ||
| * - If statuses are equal → 'unchanged' | ||
| * - Otherwise → 'updated' | ||
| */ | ||
| export declare function classifyDiffStatus(oldEffectiveStatus: string, newEffectiveStatus: string): RequirementState; | ||
| //# sourceMappingURL=status.d.ts.map |
| {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AA0BjE;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,kBAAkB,CAAC,EAAE,MAAM,GAC1B,MAAM,CAyCR;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,YAAY,CAAC,EAAE,MAAM,EACrB,YAAY,CAAC,EAAE,MAAM,GACpB,YAAY,EAAE,CAuDhB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,kBAAkB,EAAE,MAAM,EAC1B,kBAAkB,EAAE,MAAM,GACzB,gBAAgB,CAkBlB"} |
-137
| /** Status severity ranking — higher index = worse */ | ||
| const STATUS_SEVERITY = [ | ||
| 'notApplicable', | ||
| 'notReviewed', | ||
| 'passed', | ||
| 'failed', | ||
| 'error', | ||
| ]; | ||
| /** Statuses that count as "passing" for fixed/regressed classification */ | ||
| const PASSING_STATUSES = new Set(['passed']); | ||
| /** Statuses that count as "failing" for fixed/regressed classification */ | ||
| const FAILING_STATUSES = new Set(['failed', 'error', 'notReviewed']); | ||
| /** | ||
| * Determine the effective status of a requirement from its results and overrides. | ||
| * | ||
| * Priority: | ||
| * 1. impact === 0 → notApplicable (regardless of results) | ||
| * 2. effectiveStatus field set (and no statusOverrides) → use it | ||
| * 3. Non-expired statusOverrides → use first non-expired | ||
| * 4. Aggregate results using worst-wins | ||
| * 5. Empty results → notReviewed | ||
| */ | ||
| export function computeEffectiveStatus(requirement, referenceTimestamp) { | ||
| const impact = requirement['impact']; | ||
| if (impact === 0) { | ||
| return 'notApplicable'; | ||
| } | ||
| const overrides = requirement['statusOverrides']; | ||
| if (overrides && overrides.length > 0) { | ||
| const refTime = referenceTimestamp ? new Date(referenceTimestamp).getTime() : Date.now(); | ||
| for (const override of overrides) { | ||
| const expiresAt = new Date(override.expiresAt).getTime(); | ||
| if (expiresAt > refTime) { | ||
| return override.status; | ||
| } | ||
| } | ||
| // All overrides expired — fall through to results | ||
| } | ||
| // If effectiveStatus is set and there are no overrides, use it | ||
| const effectiveStatus = requirement['effectiveStatus']; | ||
| if (effectiveStatus && (!overrides || overrides.length === 0)) { | ||
| return effectiveStatus; | ||
| } | ||
| const results = requirement['results']; | ||
| if (!results || results.length === 0) { | ||
| return 'notReviewed'; | ||
| } | ||
| // Worst-wins: find the status with the highest severity index | ||
| let worstIndex = -1; | ||
| let worstStatus = 'notReviewed'; | ||
| for (const result of results) { | ||
| const idx = STATUS_SEVERITY.indexOf(result.status); | ||
| if (idx > worstIndex) { | ||
| worstIndex = idx; | ||
| worstStatus = result.status; | ||
| } | ||
| } | ||
| return worstStatus; | ||
| } | ||
| /** | ||
| * Classify why the status changed between two requirements. | ||
| * Returns an array of change reasons (a status change can have multiple causes). | ||
| */ | ||
| export function classifyChangeReasons(oldReq, newReq, oldTimestamp, newTimestamp) { | ||
| const reasons = []; | ||
| // Check result status changes | ||
| const oldResults = oldReq['results'] ?? []; | ||
| const newResults = newReq['results'] ?? []; | ||
| const oldResultStatuses = oldResults.map((r) => r.status).sort(); | ||
| const newResultStatuses = newResults.map((r) => r.status).sort(); | ||
| if (JSON.stringify(oldResultStatuses) !== JSON.stringify(newResultStatuses)) { | ||
| reasons.push('resultChanged'); | ||
| } | ||
| // Check override changes | ||
| const oldOverrides = oldReq['statusOverrides'] ?? []; | ||
| const newOverrides = newReq['statusOverrides'] ?? []; | ||
| if (newOverrides.length > oldOverrides.length) { | ||
| reasons.push('overrideAdded'); | ||
| } | ||
| else if (newOverrides.length < oldOverrides.length) { | ||
| reasons.push('overrideRemoved'); | ||
| } | ||
| // Check for override expiration between scans | ||
| if (oldTimestamp && newTimestamp && oldOverrides.length > 0) { | ||
| const oldTime = new Date(oldTimestamp).getTime(); | ||
| const newTime = new Date(newTimestamp).getTime(); | ||
| for (const override of oldOverrides) { | ||
| const expiresAt = new Date(override.expiresAt).getTime(); | ||
| if (expiresAt > oldTime && expiresAt <= newTime) { | ||
| reasons.push('overrideExpired'); | ||
| break; // Only report once | ||
| } | ||
| } | ||
| } | ||
| // Check impact changes | ||
| const oldImpact = oldReq['impact']; | ||
| const newImpact = newReq['impact']; | ||
| if (oldImpact !== newImpact) { | ||
| reasons.push('impactChanged'); | ||
| } | ||
| // Check baseline metadata changes (tags, descriptions, title) | ||
| const oldTags = JSON.stringify(oldReq['tags'] ?? {}); | ||
| const newTags = JSON.stringify(newReq['tags'] ?? {}); | ||
| const oldDescs = JSON.stringify(oldReq['descriptions'] ?? []); | ||
| const newDescs = JSON.stringify(newReq['descriptions'] ?? []); | ||
| const oldTitle = oldReq['title']; | ||
| const newTitle = newReq['title']; | ||
| if (oldTags !== newTags || oldDescs !== newDescs || oldTitle !== newTitle) { | ||
| reasons.push('metadataChanged'); | ||
| } | ||
| return reasons; | ||
| } | ||
| /** | ||
| * Classify the overall diff status based on old and new effective statuses. | ||
| * | ||
| * - If old is failing and new is passing → 'fixed' | ||
| * - If old is passing and new is failing → 'regressed' | ||
| * - If statuses are equal → 'unchanged' | ||
| * - Otherwise → 'updated' | ||
| */ | ||
| export function classifyDiffStatus(oldEffectiveStatus, newEffectiveStatus) { | ||
| if (oldEffectiveStatus === newEffectiveStatus) { | ||
| return 'unchanged'; | ||
| } | ||
| const oldIsFailing = FAILING_STATUSES.has(oldEffectiveStatus); | ||
| const newIsPassing = PASSING_STATUSES.has(newEffectiveStatus); | ||
| const oldIsPassing = PASSING_STATUSES.has(oldEffectiveStatus); | ||
| const newIsFailing = FAILING_STATUSES.has(newEffectiveStatus); | ||
| if (oldIsFailing && newIsPassing) { | ||
| return 'fixed'; | ||
| } | ||
| if (oldIsPassing && newIsFailing) { | ||
| return 'regressed'; | ||
| } | ||
| return 'updated'; | ||
| } | ||
| //# sourceMappingURL=status.js.map |
| {"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"} |
| import type { ComparisonSummary, RequirementDiff } from './types.js'; | ||
| /** | ||
| * Compute summary counts from an array of RequirementDiff entries. | ||
| */ | ||
| export declare function computeSummary(requirements: RequirementDiff[]): ComparisonSummary; | ||
| //# sourceMappingURL=summary.d.ts.map |
| {"version":3,"file":"summary.d.ts","sourceRoot":"","sources":["../src/summary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAErE;;GAEG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,eAAe,EAAE,GAAG,iBAAiB,CA4CjF"} |
| /** | ||
| * Compute summary counts from an array of RequirementDiff entries. | ||
| */ | ||
| export function computeSummary(requirements) { | ||
| const summary = { | ||
| fixed: 0, | ||
| regressed: 0, | ||
| new: 0, | ||
| absent: 0, | ||
| unchanged: 0, | ||
| updated: 0, | ||
| total: requirements.length, | ||
| matchedCount: 0, | ||
| unmatchedOldCount: 0, | ||
| unmatchedNewCount: 0, | ||
| }; | ||
| for (const req of requirements) { | ||
| switch (req.state) { | ||
| case 'fixed': | ||
| summary.fixed++; | ||
| summary.matchedCount++; | ||
| break; | ||
| case 'regressed': | ||
| summary.regressed++; | ||
| summary.matchedCount++; | ||
| break; | ||
| case 'new': | ||
| summary.new++; | ||
| summary.unmatchedNewCount++; | ||
| break; | ||
| case 'absent': | ||
| summary.absent++; | ||
| summary.unmatchedOldCount++; | ||
| break; | ||
| case 'unchanged': | ||
| summary.unchanged++; | ||
| summary.matchedCount++; | ||
| break; | ||
| case 'updated': | ||
| summary.updated++; | ||
| summary.matchedCount++; | ||
| break; | ||
| } | ||
| } | ||
| return summary; | ||
| } | ||
| //# sourceMappingURL=summary.js.map |
| {"version":3,"file":"summary.js","sourceRoot":"","sources":["../src/summary.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,YAA+B;IAC5D,MAAM,OAAO,GAAsB;QACjC,KAAK,EAAE,CAAC;QACR,SAAS,EAAE,CAAC;QACZ,GAAG,EAAE,CAAC;QACN,MAAM,EAAE,CAAC;QACT,SAAS,EAAE,CAAC;QACZ,OAAO,EAAE,CAAC;QACV,KAAK,EAAE,YAAY,CAAC,MAAM;QAC1B,YAAY,EAAE,CAAC;QACf,iBAAiB,EAAE,CAAC;QACpB,iBAAiB,EAAE,CAAC;KACrB,CAAC;IAEF,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,QAAQ,GAAG,CAAC,KAAK,EAAE,CAAC;YAClB,KAAK,OAAO;gBACV,OAAO,CAAC,KAAK,EAAE,CAAC;gBAChB,OAAO,CAAC,YAAY,EAAE,CAAC;gBACvB,MAAM;YACR,KAAK,WAAW;gBACd,OAAO,CAAC,SAAS,EAAE,CAAC;gBACpB,OAAO,CAAC,YAAY,EAAE,CAAC;gBACvB,MAAM;YACR,KAAK,KAAK;gBACR,OAAO,CAAC,GAAG,EAAE,CAAC;gBACd,OAAO,CAAC,iBAAiB,EAAE,CAAC;gBAC5B,MAAM;YACR,KAAK,QAAQ;gBACX,OAAO,CAAC,MAAM,EAAE,CAAC;gBACjB,OAAO,CAAC,iBAAiB,EAAE,CAAC;gBAC5B,MAAM;YACR,KAAK,WAAW;gBACd,OAAO,CAAC,SAAS,EAAE,CAAC;gBACpB,OAAO,CAAC,YAAY,EAAE,CAAC;gBACvB,MAAM;YACR,KAAK,SAAS;gBACZ,OAAO,CAAC,OAAO,EAAE,CAAC;gBAClB,OAAO,CAAC,YAAY,EAAE,CAAC;gBACvB,MAAM;QACV,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"} |
-208
| /** | ||
| * Why a requirement's effective status changed between two evaluations. | ||
| * | ||
| * Aligned with the hdf-comparison schema's Change_Reason enum: | ||
| * - resultChanged: The underlying test results differ (e.g., a fix was deployed) | ||
| * - overrideAdded: A new statusOverride (waiver/attestation) was added | ||
| * - overrideExpired: A statusOverride present in the old scan has expired by the new scan's timestamp | ||
| * - overrideRemoved: A statusOverride was removed between scans | ||
| * - overrideModified: An existing statusOverride was modified (reserved for future use) | ||
| * - impactChanged: The impact score changed (e.g., from 0.7 to 0.0 means N/A) | ||
| * - baselineUpgraded: The baseline version changed (reserved for future use) | ||
| * - controlMapped: A control was mapped to a different framework (reserved for future use) | ||
| * - scannerChanged: A different scanner was used (reserved for future use) | ||
| * - targetChanged: The scan target changed (reserved for future use) | ||
| * - configChanged: The scan configuration changed (reserved for future use) | ||
| * - metadataChanged: Non-impact baseline metadata changed (tags, descriptions, title, etc.) | ||
| */ | ||
| export type ChangeReason = 'resultChanged' | 'overrideAdded' | 'overrideExpired' | 'overrideRemoved' | 'overrideModified' | 'impactChanged' | 'baselineUpgraded' | 'controlMapped' | 'scannerChanged' | 'targetChanged' | 'configChanged' | 'metadataChanged'; | ||
| /** | ||
| * Classification of how a requirement's state changed between evaluations. | ||
| * Uses SARIF-inspired vocabulary. | ||
| * | ||
| * - new: Requirement exists only in the new evaluation (was "added") | ||
| * - absent: Requirement exists only in the old evaluation (was "removed") | ||
| * - unchanged: Same effective status in both evaluations | ||
| * - updated: Status changed but doesn't fit fixed/regressed (was "changed") | ||
| * - fixed: Was failing/error, now passing | ||
| * - regressed: Was passing, now failing/error | ||
| * - moved: Requirement ID changed but content is the same (future use) | ||
| * - split: One requirement became multiple (future use) | ||
| * - merged: Multiple requirements became one (future use) | ||
| */ | ||
| export type RequirementState = 'new' | 'absent' | 'unchanged' | 'updated' | 'fixed' | 'regressed' | 'moved' | 'split' | 'merged'; | ||
| /** | ||
| * A field-level difference on a requirement, following JSON Patch-like conventions. | ||
| */ | ||
| export interface FieldChange { | ||
| /** The operation type: add, remove, or replace */ | ||
| op: 'add' | 'remove' | 'replace'; | ||
| /** Dot-notation path to the changed field (e.g., 'impact', 'tags.cci') */ | ||
| path: string; | ||
| /** Value in the old evaluation (undefined for 'add' operations) */ | ||
| oldValue?: unknown; | ||
| /** Value in the new evaluation (undefined for 'remove' operations) */ | ||
| newValue?: unknown; | ||
| } | ||
| /** | ||
| * The diff for a single requirement across two evaluations. | ||
| */ | ||
| export interface RequirementDiff { | ||
| /** The requirement ID (e.g., 'SV-238196') */ | ||
| id: string; | ||
| /** Classification of the change */ | ||
| state: RequirementState; | ||
| /** Why the status changed — empty array if unchanged */ | ||
| changeReasons: ChangeReason[]; | ||
| /** Full snapshot of the requirement from the old evaluation (null when state = 'new') */ | ||
| before: Record<string, unknown> | null; | ||
| /** Full snapshot of the requirement from the new evaluation (null when state = 'absent') */ | ||
| after: Record<string, unknown> | null; | ||
| /** The requirement title (from whichever evaluation has it) */ | ||
| title?: string; | ||
| /** Effective status in the old evaluation (undefined if new) */ | ||
| oldEffectiveStatus?: string; | ||
| /** Effective status in the new evaluation (undefined if absent) */ | ||
| newEffectiveStatus?: string; | ||
| /** Impact in the old evaluation */ | ||
| oldImpact?: number; | ||
| /** Impact in the new evaluation */ | ||
| newImpact?: number; | ||
| /** Field-level diffs for non-status fields */ | ||
| fieldChanges: FieldChange[]; | ||
| /** The matching strategy that paired this requirement (e.g., 'exactId') */ | ||
| matchStrategy?: string; | ||
| /** Confidence of the match (0.0-1.0) */ | ||
| matchConfidence?: number; | ||
| /** Index into the sources array for fleet mode */ | ||
| sourceIndex?: number; | ||
| } | ||
| /** | ||
| * Summary counts for the comparison. | ||
| */ | ||
| export interface ComparisonSummary { | ||
| /** Requirements that went from failing/error to passing */ | ||
| fixed: number; | ||
| /** Requirements that went from passing to failing/error */ | ||
| regressed: number; | ||
| /** Requirements present only in the new evaluation (was "added") */ | ||
| new: number; | ||
| /** Requirements present only in the old evaluation (was "removed") */ | ||
| absent: number; | ||
| /** Requirements with the same effective status */ | ||
| unchanged: number; | ||
| /** Requirements whose status changed in a way other than fixed/regressed (was "changed") */ | ||
| updated: number; | ||
| /** Total unique requirements across both evaluations */ | ||
| total: number; | ||
| /** Number of requirements matched between old and new */ | ||
| matchedCount: number; | ||
| /** Number of requirements only in old (unmatched) */ | ||
| unmatchedOldCount: number; | ||
| /** Number of requirements only in new (unmatched) */ | ||
| unmatchedNewCount: number; | ||
| } | ||
| /** | ||
| * The diff for a single component across two system documents. | ||
| * Used in systemDrift comparison mode. | ||
| */ | ||
| export interface ComponentDiff { | ||
| /** Component name */ | ||
| name: string; | ||
| /** Classification of the change: new, absent, unchanged, or updated */ | ||
| state: 'new' | 'absent' | 'unchanged' | 'updated'; | ||
| /** Component snapshot from the old system (null when state = 'new') */ | ||
| before: Record<string, unknown> | null; | ||
| /** Component snapshot from the new system (null when state = 'absent') */ | ||
| after: Record<string, unknown> | null; | ||
| /** Field-level diffs between old and new component */ | ||
| fieldChanges: FieldChange[]; | ||
| } | ||
| /** | ||
| * The diff for a single baseline across two evaluations. | ||
| */ | ||
| export interface BaselineDiff { | ||
| /** Baseline name */ | ||
| name: string; | ||
| /** Version in the old evaluation */ | ||
| oldVersion?: string; | ||
| /** Version in the new evaluation */ | ||
| newVersion?: string; | ||
| /** Whether this baseline was new, absent, updated, or unchanged */ | ||
| state: 'new' | 'absent' | 'updated' | 'unchanged'; | ||
| } | ||
| /** | ||
| * Metadata about a source document used in the comparison. | ||
| */ | ||
| export interface Source { | ||
| /** Role of the source in the comparison */ | ||
| role: 'old' | 'new' | 'golden' | 'reference' | 'system'; | ||
| /** Human-readable label */ | ||
| label: string; | ||
| /** URI to the source document */ | ||
| uri?: string; | ||
| /** Original format of the source (e.g., 'hdf-results-v2', 'inspec-exec-json-v1') */ | ||
| originalFormat?: string; | ||
| /** Assessment timestamp from the source document */ | ||
| assessmentTimestamp?: string; | ||
| } | ||
| /** | ||
| * An annotation attached to a requirement diff. | ||
| */ | ||
| export interface Annotation { | ||
| /** Human-readable label */ | ||
| label: string; | ||
| /** Description of the annotation */ | ||
| text: string; | ||
| /** When the annotation was created */ | ||
| timestamp?: string; | ||
| } | ||
| /** | ||
| * The top-level comparison result comparing two or more HDF evaluations. | ||
| */ | ||
| export interface HdfComparison { | ||
| /** Schema version for the comparison format */ | ||
| formatVersion: '1.0.0'; | ||
| /** The mode of comparison */ | ||
| comparisonMode: 'temporal' | 'baseline' | 'fleet' | 'multiSource' | 'baselineEvolution' | 'systemDrift'; | ||
| /** When the comparison was generated */ | ||
| timestamp?: string; | ||
| /** Source documents used in the comparison */ | ||
| sources: Source[]; | ||
| /** Matching configuration used */ | ||
| matching?: MatchingConfig; | ||
| /** Aggregate counts */ | ||
| summary: ComparisonSummary; | ||
| /** Per-baseline diffs */ | ||
| baselineDiffs: BaselineDiff[]; | ||
| /** Per-requirement diffs, sorted by id */ | ||
| requirementDiffs: RequirementDiff[]; | ||
| /** Per-component diffs (systemDrift mode only) */ | ||
| componentDiffs?: ComponentDiff[]; | ||
| /** Per-package diffs from embedded SBOM comparison (systemDrift mode only) */ | ||
| packageDiffs?: import('./sbom.js').PackageDiff[]; | ||
| /** URI identifying the system being compared (systemDrift mode only) */ | ||
| systemRef?: string; | ||
| /** Requirements that drifted from a golden baseline (future use) */ | ||
| drift?: RequirementDiff[]; | ||
| /** Annotations keyed by requirement ID */ | ||
| annotations?: Record<string, Annotation>; | ||
| /** Extension data for custom integrations */ | ||
| extensions?: Record<string, unknown>; | ||
| } | ||
| /** | ||
| * Configuration for how requirements are matched between evaluations. | ||
| */ | ||
| export interface MatchingConfig { | ||
| /** The primary strategy used for matching requirements across sources */ | ||
| primaryStrategy: string; | ||
| /** Minimum confidence threshold for a match */ | ||
| confidenceThreshold?: number; | ||
| } | ||
| /** @deprecated Use RequirementState instead */ | ||
| export type DiffStatus = RequirementState; | ||
| /** @deprecated Use ComparisonSummary instead */ | ||
| export type DiffSummary = ComparisonSummary; | ||
| /** @deprecated Use HdfComparison instead */ | ||
| export type HdfDiff = HdfComparison; | ||
| //# sourceMappingURL=types.d.ts.map |
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"} |
| export {}; | ||
| //# sourceMappingURL=types.js.map |
| {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""} |
| /** | ||
| * Result of validating a document against the hdf-comparison schema. | ||
| */ | ||
| export interface ValidationResult { | ||
| /** Whether the document conforms to the schema */ | ||
| valid: boolean; | ||
| /** Human-readable error messages (only present when valid is false) */ | ||
| errors?: string[]; | ||
| } | ||
| /** | ||
| * Validate a document against the hdf-comparison schema. | ||
| * | ||
| * Delegates to @mitre/hdf-validators which loads schemas from embedded | ||
| * bundled JSON (no filesystem access, no hardcoded version URLs). | ||
| * | ||
| * @param doc - The document to validate (typically the output of `diffHdf()`) | ||
| * @returns Validation result with `valid` boolean and optional `errors` array | ||
| */ | ||
| export declare function validateComparison(doc: unknown): ValidationResult; | ||
| //# sourceMappingURL=validate.d.ts.map |
| {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,KAAK,EAAE,OAAO,CAAC;IACf,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,CAWjE"} |
| import { validateComparison as validatorsValidateComparison } from '@mitre/hdf-validators'; | ||
| /** | ||
| * Validate a document against the hdf-comparison schema. | ||
| * | ||
| * Delegates to @mitre/hdf-validators which loads schemas from embedded | ||
| * bundled JSON (no filesystem access, no hardcoded version URLs). | ||
| * | ||
| * @param doc - The document to validate (typically the output of `diffHdf()`) | ||
| * @returns Validation result with `valid` boolean and optional `errors` array | ||
| */ | ||
| export function validateComparison(doc) { | ||
| const result = validatorsValidateComparison(doc); | ||
| if (result.valid) { | ||
| return { valid: true }; | ||
| } | ||
| const errors = result.errors.map(e => e.field === '(root)' ? e.message : `${e.field}: ${e.message}`); | ||
| return { valid: false, errors }; | ||
| } | ||
| //# sourceMappingURL=validate.js.map |
| {"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,IAAI,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAY3F;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAY;IAC7C,MAAM,MAAM,GAAG,4BAA4B,CAAC,GAAG,CAAC,CAAC;IAEjD,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACzB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CACnC,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,OAAO,EAAE,CAC9D,CAAC;IACF,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAClC,CAAC"} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
Unidentified License
LicenseSomething that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
235225
12.43%120
36.36%7
-92.22%1
Infinity%80
-20%2665
-11.78%2
100%1
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added