Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

First project

You will create a three-module calculator project, run its checks and tests, build it for the web, and break one test on purpose. The project’s semaprax.toml is its manifest: it lists the modules, tests, and exports.

1. Create it

semaprax new first-semaprax
cd first-semaprax

The destination must not exist. Add --name <project-name> to override the name, or --template library or --template service for another starter.

first-semaprax/
├── semaprax.toml
├── README.md
├── AGENTS.md
└── src/
    ├── app.spx
    ├── core.spx
    └── tests.spx
FileHolds
src/app.spxThe entry point, main.
src/core.spxThe logic: add.
src/tests.spxThe tests.
AGENTS.mdCommands and language rules for coding agents. Keep it.

2. Run it

Run these inside first-semaprax/:

semaprax fmt . --check
semaprax check .
semaprax test .
semaprax run .
verified project first-semaprax (sha256:...)
project tests passed
42

fmt . --check prints nothing when every file is canonical. Every command also accepts semaprax.toml instead of ..

3. Read the files

schema = "semaprax.manifest.v1"

[package]
name = "first-semaprax"
version = "0.1.0"

[modules]
entry = "first_semaprax.app"
sources = ["src/app.spx", "src/core.spx", "src/tests.spx"]
tests = ["first_semaprax.tests"]

[exports]
web = ["first-semaprax.add"]
module first_semaprax.core;

@id("first-semaprax.add")
fn add(left: i64, right: i64) -> i64
{
    left + right
}

src/app.spx imports add by stable ID and calls it:

module first_semaprax.app;
use function @id("first-semaprax.add") from first_semaprax.core as add;

@id("first-semaprax.app.main")
fn main() -> i64
{
    add(19, 23)
}
module first_semaprax.tests;

@id("first-semaprax.tests.main")
fn main() -> i64
{
    if 19 + 23 == 42 { 0 } else { 1 }
}

Three names do three jobs:

NameExampleJob
File pathsrc/core.spxWhere the source lives.
Modulefirst_semaprax.coreWhich module declares the function.
Stable IDfirst-semaprax.addWhat other modules import.

use function @id("...") from <module> as <name>; imports by stable ID. See Modules and imports.

4. Inspect and build

semaprax query . --kind function
src/app.spx	function	first-semaprax.app.main	fn main() -> i64
src/core.spx	function	first-semaprax.add	fn add(left: i64, right: i64) -> i64
src/tests.spx	function	first-semaprax.tests.main	fn main() -> i64
semaprax build . --target web -o dist/web

The [exports] table in the manifest picks which functions the web package exposes (first-semaprax.add). Building does not start a server. The output directory must not exist yet. See Targets.

5. Break a test

Open src/tests.spx and change 19 + 23 == 42 to 19 + 23 == 41. Run semaprax test . and read the failure. Restore the line and run it again.

To add named cases, write fn test_<name>() -> i64 functions with an @id that return 0 on success. See Testing.

Add a module

  1. Create src/<name>.spx with module first_semaprax.<name>;.
  2. Add its path to sources in semaprax.toml. List a test module under tests.
  3. Run semaprax check .. Check the whole project, not a single file: a lone file that imports another module reports SPX-G172 or SPX-T105.

When the manifest is rejected

Semaprax accepts one canonical manifest layout. Keep the generated table order, one-line arrays, and blank lines, and follow the first SPX-J100 hint. The manifest guide lists every field.

semaprax project-scaffold --name <name> prints a starter as one JSON capsule without writing files. It is for tools; use new to create a project.

Next: Language essentials, or write your own modules.