typescript-build-references
Skill1 starsChanged 6 days agoMITView on GitHub
Guidance for configuring TypeScript project references and incremental builds (`tsc -b`/`--build`, `composite`, solution-style tsconfigs) and declaration emission (`.d.ts` files) with `isolatedDeclarations` in an existing repo or monorepo. Use this whenever asked to add project references, build mode, incremental builds, declaration files, or isolated declarations, even when the repo already has a working build or bundler.
- Installs packages
In this repository4 other files it gives its agents.
- AGENTS.mdAGENTS.md
- typescript-project-setupSkill
- typescript-strictSkill
- typescript-updateSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
27 rules. About 0 are common, 10 are distinctive, and 17 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈10
Distinctive · estimate
This author's own choices. Worth a look.
17
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Distinctive: line 18
If you truly cannot run something, say so and list exactly what is unverified.
- Very specific: line 26
If node_modules is missing or tsc is not found, run the repo's frozen install first, chosen by lockfile: pnpm install --frozen-lockfile …
- Very specific: line 28
Don't use --offline, and don't skip the install to "avoid side effects": installing pinned dependencies is required to verify this task.
- Distinctive: line 31
Don't install browsers or system packages, or switch Node versions, to make a suite runnable.
- Very specific: line 34
Use the repo-local compiler (node_modules/.bin/tsc, or the package manager's exec).
- Very specific: line 38
Check the installed TypeScript version.
- Distinctive: line 40
If the request names a set of packages, cover all of them.
- Very specific: line 48
Use a solution-style root config ("files": [], "references": [...]) so tsc -b at the root builds the projects and the root doesn't also …
- Distinctive: line 50
Keep tests, scripts, and tooling in separate projects (or the existing broad checker) when they have different options.
- Very specific: line 58
Cover every TypeScript extension: Prefer directory includes ("include": ["src"] or ["."] with exclude for output).
- Very specific: line 63
Every extends target must exist.
- Very specific: line 70
Don't leave the existing typecheck/build unchanged just to keep the diff small.
- Distinctive: line 72
Keep existing no-emit typechecks working or replace them with an equivalent that covers at least the same files.
- Distinctive: line 73
Check caches: task-runner caches can hide a broken build.
- Very specific: line 83
prefer an existing named type over a long structural or import("…") type, and never widen to any/unknown (§6).
- Distinctive: line 85
run the repo's formatter on the changed files once.
- Very specific: line 87
Check inherited options that conflict.
- Very specific: line 89
Never finish with isolatedDeclarations enabled in a config that the native build reads while that build still fails.
- Very specific: line 95
Prefer existing named types, or write the faithful structural type.
- Very specific: line 96
Avoid any, any[], unknown, broad assertions, and widening to a supertype.
- Distinctive: line 98
Run the repo's API/declaration snapshot tests from §0 immediately after the first annotation pass (§5) and again at the end.
- Very specific: line 99
If any signature changed (a type, generic, optionality, readonly, overload set, or export), fix the annotation.
- Very specific: line 101
If all remaining differences are printer-only, update the snapshots with the test tool's normal update flag (e.g.
- Very specific: line 109
Keep declaration output out of runtime/source resolution: don't point tests or imports at generated .d.ts directories, and don't let …
- Very specific: line 110
Don't weaken checking to make the migration pass (strict, checkJs, skipLibCheck changes, new excludes, @ts-ignore).
- Distinctive: line 115
Run, in order, and record which actually completed:
- Distinctive: line 127
If anything could not be run (install failure, time), say exactly what was and was not run.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
<!-- prettier-ignore-start -->Your role is to add a TypeScript build-mode (project reference) graph to an existing repo without breaking its current build, public API, or runtime behavior.
Definition of done. You are finished only when:
- the repo's existing root
buildortypecheckscript (or every in-scope package'sbuild) runs the newtsc -bgraph, - in a workspace monorepo,
workspaces-to-typescript-project-references --check(below) reports no differences, and - after the frozen install, the repo's own root
build,typecheck, and full test surface pass withisolatedDeclarationsenabled (when it was requested). The full test surface is the roottestscript plus every other test and API/declaration check the repo defines (§0); the roottestis often only a subset.
Handwritten node -e / JSON.parse checks, your own validator scripts, and editor diagnostics on JSON files are not verification. The --check run is a required lint in addition to the native build, not a substitute for it.
Configs that merely look right, or a new script that nothing calls, are not a finished migration.
If you truly cannot run something, say so and list exactly what is unverified.
Reference check. In a workspace monorepo (npm/yarn/pnpm/lerna workspaces), check that each package's references match its workspace dependencies with @monorepo-utils/workspaces-to-typescript-project-references, run from the repo root after installing:
npx --yes @monorepo-utils/workspaces-to-typescript-project-references --check: compares each workspace package'stsconfig.jsonreferenceswith the workspace packages it depends on inpackage.json, and exits 1 if they differ (§3). Add--tsconfigPath <file>if the packages' emission projects aren't namedtsconfig.json. Don't add the tool to the repo's dependencies.
0. Before editing anything: install, then record a baseline
- If
node_modulesis missing ortscis not found, run the repo's frozen install first, chosen by lockfile:pnpm install --frozen-lockfile(pnpm-lock.yaml),yarn install --immutable(Yarn 2+yarn.lock),yarn install --frozen-lockfile(Yarn 1),npm ci(package-lock.json),bun install --frozen-lockfile(bun.lock/bun.lockb). Don't use--offline, and don't skip the install to "avoid side effects": installing pinned dependencies is required to verify this task. If the install fails, report the error instead of continuing unverified. - Find the repo's full test surface: read the root (and package)
package.jsonscripts and the CI workflows for every test and API/declaration check, not justtest. Look fortest:all, othertest:*scripts, unfilteredvitest run/jest, test-runnerprojects/workspaces thattestfilters out, and API checks such astest:exports,test:types,api-extractor,tsnapi,tsd,attw, andpublint. - Run the root
buildandtypecheckscripts and every command on that test list, and record the results before changing configs, so you can tell your regressions from pre-existing failures. Skip only projects that genuinely cannot run here (e.g. a browser runner with no browser), and say which.- Don't install browsers or system packages, or switch Node versions, to make a suite runnable; report it as not run instead.
- Coverage variants of suites you already run (
test:cov,--coverage) need not be run separately. - Compare only suites that ran both before and after your change; don't call a failure "pre-existing" without a baseline run.
- Use the repo-local compiler (
node_modules/.bin/tsc, or the package manager'sexec). Don't runnpx tscwithout the compiler installed: it may offer to install the unrelatedtscpackage. If a command is waiting for input, answer or stop it; don't leave it running.
1. Inventory
- Check the installed TypeScript version.
isolatedDeclarationsand${configDir}need 5.5+. If the repo deliberately pins an older compiler, don't upgrade or downgrade it just to enable an option; keep the references and skip the unsupported option, and say so. - List every in-scope package by identity and source root. Include private, CLI, and runtime packages, not just the publishable libraries. If the request names a set of packages, cover all of them.
- Record how each package is actually built today: root scripts, task runners (
lage,nx,turbo,nr,just, ...), per-package scripts, CI workflows, and who produces declarations (tsc, a bundler plugin such astsdown/rolldown-plugin-dts/tsup/unbuild/vite-plugin-dts,preconstruct,vue-tsc, ...). Trace root → package command chains to the real compiler invocations. Don't stop at a package script that looks relevant. - Record the original inputs of each library:
include/files/exclude, test and tooling files, generated sources, path aliases, and any inheritedtsconfigsettings (includingallowJs/checkJs). Everything that was type-checked before must still be checked after. - For packages whose public types you may touch, keep the emitted
.d.tsfiles or API reports from the baseline build.
2. Design the reference graph
- Use a solution-style root config (
"files": [],"references": [...]) sotsc -bat the root builds the projects and the root doesn't also check everything itself. - Give each package an emission project with
composite(which impliesdeclaration),rootDir/outDiroremitDeclarationOnlyas appropriate, and its owntsBuildInfoFileif the default location would collide. If a bundler still produces the JavaScript, keep that; the references build only adds declarations and checking. Keep tests, scripts, and tooling in separate projects (or the existing broad checker) when they have different options; they must still be covered. - Derive
referencesfrom both package manifests (dependencies,peerDependencies,optionalDependencies, workspace links) and actual source imports (including aliases and relative cross-package imports). Check for missing direct edges, not just transitive ones, and for cycles. - Each project must reference every in-scope project it imports. Verify the whole graph is reachable from the root solution.
Package boundaries are hard rules:
- One package, its own projects. Each in-scope package gets its own emission project whose inputs are only files under that package's directory. Never set
rootDirabove the package, and neverinclude../paths into another package (e.g.../core/**/component.ts). If package B imports package A's files, by name or by relative path, keep those files in A's project and add areferencesentry from B to A;tsc -bmaps B's imports to A's declarations. - Never exclude a package's own source files from its emission project to reduce
isolatedDeclarationserrors or to silence "not listed within the file list of project" (TS6307) errors. Annotate them instead (§5). Only tests, demos, scripts, and generated output may stay out of the emission project, and they must still be checked by another project. - Cover every TypeScript extension. Prefer directory includes (
"include": ["src"]or["."]withexcludefor output). If you use globs, list**/*.ts,**/*.tsx,**/*.mts, and**/*.cts:**/*.tsalone silently drops.mts,.cts, and.tsxfiles, including hand-written.d.mtsdeclarations.
3. Check effective configuration, not JSON shape
include,files,exclude,outDir,rootDir,baseUrl, andtsBuildInfoFilein an inherited config are resolved relative to the config that declares them. A shared../tsconfig.base.jsonwith"include": ["src"]points at the base directory'ssrc, not each package's. Keep path-bearing settings package-local, or use${configDir}deliberately (TS 5.5+).- Every
extendstarget must exist. Validate with the compiler, notJSON.parse: tsconfig is JSONC and supportsextendsarrays. - In a workspace monorepo, after every graph edit and again before finishing, run
npx --yes @monorepo-utils/workspaces-to-typescript-project-references --checkfrom the repo root. Fix every difference by changingreferences, never by moving, deleting, or excluding sources. It only comparesreferenceswithpackage.jsonworkspace dependencies; check package boundaries, input coverage,composite, and import-only edges yourself (§2). - To debug a single project, use
tsc --showConfig -p <project>ortsc -p <project> --listFilesOnly.
4. Wire it into the repo's real build entry points
- Make the repo's existing root
buildortypecheckscript run the solution build, e.g."typecheck": "tsc -b tsconfig.build.json && <existing check>", or puttsc -binside each in-scope package'sbuildscript that the rootbuildalready runs. - Don't leave the existing
typecheck/buildunchanged just to keep the diff small. A new script (types:build,typecheck:build,build:types, ...) that no root script or CI job invokes is not a migration. - Preserve the existing bundler, task runner, and JS emission. Don't replace a working Lage, Nx, Turbo, Vue,
preconstruct, or similar pipeline withtsc -b; integrate the declaration build into it as a deliberate step. - Keep existing no-emit typechecks working or replace them with an equivalent that covers at least the same files.
- Check caches: task-runner caches can hide a broken build. Run the build uncached (or clean) at least once.
5. Isolated declarations
isolatedDeclarations requires declaration or composite. Enabling it is a source migration, not just a config flag: library code usually needs explicit annotations on exported functions, variables, classes, and object members.
- Every declaration-emission path reads the option. Bundlers and wrappers (
tsdown/rolldown-plugin-dts,tsup,unbuild,vite-plugin-dts,preconstruct,vue-tsc, framework tooling) run their own declaration emit with the package's tsconfig, so enabling the flag can break them even whentsc -bpasses. After enabling it, run the repo's realbuild, not onlytsc -b. - Every declaration-emission path may also print declarations differently. For example, tsdown/
rolldown-plugin-dtsswitches from thetscdeclaration generator tooxcwhenisolatedDeclarationsis on. That changes the emitted.d.tstext (quote style, overload listing, ordering) even when the types are identical, so expect API/declaration snapshot tests to change (§6). - Fix the resulting errors (TS9007, TS9010, TS9011, TS9038, ...) until the enabled build succeeds. Work package by package in dependency order. Hundreds of annotation errors are normal for this task and are the work itself; don't stop at "this is a larger source change than configuration". A build with the option disabled or with
--noCheckis a diagnostic only. - Add the missing annotations by hand. For each library project, in dependency order, build it with the option enabled and annotate each reported site. Write the smallest readable annotation that is faithful to the inferred type (what the old
.d.tscontained):- prefer an existing named type over a long structural or
import("…")type, and never widen toany/unknown(§6); - fix any new type errors in changed files (usually a type that must be exported or imported);
- run the repo's formatter on the changed files once;
- run the repo's API/declaration snapshot tests from §0 immediately after the first annotation pass, not only at the end, and handle any differences as in §6. Importing named types for annotations can change how declarations print, so declarations that printed
import("m").Tmay now printT.
- prefer an existing named type over a long structural or
- Check inherited options that conflict. TS5053 (
allowJscannot be specified withisolatedDeclarations): if the library's emission project has no JavaScript inputs, set"allowJs": falseon that project and keep any JS files (configs, scripts) checked by a separate project or the existing broad checker. - Runtime/tooling that consumes
tsconfig(e.g.ts-node, test runners, scripts) may conflict withcomposite/declaration/isolatedDeclarations(TS5069 and similar). Give those tools a separate transpile/tooling configuration or override instead of removing the option from library emission. - Never finish with
isolatedDeclarationsenabled in a config that the native build reads while that build still fails.
6. Don't weaken the public API
An annotation must describe the type that was inferred, not a looser one.
- Prefer existing named types, or write the faithful structural type. Look at the declaration the compiler emitted before the change (or
typeofhover) and copy that. - Avoid
any,any[],unknown, broad assertions, and widening to a supertype. Preserve generics, overloads, literal discriminants, optionality,readonly, writable vs read-only refs (e.g.WritableComputedRefvsComputedRef), and framework component types. - Compare emitted
.d.tsor API-report output before and after. Compile representative consumers, including existing tests and demos that write to or narrow the exported values. - Run the repo's API/declaration snapshot tests from §0 immediately after the first annotation pass (§5) and again at the end. Never edit generated snapshot or API-report files by hand; only the tool may write them. If they fail, classify every difference:
- If any signature changed (a type, generic, optionality,
readonly, overload set, or export), fix the annotation. Don't regenerate. - Printer-only differences are quote style,
import("m").Tvs an importedT, member or overload order, whitespace, and omitted default type arguments. If the declaration generator changed, first try keeping the previous generator through the bundler's own option (check its type definitions innode_modules). - If all remaining differences are printer-only, update the snapshots with the test tool's normal update flag (e.g.
vitest run --project <name> -u). - If that update is refused by a breaking-change guard, regenerate once with the tool's documented override (e.g. tsnapi
allowBreaking, api-extractor--local), scoped to that single command, and revert any temporary config change. Never leave an "allow breaking" override enabled in the repo, and never use one when a signature changed. - Then run the snapshot tests again in normal compare mode; they must pass.
- In the final message, say whether snapshots were unchanged or regenerated, and list every regenerated symbol with the reason it is printer-only. Never say "updated" for an update the guard refused.
- If any signature changed (a type, generic, optionality,
7. Preserve native tooling and runtime resolution
- Know who owns declaration output,
exports/typesconditions, aliases, generators, and caches before moving them. Tools likepreconstructown the package'sexports/typesfields: don't hand-edit them; use the tool's ownfix/validatecommand, and run its install hook (e.g.preconstruct dev) to confirm. - Keep declaration output out of runtime/source resolution: don't point tests or imports at generated
.d.tsdirectories, and don't let generated declarations shadow source modules. - Don't weaken checking to make the migration pass (
strict,checkJs,skipLibCheckchanges, newexcludes,@ts-ignore). Disabling a flag for a tooling-only project is fine; disabling it for the library is not. Exception: setting"allowJs": falseon a declaration-emission project with no JS inputs is required forisolatedDeclarations(TS5053) and is not weakening, as long as the JS files stay checked elsewhere.
8. Verify and report honestly
Run, in order, and record which actually completed:
- In a workspace monorepo,
npx --yes @monorepo-utils/workspaces-to-typescript-project-references --checkwith no differences. - The repo's formatter (write mode) once on the files you changed, then its lint in check mode, before the final gates, instead of repeated lint runs. Don't run repo-wide fixers. If you do use a lint fixer, inspect
git diffafterwards and revert every change that is not part of the migration (removed casts, rewritten imports, unrelated files). - A clean reference build (
tsc -b --verbose, after removing outputs and build-info), then an immediate second build that reports every project up to date. Its output already shows build order and incrementality; don't add temporary probe edits or exports to test rebuilds. - The repo's native root
build,typecheck, and full test surface from §0, including the API/declaration snapshot tests (uncached), withisolatedDeclarationsenabled, plus any runtime/CLI smoke tests that existed before.
In the final message:
- Name the root script that now runs
tsc -b, and show it passing. - List the commands you ran with their results, including every test and API check from §0. Don't present the root
testalone as the full suite. - Say whether API/declaration snapshots were unchanged or regenerated, and list regenerated symbols (§6).
- If anything could not be run (install failure, time), say exactly what was and was not run. Don't claim success from a hand-written validator or from the config files looking correct.
<!-- prettier-ignore-end -->Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

