Monorepo companion packages
SkillDev toolsThis skill should be used when orienting in the brepjs monorepo's packages/ and apps/ directories — answering "what is brepjs-viewer / brepjs-cad / brepjs-voxel", "is package X published", "which workspace do I edit", "why does npm ci fail with ETARGET on a workspace version-range", "playground shows stale behavior from brepjs-bim/sheetmetal", "in what order do I build the packages", "Dependabot flags a workspace `*` dependency", "add a new workspace package", or deciding how a change in one package ripples into its consumers.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Monorepo companion packages skill
What this skill tells your AI
The instructions your AI receives, as published by andymai/brepjs in .claude/skills/companion-packages/SKILL.md and read by ahel’s review.
The root package.json workspaces field is the source of truth for the monorepo layout: 11 packages under packages/ plus apps/playground and apps/docs. The ## Packages list in CLAUDE.md is a curated subset — when it disagrees with the manifests, trust the manifests.
Package map
| Workspace | Purpose | npm status |
|---|---|---|
brepjs (root) | Core CAD library | Published; auto-released via release-please |
packages/brepjs-opencascade | Custom OpenCascade WASM build (fallback kernel) | Published; manual publish only (publish-opencascade.yml — expensive WASM build) |
packages/brepjs-bim | IFC4 parametric building elements + IFC import/export | Published, experimental; auto-released |
packages/brepjs-families | Declarative family layer: element trees, key paths → CSG IR | Published, experimental; auto-released (dist build; bim type-imports it, bundles nothing) |
packages/brepjs-sheetmetal | Flange authoring, fold/unfold flat patterns, DXF/STEP | Published, experimental; auto-released |
packages/brepjs-cad | Agent skill pipeline + brep/brep-mcp CLI bins + WASM viewer | Published; auto-released |
packages/create-brepjs | npm create brepjs scaffolder (dependency-free bin + templates) | Published; auto-released |
packages/brepjs-viewer | Shared React/R3F renderer (playground + brepjs-cad) | Published; manual publish, deliberately unmanaged by release-please |
packages/brepjs-manifold | Manifold mesh/CSG preview kernel adapter | Unpublished, source-shipped (exports → ./src/index.ts, no build) |
packages/brepjs-voxel-wasm | Rust→WASM voxel/SDF engine (wasm-pack build, committed pkg/) | Versioned/tagged by release-please but no publish workflow — not on npm |
packages/brepjs-voxel | TS loader for brepjs-voxel-wasm | Unpublished, source-shipped |
packages/brepjs-vscode | VS Code extension: live 3D preview for .brep.ts | private: true — the only packages/ workspace that is private (not published, not source-shipped) |
apps/playground | Interactive docs playground (Vercel) | private: true |
apps/docs | VitePress docs site | private: true |
Three tiers, and the rules differ per tier:
- Published satellites (opencascade, bim, families, sheetmetal, cad, viewer, create-brepjs): consumers may resolve them from the npm registry, so their manifests must always describe an installable package.
- Source-shipped internals (manifold, voxel, voxel-wasm): resolved only through workspace symlinks; manifold and voxel have no build step at all — editing their
src/is immediately live for consumers. - Private (vscode, playground, docs): never published; playground and docs deploy via Vercel/Pages instead.
READMEs exist only for bim, families, sheetmetal, cad, and viewer — point users there for per-package API detail rather than restating it.
Workspace dependency rules
- Satellites depend on
brepjsas a floor-ranged peerDependency:"brepjs": ">=18.0.0"inpackages/brepjs-bim/package.json,packages/brepjs-families/package.json, andpackages/brepjs-sheetmetal/package.json. Exception:brepjs-cadtakes brepjs as a real dependency (">=18.117.1") because its CLI must run standalone. - bim depends on families as a bounded range:
"brepjs-families": ">=0.1.0 <1.0.0"inpackages/brepjs-bim/package.json(runtime dep forfamiliesToBimconsumers; the adapter itself only type-imports families, so bim's dist bundles none of it). Release ordering: the families release PR merges before bim's. - Root brepjs's kernel packages are optional peers:
brepjs-manifold,brepjs-opencascade,brepkit-wasm,occt-wasmare alloptional: trueunderpeerDependenciesMetain the rootpackage.json. Never promote one to a hard dependency — consumers pick exactly one kernel. - Internal cross-references use
"*"(e.g."brepjs-viewer": "*"inapps/playgroundand in brepjs-cad's devDependencies). npm workspaces symlink these locally. Gotcha: Dependabot scans each sub-manifest without a co-located lockfile, so an unconstrained"*"on an external dev tool reads as permitting every vulnerable version. Fix by constraining a floor on the flagged spec ("vite": "^8.0.0","vitest": "^4.0.0"— seepackages/brepjs-viewer/package.json), not by lockfile churn. brepjs-viewerrole differs per consumer: runtimedependencyof the playground; build-timedevDependencyof brepjs-cad (its viewer bundle inlines it — see thepackages-verifycomment in.github/workflows/ci.yml). Viewer declares its react/three/fiber/drei peers as floor ranges (>=19,>=0.184, etc.) compatible with the playground's versions, so npm dedups to one copy monorepo-wide; rationale inpackages/brepjs-viewer/README.md.- npm overrides use nested-object syntax:
"vitepress": { "vite": "6.4.3" }in rootpackage.jsonoverrides, plus scoped-range keys like"esbuild@<0.25.0": "^0.25.0". There is no pnpm-styleparent>depseparator in npm. - Never regenerate
package-lock.jsonfrom scratch. For security floors, edit the spec then runnpm install --package-lock-only(no re-resolution, no churn). Full regeneration has historically dropped required@emnapi/*peer entries and broken CI. - 0.x caret hazard:
brepjs-voxelpins"brepjs-voxel-wasm": ">=0.2.0"as a plain range, not^0.x— a^0.1.0caret excludes0.2.0, and an internal 0.x bump once left consumers pointing at a version that was never published, 404-ingnpm cirepo-wide. Use>=floors for unpublished internal 0.x packages.
Build order
Workspace symlinks resolve manifests instantly, but built packages resolve through their dist/ — a stale or missing dist fails typecheck or, worse, silently runs old code.
Three ordered chains, all encoded in scripts (prefer running the script over hand-ordering):
- Playground —
build:depsinapps/playground/package.jsonbuilds brepjs-bim, brepjs-sheetmetal, and brepjs-viewer, and runs automatically aspredev/prebuild. If playground behavior looks stale after editing a companion package, this chain (or a manualnpm run build --workspace=<pkg>) is the fix. - Full site — root
build:site: provision-fonts → root build → brepjs-bim → brepjs-sheetmetal → apps/playground → apps/docs → copy playground dist into the docs dist. - brepjs-cad — root
npm run build→npm run build --workspace=brepjs-viewer→npm run build --workspace=brepjs-cad. The cad viewer worker importsbrepjsand bundlesbrepjs-viewer, so both dists must exist first; CI'spackages-verifyjob andpublish-brepjs-cad.ymlboth follow this order.
WASM bootstrap: the OpenCascade runtime files (brepjs_single.js/.wasm) are gitignored. Root prepare runs scripts/ensure-wasm.sh, which downloads them from the published brepjs-opencascade npm package into packages/brepjs-opencascade/src, keyed by a .wasm-version marker. Corollary: npm ci --ignore-scripts skips the download — fine when WASM is not needed (publish-brepjs-viewer.yml does this deliberately), otherwise re-run bash scripts/ensure-wasm.sh manually (publish-brepjs-cad.yml does, with retries).
Release and publish (summary)
The package topology that shapes the pipeline lives in references/publish-pipeline.md; the operator mechanics live in the release-publishing skill. The shape:
release-please-config.jsonmanages 8 components: root, opencascade, voxel-wasm, cad, bim, families, sheetmetal, create-brepjs; separate PRs per component and no plugins (plugins: []). Viewer and voxel are deliberately excluded.- Leaf release PRs are held until the root brepjs release merges — merging a leaf first pins it to an unpublished brepjs version and breaks
npm ciwith ETARGET. - npm OIDC trusted publishers are bound to specific workflow filenames, so release-please dispatches each
publish-brepjs-*.ymlrather than inliningnpm publish. - Every
publish-*.ymlisworkflow_dispatchwithdry_rundefaulting to true — a bare manual dispatch is a safe dry run; passdry_run=falseto actually publish.
See the release-publishing skill for the operator playbook on cutting and recovering releases; this skill covers only how the package topology shapes that pipeline.
CI coverage per package
| Workspace | CI gate (.github/workflows/ci.yml) |
|---|---|
| root | typecheck/lint/boundaries/patterns + 4-way sharded tests |
| brepjs-viewer | packages-viewer: typecheck, lint, test, build |
| brepjs-cad | packages-verify: typecheck, lint, test, build, eval, smoke, smoke:standalone |
| brepjs-sheetmetal | packages-sheetmetal: typecheck, lint, test, build |
| brepjs-bim | packages-bim: typecheck, lint, test, build |
| brepjs-voxel-wasm | voxel-wasm-rust: cargo test + clippy (path-filtered on packages/brepjs-voxel-wasm/**) |
| apps/playground | playground-build (path-filtered; mirrors the Vercel build) |
| brepjs-vscode, brepjs-manifold, brepjs-voxel (TS) | no CI job — changes here are ungated; run their checks manually |
Two consequences worth internalizing: root npm run validate does not cover satellites (run npm run <script> --workspace=<name> directly when touching one), and the ungated packages rot silently — verify manually before relying on them.
Symptom → cause → fix
| Symptom | Cause | Fix |
|---|---|---|
npm ci/Vercel fails with ETARGET on a brepjs or workspace version | A manifest pins a version not yet on npm (leaf merged before root, or 0.x caret on an internal bump) | Merge/publish the root release first, or widen the internal range to a >= floor; see references/publish-pipeline.md |
| Playground ignores an edit in brepjs-bim/sheetmetal/viewer | Playground worker runs the companion's built dist, which is stale | npm run build:deps in apps/playground (automatic on predev/prebuild) |
brepjs-cad typecheck/build fails on missing brepjs or viewer types | Root or viewer dist missing | Build in order: root → viewer → cad |
Kernel init fails locally with missing brepjs_single.js | Gitignored WASM never downloaded (--ignore-scripts, fresh clone glitch) | bash scripts/ensure-wasm.sh |
Dependabot alert on a workspace "*" dev-dep | Sub-manifest scanned without lockfile context | Constrain a floor version on the spec; npm install --package-lock-only |
| Manual publish dispatch "succeeded" but nothing on npm | dry_run defaults to true | Re-dispatch with dry_run=false |
A satellite change passed root validate but fails CI | Root validate does not run satellite gates | Run typecheck/lint/test/build with --workspace=<name> before pushing |
Adding a new workspace package — checklist
- Add the directory to root
package.jsonworkspaces. - Decide the tier: published (needs build,
files, exports todist/, apublish-<name>.ymlworkflow, and an npm trusted-publisher entry bound to that filename), source-shipped (exports→./src/index.ts, no build — copypackages/brepjs-voxel/package.json), orprivate: true. - Dependency shape:
brepjsas">=18.0.0"peerDependency for library satellites; internal siblings as"*"; floor-constrain any external dev tool that Dependabot might flag. - Add the directory to the root component's
exclude-pathsinrelease-please-config.json(everypackages/*is excluded so satellite-only commits never bump root). If published: add entries torelease-please-config.jsonand.release-please-manifest.json, and a dispatch job inrelease-please.yml(mirrorpublish-brepjs-bim). If it must not auto-release (like viewer), leave it out of the managed config. - Add a CI job in
ci.ymlmodeled onpackages-bim(build root first if the package importsbrepjsthrough dist exports). - If the playground consumes it, append it to
build:depsinapps/playground/package.json. npm installfrom the repo root to register the workspace in the lockfile — never regenerate the lockfile wholesale.
Additional resources
- references/publish-pipeline.md — release topology: which packages release-please manages/excludes and why, and the build order baked into each publish workflow (operator mechanics live in the release-publishing skill)
packages/brepjs-cad/README.md— the two-rail distribution model: skills install via the repo's Claude plugin marketplace (.claude-plugin/marketplace.json→packages/brepjs-cad), runtime vianpm i -D brepjs-cad brepjs occt-wasmpackages/brepjs-viewer/README.md— exports and peer-pinning rationale- Workflow comments in
.github/workflows/release-please.ymlandci.yml— the best inline docs for release mechanics and per-package build-order rationale - Sibling skills:
release-publishing(operating the release pipeline),ci-triage(diagnosing red CI),playground-examples(adding examples insideapps/playground),quality-gates(root repo gates),wasm-interop(kernel WASM behavior beyond the bootstrap covered here)
Signals
- GitHub stars
- 101
- Forks
- 9
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
companion-packages- Source
- github.com/andymai/brepjs