Skip to content

Contributing

enumeratio edited this page Sep 30, 2026 · 11 revisions

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.

Getting started

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 check or the tests: type-aware lint and the tests resolve sibling packages through their dist/. If a sibling's dist is missing on the first pass, run the build again. Keep both filters; a bare pnpm -r run build also builds the site, which takes more than 2 GB.
  • The site is VitePress in web/: pnpm --filter @enumeratio/web run build, or vitepress dev for the dev server.
  • If setup or package-manager behaviour looks wrong, vp env doctor says why.

Names

  • enumeratio is the mathematics: the symbol definitions (collections, statistics and maps, the special functions, the algebras) and their evaluation, built on @cortex-js/compute-engine and 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 *Form heads). See Syntax and Formats.
  • In the reference data, an example's retypeable text form is keyed epsil, and notatio keys 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).

Where a change goes

  • 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.md is the landing page, and docs/**/*.md are its further pages (order in front matter; a guide is one of these). The site serves them under /docs/<package>/; nothing is copied into web/. 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 in packages/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 stable id (lowercase words joined by -), expr and expected as MathJSON, and each system's hand classification. An example whose value is known from outside our evaluation also carries known, its source (DLMF 25.6.1, OEIS A000110…) and optionally a tolerance. Never "fix" a failing known check by rewriting expected.
    • 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, run node packages/reference/scripts/format-records.ts. After adding or changing an example (or a printer or transpiler), run UPDATE_FORMS=1 node packages/frontend/scripts/collect-forms.ts; the forms test says when that's needed. Add a head by adding its folder.

Tests

  • Examples are the snapshots. A head's expected values are its reference examples (expected, known, and the generated examples.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 a role: test example.
  • Golden JSON only for output that isn't a head's value (CLI demos, rendered forms, visuals): expect(actual).toEqual(golden[id]), regenerated behind an UPDATE_* flag. No toMatchSnapshot in packages/: 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.

Code

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 like 0. 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 real on 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.

Git

  • Work on a branch or worktree, never directly on main. Land through a pull request: main requires its status checks, so a direct push skips them. Never force-push main.
  • 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 on main breaks 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-By or 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.

CI and deployment

  • The gate (ci.yml, every push and PR): build the library packages, then vp check, the per-package tests and the site build.
  • Production (enumeratio.dev) ships from GitHub Pages on merge to main; 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-fixup issues.

The workflows, secrets and artifacts are on CI and Deployment.

Home

Project

Design: every design page, settled, speculative and archived

Clone this wiki locally