diff --git a/.changeset/a-withdrawn-scan-target-is-refused-not-cleared.md b/.changeset/a-withdrawn-scan-target-is-refused-not-cleared.md deleted file mode 100644 index c99b9f0..0000000 --- a/.changeset/a-withdrawn-scan-target-is-refused-not-cleared.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -"@cosyte/astm": patch ---- - -**The PHI scanner now refuses a run that enumerated a target and never read it, and the install -settings that hold a resolution back are in force rather than merely written down.** No published -value, type, warning code or emitted byte moves: this is repository tooling and supply chain -configuration only. - -Changed: - -- `scripts/phi-scan.ts` reconciles the targets a run ENUMERATED against the ones it actually READ, - on every route rather than on the whole-tree sweep alone. The whole-file bypass is subtracted - from each route's finished target list, so before this a run naming two paths and withdrawing one - opened one file and answered for both, at the same exit code the same arguments produce over a - corpus whose only violator is the withdrawn one. A caller could not tell those two runs apart. - The run now exits with the scanner's invocation-error code, names every path nobody opened, and - still prints any hit it had already found, because a refusal must not swallow a real finding. - Detection is untouched: the loci it reads, the format-agnostic floor and the allow list are all - exactly as they were, and this is the enumeration half again. -- A run that passes no bypass is byte-identical on all three routes, whole tree, staged files and - named paths, which is asserted against a copy of the scanner without the reconciliation rather - than assumed. -- `pnpm-workspace.yaml` declares a 1440 minute minimum release age and a no-downgrade trust policy, - and the pinned package manager moves to a release that enforces both. An older one ignores the - keys entirely, which is a settings file that decorates rather than defends, so the two changes - are one control. A refusal to install because no version in a range is old enough is that control - working and is never answered by lowering the number. -- The `js-yaml` resolution override is superseded rather than joined: the stale selector pinned a - lower version over an overlapping range, so it is replaced by one covering the whole advisory - range. The 3.x resolution reached through the release tooling is a known residual and is - deliberately left where it is. diff --git a/.changeset/first-minor-release.md b/.changeset/first-minor-release.md deleted file mode 100644 index eebd9fc..0000000 --- a/.changeset/first-minor-release.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -"@cosyte/astm": minor ---- - -**This is 0.1.0, the first release whose public API we treat as settled.** - -What is covered, and what you can build against: - -- Reading ASTM E1394 (CLSI LIS02-A2) records: `H`, `P`, `O`, `R`, `C`, `Q`, `M`, `S` and `L`, with - the delimiters each header declares, escape sequences decoded before a value is split, values and - units surfaced exactly as sent, abnormal flags and result status interpreted without guessing, and - every tolerated deviation reported as a stable warning code instead of a thrown error. -- Framing and transport per ASTM E1381 (CLSI LIS01-A2): checksum-verified frame decoding and - composing, the 240-byte split, framed versus raw detection, and a pure receiver state machine for - the `ENQ`/`ACK`/`NAK`/`EOT` exchange that never acknowledges a frame it could not verify. -- Emitting spec-clean records and frames (`buildAstmMessage`, `serializeAstmRecords`, - `serializeFramedAstm`), which write only the values you supply and never a default clinical value. -- Mapping analyzer local codes to LOINC through a LIVD catalog you supply, and date conversions - (`toObject`, `toISO`, `toDate`) that never assume a timezone. - -What the version promises. The exported names, options, return shapes and warning codes are the -surface we keep stable. While the package is below 1.0, a breaking change bumps the minor version -(0.1 to 0.2) and is called out in this changelog with its migration; a fix that changes no public -value ships as a patch. Upgrading from 0.0.x is itself breaking in the LIVD mapping and in what -`primaryCode()` returns: read the entries below before you upgrade. - -What is not covered yet. No named per-vendor profile ships: the built-in set is `default` and -`referenceCorpus`. No LOINC, SNOMED or LIVD data is bundled, and no LOINC is validated. The -transfer protocol's timers (contention, timeouts, retransmit) are yours to drive. The wire is read as -Latin-1. Three behaviors (forward scoping of redeclared delimiters, the Latin-1 encoding and the -reserved-byte set) are reasoned from this package's reader rather than cited to the standard's -purchase-gated text. diff --git a/.changeset/livd-catalog-answers-the-analyte.md b/.changeset/livd-catalog-answers-the-analyte.md deleted file mode 100644 index e6f3d13..0000000 --- a/.changeset/livd-catalog-answers-the-analyte.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -"@cosyte/astm": minor ---- - -**BREAKING: a populated first component no longer bypasses your LIVD catalog, and is no longer reported as a LOINC.** The Universal Test ID's first component is a LOINC slot that the governing mapping guide puts out of scope for instrument traffic: the analyte arrives as a vendor-defined code, and a LOINC is what a consumer maps to afterwards. This package used to answer the analyte-identity question from that slot instead, tagging any non-empty first component as an inline LOINC candidate with no evidence at all and returning it without ever consulting the catalog, so a record reading `R|1|Glucose|28.6|U/L||N||F` reported a LOINC candidate of `Glucose`. That is defect 9 in this repo's register, and it is closed here. - -The catalog is now consulted whenever a vendor local code is present, keyed on that code alone, and a first-component value is carried verbatim as a value this library does not vouch for. No shape test was added and none is coming: this package performs **no LOINC validation of any kind**, so `2345-7` and `Glucose` in component 1 are treated identically and the routing is positional throughout. - -Public values REMOVED or RENAMED, with their replacements: - -- `UniversalTestId.loincCandidate` is **removed**. Its replacement is `UniversalTestId.unvalidatedWireValue`: the same verbatim component 1 value, under a name that vouches for nothing. -- The `"inline-loinc-candidate"` provenance token is **removed** from `UniversalTestIdProvenance`. A record with a populated component 1 **and** a vendor local code is now `"local-code"`, because the local code is the identifier. A record with a populated component 1 and **no** vendor local code is the new token `"unvalidated-wire-value-only"`, which reports that the value was seen without claiming it is a code. -- The `LivdMapping` variant `{ status: "inline-loinc", loinc, source: "wire" }` is **removed**. There is no disposition that reports a wire value as a LOINC. Its positional replacement is the new variant `{ status: "no-vendor-code" }`, which says a populated component 1 arrived with no vendor local code to look up. The four retained dispositions (`mapped`, `unmapped`, `ambiguous`, `no-code`) keep their documented meanings exactly; only which inputs reach them changed. - -Public values whose BEHAVIOR changed while keeping their name: - -- `primaryCode()` now returns the vendor local code and nothing else. Where it used to answer with a first-component value it now returns `undefined`, which is the same absence it has always returned for a record carrying nothing usable, and which its callers already had to handle. This is a breaking change with no compile error behind it: audit every call site. The fail-safe direction is deliberate, it now reports no key rather than a key the library cannot vouch for. - -Added: - -- `LivdAnnotation.unvalidatedWireValue`: the verbatim first-component value, present on **every** disposition and absent only when component 1 is empty. -- `LivdAnnotation.wireValueDisagreesWithCatalog`: `true` if and only if the catalog vouched for exactly one LOINC, component 1 is populated, and the two are not byte-identical. `false` in every other case, never absent. It reports the difference and nothing else: both values stay surfaced, neither is marked correct, and no field says the difference was settled. -- `LivdAnnotation.reportedCode` is now always the code the catalog was actually consulted with, and is absent when nothing was looked up. - -Two fail-safe corners are now stated and tested: a consumer catalog whose `lookup` throws propagates unchanged rather than reading as a catalog miss, and a hit whose LOINC is a zero-length string is reported as a miss rather than as a vouched-for empty LOINC. diff --git a/.changeset/livd-catalog-says-which-loinc-version.md b/.changeset/livd-catalog-says-which-loinc-version.md deleted file mode 100644 index 03d17e7..0000000 --- a/.changeset/livd-catalog-says-which-loinc-version.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -"@cosyte/astm": minor ---- - -**A LIVD catalog can now say which publication it came from and which LOINC version the mapping was made against, and that version rides on every annotation.** A consumer could record the vendor rows and nothing about the publication behind them, so a mapping this library annotated was not reproducible (nothing said which version of LOINC it was made against) and the attribution statement the LOINC license requires had nowhere to live beside the mappings it applies to. Four optional publication-level elements now ride on the catalog itself, and the LOINC version rides onto every annotation the catalog produces. - -Added: - -- `LivdPublication`, supplied as an **optional second argument** to `defineLivdCatalog` and readable back as `LivdCatalog.publication`: `publisher`, `publicationVersion`, `loincVersion` and `loincCopyright`, every one of them optional. Each declared value is preserved **verbatim**, with no trimming, case folding, reordering, reformatting or defaulting. A value that is absent, empty or whitespace only is one case, no value declared, and is stored as absent rather than as a blank. -- `LivdAnnotation.catalogLoincVersion`: the catalog's declared LOINC version, carried on **every** annotation that catalog produced, whatever the lookup answered (`mapped`, `unmapped`, `ambiguous`, `no-vendor-code`, `no-code`). It is provenance about the CATALOG, and no field says any LOINC was checked against it, because none was. -- `ASTM_LIVD_CATALOG_NO_LOINC_VERSION` in `LIVD_WARNING_CODES`, with `AstmLivdCatalogWarning`, `LivdCatalogIdentity`, `LIVD_CATALOG_IDENTITY_UNDECLARED` and the builder `livdCatalogMissingLoincVersion`. A catalog defined with no LOINC version surfaces one such warning on `LivdCatalog.warnings`. - -**It warns, and it never refuses.** A catalog with no LOINC version is built, indexed and answers every lookup exactly as it would have. The warning is raised **once, where the catalog is defined**, never per record, so the per-record stream `applyLivd` returns carries the same codes, in the same order, in the same number as before. No answer moves and no `mapped` becomes `ambiguous`. - -**The warning's message is a constant and the catalog is named in a structured field.** The metadata is consumer-supplied text this library cannot vouch for, so interpolating it into a message would put whatever a consumer stored into every log that prints one. The message says nothing about the catalog; `AstmLivdCatalogWarning.catalog` carries the publisher and publication version the catalog declared. A catalog that declared neither is reported as having declared no identity (`LIVD_CATALOG_IDENTITY_UNDECLARED`), positively, and no name, ordinal or borrowed row value is invented to stand in for one. - -**Nothing here is validated, and nothing here is a claim by this package.** There is no version format check, no copyright-text check and no publisher check. This package performs **no LOINC validation of any kind**, which is unchanged: it cannot tell a real LOINC version from a typed one and does not try, so `catalogLoincVersion` states what the consumer declared and never that a LOINC was checked, found or conformed against it. **Carrying an attribution statement is not discharging it**: the LOINC license obligation stays the consumer's, and this package supplies no statement on anyone's behalf. - -**This is additive and source compatible.** The second argument is optional, so the existing single-argument call keeps working; `LivdCatalog.publication` and `LivdCatalog.warnings` are optional members, so a catalog a consumer implemented by hand still satisfies the interface and still annotates without raising; and `catalogLoincVersion` is conditional, so an annotation from a catalog declaring no LOINC version keeps the exact key set it has today. The one visible widening is `LivdWarningCode`, which gains the new registry member: an exhaustive `switch` over that union needs one more arm. diff --git a/.changeset/livd-units-choose-between-candidates.md b/.changeset/livd-units-choose-between-candidates.md deleted file mode 100644 index df880ab..0000000 --- a/.changeset/livd-units-choose-between-candidates.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -"@cosyte/astm": minor ---- - -**A LIVD catalog can now say what tells two candidate LOINCs apart, and the reported units choose between them.** One vendor analyte code mapping to several LOINCs is the ordinary case rather than an edge: the governing mapping guide gives the commonest chemistry analytes as its own worked examples, a serum glucose reported as a mass concentration versus a substance concentration and a urine analyte reported as a spot concentration versus a 24 hour excretion rate, and its remedy is to define a mapping per unit. `LivdEntry` had nowhere to record any of that, so a catalog covering more than one reporting unit answered `ambiguous` for a large share of ordinary analytes. The `R` record already states its units on the wire, so those units, and only those units, now settle it. - -Added: - -- `LivdEntry.vendorSpecimenDescription`, `LivdEntry.vendorResultDescription` and `LivdEntry.representativeUnit`: three **optional** attributes carried **verbatim**, with no trimming, case folding or normalization of any kind. -- `LivdCatalog.lookup` takes an **optional** second parameter, the units the record reported. `applyLivd` and `lookupLivdForRecord` supply field 5 of an `R` record verbatim; an `O` record has no units field, so nothing is supplied for one. **This is source compatible**: a hand-written catalog whose `lookup` declares only the vendor code still satisfies the interface and answers exactly as it did. -- `LivdUnitComparison`, on a `mapped` answer as `unitComparison`, and present **if and only if** a unit comparison chose that LOINC from more than one candidate. -- `LivdCandidate`, on an `ambiguous` answer as `candidateDetails`, plus `LivdAmbiguityReason` as `reason`. - -**The comparison is verbatim and case sensitive, and it is not UCUM.** Nothing is normalized, case folded, scaled or converted on either side, so a catalog saying `mg/dL` does not match a feed saying `MG/DL`, never matches `ug/dL` however convertible the two are, and a unit differing only in surrounding or internal whitespace is a different unit. UCUM defines a case-insensitive variant of every terminal symbol and requires a program declaring full conformance to compare unit expressions by their semantics, so this is a deliberately limited choice: the only comparison that cannot invent an equivalence. Because a consumer could otherwise read a matched unit as a conformance claim this package does not make, every unit-selected answer states what was compared (`comparison: "verbatim-case-sensitive"`, `ucumSemantic: false`, both units verbatim, and a note). - -**Nothing about `ambiguous` was narrowed, and refusing still beats guessing.** The units matching no candidate, matching more than one, or not being readable at all (absent, empty, whitespace only, or lost to a truncated or malformed record) are all `ambiguous` with every candidate surfaced and no LOINC chosen. An unreadable units field reads as no units reported: a typed annotation is still produced, nothing raises, and no record is omitted. A candidate whose representative unit is absent, empty or whitespace only is **not unit qualified**, so it is never selected by a unit comparison, and it is still surfaced among the candidates. - -**This is additive, and an answer that was `mapped` can never become `ambiguous`.** A vendor code carrying exactly one candidate LOINC is answered whether or not the units agree, and with no `unitComparison`, because no unit chose anything there. All three attributes are optional and every new answer field is conditional, so a catalog carrying none of them answers byte-identically to how it answered before, for `mapped`, `unmapped` and `ambiguous` alike. - -**A `mapped` answer's `representativeUnit` is the catalog row's, not the record's.** Where a vendor code carries a single candidate LOINC across several rows that spell the unit differently, the answer takes the first row's attributes, exactly as it has always taken the first row's `loincLongName`, so that field can name a unit the record did not report. Only `unitComparison` ever asserts that the two were equal, and it is present only where a unit actually chose between candidates, so that is the field to branch on. - -**Two things are deliberately not done.** The vendor specimen description and the vendor result description are **never matched on**: both are free text, and the guide states directly that this information is not intended to be parsed by software that automates the mapping, so they are stored and surfaced for a human and nothing else. And no LOINC is validated or shape-tested anywhere here, which is unchanged: this package still performs no LOINC validation of any kind. diff --git a/.changeset/publish-a-dependency-inventory-with-each-release.md b/.changeset/publish-a-dependency-inventory-with-each-release.md deleted file mode 100644 index 49207e4..0000000 --- a/.changeset/publish-a-dependency-inventory-with-each-release.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -"@cosyte/astm": patch ---- - -Publish a machine-readable dependency inventory with each release. - -Every published version now carries a CycloneDX inventory of what that build was made from, attached -to its GitHub release as a downloadable asset. The inventory names this package and the version -being built alongside every dependency that build resolved, runtime and build-time alike, -transitively, each with the exact version it resolved to. A team evaluating this package for a -regulated laboratory can now read what it is built from and feed it to their own tooling, instead of -taking a sentence about dependencies on trust or reproducing an install to find out. - -It is produced and checked by the repository itself, from the checkout, with no registry credential. -The check runs before the registry upload: a version whose inventory cannot be produced, or which -does not truthfully describe the build, fails the release rather than publishing with nothing to -describe it. - -What the inventory does not claim. It states what was resolved and nothing about whether that is -safe: it is not a vulnerability scan, a licence assessment, an attestation or a signature, and npm -provenance remains the separate thing it already was. It carries component names and versions only. - -No exported value, type or behaviour changes, and no byte is emitted differently. diff --git a/.changeset/say-which-vocabulary-a-letter-was-graded-against.md b/.changeset/say-which-vocabulary-a-letter-was-graded-against.md deleted file mode 100644 index 0cec6f8..0000000 --- a/.changeset/say-which-vocabulary-a-letter-was-graded-against.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -"@cosyte/astm": minor ---- - -**Every interpreted abnormal flag and result status now says which vocabulary it was graded against, and `HU`/`LU` are recognized.** The `R` record's flag and status letters come from vocabularies that move on other bodies' schedules, while the record grammar around them is archived and frozen. Reporting a letter as `"undefined"` without saying what it was compared against hid that: a consumer holding `{ recognized: false, meaning: "undefined" }` could not tell a code **no published vocabulary defines** from a code **this library had not caught up to**. That distinction is now on the value itself. - -Added: - -- `AbnormalFlag.vocabulary`: the published code system the flag was graded against, named by `system` (identifier) and `version`, present whether or not the letter was recognized. It is the HL7 v3 ObservationInterpretation code system, which carries the concepts kept aligned with the HL7 v2 Table 0078 interpretation codes this library has always recognized. **The version records what this library compared against; it is not a claim about which version the sender meant**, and it is not a conformance statement about your instrument. -- `ResultStatus.vocabulary`: always the explicit **no citable published source**, with `attributed: false` and fixed prose in `reason` saying why. The normative text that would bind the status letter set is purchase-gated and has not been read here, and a neighbouring HL7 table whose letters partly agree is not adopted in its place: that would be an assertion nothing in the public record supports. An unattributed set is deliberately distinguishable from an absent attribution, and it never carries an identifier. -- `HU` (`"significantly-high"`) and `LU` (`"significantly-low"`) are recognized abnormal flags. Both are active concepts in the graded vocabulary and were previously absent, so a feed sending either got a warning and `"undefined"`. They are distinct from `H`/`L` (merely outside the interval), from `HH`/`LL` (critical) and from the directional `U`/`D` (a change since the last result), all six of which keep the readings they had. The recognized set is now eighteen letters: `L`, `H`, `LL`, `HH`, `<`, `>`, `N`, `A`, `AA`, `U`, `D`, `B`, `W`, `S`, `R`, `I`, `HU`, `LU`. -- `ABNORMAL_FLAG_CODES`, the recognized set as a list; `ABNORMAL_FLAG_VOCABULARY` and `RESULT_STATUS_VOCABULARY`, the two attribution constants; `describeVocabulary`, which renders one as fixed prose; and the `NamedVocabulary`, `UnattributedVocabulary` and `VocabularyAttribution` types. - -Changed: - -- The `ASTM_RECORD_UNDEFINED_ABNORMAL_FLAG` and `ASTM_RECORD_UNDEFINED_RESULT_STATUS` warning **messages** now carry the same attribution the interpreted value reports, read from the same constant so the two cannot drift. Both warning **codes** are unchanged, and both messages remain value-free: position and fixed prose only, never field text. - -Nothing else moved. No tolerance was widened: recognition is still exact match after the surrounding whitespace is trimmed, so `hu` and `f` are reported unrecognized with their raw text intact rather than folded into a match. An unrecognized flag is still never coerced to `normal`, an absent status is still never `final`, and no other concept from that code system is adopted, including the deprecated `H>` and `L<` whose replacements `HU` and `LU` are. The attribution is interpretation output only: a parsed and re-serialized record emits byte-identical output, with no identifier, version or attribution text reaching the wire. No new dependency. diff --git a/.changeset/shared-date-conversion-surface.md b/.changeset/shared-date-conversion-surface.md deleted file mode 100644 index ada89f6..0000000 --- a/.changeset/shared-date-conversion-surface.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -"@cosyte/astm": minor ---- - -**`toObject`, `toISO` and `toDate`: the shared `@cosyte` date conversion surface, with the timezone honesty ASTM forces.** A parsed `AstmDate` could be rendered (`astmDateToLocalISO`) but not read as components and not converted to an instant, and the name it was rendered under was this package's alone, so a consumer holding dates from two `@cosyte` parsers wrote two different conversions for the same job. The three names, their return shapes and their timezone rule are now identical across the sibling parsers, so moving between them costs nothing to relearn. - -Added: - -- `toObject(value)`: a **frozen** object carrying only the components the value stated (`{ year: 2024, month: 3, day: 15 }` for `"20240315"`), with a spec-native 1-to-12 month and singular `hour`/`minute`/`second` keys. Nothing is zero-filled and no key holds `undefined`, so `Object.keys()` is the value's precision. The parse bookkeeping stays on `AstmDate` and does not cross over: there is no `raw`, `precision` or `truncated` key, and a digit run that cut a component in half reports only its complete components, with the dangling digit reaching no field. Deleting `offsetMinutes`, which an ASTM value never carries, leaves an object `Temporal.PlainDateTime.from` and luxon's `DateTime.fromObject` accept with no key rename and no value adjustment. Neither library is a dependency: the shape is the interoperability, not an import. -- `toISO(value)`: ISO-8601 truncated to the stated precision, never padded out to a precision the value did not state, and with no `Z` and no offset appended because ASTM states none. It returns the same string `astmDateToLocalISO` returns for every value `parseAstmDate` produces whose components are a real calendar date. That function is unchanged, still exported, and still the repo-native name; on a value whose components are not a real calendar date it still renders the digits it was handed (`"1988-13-01"`) and `toISO` answers `undefined` instead, which is the only place the two differ. -- `toDate(value, options?)`: an absolute-instant `Date`, and **only** when the caller makes the zone determinate with `options.assumeOffsetMinutes` (signed minutes east of UTC, an explicit `0` meaning "read this value as UTC"). An ASTM timestamp is local to the instrument that wrote it and states no offset, so without that assumption there is no instant to give and the answer is `undefined`. The host machine's zone is never read and UTC is never assumed: either guess is silent and shifts a date by a day in every negative-offset zone. Components below the stated precision fill to their lowest legal value for the instant only, leaving the value's own precision untouched, and a four-digit year below 100 stays that year (`"00500101"` is the year 50, never 1950). An offset that names no usable zone is refused rather than applied, and the answer is `undefined`, never an `Invalid Date`: that covers an `assumeOffsetMinutes` which is not a finite number, and one large enough that applying it lands outside the range a JS `Date` represents. An `Invalid Date` satisfies the declared `Date | undefined` return and defeats it, because a caller cannot tell it from a real instant without testing `getTime()` for `NaN` and `toISOString()` on it throws. -- The `DateParts` and `ToDateOptions` types, and a README section documenting all three together with the import-aliasing pattern the shared names force on a consumer using two `@cosyte` parsers in one file. - -A component that is not on the calendar is refused by all three rather than normalised. `parseAstmDate` is lenient by design and reads `"19881301"` into month 13 rather than dropping the field, and that is unchanged, so the conversions are where it is answered: a month outside 1 to 12, a day past the end of its own month (29 February in a common year included), an hour past 23 and a minute or second past 59 each yield `undefined`. A JS `Date` would instead roll month 13 into January of the following year and return a date of birth a year out with nothing to say so. The refusal is of the whole value, never of the offending component alone. - -None of the three ever throws: `undefined`, `null`, a value `parseAstmDate` refused, a value stating no component at all, and a value whose components are not a real calendar date each answer `undefined` rather than a partial one. Nothing else moved: no existing export was removed, renamed or changed in behaviour, no runtime or development dependency of any kind was added, and the supported Node.js floor is unchanged. diff --git a/package.json b/package.json index 957b855..a6ab577 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cosyte/astm", - "version": "0.0.22", + "version": "0.1.0", "description": "ASTM parser, serializer, and builder for Node.js and TypeScript: lenient on parse, spec-clean on emit.", "keywords": [ "astm", diff --git a/src/index.ts b/src/index.ts index 8065ebc..d9bbc70 100644 --- a/src/index.ts +++ b/src/index.ts @@ -64,7 +64,7 @@ * console.log(VERSION); * ``` */ -export const VERSION: string = "0.0.22"; +export const VERSION: string = "0.1.0"; export { parseAstmRecords, AstmStrictError, attachComments } from "./records/parse.js"; export {