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

Command line

Everything SSPUR does goes through one binary, sspur. This is its usage text, exactly as the current build prints it (CI checks this page against sspur --help and sspur deploy). Commands and flags marked (experimental) may change in a 1.x minor release; Stability and deprecation lists what 1.x keeps:

$ sspur --help
usage:
  sspur new web-app NAME | --list       create a project from a template: web-app is a notes service with accounts (README.md inside)
  sspur init [file.ssp] [--pkg NAME]    create a codebase in .sspur/ (optionally import a file; --pkg also writes sspur.toml)
  sspur add <name[@^1.2]|scope/name|path|git-url[@rev]> [--as KEY]  add a dependency (a published package, a path or a git URL) to sspur.toml and pin it in sspur.lock
  sspur deps fetch | update [NAME...] [--force] | tree   verify and fetch locked deps; upgrade with a semantic diff (refused if it breaks the build); show the graph
  sspur update [NAME...] [--force]      the same as deps update: newest matching versions, with the semantic diff
  sspur search [QUERY] [--json]         published packages; sspur source NAME[@VERSION] [DIR] downloads one, verified, without running it
  sspur publish [--dry-run] [--ack net,fs] [--ref TAG] [--no-tests]   run the registry checks here, then open the publish request; sspur archive [--rev REV] [-o FILE] builds the source snapshot the registry stores
  sspur yank NAME@VERSION [--undo] [--deprecate MSG]   request a yank (or deprecation); sspur api [--json] shows the public API and the facts the checks use
  sspur edit [file|-] [-e SRC] [--test] replace or add definitions by name (also 'rename A B', 'remove A' lines)
  sspur apply [tx.json|-] [-e JSON] [--test] apply a transaction of ops
  sspur q <query> [target] [--budget N] (experimental) query the codebase (list [types|fns|tests] sig body callers callees effects find grep pack why impact holes diag log; body and pack take A,B,C)
    q find 'a|b*'  names matching, with signatures   q grep TEXT  definitions whose source contains TEXT, with the lines   q body A,B  several bodies
  sspur start [NAME|PATTERN...]         (experimental) the agent spec plus this codebase: all of it if small, else counts, q pack of NAMEs, q find of the rest
  sspur spec [--more|--full] [src] [QUERY TARGET...]  (experimental) the spec (--more: the rest of the builtins, --full: the reference), then optionally src and queries
  sspur src | log | export | mcp [--dir PATH] | --version   (mcp is experimental)
  sspur lsp                             the language server, over stdio, for VS Code and other editors (docs: Editor setup)
  sspur bind header.h [--lib NAME] [-o out.ssp]   generate extern declarations from a C header (uses clang)
  sspur export-c file.ssp [-o libfoo] [--shared] [--prefix P]  build a C library and header from the C-compatible functions
  add --json for machine output (apply, edit, q, check)
  sspur check|run|test|fuzz|verify|hash|fmt|native [file.ssp] [--json] [--cases N] [--seed N] [--edge] [--write] [--full]
  sspur fix [file.ssp|dir...] [--check]  rewrite code written for 0.14 so 1.0 reads it the same, and list what to check (--check writes nothing, exit 1 if it would rewrite)
  check also lists up to 3 candidates for each typed hole, run against the tests and contracts (--fill-ms N per hole, default 400; --no-fill skips them)
  sspur fill [file.ssp] [?hole|fn] [--apply] [--all] [--json] [--fill-ms N]  the best expression for each typed hole; --apply writes it in
  sspur suggest [file.ssp] [fn] [--apply] [--json] [--cases N]  pre and post clauses that held on every test-derived and fuzzed run; --apply adds them
  verify proves pre/post/where clauses with z3 and reports proved, counterexample, or unknown per clause
  run/test compile to native code by default (cached); --interp forces the interpreter, --native uses the Cranelift JIT (experimental),
  --O3 raises the optimization level; fuzz --differential compares native against the interpreter
  a failed native build prints one warning and falls back (--quiet hides it); --strict-native or SSPUR_STRICT_NATIVE=1 makes it an error
  sspur fuzz --gen N [--seed S] [--cases C] [--group G] [--modes O2,O2-whole,O3] [--keep DIR] [--print]  generate N random programs and compare interpreter and native
  run/test/build --pgo [--retrain] (experimental) train once, then build with clang profile data; --lto off|thin|full (ADR 0024)
  sspur build --backend llvm file.ssp [-o prog]  (experimental) direct LLVM IR prototype for the scalar and record subset (ADR 0024)
  sspur explain-opt file.ssp [--all]    show the proven rewrites applied before native codegen (--all adds rejected candidates)
  sspur run --profile file.ssp          run in the interpreter and record call counts that guide inlining (ADR 0022)
  sspur build --target riscv64-qemu|aarch64-qemu|thumbv7em-mps2 file.ssp [-o kernel.elf]  (experimental) build a 'profile bare' kernel or Cortex-M4 firmware (freestanding C, clang, ld.lld)
  run/test/build --debug (or SSPUR_DEBUG=1) build native code with DWARF (CodeView on Windows) mapped to the .ssp lines, at -O0
  sspur debug [file.ssp] [args] [--debugger lldb|gdb]  build with --debug and start lldb (macOS) or gdb (Linux) on it, stopped at main
  sspur debug-init [--install]          write the lldb and gdb formatters for Str, List, Opt, Bool and sums; --install loads them from ~/.lldbinit and ~/.gdbinit
  sspur gpu file.ssp [--emit metal|opencl|spirv|ptx] [-o out]  (experimental) emit the kernel fns as Metal, OpenCL C, SPIR-V or PTX (ADR 0020)
  sspur deploy plan|local|migrate|replay|swap|promote|rollback|backfill|status   (experimental; sspur deploy for details; ADR 0016, 0019; never calls AWS)
  sspur sync serve [--port N] [--max N] | push|pull REMOTE [--agent NAME] | status | resolve ours|theirs [PATH]   (experimental) replicate by hash (REMOTE: dir or tcp://host:port; ADR 0021)
$ sspur deploy
usage (nothing here calls AWS):
  sspur deploy plan <file.ssp> [--out DIR]          write template.json, iam/*.json, plan.json, bootstrap.c, build.sh, deploy.sh, rollback.sh, backfill.sh and a host build to DIR (default deploy/)
  sspur deploy local <file.ssp> [--port N] [--record FILE]   serve on 127.0.0.1 with emulated Lambda and DynamoDB; --record appends requests, responses and db calls as JSON lines
  sspur deploy migrate <old.ssp> <new.ssp> [--out DIR] [--json]   classify store schema changes, check migrate_/unmigrate_ functions, write migrate.json and backfill.sh
  sspur deploy replay <recording> <new.ssp> [--strict] [--json]   re-run a recording against new.ssp and report behavior differences (exit 1 if any)
  sspur deploy swap <new.ssp> [--weight P] [--port N]   hot swap a running deploy local to new.ssp (P% canary, default 100)
  sspur deploy promote|rollback|status [--port N]   promote the canary, roll back to the previous version, or show versions
  sspur deploy backfill [--store S] [--port N]       migrate every stored item of the live version's migrated stores in the emulated table

By task

TaskCommands
Start a projectsspur new web-app NAME (the reference web app, see Build a web app), sspur new --list
Run and test a filesspur run FILE, sspur test FILE, sspur check FILE [--json], sspur fmt FILE [--write]
Find bugs and prove propertiessspur fuzz FILE (contracts as property tests), sspur fuzz --differential FILE (native against the interpreter), sspur verify FILE (Z3)
Write code and contracts from testssspur fill [FILE] [?HOLE] [--apply] (typed holes), sspur suggest [FILE] [FN] [--apply] (pre and post from runs)
Work on a codebasesspur init [FILE], sspur start [NAME...], sspur q QUERY TARGET, sspur edit --test -e SRC, sspur log, sspur src
Agents and MCP`sspur spec [–more
Editorssspur lsp (the language server, see Editor setup)
Packagessspur init --pkg NAME, sspur add NAME[@REQ] [--as KEY], sspur add PATH-OR-URL[@REV], sspur deps fetch, sspur update [NAME...] [--force] (also sspur deps update), sspur deps tree
The registrysspur search [QUERY], sspur source NAME[@VERSION] [DIR], sspur publish [--dry-run], sspur yank NAME@VERSION, sspur api [--json], sspur archive (see Publishing a package)
Native code and Csspur native FILE, sspur run --O3, --pgo, --lto, sspur explain-opt FILE, sspur bind HEADER.h, sspur export-c FILE
Debug with lldb or gdbsspur debug FILE [ARGS], sspur build --debug FILE, sspur run --debug FILE, sspur debug-init [--install] (see Debugging)
Bare metal and GPUsspur build --target riscv64-qemu|aarch64-qemu|thumbv7em-mps2 FILE, sspur gpu FILE --emit metal|opencl|spirv|ptx
Servicessspur deploy plan, local, migrate, replay, swap, promote, rollback, backfill, status
Many agents and replicassspur sync serve, push, pull, status, resolve
Move code from 0.14 to 1.0sspur fix [FILE|DIR...] [--check] (see Migrating to 1.0)

Flags the usage text leaves out

These are for working on SSPUR itself or for scripts, so the usage text doesn’t list them:

FlagDoes
sspur native --emit-c FILEPrints the generated C instead of building it
sspur native --release FILEBuilds each definition at -O2 and prints which functions are native and which were skipped, and why
sspur export-c --keep-c FILEKeeps the generated .c file next to the library
sspur q list --testsLists the tests only
sspur fuzz --gen N --from KStarts the generated programs at index K (to rerun a range)
sspur fuzz --reduce FILEShrinks a program while it still typechecks and still shows the behavior: a mismatch between --modes, --want-skip TEXT (a native skip reason), --want-build TEXT (a build error) or --want-hang "ARGS FILE" (a child sspur ARGS that crashes or hangs)
sspur q pack NAME --fullAlso prints the estimated tokens and what was included and omitted, on stderr
sspur init FILE --json, sspur sync push|pull|resolve --jsonPrint the result as JSON
sspur publish --tests-jsonRuns the package’s tests in both tiers and prints them as JSON, for the registry’s test job

Starting a project

sspur new web-app NAME writes a new directory NAME (which must not exist or be empty; its last component is the package name, so notes or my_notes) with SSPUR’s reference application, a notes service with accounts: main.ssp with the routes and handlers, module packages for the data types, SQLite storage, accounts and HTML views, three reusable packages, tests that sspur run tests/run.ssp runs in both tiers, a backup tool, a systemd unit and a Caddyfile, and a README that covers running, testing, debugging, configuring, backups and deploying. The template is built into sspur, so nothing is downloaded. sspur new --list lists the templates.

Migrating from 0.14

sspur fix takes files or directories (all the .ssp files under them; with none, the package the current directory is in) and rewrites code written for 0.14 so that 1.0 reads it as 0.14 did: string literals whose braces or escapes changed meaning, arms that 0.14 gave to a nested match or catch, and binding annotations that 0.14 ignored and that don’t hold. It edits only those spans, so comments and layout stay, and running it again changes nothing. It also lists, as check: lines, the places whose meaning changed and that it can’t rewrite: == through a hand-written Eq inside an Opt, List, Res, tuple or derived type, round floor ceil trunc on an F64, and comparisons with 0.0. --check writes nothing and exits with 1 if it would rewrite something. Migrating to 1.0 has each change.

Execution tiers

run and test compile to native code through C and clang, cached by content hash under ~/.cache/sspur/native/. A plain sspur run FILE of a program that hasn’t changed since its last run skips parsing and checking and calls the cached library’s main directly (ADR 0034); --no-cache takes the normal path. --interp uses the reference interpreter and --native the Cranelift JIT. If a native build fails, SSPUR prints one warning and falls back to the interpreter; --strict-native or SSPUR_STRICT_NATIVE=1 makes that an error, which is how CI runs.

Debugging

--debug on run, test, build and native, or SSPUR_DEBUG=1, builds native code with debug info that maps to the .ssp lines: DWARF (CodeView on Windows) at -O0, functions, parameters and locals under their SSPUR names where C allows it, cached separately from normal builds, which don’t change. sspur build --debug FILE builds it and prints the library and the source file its lines refer to (for a codebase, a copy of its source under ~/.cache/sspur/debug/src/). sspur debug FILE [ARGS] builds it and starts lldb (macOS) or gdb (Linux, or --debugger gdb) with the formatters loaded and breakpoints on fn main and on sspur_trap, which every trap calls. sspur debug-init writes the lldb and gdb formatters to ~/.cache/sspur/debug/ and prints the lines that load them; --install adds those lines to ~/.lldbinit and ~/.gdbinit. See Debugging and ADR 0036.

Queries

The query API is how an agent reads a large codebase without printing all of it. The graph model describes every query; these are the ones agents use most:

QueryPrints
q find 'ship|tax_*'definitions whose names match (substring, * glob, ^ and $ anchors), with signatures
q grep TEXTdefinitions whose source contains TEXT, with up to 3 matching lines each
q body A,Bthe source of several definitions
q pack A,B,Ceach definition with the types and signatures it uses, its tests and its callers, and a line saying whether that list is complete
q callers NAME, q callees NAMEthe call graph around one definition
q effects NAMEthe declared effect row of one definition
q diag, q logevery diagnostic, and the history of edits

Holes and suggested contracts

sspur check lists up to three candidates under each typed hole when the program has no errors, and --json puts them in the hole’s fix as fill ops with an evidence string and ok. The search has 400 ms per hole (--fill-ms N changes it, --no-fill turns it off); files without holes are checked exactly as before.

CommandPrints
sspur fill [FILE] [?NAME|FN]The best candidate for each hole (or the named one, or every hole in FN): ?total: items.map(_.qty * _.price).sum (passes 3 tests, post), with 2 others also fit added when other candidates pass the same checks, so the tests don’t decide between them. --all shows up to 3, --json adds the search size and time. --apply writes the passing ones into the file, in parentheses where precedence needs them, or applies them to the codebase as fill ops. The budget is 1000 ms per hole
sspur suggest [FILE] [FN]For each function, the pre and post lines that held on every run, ready to paste: post r.len <= xs.len // proved. --json gives refine fixes, --apply adds them, --cases N sets the number of fuzzed inputs (200)

The MCP server has both as the tools fill and suggest (MCP).

The registry

CommandDoes
sspur add NAME[@REQ]Adds a published package to [deps] as { version = "REQ" } (default ^ the newest version) and locks the newest version that meets it. NAME@1.2.3, a whole version, writes =1.2.3: exactly that version. A scoped name, sspur add acme/money, is added under the key after the scope, money = { package = "acme/money", version = "^1.0.0" }; --as KEY chooses the key, and is needed when the key is taken. A file or directory called NAME is added by path instead
sspur update [NAME...] [--force]sspur deps update: moves the named packages (or all) to the newest versions their requirements allow, prints the export diff, and writes the lock only if your code still checks
sspur search [QUERY] [--json]Packages whose name, keywords or description match, with their newest version
sspur source NAME[@VERSION] [DIR]Downloads a version’s source into DIR (default NAME-VERSION, with / as - for a scoped name), verified against its snapshot SHA-256, without building or running it. @1.2.3 is exactly that version, @^1.2 the newest that matches
sspur publishRuns the registry checks on the package at HEAD and every module in it (their tests natively and in the interpreter too, side by side, in a copy of the commit), prints the report, and when no blocking check fails, opens the publish request: with the GitHub CLI if it is signed in, else by printing the prefilled form URL. --dry-run only checks, --ack net,fs acknowledges new capability effects, --ref TAG names the tag (default the tag at HEAD), --repo URL the GitHub repository (default the origin remote), --no-tests skips the tests, --no-gh prints the URL, --json prints the report as JSON
sspur yank NAME@VERSIONOpens a request to yank the version (acme/money@1.0.0 for a scoped name); --undo unyanks, --deprecate MESSAGE deprecates (with --undo, undeprecates), --reason TEXT explains, --no-gh prints the URL
sspur api [--json] [--rev REV]The package’s public API with lines and doc comments; --json gives the facts the checks read (diagnostics, formatting, definitions, effects, export hash, and in a git repository the files of REV)
sspur archive [--rev REV] [-o FILE]Builds the source snapshot of a commit (default HEAD) as the registry stores it, a deterministic tar, and prints its SHA-256 and size

The index is read from SSPUR_INDEX (default sspur-packages on raw.githubusercontent.com) and cached under ~/.cache/sspur/index; see Environment variables.

The language server

sspur lsp speaks the Language Server Protocol over stdin and stdout, for VS Code and any other editor with an LSP client (Editor setup, ADR 0031). It publishes diagnostics as you type, a short pause after the last edit, and then the candidates for each typed hole (600 ms a hole, or SSPUR_FILL_MS). It answers completion, hover, signature help (after ( and ,, with the argument being typed highlighted, on half-typed calls too), inlay hints (the inferred type after a binding whose type isn’t written, and the parameter name before a literal argument), code actions (the compiler’s fixes and the hole fills), formatting, definition, references, document highlights, rename, the outline and semantic tokens, all within one file, and the command sspur.fill fills every hole that has a passing candidate. A file under a directory with sspur.toml is checked with that package’s dependencies. It takes no options, and ignores --stdio, which some clients add. Inlay hints can be narrowed with initializationOptions or the sspur settings section, {"inlayHints": {"types": true, "lambdas": true, "params": false}}, where types covers let, guard, for and var bindings, lambdas lambda parameters and params the names before literal arguments; all three are on by default.