Skip to content

fix: make the published manifest installable, and stop VERSION lying - #27

Merged
NSchatz merged 3 commits into
mainfrom
fix/version-sync-and-installable-manifest
Aug 3, 2026
Merged

fix: make the published manifest installable, and stop VERSION lying#27
NSchatz merged 3 commits into
mainfrom
fix/version-sync-and-installable-manifest

Conversation

@NSchatz

@NSchatz NSchatz commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Two defects that together made @cosyte/cli unusable, plus a third found while verifying the first.

1. The published manifest was uninstallable

0.0.1 and 0.0.2 both published with all ten @cosyte/* siblings declared as file:vendor/*.tgz local paths. vendor/ is not in files and there is no bundledDependencies, so the tarball shipped none of them.

Negative control, run first, on the current published version:

$ npm install @cosyte/cli@0.0.2
npm error code ENOENT
npm error path .../node_modules/@cosyte/cli/vendor/cosyte-fhir-0.0.0.tgz
npm error enoent ENOENT: no such file or directory

Every version was derived from the live registry, never from a file. The siblings are now real ranges: @cosyte/hl7 ^0.0.7 and @cosyte/terminology ^0.0.9 as hard dependencies; the six breadth parsers plus @cosyte/transform ^0.0.4 as optionalDependencies.

Verified by installing, which a dry-run cannot do

$ npm install ../cosyte-cli-0.0.2.tgz
added 111 packages, and audited 112 packages in 4s          # exit 0

$ cosyte --version
0.0.2                                                       # exit 0   (published 0.0.2 prints 0.0.0)

$ cosyte parse adt.hl7                                      # exit 0
$ cosyte inspect claim.x12                                  # exit 0
$ node -e 'import("@cosyte/cli").then(m=>console.log(m.VERSION))'
ESM ok  VERSION=0.0.2  exports=48
$ node -e 'console.log(require("@cosyte/cli").VERSION)'
CJS ok  VERSION=0.0.2  exports=48

$ printf '<initialize>' | cosyte-mcp
{"result":{...,"serverInfo":{"name":"cosyte","version":"0.0.2"}},"jsonrpc":"2.0","id":1}

What still blocks a fully-featured install, stated rather than papered over

@cosyte/fhir could not be de-vendored and is not declared at all. It is a registry 404 (FHIR-NPM-NAME, a persistent unexplained E403; the name-similarity reading was retracted ecosystem-wide today, so no rename is proposed and the publish was not re-tested). Decision: @cosyte/fhir is undeclared, @cosyte/transform is declared optional. That was measured, not reasoned:

root manifest declares npm install
optional @cosyte/fhir alone exit 0
optional @cosyte/transform alone exit 0
both ERESOLVE, exit 1
@cosyte/fhir as an optional peer + transform ERESOLVE, exit 1

So the two cannot both be named. transform is declared because it is real and starts resolving by itself the day fhir publishes, with no release here. Not attributed to a missing peerDependenciesMeta.optional flag: measured across the suite, that flag does not decide the outcome (synth marks all seven peers optional and still ERESOLVEs; deid declares the same optional fhir peer and installs cleanly). The mechanism is unexplained; only the measurements are recorded.

Consequence, stated on every consumer surface: an installed copy has no FHIR support. That required a code change, because shipping the manifest swap alone would have been worse than the install break. Both packages were loaded with a bare await import(), so an install without them raised a raw resolver error and a stack frame, which the value-free posture forbids. loadOptionalPackage(detail, load) now sits under loadOptional (which takes a CosyteFormat and hardcodes "parser", wrong for both cases), plus loadFhir():

$ cosyte parse patient.json
cosyte: CLI_PARSER_UNAVAILABLE: the @cosyte/fhir parser is not installed, and it is not currently on
the npm registry, so it cannot be installed from there; FHIR support is unavailable in this install
exit=69

$ cosyte convert adt.hl7 --to fhir
cosyte: CLI_PARSER_UNAVAILABLE: the @cosyte/transform conversion library is not installed, so convert
is unavailable; it requires @cosyte/fhir, which is not currently on the npm registry, so npm skips
transform as an unresolvable optional dependency and installing it directly fails for the same reason
exit=69

Neither says "install it", deliberately: npm install @cosyte/transform fails E404 on its own peer, so that advice would point at a command that cannot succeed.

@cosyte/fhir is kept as a devDependency on the vendored tarball so this repo's FHIR/convert tests still run. Note precisely: devDependencies are published, so one file: specifier does remain in the manifest, harmlessly, because a consumer never installs a dependency's devDependencies, verified by the exit-0 install above.

The dependabot config keeps its honesty and gains coverage: the siblings are now watchable for the first time; @cosyte/fhir is the one it still cannot see, and the comment says so rather than implying cover.

2. The VERSION export lied

src/core/version.ts exported "0.0.0" against a 0.0.2 manifest, and its JSDoc already claimed a Changesets sync step that did not exist. Confirmed independently in the published tarball:

$ npm pack @cosyte/cli@0.0.2 && tar xzf ... && grep -o 'VERSION = "[0-9.]*"' package/dist/index.mjs
VERSION = "0.0.0"
$ node -p "require('./package/package.json').version"
0.0.2

It reached two user-visible surfaces: cosyte --version and the MCP server's advertised serverInfo.version.

Proof the new test fails on the unfixed tree, as required:

FAIL  test/sanity.test.ts > toolchain sanity > package exports VERSION matching package.json
AssertionError: expected '0.0.0' to be '0.0.2' // Object.is equality
Expected: "0.0.2"
Received: "0.0.0"

FAIL  test/sanity.test.ts > toolchain sanity > declares VERSION in the exact shape scripts/sync-version.mjs rewrites
AssertionError: expected null not to be null

Tests  2 failed | 2 passed (4)

scripts/sync-version.mjs is ported from @cosyte/transform (87b5282), and test/sanity.test.ts narrows the parsed manifest **without an as cast`.

Both named traps closed

  • A smoke test that asserts a TYPE asserts nothing. docs-content/installation.md did exactly what synth's docs did: typeof VERSION; // => "string", true of every wrong value, executed against built dist/ by test/docs-content.test.ts on every release. It now asserts VERSION; // => "0.0.2", a real toStrictEqual, and the sync script rewrites that literal as a second target so the docs gate stays load-bearing instead of going stale.
  • The : string annotation. It was absent entirely; it is now present and pinned by its own test. Verified that disarming it reds only that assertion:
$ sed -i 's/^export const VERSION: string = /export const VERSION = /' src/core/version.ts
× declares VERSION in the exact shape scripts/sync-version.mjs rewrites
  Tests  1 failed | 3 passed (4)
$ node scripts/sync-version.mjs
sync-version: could not find `export const VERSION: string = "...";` in src/core/version.ts.
sync-version exit=1

3. Pre-existing, and NOT fixed by either of the above

npx @cosyte/cli never worked, independent of the packaging defect. npx runs the executable matching the package name's last segment (cli); this package ships cosyte and cosyte-mcp. Reproduced on published 0.0.2 and on the fixed tarball, whose bin block is byte-identical, so the swap provably cannot have changed it:

$ npx @cosyte/cli --help
npm error could not determine executable to run

$ npx --package @cosyte/cli cosyte --version     # the form the docs now use
0.0.2

Docs (README, installation.md, both MCP registration snippets) now use the measured working form. A cli bin alias would fix the short form and is deliberately not added, because npm install -g would then claim the name cli on every user's PATH. That trade is a founder call, not a packaging tidy-up.

Gate

gate-refuter (not conformance-refuter: no healthcare-data parsing is touched), 2 passes.

  • Pass 1: VERDICT: REFUTED: two INTRODUCED majors, both documentation-honesty, no code defects. (a) README.md still said "the swap is blocked... until @cosyte/fhir can be published under a name npm accepts", stale and restating the retracted diagnosis, 44 lines below new text saying the swap was done. (b) the convert diagnostic said "install it to use convert", advice that fails E404, while three documents claimed it said the opposite, and in a plain install that is the only branch convert reaches. Plus four minors. Remedied in 7b2b596; the fix was text plus one diagnostic string, and neither the loader nor the guard was grown.
  • Pass 2: VERDICT: NOT REFUTED: all seven findings resolved or accepted, re-measured in a clean room. One new minor inside the remedy: my source-extraction regexes silently dropped each constant's last line (182 of 270 chars), so the negative assertions had a blind spot exactly where a re-introduced "install it" tail would land. Fixed in 4778919 (one token, ;?) with a test pinning both strings' final words; negative control confirms restoring the old regex reds that assertion and only it.

Filed, not fixed here (PRE-EXISTING, reproduces on c858cb0): the built dist/bin/cosyte.mjs imports @modelcontextprotocol/sdk statically at top level, so --omit=optional yields an install where every command, --version included, dies with ERR_MODULE_NOT_FOUND and a raw stack trace. It also falsifies src/bin/cosyte.ts's "a plain cosyte parse never pulls it" and the test/mcp-isolation.test.ts claim. This release makes it reachable for the first time, because the package could not be installed at all before. Three docs pages now say not to use that flag rather than implying it is supported. It needs its own change.

Verification

scripts/verify.sh cli green. typecheck, lint (--max-warnings=0), format:check, check:no-emdash, check:no-internal-refs, phi-scan, attw, smoke all green. 361 tests, 29 files. Coverage 97.7 / 92.28 / 93.87 / 98.56, per-directory ≥ 90 on core and commands. Treat this PR's own check runs as authoritative.

Changeset included (patch, 0.0.x ladder). The open "Version Packages" PR #26 is deliberately untouched: the release is the coordinator's to drive once this is on main. No umbrella pointer bump, no operations/, documentation/, .gitmodules or other submodule touched.

NSchatz added 3 commits August 3, 2026 15:41
Two defects that together made @cosyte/cli unusable, plus a third found while
verifying the first.

1. CLI-UNINSTALLABLE-MANIFEST. 0.0.1 and 0.0.2 both published with all ten
   @cosyte/* siblings declared as file:vendor/*.tgz local paths. vendor/ is not
   in files and there is no bundledDependencies, so the tarball shipped none of
   them and every install route died with ENOENT on
   node_modules/@cosyte/cli/vendor/cosyte-fhir-0.0.0.tgz.

   The siblings are now real registry ranges: @cosyte/hl7 ^0.0.7 and
   @cosyte/terminology ^0.0.9 as hard dependencies, the six breadth parsers plus
   @cosyte/transform ^0.0.4 as optionalDependencies. Verified by packing the
   tarball and installing it in a clean directory outside the repo (exit 0), then
   running both bins and importing the . subpath under ESM and CJS. Negative
   control: the published 0.0.2 still ENOENTs.

   @cosyte/fhir could NOT be made real and is not declared at all. It is not on
   the registry, and declaring it in any form alongside @cosyte/transform (whose
   @cosyte/fhir peer is mandatory) fails the whole install with ERESOLVE, as an
   optional dep and as an optional peer alike. Either alone installs clean; the
   pair does not. Do not attribute that to a missing peerDependenciesMeta.optional
   flag: measured across the suite that flag does not decide the outcome, and the
   mechanism is unexplained. It is retained as a devDependency on the vendored
   tarball so this repo's own FHIR and convert tests run.

   Consequence, stated on every consumer surface rather than left to be
   discovered: an installed copy has no FHIR support. FHIR parse/inspect/fmt/
   validate and convert now degrade to a value-free CLI_PARSER_UNAVAILABLE (69)
   instead of crashing on a bare await import(). Adds
   loadOptionalPackage(detail, load) beneath loadOptional (which takes a
   CosyteFormat and hardcodes the word "parser", wrong for both cases) and
   loadFhir(), whose diagnostic says the package is not on the registry rather
   than "install it". test/absent-sibling.test.ts pins the mapping and adds a
   static guard that reds if a new bare import("@cosyte/fhir") or
   import("@cosyte/transform") appears in src/.

2. CLI-VERSION-DRIFT. src/core/version.ts exported "0.0.0" against a 0.0.2
   manifest, confirmed in the published tarball, and reached two user-visible
   surfaces: cosyte --version and the MCP server's advertised
   serverInfo.version. The constant's own doc comment already claimed a sync step
   that did not exist. scripts/sync-version.mjs (ported from @cosyte/transform)
   now runs inside the version script, and test/sanity.test.ts compares the export
   against package.json.

   The assertions are fixed, not just the value, because those are what let the
   same defect through five sibling releases: docs-content/installation.md
   asserted typeof VERSION, true of every wrong value, and now asserts the exact
   version, which the sync script keeps in step; and the declaration's ": string"
   annotation that the script's pattern keys on is pinned by its own test.
   Verified that dropping the annotation reds only that assertion and makes the
   script exit 1 rather than silently no-op.

3. Pre-existing, and NOT fixed by either of the above: npx @cosyte/cli fails with
   "could not determine executable to run", because npx runs the bin matching the
   package name's last segment (cli) and this package ships cosyte and
   cosyte-mcp. Reproduced on the published 0.0.2 and on the fixed tarball, whose
   bin block is byte-identical. The docs now use the measured working form,
   npx --package @cosyte/cli cosyte. A cli bin alias would fix the short form and
   is deliberately not added, because npm install -g would then claim the name
   cli on the user's PATH; that trade is a founder call.
Remedy for gate-refuter pass 1 (VERDICT: REFUTED, two majors, both
documentation-honesty rather than code). No loader or guard was grown.

1. The convert diagnostic gave advice that cannot be followed. It said "install
   it to use convert (it is an optional dependency)", but `npm install
   @cosyte/transform` fails E404 on its own @cosyte/fhir peer, so it pointed at a
   command that always fails. That is the exact defect the fhir diagnostic exists
   to avoid, and the same commit's test asserted the fhir message must NOT say
   it. In a plain install the transform branch is the only one convert can reach,
   so this was the shipped behaviour, on the terminal and the MCP surface alike.
   The message now names the real cause. CHANGELOG, the changeset and
   troubleshooting.md claimed it already did; that claim is now true.
   test/absent-sibling.test.ts pins both diagnostics against the "install it"
   wording.

2. README.md still said "The swap is blocked, not scheduled, and it stays blocked
   until @cosyte/fhir can be published under a name npm accepts", forty-four lines
   below new text saying the swap was done. It also restated the name-similarity
   diagnosis that was retracted ecosystem-wide on 2026-08-03 and that this same
   commit retracted in RELEASING.md and CHANGELOG.md. Corrected.

3. "It is not published, because devDependencies are not" is false.
   devDependencies ARE published (npm view @cosyte/hl7@0.0.7 devDependencies
   returns a full list), so the fix release's manifest does still carry one
   file:vendor/*.tgz specifier. It is harmless because a consumer never installs a
   dependency's devDependencies, which is what the text now says. RELEASING.md and
   vendor-refresh.sh.

4. New docs presented --omit=optional as a supported way to slim the install.
   Measured: it installs clean, then every invocation including --version dies
   with ERR_MODULE_NOT_FOUND on @modelcontextprotocol/sdk and a raw stack trace,
   because dist/bin/cosyte.mjs imports the SDK statically at top level. That code
   defect is PRE-EXISTING (identical on c858cb0) and is NOT fixed here; it is now
   documented rather than implied away, and recorded as needing its own change.

Also: the static guard's reach was overstated as catching "any" new call site. A
refuter falsified that by assigning the thunk to a variable, leaving the suite
green. RELEASING.md and CLAUDE.md now state what it does and does not catch,
including that it cannot see the repo's first static `import type` of
@cosyte/fhir, one token from a runtime import. And README/installation no longer
imply an installable release exists: 0.0.2 is still the newest on npm.
Pass-2 finding, minor. Both source-extraction helpers in absent-sibling.test.ts
matched `"..."` segments followed by ` +` or a newline, but the LAST line of each
concatenation ends `";`, so the final segment never matched. Measured: the
transform detail came out 182 of 270 characters, the fhir detail likewise short.

The helpers' own comment said they read the shipped text "not a copy", while
asserting about 62% of it, and the blind spot sat exactly where a re-introduced
"install it" tail would land, so the regression the test was written for could
have walked back in unseen.

One token (`;?`) in each regex, plus a test pinning the last words of both
strings so a truncation of any length reds. Verified as a negative control:
restoring the old regex reds that one assertion and nothing else.
@NSchatz
NSchatz merged commit c017c66 into main Aug 3, 2026
7 of 8 checks passed
@NSchatz
NSchatz deleted the fix/version-sync-and-installable-manifest branch August 3, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant