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
pnpm -r --filter "./packages/**" --filter "./upstream/**" run 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/. If a sibling's dist is missing on the first pass, run the build again. Keep both filters; a barepnpm -r run buildalso 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
upstream/compute-engine/, 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 Upstreaming §10. -
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 system: our own forms and each oracle's answers.
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. 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
upstream/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 Upstreaming); 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.
- 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 GitHub 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.