|
| 1 | +# Plan 032: Reduce the `devframe` + `@devframes/hub` public API surface for 0.9 ⚠️ |
| 2 | + |
| 3 | +> **Executor instructions**: Follow this plan step by step. Run every |
| 4 | +> verification command and confirm the expected result before moving on. If a |
| 5 | +> STOP condition occurs, stop and report. When done, update this plan's row in |
| 6 | +> `plans/README.md`. |
| 7 | +> |
| 8 | +> **Drift check (run first)**: `git diff --stat ffdb690..HEAD -- packages/devframe/package.json packages/devframe/tsdown.config.ts packages/devframe/src/index.ts packages/devframe/src/node/index.ts packages/hub/src/index.ts` |
| 9 | +> On any change since this plan was written, compare against the "Current state" |
| 10 | +> excerpts below before proceeding; on a mismatch, STOP. |
| 11 | +
|
| 12 | +## Status |
| 13 | + |
| 14 | +- **Priority**: P2 |
| 15 | +- **Effort**: L (touches both packages' export maps + ~80 consumer import sites + docs) |
| 16 | +- **Risk**: MEDIUM (breaking, but 0.9 is already the breaking-cleanup release; every removed export is either unused or has a documented replacement) |
| 17 | +- **Depends on**: none (extends the 0.9 cleanup already started in #180 / `migration-0.9.md`) |
| 18 | +- **Category**: direction / tech-debt |
| 19 | +- **Planned at**: commit `ffdb690`, 2026-08-10 |
| 20 | + |
| 21 | +## Why this matters |
| 22 | + |
| 23 | +`devframe` publishes **39** entry points and `@devframes/hub` **6**. An audit of |
| 24 | +every consumer — the in-repo `plugins/*`, `examples/*`, |
| 25 | +`packages/next|nuxt|hub-ui|json-render*`, and the canonical external consumer |
| 26 | +`vitejs/devtools` (`packages/core` = node/server host, `packages/kit` = |
| 27 | +re-exports the whole hub surface under `DevTools*` aliases) — found: |
| 28 | + |
| 29 | +- Entry points **nobody imports anywhere** (code, docs, skills, templates). |
| 30 | +- The **primary authoring function `defineDevframe` is missing from the main |
| 31 | + barrel** and only reachable via `devframe/types`, making `devframe/types` the |
| 32 | + single most-imported entry point (78 files) for the wrong reason. |
| 33 | +- `devframe/node` exports **16 values, 12 of them internal impl with zero |
| 34 | + consumer usage** (host classes, rpc/scope/settings factories, host helpers). |
| 35 | +- `@devframes/hub` exports `DEFAULT_CATEGORIES_ORDER` from **four** entry points |
| 36 | + when consumers only ever import it from one. |
| 37 | + |
| 38 | +0.9 is already the breaking-cleanup release. This plan shrinks the surface to |
| 39 | +what consumers actually use, moves the primary author API to the front door, and |
| 40 | +records every change in `docs/guide/migration-0.9.md`. |
| 41 | + |
| 42 | +Consumer usage was measured across in-repo `plugins/*`, `examples/*`, |
| 43 | +`packages/*` and the external `vitejs/devtools` source; "unused" below means no |
| 44 | +consumer in any of those imports it. |
| 45 | + |
| 46 | +## Scope |
| 47 | + |
| 48 | +**In scope**: `packages/devframe` and `packages/hub` `exports` maps + `tsdown` |
| 49 | +entries + barrel source; the `defineDevframe` relocation; the `devframe/node` |
| 50 | +trim; the hub `DEFAULT_CATEGORIES_ORDER` de-dup; rewriting in-repo consumer |
| 51 | +imports; updating `docs/**`, `skills/devframe/**`, and templates; extending |
| 52 | +`docs/guide/migration-0.9.md`. |
| 53 | + |
| 54 | +**Out of scope**: any rename of a widely-used, well-placed export |
| 55 | +(`defineRpcFunction`, `createHostContext`, `startHttpAndWs`, the hub |
| 56 | +client/type surface); version bumps / releasing (needs separate human |
| 57 | +approval); changing `vitejs/devtools` (external — it follows the migration |
| 58 | +guide). |
| 59 | + |
| 60 | +## Decisions locked in (from the design consult) |
| 61 | + |
| 62 | +| Area | Decision | |
| 63 | +|------|----------| |
| 64 | +| Ambition | Remove dead subpaths **and** redesign the core shape. | |
| 65 | +| `defineDevframe` | Move onto the main `devframe` barrel; make `devframe/types` strictly type-only; steer all consumers to `devframe`. | |
| 66 | +| `devframe/node` | Drop all 12 internal exports; keep only the used server-assembly API. | |
| 67 | +| `devframe/node/auth` | **Keep public** (low-level auth layer). | |
| 68 | +| `devframe/rpc/client` + `rpc/transports/ws-client` | **Keep both**, document as the low-level / test client. | |
| 69 | +| `devframe/rpc/server` + `rpc/transports/ws-server` | **Keep** (hand-rolled-server hosts use them). | |
| 70 | +| `devframe/adapters/embedded` | **Remove** (one-liner `d.setup(ctx)`). | |
| 71 | +| `devframe/rpc/transports/ws-bun` | **Remove** (zero refs). | |
| 72 | +| `devframe/utils/hash`, `utils/promise`, `utils/scope` | **Remove** (zero refs). | |
| 73 | +| `devframe/utils/structured-clone` | **Keep**. | |
| 74 | +| `devframe/utils/agent-tool-name`, `utils/crypto-token`, `utils/streaming-channel` | **Keep all three** (internal primitives w/ occasional external use). | |
| 75 | +| `@devframes/hub` `DEFAULT_CATEGORIES_ORDER` | Keep only on `/constants`; drop from `.`, `/node`, `/client`. | |
| 76 | +| `@devframes/hub/initiate` (`initHub`) | **Keep as-is** (deliberate parallel to `devframe/initiate`). | |
| 77 | + |
| 78 | +### ⚠️ One item to confirm during review |
| 79 | + |
| 80 | +`packages/devframe/src/node/index.ts` documents `createContextRpcServer` |
| 81 | +(`./rpc-core`) as intentionally public "so hosts that bind their own transports |
| 82 | +reuse the exact session/auth wiring." No consumer uses it — `vitejs/devtools` |
| 83 | +rebuilds that wiring itself from the lower `devframe/rpc/server` + |
| 84 | +`devframe/rpc/transports/ws-server` primitives (`packages/core/src/node/ws.ts`), |
| 85 | +which this plan **keeps public**. The locked decision ("drop all 12") therefore |
| 86 | +removes `rpc-core` too. If the reviewer wants to preserve the documented |
| 87 | +custom-transport contract, split it out: keep `createContextRpcServer` and drop |
| 88 | +only the other 11. **Default: drop it with the rest.** |
| 89 | + |
| 90 | +--- |
| 91 | + |
| 92 | +## Current state |
| 93 | + |
| 94 | +### `packages/devframe/src/index.ts` |
| 95 | +```ts |
| 96 | +// Public API. The full defineDevframe + adapter surface lands in later commits. |
| 97 | +export * from './define' |
| 98 | +export type * from './types' |
| 99 | +``` |
| 100 | +`./define` exports only `defineRpcFunction`. `export type *` strips the |
| 101 | +`defineDevframe` **value** that lives in `./types/devframe.ts`, so it never |
| 102 | +reaches the main barrel. |
| 103 | + |
| 104 | +### `packages/devframe/src/define.ts` |
| 105 | +```ts |
| 106 | +import type { DevframeNodeContext } from 'devframe/types' |
| 107 | +import { createDefineWrapperWithContext } from 'devframe/rpc' |
| 108 | + |
| 109 | +export const defineRpcFunction = createDefineWrapperWithContext<DevframeNodeContext>() |
| 110 | +``` |
| 111 | + |
| 112 | +### `packages/devframe/src/types/devframe.ts` (tail) |
| 113 | +```ts |
| 114 | +export function defineDevframe(d: DevframeDefinition): DevframeDefinition { |
| 115 | + return d |
| 116 | +} |
| 117 | +``` |
| 118 | +`src/types/index.ts` does `export * from './devframe'`, so this function is the |
| 119 | +**only value** on the `devframe/types` entry. |
| 120 | + |
| 121 | +### `packages/devframe/src/node/index.ts` |
| 122 | +Barrels `./context`, `./host-agent`, `./host-diagnostics`, `./host-services`, |
| 123 | +`./host-views`, `./host-h3`, `./rpc-core`, `./rpc-shared-state`, |
| 124 | +`./rpc-streaming`, `./scope`, `./server`, `./settings`, `./storage`, `./utils`, |
| 125 | +`./agent-args`, a selective `RpcFunctionsHost` type, and the instance-registry |
| 126 | +pair. See the file for the exact lines. |
| 127 | + |
| 128 | +### `packages/hub/src/index.ts` |
| 129 | +```ts |
| 130 | +export { DEFAULT_CATEGORIES_ORDER } from './constants' |
| 131 | +export * from './define' |
| 132 | +export type * from './types' |
| 133 | +``` |
| 134 | +`DEFAULT_CATEGORIES_ORDER` is **also** re-exported by `src/node/index.ts:3` and |
| 135 | +`src/client/index.ts:3`; its canonical home is `src/constants.ts`. |
| 136 | + |
| 137 | +--- |
| 138 | + |
| 139 | +## Changes |
| 140 | + |
| 141 | +### A. Remove dead `devframe` subpaths |
| 142 | + |
| 143 | +Delete these keys from `packages/devframe/package.json` `exports` **and** their |
| 144 | +entries from `packages/devframe/tsdown.config.ts` (`serverEntries` / |
| 145 | +`clientEntries` / the client-dist check list, as applicable): |
| 146 | + |
| 147 | +| Entry | package.json key | tsdown entry key | |
| 148 | +|-------|------------------|------------------| |
| 149 | +| embedded adapter | `./adapters/embedded` | `adapters/embedded` (serverEntries) | |
| 150 | +| Bun WS transport | `./rpc/transports/ws-bun` | `rpc/transports/ws-bun` (serverEntries) | |
| 151 | +| hash util | `./utils/hash` | `utils/hash` (clientEntries **and** the `check-client-dist` list) | |
| 152 | +| promise util | `./utils/promise` | `utils/promise` (clientEntries **and** the `check-client-dist` list) | |
| 153 | +| scope util | `./utils/scope` | `utils/scope` (clientEntries **and** the `check-client-dist` list) | |
| 154 | + |
| 155 | +Then handle the now-orphaned source: |
| 156 | +- `src/adapters/embedded.ts` — delete (and its `__tests__` if any). |
| 157 | +- `src/rpc/transports/ws-bun.ts` — delete (and `ws-bun*.test.ts` if any). |
| 158 | +- `src/utils/hash.ts`, `src/utils/promise.ts`, `src/utils/scope.ts` — if |
| 159 | + nothing imports them internally (verify with the STOP check below), delete; |
| 160 | + otherwise switch the internal importer(s) to a relative import and leave the |
| 161 | + file un-exported. Verify: |
| 162 | + ```sh |
| 163 | + rg -n "utils/hash|utils/promise|utils/scope" packages/devframe/src --glob '!**/*.test.ts' |
| 164 | + ``` |
| 165 | + Expected: no non-test hits outside the files themselves. If a hit exists, |
| 166 | + STOP and report (a keeper was misclassified). |
| 167 | + |
| 168 | +`DevframeRuntime` in `types/devframe.ts` includes the string literal |
| 169 | +`'embedded'`. Leave the literal (it names a runtime mode, not the adapter). Only |
| 170 | +the `createEmbedded` adapter goes away. |
| 171 | + |
| 172 | +### B. Move `defineDevframe` to the main barrel; make `devframe/types` type-only |
| 173 | + |
| 174 | +1. In `src/types/devframe.ts`, **delete** the `defineDevframe` function (keep the |
| 175 | + `DevframeDefinition` interface and every option type). |
| 176 | +2. In `src/define.ts`, **add** it next to `defineRpcFunction`: |
| 177 | + ```ts |
| 178 | + import type { DevframeDefinition, DevframeNodeContext } from 'devframe/types' |
| 179 | + import { createDefineWrapperWithContext } from 'devframe/rpc' |
| 180 | + |
| 181 | + export const defineRpcFunction = createDefineWrapperWithContext<DevframeNodeContext>() |
| 182 | + |
| 183 | + /** Identity helper that types a devframe definition. */ |
| 184 | + export function defineDevframe(d: DevframeDefinition): DevframeDefinition { |
| 185 | + return d |
| 186 | + } |
| 187 | + ``` |
| 188 | + (`src/index.ts` already does `export * from './define'`, so `defineDevframe` |
| 189 | + now reaches the main barrel as a value. Drop the stale "lands in later |
| 190 | + commits" comment.) |
| 191 | +3. `src/types/index.ts` is unchanged; with the function gone it now re-exports |
| 192 | + **types only** — `devframe/types` becomes strictly type-only, matching its |
| 193 | + name. Keep the `devframe/types` entry point (kept per decision). |
| 194 | + |
| 195 | +**Consumer migration.** All types already reach the main barrel via |
| 196 | +`export type *`, so the canonical import is now `devframe`. Rewrite in-repo |
| 197 | +consumers: |
| 198 | +- Find value importers: |
| 199 | + ```sh |
| 200 | + rg -n "defineDevframe" plugins examples packages --glob '!**/dist/**' |
| 201 | + ``` |
| 202 | + Change `import { defineDevframe } from 'devframe/types'` → |
| 203 | + `import { defineDevframe } from 'devframe'`. |
| 204 | +- Steer type importers off `devframe/types` to `devframe`: |
| 205 | + ```sh |
| 206 | + rg -n "from 'devframe/types'" plugins examples packages --glob '!**/dist/**' |
| 207 | + ``` |
| 208 | + Rewrite each to `from 'devframe'` (preserving `import type` / inline `type`). |
| 209 | + ~78 files reference `devframe/types`; the change is mechanical. Do **not** |
| 210 | + touch `packages/hub/src/**` re-exports of `devframe/types` — those are the |
| 211 | + hub's own type barrel and stay. |
| 212 | + |
| 213 | +STOP condition: if any consumer imports a **value** (not a type) from |
| 214 | +`devframe/types` other than `defineDevframe`, STOP — the audit says there is |
| 215 | +none, so a hit means the surface changed. |
| 216 | + |
| 217 | +### C. Trim `devframe/node` to the used server-assembly API |
| 218 | + |
| 219 | +Rewrite `src/node/index.ts` to: |
| 220 | +```ts |
| 221 | +// Node-side public API for hosts that wire up their own runtime. |
| 222 | +export * from './context' // createHostContext (+ CreateHostContextOptions) |
| 223 | +export type { RpcFunctionsHost } from './host-functions' |
| 224 | +export * from './host-h3' // createH3DevframeHost (+ CreateH3DevframeHostOptions) |
| 225 | +export { listLiveDevframeInstances, registerDevframeInstance } from './instance-registry' |
| 226 | +export type { DevframeInstanceRecord, DevframeInstanceRegistration } from './instance-registry' |
| 227 | +export * from './server' // startHttpAndWs (+ StartHttpAndWsOptions, StartedServer) |
| 228 | +export * from './storage' // createStorage (+ CreateStorageOptions) |
| 229 | +export { isObject, normalizeHttpServerUrl } from './utils' |
| 230 | +``` |
| 231 | +Removed from the public barrel (source files **stay** for internal use): |
| 232 | +- Host classes: `./host-agent` (`DevframeAgentHost`), `./host-diagnostics` |
| 233 | + (`DevframeDiagnosticsHost`), `./host-services` (`DevframeServicesHostImpl`), |
| 234 | + `./host-views` (`DevframeViewHost`). |
| 235 | +- Factories: `./rpc-core` (`createContextRpcServer` + its option/result types — |
| 236 | + see the ⚠️ confirm note), `./rpc-shared-state` |
| 237 | + (`createRpcSharedStateServerHost`), `./rpc-streaming` |
| 238 | + (`createRpcStreamingServerHost`), `./scope` (`createScopedNodeContext`), |
| 239 | + `./settings` (`createNodeSettings`). |
| 240 | +- Helpers: `./agent-args` (`coerceAgentPositionalArgs`, `AgentArgsFallback`), |
| 241 | + and `toDialableHost` / `formatHostForUrl` from `./utils` (keep only `isObject` |
| 242 | + + `normalizeHttpServerUrl`, both used by `vitejs/devtools`). |
| 243 | + |
| 244 | +These are single-package internals reached via relative imports inside |
| 245 | +`packages/devframe/src` (e.g. `node/context.ts` news up the host classes); |
| 246 | +removing them from the barrel does not break internal wiring. Verify no consumer |
| 247 | +depended on them: |
| 248 | +```sh |
| 249 | +rg -n "DevframeAgentHost|DevframeServicesHostImpl|createContextRpcServer|createRpcSharedStateServerHost|createRpcStreamingServerHost|createScopedNodeContext|createNodeSettings|coerceAgentPositionalArgs|toDialableHost|formatHostForUrl" plugins examples packages/next packages/nuxt packages/hub-ui packages/json-render packages/json-render-ui --glob '!**/dist/**' |
| 250 | +``` |
| 251 | +Expected: no hits. (The `DevframeViewHost` / `DevframeDiagnosticsHost` **types** |
| 252 | +remain exported from `devframe/types` and re-exported by the hub — that is |
| 253 | +separate from these node **class** exports.) |
| 254 | + |
| 255 | +The `devframe/node` **entry point itself stays** (only its member set shrinks); |
| 256 | +no `package.json`/`tsdown` change here. |
| 257 | + |
| 258 | +### D. De-dup `@devframes/hub` `DEFAULT_CATEGORIES_ORDER` |
| 259 | + |
| 260 | +- `packages/hub/src/index.ts`: delete line |
| 261 | + `export { DEFAULT_CATEGORIES_ORDER } from './constants'`. |
| 262 | +- `packages/hub/src/node/index.ts`: delete line 3 |
| 263 | + (`export { DEFAULT_CATEGORIES_ORDER } from '../constants'`) and its 2-line |
| 264 | + comment. |
| 265 | +- `packages/hub/src/client/index.ts`: delete line 3 (same). |
| 266 | +- Keep the definition + export in `packages/hub/src/constants.ts`. |
| 267 | + |
| 268 | +Zero consumer churn: hub-ui (`src/client/constants.ts`) and the |
| 269 | +`vitejs/devtools` kit (`packages/kit/src/constants.ts`) already import it from |
| 270 | +`@devframes/hub/constants`. Verify: |
| 271 | +```sh |
| 272 | +rg -n "DEFAULT_CATEGORIES_ORDER" plugins examples packages --glob '!**/dist/**' | rg -v "packages/hub/src" |
| 273 | +``` |
| 274 | +Expected: only `/constants` imports. |
| 275 | + |
| 276 | +### E. Docs, skills, templates |
| 277 | + |
| 278 | +- `docs/adapters/embedded.md` — delete the page; drop it from |
| 279 | + `docs/adapters/index.md` and any sidebar/nav config. Where dynamic |
| 280 | + registration is described, show `await devframe.setup(ctx)` (standalone) or |
| 281 | + `mountDevframe` (hub) instead. |
| 282 | +- `skills/devframe/SKILL.md` — remove `createEmbedded` / `adapters/embedded` |
| 283 | + references; update any `defineDevframe` import example to `from 'devframe'`; |
| 284 | + ensure no example imports from `devframe/types`, `utils/hash|promise|scope`, |
| 285 | + or `rpc/transports/ws-bun`. |
| 286 | +- `skills/devframe/templates/*` and `docs/**` code samples — same import sweep: |
| 287 | + ```sh |
| 288 | + rg -n "from 'devframe/types'|adapters/embedded|createEmbedded|utils/hash|utils/promise|utils/scope|ws-bun" docs skills |
| 289 | + ``` |
| 290 | +- Node reference (`docs/**` node page) — remove the dropped node exports from |
| 291 | + any documented list. |
| 292 | + |
| 293 | +### F. Migration guide |
| 294 | + |
| 295 | +Append sections to `docs/guide/migration-0.9.md` (style: short prose + a |
| 296 | +before/after import table, matching the existing sections): |
| 297 | + |
| 298 | +1. **`defineDevframe` moves to the main entry** — `from 'devframe/types'` → |
| 299 | + `from 'devframe'`; note `devframe/types` is now type-only and types are |
| 300 | + importable from `devframe` directly. |
| 301 | +2. **`devframe/adapters/embedded` is removed** — replace |
| 302 | + `createEmbedded(d, { ctx })` with `await d.setup(ctx)` (or `mountDevframe` in |
| 303 | + a hub). |
| 304 | +3. **`devframe/rpc/transports/ws-bun` is removed** — build a Bun WS route with |
| 305 | + `createRpcServer` + `attachWsRpcTransport` (`devframe/rpc/server` + |
| 306 | + `devframe/rpc/transports/ws-server`), which remain public. |
| 307 | +4. **`devframe/utils/{hash,promise,scope}` are removed** — use |
| 308 | + `Promise.withResolvers()` for `promise`; inline the trivial `hash` / `scope` |
| 309 | + helpers if needed. |
| 310 | +5. **`devframe/node` slimmed** — list the removed host classes / factories / |
| 311 | + helpers; note hand-rolled-server hosts should compose from |
| 312 | + `devframe/rpc/server` + `devframe/rpc/transports/ws-server` + |
| 313 | + `devframe/node/hub-internals` (the path `vitejs/devtools` already uses). |
| 314 | +6. **`@devframes/hub` `DEFAULT_CATEGORIES_ORDER`** — import from |
| 315 | + `@devframes/hub/constants` (removed from `.`, `/node`, `/client`). |
| 316 | + |
| 317 | +--- |
| 318 | + |
| 319 | +## Verification |
| 320 | + |
| 321 | +Run from the repo root, in order: |
| 322 | + |
| 323 | +```sh |
| 324 | +pnpm lint --fix |
| 325 | +pnpm knip # must report no newly-unused files/exports; a deleted export that |
| 326 | + # left a dangling source file will surface here |
| 327 | +pnpm test # runs build first (tsnapi api-snapshot compares fresh dist) |
| 328 | +pnpm typecheck |
| 329 | +pnpm build |
| 330 | +``` |
| 331 | + |
| 332 | +Expected: |
| 333 | +- `knip` clean (if a kept-internal source file is now unused, either it was |
| 334 | + truly dead → delete it, or a relative importer is missing → fix it). |
| 335 | +- `pnpm test` regenerates the `tsnapi` API snapshots under |
| 336 | + `test/__snapshots__/`; **review the snapshot diff** — it is the authoritative |
| 337 | + record of the surface change and must show exactly the removed/moved exports |
| 338 | + and nothing else. Commit the updated snapshots. |
| 339 | +- `typecheck` clean across all workspaces (the consumer import rewrites in B are |
| 340 | + the main risk; a missed `devframe/types` value import fails here). |
| 341 | + |
| 342 | +STOP conditions: |
| 343 | +- `knip` flags an unexpected unused export → a consumer rewrite was missed. |
| 344 | +- The API snapshot shows a surface change **not** in this plan → investigate |
| 345 | + before committing. |
| 346 | +- Any `vitejs/devtools`-relied entry point (`devframe/rpc/server`, |
| 347 | + `devframe/rpc/transports/ws-server`, `devframe/node/hub-internals`, |
| 348 | + `devframe/node` {`isObject`,`normalizeHttpServerUrl`,`RpcFunctionsHost`}, |
| 349 | + `@devframes/hub/{client,node,types,constants}`) changed → revert that part; |
| 350 | + it must stay. |
| 351 | + |
| 352 | +## Suggested commit sequence (one PR) |
| 353 | + |
| 354 | +1. `feat(devframe)!: remove dead subpaths (embedded, ws-bun, utils hash/promise/scope)` |
| 355 | +2. `feat(devframe)!: slim the node barrel to the server-assembly API` |
| 356 | +3. `feat(devframe)!: move defineDevframe to the main entry; make /types type-only` (+ consumer rewrites) |
| 357 | +4. `feat(hub)!: export DEFAULT_CATEGORIES_ORDER only from /constants` |
| 358 | +5. `docs: 0.9 migration notes + skill/template/adapters-doc sweep` |
| 359 | + |
| 360 | +Each commit must pass `pnpm build && pnpm typecheck` on its own so the API |
| 361 | +snapshot updates land with the change that causes them. |
| 362 | + |
| 363 | +## Maintenance notes |
| 364 | + |
| 365 | +**Breaking (⚠️), intentional, all covered by `migration-0.9.md`.** Every removed |
| 366 | +export is either unused across all audited consumers or has a documented |
| 367 | +in-tree replacement. The kept low-level layer (`node/auth`, `rpc/client` + |
| 368 | +`ws-client`, `rpc/server` + `ws-server`, `utils/{agent-tool-name,crypto-token, |
| 369 | +streaming-channel,structured-clone}`) is retained deliberately as the primitives |
| 370 | +the ergonomic APIs are built on and that test harnesses / hand-rolled hosts |
| 371 | +consume. Releasing 0.9.0 itself requires separate human approval. |
0 commit comments