Imported from zksecurity/sagemath-ts (
AGENTS.md). Install upstream withnpx skills add zksecurity/sagemath-ts. Copyright stays with the author.
Agent Guidelines
This document provides instructions for AI agents working on sagemath-ts.
Required Reading
Before working on this codebase, read these documents:
- DESIGN.md - Design decisions for the SageMath to TypeScript port (type mappings, function signatures, dependency architecture)
- DEVIATIONS.md - Documented differences from SageMath behavior
- SCOPE.md - Module implementation status and assignments
Project Overview
We are porting SageMath to TypeScript with a focus on number theory and cryptography. The goal is exact behavioral equivalence with SageMath for deterministic functions.
Key Principles
1. Mirror SageMath Structure Exactly
- File paths must match:
sage/rings/integer.py->src/rings/integer.ts - Function names must match: Use the exact same names as SageMath
- Module hierarchy must match: Preserve the import structure
2. Reference the Source
Before implementing anything:
- Read the SageMath source in
reference/sage/src/sage/ - Read relevant dependency source (PARI/GP, FLINT, NTL) in
reference/ - Understand the algorithm completely before writing TypeScript
- Check for tests in the reference code and replicate them
3. TypeScript Style
See DESIGN.md for complete details on type mappings and conventions. Key points:
// Accept IntegerLike, return bigint
import { IntegerLike, toBigInt } from '../types/coercion.js';
function gcd(a: IntegerLike, b: IntegerLike): bigint {
const _a = toBigInt(a);
const _b = toBigInt(b);
// ... implementation
}
// Use options objects for keyword arguments (illustrative — the real `factor` takes
// no options; see `arith/misc.ts`. Do not treat this shape as an existing signature.)
function some_function(n: IntegerLike, options?: { algorithm?: 'pari' | 'flint' }): Result { ... }
// Preserve SageMath's error messages
throw new ValueError("n must be positive");
4. Property Testing Requirements
Every implemented function MUST have a property test. Tests are organized by area, not by source file — one Python file and one TypeScript file per area, with a shared case list:
tests/property/
cases/arith.cases.json # The shared case list for the area
python/areas/arith.py # Python/SageMath side
typescript/areas/arith.ts # TypeScript side
Both sides must use identical random seeds and emit results in identical format. Adding a
new area means adding all three files; bun tests/run-test-tier.ts property fast --list
shows the areas currently registered.
Write concrete inputs as rows ([seed, arg1, ...]), not fixedValue(...) generators,
and run bun tests/property/normalize-cases.ts before committing case files: the
tests/property/case-format.test.ts unit test rejects any case file that is not in the
canonical compact form (see tests/property/README.md).
New property tests must generate inputs from fresh seeds and compare both runtimes live. Print the replay seed; preserve the seed/generator version or a small minimized input when a bug is found. Do not commit generated expected-output snapshots or passing transcripts. A comparison run requires its original runtime; missing native tools must fail instead of silently skipping or falling back to stale output.
Bulk legacy sweeps have been retired with explicit user authorization. Use compact
recipes for constrained inputs, including fixed constructor shapes where required,
and fresh generated payloads. Preserve a small named regression when fixing a bug.
Do not commit generated input corpora, positional selectors into old corpora or
expected-output snapshots. Native suite IDs in native-suites.json name generators
and live reference calls; they are not paths to stored output files.
Unit tests are colocated with their source: packages/*/src/**/*.test.ts.
5. Document Deviations (MANDATORY)
When your implementation differs from SageMath, you MUST document it appropriately:
| Type of Difference | Document In | Purpose |
|---|---|---|
| Behavioral differences (outputs differ from SageMath) | DEVIATIONS.md |
Track when results are different |
| Architectural decisions (type patterns, conventions) | DESIGN.md |
Explain how we map concepts |
DEVIATIONS.md entries require:
- What SageMath does vs what we do
- Rationale - Why we made this choice
- Trade-offs - What we lose
- Behavioral impact - Does it affect outputs?
Rules:
DEVIATIONS.mdat the project root is the single source of truth- Same-change requirement: Code changes that introduce deviations must update
DEVIATIONS.mdin the same commit - Add
@see Deviation:in affected docstrings
5b. Keep LLM.md True (MANDATORY)
LLM.md is the public API quick reference — it is what downstream agents and vendored
consumers read instead of the source. If you change an exported signature, a method name,
an import path, or the package exports map, update LLM.md in the same commit.
Its examples are executed by tests/llm-doc.test.ts. Never fix a failure there by
loosening the assertion: either the doc is stale (fix the doc) or the change was
unintentionally breaking (fix the code). Only document behavior you have actually run.
6. Scope Tracking (MANDATORY)
Update SCOPE.md after completing any work.
| When | Action |
|---|---|
| Starting work | Mark as 🟡 with your identifier |
| Completing work | Mark as ✅ with test coverage |
| Blocked | Mark as 🔴 with reason |
7. Stub Unimplemented Functions
Stub ALL functions first with NotImplementedError:
export function unimplemented(n: IntegerLike): bigint {
throw new NotImplementedError('SAGE_NOT_IMPLEMENTED: unimplemented');
}
Find unimplemented functions: grep -r "SAGE_NOT_IMPLEMENTED" packages/
Architecture Fidelity
When SageMath delegates to an external library, we MUST also delegate to our port of that library.
See DESIGN.md for the complete dependency mapping. Before implementing, check if SageMath calls:
__pari__(),pari(...)-> Useparigp-tsflint_...,fmpz_...-> Useflint-tsntl_...-> Usentl-ts
Current Focus: Number Theory for Cryptography
Priority modules (implement in this order):
sage.rings.integer- Arbitrary precision integerssage.rings.finite_rings- Finite fields (GF(p), GF(p^n))sage.arith- Basic number theory (gcd, lcm, factor, primality)sage.rings.polynomial- Polynomial arithmeticsage.groups.generic- Generic group operationssage.schemes.elliptic_curves- Elliptic curve operations
Workflow for New Module
- Check SCOPE.md - Ensure the module isn't already assigned/complete
- Update SCOPE.md - Mark as 🟡 in progress with your identifier
- Study the SageMath source - Read
reference/sage/src/sage/<path> - Create mirrored file structure in
packages/sagemath-ts/src/ - Implement with tests - Write property tests alongside implementation
- Run transcript comparison -
bun run test:property - Update SCOPE.md - Mark as ✅ complete with test coverage
Algorithm Fidelity (CRITICAL)
NEVER write naive O(n) implementations when SageMath uses O(√n) or O(log n) algorithms.
Before implementing ANY function:
- Read the SageMath source to understand the algorithm used
- Check the complexity - if SageMath uses BSGS, Pohlig-Hellman, factorization-based methods, etc., we must too
- Check for delegation - if SageMath calls PARI/FLINT/NTL, we delegate to our ports
Common Algorithm Patterns to Watch For
| Operation | WRONG (naive) | RIGHT (SageMath's approach) |
|---|---|---|
| Element order in group | O(n) repeated multiplication | O(√n) BSGS via order_from_bounds |
| Discrete logarithm | O(n) brute force | Pohlig-Hellman + BSGS |
| Verify exact order | Just check n*P = O |
Also check (n/p)*P ≠ O for all prime divisors |
| Point order on E/F_q | Compute in TypeScript | Delegate to PARI's ellorder |
| Curve cardinality | Naive point counting | Delegate to PARI's ellcard (Schoof-Elkies-Atkin) |
| Factorization | Trial division only | Delegate to PARI's Z_factor |
Red Flags in Code
If you see any of these patterns, STOP and check SageMath:
// 🚫 BAD: O(n) loop for order computation
while (!current.is_zero()) {
current = current.add(this);
n++;
}
// 🚫 BAD: Simple divisibility check for has_order
has_order(n) { return this.mul(n).is_zero(); }
// 🚫 BAD: Brute force enumeration
for (let i = 0n; i < groupOrder; i++) { ... }
Delegation Architecture
SageMath Our Port
──────── ────────
sage.groups.generic → src/groups/generic.ts (BSGS, Pohlig-Hellman)
cypari2.ellorder() → parigp-ts/ellorder()
cypari2.ellcard() → parigp-ts/ellcard()
cypari2.factor() → parigp-ts/Z_factor()
When implementing a function that SageMath delegates:
- First check if the dependency function exists in our port (parigp-ts, flint-ts, etc.)
- If not, implement it there first
- Then have sagemath-ts delegate to it
Avoiding Common Mistakes
- Don't guess algorithms - Always verify against SageMath source
- Don't write naive implementations - Check SageMath's algorithm complexity first
- Don't skip edge cases - SageMath handles many edge cases; we must too
- Don't change function signatures - Even if TypeScript conventions differ
- Don't use floating point - Use BigInt and rational arithmetic
- Don't implement without tests - Property tests are mandatory
- Don't restrict input types - Use
IntegerLikenot justbigint(see DESIGN.md) - Don't implement what PARI/FLINT provides - Delegate to our ports instead
Dependency Libraries
| SageMath uses | We implement in |
|---|---|
| PARI/GP (via cypari2) | packages/parigp-ts/ |
| FLINT | packages/flint-ts/ |
| NTL | packages/ntl-ts/ |
| GMP | Native BigInt + packages/gmp-ts/ if needed |
Testing Commands
Use the bounded-output wrapper by default for test runs, builds, typechecks and other potentially verbose commands. It preserves exit codes and stores full output in an OS temporary log while returning a small summary. See scripts/quiet.md.
# Run all tests
bun --silent run quiet -- bun test
# Run property tests with transcript comparison
bun --silent run quiet -- bun run test:property
# Run tests for specific module
bun --silent run quiet -- bun test --filter "rings/integer"
# Generate coverage report
bun --silent run quiet -- bun test --coverage
# Inspect a failure without dumping the full log
bun --silent run quiet show /path/printed/by/the/run/output.log --match 'error' --lines 12
Do not cat generated data or full test logs into tool output. Inspect file sizes,
JSON keys/counts, or targeted bounded excerpts first. A line limit alone does not
protect context from huge single-line integers or JSON. For broad Git changes use
--stat/--name-only; use git commit --quiet or the wrapper for commits. Do not
rerun a long command just to see more output: inspect the saved log. Keep tool-call
output limits modest as an additional safeguard. The summary is heuristic; inspect
the relevant log records before diagnosing a failure.
Versioning and Changelog
- Keep versions in sync across
package.jsonand allpackages/*/package.json. - Semantic versioning: patch for fixes/docs/audits, minor for new features, major for breaking changes.
- Update
CHANGELOG.mdfor every change (code, tests, or docs) with date and concise bullets. - Version bump and changelog update must be in the same commit as the changes.
Git Commits
- Always commit your work when done
- Use one-line commit messages
- Do not add co-author attribution
Questions?
If unclear about implementation details:
- Check SageMath documentation: https://doc.sagemath.org/
- Check the source in
reference/sage/ - Run the operation in SageMath to observe behavior
- Document any ambiguities in the code comments
