dev-formata-io/stof/content
Claude Code and agent skill by dev-formata-io/stof
- community
Checking that the SKILL.md file is still in the repository.
Write, read, debug, and embed Stof (.stof) documents. Stof is a data runtime: a superset of JSON where documents carry their own functions, types, units, schemas, and tests, and run sandboxed anywhere (Rust, WebAssembly/TypeScript, Python). Use for any .stof file, Stof syntax or error question, converting JSON/YAML/TOML to Stof, self-validating configs (#[schema]), prototype types (#[type]), #[main]/#[test]/#[run] functions, the `stof` CLI, or embedding Stof with StofDoc (JS), Doc (Python), or Graph/Runtime (Rust). Also use when code shows `self.`, `#[type]`, `new X {}`, `schemafy`, `Lib::func`, `funcs(attributes = ...)`, or `stof test`.
Install
npx vskill@latest install dev-formata-io/stof/contentSKILL.md
Stof
Stof is a data runtime: a document is data plus the logic that belongs with it. Every JSON document is already valid Stof. On top of JSON, Stof adds functions, types, units (5km, 2GB, 30s), semantic versions, attributes, prototypes, schemas, imports, and tests. Documents run sandboxed: they can only see themselves and the libraries the host gives them.
This file is the language guide. Each standard library is documented in references/libs/<Lib>.md; read the one you need before using functions not shown here (list at the end).
Mental model
- A document is a graph of objects. Each object holds fields (named values) and functions.
- The top-level body of a file is the main root object.
root Name { ... }declares another root. Imports
can create roots (import './x.stof' as X;).
- Inside a function,
selfis the object the function lives on,superis its parent, androotis the
root above it.
- Functions are values on objects (
self.fn_name), not globals. Call them with a path:self.total(). - Fields are created by declaring them in the document or by assigning (
self.count = 1). Variables (let,
const) live only inside a function.
Rules that trip people up
Read these before writing Stof. Each one is a real mistake that produces wrong results or an error.
- Paths start at a root, a variable, `self`, `super`, or `root`. Inside a function,
config.portmeans
"the root named config". For a field, write self.config.port. The parser warns about bare names that can't resolve: warning: unknown name 'config' ... (use self.config for a field).
- Functions only see their own parameters and locals. A named function can't read its caller's variables;
pass values as arguments. Arrow functions ((x: int): int => x + offset) do see the variables of the function they're written in.
- Arithmetic with null is an error.
null * 3throws `cannot multiply null: the left value is null or
missing (use ?? to give a default). Give defaults with ??: (self.rate ?? 0) * 3. Initialize accumulators: let total = 0;. Comparisons with null don't throw (null < 10 is true`), so check for null first.
- `{ ... }` inside an expression is a map or a set, not an object.
{ b: 2 }is a map whose key is the
value of the variable b. Create objects with new: new { a: 1, nested: new { b: 2 } }. Inside new { ... } and in the document body, { ... } is an object.
- `return x;` needs the semicolon. The last expression of a block without a
;is its value:
fn area() -> float { self.w * self.h }.
- Number methods return a new value.
x.round(2)doesn't changex; writex = x.round(2). - Constructors run only with `new`. An object declared in the document with a type (
Rect unit: {...})
gets the type's fields and functions, but its #[constructor] doesn't run.
- Lists, maps, sets, and objects are shared on assignment.
let a = l;aliasesl; usecopy(l)for
an independent copy.
- `async { expr }` with a single expression is an async set literal. Use a statement:
async { return 1 + 1; }.
- Template strings are always `str`. `
${a}${b}` concatenates text even for numbers; null prints
null. \$ escapes a dollar sign.
- Catch values are error codes.
catch (e: str)receives'AssignConst',FuncDne("Num.split"), or the
value passed to throw(...). Match on the code; for the human-readable message (to log or show), name a second variable: catch (e, message) { err(message); }.
- Arguments are cast to the parameter type.
fn f(v: int)called with'5'receives5; an impossible
cast throws (CastVal(...)).
- `int / int` is integer division.
7 / 2 == 3. Use a float operand (7 / 2.0,(n as float) / 2) for
a fractional result. Wrap casts in parentheses inside larger expressions: (n as float) / 2, not n as float / 2 (a parse error).
- Tests run concurrently on one shared document. Processes switch at every loop iteration and Stof function call
(and await/sleep); code between those points runs without interruption. So tests that change the same fields (self.api.timeout = 2min in one, self.api.replicas = 0 in another) interleave and break each other. Give each test its own data (new { ... }, or fields only that test uses) and treat shared fields as read-only. Sharing is intended for tests that coordinate on purpose.
- Objects are never garbage collected.
new { ... }adds an object to the document (a child ofself, or of
the on parent) until something drops it; an unreferenced one is still in binary (BSTF) exports of its parent. Values (numbers, strings, lists, maps, sets, tuples) are freed automatically. For a temporary object, declare it with using and it is dropped when its block or function ends, on every exit (return, break, error): using event = new { id, amount };, or inline: self.handle(using new { id }). Use a map instead when you don't need an object. For many temporaries, put them on one scratch object and drop that: { using scratch = new {}; ... new { .. } on scratch ... }. Don't return a using object (it is dropped on return). stof test --leaks fails tests that leave objects behind.
Answers to common questions:
- A missing field reads as null (
self.nopeis null, no error); calling a missing function throws (FuncDne)
unless you use ?..
{ ... }inside a list in the document body is an object (items: [{ qty: 2 }]); JSON arrays of objects
parse into lists of objects (o.users[0].name).
intandfloatcompare by value (85.0 == 85);str(2.0)is'2'; casts withaswork between numbers,
strings, and units ('3.5' as float, 42 as str, 512MiB as GiB).
- Typed locals:
let total: float = 0;.conststops reassignment, not mutation (const l = []; l.push_back(1);
is fine).
- Destructuring:
const { name, age: years, role = 'member', address: { city } } = user;takes fields of an object
(or string keys of a map); const [first, _, third] = list; and const (key, value) = pair; take items of a list or tuple by position (_ skips one). A missing field or item is null, or its default; destructuring anything else (null included) throws. let declares them mutable.
new { ... }inside a function creates a child ofselfthat lives until dropped: declare temporaries with
using (see 15), or drop(o) them.
obj.fields()is a list of(name, value)tuples (no functions).- A function value keeps its own
self:handler(x)runs withself= the object the handler is defined on. pln(a, b)prints its arguments concatenated with no separator.
Fields and values
Types: null, bool, int, float, str, ver, blob, list, map, set, tuples ((int, str)), obj, fn, data, Promise<T>, unknown, unions (int | str), units, and custom types (Rect, Geometry.Point). typeof v gives the base type ('obj', 'float'); typename v gives the specific one ('Rect', 'cm').
Units: length km hm dcm m dm cm mm um nm mi yd ft in; time day hr min s ms us ns; temperature K C F; mass Gt Mt t kg g mg ug ng pg Ton lb oz; data bit byte KB MB GB TB PB … KiB MiB GiB TiB …; angles rad deg. Literals carry units (5km), math converts (1km + 500m == 1.5km, in the larger unit), as converts (1km as m), and v.to_units('GB') converts by name. A plain number has no units, so comparing one with a unit value compares bare numbers: Time::now() + 1hr is in hours, so give a time from JSON its units first (const at: ms = record.expires;) before comparing.
Literals: 'single', "double", ` template ${expr} , r#"raw"#, [list], {'map': 1}, {1, 2} (set), (1, 'two') (tuple), 1_000, 0xFF, 1e3, Infinity, -Infinity, NaN, 1.2.3` (version).
Functions
Library calls: name a library with ::, always: Num::abs(-3), Http::fetch(url), Obj::fields(o), Time::now(), App::notify(msg) (a host's library). Don't write Num.abs(-3): a . is a path, so it looks for a field or object named Num first and only falls back to the library (and changes meaning if the document ever has one). (-3).abs() calls the library as a method through the value's type; Lib?::func() returns null when the library or function doesn't exist (Ex. an optional host library). Standard library functions (pln, assert_eq, parse, copy, seal, ...) are called bare.
Null-safe access: self.a?.b, self.obj?.func(), ?self.maybe_missing() return null instead of throwing. a ?? b gives b only when a is null (false ?? 'x' is false).
Control flow and errors
References: let first = &l[0]; first = 0; writes into the list; swap(&a, &b) swaps variables.
Objects, types, and prototypes
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingcontent/SKILL.md
Popularity
- Repository stars
- 58
- Repository forks
- 4
- 7-day trend score
- 20
Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 19 Mar 2026, 7 months ago.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/dev-formata-io/stof/content)