@cosyte/cli 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,33 +7,237 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
  Versions and publishing are managed with [Changesets](https://github.com/changesets/changesets);
8
8
  this file is maintained by hand (Changesets handles the version bump and publish only).
9
9
 
10
+ The released sections below were reconstructed on 2026-08-04. Until then every entry sat under
11
+ `Unreleased`, including entries describing releases that had already shipped, so each published
12
+ tarball carried a changelog calling its own contents unreleased. Those tarballs are immutable and
13
+ still do. Each entry was assigned to the release whose tag first contains it, read off `v0.0.1`,
14
+ `v0.0.2` and `v0.0.3`; no entry text was rewritten to fit a section.
15
+
10
16
  ## [Unreleased]
11
17
 
12
18
  ### Fixed
13
19
 
14
- - **The public surface now states that `0.0.1` is published and uninstallable (ASSETS-P8).** `0.0.1`
15
- published on 2026-07-29 with all ten `file:vendor/*.tgz` dependency specifiers intact. `vendor/` is
16
- not in `files` and there is no `bundledDependencies`, so every install route (`npm i`, `npm i -g`,
17
- `npx`) fails with `ENOENT` on `node_modules/@cosyte/cli/vendor/cosyte-fhir-0.0.0.tgz`. Reproduced
18
- in a clean directory. A published version is immutable (ADR 0001), so `0.0.1` stays broken and the
19
- fix must ship as a later version.
20
- - `README.md` gains a **"Known issue"** section stating what fails, the exact error, why, and that
21
- a source checkout is the only workaround. `docs-content/installation.md` gains the same under
22
- **"Installing fails today"**. Both copies of the broken `npx`-based MCP registration snippet are
23
- annotated (`README.md` and `docs-content/mcp.md`).
24
- - **Four false claims corrected.** (1) `README.md` said "not yet published to npm"; it is
25
- published. (2) It described swapping the vendored sibling deps for real `@cosyte/*` npm ranges as
26
- a step still to come "at that flip"; the flip already happened without the swap, which is the
27
- defect. (3) The same "not yet published" claim appeared in **three** `docs-content/` pages. (4)
28
- The `redact`/`deid` terminal diagnostic, the `--help` text, three JSDoc blocks that compile into
29
- `dist/*.d.ts`, and **five** docs pages said `@cosyte/deid` was unpublished, unshipped, or unbuilt;
30
- it is published at `0.0.2`, and the accurate statement is that the CLI does not wire it yet. Every
31
- count here was re-derived by census of the base tree, not estimated.
32
- - **The "all eight formats" claim was checked and is correct**: `CosyteFormat` and `OP_SUPPORT` both
33
- enumerate exactly eight.
34
- - `RELEASING.md` records that the documented dependency-swap step was skipped, that a green
35
- `npm publish --dry-run` cannot catch this (it packs a tarball but never resolves its deps from a
36
- registry), and adds a checklist step to install the published version from outside the repo.
20
+ - **The shipped documentation sidebar was off the canonical IA spine, and it was holding up the
21
+ docs site's deploy (CLI-SIDEBAR-IA-NONCANONICAL).** `docs-content/sidebars.json` declared two
22
+ top-level categories that are not on the spine, **"MCP server"** and **"Reference"**. The docs
23
+ site lints the sidebar it receives from each package's released `docs-content.tar.gz`, and in
24
+ strict mode a non-canonical top-level label grades as an error, so this package failed that gate
25
+ and the site stopped deploying. Pre-existing since the pages were authored; it surfaced only once
26
+ an unrelated build failure stopped masking it.
27
+ - Both categories are removed and their pages folded into categories this package already had, so
28
+ **no page left the navigation and none was orphaned** (verified: the nine documents in
29
+ `docs-content/` and the nine referenced by the sidebar are the same nine). **`mcp`** and
30
+ **`reference-commands`** join **Guides**, next to `guides-overview`, both being task-oriented:
31
+ what to run and what comes back. **`limitations`** joins **Troubleshooting**, next to
32
+ `troubleshooting`, which is where the documentation standard puts known limitations.
33
+ - The categories this package ships are now `Installation`, `Quickstart`, `Core Concepts`,
34
+ `Guides` and `Troubleshooting`, in that order, under the `intro` document.
35
+ - **"Reference" was deliberately NOT renamed to "API Reference".** That category is injected by
36
+ the docs site, and a hand-authored one is refused outright rather than warned about, so the
37
+ rename would have traded a failing check for a differently failing one. Whether and where the
38
+ site adds it is the site's decision, not this package's, and this package neither authors nor
39
+ positions it.
40
+ - Verified against the site's own linter rather than by inspection, with the sidebar shipped in
41
+ the previous release as a **negative control**: that one produces two errors and this one
42
+ produces no findings. **Only a new release can clear the gate**, because it reads the sidebar
43
+ out of the released artifact and published releases are immutable, so the previous release
44
+ keeps its non-canonical sidebar permanently. That is reported without gating once a release is
45
+ no longer current.
46
+ - Documentation-artifact change only: no command, flag, exit code, diagnostic code or export
47
+ moves, and no page content was rewritten.
48
+
49
+ - **The pre-commit PHI gate never saw a `git mv` into a scan root
50
+ (PHI-SCAN-RENAME-BLIND-AT-PRECOMMIT).** `scripts/phi-scan.ts --staged` listed the index with
51
+ `git diff --cached --name-only --diff-filter=AM`. Rename detection is on by default, so
52
+ `git mv <path> test/__fixtures__/<name>` stages as a **two-path `R100` record**, and `R` and `C`
53
+ are in neither `AM` nor `AMT`: the status filter deleted the record outright and the destination
54
+ was never enumerated. Measured on this repo, both shapes walking through the
55
+ `pnpm phi-scan --staged` pre-commit hook at **exit 0**: a regular file carrying a value this
56
+ scanner's own floor catches (`:100644 100644 <sha> <sha> R100`), and a symbolic link
57
+ (`:120000 120000 <sha> <sha> R100`, index mode `120000` under the scan root). `git mv` is an
58
+ ordinary developer action, not crafted input, and the pre-commit hook is the gate it walked
59
+ through.
60
+ - **`--no-renames` is the remedy, and it costs the record stride nothing.** The destination
61
+ arrives as a single-path `A` and the source as a `D` the filter drops, so the enumeration is a
62
+ strict **superset** of the previous one. Verified under `diff.renames` set to `true`, `copies`,
63
+ `false` and `1`, and under `diff.renameLimit=1`: no `R` or `C` record survives in any of them,
64
+ which makes the two-field record stride structural rather than conditional. `copies` is not
65
+ hypothetical; it produces a live `C100` here.
66
+ - **The destination mode was never read at all.** The route enumerated with `--name-only` and read
67
+ content with `git show :<path>`, and git stores a symbolic link as its **target path** under
68
+ mode `120000`, so the scan was handed the path text and never the target's bytes. It now lists
69
+ with `--raw -z` and **refuses** (exit `2`) any in-scope entry whose destination mode is not a
70
+ regular blob. A refusal names the entry's own repo-relative path and an engine-owned token for
71
+ its kind, and **never the link target**, which is working-tree text that can itself carry PHI.
72
+ Every offender is named, not just the first. That guarantee is about a **refusal** and does not
73
+ extend to a hit; see the linked-scan-root residual below.
74
+ - **`T` (typechange) is now in the filter**, because replacing a _tracked_ regular fixture with a
75
+ link is neither an add nor a modify (`:100644 120000 <sha> <sha> T`), so `--diff-filter=AM`
76
+ deleted the record before any mode could be read. The reverse direction, a link replaced by a
77
+ real file, is now scanned as the file it became.
78
+ - **Each scan root's own path is in scope**, not just its contents. Git records no index entry for
79
+ a directory, so an entry at exactly `test/__fixtures__` or `src` is that root replaced by a blob
80
+ or a link, and a prefix test requiring the trailing slash let it through while the whole corpus
81
+ went unscanned.
82
+ - **The all-mode walk gets the same refusal.** A scanner whose pre-commit half refuses a link
83
+ while its CI half silently drops one is not one a developer can reason about. The walk
84
+ enumerates `Dirent.isFile()`, an lstat answer, so a link (and a linked _directory_, which
85
+ `isDirectory()` also answers false for) fell out of the loop silently. The `.md` exemption
86
+ deliberately does not reach a link: it is a judgement about a file whose bytes the walk could
87
+ have read, and a link's name is no evidence about what is on the other side of it. A gitignored
88
+ entry stays out of scope, by the same rule that already excludes a gitignored fixture.
89
+ - **Two pre-existing exit-code defects fixed with it.** A missing or unreadable allow-list, and an
90
+ unreadable scan root, both threw past every handler and exited **1** with a raw stack trace. `1`
91
+ is this contract's code for _hits found_, so a caller branching on the exit code read a broken
92
+ invocation as a PHI finding and a caller branching on "not 0" read it as the gate working.
93
+ Neither was true. Both are now exit `2` with a diagnostic.
94
+ - **Stated rather than left to be inferred, both pre-existing and neither closed here.** The
95
+ staged route still does not enumerate `D` (a deletion has no staged blob to scan) or `U` (an
96
+ unmerged path has no single one). The `U` half costs nothing that can reach a commit, and that
97
+ was measured rather than assumed: `git commit` refuses an unmerged index outright. Under `src/`
98
+ the staged route still covers only `.ts` files while the all-mode walk covers every non-`.md`
99
+ file, so the CI sweep is what covers the difference; widening the staged half is a scope
100
+ decision and was not taken here.
101
+ - **The refusal rule is scoped to an _enumerated_ entry, and the unqualified version of it is
102
+ false.** It covers an entry the walk reached beneath a root it had already opened, and a staged
103
+ record in scope per the boundary rule, which under `src/` is `.ts` files only, so a staged link
104
+ at `src/notes.json` is under a scan root and `--staged` still exits 0 over it while the all-mode
105
+ sweep refuses it. Three shapes escape the rule, all pre-existing, all measured, none
106
+ closed here: **(1)** a scan root that is itself a **live** link is followed by the all-mode walk,
107
+ because `existsSync` and `readdirSync` both resolve, so the walk reads files no commit contains
108
+ and reports their values under a **fabricated in-repo path** that holds no such file (the
109
+ **dangling** direction is the mirror image: it reports clean over a corpus it never opened);
110
+ **(2)** an **ancestor** of a scan root is in neither route's scope, so staging `test` as a link
111
+ leaves `--staged` at exit 0 and the walk then follows it; **(3)** paths mode follows an
112
+ explicitly named link, because `statSync` resolves. The `--staged` half of shape (1) **is**
113
+ closed here. Closing the rest needs a refuse-a-scan-that-observed-nothing rule plus a decision
114
+ about how far above a scan root to look, which is its own change.
115
+ - `test/scripts/phi-scan.test.ts` builds throwaway git repositories for all of it, because these
116
+ are properties of what `git diff --cached` reports and cannot be reproduced by scanning a path.
117
+ **16 of its 34 tests run red against the scanner on `a7a92f8`**; the 18 that stay green are the
118
+ pre-existing floor tests, the deliberate controls (an ordinary staged hit, a clean pass, a
119
+ staged link outside both roots, an ignored link, and a check that the payload under test is
120
+ something this scanner would otherwise catch), and the four that **pin the disclosed residuals
121
+ above**. Those four are green on both trees by design: they pin behaviour this change does not
122
+ alter, so that the scoped wording of the refusal rule cannot quietly revert to the absolute one.
123
+ Synthetic values only.
124
+ - **No change to the CLI's runtime surface**: no command, flag, exit code, diagnostic code or
125
+ export moves. This is repository tooling.
126
+
127
+ ## [0.0.3] - 2026-08-03
128
+
129
+ ### Changed
130
+
131
+ - **FHIR support is unavailable in an npm-installed copy, and now says so instead of crashing.**
132
+ `@cosyte/fhir` is not on the npm registry, so it cannot be declared as a dependency at all;
133
+ measured, declaring it alongside `@cosyte/transform` (which requires it) fails the whole install
134
+ with `ERESOLVE`. `@cosyte/transform` is therefore skipped by npm as an unresolvable optional
135
+ dependency. Both were previously loaded with a bare `await import()`, which in an installed copy
136
+ would have surfaced a raw resolver error and a stack frame, so FHIR `parse`/`inspect`/`fmt`/`validate`
137
+ and `convert` now degrade to a value-free `CLI_PARSER_UNAVAILABLE` (exit `69`) with a diagnostic that
138
+ says the package is not on the registry rather than "install it". **Neither diagnostic says "install
139
+ it", and that is deliberate**: `npm install @cosyte/transform` fails `E404` on its own
140
+ `@cosyte/fhir` peer, so `loadOptional()`'s stock wording ("install it to use this format, it is an
141
+ optional dependency") would point a user at a command that cannot succeed. New
142
+ `loadOptionalPackage(detail, load)` under `loadOptional`, exported on the `.` subpath.
143
+ `@cosyte/fhir` is retained as a `devDependency` on the vendored tarball so this repo's own FHIR and
144
+ `convert` tests still run; note that `devDependencies` **are** published, so that one
145
+ `file:vendor/*.tgz` specifier does remain in the manifest, harmlessly, because a consumer never
146
+ installs a dependency's `devDependencies` (verified: the install exits `0`). HL7 v2, `map-codes` and
147
+ the six breadth formats are unaffected and work from a plain install.
148
+
149
+ - **Documented a pre-existing defect that this release makes reachable for the first time:
150
+ `--omit=optional` produces an install in which the `cosyte` command does not run at all.** The
151
+ install exits `0`, then every invocation, `--version` included, fails with `ERR_MODULE_NOT_FOUND` on
152
+ `@modelcontextprotocol/sdk` and a raw stack trace, because the built `dist/bin/cosyte.mjs` imports
153
+ the SDK statically at the top level rather than only on the `mcp` path. Verified identical on the
154
+ base commit, so it is not introduced here; it simply could not be hit before, because the package
155
+ could not be installed at all. `docs-content/` now says not to use that flag instead of implying it
156
+ is a supported way to slim the install. **The code defect is not fixed here** and needs its own
157
+ change: it also falsifies the "a plain `cosyte parse` never pulls it" claim in `src/bin/cosyte.ts`.
158
+
159
+ - **The docs no longer tell you to run `npx @cosyte/cli …`, which never worked.** Separate from the
160
+ packaging defect and not fixed by it: `npx` runs the executable whose name matches the package
161
+ name's last segment, which would be `cli`, and this package ships `cosyte` and `cosyte-mcp`, so the
162
+ short form fails with `could not determine executable to run`. Reproduced on the published `0.0.2`
163
+ and on the fixed tarball, whose `bin` block is byte-identical. `README.md`, `docs-content/installation.md`
164
+ and both MCP registration snippets now use `npx --package @cosyte/cli cosyte …` /
165
+ `npx -y --package @cosyte/cli cosyte-mcp`, each measured working. A `cli` bin alias would fix the
166
+ short form and is deliberately not added, because `npm install -g` would then claim a command named
167
+ `cli` on the user's `PATH`.
168
+
169
+ ### Fixed
170
+
171
+ - **`@cosyte/cli` can be installed from npm again (CLI-UNINSTALLABLE-MANIFEST).** `0.0.1` and `0.0.2`
172
+ both published with all ten `@cosyte/*` sibling packages declared as `file:vendor/*.tgz` local
173
+ paths. `vendor/` is not in `files` and there is no `bundledDependencies`, so the tarball shipped
174
+ none of them and every install route died on the first: `ENOENT ...
175
+ node_modules/@cosyte/cli/vendor/cosyte-fhir-0.0.0.tgz`. The siblings are now real registry ranges:
176
+ `@cosyte/hl7` (`^0.0.7`) and `@cosyte/terminology` (`^0.0.9`) as hard `dependencies`, and the six
177
+ breadth parsers plus `@cosyte/transform` (`^0.0.4`) as `optionalDependencies`. Verified the way a
178
+ dry-run cannot: the packed tarball was installed in a clean directory outside the repo (exit `0`),
179
+ then both bins were run and the `.` subpath imported under ESM and CJS.
180
+
181
+ - **`VERSION` and `cosyte --version` now report the release you are running (CLI-VERSION-DRIFT).**
182
+ `src/core/version.ts` exported `"0.0.0"` while `package.json` said `0.0.2`, and the constant's own
183
+ doc comment claimed it was "synced with `package.json#version` on release by the Changesets
184
+ `version` script" when no such step existed. Confirmed in the published tarball: `@cosyte/cli@0.0.2`
185
+ ships `VERSION = "0.0.0"` in `dist/index.mjs` and `dist/index.cjs`. It reached two user-visible
186
+ surfaces, `cosyte --version` and the MCP server's advertised `serverInfo.version`. `scripts/sync-version.mjs`
187
+ (ported from `@cosyte/transform`) now runs inside the `version` script, and `test/sanity.test.ts`
188
+ compares the export against `package.json` so a skipped sync goes red rather than shipping.
189
+ **The two assertions that let five bad releases through the sibling packages are fixed here, not
190
+ just the value:** `docs-content/installation.md` asserted `typeof VERSION` (true of every wrong
191
+ value) and now asserts the exact version, which the sync script keeps in step; and the declaration's
192
+ `: string` annotation, which the sync script's pattern keys on, is pinned by its own test, so
193
+ dropping it fails at `pnpm test` instead of silently at release time.
194
+
195
+ - **The `attw` publish gate no longer exits 0 on an untyped pack (ATTW-FALSE-GREEN-PORT).** The
196
+ `attw` script was the bare CLI (`attw --pack . --profile node16`), and
197
+ `@arethetypeswrong/cli@0.18.4`'s `getExitCode.js` opens with `if (!analysis.types) return 0`,
198
+ returning before the problem list is read. So a tarball carrying no declarations at all printed
199
+ "This package does not contain types." and **exited 0**, and `verify.sh` propagated that 0 as a
200
+ pass. Reproduced on this package with zero concurrency and the real `--profile node16`
201
+ invocation, twice: with `dist/` removed, and with all ten of the build's declaration files
202
+ deleted and its JS left in place. The second is the realistic window, because `tsup` writes JS in
203
+ one pass and declarations in a later one; instrumented on one build here at a 10ms poll, all
204
+ eight `.mjs`/`.cjs` files appeared on a single poll at 4.92s and all ten declarations on a single
205
+ later poll at 11.82s, a 6.90s interval. That interval moves with load (7.85s on a busier box);
206
+ the ordering, and the ten declarations landing together, do not. Not answered with a lock or a
207
+ build queue: the gate is now able to report that its own inputs were missing, whatever removed
208
+ them.
209
+ - `scripts/attw.mjs` (ported from the fix shipped in `@cosyte/terminology`) runs two nets around
210
+ the real binary. A **preflight** that every relative path `package.json` promises exists and is
211
+ non-empty, naming the missing file; and a **post-check** that promotes the untyped sentence to a
212
+ failure, which catches declarations that are on disk but excluded from the tarball. Every
213
+ argument this repo passes is forwarded, so `--profile node16` keeps its exact meaning: measured
214
+ here, without it `@cosyte/cli/mcp` fails `node10` resolution and `attw` exits 1.
215
+ - **The preflight also walks `bin`, which the ported original did not**, because this is a `bin`
216
+ package and the sibling it came from ships no executable. Measured: with `dist/bin/cosyte.mjs`
217
+ deleted and everything else built, `attw --pack . --profile node16` printed every subpath green
218
+ and exited 0 over a tarball with no `cosyte` command in it.
219
+ - **`--quiet`, `-q`, `--format`, `-f` and `--config-path` are refused by option name and not by
220
+ value**, as is a `.attw.json` setting `quiet` or `format` (`readConfig()` applies it after
221
+ argv). Each was measured here to remove the untyped sentence from the output while still
222
+ exiting 0. `--config-path` is refused on a measurement rather than the inference the original
223
+ recorded. **Two limits are disclosed rather than closed**, both measured: the match is on an
224
+ exact argv token, so commander's attached and clustered short forms `-fjson` and `-Pf json` get
225
+ through (`-qP` does not; the wrapper treats an empty transcript as a failure), and a declared
226
+ path not starting with `.` is skipped by the preflight. The invocation this replaced exited 0
227
+ on the same pack with no arguments at all.
228
+ - **A correction to the ported message.** `files: ["dist"]` packs all ten declarations `tsup`
229
+ emits while `package.json` names only four; the shared `dist/io-<hash>.d.*` **and the four
230
+ `dist/bin/*.d.*`** ride along unnamed, and `analysis.types` is true if the tarball carries any
231
+ declaration. Measured on this tree: losing the four declared declarations gives "No types"
232
+ problems and **exit 1** while any of the other six survives, and only losing all ten gives the
233
+ untyped sentence and **exit 0**. So a partial declaration loss is caught by `attw` itself and
234
+ only a total one is the false green. The preflight reports both outcomes instead of asserting
235
+ the exit 0, which the inherited wording would have done falsely.
236
+ - `test/scripts/attw-gate.test.ts` pins all of it against the real binary, including attw's own
237
+ exit 0, a negative control on a well-formed package, that a genuine attw failure still fails
238
+ with attw's own status, and that the profile flag survives the wrapper in both directions.
239
+ - `scripts/verify.sh` in the meta-repo is unchanged; its propagation was never at fault.
240
+
37
241
  - **The route to an installable release, recorded in `RELEASING.md` and verified against the
38
242
  registry.** `@cosyte/hl7` (`0.0.3`), `@cosyte/terminology` (`0.0.4`) and all six breadth parsers
39
243
  would swap to real ranges today. `@cosyte/fhir` is unpublished (`FHIR-NPM-NAME`, an npm E403
@@ -44,6 +248,15 @@ this file is maintained by hand (Changesets handles the version bump and publish
44
248
  routed through a guarded loader to degrade to `CLI_PARSER_UNAVAILABLE` (exit `69`) rather than
45
249
  crash. `loadOptional()` cannot be reused unchanged: it takes a `CosyteFormat`, and `"transform"` is
46
250
  not one, and its diagnostic hardcodes the word "parser". Not undertaken here.
251
+ - **Superseded by the two `0.0.3` entries above**: the swap and the loader change were both
252
+ carried out. Two claims that entry made did not survive
253
+ contact: the "name similarity" reading of the `@cosyte/fhir` `E403` was **retracted across the
254
+ ecosystem on 2026-08-03** (the cause is unexplained; do not assert one), and "npm tolerates an
255
+ `optionalDependency` that fails to resolve" is true only in isolation. Declaring **both**
256
+ `@cosyte/fhir` and `@cosyte/transform` optional fails the install outright with `ERESOLVE`, which
257
+ is why `@cosyte/fhir` ended up not declared at all rather than declared optional.
258
+
259
+ ## [0.0.2] - 2026-07-31
47
260
 
48
261
  ### Added
49
262
 
@@ -84,6 +297,36 @@ this file is maintained by hand (Changesets handles the version bump and publish
84
297
  would be a wasted line. The wording is the one eight sibling packages already carry, confirmed
85
298
  against both rendered PNGs here rather than copied on trust.
86
299
 
300
+ ### Fixed
301
+
302
+ - **The public surface now states that `0.0.1` is published and uninstallable (ASSETS-P8).** `0.0.1`
303
+ published on 2026-07-29 with all ten `file:vendor/*.tgz` dependency specifiers intact. `vendor/` is
304
+ not in `files` and there is no `bundledDependencies`, so every install route (`npm i`, `npm i -g`,
305
+ `npx`) fails with `ENOENT` on `node_modules/@cosyte/cli/vendor/cosyte-fhir-0.0.0.tgz`. Reproduced
306
+ in a clean directory. A published version is immutable (ADR 0001), so `0.0.1` stays broken and the
307
+ fix must ship as a later version.
308
+ - `README.md` gains a **"Known issue"** section stating what fails, the exact error, why, and that
309
+ a source checkout is the only workaround. `docs-content/installation.md` gains the same under
310
+ **"Installing fails today"**. Both copies of the broken `npx`-based MCP registration snippet are
311
+ annotated (`README.md` and `docs-content/mcp.md`).
312
+ - **Four false claims corrected.** (1) `README.md` said "not yet published to npm"; it is
313
+ published. (2) It described swapping the vendored sibling deps for real `@cosyte/*` npm ranges as
314
+ a step still to come "at that flip"; the flip already happened without the swap, which is the
315
+ defect. (3) The same "not yet published" claim appeared in **three** `docs-content/` pages. (4)
316
+ The `redact`/`deid` terminal diagnostic, the `--help` text, three JSDoc blocks that compile into
317
+ `dist/*.d.ts`, and **five** docs pages said `@cosyte/deid` was unpublished, unshipped, or unbuilt;
318
+ it is published at `0.0.2`, and the accurate statement is that the CLI does not wire it yet. Every
319
+ count here was re-derived by census of the base tree, not estimated.
320
+ - **The "all eight formats" claim was checked and is correct**: `CosyteFormat` and `OP_SUPPORT` both
321
+ enumerate exactly eight.
322
+ - `RELEASING.md` records that the documented dependency-swap step was skipped, that a green
323
+ `npm publish --dry-run` cannot catch this (it packs a tarball but never resolves its deps from a
324
+ registry), and adds a checklist step to install the published version from outside the repo.
325
+
326
+ ## [0.0.1] - 2026-07-29
327
+
328
+ ### Added
329
+
87
330
  - **Phase 7: release hardening (the final roadmap phase; the CLI is feature-complete).** No new
88
331
  runtime command surface: this phase is publish-readiness.
89
332
  - **Fuzz gate over the two input boundaries.** `test/fuzz.property.test.ts` fuzzes the terminal
@@ -104,6 +347,7 @@ this file is maintained by hand (Changesets handles the version bump and publish
104
347
  honest per-(format, operation) support matrix, the PHI-default posture), a man-page-style
105
348
  `docs-content/reference-commands.md`, and `RELEASING.md` (the one-package-two-bins publish,
106
349
  provenance/OIDC, the vendored-`file:`→npm dep swap, and the two standing founder stops).
350
+
107
351
  - **Phase 6: six more formats + streaming + shell completion (ADR 0025).** The `cosyte` CLI now wraps
108
352
  **all eight cosyte formats**, routed through a single lazy **per-format adapter registry**
109
353
  (`src/core/parsers.ts`) that replaces the old per-command `hl7 ? : fhir` branches and makes support
@@ -162,6 +406,7 @@ parse` invocation never loads it; the core works with the SDK absent (`--omit=op
162
406
  (`createMcpServer`, `startStdioServer`, `dispatchTool`, `TOOL_DEFS`, and the MCP result types) on the
163
407
  `./mcp` subpath. `redact`/`deid` (gated on `@cosyte/deid`) and `map-codes` are deliberately not
164
408
  exposed as tools yet.
409
+
165
410
  - **Phase 4: `convert` / `map-codes` (the consumer-of-consumers commands).** Two commands that wrap
166
411
  the higher-layer libraries; the CLI adds **no** mapping or terminology logic of its own.
167
412
  - **`convert <file|-> --to fhir [--json] [--quiet]`**: **HL7 v2 → FHIR R4** via
@@ -192,6 +437,7 @@ parse` invocation never loads it; the core works with the SDK absent (`--omit=op
192
437
  lazy-loaded per command, so the `parse` fast path never loads them).
193
438
  - **ADR `0023`**: wire `@cosyte/transform` + `@cosyte/terminology`; the deliberate 2 → 4 cap raise
194
439
  (amends ADR 0021).
440
+
195
441
  - **Phase 3: `validate` / `inspect` / `fmt`.** Three commands over the two wired parsers
196
442
  (HL7 v2 + FHIR R4), each a thin wrapper that re-implements no library logic.
197
443
  - **`validate <file|-> [--profile] [--json] [--quiet]`**: parse + run the wrapped parser's own
@@ -218,6 +464,7 @@ parse` invocation never loads it; the core works with the SDK absent (`--omit=op
218
464
  helpers (behavior-preserving). New programmatic exports: `validateCommand`, `inspectCommand`,
219
465
  `fmtCommand`, `resolveInput`, `parseFailureResult`, `formatHl7Position`, `errorResult`. No new
220
466
  runtime dependencies: stays within the cap of 2.
467
+
221
468
  - **Phase 2: PHI posture hardened + `redact`/`deid` + `--unsafe-show-values`.**
222
469
  - **`--unsafe-show-values`**: a global, opt-in, PHI-exposing flag, resolved once and order-
223
470
  independently and funnelled through a **single chokepoint** (`core/phi.ts`), so the "a value
@@ -238,28 +485,35 @@ parse` invocation never loads it; the core works with the SDK absent (`--omit=op
238
485
  - New `CLI_NOT_IMPLEMENTED` diagnostic code and `EXIT.UNAVAILABLE` (`69`); new programmatic exports
239
486
  (`PhiPosture`, `VALUE_FREE`/`SHOW_VALUES`, `extractPhiPosture`, `unsafeInputSuffix`, `deidStatus`,
240
487
  `redactCommand`).
488
+
241
489
  - **Phase 1: the `cosyte parse` foundation.** Reshaped the scaffold from a library skeleton into a
242
490
  **`bin` package**: `package.json#bin` maps `cosyte` → `dist/bin/cosyte.mjs` (a shebang entry over a
243
491
  testable `core`), argument-parsed with Node's built-in `util.parseArgs` + a hand-rolled subcommand
244
492
  dispatcher (**no third-party CLI framework**).
493
+
245
494
  - **`cosyte parse <file|->`**: reads a file argument or stdin (`-`); **autodetects the format by
246
495
  content** (HL7 v2 `MSH` framing, FHIR JSON `resourceType`): conservative and fail-safe (a confident
247
496
  single match parses; ambiguity/no-match is a data error asking for `--format`, never a guess); routes
248
497
  to the wrapped parser (**lazy-loaded** per format); emits the parsed model as **typed JSON on
249
498
  stdout**. Flags: `--format`, `--json`, `--quiet`, `--no-color`.
499
+
250
500
  - **The exit-code contract** (`sysexits.h`-grounded, documented, tested): `0` success · `2` usage ·
251
501
  `65` data/parse error (`EX_DATAERR`) · `66` no input (`EX_NOINPUT`) · `70` internal (`EX_SOFTWARE`).
252
502
  The CLI never exits `0` on input it could not handle.
503
+
253
504
  - **Value-free diagnostic channel** with stable `CLI_*` codes (`CLI_FORMAT_UNDETECTED`,
254
505
  `CLI_FORMAT_AMBIGUOUS`, `CLI_FORMAT_UNSUPPORTED`, `CLI_NO_INPUT`, `CLI_EMPTY_INPUT`,
255
506
  `CLI_PARSE_FAILED`, `CLI_USAGE`, `CLI_INTERNAL`). **stdout is the data channel; every stderr line is
256
507
  value-free**: code + position only, never a field value. No temp files, no file logging.
508
+
257
509
  - **Programmatic `core` API** (the `.` subpath): `detectFormat` / `classifyCandidates` /
258
510
  `detectionError`, `EXIT`, `CLI_CODES` / `CliError`, `run`, `parseCommand`, `VERSION`.
511
+
259
512
  - **Runtime dependencies (ADR 0021):** `@cosyte/hl7` (`46d50eb`, v0.0.1) and `@cosyte/fhir` (`7a099b2`,
260
513
  v0.0.0) as **hard, first-party** deps (an `npx` bin cannot peer-depend) vendored as `pnpm pack`
261
514
  tarballs under `vendor/` until PUB-FLIP (`pnpm vendor:refresh`; umbrella ADR 0008). Capped at **2**
262
515
  by the umbrella `verify-policy.json`; third-party CLI-core runtime deps stay **zero**.
516
+
263
517
  - **ADRs:** `0021` (developer-tooling tier is a `bin` that hard-depends on first-party siblings;
264
518
  third-party runtime deps minimized) and `0022` (one repo, two bins: the CLI and the future
265
519
  `cosyte-mcp` MCP server over one core; the web playground is out of scope).
@@ -322,6 +576,7 @@ parse` invocation never loads it; the core works with the SDK absent (`--omit=op
322
576
  surrounding guarantees (value-free stderr, the never-a-fake `CLI_FORMAT_UNSUPPORTED`, the gated
323
577
  `redact`, the exit-code contract) are worded exactly as strongly as before. Internal traceability
324
578
  stays where the convention puts it: this file, the changesets, the commits and the roadmap.
579
+
325
580
  - **A dependency-budget figure left `src/core/deid.ts` too.** The module said wiring `@cosyte/deid`
326
581
  "would breach the CLI's runtime-dep cap (2)". Only the number was stale: the cap has been 4 since
327
582
  `convert`/`map-codes` landed, and the constraint itself still holds, because the package declares
@@ -330,10 +585,12 @@ parse` invocation never loads it; the core works with the SDK absent (`--omit=op
330
585
  constraint lapsed. The reasons that actually ground the refusal to ship a built-in redactor are
331
586
  untouched: `@cosyte/deid` is unpublished, the wrapped parsers expose no de-identification API, and
332
587
  a partial scrub would present a false-safety impression.
588
+
333
589
  - **`CosyteFormat`'s documentation no longer understates autodetection.** It read as though content
334
590
  detection recognised only HL7 v2 and FHIR, with the other six accepted by `--format` but "not yet
335
591
  wired", which has not been true since all eight formats gained signatures. The stale sentence is
336
592
  removed rather than restated.
593
+
337
594
  - **A gate now enforces the public-surface rule, which is why the class stops regrowing.**
338
595
  `scripts/check-no-internal-refs.sh` (`pnpm check:no-internal-refs`, on the `verify.sh cli` ladder)
339
596
  plus `.github/workflows/no-internal-refs.yml` port the shape of `hl7`'s gate
@@ -352,6 +609,7 @@ parse` invocation never loads it; the core works with the SDK absent (`--omit=op
352
609
  purpose, as it is in `hl7` and `ncpdp`: it ships inside the npm tarball, yet the convention names it
353
610
  as one of the places identifiers belong. That contradiction is ecosystem-wide, and it is recorded
354
611
  here rather than settled by one repo.
612
+
355
613
  - **`redact`/`deid` no longer names an internal tracking identifier on any consumer surface.** The
356
614
  `CLI_NOT_IMPLEMENTED` text printed when de-identification is unavailable carried an internal work
357
615
  item that means nothing to anyone running the command, and the same identifier reached the
@@ -360,25 +618,24 @@ parse` invocation never loads it; the core works with the SDK absent (`--omit=op
360
618
  `@cosyte/deid`, which is unpublished, and the CLI ships no built-in redactor because a partial
361
619
  scrub would present a false-safety impression. The stable `CLI_NOT_IMPLEMENTED` code and the exit
362
620
  `69` are unchanged.
621
+
363
622
  - **`CLI_PARSER_UNAVAILABLE`'s message no longer cites an internal decision record.** The error
364
623
  raised when an optional per-format parser is not installed pointed at an ADR number, which is
365
624
  meaningless to a caller; it now just names the package to install. The stable
366
625
  `CLI_PARSER_UNAVAILABLE` code and the exit `69` are unchanged. The same sweep removed the
367
626
  remaining roadmap-phase language from `README.md` and `docs-content/troubleshooting.md`.
627
+
368
628
  - **The CI checks are now binding on `main`.** `ci / verify (22, ubuntu-latest)`,
369
629
  `ci / verify (24, ubuntu-latest)`, `ci / actionlint` and `codeql / analyze (javascript-typescript)`
370
630
  are required status checks, each pinned to the `github-actions` app, alongside branch deletion and
371
631
  force-push protection. They were advisory before: a red check could not stop a merge. Dependabot
372
632
  now watches the npm and GitHub Actions dependency surfaces weekly, which nothing did previously.
633
+
373
634
  - **Reshaped the package from the parser-library scaffold to a `bin` package.** Removed the archetype
374
635
  stubs (`parseCli`, `WARNING_CODES`, `FATAL_CODES`); replaced the library `src/index.ts` and the
375
636
  round-trip property test with the command tree, the programmatic `core` API, and command-contract /
376
637
  autodetection / PHI-leak / equivalence tests. Rewrote `docs-content/` and `README.md` for the CLI.
377
638
 
378
- ### Deprecated
379
-
380
- ### Removed
381
-
382
639
  ### Fixed
383
640
 
384
641
  - **README + guides now describe the shipped Phase-3 command surface.** The `README.md` and
@@ -387,10 +644,12 @@ parse` invocation never loads it; the core works with the SDK absent (`--omit=op
387
644
  and the gated `redact`/`deid`) over the two wired parsers (HL7 v2 + FHIR R4). The pre-alpha,
388
645
  not-yet-published-to-npm status is unchanged (accurate), and the `npx`/`npm install -g` examples now
389
646
  carry a "not on npm yet" caveat (docs-only; README-ORG-SWEEP).
647
+
390
648
  - **`phi-scan` now scans the real fixture directory.** The scanner's fixture root pointed at a
391
649
  nonexistent `test/fixtures/`; it now walks `test/__fixtures__/` (and the same path in the staged
392
650
  filter), so the PHI commit-gate actually covers the CLI's synthetic fixtures.
393
651
 
394
- ### Security
395
-
396
- [Unreleased]: https://github.com/cosyte/cli/commits/main
652
+ [Unreleased]: https://github.com/cosyte/cli/compare/v0.0.3...HEAD
653
+ [0.0.3]: https://github.com/cosyte/cli/compare/v0.0.2...v0.0.3
654
+ [0.0.2]: https://github.com/cosyte/cli/compare/v0.0.1...v0.0.2
655
+ [0.0.1]: https://github.com/cosyte/cli/releases/tag/v0.0.1
package/README.md CHANGED
@@ -22,10 +22,9 @@ It is a thin, honest skin over libraries that already own correctness ([`@cosyte
22
22
  and owns two disciplines of its own: a documented **exit-code contract** and a **value-free
23
23
  diagnostic** posture.
24
24
 
25
- ## Known issue: `0.0.1` is published but cannot be installed
25
+ ## Known issue: `0.0.1` and `0.0.2` are published but cannot be installed
26
26
 
27
- **`@cosyte/cli@0.0.1` is on npm and `npm install @cosyte/cli` fails.** It ends in an `ENOENT`, like
28
- this:
27
+ **`npm install @cosyte/cli@0.0.1` and `@0.0.2` both fail.** They end in an `ENOENT`, like this:
29
28
 
30
29
  ```
31
30
  npm error code ENOENT
@@ -33,26 +32,33 @@ npm error path node_modules/@cosyte/cli/vendor/cosyte-fhir-0.0.0.tgz
33
32
  npm error enoent ENOENT: no such file or directory
34
33
  ```
35
34
 
36
- `npx @cosyte/cli …` and `npm install -g @cosyte/cli` fail the same way, and so does the `npx`-based
37
- MCP server registration below. There is no workaround from the consumer side. **Nothing is wrong with
38
- your setup, and re-running it will not help.**
35
+ `npx` and `npm install -g` fail the same way on those two versions, and so does the `npx`-based MCP
36
+ server registration below. There is no workaround from the consumer side. **Nothing is wrong with your
37
+ setup, and re-running it will not help.**
39
38
 
40
- **Why.** The published manifest declares its ten `@cosyte/*` sibling packages as local file paths
39
+ **Why.** Those manifests declared the ten `@cosyte/*` sibling packages as local file paths
41
40
  (`file:vendor/*.tgz`) rather than as npm version ranges. Those tarballs live in the git repository and
42
- are deliberately not part of the published package, so npm resolves the paths against a directory that
43
- does not exist in your `node_modules` and stops at the first one. It is a packaging defect in that
44
- release, not a fault in any command.
45
-
46
- **A published version is immutable, so `0.0.1` will stay broken.** The fix has to arrive as a later
47
- version, and it is blocked on something outside this package: `@cosyte/fhir` is not on npm yet (its
48
- name was rejected by the registry as too similar to an existing package, which is a naming problem
49
- rather than missing work), and `@cosyte/transform` is on npm but currently fails to install for the
50
- same reason, because it peer-depends on `@cosyte/fhir`. `@cosyte/hl7` and `@cosyte/terminology` are
51
- published and would swap over today. Until the naming issue is resolved, run the CLI from a source
52
- checkout (`pnpm install && pnpm build`, then invoke `dist/bin/cosyte.mjs`).
53
-
54
- > **Status:** pre-alpha (`0.0.x`), published to npm at `0.0.1`, which **cannot be installed** (see
55
- > above). The `cosyte` command wraps **all eight
41
+ are deliberately not part of the published package, so npm resolved the paths against a directory that
42
+ does not exist in your `node_modules` and stopped at the first one. It was a packaging defect in those
43
+ releases, not a fault in any command.
44
+
45
+ **A published version is immutable, so `0.0.1` and `0.0.2` stay broken: install a later version.** The
46
+ sibling packages are now declared as real npm ranges, and the resulting tarball has been installed from
47
+ outside this repository and exercised, which is the check a `npm publish --dry-run` cannot perform.
48
+
49
+ **One dependency could not be made real, and it costs you FHIR support.** `@cosyte/fhir` is not on the
50
+ npm registry, so it cannot be a dependency of this package at all; `@cosyte/transform` is on npm but
51
+ requires it, so npm skips that too. In an installed copy:
52
+
53
+ - FHIR `parse` / `inspect` / `fmt` / `validate` and the `convert` command report a value-free
54
+ `CLI_PARSER_UNAVAILABLE` and exit `69`. They never guess, and they never blame your input.
55
+ - HL7 v2, `map-codes`, and the six breadth formats (X12, C-CDA, DICOM, NCPDP, ASTM, MLLP) all work.
56
+
57
+ To use the FHIR commands today, run the CLI from a source checkout (`pnpm install && pnpm build`, then
58
+ invoke `dist/bin/cosyte.mjs`), where the FHIR library is supplied locally.
59
+
60
+ > **Status:** pre-alpha (`0.0.x`). `0.0.1` and `0.0.2` are on npm and **cannot be installed** (see
61
+ > above); a later version fixes that. The `cosyte` command wraps **all eight
56
62
  > cosyte formats** (**HL7 v2**, **FHIR R4**, **X12**, **ASTM**, **NCPDP SCRIPT**, **C-CDA**, **DICOM**,
57
63
  > and **MLLP**) plus the `@cosyte/transform` and `@cosyte/terminology` higher-layer libraries, with
58
64
  > conservative content-format autodetection and a documented exit-code contract:
@@ -82,25 +88,40 @@ checkout (`pnpm install && pnpm build`, then invoke `dist/bin/cosyte.mjs`).
82
88
  >
83
89
  > The CLI is **feature-complete**: an argv+stdin+MCP fuzz gate, an exit-code golden matrix, a
84
90
  > built-package smoke of both bins, and a clean `npm publish` dry-run. Those gates cover the code, and
85
- > the code is not what is broken. The one release step they do not cover is the dependency swap
86
- > described above, which is why `0.0.1` published green and still cannot be installed: a dry-run
87
- > builds the tarball but never resolves it from a registry. **The swap is blocked, not scheduled**,
88
- > and it stays blocked until `@cosyte/fhir` can be published under a name npm accepts, which also
89
- > unblocks `@cosyte/transform`. See [RELEASING.md](./RELEASING.md).
91
+ > the code was never what was broken. The one release step they do not cover is the dependency swap
92
+ > described above, which is why `0.0.1` and `0.0.2` published green and still cannot be installed: a
93
+ > dry-run builds the tarball but never resolves it from a registry. **That swap has now been made**,
94
+ > and installing the packed tarball from outside the repository is a release step in its own right.
95
+ > `@cosyte/fhir` is the one dependency it could not cover, which is why FHIR support is absent from an
96
+ > installed copy rather than merely deferred. See [RELEASING.md](./RELEASING.md).
90
97
 
91
98
  ## Run it
92
99
 
93
- > **None of the commands in this section work today.** `@cosyte/cli` is on npm, but every install
94
- > route fails with the `ENOENT` described in [the known issue](#known-issue-001-is-published-but-cannot-be-installed)
95
- > above. They are recorded here as the shape install will take once a fixed version can ship. Until
96
- > then, run it from a local checkout (`pnpm install && pnpm build`, then invoke `dist/bin/cosyte.mjs`).
100
+ > **These commands do not work yet, and the reason is now only that no fixed version has shipped.**
101
+ > The newest version on npm is `0.0.2`, and it is one of the two that cannot be installed. The
102
+ > packaging defect is fixed in this repository and proven by installing the packed tarball outside it,
103
+ > but a published version is immutable, so the fix reaches you only in the next release. Until then,
104
+ > run it from a source checkout: `pnpm install && pnpm build`, then invoke `dist/bin/cosyte.mjs`.
97
105
 
98
106
  ```bash
99
- npx @cosyte/cli parse message.hl7 # no install; format autodetected → HL7 v2
100
- npm install -g @cosyte/cli # or put `cosyte` on your PATH
101
- cosyte --help
107
+ npm install -g @cosyte/cli # put `cosyte` on your PATH
108
+ cosyte parse message.hl7 # format autodetected → HL7 v2
102
109
  ```
103
110
 
111
+ Or without installing, using `npx`:
112
+
113
+ ```bash
114
+ npx --package @cosyte/cli cosyte parse message.hl7
115
+ ```
116
+
117
+ > **`npx @cosyte/cli …` (the short form) does not work, and this is not the packaging defect above.**
118
+ > It fails with `could not determine executable to run`. When a package ships more than one
119
+ > executable, `npx` runs the one whose name matches the package name's last segment; that would be
120
+ > `cli`, and this package ships `cosyte` and `cosyte-mcp`. Naming the executable explicitly with
121
+ > `--package` is the supported form, and it works. A `cli` executable is deliberately **not** added as
122
+ > an alias: `npm install -g` would then put a command called `cli` on every user's `PATH`, which is far
123
+ > too generic a name to claim.
124
+
104
125
  ## `cosyte parse`
105
126
 
106
127
  Read a file (or stdin via `-`), autodetect the format by content, and print the parsed model as typed
@@ -274,15 +295,16 @@ subprocess**, not a hosted endpoint. Register it in an MCP client's config:
274
295
  ```json
275
296
  {
276
297
  "mcpServers": {
277
- "cosyte": { "command": "npx", "args": ["-y", "@cosyte/cli", "mcp"] }
298
+ "cosyte": { "command": "npx", "args": ["-y", "--package", "@cosyte/cli", "cosyte-mcp"] }
278
299
  }
279
300
  }
280
301
  ```
281
302
 
282
- > This registration **does not work yet**: `npx` has to install the package first, and that install
283
- > fails with the `ENOENT` described in
284
- > [the known issue](#known-issue-001-is-published-but-cannot-be-installed) above. To run the MCP
285
- > server today, point `command` at a built local checkout's `dist/bin/cosyte-mcp.mjs` instead.
303
+ > **`--package` is required, and the shorter `["-y", "@cosyte/cli", "mcp"]` does not work**: it fails
304
+ > with `could not determine executable to run`, for the `npx` executable-selection reason described
305
+ > under [Run it](#run-it) above. This also needs a version that can be installed at all, so not
306
+ > `0.0.1` or `0.0.2`. The `convert` tool reports `CLI_PARSER_UNAVAILABLE` from an npm install, because
307
+ > the FHIR library is not on the registry.
286
308
 
287
309
  `cosyte mcp` and the standalone `cosyte-mcp` bin both start the stdio server. It exposes four tools
288
310
  (`parse`, `validate`, `inspect`, `convert`), each calling the same command the terminal runs, so the CLI