A project reference is an entry in references pointing at another tsconfig.json. The referenced project must set composite: true. After that, importing from it loads its output declaration (.d.ts) rather than re-checking its source, so a package is checked once, and projects cannot import files outside their own include. This gives layering (tests cannot be imported by src), separate output folders, and faster editor and CI checks.
What composite enforces: declaration is on (turning it off is TS6304), rootDir defaults to the config's folder, every source file must be matched by include or files, and incremental defaults to true.
incremental saves the previous program's state to a .tsbuildinfo file so the next run re-checks and re-emits only what changed. It works on a single project and is not tied to references.
tsc --build (-b) is the orchestrator. It finds the referenced projects, builds them in dependency order, and skips projects that are up to date. A plain tsc -p app does not build its dependencies. If a dependency's source changes but its .d.ts output does not (a function body edit), dependents are not rebuilt; only their timestamps are updated.
Since TS 5.6, -b continues past errors in upstream projects and writes best-effort output; --stopBuildOnErrors stops at the first failing project. TS 7.0 adds an experimental --builders N flag for building several projects in parallel.
Practical layout: a base config for shared options, one composite project per package, and a "solution" root with "files": [] and the list of references. Consume sibling packages by package name (workspaces symlinks), not by relative paths across project folders.