Repository toolchain

September 20, 2026 · View on GitHub

Use Vite+ for repository commands. Bun is the package manager and script runtime. Framework packages live in packages/*; runnable examples live in examples/*.

Versions {#source-of-truth-versions}

The root package.json owns shared dependency versions. Workspace manifests use catalog: for those dependencies and workspace:* for internal packages. The travel planner is a release consumer: its Effect Agent dependencies and compatible effect-cf version pin exact npm versions and advance together after publication. bunfig.toml disables implicit workspace linking, so only explicit workspace: dependencies use local source; registry dependencies, including transitive ones, stay on published packages. Commit the Bun lockfile; CI installs with --frozen-lockfile.

ToolRepository version
Bun1.4.2
Vite+0.3.2
Alchemy and its Cloudflare runtime2.0.0-beta.77 with upstream compatibility patches
Effect and its provider/platform/SQL/Atom/test packages4.0.0-rc.116
effect-cf0.44.1
TypeScript7.0.2
@effect/tsgo0.45.0
Node.js22.18+ or 24.11+

Public packages require effect@^4.0.0-rc.116 as a peer. The exact catalog pin supplies the development version. Raise the peer minimum when code needs a newer API. Private examples declare Effect as a regular dependency. Adapters depend on the platform and SQL implementations they use.

platform-cloudflare requires effect-cf@^0.44.1 and effect@^4.0.0-rc.116 as host peers and uses the exact catalog versions for development. Supply Effect SQL packages compatible with rc.116 for effect-cf. Consumers provide the shared runtime.

Root overrides keep Effect, its Node/browser platforms, shared SQL adapters, and test packages on the catalog versions, including dependencies of published consumers. The root also installs Alchemy's optional @effect/platform-bun peer at the shared Effect version so its Bun entry points remain available. Vite+ supplies Vitest except in the two Cloudflare packages, whose Workers pool requires a direct catalog-pinned Vitest dependency and a Vite task. Run those tasks through vp run. Operational harnesses under tooling/* also use Vite tasks for Miniflare tests.

VitePress uses its own Vite dependency. Keep the root Vite+ core alias required by Vite+; do not add a global Vite override.

Alchemy and its Cloudflare runtime advance together. Their published beta.77 packages and Distilled rc.9 clients still use Effect APIs renamed in rc.113. The version-specific Bun patches backport Alchemy's compatibility fix and Distilled's matching fix, including the published JavaScript entry points. The root declares mime because the Cloudflare runtime imports it without declaring the dependency. Keep these corrections until a published upgrade includes them; verify that upgrade with a frozen install and vp run check:deploy.

Current workspace

See the package map for public packages and capabilities.

DirectoryPurpose
packages/*Framework and private PR-review integration packages
examples/travel-plannerCanonical Cloudflare application, deployed with Alchemy
tooling/runtime-benchmarkDeterministic runtime comparisons
tooling/context-continuity-evalRelease continuity gates and deployed performance
tooling/cloudflare-memoryThread-to-Memory latency and heap measurements
tooling/browser-run-worker-proofOpt-in hosted Browser Run verification
tooling/pr-review-evalOpt-in live review evaluation
tooling/semantic-memory-evalSemantic-memory quality evaluation
action/PR-review Action contract and ignored build output

Framework code stays in packages/*. The canonical app and operational harnesses are leaf workspaces. Provider integrations come from upstream Effect AI Layers, including @effect/ai-typesafe. ai-decision owns thread model selection and consumes Effect's native Decision and DecisionModel.

effect-agent <- storage adapters
effect-agent <- workflow
effect-agent + selected adapters <- platform packages
effect-agent <- sandbox-local
effect-agent <- testing
effect-agent <- pr-review

Within packages/effect-agent/src, dependencies point inward: core <- engine <- capabilities <- durable and core <- sandbox <- capabilities. Public module paths address these implementations directly; source directories are not separate packages. Keep core and sandbox contracts platform-neutral. The export check enforces these internal boundaries as well as package imports.

Arrows point toward dependencies. An inward package must not import an outward one. The testing package consumes ai-decision as a devDependency for model integration fixtures. Shared compiler settings live in tsconfig.base.json.

Commands

Run vp help or vp <command> --help for options.

CommandUse
vp installInstall dependencies and hooks
vp checkFormat, lint, and type checks
vp fmt / vp fmt --checkFormat files / check formatting
vp lint / vp lint --fixLint / apply fixes
vp testRoot test runner
vp run checkAll static checks, package types, scripts, and purity
vp run testAll workspace suites, including Cloudflare
vp run buildPackage, docs, and Action builds
vp run readyFull handoff gate: check, test, build
vp run docs:devDocs development server
vp run docs:buildBuild docs and check links
vp run docs:previewPreview built docs
vp run docs:deploy --yesDeploy docs to the existing production stack
vp run check:deployLoad both deployment CLIs without deploying
vp run -F @effect-agent/example-travel-planner devCloudflare travel planner
vp env doctorDiagnose toolchain setup

Use vp run <task> for other scripts. Do not use bun run, npm run, pnpm run, yarn run, or invoke the wrapped compiler, formatter, linter, or test runner directly. Include vp env doctor output when asking for toolchain help.

Local tests run with at most four workspace tasks at once and without dependency ordering. CI gives the travel planner, context-continuity evaluation, runtime benchmark, Node platform, testing package, and both Cloudflare packages separate runners. The remaining workspace suites share one runner and run sequentially. Each suite keeps its own Vitest/workerd worker limits; running more heavy suites on one runner can starve ownership-lease renewals in crash tests. Builds follow dependency order. Process-kill, soak, and adapter contract suites are part of the ordinary test command.

Vite Task caches successful results against their inputs. Vitest's mutable result cache is disabled so it does not invalidate task caching. CI transfers node_modules/.vite/task-cache. Direct Vitest tasks, Node platform and Cloudflare-memory tests, and travel-planner tests and builds exclude generated Vite files and dependency directory listings, but track imported dependency files and the lockfile. Wrangler dry-run builds exclude their temporary .wrangler bundles; the Action build excludes its generated bundle from inputs and restores action/dist/index.mjs on a cache hit. Failed tasks are never cached. Vite Task fingerprints whole files, including package manifests: a version-only change can invalidate tests even when their source is unchanged. Keep manifests tracked because exports, module type, and dependency declarations also affect execution.

Use vp run -v test for cache decisions, vp run --last-details for the previous run, or vp run --no-cache test to rerun every suite.

Storage certification reports

The repository runs the same certification against memory, SQLite, and Cloudflare adapters. Set EFFECT_AGENT_CERTIFICATION_OUT in a Node certification test to write a local schema-encoded report. Set PRINT_REPORT in the Cloudflare test file to print its workerd report.

Documentation examples

Lead implementation guides with a short, concrete code example after at most one introductory sentence. Explain behavior after the code it describes. Keep complete setup available and type-check examples through their runnable entry points; move advanced contracts and recovery details into linked reference pages instead of front-loading them in walkthroughs.

The homepage imports docs/snippets/travel-planner/*.ts. Edit those files to change its examples. A twoslash fence enables type hovers and compiler validation during vp run docs:build. Relative imports resolve from that snippet directory.

Twoslash uses the pinned typescript-twoslash JavaScript compiler API; repository checks use TypeScript 7. Production builds reuse Twoslash compiler and filesystem caches within the process; the dev server disables both so imported snippet edits remain visible. Keep compiler validation enabled. Do not suppress errors with noErrors or noErrorValidation.

Keep yield* inside a generator, such as Effect.gen(function* () { ... }). Outside a generator, the formatter parses * as multiplication and inserts spaces.

The docs config adds Open Graph and Twitter metadata to the built HTML. Each page uses its resolved title and description, a canonical URL on https://effect-agent.com, and its own 1200 × 630 PNG. docs/.vitepress/social-images.ts renders the page title, description, and URL using the installed IBM Plex fonts and docs/public/mark.svg. The build writes images under docs/.vitepress/dist/social/; no browser, remote font request, or manual screenshot is needed. Preview crawlers read the metadata and images without JavaScript.

Edit the renderer to change the artwork, or the page's frontmatter to change its title and description. The build fails if text cannot fit at the minimum font size. Section fragments such as #workflow are not sent to the server, so they share their parent page's preview. A section needs its own page URL to have a distinct preview. Link to guide/workflows for the Effect Workflows preview. The old platforms/node#workflow anchor points readers to that guide.

Run vp run docs:build and inspect the generated HTML for the homepage, a guide, and a directory index such as platforms/index.html. Open their generated PNGs to check the layout. Image URLs must be absolute, and canonical URLs must match the site's clean routes. Existing messages may retain a cached preview after a deployment.

Releasing to npm

All eleven public packages share one Changesets fixed group and publish to beta as X.Y.Z-beta.N. Keep the group in .changeset/config.json aligned with public workspaces. The travel planner is a private application with no package version. It does not receive changesets, version bumps, changelogs, package tags, or npm releases. Private-package versioning and tagging remain disabled in the Changesets configuration. Changesets updates internal dependency ranges only when they use workspace:. Exact registry pins, including the travel planner's published Effect Agent dependencies, stay unchanged during versioning. Upgrade those consumers and their import paths separately after publication; otherwise the version task's install would request packages that have not been published yet. The project is in prerelease mode. Leaving it requires an explicit release decision and vp run changeset pre exit.

Use vp run changeset to describe a consumer-visible change. On each main push, .github/workflows/release.yml maintains the version PR without waiting for CI. After that PR merges and its exact main revision passes CI, the workflow publishes through npm trusted publishing with provenance. PR updates and publication use separate queues.

Publication first runs release:checked-publish, which checks npm for unpublished public versions. If all versions already exist, it skips publication and the paid evaluations. Registry failures stop the attempt before inference. A pending release requires fresh, uncached continuity and hosted checkout checks on the exact clean candidate checkout. Missing credentials, incomplete runs, model failures, or failed assertions stop publication.

For the context continuity evaluation, configure OPENAI_API_KEY as a repository secret and optionally CONTEXT_EVAL_MODEL as a repository variable; the workflow explicitly selects gpt-6-astra by default. Each suite has a conservative $10 spending limit. Nightly and release jobs select one existing explicit-rollover profile; manual dispatch may select one bounded SQLite pressure/restart profile. Cloudflare and full-capacity coverage require separate explicit preparation. PR checks are deterministic and never call a model. Each attempt preserves its own evidence artifact, including failures. This gate proves the documented continuity scenario; it does not certify large-history startup or Cloudflare host performance.

The hosted checkout gate runs 12 automated cases with gpt-5.6-luna, four concurrently, before npm publication. Checkout or cleanup failure blocks the release. Configure the CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN and narrow BROWSER_RENDERING_API_TOKEN repository secrets and the CLOUDFLARE_WORKERS_SUBDOMAIN variable; it reuses OPENAI_API_KEY. Its report is retained for 30 days and recorded cleanup is retried after failure or cancellation. Manual hosted checkout also runs on demand for a selected revision. Neither changeset additions nor ordinary PRs trigger this paid matrix, and CI never waits for human verification.

The release PR always runs candidate builds and the required ready gate. It can reuse proven ordinary source checks through the release metadata proof. The workflow uses the existing Effect Agent GitHub App to create and update it, so those pushes trigger PR CI. Keep the App's contents and pull-request write permissions enabled and configure the EFFECT_AGENT_APP_ID and EFFECT_AGENT_APP_PRIVATE_KEY repository secrets. The checkout disables persisted credentials so Changesets uses the App token.

After merge, Changesets handles registry version checks, publishing, package tags, and GitHub releases. CI transfers the exact validated package build to publication; manual releases build locally. The publisher temporarily prepares npm-ready manifests: source exports point at built files, workspace:* dependencies use the current workspace versions, and catalog: dependencies use the root catalog. All source manifests are restored on success, failure, or interruption. npm publishes through OIDC with provenance; each package must list release.yml in danieljvdm/effect-agent as its trusted publisher.

Changesets defaults packages with no stable release to latest. The adapter temporarily marks the prerelease state as exiting while running changeset publish --tag beta, then restores it. It never runs versioning in that state, so versions and the ongoing beta release train stay intact.

The release workflow does not require strict up-to-date branch rules or post a separate ready check. Changesets refreshes the version PR as changes land on main.

For an authenticated manual release:

  1. Run vp run changeset.
  2. Run vp run changeset:version, then vp install.
  3. Run vp run ready.
  4. Supply the continuity and checkout environment for the exact clean candidate. Use two checkout repetitions and CHECKOUT_HUMAN=false.
  5. Run vp run release:publish --dry-run, then EFFECT_AGENT_LIVE=1 vp run --no-cache release:checked-publish. Add --otp <code> if npm requests it.
  6. Run git push --follow-tags to push the tags created by Changesets.

Use release:publish so npm receives built exports and resolved dependencies. Its dry run builds and inspects packages without publishing or creating tags. All public packages use the MIT license.

Script runners

Package scripts use Bun through vp run. Scripts that import @effect-agent/storage-sqlite continue to use node --experimental-transform-types to exercise the Node host runtime. Strip-only execution cannot handle the framework's runtime namespaces. This includes admin:durable and the Node crash/soak workers.

Post-install setup

vp install runs vp config --no-agent --hooks-dir .vite-hooks through prepare. Apply the compiler patch separately with vp run patch:tsgo. The pinned upstream command selects a replacement for the installed TypeScript version and fails if that replacement is unavailable. Dependency versions live in the root catalog.

CI suppresses lifecycle scripts, then explicitly patches the compiler in jobs that check, test, or build TypeScript. Read installed Effect sources in node_modules/effect.

preferTypedSchemaDecoder is enabled as a warning in tsconfig.base.json and fails typechecks when an unknown-input decoder discards a known encoded type. Use the typed decoder when the input matches the Schema's Encoded type; keep unknown-input decoders at untyped boundaries. Both forms perform the same runtime validation.

The config also enforces direct Effect combinators for selective error recovery, collection traversal, conditional validation, Option conversion, single-service provision, timeout recovery, and Exit runners. Prefer these built-in operations when they preserve the existing error channels, concurrency, interruption, and resource lifetime. Other style suggestions remain advisory; synchronous Schema codecs are not prohibited globally.

To upgrade Effect:

  1. Update all Effect-family catalog entries together.
  2. Run vp install.
  3. Run vp run ready, including canonical application compilation.
  4. Run applicable opt-in provider checks with host credentials.
  5. Commit the catalog and lockfile together.

Contributor agent skills

.agents/skills contains repo-owned Dev Kit skills, tracked by individual .dev-kit-origin.json receipts and linked from .claude/skills.

  • Check updates with bunx @danieljvdm/dev-kit@latest skills status.
  • Update an unmodified skill with bunx @danieljvdm/dev-kit@latest skills update <name>.
  • Add an approved skill with bunx @danieljvdm/dev-kit@latest skills add <name>.

The CLI leaves locally edited skills for an agent merge. Use the dev-kit skill before changing these files. Contributor skills are tooling; framework packages must not import them.

Adding a package

Get owner agreement before adding a new framework concern.

  1. Add packages/<name>/package.json, src/index.ts, and tsconfig.json.
  2. Match sibling manifests: MIT license, public publishing, and source-first exports.
  3. Add the package to the Changesets fixed group and configure its npm trusted publisher.
  4. Use catalog: and workspace:* dependencies with the required inward direction.
  5. Extend tsconfig.base.json and add a root tsconfig.json project reference.
  6. Add applicable check, test, and build tasks and update the guides.
  7. Run vp run ready.

vp run build dispatches vp pack. A package-level Vite config overrides zero-config pack defaults, so declare dts and sourcemap there when needed.

Exports and entry points

Follow the pinned Effect package's module layout. Package roots and public groups use namespace exports such as export * as Agent from "./Agent.ts"; explicit named conveniences are also allowed, as Effect does for pipe and flow. Public namespaces and source filenames use PascalCase; public import subpaths use kebab-case. import { Agent } from "effect-agent" and import * as Agent from "effect-agent/agent" select the same module.

  • Lead documentation examples with named namespace imports from package roots, such as import { NodeDurableHost } from "@effect-agent/platform-node". Use kebab-case subpaths for individual declarations such as services, schemas, or types; direct module and lazy-loading examples; and specialized adapters or runtime-specific helpers. In particular, Node-safe Cloudflare helpers must use their dedicated subpaths rather than the Workers package root.
  • Keep implementations in named modules. A public module exposes every declaration it exports; move sibling-only helpers into private files. A small, explicit public selector may expose supported declarations from a private implementation without exposing its helpers.
  • Import sibling implementations relatively and directly. Do not route them through the package's own public entry points or index barrels. Re-export-only files under internal/ add no public boundary and should be removed.
  • Import other framework packages through their declared public entry points. Direct modules, roots, and namespace groups are allowed; declare the dependency and respect package direction.
  • Keep one implementation owner for each API. Deliberate public modules may forward another package's bindings, including with export *, as Effect's platform packages do. A namespace group such as /testing is also a valid public boundary. Review additions for consumer value; avoid accumulating overlapping aliases without a reason.
  • Keep test-only groups and modules under /testing or /testing/module, excluded from production entry points. Optional browser adapters and fixtures may remain direct-only imports.
  • Keep package.json exports explicit, with matching pack entries. Exports may address nested source modules directly, such as ./agent mapping to ./src/core/Agent.ts; no forwarding file is needed. Pack preserves paths relative to src, and the publisher maps them to dist. Do not publish wildcard, internal, or index subpaths.

See the package map for API ownership and import changes.

Oxlint enforces export-only root and group indexes, prevents self-barrel imports, and rejects re-export-only internal files during vp lint, vp check, and the pre-commit hook. Indexes may contain namespace and explicit named re-exports, but not bare wildcard exports or implementation code. Public forwarding modules need no umbrella-specific exception or file allowlist.

The export check in vp run check verifies manifest paths, exact filename casing, namespace targets, pack entries, declared workspace dependencies, and the inward-only source layers within effect-agent. The purity check uses declared testing targets as well as known test-module paths to prevent production entry points from reaching test-only code. Choosing supported APIs and useful public groups still requires review.

Audited packages declare "sideEffects": [], as Effect does, so bundlers can discard unused modules. This describes import-time behavior, not whether exported operations perform effects. Keep I/O and registration inside Effects and Layers. Recheck the declaration when adding an import-time initializer or upgrading a dependency with startup behavior. The Cloudflare platform package remains unmarked because its optional Puppeteer adapters patch globals on import.

Bundle size comparisons

Pull requests run the Bundle size workflow against the exact base and head commits. Like Effect's bundle check, it bundles small consumer fixtures against built packages. Each checkout installs its own lockfile. The comparison uses the PR's esbuild version and the same fixture source for both sides. Disposable comparison manifests alias historical PascalCase subpaths to their kebab-case names; staged modules also expose the former Ephemeral assembly as InMemory. The published packages retain only their canonical exports. Renamed modules remain comparable.

The fixtures in scripts/bundle cover agent construction, importing the runtime's run function, the in-memory assembly, and loading the runtime on demand, through both root and direct module imports. The analyzer stages copies of dist and uses the release publisher's manifest conversion. It does not bundle workspace source or externalize Effect. It uses minified ESM, a browser target, es2022, production mode, and gzip level 9 per emitted chunk.

Initial counts the entry and every statically reachable shared chunk. Deferred counts the remaining output, and total counts each chunk once. These are byte measurements, not startup timing or a promise about another bundler. Newly introduced modules show n/a for the base. Build failures fail the report; size increases are informational.

Use direct module paths at lazy-loading boundaries. With the measured esbuild configuration, statically importing Agent from the root and dynamically importing AgentRuntime from the same root pulls the runtime into the initial chunk. Direct effect-agent/agent and effect-agent/agent-runtime imports preserve a deferred runtime chunk; shared Effect dependencies still count toward the initial load.

The comparison also bundles and executes runtime-smoke.ts against the PR's staged packages. This Node check verifies root/direct bindings and a deterministic agent run after minification and tree shaking. It is excluded from browser byte totals, and failure prevents a success report.

To reproduce locally, install dependencies and run vp run -F './packages/*' build in each checkout, then run from the PR checkout:

vp run bundle:compare -- --base-dir /path/to/base-checkout

.bundle-report/report.md contains the table; report.json contains exact raw/gzip bytes, revisions, Effect versions, and chunk membership. Each fixture also gets emitted .mjs files, an esbuild meta.json, and modules.txt with module contributions. The metafile can be opened in esbuild's analyzer. CI attaches these as bundle-stats and bundle-analysis artifacts and updates one PR comment through a separate trusted workflow. The comment workflow becomes active after it is merged into the default branch.

Runtime performance comparisons

Pull requests run the Runtime performance workflow against the exact base and head commits. The scripted benchmark runs identical fixture bytes against production builds and each revision's own lockfile on the same Node runtime. Run vp run perf:compare --help for local reproduction. Timing tasks bypass the task cache; keep other builds, tests, and benchmarks idle during measurement.

The PR profile covers small runs and streams, fixed-size responses with increasing fragmentation, growing prompts, parallel tools and repeated rounds, file-backed SQLite history, checkpoint recovery, and settled Submission ledgers. Fresh durable startup includes reopening the host and admission; recovery of an existing Run is a separate case. First-model timing ends in the actual provider callback. A separate subprocess measurement includes startup and fixture imports. Retain raw samples, failures, environment metadata, fixture and artifact hashes, and exact SHAs. The trusted comment workflow becomes active after it reaches the default branch.

Latency reports are informational until repeated CI runs establish variance and useful absolute and relative thresholds. Deterministic call, concurrency, ownership, tracing, and history-work budgets remain correctness gates. The fresh-submission history guard permits two linear scans plus fixed work for histories without compaction. Compacted and checkpoint-seeded views keep their existing validation passes. Checkpoint recovery bounds do not establish constant-time fresh admission. Fairness, lock contention, optional memory/MCP publication, and large settled-ledger indexing require their own controlled evidence before changing those paths.

The separate manual Cloudflare evaluation deploys real model-and-tool workloads through the public HTTP/DO host. Use vp run perf:cloudflare --dry-run to inspect the deployment plan without credentials. The workflow accepts exact commits and runs only on explicit dispatch with its dedicated environment credentials. Its bounded fresh, warm, and recovery cases assert consumed tool results, validated output, canonical settlement, same-thread continuity, and cleanup. Preserve failure artifacts and retry any recorded cleanup before closing an attempt. Offline workerd tests validate the harness, not deployed latency.

Compare matched workloads and clock domains. Local scripted timings do not measure provider latency, Cloudflare CPU billing, or provider cache effects. A live report identifies what it can observe and must accompany claims about the exact candidate and configuration it measured.

CI and hooks

PR CI runs static checks, tests, and builds, then reports the required ready result. Static checks include check:deploy, which invokes both deployment entry points with --help and imports both stack files using a temporary Alchemy profile. This catches missing dependencies and incompatible Effect APIs without credentials or infrastructure changes; it does not verify Cloudflare credentials or remote deployment. Deployment tasks disable Vite Task caching so every invocation runs and receives its deployment environment. Cloudflare storage, Cloudflare platform, Node platform, and testing have dedicated test runners. The remaining-workspace job includes every other package and runs one package task at a time.

The generated Changesets PR uses the release metadata proof below, with ordinary CI as its fallback. Explicit @effect-agent review comments still request review.

PR Review uses pull_request_target and runs only trusted default-branch code. It publishes the shared Effect Agent review check on the inspected PR head using the workflow token's checks: write permission. Automatic and manual reviews use the same check name; manual retries show progress in the PR checks panel. Published findings and incomplete coverage fail that check, while setup, execution, and check publication failures also fail the workflow job. See the Action check configuration for consumer setup. Fork reviews wait for approval before checkout, token creation, or model execution. Open the PR Review run from the PR's checks, select Review deployments, select pr-review-forks, then Approve and deploy. GitHub uses deployment wording for this approval gate, but the job does not deploy anything or create deployment records. Approving an ordinary fork workflow does not grant it repository secrets.

Before enabling this workflow, configure Settings → Environments → pr-review-forks with repository maintainers as required reviewers. The current reviewer is danieljvdm; update this list when maintainers change. Allow self-review so a maintainer can approve their own fork PR. Keep this environment and its required-reviewer rule in place; a missing environment is automatically created without protection by GitHub. The separate pr-review environment has no approval requirement and is used for same-repository PRs and authorized review comments. Both environments use deployment: false to avoid adding review runs to deployment history.

Each fork PR update requires approval. The Action's expected-head check skips an approved run if its PR head has since changed. Comment-triggered reviews retain their existing maintainer authorization and do not require a second approval. Never check out, install dependencies from, or execute the PR head in this secret-bearing workflow; the reviewer reads untrusted source through GitHub's API instead.

Each test-matrix job has its own task-cache key. The three suites split from the workspace job also fall back to its earlier cache, so splitting the matrix does not discard reusable results. Static checks, tests, and builds save successful task results even when another task fails. Ordinary main pushes run static checks, tests, and builds to populate shared caches and validate Action releases. Proven version merges reuse their source checks and exact PR build. The ready fan-in runs only on PRs. Main runs are not cancelled by newer pushes. GitHub scopes PR caches to each PR's merge ref, so another PR cannot reuse them. A new release PR can restore the latest main results only after those jobs finish saving their caches. Waiting for those caches alone does not prevent version fields from invalidating whole-file task fingerprints. Ordinary task results are reused only when task inputs match.

Release metadata CI {#release-metadata-ci}

scripts/release-ci.ts reuses static checks and all eight test-matrix gates from ordinary CI on the exact source base, both on the version PR and after its merge. The verifier and its dependencies run from that base, with read-only contents, Actions and pull-request permissions. Candidate files are read as Git objects; the proof does not execute candidate code.

main push -> ordinary source CI
          -> version PR: proof or ordinary CI + build + package checks
successful source + PR CI -> version merge: proof + restore PR build + package checks
successful main CI -> publication: restore main build + package checks + live gate -> npm

The supported delta is deliberately narrow: every public package in the single fixed group advances by one beta number, changelogs prepend the corresponding entry without rewriting history, and bun.lock changes only the matching workspace version fields. Manifests and the lockfile must otherwise remain byte-identical. Prerelease state must record all existing changeset IDs and initialize each newly included public package at its base version; mode, tag and existing initial versions stay fixed. Changeset files themselves, dependencies, exports, scripts, module type, source, tests, configuration and workflow policy cannot change. Stable releases, other prerelease transitions and unfamiliar layouts run ordinary CI.

The proof checks the current PR head and base, current main, the synthetic merge commit's exact two parents, and equality of the merge and head trees. It queries the latest base push run of the identified CI workflow, requiring a completed successful run and successful ordinary command steps for static checks, every test suite and the build. Evidence is bound to its run ID and attempt, then rechecked along with the PR revisions. A skipped command, missing job, partial API page, changing attempt, policy change, API failure or 45-second proof timeout selects ordinary CI. Setup failures also fall back. The summary records the immutable revisions and evidence run. Only main-push source validation can authorize reuse; fast-path PR results never authorize another fast path. No manifest or lockfile is globally excluded from Vite Task inputs.

The version PR still receives a frozen install, all package/example/docs/Action builds, formatting, export and purity checks, and ci:release-packages. Main repeats the frozen install, formatting, export, purity and package checks after restoring that exact build. Package inspection temporarily prepares the same npm-ready manifests used by publication and checks npm pack --dry-run --ignore-scripts for the actual version and every exported JavaScript and declaration file. Source manifests and prerelease state are restored. A failed retained check fails ready. This path neither publishes nor calls paid models; the separate paid gates in release:checked-publish remain intact.

Release PR generation runs on push alongside main CI and skips superseded main revisions. If ordinary source CI is still running when the PR proof checks it, the PR runs ordinary CI. Publication alone uses workflow_run: completed after successful main CI and skips commits with pending changesets. Because Changesets uses github.sha internally, publication requires that SHA to equal the completed run's head; a newer main revision waits for its own CI. An already stale PR or changed merge tree also falls back to ordinary CI. Evidence applies only to the recorded merge checkout, just as ordinary PR checks do; it does not validate later base movement or replace branch protection's up-to-date requirements.

A version merge must belong to the single merged Changesets PR, have its recorded base as the previous main revision, and have exactly the PR head's tree. Squash and two-parent merges are supported; changed bases, merge resolutions and other topologies select ordinary CI. The proof rechecks ordinary source CI and the latest successful version-PR CI attempt, including its actual build, package checks and ready command. It never chains source approval through another fast path.

Each build uploads one release-build-<run>-<attempt> artifact containing package dist files and the Action bundle, bound to its Git tree, commit and parents. Consumers check the authenticated GitHub artifact identity and SHA-256 of the complete archive before decoding it, then check the recorded revisions and file hashes. Only generated build paths can be restored. Main records the restored build under its own identity after package validation. Documentation and example outputs are not transferred, but their successful exact-tree build remains required evidence.

Publication accepts only the successful exact-main CI run selected by workflow_run, rechecks its attempt and current main before preparation and after the live gate, and inspects the restored npm packages again. Main rebuilds if the PR artifact cannot be restored. Publication fails on missing, expired, corrupt or mismatched artifacts; they never authorize publication. Artifact retention is seven days; rerun main CI to replace expired evidence. The fresh paid gate, Changesets registry checks, npm OIDC provenance and Action tag publication remain required. Local controlled proofs establish correctness; hosted release latency needs a matched version-merge run.

The pre-commit hook runs vp check --fix on staged JavaScript and TypeScript. CI runs the full gate, including package type checks and the Action build.

Action bundles use the catalog-pinned esbuild. vp run action:build writes ignored output to action/dist/index.mjs and checks its Node.js syntax. The root build task also builds the Action, so every PR validates bundling without committing generated JavaScript. There is no bundle freshness check or input-hash manifest.

On successful main runs, CI publishes the exact build artifact in a child commit of the validated source. It creates action-<source-commit-sha> and advances action-v1 in one atomic push, without changing main. Only the publication job has repository write permission; it installs no dependencies and runs no project code. Superseded source commits are skipped, and a Git lease prevents competing publishers from overwriting a newer channel. Failed publication preserves the last release and can be retried by rerunning the failed CI job.

Consumers use danieljvdm/effect-agent/action@action-v1 or pin the distribution commit SHA printed in the CI summary. New source commits and @main no longer contain a runnable bundle; older SHA pins still work. Before the initial cutover, seed action-v1 with the last validated source commit that still contains the bundle, then migrate existing workflows. The first successful main CI run publishes the new distribution commits. These tags are independent of npm package releases.