Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .claude/hooks/content-lint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# PostToolUse(Write|Edit): block an edit that ADDS a banned token to a .rs or
# .md file. Counts are compared against the committed version, so a file that
# already carries a banned token stays editable and only a net increase blocks.
set -u
f=$(jq -r '.tool_input.file_path // .tool_response.filePath // empty' 2>/dev/null) || exit 0
case "$f" in *.rs|*.md) ;; *) exit 0 ;; esac
[ -f "$f" ] || exit 0

block() { printf '{"decision":"block","reason":%s}\n' "$(jq -Rn --arg r "$1" '$r')"; exit 0; }

# $1 = rg flags, $2 = pattern, $3 = message
check() {
local now head root rel
now=$(rg -o $1 -- "$2" "$f" 2>/dev/null | wc -l | tr -d ' '); now=${now:-0}
[ "$now" -eq 0 ] && return 0
head=0
if root=$(git -C "$(dirname "$f")" rev-parse --show-toplevel 2>/dev/null); then
rel=${f#"$root"/}
head=$(git -C "$root" show "HEAD:$rel" 2>/dev/null | rg -o $1 -- "$2" 2>/dev/null | wc -l | tr -d ' ')
head=${head:-0}
fi
[ "$now" -gt "$head" ] && block "$3 File: $f (was $head, now $now)."
return 0
}

check -F $'\xe2\x80\x94' "This edit adds an em-dash. House style bans em-dashes: use an ASCII hyphen, a colon, or split the sentence."
exit 0
31 changes: 31 additions & 0 deletions .claude/hooks/nextest-on-stop.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Stop hook: run `cargo nextest run` for the workspace crates that have
# uncommitted .rs changes. Non-blocking; reports a pass/fail summary. No-op when
# nothing relevant changed or cargo/nextest are unavailable.
set -u
root=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
cd "$root" || exit 0
command -v cargo >/dev/null 2>&1 || exit 0
cargo nextest --version >/dev/null 2>&1 || exit 0

changed=$(git status --porcelain=v1 2>/dev/null | awk '{print $NF}' | rg '\.rs$' || true)
[ -z "$changed" ] && exit 0

meta=$(cargo metadata --no-deps --format-version 1 2>/dev/null) || exit 0
pkgs=$(printf '%s\n' "$changed" | while IFS= read -r f; do
[ -n "$f" ] || continue
printf '%s' "$meta" | jq -r --arg f "$root/$f" \
'.packages[] | (.manifest_path | rtrimstr("Cargo.toml")) as $d | select($f | startswith($d)) | .name'
done | sort -u)
[ -z "$pkgs" ] && exit 0

args=(); while IFS= read -r p; do [ -n "$p" ] && args+=(-p "$p"); done <<< "$pkgs"
list=$(printf '%s' "$pkgs" | tr '\n' ' ')
if out=$(cargo nextest run --no-tests=warn "${args[@]}" 2>&1); then
printf '{"systemMessage":%s,"suppressOutput":true}\n' "$(jq -Rn --arg m "nextest OK for touched crates: $list" '$m')"
else
fails=$(printf '%s' "$out" | rg -N 'FAIL|error\[|test result: FAILED|panicked' | tail -15)
printf '{"systemMessage":%s}\n' "$(jq -Rn --arg m "nextest FAILED for touched crates ($list):
$fails" '$m')"
fi
exit 0
10 changes: 10 additions & 0 deletions .claude/hooks/rustfmt-on-edit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
# PostToolUse(Write|Edit): format the edited Rust file with rustfmt (edition 2024).
# Fast per-file format; no-op for non-.rs paths or when rustfmt is unavailable.
set -u
f=$(jq -r '.tool_input.file_path // .tool_response.filePath // empty' 2>/dev/null) || exit 0
case "$f" in *.rs) ;; *) exit 0 ;; esac
[ -f "$f" ] || exit 0
command -v rustfmt >/dev/null 2>&1 || exit 0
rustfmt --edition 2024 "$f" 2>/dev/null || true
exit 0
32 changes: 32 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/rustfmt-on-edit.sh",
"timeout": 30
},
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/content-lint.sh",
"timeout": 15
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/nextest-on-stop.sh",
"timeout": 600
}
]
}
]
}
}
3 changes: 3 additions & 0 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[profile.default]
# Fail a hung test by name after 5 minutes instead of letting it eat the CI timeout.
slow-timeout = { period = "60s", terminate-after = 5 }
9 changes: 7 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,14 @@ Thumbs.db
# which is then caught by the rule above).
!.env.example

# Agent skills / AI tooling — installed locally, never committed.
# Agent skills / AI tooling. Shared repo config is tracked; anything personal
# or per-session is not.
.agents/
.claude/
.claude/*
!.claude/settings.json
!.claude/hooks/
.claude/settings.local.json
.claude/worktrees/
skills-lock.json

# Engine runtime state (default state_dir from engine.toml).
Expand Down
75 changes: 75 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
`CLAUDE.md` is a symlink to this file.

## What nexum-runtime is

nexum-runtime is a WASM Component Model host runtime for web3 modules, and it supervises guest components built against the `nexum:host` WIT world.
Each module gets a capability-gated view of the host: chain access over JSON-RPC, an allowlisted `wasi:http` outbound gate, a local key-value store, clocks, and structured logging.
The host enforces fuel, memory, and epoch limits per module.

The runtime is generic and venue-agnostic: it ships no venue and no domain payload, and `crates/nexum-cli` composes the core lattice and nothing else.
A downstream layer adds its own capabilities through the extension seam in `crates/nexum-world`.
A composition root declares `[extensions.<name>]` rows in an `extensions.toml`, and `synthesize` emits those rows after the core capability rows in the module's WIT world.

This repository is the leaf of the Nullis runtime stack and carries no cross-repo dependencies.
The videre and shepherd repositories build on the SDK and the runtime published here.

## Layout

- `crates/nexum-runtime` - the engine host: wasmtime embedding, supervisor, capability providers, and metrics.
- `crates/nexum-cli` - the bare `nexum` engine binary, composed over `nexum-launch` with the `CoreRuntime` preset.
- `crates/nexum-launch` - the generic launcher: CLI parsing, config loading, tracing setup, and the run loop for a preset.
- `crates/nexum-sdk` - the guest-side SDK that modules build against, host-neutral and domain-free.
- `crates/nexum-sdk-test` - in-memory host mocks and assertion helpers for module unit tests.
- `crates/nexum-module-macros` - the `#[module]` proc-macro, reached through `nexum_sdk::module`.
- `crates/nexum-tasks` - task lifecycle and graceful shutdown, and the only crate that spawns raw `tokio` tasks.
- `crates/nexum-world` - per-module WIT world synthesis, the core capability table, and the extension registry.
- `modules/example` - the minimal reference module, with balance-tracker, http-probe, and price-alert under `modules/examples/`.
- `modules/fixtures/` - adversarial fixtures: clock-reader, flaky-bomb, fuel-bomb, memory-bomb, panic-bomb, and slow-host.
- `tools/load-gen` - the load generator for soak runs.
- `wit/nexum-host` - the `nexum:host` WIT package.

## Build, test, lint

The workspace uses Rust edition 2024.
The flake pins the toolchain to Rust 1.94.0, which matches the toolchain CI installs.
Run `nix develop` to enter the dev shell, or run `direnv allow` once.
The dev shell supplies the toolchain, the `wasm32-wasip2` target, `cargo-nextest`, `just`, `ripgrep`, and `ast-grep`.

Use the justfile recipes:

```sh
just build # the engine plus every guest wasm
just test # host engine unit tests through nextest
just fmt # cargo fmt --all
just lint # cargo clippy --workspace --all-targets --all-features -- -D warnings
just ci # the full CI series: fmt, clippy, doc, wasms, nextest, doctests
```

Run tests with `cargo nextest run`, not `cargo test`.
nextest does not run doctests, so run `cargo test --doc --workspace --all-features` as well.
Run `just fmt` and `just lint` before each commit, because CI fails on any rustfmt or clippy warning.

The hooks in `.claude/hooks/` support this loop.
`rustfmt-on-edit.sh` formats each edited `.rs` file.
`nextest-on-stop.sh` runs nextest for the crates with uncommitted `.rs` changes at the end of a turn.
Each hook exits without work when `rustfmt`, `cargo`, or `cargo-nextest` is absent, so both stay silent outside the dev shell.

## House rules

Do not use em-dashes in source, rustdoc, markdown, commit messages, or PR and issue bodies.
Use an ASCII hyphen, use a colon, or write two sentences.
`.claude/hooks/content-lint.sh` blocks an edit that adds an em-dash to a `.rs` or `.md` file.

Write each commit message as a Conventional Commit with an imperative subject.
Disclose AI assistance with an honest `AI Assistance: <tool> used for <what>` line in the commit message and in the PR body.
Never add a `Co-Authored-By: Claude Code` footer or a `Generated with Claude Code` line.

Keep one logical line per paragraph in a PR or issue body.
GitHub renders a single newline in a comment as a line break, so a sentence-per-line body wraps too early.

## Documentation

Write all documentation in ASD-STE100 Simplified Technical English.
Use short sentences, the active voice, and one idea per sentence.
In a markdown file, put each sentence on its own line and do not wrap within a sentence.
GitHub reflows the file when it displays it, and the diff then shows one changed line per changed sentence.
1 change: 1 addition & 0 deletions CLAUDE.md
4 changes: 4 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@
wasm-tools
wabt
just
# Search tooling the agent workflow leans on: ripgrep for text,
# ast-grep for syntax-aware Rust queries and rewrites.
ripgrep
ast-grep
pkg-config
openssl
] ++ lib.optionals stdenv.isLinux [ mold ];
Expand Down
Loading