@6digit/mnemonics
v0.5.0
Published
Reader-shaped evaluator for named expressions over an environment. Brutal, async-capable, programmable resolver chain.
Maintainers
Readme
@6digit/mnemonics
A mnemonic is a name bound to an expression. The expression is evaluated under a provided environment. The environment carries everything the expression needs — entity data, ambient state, other mnemonics, native helpers. The mnemonic itself stays a small pure thing that names a meaning.
That shape is the Reader monad: Reader<Env, Value>. The shape is what
matters; the term is incidental.
What it gives you
A substrate for named, evaluatable concepts that compose. Same primitive covers:
- predicates (
is_visible,should_evolve) - numeric formulas (
damage,effective_speed) - state transformations (
apply_blessing) - narrative pieces (
act_one_summary) - deferred references that other code can run later
The kernel returns whatever the expression produces — booleans stay booleans, objects stay objects. No coercion.
Install
npm install @6digit/mnemonics
# or
bun add @6digit/mnemonicsPeer requirement: subscript ^10.
Quick start
import { MnemonicEvaluator } from '@6digit/mnemonics';
const source = {
get(name: string) {
return {
damage: { name: 'damage', expression: 'base * power' },
base: { name: 'base', expression: '10' },
}[name];
},
};
const evaluator = new MnemonicEvaluator({ source });
const result = await evaluator.evaluate('damage', { power: 3 });
// result.value === 30
// result.contextRead → ['power'] (env keys actually read)
// result.mnemonicDeps → ['base'] (sub-mnemonics that resolved)What's in the box
The package ships in layers. Each layer has its own invariants and dense test bed; layers are decoupled — you can use the kernel without the vocabulary layer, the vocabulary layer without async resolvers, and so on.
- L0 — Kernel. Recursive evaluator over
subscriptAST. Reader-shaped: every recursion derives a fresh env, never mutates. Cycle detection, max-depth protection. Read-set tracked transitively via Proxy. Errors carry the full chain. - L1 — Vocabulary. A mnemonic is
{name, summary?, description?, expression, type?, returnType?}. Sources canlist()cheap previews (titles + summaries) without paying the full-read cost. - L2 — Programmable resolver. Name resolution is a composable chain
of
(name, ctx) => Value | Promise<Value>stages. Custom resolvers prepend; defaults handle env lookup and mnemonic recursion. The chain is the only way a name becomes a value. - L3 —
with(...)shadowing. The only parameter-passing mechanism.with(prompt: "hi", body)evaluatesbodyunder the env extended withprompt = "hi", then discards the extension. Mnemonics don't take arguments — you shadow what they read. - L4 — Async resolvers. Resolvers may return promises. Independent name resolutions run in parallel. Async errors propagate with the full chain.
- L5 —
ref()quotation. A first-class deferred-reference primitive.ref("name")returns a value that names a mnemonic without running it;evaluator.run(reference, env?)evaluates it later, optionally under a different env. Decoupled from any mutation system.
See VISION.md for the full design and invariants.
Brutal invariants
- Fail loud. Cycles, missing refs, type mismatches, async errors — all structural, all noisy. No silent fallbacks.
- AST, not strings. Every name-extraction question is answered by walking subscript's AST. Property keys, method names, and call callees are distinguished structurally.
- No mutation. Caller env is never mutated. Recursive resolutions build fresh, scoped contexts.
- No coercion. Expression results pass through untouched.
- The chain is the truth. Every error names every link in the recursive resolution path.
Status
Pre-1.0 — the API may still shift before stabilization. 71 tests passing across kernel, analysis, and intent layers.
License
MIT — see LICENSE.
