diff --git a/.changeset/vale-platform-packages.md b/.changeset/vale-platform-packages.md new file mode 100644 index 00000000..f1c08155 --- /dev/null +++ b/.changeset/vale-platform-packages.md @@ -0,0 +1,11 @@ +--- +"@taskless/cli": minor +--- + +Ship Vale as per-platform binary packages. + +The CLI now declares `@taskless/vale--` as `optionalDependencies` pinned +to an exact version, so installing it also brings down a verified Vale binary for +the host platform — no lifecycle script, and nothing to download at runtime. Only +the matching platform installs; unsupported hosts install cleanly with none +present and continue to fall back to a `vale` found on `PATH`. diff --git a/openspec/changes/add-vale-binary-packages/.openspec.yaml b/openspec/changes/archive/2026-08-07-add-vale-binary-packages/.openspec.yaml similarity index 100% rename from openspec/changes/add-vale-binary-packages/.openspec.yaml rename to openspec/changes/archive/2026-08-07-add-vale-binary-packages/.openspec.yaml diff --git a/openspec/changes/add-vale-binary-packages/design.md b/openspec/changes/archive/2026-08-07-add-vale-binary-packages/design.md similarity index 100% rename from openspec/changes/add-vale-binary-packages/design.md rename to openspec/changes/archive/2026-08-07-add-vale-binary-packages/design.md diff --git a/openspec/changes/add-vale-binary-packages/proposal.md b/openspec/changes/archive/2026-08-07-add-vale-binary-packages/proposal.md similarity index 100% rename from openspec/changes/add-vale-binary-packages/proposal.md rename to openspec/changes/archive/2026-08-07-add-vale-binary-packages/proposal.md diff --git a/openspec/changes/add-vale-binary-packages/specs/cli/spec.md b/openspec/changes/archive/2026-08-07-add-vale-binary-packages/specs/cli/spec.md similarity index 100% rename from openspec/changes/add-vale-binary-packages/specs/cli/spec.md rename to openspec/changes/archive/2026-08-07-add-vale-binary-packages/specs/cli/spec.md diff --git a/openspec/changes/add-vale-binary-packages/specs/infrastructure/spec.md b/openspec/changes/archive/2026-08-07-add-vale-binary-packages/specs/infrastructure/spec.md similarity index 100% rename from openspec/changes/add-vale-binary-packages/specs/infrastructure/spec.md rename to openspec/changes/archive/2026-08-07-add-vale-binary-packages/specs/infrastructure/spec.md diff --git a/openspec/changes/add-vale-binary-packages/specs/vale-binary-packages/spec.md b/openspec/changes/archive/2026-08-07-add-vale-binary-packages/specs/vale-binary-packages/spec.md similarity index 100% rename from openspec/changes/add-vale-binary-packages/specs/vale-binary-packages/spec.md rename to openspec/changes/archive/2026-08-07-add-vale-binary-packages/specs/vale-binary-packages/spec.md diff --git a/openspec/changes/add-vale-binary-packages/tasks.md b/openspec/changes/archive/2026-08-07-add-vale-binary-packages/tasks.md similarity index 75% rename from openspec/changes/add-vale-binary-packages/tasks.md rename to openspec/changes/archive/2026-08-07-add-vale-binary-packages/tasks.md index 3efa61f5..ed756599 100644 --- a/openspec/changes/add-vale-binary-packages/tasks.md +++ b/openspec/changes/archive/2026-08-07-add-vale-binary-packages/tasks.md @@ -34,12 +34,12 @@ ## 6. CLI wiring -- [ ] 6.1 Add the platform packages to `packages/cli` `optionalDependencies` at literal exact versions — not `devDependencies` (never installed for consumers) and not `workspace:*` (would silently re-point at the newest stamp) -- [ ] 6.2 Verify installing the CLI on a supported platform yields a resolvable binary from the CLI's module context, and that an unsupported platform installs cleanly with none present -- [ ] 6.3 Confirm publishing a newer platform package leaves an unchanged CLI resolving its pinned version +- [x] 6.1 Add the platform packages to `packages/cli` `optionalDependencies` at literal exact versions — not `devDependencies` (never installed for consumers) and not `workspace:*` (would silently re-point at the newest stamp). Pinned at `3.17.1-20260810052605`; the lockfile records a registry resolution, not a workspace `link:`, which is what keeps the literal pin meaningful while the packages are also workspace members. +- [x] 6.2 Verify installing the CLI on a supported platform yields a resolvable binary from the CLI's module context, and that an unsupported platform installs cleanly with none present. `createRequire` from the CLI resolves `@taskless/vale-darwin-arm64/package.json`; the sibling `vale` is mode `755`, 37.7 MB, and reports `vale version 3.17.1`. Only the host-matching package installs — the other five are filtered by `os`/`cpu`. +- [x] 6.3 Confirm publishing a newer platform package leaves an unchanged CLI resolving its pinned version. Verified against two real published stamps: with `dist-tags.latest` at `3.17.1-20260810052605`, a project pinned to `3.17.1-20260810045147` installs `045147`. `^3.17.1` matches no version at all, so a range cannot float into a prerelease (D4). ## 7. Quality gates - [x] 7.1 `pnpm typecheck && pnpm lint && pnpm test` clean at the repo root - [x] 7.2 Dry-run the release workflow end to end without publishing, and confirm the packed tarball contains the executable with its permission bit -- [ ] 7.3 Once published, remove tasks 5.1b–5.1e from `add-vale-rule-engine`, which reduces to the runtime resolution (its task 5.1) alone +- [ ] 7.3 Once published, remove tasks 5.1b–5.1e from `add-vale-rule-engine`, which reduces to the runtime resolution (its task 5.1) alone. **Carried to `add-vale-rule-engine` (#71), the only branch where that file exists** — it is not present here, so this box cannot be ticked from this change. The precondition ("once published") is now met: all six packages are live at `3.17.1-20260810052605`. diff --git a/openspec/specs/cli/spec.md b/openspec/specs/cli/spec.md index db0be675..c5f233c6 100644 --- a/openspec/specs/cli/spec.md +++ b/openspec/specs/cli/spec.md @@ -406,3 +406,29 @@ When no candidate yields an executable, resolution SHALL fail with an error nami - **WHEN** no candidate location yields an executable - **THEN** resolution fails with an error naming the locations that were tried, rather than deferring to a spawn failure + +### Requirement: The CLI declares Vale platform packages as optional dependencies + +`packages/cli` SHALL declare every supported Vale platform package in `optionalDependencies`, so that installing the CLI also installs the Vale binary matching the host. Each SHALL be pinned to a literal exact version rather than a range or a workspace protocol, so that a newly published platform package reaches the CLI only through a deliberate change. + +The declaration SHALL NOT be a `devDependency`, which would not be installed for consumers of the CLI. `optionalDependencies` is required so an unsupported host installs the CLI successfully with no platform package present. + +#### Scenario: Installing the CLI brings the host's Vale binary + +- **WHEN** the CLI is installed on a supported platform +- **THEN** the matching Vale platform package is installed alongside it and the binary is resolvable from the CLI's module context + +#### Scenario: Unsupported platform still installs + +- **WHEN** the CLI is installed on a platform with no published Vale package +- **THEN** the install succeeds with no platform package present, and no error is raised at install time + +#### Scenario: Versions are pinned exactly + +- **WHEN** the CLI's `optionalDependencies` are inspected in a published tarball +- **THEN** each Vale platform package is pinned to a single exact version, not a range or workspace protocol + +#### Scenario: A newer platform package does not change the CLI + +- **WHEN** a platform package is published for a newer upstream Vale release and the CLI's pin is unchanged +- **THEN** the CLI continues to resolve the pinned version diff --git a/openspec/specs/infrastructure/spec.md b/openspec/specs/infrastructure/spec.md index 54966a8e..0a49ce8b 100644 --- a/openspec/specs/infrastructure/spec.md +++ b/openspec/specs/infrastructure/spec.md @@ -246,3 +246,24 @@ The workflow SHALL NOT include any publish, release, or npm registry push steps. - **WHEN** inspecting the workflow file - **THEN** there SHALL be no steps that run `pnpm publish`, `npm publish`, or interact with an npm registry + +## Requirements + +### Requirement: Script-versioned packages are excluded from changesets + +Workspace packages whose versions are assigned by a release workflow SHALL be listed in the changesets `ignore` configuration, so that changesets neither versions nor publishes them and no changeset is required for them. + +#### Scenario: Changesets does not version the platform packages + +- **WHEN** `changeset version` runs +- **THEN** the workflow-versioned platform packages are left at their current versions + +#### Scenario: Changesets does not publish the platform packages + +- **WHEN** the release flow publishes on the default branch +- **THEN** it publishes only the packages changesets manages, and the platform packages are untouched + +#### Scenario: A platform-package change needs no changeset + +- **WHEN** a pull request modifies only workflow-versioned platform packages +- **THEN** the changeset requirement check does not fail for the absence of a changeset diff --git a/openspec/specs/vale-binary-packages/spec.md b/openspec/specs/vale-binary-packages/spec.md new file mode 100644 index 00000000..fb0966f4 --- /dev/null +++ b/openspec/specs/vale-binary-packages/spec.md @@ -0,0 +1,123 @@ +# vale-binary-packages Specification + +## Purpose + +TBD - created by archiving change add-vale-binary-packages. Update Purpose after archive. + +## Requirements + +### Requirement: A Vale binary package is published per supported platform + +The repository SHALL publish one npm package per supported platform, each carrying the Vale executable for that platform in its published tarball. Each package SHALL declare `os` and `cpu` matching the binary it carries, so that a consumer installs only the package matching its host. + +#### Scenario: Only the host-matching package installs + +- **WHEN** a consumer installs a package that declares every platform package as an optional dependency +- **THEN** only the package matching the host's `os` and `cpu` is installed, and the remainder are skipped without failing the install + +#### Scenario: The binary is executable as published + +- **WHEN** a published platform package is extracted +- **THEN** the Vale executable is present with its executable permission bit intact, requiring no permission change by the consumer + +### Requirement: Platform packages contain no code and no install scripts + +A platform package SHALL contain only the Vale executable and package metadata — `package.json`, a `README`, and the upstream `LICENSE`. It SHALL NOT declare a `bin` entry, SHALL NOT contain executable JavaScript, and SHALL NOT define any lifecycle script. + +Consumers locate the binary by resolving the package and executing it by path, so nothing needs to be linked or copied into place at install time. Availability of the binary SHALL NOT depend on a consumer's package manager permitting dependency lifecycle scripts to run. + +#### Scenario: No lifecycle script is required for the binary to be usable + +- **WHEN** a consumer installs a platform package with dependency lifecycle scripts disabled +- **THEN** the Vale executable is present and usable, because no install-time step was needed to place it + +#### Scenario: Package declares no bin entry + +- **WHEN** a published platform package's `package.json` is inspected +- **THEN** it declares no `bin` entry and no `scripts` entry + +### Requirement: Platform packages are versioned as timestamped prereleases + +Every platform package SHALL be versioned `-`, where `` is the upstream Vale release it carries and the prerelease identifier is the UTC release timestamp. A plain `` SHALL NOT be published. + +#### Scenario: Version names its upstream Vale release + +- **WHEN** a platform package version is read +- **THEN** its `major.minor.patch` component is the upstream Vale version the package carries + +#### Scenario: Republishing the same Vale version is always possible + +- **WHEN** a packaging fix is needed for a Vale version that has already been published +- **THEN** a new package is published with the same `major.minor.patch` and a later timestamp, without requiring the Vale version component to change + +#### Scenario: A caret range cannot resolve a platform package + +- **WHEN** a consumer declares a dependency on a platform package using a caret or tilde range over the Vale version +- **THEN** no published version satisfies it, because every published version is a prerelease + +#### Scenario: Timestamps order monotonically + +- **WHEN** two platform package versions share a Vale version and differ by timestamp +- **THEN** the later timestamp is ordered as the greater version + +### Requirement: Fetched binaries are verified against committed checksums + +The repository SHALL commit a SHA256 checksum for each platform's upstream Vale release asset. The release pipeline SHALL verify every fetched binary against its committed checksum and SHALL refuse to publish on a mismatch. + +Verification SHALL occur before any step holding publish credentials handles the binary, so that a credentialed step only ever processes bytes matching a reviewed digest. + +#### Scenario: Checksum mismatch aborts the release + +- **WHEN** a fetched binary's SHA256 does not match the committed checksum for that platform +- **THEN** the release fails and nothing is published + +#### Scenario: Changing an expected binary requires review + +- **WHEN** the Vale version or a platform's release asset changes +- **THEN** the corresponding committed checksum must change in the repository, passing through code review before any publish can succeed + +### Requirement: Binaries are absent from version control + +The repository SHALL NOT store Vale executables in version control. A platform package directory SHALL contain only its source-controlled metadata, and the executable SHALL be placed into the package by the release pipeline before packing. + +#### Scenario: A clean checkout contains no binaries + +- **WHEN** the repository is cloned +- **THEN** no Vale executable is present in any platform package directory, and the working tree is clean + +### Requirement: Platform packages are released by their own workflow, tracking upstream + +Platform packages SHALL be versioned and published by a workflow dedicated to them, independent of the workflow that releases packages managed by changesets. That workflow SHALL compare the latest upstream Vale release against what the repository has already published, and SHALL publish only when upstream is ahead. + +A published-version check cannot bound these runs — every run stamps a previously unused timestamp — so the upstream comparison SHALL be what prevents redundant publishing. + +#### Scenario: Upstream unchanged publishes nothing + +- **WHEN** the workflow runs and the latest upstream Vale release is already published as a platform package +- **THEN** no package is versioned or published + +#### Scenario: A new upstream release opens a pull request rather than publishing + +- **WHEN** the workflow runs and upstream Vale is ahead of what the repository has published +- **THEN** it opens a pull request updating the pinned Vale version and the committed checksums, and publishes nothing + +#### Scenario: Merging the update publishes the set + +- **WHEN** that pull request is merged +- **THEN** every supported platform package is stamped with the same version and published together, verified against the checksums that were just reviewed + +#### Scenario: Ordinary pushes do not publish platform packages + +- **WHEN** a commit is pushed to the default branch +- **THEN** the changeset-managed release flow publishes no platform package + +### Requirement: Publishing a platform package changes no consumer + +Publishing a platform package SHALL NOT alter the behavior of any already-published consumer. A consumer SHALL reach a newly published platform package only by a deliberate, reviewed change to the version it pins. + +This is what allows the release workflow to run unattended. + +#### Scenario: A new platform package is inert until pinned + +- **WHEN** a platform package is published for a newer upstream Vale release +- **THEN** consumers continue to resolve the version they pin, and none resolves the new package until its pin is changed diff --git a/packages/cli/package.json b/packages/cli/package.json index 46d8a720..76e4df83 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -64,6 +64,12 @@ "@ast-grep/cli-linux-x64-gnu": "0.41.0", "@ast-grep/cli-win32-arm64-msvc": "0.41.0", "@ast-grep/cli-win32-ia32-msvc": "0.41.0", - "@ast-grep/cli-win32-x64-msvc": "0.41.0" + "@ast-grep/cli-win32-x64-msvc": "0.41.0", + "@taskless/vale-darwin-arm64": "3.17.1-20260810052605", + "@taskless/vale-darwin-x64": "3.17.1-20260810052605", + "@taskless/vale-linux-arm64": "3.17.1-20260810052605", + "@taskless/vale-linux-x64": "3.17.1-20260810052605", + "@taskless/vale-win32-arm64": "3.17.1-20260810052605", + "@taskless/vale-win32-x64": "3.17.1-20260810052605" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fdf5e6c6..c0e399ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -151,6 +151,24 @@ importers: '@ast-grep/cli-win32-x64-msvc': specifier: 0.41.0 version: 0.41.0 + '@taskless/vale-darwin-arm64': + specifier: 3.17.1-20260810052605 + version: 3.17.1-20260810052605 + '@taskless/vale-darwin-x64': + specifier: 3.17.1-20260810052605 + version: 3.17.1-20260810052605 + '@taskless/vale-linux-arm64': + specifier: 3.17.1-20260810052605 + version: 3.17.1-20260810052605 + '@taskless/vale-linux-x64': + specifier: 3.17.1-20260810052605 + version: 3.17.1-20260810052605 + '@taskless/vale-win32-arm64': + specifier: 3.17.1-20260810052605 + version: 3.17.1-20260810052605 + '@taskless/vale-win32-x64': + specifier: 3.17.1-20260810052605 + version: 3.17.1-20260810052605 packages/vale-darwin-arm64: {} @@ -813,6 +831,36 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@taskless/vale-darwin-arm64@3.17.1-20260810052605': + resolution: {integrity: sha512-182XIfZmbUaa8js7t11a7MB7Iw+tKsIUOXd8sDIKD7QhuEcdnrRt3dWHXRVR065tr86U5/z1tnHqenlWlx2BfA==} + cpu: [arm64] + os: [darwin] + + '@taskless/vale-darwin-x64@3.17.1-20260810052605': + resolution: {integrity: sha512-NR3fWrTTu1Lj/nWOcKTO27pGv+wRTwbETcrxmDCxbsP5wNGPUHk/lhZIdvR4JE2PcIBg79mDOuE3CGt/G8nlnQ==} + cpu: [x64] + os: [darwin] + + '@taskless/vale-linux-arm64@3.17.1-20260810052605': + resolution: {integrity: sha512-bxQgaTVxuxASzuLSVF/hm/blQnM8b3+2+y5swm91YbcCW8PW0/myjSCsNCazErfOwZzbdOdsSV4GJkNjmzSSsA==} + cpu: [arm64] + os: [linux] + + '@taskless/vale-linux-x64@3.17.1-20260810052605': + resolution: {integrity: sha512-XByCGwrckU2vueP1XoqIaUD5sqPsgqhWAlVKHhTlTql/5wecQLT6c6/WGa8g8uN4uRYbl7MG12eJPBHWabOXiw==} + cpu: [x64] + os: [linux] + + '@taskless/vale-win32-arm64@3.17.1-20260810052605': + resolution: {integrity: sha512-PxX0QZUTB1VR3h6T8A7/YJ8QtXCAEWnYifBrHHy0yYTPlgb7hQw69px26AAnlZy2ml7CClzt4LwF7Syq7gE+sw==} + cpu: [arm64] + os: [win32] + + '@taskless/vale-win32-x64@3.17.1-20260810052605': + resolution: {integrity: sha512-JBldnOID0Q7RM3jy9tr98ZI2DFav9UzLtRdR1bkoJ2i8X5XdkeEfKVD6Na5YNJ/C87I3z6tcZyoiIxcTKYcM9w==} + cpu: [x64] + os: [win32] + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -3039,6 +3087,24 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@taskless/vale-darwin-arm64@3.17.1-20260810052605': + optional: true + + '@taskless/vale-darwin-x64@3.17.1-20260810052605': + optional: true + + '@taskless/vale-linux-arm64@3.17.1-20260810052605': + optional: true + + '@taskless/vale-linux-x64@3.17.1-20260810052605': + optional: true + + '@taskless/vale-win32-arm64@3.17.1-20260810052605': + optional: true + + '@taskless/vale-win32-x64@3.17.1-20260810052605': + optional: true + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2