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
112 changes: 112 additions & 0 deletions .agents/skills/netscript-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,27 @@ After the release PR merges:
`jsr:@netscript/cli@<version>`, and runs the published CLI smoke and scaffold runtime suite.
5. The manual `workflow_dispatch` path still accepts `published-version` for targeted rechecks.

### Minimum-Dependency-Age At Release Time

Deno 2.9's default ~24h minimum-dependency-age rejects a same-day publish: a package published
minutes ago is treated as too fresh to depend on. This bit the beta.10 cut twice in the same
release, back to back:

- #813 pinned `--minimum-dependency-age=0` on every `jsr:@netscript/*` invocation the production E2E
harness runs — but the harness still failed the very next run
([`29564434302`](https://github.com/rickylabs/netscript/actions/runs/29564434302)).
- #817 found the real cause: `deno x` resolves and installs the JSR executable, then internally
re-invokes `deno run` in a **child process** that does not inherit the parent's
`--minimum-dependency-age` flag. The fix bypasses `deno x` for fresh-plugin resolution and runs
`deno run --minimum-dependency-age=0` directly against the resolved entrypoint, keeping fresh
plugin resolution in one Deno process.

Both fixes are release-harness-only (`.llm/tools/release/` and the E2E suite). The same exposure
exists in the **shipped CLI's** own `deno x` plugin dispatch — tracked separately as a user-facing
product-policy decision in [#818](https://github.com/rickylabs/netscript/issues/818) (a project on a
fresh release hits the 24h wall on every `jsr:@netscript/*` resolution for its first day), not
silently patched here.

### Exact-version resolution and same-semver recovery

- Production E2E must resolve the exact just-published semver. Published NetScript invocations use
Expand Down Expand Up @@ -188,6 +209,97 @@ prerelease and repeat both gates. The release is not complete until the pair is
- #811: a SHA-bound canary publish plus exact canary-pinned production E2E is the mandatory evidence
boundary before stable publication.

## First-Publish Checklist

`publish.yml` auto-provisions new JSR packages (`.llm/tools/release/jsr-provision-packages.ts`), but
provisioning is not readiness. Before the first release that carries a brand-new workspace member,
verify by hand:

- README meets the production standard (Install / Quick example / Docs sections) — see #807 for the
bar `@netscript/mcp` was held to before its first publish.
- Tagline stays under the JSR byte cap.
- `license`, `exports`, and workspace `deno.json` config are complete and correct for the new
member.
- The docs site has coverage for the new package (or an explicit stub with a tracked follow-up).
- For a server or CLI package, do a live release-candidate/runtime validation pass, not just
`publish:dry-run` (see the same-semver republish section below for why dry-run passing is not
sufficient). With the canary channel (#812) now in place, live validation runs against the
canary-published surface and cites its pinned production E2E verdict — not the in-tree CLI, which
was the pre-canary bar (#808/#809: in-tree maintainer CLI against a real scaffolded app).

The `publish-readiness.ts` gate (#811/#812: new-package detection, README/tagline/license/exports
checks, docs-site coverage pointer) now runs inside the mandatory canary-publish channel — prefer
the automated `release:canary` verdict over this manual checklist; keep the manual pass for items
the gate does not yet cover.

## Same-Semver Republish (Partial-Publish Recovery)

Proven during the 0.0.1-beta.10 cut (2026-07-17,
[run `29558968037`](https://github.com/rickylabs/netscript/actions/runs/29558968037)): a
`publish.yml` run can partially succeed. Keep the unauthenticated graph-preflight outcomes separate
from the real Publish step's registry outcomes — they are two different steps in the same run and
must not be summed:

- **Publish preflight (real graph build, no upload)** deliberately runs with an invalid token to
build the full dependency graph without uploading. It reported 10 token failures and 25 skipped
dependents, then stopped at the auth boundary. None of these are registry outcomes — nothing was
uploaded.
- **Publish** (the real upload) then ran: 33 of the 35 workspace members published, `@netscript/mcp`
failed on the registry-rejected text import that #810 later fixed, and `@netscript/cli` was
skipped as a dependent of the failed `mcp` publish.

**JSR version immutability binds only to published members.** A member that failed or was skipped
can still publish at the exact same semver, and workspace `deno publish` skips already-registered
versions on the members that already succeeded (`Warning: Skipping, already published`) rather than
re-publishing or failing on them. This makes recovery a **fix-forward**, not a version bump,
whenever the fix does not touch content that already published.

Byte identity of every already-published member across the tag move is a **MANDATORY precondition**
for step 2 below, not an aspiration. Verify it with
`git diff <old-tag-sha> <new-tag-sha> -- <published-member-paths>` for every member that already
published; if the diff is non-empty for even one of them, that member's published content changed
and the tag move is not safe — use the semver-bump fallback instead.

Recovery steps, as executed for beta.10:

1. Fix forward to `main`. `8a8a9537` (#810) fixed the `@netscript/mcp` text-import rejection and is
the **direct child commit** of the `a5adb706` release-cut commit. Version **values** stayed at
`0.0.1-beta.10` in both commits, but this is not the same claim as "every version file
unchanged": `deno.json` itself changed between the two commits (its content differs even though
the version field does not).
2. Fast-forward the release tag to the fixed commit — **only** after the byte-identity precondition
above passes. Applied literally to beta.10, it does not: `git diff a5adb706 8a8a9537` is
non-empty for six members that run `29558968037` had already published — `@netscript/plugin-ai`,
`plugin-auth`, `plugin-sagas`, `plugin-streams`, `plugin-triggers`, and `plugin-workers` —
because their publish-included `src/**`/`services/**` files changed alongside the `mcp` fix.
beta.10 is therefore not a compliant byte-identical precedent for this step; it is evidence for a
narrower claim (see below).
3. Re-dispatch `publish.yml` via its `workflow_dispatch` `tag` input set to the existing tag (for
example `v0.0.1-beta.10`) — do not create a new GitHub Release; that would demand a new semver.
The workflow checks out the fast-forwarded tag ref, so already-published members log
`Skipping, already published` and only the previously-failed/skipped members publish for the
first time.
4. The Hard Release Completion Gate above still applies to the **re-dispatched** run, not the
original: beta.10's first `publish.yml` run failed
([`29558968037`](https://github.com/rickylabs/netscript/actions/runs/29558968037)); the
re-dispatch on the fast-forwarded tag succeeded
([`29562537123`](https://github.com/rickylabs/netscript/actions/runs/29562537123), `mcp` and
`cli` publishing for the first time); only that green run's artifact is what `e2e-cli-prod` is
allowed to validate against.

**What beta.10 actually demonstrates:** because JSR's `deno publish` skips already-registered
versions on members that already succeeded and fills in members that never reached the registry, the
re-dispatch on the fast-forwarded tag still completed the partial publish even though six
already-published members were not byte-identical across the move — those six simply logged
`Skipping, already published` with their pre-fix content permanently on the registry at
`0.0.1-beta.10`. This is evidence that JSR skips existing versions and fills unpublished members; it
is **not** evidence that a non-byte-identical tag move is safe. Do not repeat the beta.10 tag move
as a template — treat it as the incident that motivated the mandatory precondition above.

**Fallback:** a semver-bump hotfix is required instead whenever the byte-identity precondition fails
for any already-published member — same-semver republish can only ever fill in members that never
reached the registry, never re-publish over one that did.

## Rollback And Retry

- If `release:cut` fails before branch creation, fix the reported gate and rerun. Restore any
Expand Down
112 changes: 112 additions & 0 deletions .claude/skills/netscript-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,27 @@ After the release PR merges:
`jsr:@netscript/cli@<version>`, and runs the published CLI smoke and scaffold runtime suite.
5. The manual `workflow_dispatch` path still accepts `published-version` for targeted rechecks.

### Minimum-Dependency-Age At Release Time

Deno 2.9's default ~24h minimum-dependency-age rejects a same-day publish: a package published
minutes ago is treated as too fresh to depend on. This bit the beta.10 cut twice in the same
release, back to back:

- #813 pinned `--minimum-dependency-age=0` on every `jsr:@netscript/*` invocation the production E2E
harness runs — but the harness still failed the very next run
([`29564434302`](https://github.com/rickylabs/netscript/actions/runs/29564434302)).
- #817 found the real cause: `deno x` resolves and installs the JSR executable, then internally
re-invokes `deno run` in a **child process** that does not inherit the parent's
`--minimum-dependency-age` flag. The fix bypasses `deno x` for fresh-plugin resolution and runs
`deno run --minimum-dependency-age=0` directly against the resolved entrypoint, keeping fresh
plugin resolution in one Deno process.

Both fixes are release-harness-only (`.llm/tools/release/` and the E2E suite). The same exposure
exists in the **shipped CLI's** own `deno x` plugin dispatch — tracked separately as a user-facing
product-policy decision in [#818](https://github.com/rickylabs/netscript/issues/818) (a project on a
fresh release hits the 24h wall on every `jsr:@netscript/*` resolution for its first day), not
silently patched here.

### Exact-version resolution and same-semver recovery

- Production E2E must resolve the exact just-published semver. Published NetScript invocations use
Expand Down Expand Up @@ -188,6 +209,97 @@ prerelease and repeat both gates. The release is not complete until the pair is
- #811: a SHA-bound canary publish plus exact canary-pinned production E2E is the mandatory evidence
boundary before stable publication.

## First-Publish Checklist

`publish.yml` auto-provisions new JSR packages (`.llm/tools/release/jsr-provision-packages.ts`), but
provisioning is not readiness. Before the first release that carries a brand-new workspace member,
verify by hand:

- README meets the production standard (Install / Quick example / Docs sections) — see #807 for the
bar `@netscript/mcp` was held to before its first publish.
- Tagline stays under the JSR byte cap.
- `license`, `exports`, and workspace `deno.json` config are complete and correct for the new
member.
- The docs site has coverage for the new package (or an explicit stub with a tracked follow-up).
- For a server or CLI package, do a live release-candidate/runtime validation pass, not just
`publish:dry-run` (see the same-semver republish section below for why dry-run passing is not
sufficient). With the canary channel (#812) now in place, live validation runs against the
canary-published surface and cites its pinned production E2E verdict — not the in-tree CLI, which
was the pre-canary bar (#808/#809: in-tree maintainer CLI against a real scaffolded app).

The `publish-readiness.ts` gate (#811/#812: new-package detection, README/tagline/license/exports
checks, docs-site coverage pointer) now runs inside the mandatory canary-publish channel — prefer
the automated `release:canary` verdict over this manual checklist; keep the manual pass for items
the gate does not yet cover.

## Same-Semver Republish (Partial-Publish Recovery)

Proven during the 0.0.1-beta.10 cut (2026-07-17,
[run `29558968037`](https://github.com/rickylabs/netscript/actions/runs/29558968037)): a
`publish.yml` run can partially succeed. Keep the unauthenticated graph-preflight outcomes separate
from the real Publish step's registry outcomes — they are two different steps in the same run and
must not be summed:

- **Publish preflight (real graph build, no upload)** deliberately runs with an invalid token to
build the full dependency graph without uploading. It reported 10 token failures and 25 skipped
dependents, then stopped at the auth boundary. None of these are registry outcomes — nothing was
uploaded.
- **Publish** (the real upload) then ran: 33 of the 35 workspace members published, `@netscript/mcp`
failed on the registry-rejected text import that #810 later fixed, and `@netscript/cli` was
skipped as a dependent of the failed `mcp` publish.

**JSR version immutability binds only to published members.** A member that failed or was skipped
can still publish at the exact same semver, and workspace `deno publish` skips already-registered
versions on the members that already succeeded (`Warning: Skipping, already published`) rather than
re-publishing or failing on them. This makes recovery a **fix-forward**, not a version bump,
whenever the fix does not touch content that already published.

Byte identity of every already-published member across the tag move is a **MANDATORY precondition**
for step 2 below, not an aspiration. Verify it with
`git diff <old-tag-sha> <new-tag-sha> -- <published-member-paths>` for every member that already
published; if the diff is non-empty for even one of them, that member's published content changed
and the tag move is not safe — use the semver-bump fallback instead.

Recovery steps, as executed for beta.10:

1. Fix forward to `main`. `8a8a9537` (#810) fixed the `@netscript/mcp` text-import rejection and is
the **direct child commit** of the `a5adb706` release-cut commit. Version **values** stayed at
`0.0.1-beta.10` in both commits, but this is not the same claim as "every version file
unchanged": `deno.json` itself changed between the two commits (its content differs even though
the version field does not).
2. Fast-forward the release tag to the fixed commit — **only** after the byte-identity precondition
above passes. Applied literally to beta.10, it does not: `git diff a5adb706 8a8a9537` is
non-empty for six members that run `29558968037` had already published — `@netscript/plugin-ai`,
`plugin-auth`, `plugin-sagas`, `plugin-streams`, `plugin-triggers`, and `plugin-workers` —
because their publish-included `src/**`/`services/**` files changed alongside the `mcp` fix.
beta.10 is therefore not a compliant byte-identical precedent for this step; it is evidence for a
narrower claim (see below).
3. Re-dispatch `publish.yml` via its `workflow_dispatch` `tag` input set to the existing tag (for
example `v0.0.1-beta.10`) — do not create a new GitHub Release; that would demand a new semver.
The workflow checks out the fast-forwarded tag ref, so already-published members log
`Skipping, already published` and only the previously-failed/skipped members publish for the
first time.
4. The Hard Release Completion Gate above still applies to the **re-dispatched** run, not the
original: beta.10's first `publish.yml` run failed
([`29558968037`](https://github.com/rickylabs/netscript/actions/runs/29558968037)); the
re-dispatch on the fast-forwarded tag succeeded
([`29562537123`](https://github.com/rickylabs/netscript/actions/runs/29562537123), `mcp` and
`cli` publishing for the first time); only that green run's artifact is what `e2e-cli-prod` is
allowed to validate against.

**What beta.10 actually demonstrates:** because JSR's `deno publish` skips already-registered
versions on members that already succeeded and fills in members that never reached the registry, the
re-dispatch on the fast-forwarded tag still completed the partial publish even though six
already-published members were not byte-identical across the move — those six simply logged
`Skipping, already published` with their pre-fix content permanently on the registry at
`0.0.1-beta.10`. This is evidence that JSR skips existing versions and fills unpublished members; it
is **not** evidence that a non-byte-identical tag move is safe. Do not repeat the beta.10 tag move
as a template — treat it as the incident that motivated the mandatory precondition above.

**Fallback:** a semver-bump hotfix is required instead whenever the byte-identity precondition fails
for any already-published member — same-semver republish can only ever fill in members that never
reached the registry, never re-publish over one that did.

## Rollback And Retry

- If `release:cut` fails before branch creation, fix the reported gate and rerun. Restore any
Expand Down
Loading