# npm Package Release This document defines the durable release conventions for public npm packages in this repository. ## Scope The npm package release flow covers public packages intended for consumption outside this repository. All public npm packages in this repository publish under the npm organization scope `@tutti-os`. Do not introduce new public packages under a different scope, personal scopes, or unscoped names unless this document and the release automation are updated together. The current fixed release group is: ```text @tutti-os/analytics @tutti-os/analytics-debug @tutti-os/event-stream-core @tutti-os/connector-contracts @tutti-os/connector-renderer @tutti-os/workspace-file-manager @tutti-os/workspace-file-reference @tutti-os/workspace-issue-manager @tutti-os/workspace-user-project @tutti-os/workspace-app-center @tutti-os/workspace-external-core @tutti-os/workspace-terminal @tutti-os/agent-activity-core @tutti-os/agent-gui @tutti-os/agent-session-replay @tutti-os/agent-session-replay-runner @tutti-os/commerce @tutti-os/claude-sdk-sidecar @tutti-os/browser-node @tutti-os/workspace-file-preview @tutti-os/workbench-snapshot @tutti-os/workbench-electron @tutti-os/workbench-host @tutti-os/workbench-launchpad @tutti-os/workbench-surface @tutti-os/app-release-tools @tutti-os/auth-bridge @tutti-os/desktop-update-admission @tutti-os/ui-i18n-runtime @tutti-os/ui-notifications @tutti-os/ui-rich-text @tutti-os/ui-react-hooks @tutti-os/ui-system ``` These packages use one shared version number and are released together. Do not introduce independent package versions until there is a clear compatibility need and a migration plan. This document covers public npm packages only. Shared non-npm modules such as `packages/workspace/files` follow their owning language and module conventions and are not part of the npm release flow unless a separate release contract is defined for them. This document is the durable source of truth for which workspace packages participate in the npm release flow. Do not duplicate the participating package roster in architecture docs. This document does not cover the desktop app release flow. Desktop releases are defined in [Desktop Release](./desktop-release.md). ## Stable Releases Stable package releases use one manual GitHub Actions workflow dispatch on `main`. Each stable run resolves the next shared package version from the latest existing stable package tag. Stable versions are limited to `0.0.x` during this iteration: - if there is no prior stable package tag, the workflow publishes `0.0.1` - if the latest stable package tag is `packages-v0.0.7`, the next run publishes `0.0.8` - if any existing `packages-v*` tag is outside the `0.0.x` line, the workflow must fail rather than guessing The workflow applies the resolved version to the fixed release group inside the CI checkout, validates tarballs, and prepares a release-only commit. In that commit, internal Go module requirements use the resolved shared version and workspace-relative replacements are removed. The workflow publishes the npm packages with the `latest` dist-tag, then points the matching npm release tag and every `packages/**/go.mod` module tag at that release commit. The commit is reachable through those tags only and is not pushed to `main`. Stable releases do not open version PRs and do not require Changeset files. Package publication uses bounded concurrency so registry and provenance round-trips do not serialize the full fixed release group. The workflow pins `TUTTI_NPM_PUBLISH_CONCURRENCY=4`; release tooling accepts an integer from `1` through `8` for controlled CI tuning and rejects unbounded or invalid values. Each worker checks only the exact immutable package version before publishing, preserves the bounded provenance retry, and stops admitting new packages after the first terminal failure. Release and Go module tags are created and pushed only after every package publish has completed successfully. Because the stable release version is applied only in CI, repository manifests on `main` may lag behind the latest published `0.0.x` version. The durable release history is the sequence of `packages-v0.0.x` git tags plus the published npm versions. When the participating package roster changes, keep these release surfaces aligned with this document: - `/.changeset/config.json` - `package.json` package release scripts - `tools/scripts/apply-ci-package-release-version.mjs` - `tools/scripts/check-package-packs.mjs` - `tools/scripts/next-package-release-version.mjs` - `tools/scripts/release-beta.mjs` - `tools/scripts/publish-packages.mjs` The npm package release workflow should be triggered manually only after the target package changes have already landed on `main`. Stable package tags use this shape: ```text packages-v0.0.1 ``` Do not reuse desktop release tags such as `tutti-desktop-v*` for npm packages. Go module tags for modules under `packages/` are created during the same stable release and use the Go submodule tag shape: ```text packages/workbench/service/v0.0.1 packages/appcli/core/v0.0.1 packages/workspace/files/v0.0.1 packages/workspace/issues/v0.0.1 ``` Adding a new `packages/**/go.mod` opts that module into this shared stable package release tag sequence. Do not add package Go modules that require an independent release cadence unless this convention and the release automation are updated together. `packages/device-link` participates in the same stable Go module tag sequence after the Personal Android/Desktop path validated its authenticated connection lifecycle and AAR consumer build. TSH and other consumers must install its released cohort version; do not consume a pseudo-version or add a workspace replacement. ## Local Beta Releases Use local beta releases for temporary cross-repository or external integration debugging. The local command is: ```bash pnpm release:beta ``` The beta command publishes the fixed package group with the `beta` dist-tag and a unique prerelease version such as: ```text 0.1.1-beta-20260521143012 ``` The command must not publish to `latest`, create git tags, or commit files. It must restore temporary package manifest changes before it exits. Install beta packages explicitly: ```bash pnpm add @tutti-os/browser-node@beta pnpm add @tutti-os/agent-session-replay-runner@beta pnpm add @tutti-os/workspace-file-reference@beta pnpm add @tutti-os/workspace-file-preview@beta pnpm add @tutti-os/workspace-file-manager@beta pnpm add @tutti-os/workspace-issue-manager@beta pnpm add @tutti-os/connector-renderer@beta pnpm add @tutti-os/connector-contracts@beta pnpm add @tutti-os/workspace-app-center@beta pnpm add @tutti-os/workspace-terminal@beta pnpm add @tutti-os/workbench-electron@beta pnpm add @tutti-os/workbench-host@beta pnpm add @tutti-os/workbench-surface@beta pnpm add @tutti-os/workbench-snapshot@beta pnpm add @tutti-os/desktop-update-admission@beta pnpm add @tutti-os/ui-i18n-runtime@beta pnpm add @tutti-os/ui-notifications@beta pnpm add @tutti-os/ui-rich-text@beta pnpm add @tutti-os/ui-react-hooks@beta pnpm add @tutti-os/ui-system@beta ``` Do not add a second stable release path that bypasses the shared package roster or the stable tag sequence. ## Package Shape Published packages build into `dist` and expose built artifacts through package exports. Do not expose `src/*` as public npm API. Inside the workspace, package manifests may point `exports` and `types` at `src` so local development does not require a package build before every typecheck or desktop dev run. Use `publishConfig` to override those fields to `dist` when pnpm packs or publishes the package. Published packages should include: - `dist` - `README.md` - `package.json` Published packages should not include: - `src` - package-local `tsconfig.json` - package-local build config - generated junk files Packages that expose Tiptap runtime objects or types must declare their shared Tiptap runtime packages as peer dependencies, with the same ranges repeated as development dependencies for workspace builds. Tiptap packages require exact peer versions, so component-internal extensions must be bundled from development dependencies rather than resolved independently in the consumer. A package-private copy of the shared runtime can make structurally identical extensions type-incompatible in consumers. Platform-specific peers used only by an optional public subpath must remain peer dependencies and set `peerDependenciesMeta..optional` to `true`. The platform app that imports that subpath must declare the concrete runtime dependency itself. This keeps web and desktop consumers from installing unused native dependency chains. Runtime assets that are rendered or referenced by public entrypoints must also survive the packed package shape. When a public runtime entrypoint such as `./workbench` or `./ui` renders a package-local image, icon bitmap, schema, or similar asset, keep the main runtime entrypoint asset-free whenever possible. Prefer an explicit public asset subpath such as `./assets/workspace-dock-website.png`, let the business consumer import that asset only when it actually uses the default visual, and ship the asset file through an explicit package build rule. Use this command to inspect publish contents: ```bash pnpm release:pack:check ``` Without arguments the command builds and inspects every configured public npm package. Repository-managed changed-file gates may pass a validated package subset: ```bash pnpm release:pack:check -- --packages-json '["@tutti-os/agent-gui"]' ``` The subset build includes the selected packages' workspace dependencies, but only the selected package tarballs are inspected. Use the unfiltered command for release infrastructure, workspace dependency, lockfile, Changesets, or fixed release-group changes. Before publishing a package that exposes runtime assets, verify all of the following: - `pnpm release:pack:check` shows the packed output that consumers will install - the packed tarball includes every exported asset subpath such as `./dist/assets/workspace-dock-website.png` - the main runtime entrypoint no longer hard-depends on a package-local image unless that dependency is an intentional part of the public contract - a consumer-facing build emits or copies the asset only when the consumer explicitly imports the asset subpath - packed JavaScript does not contain raw XML SVG data URLs or bare relative SVG strings; SVGs interpolated into runtime CSS `url(...)` values must use a CSS-safe data URL or an absolute URL constructed relative to the module - every asset referenced through `new URL(relativePath, import.meta.url)` exists at that exact module-relative location inside the packed tarball - published code that may be dependency-prebundled must import fallback images through explicit public asset subpaths; do not leave `new URL("./assets/...", import.meta.url)` in a bundled runtime entrypoint, because a consumer optimizer may relocate the JavaScript without relocating the adjacent asset Emitting an SVG next to a bundled JavaScript file is not sufficient when the bundle only exports a string such as `./icon-HASH.svg`. CSS resolves that value relative to the consuming page, and consumer bundlers cannot discover the runtime string as an asset dependency. Do not use a workspace application build as the only validation for asset changes. Workspace exports can point at source while published exports point at `dist`, so the two paths may apply different asset transforms. The packed tarball is the consumer contract. ## Package Entrypoints The stable package entrypoints are: ```text @tutti-os/agent-activity-core @tutti-os/agent-session-replay-runner @tutti-os/agent-session-replay-runner/activity-contract.json @tutti-os/agent-session-replay-runner/cassette-policy.json @tutti-os/agent-session-replay-runner/cassette @tutti-os/agent-session-replay-runner/cassette-policy @tutti-os/agent-session-replay-runner/checkpoint-plan @tutti-os/agent-session-replay-runner/evidence-helpers @tutti-os/agent-session-replay-runner/managed-log-prefixes @tutti-os/agent-session-replay-runner/managed-shutdown @tutti-os/agent-session-replay-runner/playback-controller @tutti-os/agent-session-replay-runner/playback-helpers @tutti-os/agent-session-replay-runner/product-ports @tutti-os/agent-session-replay-runner/recording @tutti-os/agent-session-replay-runner/replay-stimuli @tutti-os/agent-session-replay-runner/serial-queue @tutti-os/agent-session-replay-runner/stimulus @tutti-os/agent-session-replay-runner/turn-freshness @tutti-os/agent-session-replay-runner/turn-identity-tracker @tutti-os/agent-session-replay-runner/wait-diagnostics @tutti-os/analytics @tutti-os/analytics-debug @tutti-os/analytics-debug/react @tutti-os/agent-gui @tutti-os/agent-gui/agent-conversation @tutti-os/agent-gui/agent-env @tutti-os/agent-gui/agent-env-ui @tutti-os/agent-gui/agent-message-center @tutti-os/agent-gui/context-mention-palette @tutti-os/agent-gui/i18n @tutti-os/agent-gui/quick-composer @tutti-os/agent-gui/styles.css @tutti-os/agent-gui/workbench @tutti-os/agent-gui/workbench/browser-element-context @tutti-os/agent-gui/workbench/tool-sidebar @tutti-os/agent-gui/workspace-settings-panel @tutti-os/commerce @tutti-os/commerce/react @tutti-os/commerce/assets/star-free.png @tutti-os/commerce/assets/star-lite.png @tutti-os/commerce/assets/star-pro.png @tutti-os/commerce/assets/star-ultra.png @tutti-os/commerce/assets/registration-credits-bg.png @tutti-os/browser-node @tutti-os/browser-node/assets/workspace-dock-website.png @tutti-os/browser-node/bridge @tutti-os/browser-node/chrome-cookie-import/macos @tutti-os/browser-node/electron-main @tutti-os/browser-node/electron-preload @tutti-os/browser-node/electron-renderer @tutti-os/browser-node/i18n @tutti-os/browser-node/react @tutti-os/browser-node/workbench @tutti-os/ui-i18n-runtime @tutti-os/ui-notifications @tutti-os/ui-rich-text @tutti-os/ui-rich-text/core @tutti-os/ui-rich-text/editor @tutti-os/ui-rich-text/plugins @tutti-os/ui-rich-text/types @tutti-os/ui-react-hooks @tutti-os/connector-contracts/authorization/v1 @tutti-os/connector-contracts/openapi/connector-market.v1.yaml @tutti-os/connector-renderer/application @tutti-os/connector-renderer/i18n @tutti-os/connector-renderer/ui @tutti-os/workspace-app-center @tutti-os/workspace-app-center/contracts @tutti-os/workspace-app-center/core @tutti-os/workspace-app-center/i18n @tutti-os/workspace-app-center/ui @tutti-os/workspace-file-preview @tutti-os/workspace-file-preview/core @tutti-os/workspace-file-preview/react @tutti-os/workspace-file-manager @tutti-os/workspace-file-manager/assets/workspace-archive-fallback.png @tutti-os/workspace-file-manager/assets/workspace-folder-fallback.png @tutti-os/workspace-file-manager/services @tutti-os/workspace-file-reference @tutti-os/workspace-file-reference/contracts @tutti-os/workspace-file-reference/core @tutti-os/workspace-file-reference/react @tutti-os/workspace-file-reference/ui @tutti-os/workspace-issue-manager @tutti-os/workspace-issue-manager/assets/workspace-dock-task.png @tutti-os/workspace-issue-manager/contracts @tutti-os/workspace-issue-manager/core @tutti-os/workspace-issue-manager/i18n @tutti-os/workspace-issue-manager/openapi/issue-manager.v1.yaml @tutti-os/workspace-issue-manager/services @tutti-os/workspace-issue-manager/ui @tutti-os/workspace-issue-manager/workbench @tutti-os/workspace-user-project @tutti-os/workspace-user-project/contracts @tutti-os/workspace-user-project/core @tutti-os/workspace-user-project/i18n @tutti-os/workspace-user-project/ui @tutti-os/workspace-terminal @tutti-os/workspace-terminal/contracts @tutti-os/workspace-terminal/i18n @tutti-os/workspace-terminal/react @tutti-os/workspace-terminal/styles.css @tutti-os/workspace-terminal/workbench @tutti-os/workbench-snapshot @tutti-os/workbench-snapshot/schema.json @tutti-os/workbench-electron @tutti-os/workbench-host @tutti-os/workbench-host/conformance @tutti-os/workbench-surface @tutti-os/workbench-surface/i18n @tutti-os/workbench-surface/styles.css @tutti-os/ui-system @tutti-os/ui-system/components @tutti-os/ui-system/dev-vite @tutti-os/ui-system/icons @tutti-os/ui-system/metadata @tutti-os/ui-system/styles.css @tutti-os/ui-system/utils ``` Connector packages intentionally expose no root barrel. Import wire protocols from `@tutti-os/connector-contracts/authorization/v1`, frontend services from `@tutti-os/connector-renderer/application`, and React components from `@tutti-os/connector-renderer/ui`. New public subpaths should be added only when a real consumer needs them. `@tutti-os/ui-system/dev-vite` is a development-tooling subpath for external Vite consumers. It is part of the package release surface so external projects can configure local source sync without manual linking, but it must not be imported from application runtime code. `@tutti-os/ui-system/metadata` is a tooling metadata subpath used by the storyboard, dev server, and agent skills. It should describe stable public exports rather than exposing private source layout as the consumer API. ## Review Guidance When reviewing package extraction or release-surface changes, keep these rules separate: - `packages/*` still exists for real shared seams rather than for code that only looks reusable. - a public npm package is an explicit external contract and may be valid even before there are two in-repository consumers - when a package does not yet have a second in-repository host, the PR should make the intended external contract and release ownership explicit rather than treating publishability as implied - shared non-npm modules such as `packages/workspace/files` should not be counted as public npm packages unless they are added to this document and the release configuration ## CSS Boundary `@tutti-os/ui-system` owns shared tokens, theme styles, and primitive styles. Other reusable packages should consume UI system primitives and token-backed Tailwind utilities before introducing package-local CSS. Reusable packages should not publish their own stylesheet entrypoint by default. Prefer React components and Tailwind utilities that resolve through the consumer's Tailwind build and the shared UI system token layer. The current stylesheet entrypoint exceptions are explicit package contracts: - `@tutti-os/agent-gui/styles.css` for the carried agent GUI, session chrome, session transcript, and workspace-agent status panel selectors that remain package-owned during the migration - `@tutti-os/workbench-surface/styles.css` for surface layout, window frames, resize handles, snap previews, and dock/chrome positioning - `@tutti-os/workspace-terminal/styles.css` for the terminal node structural shell, close guard, and xterm host container selectors Tailwind consumers should include reusable packages in their source scan when they consume packages that render Tailwind utility classes. For example: ```css @source "../node_modules/@tutti-os/workbench-surface/dist"; ``` For monorepo packages that render runtime Tailwind utility classes, declare the source root in `package.json` so repository checks can enforce consumer setup: ```json { "tutti": { "tailwindSourceRoot": "src" } } ``` Desktop renderer consumers should then add the matching package source path to their Tailwind entrypoint. `pnpm check:ui-boundaries` validates this for workspace packages imported by `apps/desktop`, including mismatched `@source` paths that point at the wrong directory. Package-local CSS should be rare. Add it only when a reusable package needs selectors, keyframes, or structure that is awkward to express through primitives and Tailwind utilities. When package-local CSS is necessary, it must use UI system tokens and must not contain app-specific product styling or user-visible copy. Published stylesheet sources use standard CSS properties. In particular, author `backdrop-filter` without a handwritten `-webkit-backdrop-filter` pair. The consuming application's CSS build owns browser-target prefix generation; the packed stylesheet does not promise a second, package-owned legacy-browser prefixing pass. Consumer production builds should assert that optimization did not leave prefix-only declarations. Do not add a public `./styles.css` export for a reusable package unless that CSS is genuinely part of the package's stable contract. If the same need would help multiple packages, add the token, primitive, or utility support to `@tutti-os/ui-system` instead. ## Access And Credentials All scoped public packages must set public access through package `publishConfig.access`. Stable publishing should prefer npm trusted publishing through GitHub Actions OIDC. If a token fallback is needed, use only a GitHub Actions secret and keep it out of local scripts. When the workflow uses an `NPM_TOKEN` fallback, that token must belong to an npm account with publish access to the `@tutti-os` organization scope. Local beta publishing uses the developer's local npm login. ## Validation For changes that affect published package surfaces, run: ```bash pnpm lint:ts pnpm typecheck pnpm check:ui-boundaries pnpm release:pack:check ``` For shared UI export or CSS boundary changes, keep `pnpm check:ui-boundaries` aligned with package exports and allowed style locations.