Skip to content

Ashes Compiler CLI Specification ​

This document is the authoritative reference for every command, flag, and argument supported by the Ashes compiler CLI (ashes).

Source of truth: The CLI entry point and all argument-parsing logic live in src/Ashes.Cli/Program.cs. Update that file—and this document—together whenever CLI behaviour changes.


Overview ​

The Ashes CLI is invoked as:

sh
ashes <command> [options] [arguments]

Running ashes with no arguments (or an unrecognised command) prints the help text and exits with code 2.

Running ashes --help (or ashes -h) prints the same help text and exits with code 0. Running ashes <command> --help (or ashes <command> -h) also prints the CLI help text and exits with code 0.


Command List ​

CommandShort description
ashes compileCompile an Ashes source file/expression to a native executable
ashes runCompile and immediately execute an Ashes program
ashes replStart an interactive read-eval-print loop
ashes testRun .ash test files and compare against // expect: comments
ashes fmtFormat .ash source files
ashes initCreate a new Ashes project in the current directory
ashes addAdd a dependency to the project manifest (--path, --dev)
ashes removeRemove a dependency from the project manifest
ashes restoreResolve dependencies (path + registry); --frozen, --offline
ashes treeRender the resolved dependency tree
ashes whyShow why a package is in the dependency graph
ashes loginStore an API token for a registry
ashes publishPackage the current project and publish it to a registry
ashes yankYank (or --undo) a published version
ashes searchSearch a registry for packages
ashes infoShow a package's versions, owners, and capabilities

Registry commands ​

These talk to an Ashes package registry (see the registry API reference). Each accepts --registry <name-or-url>, resolving a name from ~/.ashes/config.json (the default entry falls back to the canonical public instance) or using a literal http(s):// URL as-is. Credentials are stored per registry base URL in ~/.ashes/credentials.json.

CommandSynopsisNotes
ashes loginashes login [--registry <r>] (--as <account> | --token <t>)--as mints a token via the registry; --token stores a provided one.
ashes publishashes publish [--registry <r>] [--project <ashes.json>] [--version <v>]Packages the project's .ash sources (plus a portable ashes.json and README/LICENSE), computes the ash1: content hash, and uploads. Rejects object-valued runtime dependencies; strips overrides and devDependencies. Namespace derives from namespace or the PascalCase package name; version from --version or the manifest. Requires a prior login.
ashes yankashes yank <namespace> <version> [--undo] [--registry <r>]Marks a version un-resolvable for new builds (existing locks still resolve); --undo reverses it. Owner-only.
ashes searchashes search <query> [--registry <r>]Prints a ranked list (namespace, latest, description).
ashes infoashes info <namespace>[@<version>] [--registry <r>]Shows owners and, for the selected (or latest) version, its capability row and dependencies.

The capability row shown by ashes info is the registry's server-computed audit — the capabilities the package's public API needs, inferred by the compiler at publish time, not a heuristic scan. Built-in ambient markers such as FileRead, ProcessSpawn, and UnsafeFfi are included in this metadata exactly like declared capability rows.


Common Options ​

The following help flags are accepted at the top level and for each command:

OptionValue typeDefaultRepeatableDescription
--help / -h——NoPrint CLI help text and exit successfully.

The following options are accepted by compile, run, repl, and test:

OptionValue typeDefaultRepeatableDescription
--target <id>enumOS-dependent (see below)NoSelect the code-generation back end.
--target-cpu <cpu>stringgeneric / x86-64NoTarget a specific CPU microarchitecture (e.g. skylake, znver3, apple-m1). Use native to auto-detect the host CPU only when the host and target architectures match; cross-compilation requires an explicit CPU or the generic default.
--parallel-stack-size <size>size1MNoPer-worker stack size for structured parallelism (Ashes.Task.Parallel). Accepts a byte count or a K/M/G suffix (e.g. 2M, 1048576); must be positive. On linux this sets the mmap'd worker-stack length; on win-x64 it is passed to CreateThread (unset leaves the OS default).
--parallel-workers <n>intcore countNoThe executable's compiled maximum / hard ceiling for concurrent structured-parallelism workers. Must be positive. When unset, the compiled program detects the machine's core count once at first fork (linux: sched_getaffinity popcount, so taskset/cgroup masks are respected; win-x64: GetSystemInfo), falling back to 8 if detection fails. A program can request fewer workers for a scoped computation with Ashes.Task.Parallel.withWorkers (effective count is min(override, this maximum)); it can never exceed this ceiling.
-O0|-O1|-O2|-O3enum-O2NoSelect LLVM optimization level.

The following option is accepted by compile and run only:

OptionValue typeDefaultRepeatableDescription
--debug / -gbool (flag)falseNoEmit DWARF debug info into the output binary. See Debug Mode below.

Optimization level values:

FlagMeaning
-O0No optimization
-O1Basic optimizations
-O2Standard optimizations (default)
-O3Aggressive optimizations

The following option is accepted by compile, run, and test:

OptionValue typeDefaultRepeatableDescription
--explain <kind>[:<selector>]enum—YesReport compiler decisions to stderr. See Compiler Reports below.

The following option is accepted by compile and run:

OptionValue typeDefaultRepeatableDescription
--emit-ir <stage>[:<selector>]enum—YesDump semantic IR to stderr. See IR Dumps below.

--project is accepted by project-scoped commands: compile, run, test, add, remove, restore, tree, why, and publish. It is not accepted by ashes repl.

OptionValue typeDefaultRepeatableDescription
--project <path>file path—NoLoad a project manifest (ashes.json). Mutually exclusive with an inline file argument and --expr.

--target values:

ValuePlatform
linux-x64Linux x86-64 — emits a native ELF64 binary
linux-arm64Linux AArch64 — emits a native ELF64 binary
win-x64Windows x86-64 — emits a native PE32+ binary
win-arm64Windows on ARM64 — emits a native PE32+ (AArch64) binary, and is also a host RID (a released compiler runs on Windows-on-ARM). Neither the emitted image nor the WoA compiler executes on an x64 host — both run only on native ARM64 Windows

Any other value is rejected with an error message and exit code 1.


Command Reference ​

ashes compile ​

Compile an Ashes program to a native executable on disk.

Synopsis ​

sh
ashes compile [--target <id>] [--target-cpu <cpu>] [--parallel-stack-size <size>] [--parallel-workers <n>] [-O0|-O1|-O2|-O3] [--debug|-g] [--explain <kind>] [-o <output>] <input.ash>
ashes compile [--target <id>] [--target-cpu <cpu>] [--parallel-stack-size <size>] [--parallel-workers <n>] [-O0|-O1|-O2|-O3] [--debug|-g] [--explain <kind>] [-o <output>] --expr "<source>"
ashes compile [--target <id>] [--target-cpu <cpu>] [--parallel-stack-size <size>] [--parallel-workers <n>] [-O0|-O1|-O2|-O3] [--debug|-g] [--explain <kind>] [-o <output>] --project <ashes.json>
ashes compile [--target <id>] [--target-cpu <cpu>] [--parallel-stack-size <size>] [--parallel-workers <n>] [-O0|-O1|-O2|-O3] [--debug|-g] [--explain <kind>] [-o <output>]          # discovers ashes.json upward

Arguments ​

NameTypeRequiredDescription
<input.ash>file pathNo†Path to the .ash source file to compile.

† Exactly one input source must be provided: a positional file, --expr, or a project (explicit or auto-discovered).

Options ​

OptionLong formValue typeDefaultRepeatableDescription
-o--outfile pathDerived from input name (see below)NoPath for the compiled output binary.
--exprstring—NoInline Ashes source to compile instead of reading a file.
--targetenumOS defaultNoTarget back end (linux-x64, linux-arm64, win-x64, or win-arm64).
--target-cpustringgeneric / x86-64NoTarget CPU microarchitecture (e.g. skylake, native).
--parallel-stack-sizesize1MNoPer-worker stack size for Ashes.Task.Parallel (byte count or K/M/G suffix).
--projectfile path—NoPath to an ashes.json project file.
-O0|-O1|-O2|-O3enum-O2NoSelect LLVM optimization level.
--debug-gbool (flag)falseNoEmit DWARF debug info (see Debug Mode).

Default output path rules (when -o is not given):

  • Single file examples/hello.ash → examples/hello (Linux) or examples/hello.exe (Windows).
  • --expr without a project → out (Linux) or out.exe (Windows).
  • Project compile → <outDir>/<name> (from ashes.json), appending .exe on Windows.

Defaults ​

PropertyDefault value
--targetlinux-x64 on Linux x86-64, linux-arm64 on Linux ARM64, win-x64 on Windows
--target-cpux86-64 for x86-64 targets, generic for ARM64
-o / --outDerived from input (see above)
-O0..-O3-O2 (standard optimizations)

Exit Codes ​

CodeMeaning
0Compilation succeeded; binary written to disk.
1Compilation error (parse, type, or code-generation failure), or user/input error such as missing input, file not found, wrong extension, or I/O failure.
2Usage error (bad flag or conflicting options).

Output (stdout / stderr) ​

Successful status messages are written to stdout via Spectre.Console. On success, a confirmation line is printed:

text
OK  Wrote <size> to <output>
     Target: <target>
     Debug:  yes          (only when --debug / -g is set)
     Time:   <elapsed>

<size> is human-readable (e.g. 8.1 KB). <elapsed> is the compilation wall-clock time (e.g. 159ms or 1.23s).

Compiler diagnostics and command errors are written to stderr. No machine-readable output format is currently supported.

Examples ​

bash
# Compile a file (output: examples/hello on Linux)
ashes compile examples/hello.ash

# Compile to an explicit path
ashes compile examples/hello.ash -o build/hello

# Compile an inline expression
ashes compile --expr 'Ashes.IO.print(40 + 2)' -o out

# Cross-compile to Windows PE
ashes compile examples/hello.ash --target win-x64 -o hello.exe

# Compile the project rooted at ashes.json
ashes compile --project path/to/ashes.json

# Auto-discover ashes.json (searches upward from cwd)
ashes compile

Set the ASHES_TIMING environment variable to any value to report how long each compile phase took on stderr, one timing: <phase> <milliseconds> ms line per phase: lower (semantic lowering with lifetime placement), optimize (the IR optimizer), and backend with its backend.emit-module, backend.verify, backend.llvm-passes, backend.bitcode-link, backend.object-code, and backend.link parts (a split program reports backend.parallel-objects with one backend.partition<N>.llvm-passes and backend.partition<N>.object-code pair per partition instead). lower is itself split into lower.deriving, lower.register-traits, lower.register-inlinable, lower.move-analysis (with its calls, handlers, reach, provenance, inspect-only, and summaries parts), lower.body, lower.entry-inference, lower.placement.entry, and lower.placement.functions; a program that needs the inferred-trait discovery pass reports the first four twice, once for that pass. optimize is split into optimize.compile-time-eval, optimize.entry, optimize.functions, optimize.closures (with its captured, returned, currying, scalarize, and returned-again parts), optimize.non-allocating, optimize.brackets, and optimize.concat. The generated code is unaffected.

The compiler runs with the server garbage collector, which keeps collection pauses off the single-threaded lowering and optimizer phases at the cost of a somewhat larger heap.

A large Linux program (1024 lifted functions or more) is optimized and turned into object code in parallel partitions that are merged into one relocatable object before linking; see Parallel code generation. Set ASHES_LLVM_JOBS to the number of partitions to use, or to 1 to keep a single module. The partition count is derived from the program's size by default, so the same source produces the same executable on every machine.


ashes run ​

Compile and immediately execute an Ashes program. The compiled binary is written to a uniquely named executable under the system temporary directory (for example, <temp>/ashes/) and is not automatically deleted by the CLI.

Synopsis ​

sh
ashes run [--target <id>] [--target-cpu <cpu>] [--parallel-stack-size <size>] [--parallel-workers <n>] [-O0|-O1|-O2|-O3] [--debug|-g] [--explain <kind>] <input.ash> [-- <args...>]
ashes run [--target <id>] [--target-cpu <cpu>] [--parallel-stack-size <size>] [--parallel-workers <n>] [-O0|-O1|-O2|-O3] [--debug|-g] [--explain <kind>] --expr "<source>" [-- <args...>]
ashes run [--target <id>] [--target-cpu <cpu>] [--parallel-stack-size <size>] [--parallel-workers <n>] [-O0|-O1|-O2|-O3] [--debug|-g] [--explain <kind>] --project <ashes.json> [-- <args...>]
ashes run [--target <id>] [--target-cpu <cpu>] [--parallel-stack-size <size>] [--parallel-workers <n>] [-O0|-O1|-O2|-O3] [--debug|-g] [-- <args...>]   # auto-discovers ashes.json

Arguments ​

NameTypeRequiredDescription
<input.ash>file pathNo†Path to the .ash source file to run.
-- <args...>string listNoArguments forwarded to the compiled program (args in Ashes source). The -- separator is required.

† Same rule as compile: one of file, --expr, or project.

Options ​

OptionValue typeDefaultRepeatableDescription
--exprstring—NoInline Ashes source to compile and run.
--targetenumOS defaultNoTarget back end.
--target-cpustringgeneric / x86-64NoTarget CPU microarchitecture (e.g. skylake, native).
--parallel-stack-sizesize1MNoPer-worker stack size for Ashes.Task.Parallel (byte count or K/M/G suffix).
--projectfile path—NoPath to an ashes.json project file.
-O0|-O1|-O2|-O3enum-O2NoSelect LLVM optimization level.
--debug / -gbool (flag)falseNoEmit DWARF debug info (see Debug Mode).

Exit Codes ​

CodeMeaning
0Program compiled and exited with code 0.
Non-zero (from program)The compiled program's own exit code is propagated as-is.
1Compilation error or user/input failure (before the program starts).
2Usage error.

Output (stdout / stderr) ​

The compiled program's stdout and stderr flow directly to the terminal without interception. No extra diagnostic lines (timing, size, etc.) are printed, so ashes run output is safe to pipe. Compiler diagnostics are printed to the CLI's stderr before the program runs.

Examples ​

bash
# Run a file
ashes run examples/hello.ash

# Run with arguments passed to the program
ashes run examples/hello.ash -- hello world

# Run an inline expression
ashes run --expr 'Ashes.IO.print(40 + 2)'

# Run the auto-discovered project
ashes run

# Run a named project
ashes run --project examples/project/ashes.json

ashes repl ​

Start an interactive read-eval-print loop. Each expression is compiled to a temporary binary, executed, and its stdout is displayed.

Synopsis ​

sh
ashes repl [--target <id>] [--target-cpu <cpu>] [-O0|-O1|-O2|-O3]

Options ​

OptionValue typeDefaultRepeatableDescription
--targetenumOS defaultNoTarget back end used for all REPL evaluations.
--target-cpustringgeneric / x86-64NoTarget CPU microarchitecture used for all REPL evaluations.
-O0|-O1|-O2|-O3enum-O2NoSelect LLVM optimization level used for all REPL evaluations.

REPL Commands (typed at the prompt) ​

CommandAliasesDescription
:help:hShow REPL help.
:quit:q, :exitExit the REPL (exit code 0).
:targetShow the current target.
:target linux-x64|linux-arm64|win-x64|win-arm64Change the active target for subsequent expressions.

Multi-line input is supported: if the parser detects an incomplete expression (unbalanced parentheses or an expected-token error), the REPL shows a ...> continuation prompt.

Exit Codes ​

CodeMeaning
0User exited cleanly (:quit / :exit / :q).
2Usage error (unknown flag).

Output (stdout / stderr) ​

All REPL output (prompts, results, errors) is written to stdout. Compiled-program stderr is relayed and highlighted in red.

Examples ​

bash
ashes repl
# ashes> Ashes.IO.print(40 + 2)
# 42
# ashes> :target linux-x64
# Target set to linux-x64
# ashes> :quit

ashes test ​

Discover and execute .ash test files. A test file must contain a leading // expect: comment; the runner compiles the file, executes it, and compares stdout against the expected value.

Synopsis ​

sh
ashes test [--target <id>] [--target-cpu <cpu>] [-O0|-O1|-O2|-O3] [--pipeline <optimized|lowered|both>] [--project <ashes.json>] [--explain <kind>] [paths...]

Arguments ​

NameTypeRequiredDescription
[paths...]file/directory path listNoOne or more .ash files or directories to search for tests. Repeatable. When --project is supplied and no paths are given, the default search root is <projectDir>/tests if that directory exists, otherwise it is the project directory itself.

Options ​

OptionValue typeDefaultRepeatableDescription
--targetenumOS defaultNoTarget back end for test compilation.
--target-cpustringgeneric / x86-64NoTarget CPU microarchitecture for test compilation.
--projectfile path—NoLoad a project manifest. When no explicit [paths...] are given, test discovery uses <projectDir>/tests if that directory exists; otherwise it falls back to the project directory itself.
-O0|-O1|-O2|-O3enum-O2NoSelect LLVM optimization level for test compilation.
--pipelineoptimized|lowered|bothoptimizedNoSelect whether tests use the normal Ashes semantic optimizer, bypass it, or run through both semantic pipelines. This is independent of the LLVM -O level.

Test File Conventions ​

A test file is a regular .ash source file with one or more leading comment directives:

ash
// expect: <expected stdout>
// exit: <expected exit code>   (optional; defaults to 0)
  • Only the first // expect: line is used.
  • // exit: must appear before // expect:.
  • Files without // expect: are reported as SKIP (treated as pass).

Special expected value:

ValueMeaning
emptyExpect empty stdout.

Example:

ash
// exit: 1
// expect: empty
panic("empty")

Exit Codes ​

CodeMeaning
0All tests with // expect: passed (or none found).
1One or more tests failed.
2Usage error.

Output (stdout / stderr) ​

Results are printed as a table to stdout:

text
 Test          Result   Time
──────────────────────────────
 hello.ash     PASS     5ms
 failing.ash   FAIL     12ms

The per-test Time value measures only the launched native process, excluding parsing, lowering, LLVM code generation, linking, executable-file setup, and test-fixture setup. Sub-millisecond executions are reported in microseconds. With --pipeline both, the column labels the two process times as opt=<time> lowered=<time>. A compile-error test displays — because it does not launch a process.

The default optimized pipeline matches ashes compile and ashes run: it lowers the program, runs the Ashes semantic optimizer, and passes the resulting IR to LLVM. lowered passes the raw lowered IR directly to LLVM, which is useful for exposing correctness bugs hidden by dead-code elimination or compile-time evaluation. both requires every runnable test to satisfy the same directives in both pipelines. Compile-error tests run once because compilation stops before the semantic optimizer boundary. The selected -O0 through -O3 level still applies independently to LLVM in every pipeline.

A summary line follows: N passed, N failed, N skipped in <elapsed>. Its elapsed value retains the complete compile-and-run test time. Failure details (expected vs. actual stdout) are printed after the table.

Examples ​

bash
# Run all tests in the default location
ashes test

# Run tests in a specific directory
ashes test tests/

# Run a single test file
ashes test tests/hello.ash

# Run tests for a named project
ashes test --project path/to/ashes.json

# Compare normal and raw-lowered semantic pipelines
ashes test --pipeline both tests/

ashes fmt ​

Format .ash source files. ashes fmt resolves formatting from the nearest .editorconfig (walking upward from each file), supporting:

  • indent_style = space|tab
  • indent_size = <int>|tab
  • tab_width = <int>
  • end_of_line = lf|crlf
  • ashes_prefer_pipelines = true|false (write a chain of two or more nested calls as a |> pipeline; see the formatter reference)

Defaults when not provided are 4 spaces, platform newline, and no pipeline rewriting. Without -w, formatted output is printed to stdout. With -w, files are updated in place.

Synopsis ​

sh
ashes fmt <file|dir> [-w]

Arguments ​

NameTypeRequiredDescription
<file|dir>file/directory pathYesA single .ash file or a directory. When a directory is given, all .ash files are found recursively.

Options ​

OptionLong formValue typeDefaultRepeatableDescription
-w--writebool (flag)falseNoWrite formatted output back to the source file(s) instead of printing to stdout.

Exit Codes ​

CodeMeaning
0Formatting succeeded (or no .ash files found).
1Parse error in one of the source files, or user/input error such as missing path, wrong extension, or path not found.
2Usage error (bad flag or ambiguous arguments).

Output (stdout / stderr) ​

  • Without -w: formatted source is written to stdout. When multiple files are formatted, a separator rule is printed before each file.
  • With -w: a summary line (OK Formatted N file(s) in <elapsed>.) is written to stdout. Files that are already correctly formatted are not rewritten.

Examples ​

bash
# Preview formatting of a single file
ashes fmt examples/hello.ash

# Format all .ash files in a directory, writing in place
ashes fmt examples -w

# Preview formatting for all .ash files in a directory
ashes fmt examples

ashes init ​

Create a new Ashes project in the current directory by scaffolding an ashes.json manifest and a starter src/Main.ash entry file.

Synopsis ​

sh
ashes init

Arguments ​

None.

Options ​

None.

Behaviour ​

  1. If ashes.json already exists in the current directory, the command fails with exit code 1.
  2. Creates ashes.json with name (derived from the directory name), entry set to "src/Main.ash", and sourceRoots set to ["src"].
  3. Creates the src/ directory if it does not exist.
  4. Creates src/Main.ash with a hello-world program. If the file already exists it is not overwritten.

Exit Codes ​

CodeMeaning
0Project created successfully.
1ashes.json already exists or an I/O error occurred.

Examples ​

bash
mkdir myapp && cd myapp
ashes init
# Created ashes.json
# Created src/Main.ash

ashes add ​

Add a dependency to the selected project manifest, or to the nearest ashes.json when --project is omitted.

Synopsis ​

sh
ashes add <package> [--project <manifest>] [--path <dir>] [--dev]

Arguments ​

NameTypeRequiredDescription
<package>stringYesThe dependency name to add.

Options ​

NameDescription
--project <manifest>Update a specific project manifest instead of discovering the nearest ashes.json.
--path <dir>Record a path dependency { "path": "<dir>" } instead of a registry version.
--devWrite to devDependencies instead of dependencies.

Behaviour ​

  1. Uses --project when supplied; otherwise discovers ashes.json by walking upward from the current directory.
  2. If no ashes.json is found, the command fails with exit code 1.
  3. Adds the dependency to dependencies (or devDependencies with --dev): { "path": "<dir>" } when --path is given, otherwise the SemVer string "*". Existing entries are overwritten; other dependencies are preserved.

Exit Codes ​

CodeMeaning
0Dependency added successfully.
1No ashes.json found, missing package argument, or I/O error.

Examples ​

bash
ashes add json-parser
# Added json-parser to dependencies.

ashes add test-helper --dev --project ashes-test.json
# Added test-helper to devDependencies.

ashes remove ​

Remove a dependency from the selected project manifest, or from the nearest ashes.json when --project is omitted.

Synopsis ​

sh
ashes remove <package> [--project <manifest>]

Arguments ​

NameTypeRequiredDescription
<package>stringYesThe package name to remove.

Options ​

NameDescription
--project <manifest>Update a specific project manifest instead of discovering the nearest ashes.json.

Behaviour ​

  1. Uses --project when supplied; otherwise discovers ashes.json by walking upward from the current directory.
  2. If no ashes.json is found, the command fails with exit code 1.
  3. If the package is not present in dependencies, the command fails with exit code 1.
  4. Removes the package from the dependencies object and writes the file back.
  5. If the dependencies object becomes empty after removal, the field is omitted from the written JSON.

Exit Codes ​

CodeMeaning
0Dependency removed successfully.
1No ashes.json found, missing package argument, package not in dependencies, or I/O error.

Examples ​

bash
ashes remove json-parser
# Removed json-parser from dependencies.

ashes restore ​

Resolve and materialize dependencies for the selected project manifest, or for the nearest ashes.json when --project is omitted.

Synopsis ​

sh
ashes restore [--project <manifest>] [--registry <name-or-url>] [--frozen] [--offline]

Options ​

NameDescription
--project <manifest>Restore a specific project manifest instead of discovering the nearest ashes.json.
--registry <r>Registry to resolve registry dependencies against (name or URL; default from config).
--frozenFail (ASH032/ASH033) if a fresh resolution would differ from the selected project's committed lock file; never rewrite it. For CI.
--offlineNever touch the network; trust the selected project's lock file and only verify its packages are in the cache.

Behaviour ​

  1. Uses --project when supplied; otherwise discovers ashes.json by walking upward from the current directory.
  2. Fetches and caches registry dependencies (resolving SemVer constraints across the transitive graph) and writes the selected manifest's lock file; then validates path dependencies (a missing path or non-project fails with ASH030 / ASH031) and lists every resolved dependency with its namespace.
  3. Applies root-level path overrides without changing the registry-only lock graph. Overrides in a dependency's manifest are ignored. The local package must declare the selected package's namespace and exact locked version.
  4. Each cached package's content is verified against the lock's ash1: hash (ASH034 on mismatch).
  5. build / run / test auto-restore when a project's lock is missing or a locked package is not cached (against the default registry). Use ashes restore explicitly to target a specific registry, or with --frozen / --offline.

Examples ​

bash
ashes restore --registry https://pkg.ashes-lang.org
# Resolved 2 dependencies into ashes.lock.

ashes restore --project ashes-test.json
# Resolved 1 registry dependency into ashes-test.lock.

ashes tree ​

Render the resolved dependency tree (project root → direct dependencies → their transitive dependencies) from the selected project's lock file. Path dependencies are shown as leaves.

sh
ashes tree [--project <manifest>]
# app
# └── Json 1.2.0
#     └── Utf8 0.4.3

ashes why ​

Show a path from a project root dependency to the named package, explaining why it is in the graph.

sh
ashes why Utf8 [--project <manifest>]
# Json -> Utf8

Project File (ashes.json) ​

The project file enables multi-module compilation and controls build settings.

Named manifests may be colocated and selected with --project. Each manifest owns the lock file with the same base name: ashes.json maps to ashes.lock, while ashes-test.json maps to ashes-test.lock.

Fields ​

FieldTypeRequiredDefaultDescription
entrystring (file path)Yes—Relative path to the entry-point .ash file.
namestringNoFilename stem of entryOutput binary name (without extension).
sourceRootsstring arrayNo["."]Directories searched when resolving import statements.
includestring arrayNo[]Additional directories searched for imported modules.
outDirstringNo"out"Directory where the compiled binary is written.
targetstring (enum)NoOS defaultDefault back end target; overridden by --target on the command line.
dependenciesobjectNo{}Runtime dependency map. Values are registry version constraints or local path objects; publishing rejects local path objects.
devDependenciesobjectNo{}Build/test dependency map with the same value forms as dependencies; never published.
overridesobjectNo{}Root-only map from a direct or transitive registry package to { "path": "..." }. The local namespace and version must exactly match the selected lock entry. Never published.

Example ​

json
{
  "name": "myapp",
  "entry": "src/Main.ash",
  "sourceRoots": ["src"],
  "outDir": "build",
  "target": "linux-x64"
}

Module Import Syntax ​

Within any .ash file in a project, modules can be imported with:

ash
import ModuleName
import Namespace.ModuleName
  • Module names must start with an uppercase letter.
  • Nested namespaces use dot notation; each component must start with an uppercase letter.
  • Import statements are collected from anywhere in the file; idiomatic style is to place them before any expression code.
  • Circular imports are detected and reported as an error.

Exit Code Summary ​

CodeMeaning
0Success.
1Compilation error, runtime error, test failure, or concrete user/input error.
2Usage / argument error (bad flag, conflicting options, or unknown command syntax).

The exit code from ashes run is the compiled program's own exit code when compilation succeeds.


Validation Rules and Error Behaviour ​

ConditionError message / behaviourExit code
Unknown commandHelp text printed2
Unknown flagUnknown argument.2
Missing required compile/run inputMissing input file or --expr.1
Missing required fmt pathMissing file or directory.1
Input file not foundFile not found: <path>1
Input file has wrong extensionInput file must have .ash extension (or use --expr).1
--project combined with file/--exprCannot combine --project with input file or --expr.2
--target receives unknown valueMessage indicating unknown target, e.g. Unknown target '<value>'.1
ashes.json missing entry fieldProject file is missing required string field 'entry'.1
ashes.json entry file not foundProject entry file not found: <path>1
Import cycle detectedImport cycle detected: A -> B -> A1
Parse / type error in sourceDiagnostic message1
ashes fmt given more or fewer than one pathProvide exactly one file or directory.2
ashes fmt path does not existPath not found: <path>1
ashes init when ashes.json existsashes.json already exists in this directory.1
ashes add without package argumentMissing package name.1
ashes add when no ashes.json foundNo ashes.json found. Run 'ashes init' first.1
ashes remove without package argumentMissing package name.1
ashes remove when no ashes.json foundNo ashes.json found. Run 'ashes init' first.1
ashes remove when package not a dependencyPackage '<name>' is not a dependency.1
ashes restore when no ashes.json foundNo ashes.json found. Run 'ashes init' first.1

Compiler Reports ​

--explain prints what the compiler decided about a program. It is accepted by compile, run, and test, may be given more than once, and takes an optional :selector restricting the report to matching functions.

sh
ashes compile app.ash --explain ownership
ashes run     app.ash --explain rc --explain reuse
ashes compile app.ash --explain traits
ashes compile app.ash --explain authority
ashes compile app.ash --explain concurrency
ashes compile app.ash --explain memory:Map.set
KindReports
ownershipThe inferred contracts: per-parameter borrowed or consumed, move safety, uniqueness, which parameters the result may alias, whether the result is fresh, and captured values.
rcThe Perceus operations in the final semantic IR — dups, drops, uniqueness checks, allocations, reused allocations, reuse tokens, and copies. Functions with no such operations are omitted.
reuseIn-place-reuse decisions: whether a specialization was generated, the candidate, the outcome, and a stable reason code with the source location of the site.
traitsHidden immutable dictionary parameters and the concrete implementation selected for each resolved trait requirement.
authorityThe inferred ambient-authority row of each public binding and the declared row of each external function.
concurrencyStructured scopes, forks, joins, and explicitly detached spawns in the final task IR.
memoryOwnership, RC, reuse, trait evidence, and physical representation correlated per source function.

The selector matches a function's source name, qualified name, generated label, or the source function a generated function came from, case-insensitively and by substring — so --explain rc:loop also covers the reuse specializations, droppers, and coroutines generated for loop, and you never need a generated symbol name. A selector matching nothing prints (no functions matched) and succeeds.

Output goes to stderr, so a program's own stdout is unaffected:

sh
ashes run app.ash --explain rc > output.txt    # output.txt holds only the program's output

An unknown kind is a usage error listing the valid values, with exit code 2. Nothing is printed when compilation fails before the report data exists; ordinary diagnostics remain primary.

Which one to reach for ​

The three facilities overlap deliberately, and it is worth knowing how:

You want to knowUse
How much reference counting a function does--explain rc
Which trait implementation was selected--explain traits
Which ambient authority public functions and externals require--explain authority
Which tasks use structured versus detached concurrency--explain concurrency
Which operations, on which values, and where--emit-ir final
What the optimizer changed--emit-ir lowered and --emit-ir final, diffed

--explain rc counts exactly the instructions --emit-ir final prints — every category is one instruction type, so the numbers agree by construction. It is the summary view, not separate information, and it earns its place by naming concepts rather than opcodes (you should not need to know that reuse tokens are DropReuse) and by staying readable across a program with dozens of functions.

--emit-ir lowered is the only one showing something the others cannot. The optimizer removes a large share of the operations lowering emits — often most of them — so the lowered stage answers a different question from either of the other two.

IR Dumps ​

--emit-ir prints the semantic IR itself, rather than a report about it. It takes lowered (as lowering emitted it) or final (what code generation receives), may be given more than once, and accepts the same :selector as --explain.

sh
ashes compile app.ash --emit-ir final
ashes compile app.ash --emit-ir lowered:loop --emit-ir final:loop   # diff the two stages
IR (final)
==========

trait evidence
  dictionary-parameter function=show source=app.ash:42 index=0 trait=Ashes.Trait.Show methods=[show] supertraits=[]
  resolved requirement=Ashes.Trait.Show(Int) implementation=Ashes.Trait (<std:Ashes.Trait>:5237)

function lambda_55  [SourceFunction from build]
  locals=2 temps=4
    LoadLocal             Target=0 Slot=1   (app.ash:3:34)
    TextFromInt           Target=1 ValueTemp=0   (app.ash:3:15)
    ConcatStr             Target=3 Left=1 Right=2 RuntimeManaged=true   (app.ash:3:15)
    Return                Source=3   (app.ash:3:1)

Operands left at their unset value — false, null, or -1 for an optional slot or temp — are omitted, so what is printed is what carries meaning. Instructions carry no ordinal: requesting both stages and diffing them is the main reason to read a dump, and an absolute index would make every line after an inserted or removed instruction register as changed. Labels anchor position where position matters.

When traits are present, the leading trait evidence block identifies every hidden dictionary parameter in ABI order and every concrete implementation selected during resolution. Paths and source offsets are stable source identities; no runtime pointer or process-specific address is printed.

Unlike the explain reports, the dump's shape is not a contract. Its content is the instruction set, so it changes whenever an instruction does; treat it as a debugging artifact rather than something to snapshot. Like the reports, it goes to stderr and cannot change generated code.

What the reports are not ​

These are static reports of compile-time decisions. They do not show how often anything executed, allocate counters, or profile a run — runtime profiling is a separate concern, and so are heap visualization and report viewers. Nor do they explain LLVM's own optimizations: everything reported here is an Ashes-level decision made before code generation.

rc describes the Ashes IR handed to the backend, not optimized LLVM IR. It observes after the Ashes-level optimizer in the default ashes test --pipeline optimized mode, matching the code that ships. With --pipeline lowered, it describes the raw lowered IR instead. --pipeline both emits a report for each pipeline.

Requesting a report cannot change generated code: the same source compiles to identical bytes with and without --explain.


Debug Mode ​

The --debug (or -g) flag instructs the compiler to embed debug information in the output binary for source-level debugging. The exact debug format is target-dependent.

Behaviour ​

AspectEffect
Debug metadataDebug information is emitted into the output binary; the exact sections and format depend on the target platform.
OptimizationWithout an explicit -O flag, optimization defaults to -O0 for the most faithful single-stepping. An explicit -O1/-O2/-O3 is honored (no cap), so a profiled debug build matches the optimized binary's inlining. The DWARF stays valid at every level — the backend re-verifies the module after optimization when debug info is requested.
Compile summaryAn extra Debug: yes line is printed in the ashes compile success output.
Target triple--debug does not change the Windows LLVM target triple; the current implementation continues to target x86_64-pc-windows-msvc.

Interaction with Optimization Levels ​

User flagsEffective optimization
--debug (no -O)-O0
--debug -O0-O0
--debug -O1-O1
--debug -O2-O2
--debug -O3-O3
-O3 (no --debug)-O3 (unchanged)

Example ​

bash
# Compile with debug info (defaults to -O0)
ashes compile --debug examples/hello.ash -o hello

# Compile with debug info and explicit -O1
ashes compile -g -O1 examples/hello.ash

# Run under debugger
ashes run --debug examples/hello.ash

Compatibility Rules ​

Breaking Changes ​

The following changes would break existing users or tooling and must be treated as breaking:

  • Removing a command or flag.
  • Changing the meaning of an existing flag.
  • Changing exit codes for existing conditions.
  • Changing the format of the success output line for ashes compile.

Non-Breaking Changes ​

The following changes are considered non-breaking:

  • Adding new commands or flags.
  • Adding new --target values.
  • Adding new fields to ashes.json (with defaults).
  • Changing diagnostic or error message wording (not the exit code).
  • Changing the visual styling of terminal output (colours, table borders).