release: v0.2.0 — the work outlives the session - #81
Merged
Conversation
Owner
Author
AetherAI3
force-pushed
the
release/v0.2.0
branch
from
August 19, 2026 11:53
5579280 to
1b1b4d4
Compare
`aether resume` could replay a prior session's transcript to the screen, but the brain never saw a byte of it: continuing meant re-typing the story so far, and only ever on the machine where the work started. This makes the resume path mean what it says. A handoff (core/handoff.ts) is the machine-facing half of a session log — a small JSON document distilled from one run: the task, the model that ran it, the verify gate's verdict, the failing-test count, the files it changed, the verification command, and the repository identity (origin remote, branch, HEAD). - `aether agent --resume <id>` now prepends a continuation brief built from that record to the task the brain receives, so a different model picks the thread up with the project context in hand. With no new task, the run continues the original one (it used to fail with "nothing to do"). - `aether resume export [id] [--out <file>]` writes the handoff to a file. Nothing in it is keyed to an absolute path, so it can be copied to another checkout, machine, or OS, and `aether agent --resume <file>` continues there. A file reference is deliberately not workspace-scoped; a session id still is. - The handoff is a summary, not a transcript: no file contents, no shell commands, no credential-shaped values. Untrusted files are validated field by field on the way back in, and one written by a newer Agent is refused with an upgrade hint rather than half-read. Three fixes the above needed, each a bug in its own right: - `aether agent --local "<task>"` spawned the separately-installed Python brain unconditionally, so a plain `npm i -g aether-agents` could only ever answer `spawn python ENOENT`. The one-shot offline path now drives the Ollama brain that ships inside the package — the same one the REPL's `--local` turns already used. `AETHER_LOCAL_BRAIN=python` opts back in, and the choice is a pure decision in core/backend.ts beside chooseBackend. - The session log's credential filter matched `pat` as a substring, so `path` (and PATH, patch, pattern) was stored as "[REDACTED]". Every log could say a file changed but not which one. `pat` is now anchored to a whole segment; pat, gh_pat and pat_token are still redacted. - The Ollama brain reported a placeholder `remaining: 1` on any unsuccessful run, so an unreachable-Ollama turn printed "1 test failing" when no test had run. The failing count comes only from the host's own verify run. `npm run demo:handoff` (scripts/handoff-demo.ts, docs/demo/handoff.md) is the end-to-end proof and takes about five seconds: it builds a throwaway git repo with two genuinely failing tests, runs the real CLI on model A until half the work is done, exports the handoff, creates a second checkout at a different path, deletes the first checkout AND its logs, then finishes the job on model B with `--resume <file>` and no restated task. The model — and only the model — is a scripted local stub so the run is deterministic and needs no download or account; AETHER_DEMO_REAL=1 runs the identical script against real Ollama models. It asserts that session B's prompt carried the brief, that `node --test` is green when run independently of the agent, and that the verify gate exited 0, so it works as a CI gate as well as a demo.
…log once
Cleanup pass over the handoff feature. No intended behaviour change except
where a reused helper is strictly better than the hand-rolled version — those
are called out below.
Reuse:
- `writeHandoff` uses `atomicWriteFile` (core/durable_store.ts), the same
write-then-rename every other durable file in this CLI already uses. An
interrupted `resume export` can no longer destroy a good handoff or leave
half a JSON document, and `--out reports/handoff.json` now creates the
missing parent instead of failing with ENOENT.
- `readHandoff` uses `readJsonFile`, which already distinguishes missing from
unreadable from corrupt — so the hand-rolled ENOENT check is gone and an
empty file reads as "corrupt", the codebase's word for it, rather than
"Unexpected end of JSON input".
- Untrusted strings go through `sanitizeTerm` (ui/text.ts) and `clipCodePoints`
(ui/theme.ts) instead of a local clipper. A handoff arrives from another
machine and its strings are BOTH printed and prepended to the brain's prompt,
so it was the only untrusted-input surface in the CLI not passing through the
terminal sanitizer. `clipCodePoints` also stops a truncation from cutting a
surrogate pair in half.
- `wroteFile()` is now the one definition of "the run changed a file", called
by both a handoff's `filesTouched` and cmdCode's live blast-radius set, which
had the predicate written out twice.
`isHandoffPath` had a real defect: its separator class held only a forward
slash, so `--resume C:\work\handoff` (no .json) was classified as a session id
and died as "invalid session id". It now inverts `requireOpaqueId` — the same
rule `loadSession` enforces a moment later — so the two branches provably
partition the input, and Windows separators, `..`, and `~/` fall out for free.
Read the session log once. `resolveHandoff` loaded the session, distilled it,
and threw it away; `replaySession` then re-derived the file-vs-id decision and
loaded the same session again to render it. `resolveResume` returns
`{handoff, session}` and `resumeReplayLines` renders from it — one read, one
decision, and `replaySession`'s `ref`/`cwd` parameters disappear. On a
2000-event log that is 2000 fewer JSON parses and decodes per resume.
`aether resume` dispatches on argv like every other subcommand-bearing command
in main.ts, instead of joining `rest` into a string and re-splitting it by
prefix. `cmdResumeExport` is its own exported function. Side effect: `aether
resume abc def` reports `no such session: abc` rather than looking up the id
`"abc def"`.
Smaller: `summarizeEvents` bounds `highlights` as it goes rather than growing
to N and slicing to 40, and uses a Set for `filesTouched`; `parseHandoff`
bounds arrays before clipping them, so a hostile file with 10k highlights costs
40 clips rather than 10k; one `repoFrom` and one `asObject` replace two
spellings each; the demo's two model scripts are one shape with two
substitutions; the demo header no longer restates docs/demo/handoff.md.
Tests: the four resume-command cases share one `withLogRoot` fixture instead of
pasting the same 10-line prologue and env restore four times, and there are new
cases for the Windows path classification, the sanitizer, the shared write
predicate, and `--out` into a missing directory.
Deliberately not done: collapsing readRepoIdentity's three git spawns into two
(one saved spawn, subtler parsing); `latestSession`'s full-log scan (real, but
pre-existing and outside this diff); rebuilding `summarizeEvents` on top of
`monologueLine` (its tree-rendering shape is not a summary's).
The README's first screen led with the category ("a coding agent for your
terminal"), which is true of every tool on the shelf. It now leads with the two
things that are specific to this one and reproducible from a clean checkout:
the verify gate decides when a run is done, and a run's context can leave the
machine it started on.
- README hero: "Start a task on one model. Finish it on another, on another
machine. Your tests decide when it's done." — followed by the install command,
a runnable handoff example, and `npm run demo:handoff`.
- Two claims corrected rather than restated. The local brain was credited to
the Unlimited Context engine, which lives in a separate Python package the npm
tarball does not carry; the shipped offline brain talks to Ollama directly,
and the Python one is now described as the opt-in it is. `--test-cmd` was
documented as defaulting to `pytest -q`; it has no default, and a run without
one ends `unverified`, never `ok`.
- The version bumps to 0.2.0. npm still serves 0.1.0 from June, so every
`npm i -g aether-agents` today predates agent dev sessions, the media history,
doctor v2, and everything in this release. Publishing is release-triggered
(.github/workflows/release.yml), so cutting the tag is what ships it.
- The production verifier's test read the expected version from a literal
"v0.1.0". It reads package.json now: a version bump is a release step, not a
reason for the release gate to go red.
Release notes in RELEASE_NOTES.md and docs/releases/2026-08-19.md.
…t or your own Ollama
AetherAI3
force-pushed
the
release/v0.2.0
branch
from
August 19, 2026 12:11
1b1b4d4 to
2606756
Compare
AetherAI3
added a commit
that referenced
this pull request
Aug 22, 2026
…notes claim On 2026-08-20 this repository described five different releases at once. package.json said 0.2.0. package-lock.json still said 0.1.0 — the #81 bump never touched it. RELEASE_NOTES.md's top entry described a 0.2.0 whose feature list was written at 477f0fc, before #72 added `aether skills`, `aether capabilities` and `aether support-bundle`. The npm registry served 0.1.0 and nothing else. And a packed aether-agents-0.2.0.tgz sat in the repo root from #83 until #90 deleted it, containing a build with no skills runtime in it at all. Nothing was lying on purpose. The four statements drifted apart because nothing compared them. 0.2.0 -> 0.3.0, not 0.2.1 ------------------------- #72 is a backwards-compatible feature addition — a skills runtime, three new CLI commands, six built-in skills shipped inside the package — that the v0.2.0 notes never mentioned. Semver makes that a minor bump. Reusing 0.2.0 was rejected on identity grounds. 0.2.0 already names a specific dated artifact: the August 19 notes entry, the README availability paragraph, install.sh's pin example, and that committed tarball. Publishing different contents under the same version would make two materially different artifacts answer to one name, which is the defect this release exists to close. 0.2.0 is retired unused; it will never be published. Reconciled ---------- - package.json, package-lock.json (both version fields) and src/version.ts all say 0.3.0. - RELEASE_NOTES.md leads with v0.3.0 covering 477f0fc..ed094dc: one feature, nine user-visible fixes, three test-only commits, one unwired module (#86 ship rail, which no command invokes), one docs commit. The v0.2.0 entry is marked superseded rather than rewritten — it is the record of what was written that day, not an install instruction. - README and the release log name 0.3.0, and both still say plainly that `npm i -g aether-agents` gives you 0.1.0, because it does. New gate: test/release_coherence.test.ts ---------------------------------------- Gate A asserts every place that names the release names the same one — including both package-lock version fields, which is the drift nothing was watching. Gate B is the load-bearing one: for every feature the release notes claim, the code behind it must be present in the file list `npm pack` would actually ship, and the command exposing it must be in the CLI registry. The source checkout's dist/ is NOT the package — the allowlist is dist/src plus four docs. Mutation-checked. Adding "!dist/src/commands/skills.js" to the files allowlist silently drops `aether skills` from the tarball: verify:production -> {"ok":true, ... "packedFiles":523} MISSED IT release_coherence -> FAIL: dist/src/commands/skills.js (agent skills runtime — `aether skills`) Restored: 7/7 pass, 524 packed files. New: npm run release:candidate ------------------------------ release.yml only runs after a founder has already tagged and published, so everything it checks is checked too late to change the decision. This runs the same sequence, in the same order, before the tag exists — against a detached git worktree of a specific commit, never the dirty checkout. A dirty tree is refused unless --allow-dirty, which marks the report commitBound:false rather than pretending. Every CLI proof runs the binary npm installed from the packed tarball into a clean prefix: --version, --help, `skills list`, `capabilities`, and the handoff demo (the demo harness is not shipped, so it is placed beside the installed package and resolves the CLI and its imports from the tarball's own dist/src). Skipped steps are recorded as "not-run" with a reason and can never read as a green tick — the full npm test suite is release.yml's gate and says so in the report. No string is handed to a shell: npm is reached through its own JS entrypoint under node, so every argument stays an argument. Also ---- - scripts/verify-production.ts exports createPackReport, so a gate reasoning about what a user receives asks npm instead of reading the build directory. - test/release_canaries.test.ts's coverage map said canaries 5 and 7 were unwritable. #86 and #87 wrote them two commits later; the excuse outlived the condition. The map is now enforced by a test that reads the delegated files. - docs/releases/OPERATOR-PACKET-v0.3.0.md: the founder-owned publish sequence, with what is proven and what is explicitly not. Gates at this commit: npm run typecheck exit 0 release + production test files 21 pass / 0 fail npm run verify:production --tag v0.3.0 ok Full npm test NOT run locally (a tool_executor test does not return on this Windows box); CI is the authority. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AetherAI3
added a commit
that referenced
this pull request
Aug 23, 2026
…notes claim (#96) * release: cut v0.3.0, and make the package prove it contains what the notes claim On 2026-08-20 this repository described five different releases at once. package.json said 0.2.0. package-lock.json still said 0.1.0 — the #81 bump never touched it. RELEASE_NOTES.md's top entry described a 0.2.0 whose feature list was written at 477f0fc, before #72 added `aether skills`, `aether capabilities` and `aether support-bundle`. The npm registry served 0.1.0 and nothing else. And a packed aether-agents-0.2.0.tgz sat in the repo root from #83 until #90 deleted it, containing a build with no skills runtime in it at all. Nothing was lying on purpose. The four statements drifted apart because nothing compared them. 0.2.0 -> 0.3.0, not 0.2.1 ------------------------- #72 is a backwards-compatible feature addition — a skills runtime, three new CLI commands, six built-in skills shipped inside the package — that the v0.2.0 notes never mentioned. Semver makes that a minor bump. Reusing 0.2.0 was rejected on identity grounds. 0.2.0 already names a specific dated artifact: the August 19 notes entry, the README availability paragraph, install.sh's pin example, and that committed tarball. Publishing different contents under the same version would make two materially different artifacts answer to one name, which is the defect this release exists to close. 0.2.0 is retired unused; it will never be published. Reconciled ---------- - package.json, package-lock.json (both version fields) and src/version.ts all say 0.3.0. - RELEASE_NOTES.md leads with v0.3.0 covering 477f0fc..ed094dc: one feature, nine user-visible fixes, three test-only commits, one unwired module (#86 ship rail, which no command invokes), one docs commit. The v0.2.0 entry is marked superseded rather than rewritten — it is the record of what was written that day, not an install instruction. - README and the release log name 0.3.0, and both still say plainly that `npm i -g aether-agents` gives you 0.1.0, because it does. New gate: test/release_coherence.test.ts ---------------------------------------- Gate A asserts every place that names the release names the same one — including both package-lock version fields, which is the drift nothing was watching. Gate B is the load-bearing one: for every feature the release notes claim, the code behind it must be present in the file list `npm pack` would actually ship, and the command exposing it must be in the CLI registry. The source checkout's dist/ is NOT the package — the allowlist is dist/src plus four docs. Mutation-checked. Adding "!dist/src/commands/skills.js" to the files allowlist silently drops `aether skills` from the tarball: verify:production -> {"ok":true, ... "packedFiles":523} MISSED IT release_coherence -> FAIL: dist/src/commands/skills.js (agent skills runtime — `aether skills`) Restored: 7/7 pass, 524 packed files. New: npm run release:candidate ------------------------------ release.yml only runs after a founder has already tagged and published, so everything it checks is checked too late to change the decision. This runs the same sequence, in the same order, before the tag exists — against a detached git worktree of a specific commit, never the dirty checkout. A dirty tree is refused unless --allow-dirty, which marks the report commitBound:false rather than pretending. Every CLI proof runs the binary npm installed from the packed tarball into a clean prefix: --version, --help, `skills list`, `capabilities`, and the handoff demo (the demo harness is not shipped, so it is placed beside the installed package and resolves the CLI and its imports from the tarball's own dist/src). Skipped steps are recorded as "not-run" with a reason and can never read as a green tick — the full npm test suite is release.yml's gate and says so in the report. No string is handed to a shell: npm is reached through its own JS entrypoint under node, so every argument stays an argument. Also ---- - scripts/verify-production.ts exports createPackReport, so a gate reasoning about what a user receives asks npm instead of reading the build directory. - test/release_canaries.test.ts's coverage map said canaries 5 and 7 were unwritable. #86 and #87 wrote them two commits later; the excuse outlived the condition. The map is now enforced by a test that reads the delegated files. - docs/releases/OPERATOR-PACKET-v0.3.0.md: the founder-owned publish sequence, with what is proven and what is explicitly not. Gates at this commit: npm run typecheck exit 0 release + production test files 21 pass / 0 fail npm run verify:production --tag v0.3.0 ok Full npm test NOT run locally (a tool_executor test does not return on this Windows box); CI is the authority. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(release): fill the operator packet with the commit-bound candidate evidence Run against a63e1c6 in a detached worktree: commitBound true, ok true, exit 0. Tarball aether-agents-0.3.0.tgz, 524 entries, 589,829 bytes packed / 2,435,029 unpacked, sha256 25f33524bd866275674eccbf8cfe5706f14e925cb0ba35861dc6bc21a9245a2d. The digest came out identical from an uncommitted tree and from the detached worktree, which is recorded as content stability across a docs-only commit and explicitly NOT as a cross-machine reproducibility claim. Also records the mutation check: dropping dist/src/commands/skills.js from the files allowlist leaves verify:production reporting ok:true at 523 files, and fails release_coherence by name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * release: cover #98 in the notes, and make the coherence gate bidirectional The ground moved while PR #96 was open. #98 squash-merged to main, main became 426b124, and the v0.3.0 notes said nothing about it — which is this lane's own defect arriving from the direction the lane did not gate. WORK A: the notes now cover #98 ------------------------------- Its user-visible half is three fixes, and the first is the exact failure class this codebase forbids: - `aether doctor --live` never received --live. main.ts parses non-strictly, so a flag the command had not declared was captured globally and stripped from what the command was handed. --live ran the fast configured-only report and EXITED 0, presenting a live end-to-end verification that was never performed. Unknown rendered as verified. --deep, --dry-run, --no-ui and --only were lost the same way, and the whole --fix repair path was unreachable. - `aether doctor --fix --yes` answered "re-run with --yes" to someone who had just passed it, because the global --yes never arrived either. - Command lookup lowercased the token while dispatch was case-sensitive, so `aether Vault` fell past the typo guard into a chat turn and billed a paid model call. The seam itself is announced too: a command now carries its own help metadata, flag table and loader in one entry, flag collisions are load-time errors, and reachability is structural rather than a regex over main.ts's source. Range moves 477f0fc..ed094dc -> 477f0fc..426b124, 16 commits -> 17, across RELEASE_NOTES.md, docs/releases/2026-08-22.md and the operator packet. Fixed a miscount while re-deriving: the packet said "9 user-visible fixes" over a list of ten. WORK B: the gate now runs both directions ----------------------------------------- Gate B ran notes -> package: a claim with no code behind it fails. It could not see the inverse — a user-visible command that ships with NO claim anywhere — and the inverse is what actually keeps happening, once per lane that lands between the note being written and the tag being cut. Gate C: every non-hidden command in the CLI registry must be announced by some release note, or named in SHIPPED_WITHOUT_A_NOTE with a reason. An explicit list is fine; silence is not. It is enforced in both directions — a stale entry fails, and an entry that IS announced fails — so it cannot rot into a permanent bypass that quietly absorbs the next unannounced command. Every entry must also appear in the operator packet, because the founder cutting the tag is the person who needs to know what goes out unannounced. 15 commands are on it today; all predate the release log or were announced by capability in the June 2026 entry, and none is new in this release. Mutation-checked, per the controller's instruction. Added to the registry: { name: "teleport", args: "<dest>", summary: "beam the working tree somewhere", section: "System" } verify:production -> {"ok":true, ... "packedFiles":527} MISSED IT release_coherence -> FAIL: no user-visible command ships without either a release note or a named exemption + [ 'teleport — beam the working tree somewhere' ] - [] Registry restored byte-identical (md5 37b3f944…, `git status src/` empty), 10/10 pass. cli_registry.ts is read-only for this lane: it was mutated only in the working tree, never committed, and the restore is verified by digest. Also: Gate B now reads the registry by IMPORTING it instead of by regex over its source. #98 replaced "reachability asserted by a regex over main.ts" for exactly this reason, and it immediately split the commands across CLI_COMMANDS and DISPATCH_COMMANDS — a source regex keyed on one array would have silently stopped covering the other. doctor is in the second one. Gates at this commit: npm run typecheck exit 0 release_coherence 10 pass / 0 fail Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(release): re-cut the candidate on the new base, and record the digest change The base moved to 426b124 (#98), so every number in the packet was taken against a tree that no longer exists. Re-run, commit-bound, at 22aa021: commitBound true, ok true, exit 0 aether-agents-0.3.0.tgz sha256 8c5c119d93cabf49af0c49c97addb055308d508af93f8675a26b6f5c8ecba307 527 entries, 597,400 packed / 2,459,474 unpacked The digest CHANGED from the a63e1c6 run (25f33524…, 524 entries), and the packet now says why that is the correct outcome rather than a discrepancy: #98 added dist/src/core/command_dispatch.* and rewrote main.js, so the packed content is genuinely different. A digest that had survived that change would have meant the pack was not reading the tree. Both mutation checks were re-measured against this base rather than carried forward: skills drop verify:production {"ok":true, ... "packedFiles":526} MISSED IT release_coherence FAIL: dist/src/commands/skills.js restored -> 527 files, 10/10 pass teleport verify:production {"ok":true, ... "packedFiles":527} MISSED IT release_coherence FAIL: 'teleport — beam the working tree somewhere' restored -> registry byte-identical, 10/10 pass I had initially edited the skills figure from 523 to 526 by inference. That is a number nobody measured, so it was re-run before being written down. Release-owned suite at this commit: 24 pass / 0 fail. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * release: regenerate the v0.3.0 truth against the merged main * release: cover #105 in the v0.3.0 notes * release: bind the v0.3.0 operator packet to the head that carries #105 Regenerates the packet against a845479 (origin/main after #105): base SHA, evidence commit, tarball digest, packed entry count, the recorded release-candidate run, and the coverage breakdown. Two corrections rather than refreshes: - Section 5 no longer lists the full test suite as unproven. It was run at the evidence commit (1464 tests, 1460 pass, 0 fail, 4 skipped); what is genuinely unproven is the suite INSIDE the candidate run, which reports npm-test as not-run by design. - Two new unproven entries name the deployed API. The notes' Authentication claims are about a server this repository cannot test, and #105 exists precisely because `aether agent` does not work end to end against production today. Both are marked rather than implied. * release: record the measured tarball size, not a rounded one The packed size in the packet header was written from npm's rounded "740.0 kB" notice rather than measured. The tarball is 739,977 bytes. A digest and a byte count that disagree are the same class of defect this packet exists to close. --------- Co-authored-by: DBarr3 <143002219+DBarr3@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #80. Everything here is packaging: no behaviour changes.
Why
npm still serves 0.1.0, published 2026-06-11. Every
npm i -g aether-agentstoday predates agent dev sessions, the durable media history,doctorv2, and the whole of #80. Publishing is release-triggered (.github/workflows/release.yml), so cutting the tag is what ships it.README first screen
The old hero led with the category — "a coding agent for your terminal" — which is true of every tool on the shelf. It now leads with the two things specific to this one, both reproducible from a clean checkout:
Below it: the install command, a runnable handoff example, and
npm run demo:handoff. Model tables, pricing, and platform links stay where they are, below the fold.Two claims corrected, not restated
AETHER_LOCAL_BRAIN=pythonopt-in it is.--test-cmdwas documented as defaulting topytest -q. It has no default — a run without one endsunverified, and neverok.The old hero paragraph also claimed QOPC memory made the agent "measurably better the more you use it". That is a server-side claim the client cannot demonstrate, so it is out of the first screen.
Version
0.1.0→0.2.0inpackage.jsonandsrc/version.ts(the lockstep test covers both), plus the pinned-version examples inREADME.mdandinstall.sh.test/production_hardening.test.tsasserted the literalv0.1.0. It readspackage.jsonnow — a version bump is a release step, not a reason for the release gate to go red.Release notes
RELEASE_NOTES.mdgains the dated v0.2.0 entry;docs/releases/2026-08-19.mdcarries the long form and is indexed indocs/releases/README.md.Verification
npm run typecheck— cleanversion+production_hardening— 9 tests green at 0.2.0, including the pack → global install →aether --version→aether --helpsmoke against the real tarballnpm pack --dry-run—aether-agents-0.2.0.tgz, 416 files, 2.0 MB unpacked, zero runtime dependenciesNot done here
Cutting the GitHub release (and therefore the npm publish) is deliberately left to a human — the draft text is ready in
docs/releases/2026-08-19.md.