Browse pages
Workspace Structure
A LiteSpec consumer, a project built with LiteSpec, keeps one monorepo tree for its applications, shared packages, documentation, tests, and records. This page states that tree, where each file kind lives inside it, and what .litespec/project.json declares. It applies to anyone who creates a file, and to agents locating code without asking.
Stack: any
Layer: workspace
Concepts#
Applications and Packages#
apps/ holds what runs: the API, each frontend, and any deployed host. packages/ holds what is reused: shared configuration, generated API types, a thin client, and theme tokens. The split keeps deployable code out of libraries and reusable code out of applications.
Records and Their Homes#
Each record type has one home in the tree: the model page names the home, and the canonical tree below realizes it. Entries live in their home file instead of their own files, so an agent derives the path from the record type without searching.
The model builder writes generated working files under .litespec/model/ while it builds the model from records, code, routes, tests, and test runs; the folder is rebuilt rather than reviewed.
Standards#
Use the Canonical Root Tree#
ruleruleguidancelitespec/workspace/structure#use-the-canonical-root-treeA consumer workspace MUST use the canonical root tree declared in this section.
Example
Consumer workspaces use this shape:
{repo}/
.config/
dotnet-tools.json
.litespec/
project.json
glossary.yaml
intent/
product.yaml
goals/ problems/ constraints/ decisions/ research/ actors/ roles/ journeys/
modules/
{module}/
module.yaml
goals/ problems/ constraints/ decisions/ research/
features/
{feature}.yaml requirements as entries
behavior/
rules/ cross-module invariants only
external-systems/ create with the first product-level external system
entry-points/
{entry-point}/
entry-point.yaml
screens/
{screen}.yaml
modules/
{module}/
permissions.yaml
use-cases/ rules/ reactions/ jobs/ notifications/ settings/ integration-events/ external-systems/
{aggregate}/
aggregate.yaml
{use-case}.yaml results and scenarios as entries
events/ rules/
implementation/
decisions/ api/ guides/ operations/ patterns/ writing/ runbooks/
operations/
limits.yaml create with the first limit record
secrets.json create with the first secret
log/ create with the first operations entry
entry-points/
{entry-point}/ UI catalog, captures and patterns
modules/
{module}/
decisions/ runbooks/
governance/
exceptions/ create with the first exception record
modules/
{module}/
exceptions/
proof/ generated registers, gate files and run outputs; no authored records
model/ generated; ignored
apps/
api/
<backend project manifest>
src/
tests/
{frontend}/
apphost/ when one host runs more than one application
packages/
AGENTS.md
<toolchain pin file>
package.json when a frontend or TypeScript package exists
pnpm-lock.yaml when a frontend or TypeScript package exists
pnpm-workspace.yaml when a frontend or TypeScript package existsA backend stack binding names the files that realize the entries in angle brackets.
The example places neither the backend application nor a frontend application at the workspace root. Under behavior/modules/, each module folder holds one folder per aggregate in plural kebab-case form. A module with one aggregate of the module's own name may stay flat.
A record file lives at the home the model page names for its type. An entry lives in its home file: requirements in their feature file, permissions in their module's permissions file, results and scenarios in their use case file, and rules and events in their aggregate file or type folder. Each entry point is one record in its own folder, with its screens beside it.
Checka reviewer answers
Reviewed forThe workspace tree matches the canonical tree in this section, with each record at its type's home under .litespec/.
Marked guidance: a reviewer answers this question.
Declare Size and Application Stacks in Project Config#
ruleruleguidancelitespec/workspace/structure#declare-size-and-application-stacks-in-project-config.litespec/project.json MUST declare size as small or large and one stack for the backend and for each frontend.
Why
Size decides which record types and test projects apply: small or large selects the project's required-record set. Each stack decides which technology pages and checks apply to its application. Declaring both in .litespec/project.json lets tools and reviewers read the selection from one file. The scope page states the full selection.
Checka reviewer answers
Reviewed for.litespec/project.json at the workspace root declares size and one stack for the backend and for each frontend.
Marked guidance: a reviewer answers this question.
Keep the Generated Model Out of Source Control#
ruleruleguidancelitespec/workspace/structure#keep-the-generated-model-out-of-source-controlThe generated .litespec/model/ folder MUST stay out of source control.
Why
The model builder writes generated working files there, such as frontend facts, while it builds the model from records, code, routes, tests, and test runs. Tracking them would commit derived files beside their sources, and every commit would carry stale working files until somebody rebuilds. The folder is rebuilt rather than reviewed.
Checka reviewer answers
Reviewed forNo path under .litespec/model/ is tracked in git; git ls-files .model prints nothing.
Marked guidance: a reviewer answers this question.
Keep Production and Test Code Separate#
ruleruleguidancelitespec/workspace/structure#keep-production-and-test-code-separateProduction code MUST live under apps/api/src/ and test code under apps/api/tests/.
Why
Production code under apps/api/src/ and test code under apps/api/tests/ keep the two graphs apart by path. The split makes a test dependency reaching production code visible in the path itself, and keeps test helpers from shipping with the product.
Example
The backend tree is:
apps/api/
<backend project manifest>
src/
tests/Checka reviewer answers
Reviewed forProduction files sit under apps/api/src/ and test files under apps/api/tests/, with no test helper shipped beside production code.
Marked guidance: a reviewer answers this question.
Keep Runnable Applications Under Apps#
ruleruleguidancelitespec/workspace/structure#keep-runnable-applications-under-appsAn independently runnable frontend, API, or deployed host MUST live under apps/{name}/.
Why
Each root makes one promise: apps/ runs, packages/ is reused. A reusable package under apps/ or deployable code under packages/ inverts that promise, so neither location tells the reader what it holds. One home per kind keeps the promise readable.
Checka reviewer answers
Reviewed forEach directory under apps/ runs on its own, and each directory under packages/ is imported by an application.
Marked guidance: a reviewer answers this question.
Limit Shared TypeScript Packages#
ruleruleguidancelitespec/workspace/structure#limit-shared-typescript-packagesA shared TypeScript package MUST have at least two consumers or carry generated output shared by design.
Why
A shared package with one consumer pays versioning, publishing, and cross-package edits for an audience of one. Requiring two consumers or generated output shared by design keeps sharing deliberate. The baseline permits shared configuration, generated API types, a thin client, and CSS theme tokens.
Checka reviewer answers
Reviewed forEach package under packages/ is imported by at least two consumers, or carries generated output shared by design; read the imports in apps/.
Marked guidance: a reviewer answers this question.
Keep Consumer Documentation at the Root#
ruleruleguidancelitespec/workspace/structure#keep-consumer-documentation-at-the-rootProject records MUST live under the root .litespec/ directory.
Why
Project records describe the product, not the standard and not the code. Placing them inside the application tree makes them disappear when it is rewritten. The root .litespec/ directory keeps records in a place that survives replacing the standard or rewriting the application.
Checka reviewer answers
Reviewed forEvery record path sits under the root .litespec/ directory.
Marked guidance: a reviewer answers this question.
Keep Orientation Documents Separate from Canonical Records#
ruleruleguidancelitespec/workspace/structure#keep-orientation-documents-separate-from-canonical-recordsA README under .litespec/ MUST link to the record rather than restate its content.
Why
A README orients; a record decides. A README that restates a decision becomes a second authored source that drifts from the record it summarizes, until the two disagree and neither is trusted. Linking to the record keeps one source with many pointers.
Checka reviewer answers
Reviewed forEach README.md under .litespec/ links to its record and restates no record content.
Marked guidance: a reviewer answers this question.
Conventions#
Name Frontends by Audience#
conventiondefaultguidancelitespec/workspace/structure#name-frontends-by-audienceName a frontend for its audience, such as web, admin, portal, or docs.
ReplacementA project whose frontends serve one audience records its own frontend names in .litespec/project.json under paths.frontends.
Why
apps/web tells the reader who it serves; apps/frontend tells them only what it is built with. Naming each frontend for its audience keeps the folder meaningful when the technology changes. frontend, client, or app names the technology rather than the audience it serves.
Checka reviewer answers
Reviewed forEach directory under apps/ carries an audience name; where replaced, paths.frontends in .litespec/project.json names the project's own frontend names.
Marked guidance: a reviewer answers this question.
Keep Generated API Contracts in Packages#
conventiondefaultguidancelitespec/workspace/structure#keep-generated-api-contracts-in-packagesPlace generated OpenAPI types in packages/api-types/ and a thin client in packages/api-client/ when more than one frontend consumes the API.
ReplacementA project with a single frontend records that frontend as keeping both outputs.
Why
Generated OpenAPI types and their thin client belong to every frontend that reads the API. With more than one consumer, packages/api-types/ and packages/api-client/ keep one copy each. A single frontend may own both locally until a second consumer appears.
Checka reviewer answers
Reviewed forGenerated OpenAPI types sit in packages/api-types/ and the thin client in packages/api-client/ once two frontends consume the API; with one frontend, that frontend keeps both outputs.
Marked guidance: a reviewer answers this question.
Reference Example#
This informative example demonstrates litespec/workspace/structure#use-the-canonical-root-tree and litespec/workspace/structure#limit-shared-typescript-packages.
An API with public and admin frontends uses apps/api/, apps/web/, and apps/admin/. Both frontends may import generated transport types from packages/api-types/. Neither imports the other frontend's feature code.