Repository navigation
Contributing
How to work on enumeratio: the toolchain, the names, where things go, and what a change has to pass before it lands. For parallel work across several sessions, see Lanes; for what to work on, see Roadmap.
The repo is a pnpm monorepo on Vite+, whose CLI is vp.
vp <name> runs a built-in command; vp run <name> runs a package.json script or a
vite.config.ts task.
vp install
node tools/ci/dist-cache.ts build
vp check
vp test- Build the library packages before
vp checkor the tests: type-aware lint and the tests resolve sibling packages through theirdist/.tools/ci/dist-cache.ts build [pkgs]builds them in dependency order and restores any package whose inputs haven't changed from a cache shared by every worktree, so a fresh worktree is ready in seconds. Within a package, the generated-definition steps skip themselves when their inputs are unchanged.BUILD_JOBScaps how many run at once. - Data derived from our own code (provenance, defined names, the crosswalk, ce-patches'
patch list) is built, not committed, so a merge never leaves a file to regenerate. Only an
external snapshot (a kernel's answers, a scrape, an expensive sweep) is committed, by its own
collect-*/fetch-*script. A newsrc/patches/<slug>.tsince-patchesregisters itself. - Don't run a bare
pnpm -r run build: it also builds the site, which takes more than 2 GB. - The site is VitePress in
web/:pnpm --filter @enumeratio/web run build, orvitepress devfor the dev server. - If setup or package-manager behaviour looks wrong,
vp env doctorsays why.
-
enumeratio is the mathematics: the symbol definitions (collections, statistics and
maps, the special functions, the algebras) and their evaluation, built on
@cortex-js/compute-engineand written in its language, Epsil. -
notatio is the interface: the notebook, the
<notatio-*>elements, the plots and glyphs, the CLI and REPL, the format registry, the docs site, and the vdom (the component form of an expression). notatio is not a syntax: the text is Epsil. - The line runs by what the word governs. enumeratio owns meaning ("a head", "defined
as", "evaluates to"); notatio owns writing and showing ("written as", "prints as",
InputForm, the
*Formheads). See Syntax and Formats. - In the reference data, an example's retypeable text form is keyed
epsil, andnotatiokeys its component serialisation, the vdom as Vue/React markup (Examples as Data §2). - Symbol names are spelled out, not abbreviated. Package names haven't all caught up; don't rename them in passing (see Component Naming for how renames wait).
-
Design writing goes on this wiki, not in the repo: settled design under Design, open design under Speculative design.
-
User docs ship with their package: its
README.mdis the landing page, anddocs/**/*.mdare its further pages (orderin front matter; a guide is one of these). The site serves them under/docs/<package>/; nothing is copied intoweb/. Link another package's docs by relative file path and the site absolutely (https://enumeratio.dev/…), so a page reads the same on GitHub and on the site. -
Upstream candidates. A head or fix compute-engine would plausibly take (Wolfram or mpmath has it, or it fixes or widens a native head) is written as a patch in
packages/ce-patches/, not in a symbol package, even before anything is proposed. Its code is laid out as in compute-engine; its reference entries stay inpackages/reference/entries/and never go upstream. See its README. -
Reference entries. Each head's record is a folder,
reference/<Head>/, in the package that declares it (packages/reference/entries/for compute-engine's own heads):-
index.md: front matter (summary, signatures, bindings, references…), with the details as a markdown list below it. -
examples.tsv: one hand-written row per example, in page order. Each row has a stableid(lowercase words joined by-),exprandexpectedas MathJSON, and each system's hand classification. An example whose value is known from outside our evaluation also carriesknown, itssource(DLMF 25.6.1,OEIS A000110…) and optionally atolerance. Never "fix" a failing known check by rewritingexpected. -
examples.values.<system>.tsv: generated, one per kernel system: itsinand its answers. Our own printed forms are built, not committed;examples.tsvpins a few as<form>.in/<form>.outcolumns, plus every<form>.back/.backOut. See Examples as Data.
Read and write records through
@enumeratio/entry/node, never by hand-parsing their files. After editing, runnode packages/reference/scripts/format-records.ts. After adding or changing an example (or a printer or transpiler), runUPDATE_FORMS=1 node packages/frontend/scripts/collect-forms.ts; the forms test says when that's needed. After a merge conflict inexamples.tsv, take one side, runnode packages/frontend/scripts/pin-forms.ts, thencollect-forms.ts. Don't name a variableeoriin an example: compute-engine lifts them to constants. Add a head by adding its folder. -
-
Examples are the snapshots. A head's expected values are its reference examples
(
expected,known, and the generatedexamples.values.<system>.tsv), and a test of what a head evaluates to asserts against them, not a golden file of its own. A case too many to show on the page is arole: testexample. -
Golden JSON only for output that isn't a head's value (CLI demos, rendered forms,
visuals):
expect(actual).toEqual(golden[id]), regenerated behind anUPDATE_*flag. NotoMatchSnapshotinpackages/: a guard test forbids it. -
Cross-check the routes. Where a fix can differ by route, the same inputs go through
evaluate(),.N(), the parse route and the compiled code, and agree. -
Patches in
packages/ce-patches/test the way their upstream does, snapshots included, so a test can go upstream with its code. -
Long sweeps run nightly. A test that samples or enumerates takes a small budget by
default and its full one under
DEEP_TESTS=1. Keep the important cases in the standard run as fixed examples, not left to the sample. Don't just raise a test timeout.
Much of this is what compute-engine's maintainer adds to the patches we send (see below); we hold our own code to it too.
-
Every route in the same change. A fix to what a head evaluates to also fixes
.N(), simplification and each compile target (JavaScript, WGSL). A fact that must hold on several routes lives in one helper they all call, not a copy per route. -
Numerics: decline rather than answer wrong. A kernel that can't vouch for its digits leaves the expression unevaluated: not
NaN(a floating-point result with no value) and not a sentinel like0. Before sending one, check:- convergence needs several consecutive small, decreasing terms and a tail bound, not one small term;
- every loop has a cap (shifts, recurrences, retries), and past it the kernel declines;
- error targets are relative to the result, and a result is returned only with the digits asked for;
- guard radii and accuracy claims are measured on a grid against an oracle (mpmath), and stated as measured;
- a float operand gives a float result;
- the type handler never claims
realon a branch cut or at a pole; - past the double range a value is carried scaled, or stays symbolic.
-
Comments state the current rule in the present tense, short and next to the code:
- cite the formula (DLMF 25.11.1);
- give the case that breaks without the code;
- a guard threshold is a named constant, commented with the case that needs it.
No history ("this used to…") and no bare references to a design section.
What we ask compute-engine for is its issues
(ours); what we carry
until it ships is packages/ce-patches/ (its README has the patch lifecycle).
-
The issue and its PR together, the issue with a repro on compute-engine's
main: the maintainer asks for a ready PR to come with the issue. Post the issue alone first only when the work may be rejected or needs his design call. Posted asenumeratio, and every draft is signed off in-house before it goes up. Batch related asks: compute-engine's maintainer prefers fewer issues. -
The mathematics stays out of compute-engine's types. A pure-TS kernel that doesn't import
compute-engine, with
declare.tsthe only file that does, is a port of one adapter file. -
Use the Wolfram spelling when one exists.
PowerModListreplaced a bespokeSplitUnitshead for exactly this reason, and the result was more flexible as well as more portable. - Never invent a head where widening an argument slot would do. Tier 2 is the model.
- Every candidate ships with an independent oracle. Not a regression test — a second computation of the same number by different means: Dedekind sums against reciprocity, Burau against the closed-form torus polynomial, Ostrowski against Zeckendorf. That is what makes a contribution reviewable by someone who did not write it.
Before merging a PR we send, compute-engine's maintainer takes it as the base and pushes a review commit onto it. What those commits add is the checklist below; a PR that already does it lands as sent. Our own code follows the same rules ("Code", above).
-
Where code goes. Keep the files the fix touches; don't reorganise. A fact that must hold under
evaluate(),.N()andsimplify()lives inboxed-expression/, and all three import it. -
Every lane in the same PR. The compiled JavaScript target, and the GPU targets where they apply, get the matching fix. Test that the compiled lane agrees with the interpreter.
-
Tests.
- Jest
describe/test, table-driven with a plainforover a typed cases array, and test names that carry the expected value. - Each case goes through
evaluate(),.N(),.simplify(), the parse route and a compound argument that reduces to it. - A single-issue fix gets its own file,
test/compute-engine/item-<issue>-<topic>.test.ts. - Explicit assertions, not snapshots, for anything with a semantic answer.
- Jest
-
Numerics. Apply the checklist in "Code", above. Beyond it, the review passes also ask for:
- a result that can't be represented stays symbolic;
- the engine's caches (the Bernoulli table, say) are reused rather than recomputed;
- the residual limits are recorded in compute-engine's
ROADMAP.md.
-
Comments follow compute-engine's
docs/COMMENTING-GUIDELINES.md: the current rule in the present tense, a formula reference (DLMF), the case that motivates the code, named guard constants. No history, no bare references to design sections. -
CHANGELOG, in the same commit as the fix, under
## [Unreleased]→### Issues Resolved:- a bold one-line claim about the resulting behaviour;
- then concrete before-and-after values;
- credit as
(#N, contributed by [enumeratio](https://github.com/enumeratio)).
After a rebase, check the entry is still under Unreleased: git slides it under a just-released version heading.
-
Commit messages. One long subject in clauses joined by
;, endingFix #N(closes the issue) orPart of #N. The rationale goes in comments and the CHANGELOG, not the body. -
CI. compute-engine's
mainis often red on its own: prettier drift inlint,corpus-pipeline, and single failing tests. Before fixing a red check on our PR, compare it with the same job onmain; say nothing about checks that fail there too. -
Allow edits by maintainers, so the review commit can land on our branch.
- Work on a branch or worktree, never directly on
main. Land through a pull request:mainrequires its status checks, so a direct push skips them. Never force-pushmain. -
Never commit conflict markers. After resolving any merge, and before committing:
git grep -nE '^(<{7}|>{7})' $(git rev-parse --show-toplevel)must be empty. A marked file landing onmainbreaks the dev server and every checkout. - Commits and PRs are authored as the project (
enumeratio), not as a person. -
No attribution markers in commits, PR descriptions or comments: no
Co-Authored-Byor other trailers, no "generated with" line, no session links, no footers. This holds for any tool or agent that writes on the project's behalf. When merging a squash, give the message explicitly, since the default adds co-author trailers.
-
The gate (
ci.yml, every push and PR): build the library packages, thenvp check, the per-package tests and the site build. -
Production (
enumeratio.dev) ships from Cloudflare Pages on merge tomain; every build also gets a Cloudflare Pages preview at<sha7>.enumeratio.pages.dev. -
Advisory sweeps (Plausible, the nightly and weekly oracle rescans) never block a merge;
their failures file rolling
nightly-fixupissues.
The workflows, secrets and artifacts are on CI and Deployment.