Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 24 additions & 17 deletions .agents/skills/jsr-audit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,9 @@ This reveals:

**The dry-run is a static gate, not proof of a working publish.** It never loads the module over the
real `https:`/`jsr:` graph, so it cannot catch the runtime-only publish failures listed in
[JSR publish gotchas](#jsr-publish-gotchas-netscript-grounded) — a green `--dry-run` is necessary but
not sufficient. The authoritative post-publish check is the production CLI e2e (`e2e-cli-prod`), which
installs and runs the published package over the remote graph.
[JSR publish gotchas](#jsr-publish-gotchas-netscript-grounded) — a green `--dry-run` is necessary
but not sufficient. The authoritative post-publish check is the production CLI e2e (`e2e-cli-prod`),
which installs and runs the published package over the remote graph.

### Step 5: Audit the File List

Expand Down Expand Up @@ -561,18 +561,25 @@ here as the canonical home; the `deno doc --lint` full-export-surface bar is cov
**`deno publish --dry-run` is NOT publish-equivalent.** Every trap below passes a clean dry-run and
only fails at real publish/install time, because the dry-run analyzes the local source graph rather
than loading the module over the remote `https:`/`jsr:` graph a consumer resolves. Treat a green
dry-run as a static pre-check, never as the release verdict — the verdict is a real-publish preflight
plus `e2e-cli-prod` against the published package.

- **Embed assets via import attributes, never `readTextFile`/`fromFileUrl`.** A published module runs
over an `https:`/`jsr:` graph where there is no filesystem, so `Deno.readTextFile(...)` /
`fromFileUrl(new URL(..., import.meta.url))` crash at runtime. Bundle text/JSON assets with import
attributes instead (`import data from "./x.json" with { type: "json" }`, `with { type: "text" }`).
Guard: `deno lint` + the production CLI e2e (`e2e-cli-prod`) exercise the real remote graph.
- **Top-level `import.meta`/`fromFileUrl` crashes over https.** Same root cause as above but even for
path resolution: a top-level `fromFileUrl(new URL("...", import.meta.url))` throws when the module
is imported over `https:`/`jsr:`. Make it lazy (resolve inside the function that needs it) and
guard on `import.meta.url`'s protocol before touching the filesystem.
dry-run as a static pre-check, never as the release verdict — the verdict is a real-publish
preflight plus `e2e-cli-prod` against the published package.

- **Embed assets as generated TypeScript constants, never import attributes or runtime file reads.**
The JSR registry can reject `with { type: "text" }` or `with { type: "json" }` while building the
module graph even when local `deno publish --dry-run` passes. A published module also runs over an
`https:`/`jsr:` graph where `Deno.readTextFile(...)` /
`fromFileUrl(new URL(..., import.meta.url))` have no package filesystem. Generate checked-in
`*.generated.ts` constants and protect them with a regeneration-plus-diff freshness gate. Guard:
`release:preflight` + `e2e-cli-prod`. This import-attribute ban is conditional and preserves the
incident lineage: #138/#142 captured the alpha.6 half-publish, #143 recovered with string
constants, and beta.10 partially published before the registry-side failure was tracked as
[denoland/deno#35546](https://github.com/denoland/deno/issues/35546). Lift the ban only after that
issue is fixed, merged, and released **and** an authenticated canary publish of a text-import
probe is green.
- **Top-level `import.meta`/`fromFileUrl` crashes over https.** Same root cause as above but even
for path resolution: a top-level `fromFileUrl(new URL("...", import.meta.url))` throws when the
module is imported over `https:`/`jsr:`. Make it lazy (resolve inside the function that needs it)
and guard on `import.meta.url`'s protocol before touching the filesystem.
- **Self-referential subpath import trap (#188).** A package that imports its OWN bare specifier
(e.g. `@netscript/x/sub` from inside `@netscript/x`) resolves at publish time against the LATEST
PUBLISHED version on JSR, not the in-repo source — so the publish graph fails or silently binds an
Expand All @@ -584,8 +591,8 @@ plus `e2e-cli-prod` against the published package.
a PATCH to the package config; otherwise ensure the `@module` doc carries the intended landing
content.
- **Prerelease-only packages show `latest=null`.** A package with only prerelease versions has JSR
`latest = null`, which renders a blank version badge/README on the package page. This self-heals at
the first non-prerelease (stable) publish; no config fix is needed.
`latest = null`, which renders a blank version badge/README on the package page. This self-heals
at the first non-prerelease (stable) publish; no config fix is needed.

## Known Limitations (2024-2025)

Expand Down
57 changes: 34 additions & 23 deletions .agents/skills/netscript-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@ semantics and `netscript-pr` for branch, PR, and comment mechanics.

- Work from a clean release-prep branch.
- Confirm `deno task release:preflight` is green before cutting. It enforces the JSR-safe bundled
asset rule: publishable source must use text imports or generated string constants, not
`Deno.readTextFile(new URL(..., import.meta.url))` or an identifier assigned from that URL.
asset rule: publishable source must use generated TypeScript string constants. Import attributes
(`with { type: ... }`) and `Deno.readTextFile(new URL(..., import.meta.url))`-style runtime reads
are rejected because the JSR registry can reject them even when local dry-run passes.
- This import-attribute ban is conditional and carries its incident lineage: #138/#142 recorded the
alpha.6 half-publish, #143 recovered with string constants, and beta.10 partially published before
the same registry-side failure was tracked as
[denoland/deno#35546](https://github.com/denoland/deno/issues/35546). Lift the ban only after that
upstream issue is fixed, merged, and released **and** an authenticated canary publish of a
text-import probe is green.
- Do not delete `deno.lock`, delete caches, or run `deno cache --reload`.
- Do not publish from a local machine. Publishing stays in GitHub Actions through OIDC.
- Use `gh ... --body-file` for release PR creation and comments.
Expand All @@ -39,8 +46,9 @@ For rehearsal:
deno task release:cut -- <version> --dry-run
```

Dry-run performs the bump, residue check, and gates, then skips branch creation, commit, push, and PR.
Run it in a disposable copy if you do not want the working tree version-bumped after a failed gate.
Dry-run performs the bump, residue check, and gates, then skips branch creation, commit, push, and
PR. Run it in a disposable copy if you do not want the working tree version-bumped after a failed
gate.

## What The Command Proves

Expand All @@ -49,12 +57,14 @@ Run it in a disposable copy if you do not want the working tree version-bumped a
1. Version validation refuses invalid semver and equal or older versions.
2. Workspace bump sets the root version, every package/plugin `deno.json`, nested workspace
`deno.json` files, and `deno.lock` `@netscript/*` ranges to the new version.
3. Residue check aborts if the old version remains in JSON files or `deno.lock`.
4. `release:preflight` blocks JSR-unsafe import-meta-relative runtime reads.
5. `publish:dry-run` proves the package publish surface builds before the real publish.
6. `deno ci --prod` proves the production dependency graph installs from the locked graph.
7. Non-dry-run creates `release/cut-<version>`, stages only bumped files, commits
`chore(release): cut <version>`, pushes, and opens the release PR.
3. `gen:publish-assets` regenerates the registry-safe TypeScript constants from the bumped manifests
and source assets; CI independently enforces `check:publish-assets` freshness.
4. Residue check aborts if the old version remains in JSON files or `deno.lock`.
5. `release:preflight` blocks JSR-unsafe import attributes and import-meta-relative runtime reads.
6. `publish:dry-run` proves the package publish surface builds before the real publish.
7. `deno ci --prod` proves the production dependency graph installs from the locked graph.
8. Non-dry-run creates `release/cut-<version>`, stages the bumped manifests and regenerated publish
assets, commits `chore(release): cut <version>`, pushes, and opens the release PR.

## Merge And Publish Flow

Expand All @@ -66,17 +76,17 @@ After the release PR merges:
deno task release:publish -- v<version> --notes-file <intro.md>
```

This creates `v<version>`, which is what triggers `publish.yml`. There is no JSR publish
without a GitHub Release, so **never hand-run `gh release create`** — it forgets the intro and
leaves the `prerelease` flag set (which stranded the Latest badge on alpha.16 through alpha.19).
This creates `v<version>`, which is what triggers `publish.yml`. There is no JSR publish without
a GitHub Release, so **never hand-run `gh release create`** — it forgets the intro and leaves the
`prerelease` flag set (which stranded the Latest badge on alpha.16 through alpha.19).
- **The introduction summary is written by hand** — the prose describing what the release ships.
Pass it via `--notes-file <path>` or `--message "<text>"`; the tool refuses to run without one.
This is the single deliberately-manual step.
- **Everything else is generated:** the "What's Changed" merged-PR list, the "Closed Issues" list
since the previous release, and the `prerelease=false` + `make_latest` flags so the newest
release shows as **Latest**. The JSR package links are appended later by `publish.yml`.
- Rehearse with `--dry-run` to print the composed body before creating. Use `--prerelease`
(with `--no-latest`) only for a genuine prerelease that must not become Latest.
- Rehearse with `--dry-run` to print the composed body before creating. Use `--prerelease` (with
`--no-latest`) only for a genuine prerelease that must not become Latest.
2. `publish.yml` resolves the release version, runs the release preflight, runs publish dry-run, and
publishes every `@netscript/*` workspace member to JSR through OIDC.
3. On successful publish, `publish.yml` writes `version.txt` and uploads it as
Expand All @@ -103,18 +113,19 @@ prerelease and repeat both gates. The release is not complete until the pair is
- #122: one command prevents manual root version, member version, and lockfile drift.
- #123: `workflow_run` plus the run-id artifact removes the race where prod E2E installs before JSR
has the just-published CLI version.
- #133: text-import preflight catches the `import.meta.url` asset-read defect class that dry-run can
miss and real publish can reject.
- #133: asset preflight catches import attributes and `import.meta.url` asset-read defects that
dry-run can miss and real publish can reject.
- #146: `deno ci --prod` is already lockfile-frozen in Deno 2.9; an explicit extra flag is rejected.

## Rollback And Retry

- If `release:cut` fails before branch creation, fix the reported gate and rerun. Restore any dry-run
version bump with normal git checkout of the bumped files if you ran it in the live checkout.
- If the release PR is wrong before merge, push a follow-up commit to the release branch or close the
PR and cut again.
- If GitHub Release publish fails before any JSR package publishes, fix the workflow or gate and rerun
the release workflow.
- If `release:cut` fails before branch creation, fix the reported gate and rerun. Restore any
dry-run version bump with normal git checkout of the bumped files if you ran it in the live
checkout.
- If the release PR is wrong before merge, push a follow-up commit to the release branch or close
the PR and cut again.
- If GitHub Release publish fails before any JSR package publishes, fix the workflow or gate and
rerun the release workflow.
- If publish partially succeeds, do not delete the tag or lockfile. Deno 2.9 skips already-published
members on retry; rerun after fixing the failing member or gate.
- If `e2e-cli-prod` fails after publish, keep the release record intact, investigate against the
Expand Down
41 changes: 24 additions & 17 deletions .claude/skills/jsr-audit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,9 @@ This reveals:

**The dry-run is a static gate, not proof of a working publish.** It never loads the module over the
real `https:`/`jsr:` graph, so it cannot catch the runtime-only publish failures listed in
[JSR publish gotchas](#jsr-publish-gotchas-netscript-grounded) — a green `--dry-run` is necessary but
not sufficient. The authoritative post-publish check is the production CLI e2e (`e2e-cli-prod`), which
installs and runs the published package over the remote graph.
[JSR publish gotchas](#jsr-publish-gotchas-netscript-grounded) — a green `--dry-run` is necessary
but not sufficient. The authoritative post-publish check is the production CLI e2e (`e2e-cli-prod`),
which installs and runs the published package over the remote graph.

### Step 5: Audit the File List

Expand Down Expand Up @@ -561,18 +561,25 @@ here as the canonical home; the `deno doc --lint` full-export-surface bar is cov
**`deno publish --dry-run` is NOT publish-equivalent.** Every trap below passes a clean dry-run and
only fails at real publish/install time, because the dry-run analyzes the local source graph rather
than loading the module over the remote `https:`/`jsr:` graph a consumer resolves. Treat a green
dry-run as a static pre-check, never as the release verdict — the verdict is a real-publish preflight
plus `e2e-cli-prod` against the published package.

- **Embed assets via import attributes, never `readTextFile`/`fromFileUrl`.** A published module runs
over an `https:`/`jsr:` graph where there is no filesystem, so `Deno.readTextFile(...)` /
`fromFileUrl(new URL(..., import.meta.url))` crash at runtime. Bundle text/JSON assets with import
attributes instead (`import data from "./x.json" with { type: "json" }`, `with { type: "text" }`).
Guard: `deno lint` + the production CLI e2e (`e2e-cli-prod`) exercise the real remote graph.
- **Top-level `import.meta`/`fromFileUrl` crashes over https.** Same root cause as above but even for
path resolution: a top-level `fromFileUrl(new URL("...", import.meta.url))` throws when the module
is imported over `https:`/`jsr:`. Make it lazy (resolve inside the function that needs it) and
guard on `import.meta.url`'s protocol before touching the filesystem.
dry-run as a static pre-check, never as the release verdict — the verdict is a real-publish
preflight plus `e2e-cli-prod` against the published package.

- **Embed assets as generated TypeScript constants, never import attributes or runtime file reads.**
The JSR registry can reject `with { type: "text" }` or `with { type: "json" }` while building the
module graph even when local `deno publish --dry-run` passes. A published module also runs over an
`https:`/`jsr:` graph where `Deno.readTextFile(...)` /
`fromFileUrl(new URL(..., import.meta.url))` have no package filesystem. Generate checked-in
`*.generated.ts` constants and protect them with a regeneration-plus-diff freshness gate. Guard:
`release:preflight` + `e2e-cli-prod`. This import-attribute ban is conditional and preserves the
incident lineage: #138/#142 captured the alpha.6 half-publish, #143 recovered with string
constants, and beta.10 partially published before the registry-side failure was tracked as
[denoland/deno#35546](https://github.com/denoland/deno/issues/35546). Lift the ban only after that
issue is fixed, merged, and released **and** an authenticated canary publish of a text-import
probe is green.
- **Top-level `import.meta`/`fromFileUrl` crashes over https.** Same root cause as above but even
for path resolution: a top-level `fromFileUrl(new URL("...", import.meta.url))` throws when the
module is imported over `https:`/`jsr:`. Make it lazy (resolve inside the function that needs it)
and guard on `import.meta.url`'s protocol before touching the filesystem.
- **Self-referential subpath import trap (#188).** A package that imports its OWN bare specifier
(e.g. `@netscript/x/sub` from inside `@netscript/x`) resolves at publish time against the LATEST
PUBLISHED version on JSR, not the in-repo source — so the publish graph fails or silently binds an
Expand All @@ -584,8 +591,8 @@ plus `e2e-cli-prod` against the published package.
a PATCH to the package config; otherwise ensure the `@module` doc carries the intended landing
content.
- **Prerelease-only packages show `latest=null`.** A package with only prerelease versions has JSR
`latest = null`, which renders a blank version badge/README on the package page. This self-heals at
the first non-prerelease (stable) publish; no config fix is needed.
`latest = null`, which renders a blank version badge/README on the package page. This self-heals
at the first non-prerelease (stable) publish; no config fix is needed.

## Known Limitations (2024-2025)

Expand Down
Loading
Loading