skills/stash-edge/SKILL.md hardcodes @cipherstash/stack@1.0.0-rc.4 in four places. Nothing in the build or release pipeline rewrites it, so the moment the package leaves the prerelease line the shipped skill will tell Deno and Supabase Edge Function users to pin a release candidate in production.
The skill ships inside the stash npm tarball and is copied into customer repos by stash init, so this lands in someone else's codebase, not ours.
The four literals
| Line |
Context |
| 59 |
import … from 'npm:@cipherstash/stack@1.0.0-rc.4/wasm-inline' — the Deno example |
| 65 |
prose: @^1.0.0 will not match 1.0.0-rc.4 |
| 67 |
prose: use a prerelease-bearing range (@^1.0.0-rc.4) |
| 77 |
the deno.json import-map example |
Nothing updates them
Three plausible mechanisms, none of which reach the file:
- Changesets.
pnpm run release is pnpm run build && changeset publish. Changesets bumps package.json versions and writes CHANGELOG.md; it does not touch other markdown.
- The build.
packages/cli/tsup.config.ts:88 copies the skills with cpSync('../../skills', 'dist/skills', { recursive: true }) — a verbatim recursive copy, no transform or substitution.
- The existing version embed.
src/runtime-versions.ts / pinnedSpec / RELEASE_TRAIN_MANIFESTS do solve exactly this problem, but as a tsup define injected into compiled CLI code (the install commands the CLI prints at runtime). It never reaches markdown.
scripts/lint-no-dead-package-paths.mjs does scan skills/, but for dead package paths, not version strings.
It is not just a find-and-replace
The prose at lines 64–67 exists specifically to explain prerelease semver — that @^1.0.0 does not match 1.0.0-rc.4 because ranges exclude prereleases unless the range itself names one. Swapping the number to 1.0.0 would leave a paragraph explaining a hazard that no longer applies, which is its own kind of wrong guidance. Whatever fixes the version needs to flag the prose for a human, not silently rewrite it.
The existing mitigation is weak but real: the skill already says to check npm view @cipherstash/stack dist-tags.
Suggested fix
A test, rather than release-time codegen — following the precedent already set by packages/cli/tests/e2e/runtime-versions-embed.e2e.test.ts, whose docstring notes it reads the workspace manifests directly so it "never needs updating per release":
- Read
packages/stack/package.json's version.
- Scan
skills/**/SKILL.md for @cipherstash/stack@<version> specifiers.
- Fail if any names a version that disagrees.
That is roughly ten lines, needs no build step, and fires on the version-bump PR — the moment someone is in a position to update the surrounding prose as well.
Worth deciding at the same time whether the examples should pin exactly at all once the package is on a stable line, or switch to @^1.0.0 and delete the prerelease explanation entirely. The "pin exactly, Deno caches by specifier" advice stands on its own merits; the prerelease caveat does not.
Timing
Not a regression — the skill is unreleased, landing with #777. This needs to be resolved before @cipherstash/stack goes to 1.0.0, not before #777 merges.
skills/stash-edge/SKILL.mdhardcodes@cipherstash/stack@1.0.0-rc.4in four places. Nothing in the build or release pipeline rewrites it, so the moment the package leaves the prerelease line the shipped skill will tell Deno and Supabase Edge Function users to pin a release candidate in production.The skill ships inside the
stashnpm tarball and is copied into customer repos bystash init, so this lands in someone else's codebase, not ours.The four literals
import … from 'npm:@cipherstash/stack@1.0.0-rc.4/wasm-inline'— the Deno example@^1.0.0will not match1.0.0-rc.4@^1.0.0-rc.4)deno.jsonimport-map exampleNothing updates them
Three plausible mechanisms, none of which reach the file:
pnpm run releaseispnpm run build && changeset publish. Changesets bumpspackage.jsonversions and writesCHANGELOG.md; it does not touch other markdown.packages/cli/tsup.config.ts:88copies the skills withcpSync('../../skills', 'dist/skills', { recursive: true })— a verbatim recursive copy, no transform or substitution.src/runtime-versions.ts/pinnedSpec/RELEASE_TRAIN_MANIFESTSdo solve exactly this problem, but as atsupdefineinjected into compiled CLI code (the install commands the CLI prints at runtime). It never reaches markdown.scripts/lint-no-dead-package-paths.mjsdoes scanskills/, but for dead package paths, not version strings.It is not just a find-and-replace
The prose at lines 64–67 exists specifically to explain prerelease semver — that
@^1.0.0does not match1.0.0-rc.4because ranges exclude prereleases unless the range itself names one. Swapping the number to1.0.0would leave a paragraph explaining a hazard that no longer applies, which is its own kind of wrong guidance. Whatever fixes the version needs to flag the prose for a human, not silently rewrite it.The existing mitigation is weak but real: the skill already says to check
npm view @cipherstash/stack dist-tags.Suggested fix
A test, rather than release-time codegen — following the precedent already set by
packages/cli/tests/e2e/runtime-versions-embed.e2e.test.ts, whose docstring notes it reads the workspace manifests directly so it "never needs updating per release":packages/stack/package.json's version.skills/**/SKILL.mdfor@cipherstash/stack@<version>specifiers.That is roughly ten lines, needs no build step, and fires on the version-bump PR — the moment someone is in a position to update the surrounding prose as well.
Worth deciding at the same time whether the examples should pin exactly at all once the package is on a stable line, or switch to
@^1.0.0and delete the prerelease explanation entirely. The "pin exactly, Deno caches by specifier" advice stands on its own merits; the prerelease caveat does not.Timing
Not a regression — the skill is unreleased, landing with #777. This needs to be resolved before
@cipherstash/stackgoes to1.0.0, not before #777 merges.