dev-formata-io/stof/content

Claude Code and agent skill by dev-formata-io/stof

Trust tier 3
  • community
Source check pending

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/content

SKILL.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, self is the object the function lives on, super is its parent, and root is 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.

  1. Paths start at a root, a variable, `self`, `super`, or `root`. Inside a function, config.port means

"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).

  1. 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.

  1. Arithmetic with null is an error. null * 3 throws `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.

  1. `{ ... }` 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.

  1. `return x;` needs the semicolon. The last expression of a block without a ; is its value:

fn area() -> float { self.w * self.h }.

  1. Number methods return a new value. x.round(2) doesn't change x; write x = x.round(2).
  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.

  1. Lists, maps, sets, and objects are shared on assignment. let a = l; aliases l; use copy(l) for

an independent copy.

  1. `async { expr }` with a single expression is an async set literal. Use a statement:

async { return 1 + 1; }.

  1. Template strings are always `str`. ` ${a}${b} ` concatenates text even for numbers; null prints

null. \$ escapes a dollar sign.

  1. 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); }.

  1. Arguments are cast to the parameter type. fn f(v: int) called with '5' receives 5; an impossible

cast throws (CastVal(...)).

  1. `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).

  1. 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.

  1. Objects are never garbage collected. new { ... } adds an object to the document (a child of self, 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.nope is 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).

  • int and float compare by value (85.0 == 85); str(2.0) is '2'; casts with as work between numbers,

strings, and units ('3.5' as float, 42 as str, 512MiB as GiB).

  • Typed locals: let total: float = 0;. const stops 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 of self that 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 with self = the object the handler is defined on.
  • pln(a, b) prints its arguments concatenated with no separator.

Fields and values

stof
// JSON works as-is (quotes, commas, and semicolons are all optional in Stof)
"name": "Stof"
version: 1.2.0 // ver (semantic version)
str label: 'hi' // typed field: the value is cast to the type
const str fixed: 'locked' // const: assigning it throws 'AssignConst'
list! tags: [] // `!` = not null
cm height: 6ft + 2in // units convert: 187.96cm
MiB memory: 2GB
ms ttl: 5min // 300000ms
server: { // nested object
port: 8080
address: 'localhost'
fn url() -> str { `https://${self.address}:${self.port}` }
}
#[readonly] ro: 42 // attributes annotate fields and functions
#[private] secret: 'x' // only this object's functions can read it (still exported)
#[no-export] cache: {} // left out of JSON and other exports
root Settings { theme: 'dark' }

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

stof
fn add(a: float, b: float = 1) -> float { a + b } // default parameter
fn greet(name: str = 'world') -> str { return `Hello, ${name}!`; }
fn maybe(id: str, limit?: int) -> int { limit ?? 10 } // optional parameter (null when omitted)
fn fib(n: int) -> int { if (n <= 1) return n; this(n - 1) + this(n - 2) } // `this` = current function
stof
self.add(1, 2) // 3
self.add(b = 5, a = 1) // named arguments
const double = (x: int): int => x * 2; // arrow function (sees the variables where it's made, even when passed on)
const f = self.add; // functions are values
f(2, 3); f.call(2, 3);

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

stof
let total = 0;
for (const x in [1, 2, 3]) total += x * index; // `index` is the loop position
for (let i in 3) total += i; // 0, 1, 2
for (const pair in self.server.fields()) pln(pair[0], pair[1]);
for (const (key, value) in map) pln(key, value); // destructure each item (also `{ name } in users`)
for (let v in &list) v *= 10; // `&` iterates by reference (modifies the list)
while (cond) { if (done) break; continue; }
const kind = switch (i) { case 4: 'four' default: 'other' };
const sign = i > 0 ? '+' : '-';
^outer for (const a in 3) { for (const b in 3) { if (a * b == 2) break ^outer; } } // labeled loops
try { throw('bad input'); }
catch (e: str) { pln(e); } // typed catch; `catch { }` and `catch (e) { }` work too
try nanoid(0); catch (e, message) { err(message); } // e: 'FuncArgs', message: 'nanoid length must be ...'
try { throw(new { code: 42 }); } catch (e: obj) { pln(e.code); }

References: let first = &l[0]; first = 0; writes into the list; swap(&a, &b) swaps variables.

Objects, types, and prototypes

stof
#[type]
Shape: {
str name: 'shape'
fn area() -> float { 0 }
fn describe() -> str { `${self.name}: ${self.area()}` }
#[constructor]
fn created() { self.made = true; } // runs on `new`
}
#[type]
#[extends('Shape')]
Rect: {
str name: 'rect'
float w: 1
float h: 1
fn area() -> float { self.w * self.h }
#[static]
fn square(side: float) -> Rect { new Rect { w: side, h: side } }
}

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

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
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/dev-formata-io/stof/content/badge)](https://verified-skill.com/skills/dev-formata-io/stof/content)

Something wrong with this skill or its page? Tell us.