Documentation / Project structure

Project structure

A Moth project combines directory-scoped modules, one strict project config and a builder that selects the final artefacts.

Normal module roots start with @. Support and package-facade roots start with +. The rest of the filename stays cosmetic, while export: defines the public API and the builder decides which normal roots become entries.

Choose the explanation level: Project layout

Project layout

A Moth project has one config file, one source root and generated output folders.

Where things live

  • config.moth at the project root controls build settings.
  • src/ (or whatever entry_root says) holds your source files.
  • dev/ and release/ are generated output. Do not edit them.

One root file per module directory

Each source directory can have one root file. An @*.moth root creates a normal module. The text after @ stays cosmetic, so @page.moth, @api.moth and @mod.moth all create the same root role.

Shared scoped packages

A +*.moth root creates a scoped support package instead of a normal module. For example, shared/+package.moth creates @shared. Its owner module, normal sibling modules and their descendants may depend on it. Other support packages in the same scope and the package's private implementation subtree cannot. A directory cannot contain both @*.moth and +*.moth roots.

The current scaffold creates an empty legacy lib/ folder. It has no special meaning in the accepted package design.

Outputs are generated

The build command creates output folders. They are not source. The builder cleans up stale artefacts automatically.

A Moth project is a directory tree with one strict config file, a source entry root and generated output folders.

Project root contents

project-root/
├── config.moth
├── src/
│   ├── @site.moth
│   ├── shared/
│   │   └── +package.moth
│   └── pages/
│       └── @pages.moth
├── dev/
│   └── .moth_manifest
├── release/
│   └── .moth_manifest
└── .gitignore

Directory roles

  • config.moth is the only project config file. It lives at the project root and is not module source.
  • The configured entry_root (usually src) is the source folder the compiler searches for module roots.
  • dev/ and release/ are builder output folders, not source folders. They are generated and managed by the build system.
  • .moth_manifest files are build-system metadata used for safe stale-output cleanup. They are not source.

Structural packages

Project-local source-backed packages are structural +*.moth support packages. A +*.moth file in a source directory marks that directory as a scoped support package root named by its containing directory.

In the example above, shared/+package.moth creates the scoped @shared support package. It is visible to its owner normal module, normal sibling modules and their descendants. It is not dependency-bound from its own private implementation subtree or from another support package in the same scope.

No folder gains package meaning without a +*.moth root. Ordinary directories do not automatically become packages.

The optional project-root +*.moth facade beside config.moth is a separate mechanism for assembling public descendant surfaces for external consumers. It is not a scoped support package.

For the full package contract see Project-local packages.

Normal module-root files inside source directories

A non-config file whose name starts with @ and ends with .moth is a module root. The filename after @ has no language-level semantic role. @page.moth, @mod.moth, @api.moth and @anything.moth are all equivalent root filenames.

The moth new html scaffold generates src/@page.moth as a conventional default. That convention does not make @page.moth a special compiler role. Any @*.moth filename in a source directory marks that directory as a module root.

Implementation note

The moth new html scaffold generates config.moth, src/@page.moth and a .gitignore. The retired flat config keys are rejected by the compiler. See the progress matrix for current status.

Source versus output

Developers own source directories. The builder generates output folders and owns their stale-output cleanup. Treat generated artefacts as output, not source.

Builder-neutral frontend versus builder-owned output

The compiler frontend discovers modules, resolves dependencies, checks types, lowers code and validates borrowing. A project builder then decides how compiled modules become real output files. The current user-facing builder is the HTML project builder.

This separation lets the same frontend feed other builders. The frontend does not decide which modules produce artefacts. The builder owns that decision.

Only config.moth has config semantics

No other filename has config meaning. @config.moth is a module root, not a config file. Alternate config filenames, compatibility handling and config-specific diagnostics for other names are not supported.

Related concepts

Choose the explanation level: Project config

Project config

Implemented grouped config and build-config values. This page teaches the grouped config shape, direct and source #Config contracts and explicit @project access supported today. Retired flat keys are rejected. Former top-level builder fields no longer configure the builder. Entry-local config: blocks remain deferred. See Progress for current support.

config.moth at the project root controls build settings. It is one self-contained compile-time source file with no source dependencies.

A minimal config

project #= |
    name = "my_project",
    entry_root = "src",
|

html #= |
    dev_output = "dev",
    release_output = "release",
|

Structure

  • One required project record provides project identity and settings.
  • project.name is required for stable identity.
  • Builder sections like html hold builder-specific settings.
  • The command selects the builder before config validation. config.moth does not select the builder.
  • Only active sections are schema-validated.

Implemented grouped config and build-config values. This page defines the grouped project-config contract, direct and source #Config contracts and the immutable explicit @project interface implemented by the compiler. config.moth authors one grouped project record plus builder sections such as html. Retired flat keys (project #= "html", dev_folder, output_folder and package_folders) are rejected. Former top-level builder fields such as origin no longer configure the builder; unregistered names remain private helpers. Entry-local config: blocks remain deferred. See Progress for current support.

config.moth is the single project config file. It lives at the project root, is loaded before source-tree discovery and is one self-contained compile-time source file with no source dependencies or package resolution.

Canonical config

project_metadata #= |
    channel = "alpha",
|

project #= |
    name = "moth_docs",
    version = "0.1.0",
    entry_root = "src",
    metadata = project_metadata,
|

html #= |
    dev_output = "dev",
    release_output = "release",
|

Structure

  • One required open project const record provides stable project identity.
  • project.name is required and must be a valid Moth project identifier.
  • Private helper constants declared before use are allowed.
  • Top-level builder and tooling section records are allowed.
  • All sections are folded, but only active sections are schema-validated.
  • The active builder project section is required, even when empty.
  • Inactive sections are parsed and folded, then discarded.
  • Project and entry schemas are separate with no shared fields.
  • Project fields may contain folded scalar values, optionals, collections, templates-as-strings and nested anonymous const records. Nested records are declare-first: a |...| list does not contain another |...| list.
  • Builder sections use backend-neutral folded values and cannot declare #Config.
  • Output settings are builder-owned.

Direct project #Config fields

A direct field of project may declare #Config of T only when the selected project schema marks that field Configurable and its value has a supported primitive or optional primitive shape. Registered fixed-only identity, source-discovery and compiler-control fields reject #Config; a primitive shape alone does not make them configurable. The compiler resolves an eligible field from explicit typed command or programmatic input, a compatible builder global, the declaration default or a missing-input diagnostic. Nested project fields cannot declare #Config contracts.

See Build configuration values for the full source and CLI contract. See Build system design for the build-system authority and @project contract.

Rejected in config

  • Every source dependency clause, including relative, project, Core, Builder, dependency-package and binding-backed clauses.
  • Runtime declarations, mutable bindings, functions, named support types, traits, conformances.
  • Standalone templates and top-level const page fragments.
  • export:.

Config is loaded before module discovery

The command selects the builder before config schema validation. config.moth does not select the builder. Config is parsed and folded before Stage 0 source-tree indexing begins.

Related concepts

Choose the explanation level: Module roots

Module roots

Each source directory can contain one module root. A normal module uses an @*.moth root, while an API-only support package uses a +*.moth root.

One root per folder

The filename after @ or + does not matter. @page.moth, @mod.moth and @api.moth all create the same normal-root role. One directory cannot contain two roots or mix an @*.moth root with a +*.moth root.

Bind the module, not the root file

Normal module-root files have no direct dependency form. Bind the directory's public module surface.

The @ in @page.moth marks the file as a module root on disk. The @ in @pages starts a dependency path. They share the same symbol but are not combined. Do not write @@pages. The second @ is invalid.

@pages

Two roles

A module can become a page entry or a dependency. The compiler checks its top-level work in both cases, but that work runs only when the builder selects the module as an entry.

A non-config file whose name starts with @ and ends with .moth is the module root for its containing directory.

Root filename semantics

The filename after @ has no language-level semantic role. These are all equivalent module root spellings:

@page.moth
@mod.moth
@api.moth
@anything.moth

The compiler treats each one as the module root for its directory. The HTML route comes from the directory position, not from the root filename.

One root per directory

A source directory may contain at most one non-config @*.moth file. Two or more normal module-root files in one directory are rejected with MOTH-CONFIG-0001.

Config files are not module roots

config.moth is the project config file. It is not a module root, does not produce HIR and does not export declarations. @config.moth is a module root, not a config file. Only plain config.moth without @ has config semantics.

Direct root-file dependencies are rejected

Normal module-root files have no direct dependency form. Bind the directory's public module surface.

The @ in @page.moth is a filesystem marker identifying a normal module root. The @ in @pages is the source syntax that introduces a dependency path. These use the same symbol to visually connect module roots and dependency roots, but they are not concatenated. The module's dependency identity comes from its directory or registered package name. The cosmetic root filename is never a dependency name.

Given this structure:

src/
├── @site.moth
└── pages/
    ├── @page.moth
    └── article.moth

Valid:

@pages

Invalid:

@@pages
@@page

Neither @page.moth nor @site.moth is bound directly. Do not add the root file's leading @ to a dependency path.

Normal files belong to the surrounding module

Files without @ in the same directory as a root file are normal source files within that module. They contribute declarations that the module root can bind and export. They do not execute top-level runtime code independently.

Active versus dependency module roots

Every selected normal module is compiled into an immutable module artefact. Its top-level runtime work becomes a dormant compiler-synthesised start. When the builder selects that module as an entry, entry assembly activates start exactly once. Binding the module exposes only its public interface and never activates its root work.

  • Entry-selected root: the builder selects this module as an entry. Entry assembly activates its dormant start and its direct top-level templates become page fragments.
  • Dependency root: its top-level runtime and page-fragment activity is inactive. Only the declarations selected by export: are visible through the module boundary.

API-only roots

A module root with no builder-relevant top-level activity (no runtime code, no const fragments and no runtime fragments) remains API-only. It may export declarations but produces no builder artefact.

Migrating older module projects

  • Rename the project config file to plain config.moth.
  • Keep one non-config @*.moth root per source directory. Merge old @page.moth and @mod.moth roles when they shared a directory.
  • Replace inline export declarations and dependency clauses with one strict export: block in the module root.
  • Bind the module directory or package prefix. Do not bind the normal module-root filename directly.

Related concepts

Choose the explanation level: Public API

Public API

Declarations are private to their module by default. The export: block marks what other modules can see.

A compact export block

private_helper || -> String:
    return "private"
;

export:
    public_name #= "Moth"

    render || -> String:
        return private_helper()
    ;
;

Private by default

Files in the same module can see each other's declarations. Other modules can only see what the export: block makes public.

The export: block is the only public API marker. It is valid only in a module root file and marks declarations as visible to other modules.

Syntax

private_helper || -> String:
    return "private"
;

export:
    public_name #= "Moth"

    render || -> String:
        return private_helper()
    ;
;

Rules

  • export: is valid only in a module root file. An export: block in a normal source file is rejected with MOTH-RULE-0077.
  • A module root may contain at most one export: block. Duplicate blocks are rejected with MOTH-RULE-0085.
  • The block ends with normal Moth ;.
  • An empty export: block is rejected with MOTH-RULE-0080.
  • Items inside the block are ordinary top-level module-root items marked public.
  • Items outside the block remain private to the module root file.

Allowed inside export:

  • direct-selection source clauses (re-exports)
  • direct-selection external clauses
  • functions
  • structs
  • choices
  • type aliases
  • traits
  • compile-time constants using #

Rejected inside export:

  • runtime bindings
  • mutable bindings
  • top-level executable statements
  • runtime templates
  • top-level const page fragments
  • trait conformances
  • trait incompatibility declarations
  • receiver methods as directly exported symbols
  • bare namespace dependency bindings or exports
  • wildcard exports
  • export lists
  • function alias exports
  • nested export: blocks
  • all legacy inline export forms

Runtime statements inside the block are rejected with MOTH-RULE-0080. Legacy inline export syntax (without the : block delimiter) is rejected with MOTH-SYNTAX-0001.

Re-exports

The export block can re-export direct selections from other files:

export:
    @components/card CardData as Card, render_card

    @core/math PI, sin
;

The selected alias becomes the public name. In this example cross-module consumers can depend on Card, not CardData, unless the export block lists CardData separately.

# constants are compile-time, not visibility

The # marker on a binding means compile-time constant. It does not control cross-module visibility. Cross-module visibility is controlled exclusively by the export: block.

Same-module versus cross-module visibility

  • Normal declarations in any file within a module are visible to other files in the same module by default.
  • Cross-module dependencies can only access declarations marked public by the module root's export: block.
  • Private declarations in the module root file are not available to other modules.
  • A module root with no export: block exports nothing.

Related concepts

Choose the explanation level: Active-root runtime and fragments

Active-root runtime and fragments

The module root file owns your page's runtime work. Helper files provide declarations.

Root activation

Top-level runtime code in the module root becomes a dormant start. It runs only when the builder selects that module as an entry. Helper files never run their own top-level code.

Page fragments

Direct top-level templates in the root become page fragments:

  • A const fragment (the # prefix form) folds at compile time and becomes static HTML.
  • A runtime fragment (the plain bracket form) runs when the module is activated as an entry and fills a slot in the page.

Helpers

Helper files define functions, types and constants. They do not own an export block. The root binds them and chooses what becomes public. Do not put page fragments in helper files.

Every selected normal module is compiled into an immutable module artefact. Its top-level runtime work becomes a dormant compiler-synthesised start. The builder selects which modules become entries. Entry assembly activates start exactly once. Dependency modules expose only their public interface and never activate their root work.

Entry-activated root runtime

The entry-selected module root's top-level runtime code becomes a dormant compiler-synthesised start function for that module. Entry assembly activates it exactly once. This includes top-level bindings, function calls and other runtime statements.

Normal files

Normal helper files define reusable declarations. They do not own an export block and cannot contain top-level runtime statements. The module root binds those declarations and decides which names become public.

Page fragments

Two forms of direct top-level template in a module root become page fragments:

  • A const fragment uses the # prefix before the opening bracket. It must fold at compile time and is emitted as static HTML.
  • A runtime fragment uses the plain bracket form without #. It is evaluated by the module's start function when entry assembly activates it, and inserted into the page at runtime.

Assigned or returned templates are not page fragments by themselves. Only direct top-level templates in the entry-selected module root become fragments.

Source order

The HTML builder preserves source order between static and runtime fragments. Static fragments are emitted immediately. Runtime fragments use generated slots in the HTML document, then the embedded JavaScript calls the module start function and fills those slots.

Dependency roots

Dependency module roots never activate their root runtime. When a module root is bound for its public API, its top-level runtime code, start body and page fragments remain dormant. Only its exported declarations remain available.

Do not put page fragments in helper files

Helper files should define reusable declarations. The module root should bind and use them. Page-producing top-level templates belong in the entry-selected module root only.

Related concepts

Choose the explanation level: HTML routing and artefacts

HTML routing and artefacts

HTML routes come from directory position, not from the @ filename.

Directory routes

src/@page.moth  -> index.html
src/about/@page.moth  -> about/index.html

The root module is the home page. Each nested directory gets its own route.

API-only modules

A module with only exports and no page activity produces no HTML. Not every @*.moth file becomes a page.

Source stays target agnostic: the builder may choose JavaScript, Wasm or mixed output and exposes stable capabilities without revealing which physical target implements them.

The HTML builder derives routes from directory position under the configured entry_root. The normal module-root filename does not choose the route name.

Directory-based routes

The module root's directory determines the HTML output path:

src/@page.moth              -> index.html
src/docs/@page.moth         -> docs/index.html
src/docs/basics/@page.moth  -> docs/basics/index.html
src/@home.moth              -> index.html
src/@api.moth               -> index.html

The root module maps to the project index. A nested directory maps to a nested index route. The normal module-root filename (@page.moth, @home.moth, @api.moth) has no effect on the route name.

API-only modules

A module root with no builder-relevant top-level activity remains API-only. A root file alone emits no HTML artefact. Artefact emission requires runtime code, const fragments or runtime fragments.

Builder ownership

The HTML builder owns route and artefact policy. The frontend remains builder-neutral. The builder decides:

  • which modules produce HTML artefacts
  • how routes are derived
  • how output files are organised
  • whether to reject unsupported reachable features

Source stays target agnostic. The HTML builder may choose JavaScript, Wasm or mixed output, but source cannot query or branch on that choice. Builder packages expose stable capabilities rather than target identity.

Output folders

The builder generates output folders and manages them through manifests. Builder sections in config.moth own output settings. HTML defaults to dev for development and release for release builds. Treat generated folders as build artefacts, not source.

Release versus development output

The default build writes to the dev folder. The --release flag writes to the output folder. The dev server serves from the dev folder.

Entry root artefact requirement

The HTML builder requires at least one artefact-producing module root at the configured entry root. When the entry root contains only API-only roots, the compiler rejects the build with MOTH-CONFIG-0001.

Experimental Wasm mode

In the experimental --html-wasm mode, the builder can emit a route folder containing index.html, page.js and page.wasm instead of a single HTML file with embedded JavaScript.

Related concepts

Choose the explanation level: Build configuration values

Build configuration values

Build configuration values let a project or source module receive typed primitive values before its modules are compiled.

Implemented contract. Direct and source #Config, typed --input values and explicit @project are supported. Entry-local config: blocks remain deferred; see Progress for current support.

#Config values

A direct project field can use #Config of String:

project #= |
    name = "my_project",
    version #Config of String = "0.1.0",
    entry_root = "src",
|

Source modules use the same spelling:

analytics #Config of Bool = false

if analytics:
    send_analytics()
;

#Config values feed ordinary if; there is no #Config if.

Command examples

moth build . --input enabled=true
moth build . --input retries=4
moth build . --input api_url=https://example.com

Bare true and false infer as Bool, whole numbers as Int, decimal values as Float, and other text falls back to String. Quote "true" when a String is intended. Omitted optional values can resolve to none.

Build configuration cannot inspect operating-system, architecture, backend or target identity; builders handle platform mapping.

Implemented build configuration values. The compiler supports direct and source #Config contracts, typed --input values for build, check and dev, ordinary static-if integration and immutable explicit @project. Semantic fingerprints are constructed transiently during frontend compilation; retaining and comparing them for dependency-aware targeted reuse remains deferred to the existing incremental and persistent artefact work. Entry-local config: blocks remain deferred. See Progress for current status.

Build configuration values are typed primitive values resolved before module AST execution semantics are finalised. They can represent schema-approved project metadata, deployment settings, feature choices and similar stable semantic configuration. They do not represent project identity, source-discovery settings, operating-system, architecture, backend or target identity.

Direct project #Config fields

Direct project #Config is governed by the selected project schema's per-field policy. Only a direct field marked Configurable by that policy, with a supported primitive or optional primitive shape, may use #Config of T. A primitive shape alone does not make a field configurable.

project #= |
    name = "moth_docs",
    version #Config of String = "0.1.0",
    author #Config of String? = none,
    license #Config of String? = none,
    entry_root = "src",
|

name is the required project identity field and entry_root controls source discovery; both are fixed-only. Compiler-control fields such as template_const_loop_iteration_limit are also fixed-only. The built-in schema marks ordinary metadata fields such as version, author and license as configurable, and open metadata fields not registered by that schema are configurable too. The exact policy comes from the selected schema; a primitive shape alone never makes a registered fixed-only field configurable.

Direct-field-only placement applies. Nested project fields, builder and tooling sections, entry-local config: blocks and runtime declarations cannot use #Config. Accepted types are String, Int, Float, Bool, Char and optional forms of those types.

The grouped project record remains open for ordinary metadata: schema-unknown folded fields are accepted and preserved for @project. Openness and qualifier eligibility are separate: the selected schema policy decides whether a direct metadata field may carry #Config, while fixed-only identity and source-discovery fields remain non-configurable.

Source #Config contracts

Any selected source module can declare a module-wide top-level compile-time contract:

api_url #Config of String = "https://api.example.com"
max_retries #Config of Int = 3
analytics #Config of Bool = false

Source defaults must be self-contained primitive literals or none for an optional contract. They cannot use names, templates, expressions, calls, casts, field projections, collections, records or another configuration value. Same-name contracts must agree on primitive type, optionality, required/default state and normalised default value.

Command build-input typing

The command accepts repeated --input name=value arguments. It splits at the first = and preserves all later = characters. Names must be lower_snake_case. Values are typed immediately in this order:

  1. exact lowercase true or false -> Bool
  2. a complete valid signed whole-number literal -> Int
  3. a complete valid decimal-point or exponent literal -> Float
  4. a complete valid single-quoted character literal -> Char
  5. a complete valid double-quoted string literal -> String
  6. every other value -> String

Examples:

moth build . --input analytics=true
moth build . --input retries=4
moth build . --input ratio=0.75
moth build . --input api_url=https://example.com
moth build . --input 'label="true"'
moth build . --input "separator=':'"

Anything else, including empty text after name=, falls back to String. A value beginning with a quote must be a complete valid quoted literal; malformed quoted text is a command-input diagnostic rather than a String fallback. Bare none is String text. To force an ambiguous String such as true or 42, use explicit double-quoted Moth String syntax. A Char requires explicit single-quoted syntax and never infers from an unquoted character.

Omitting an input lets optional absence resolve to none through the contract or default. A concrete T input may satisfy a matching T? contract as a present value. There is no implicit numeric/String coercion: in particular, Int does not satisfy Float. Programmatic build APIs use the same typed carrier and conversion policy.

Ordinary static if

Ordinary if is the only conditional source form. There is no #Config if.

analytics #Config of Bool = false

if analytics:
    send_analytics()
;

Both branches remain valid Moth source and complete frontend validation. A known Bool selects the executable branch before HIR, preserving the selected branch's lexical scope; inactive executable work does not reach HIR or later analysis. This does not condition imports, declarations, exports or package topology.

Platform boundary

Moth source cannot inspect or branch on operating system, architecture, backend, target or runtime-platform identity. Builders own physical target decisions and map stable source semantics and capabilities to JavaScript, Wasm or other platform-specific artefacts. Source may use a stable capability API exposed by a selected builder or package without learning which physical backend implements it.

Resolution and isolation

For a schema-configurable direct project field, #Config values resolve from explicit command or programmatic input, a compatible builder primitive global, the declaration default or a missing-input diagnostic. Project-wide source contracts resolve from a compatible fixed direct project field, a resolved direct project #Config, explicit source-only input, a compatible builder global, the shared source default or a missing-input diagnostic. A fixed project field is authoritative and blocks an override.

Resolved values carry boundary-local provenance and semantic fingerprints based only on their stable name, contract type and effective value-or-absence during frontend compilation. These facts are not retained after the build. Every project or package compilation boundary owns a separate configuration namespace; consumer inputs do not implicitly satisfy dependency contracts. Configuration can specialise executable behaviour, but cannot change source discovery, dependency clauses, declarations, exports, receiver methods, traits, conformances or module/package topology. Retention, dependency-aware targeted invalidation and reuse are deferred to the existing incremental and persistent artefact work.

Related concepts

Choose the explanation level: Entry config

Entry config

Entry config covers implemented explicit access to project-level values through @project and the deferred root-local config: block.

Implemented: @project binding reads project fields. Deferred: entry-local config: blocks remain outside current compiler support. See Progress for implementation status.

Using @project

Bind @project to read project fields like version or entry_root inside a module:

@project version

footer #= [: v[version]]

@project is never implicitly injected. Bind it explicitly when you need it.

Entry-local config: blocks

A normal module root may contain one optional config: block with builder metadata for that entry:

config:
    html #= |
        title = "About",
    |
;

The block lives at the top level of the root file, outside export: and outside executable bodies. It creates no module symbols or runtime values.

Related concepts

Implemented @project; entry-local config deferred. Direct and source #Config contracts and the immutable @project interface are implemented. Root-local config: blocks remain deferred under the entry-config implementation plan. See Progress for current status.

This page covers explicit @project access and the accepted, separately deferred entry-local config: contract.

@project

The folded project record produces a specialised immutable ProjectGlobalsInterface under the permanently reserved @project dependency root.

@project exposes direct project fields as namespace members. It doesn't expose another value named project.

Normal project modules and project-owned support packages may explicitly bind @project. It is never implicitly injected.

The following may not claim the @project root:

  • child modules
  • scoped support packages
  • dependency aliases
  • Core packages
  • Builder packages
  • binding-backed packages

@project cannot be directly re-exported.

Internal project modules may expose declarations derived from project values. The compiler retains project-context provenance on every affected public semantic fact. The external project package facade rejects prohibited project-context exposure.

@project version, entry_root

page_title #= [: v[version]]

Entry-local config: blocks

An entry config: block is root-local builder metadata. It isn't an embedded config.moth source file.

Placement rules:

  • valid only at the top level of a normal module root
  • at most one block per normal root
  • invalid in normal non-root files
  • invalid in support roots
  • invalid in the project package facade
  • invalid inside export:
  • invalid inside executable bodies
  • invalid in config.moth

The block contains section records only. Dependency clauses, aliases, helper constants, support types and source #Config declarations live outside the block in the normal root file.

The block uses the root file's ordinary compile-time visibility. It may reference:

  • dependency-bound constants
  • @project
  • same-file constants declared before the block
  • resolved source #Config constants
  • foldable local const-record types
  • selected-builder compile-time values available through normal module dependency clauses

Same-file forward references remain invalid.

The block creates no ordinary module symbol, HIR or project-global value. It cannot contain project or change project-level builder behaviour. It may contain active artefact-builder and tooling-overlay sections.

Active entry sections are schema-validated. Inactive sections are parsed and folded but not schema-validated.

Every normal module selected into the current command's semantic graph has its block validated whether or not an entry activates it. Dependency modules never apply their entry metadata to a consumer.

Only active artefact-builder settings contribute entry activity.

Related concepts

Choose the explanation level: Project package facade

Project package facade

A project can expose a public package API to other projects through an optional facade file at the project root.

What a facade does

A +*.moth file beside config.moth becomes the project package facade. It exports declarations that external consumers can bind. Without it, the project has no externally consumable package surface.

The facade rejects top-level runtime work and page fragments. It only exports API declarations. Support roots follow the same rule.

How it reaches descendant modules

The facade can reference public interfaces of modules below entry_root even though ordinary module visibility wouldn't allow it. Stage 0 gives the facade a special assembly namespace for this purpose.

What the facade cannot do

  • It cannot depend on @project.
  • It cannot expose declarations that depend on project-private context.
  • It isn't visible to internal project modules.

Related concepts

Partial implementation. The source-package and module foundations are partially implemented. The complete external facade and project-context contract remains partial. This page defines the accepted final contract.

The project package facade exposes a project's public API to external consumers. It is an API-only module compiled through the ordinary compiler pipeline with project-facade visibility supplied by Stage 0.

The facade root

One optional project-root +*.moth file beside config.moth defines the external project package facade. The suffix after + is cosmetic.

The facade may define and export its own legal API-only declarations: functions, structs, choices, type aliases, traits and compile-time constants.

Special assembly namespace

Stage 0 gives the facade a special assembly namespace rooted at entry_root. Through that namespace it may reference the public interfaces of descendant modules below entry_root, regardless of ordinary lexical module visibility.

Facade restrictions

The facade:

  • never bypasses an export: boundary
  • isn't visible to internal project modules
  • cannot depend on @project
  • cannot expose a semantic fact that depends on project-private context
  • rejects top-level runtime work and page fragments
  • has no implicit start
  • emits no route

Support roots and the project package facade reject top-level runtime work and page fragments. They don't merely stay inactive.

Project package assembly

The compiler produces an immutable facade module artefact and public interface. ProjectPackageAssembly is a separate link plan over:

  • the compiled facade artefact
  • selected descendant public interfaces
  • reachable generated functions
  • package runtime requirements permitted by the target

Assembly never recompiles or mutates the facade.

A project may be both an application and a package. Without the facade it has no externally consumable Moth package surface.

The facade package identity comes from project.name.

External package eligibility

The build system rejects any declaration whose public semantic facts or reachable executable implementation directly or transitively depend on private @project. This includes an exported function that calls a private project-dependent helper.

The validator doesn't treat implementation-only dependence as a reusable package specialisation mechanism.

Dependency package facades

A dependency package exposes its facade and immutable artefacts to consumers. Consumers never see the dependency's private @project.

No declaration exposed through a dependency package facade may directly or transitively depend on that dependency's private @project. Private declarations may use the dependency's own @project only when no external package export can reach or expose them.

Build-configuration namespaces are scoped to one project or package compilation boundary. A consuming command's unqualified CLI or programmatic inputs don't implicitly satisfy a dependency's #Config contracts.

Related concepts