Type to search pages and rules.

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.

Markdown10 provisions

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#

ruleruleguidance
litespec/workspace/structure#use-the-canonical-root-tree
Statement

A consumer workspace MUST use the canonical root tree declared in this section.

Example

Consumer workspaces use this shape:

Text
{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 exists

A 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#

ruleruleguidance
litespec/workspace/structure#declare-size-and-application-stacks-in-project-config
Statement

.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#

ruleruleguidance
litespec/workspace/structure#keep-the-generated-model-out-of-source-control
Statement

The 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#

ruleruleguidance
litespec/workspace/structure#keep-production-and-test-code-separate
Statement

Production 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:

Text
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#

ruleruleguidance
litespec/workspace/structure#keep-runnable-applications-under-apps
Statement

An 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#

ruleruleguidance
litespec/workspace/structure#limit-shared-typescript-packages
Statement

A 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#

ruleruleguidance
litespec/workspace/structure#keep-consumer-documentation-at-the-root
Statement

Project 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#

ruleruleguidance
litespec/workspace/structure#keep-orientation-documents-separate-from-canonical-records
Statement

A 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#

conventiondefaultguidance
litespec/workspace/structure#name-frontends-by-audience
Default

Name 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#

conventiondefaultguidance
litespec/workspace/structure#keep-generated-api-contracts-in-packages
Default

Place 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.