Skip to content

Repositories and packages

The tree

A repository’s root directories are the concepts a reader of that repository already has, written in the plural: core/ and tools/ in the toolchain, foundations/, components/ and catalogue/ in the design system, contracts/, web/, backend/, services/ and plugins/ in the platform. A package is a leaf under one of them. What kind of package it is is stated in its README, never in the tree.

Every repository carries the same files beside its trees:

File or directory Holds
README.md What the repository is, its layout, and its commands
CONTRIBUTING.md From a clean clone to a merged change
LICENSE MIT, held by Stealth Scale B.V.
SECURITY.md Where a vulnerability is reported
docs/ The tree described in Documentation
.github/workflows/ Callers of the toolchain’s reusable workflows
vite.config.ts The one configuration: the toolchain’s preset, with what is true of this repository
tsconfig.json Extends the toolchain’s tsconfig/base.json, as every package does with only its include added
bunfig.toml The install policy
.changeset/ The pending release notes, in a repository that publishes

Only the root carries a vite.config.ts. A package adds one only for what is true of that package alone, such as several entries or a dev server’s port.

Why a package exists

A module is the default. A package exists for exactly one of four reasons, and the first line of its README names which:

Kind Reason Published
library Somebody installs it on its own, or it owns a heavy dependency the rest must not carry yes
kit Another repository takes it at development time yes, as a devDependency
deployable An image or a remote; nobody installs it never
cli It ships a bin yes

Heavy means anything beyond React, Base UI, class-variance-authority and lucide on the web, and beyond the platform’s own core on a server. A concern inside a package is a directory with a subpath entry, @stealthscale/web-sdk/router, so it is one manifest, one version, and tree-shaken per entry, with a stylesheet per styled entry. A tier is a directory and a layering rule, never a package.

The name

A package is named for the singular of its group, then the path below it, with dashes for slashes. The rule has no exceptions; the generator computes the name, and a guard refuses a manifest whose name is not its path.

Directory Name
core/schema @stealthscale/core-schema
tools/config @stealthscale/tool-config
components/charts @stealthscale/component-charts
foundations/theme @stealthscale/foundation-theme
web/sdk @stealthscale/web-sdk
services/identity/contract @stealthscale/service-identity-contract
plugins/identity/web @stealthscale/plugin-identity-web

A product’s packages carry the product’s own scope, @acme/plugin-reviews-web, and are private. A deployable is private and named like everything else, @stealthscale/web-host; it never leaves its repository.

The manifest

A published package’s manifest carries:

Field Value
name The name above
description One sentence
license MIT
repository { type, url, directory }, so npm links the source and provenance can check it
type module
files ["dist"], plus a shipped stylesheet where there is one
sideEffects false, unless a module runs on import
exports Per entry: { "stealth-source": "./src/<entry>.ts", "default": "./dist/<entry>.mjs" }, and ./package.json
publishConfig.access public
scripts.build vp pack

The exports map is written by the pack step from the entries the package declares, so it cannot drift from what is built. stealth-source is the workspace’s own condition: turned on by the repository’s tsconfigs and Vite configs, and by nothing outside it, so inside the workspace every package resolves to its source and outside it to dist.

A version is declared once, in the root manifest’s catalog, and a package writes catalog:; a sibling is workspace:^, which the pack step rewrites to a caret range. workspace:* is never used: it packs as an exact pin.

React and react-dom are peers of every rendering package. A shipped file’s imports are dependencies; a spec’s, a story’s and a fixture’s are devDependencies, the package’s own or the root’s. A workspace package a shipped file imports is a dependency, never a devDependency, or the bundler vendors a second copy into dist.

The README

The first line names the kind and the reason: A library: …, A kit: …, A deployable: …, A cli: …. Then what the package gives a consumer, and what it does not do. A released README ends with an install section written by the toolchain from the package’s name and its directory, never by hand; a guard holds the file to what the tool writes. Nothing in a README narrates history or status.

What the guards check

Each rule is a function over the workspace with a spec beside it; the toolchain’s preset runs them, and a repository adds rules of its own the same way.

Guard Refuses
names A manifest whose name is not the singular of its group plus its path
pairing A source file without a spec beside it, or a spec without a source
declared An import of a workspace package the importing file’s manifest does not declare
layers A dependency that points up: a tree importing a package of a tree above it
ownership A third-party dependency declared by more than one package
stylesheets A shipped stylesheet that scans anything but its own dist
readmes A released README whose install section is not what the tool writes
roadmap An index table or graph that disagrees with the milestones’ own frontmatter

A rule never pins a count or a list of what the tree holds today: it states the invariant and derives the expectation from the tree, so adding a package edits nothing outside that package. When a rule would demand a hand edit, the tool that makes the edit is built first.