Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
1efbfec
tools: observer_gate_diff.sh normalises the exe-dir and the out-of-tr…
claude Sep 6, 2026
b6aff7e
embed: store obs_history_gap at the end of a closed isolated eval uni…
claude Sep 7, 2026
c3dd4bc
eigen: honour the report/report_value reservation in the meta-interpr…
claude Sep 7, 2026
24acd59
compiler: a fresh for binder is loop-scoped inside a function too (#1…
claude Sep 6, 2026
2eabdd5
observer: `unobserved:` keeps the value window complete (#1049)
claude Sep 7, 2026
f167c20
observer: configurable window depth + scale-free relative step (#1044…
claude Sep 7, 2026
983053c
observer: hoist the gate ahead of the observe helpers, interpreter an…
claude Sep 7, 2026
9e28b50
tests: bound the SIGUSR1 fixture by a sentinel, not by an iteration c…
claude Sep 7, 2026
ca7248a
replay: boundary refusal on a VM-less worker is a clean exit; replay_…
claude Sep 7, 2026
a6914d3
tasks: gated task_sched_trace — a pure-reader scheduler decision hist…
claude Sep 7, 2026
d02b7d4
tests: resolve the [99q] section-label collision from integration
claude Sep 7, 2026
636d03e
docs: derive the builtin counts from --api and gate them; tick ext_ne…
claude Sep 7, 2026
ada2381
rules: the pattern-kill ban graduates to bash_guard; keep only the re…
claude Sep 7, 2026
07e15a5
trace: the tape carries the observer configuration (#1044, #1045)
claude Sep 7, 2026
0b35229
repl: a failed multi-line unit no longer swallows the line that close…
claude Sep 7, 2026
f874578
tensor: accept buffers wherever a flat numeric list is accepted; zero…
claude Sep 7, 2026
49b33aa
observer gate: resolve literal imports at compile time; match names o…
claude Sep 7, 2026
6061334
lib/ui: dropdown/combobox open lists render in the overlay pass; grid…
claude Sep 7, 2026
b2df35a
autograd: reverse-mode tape on shaped buffers + matmul_at/matmul_bt/s…
claude Sep 7, 2026
51e74b1
strict: finish the fail-soft->loud ladder — JSON parse failure, the N…
claude Sep 7, 2026
25c85dd
lint: W024 flags an observer read on a binding rebound from a contain…
claude Sep 7, 2026
644e080
modules: a namespace is a LIVE VIEW of the module env, not a snapshot…
claude Sep 7, 2026
9863364
lint: every diagnostic is UTF-8 safe at the chokepoint, not per messa…
claude Sep 7, 2026
3ec5e1b
integration: reconcile #973 vs #1093 on gather, re-derive the builtin…
claude Sep 7, 2026
bd764c1
changelog: the [Unreleased] section for the 24 commits since v0.43.0
claude Sep 7, 2026
3949cbe
main: every `--<mode>` return drains the cycle collector; the lint su…
claude Sep 7, 2026
5ec3af0
changelog: the #1121 lint collector-drain entry
claude Sep 7, 2026
89bff8d
integration: make the meta-interpreter's `import` resolve, and assert…
claude Sep 8, 2026
672f83e
gate: strict_differential decides with the shell, not a pipe under pi…
claude Sep 8, 2026
ee36f77
changelog: the #1120 strict-differential entry
claude Sep 8, 2026
7ac6fad
strict: the Phase D guards leak their own allocation; enrollment fall…
claude Sep 8, 2026
3f8eaf4
changelog: the #971 round-2 entries
claude Sep 8, 2026
3574d6b
layering: give the core→ext, fs and scheduler seams structure (#744)
claude Sep 8, 2026
7aac104
changelog: the #744 layering entry
claude Sep 8, 2026
330edbf
tests/tools: no pipeline decides a verdict under pipefail (#1122)
claude Sep 8, 2026
5e1a213
ext_gfx: the drawing and audio surface answers a wrong argument loudl…
claude Sep 8, 2026
a86e86c
changelog: the #1007 ext_gfx entry
claude Sep 8, 2026
a2fc749
observer_gate_diff: corpus() falls back to find when git ls-files is …
InauguralPhysicist Sep 8, 2026
1773b18
observer_corpus: regenerate 6 goldens for the #1044/#1045 verdict shift
InauguralPhysicist Sep 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .claude/rules/test-suite.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,15 @@ paths:
as unverified. The KILL direction is worse: `pkill -f <pattern>` matches its
own command line and killed the invoking shell mid-compound (exit 144,
2026-08-23 — the commit/push/PR after it silently never ran). Kill by PID,
never by pattern. Four bites in one day; if it recurs, this graduates to
bash_guard.
never by pattern:

ps -eo pid,cmd | awk '/<pattern>/ && !/awk/ {print $1}' | while read p; do kill "$p"; done

**This has GRADUATED to enforcement** (2026-09-07). It recurred a fifth time —
the same agent that had written the rule into its own brief still reached for
`pkill -f` and lost its shell mid-cleanup — so `bash_guard` now denies
`pkill`/`killall` at command position and names the PID recipe in the refusal.
`pkill -P <pid>` is anchored to a known parent and passes. The prose stays
here for the READ direction (polling with a process-table match), which no hook
covers; the kill direction is the hook's now, and this note should not grow a
second copy of it.
2 changes: 1 addition & 1 deletion .claude/skills/write-eigenscript/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ The error-prone parts of the language. Most are not surfaced when working in a c

## Values
- `+` concatenates **strings only** — list concat is `append of [xs, v]` (in place) or a comprehension; `xs + ys` is a runtime error.
- Numbers are finite by construction: NaN collapses to `0`, overflow saturates at ±1e308 (`sqrt of -1` is `0`, not an error).
- Numbers are finite by construction: NaN collapses to `0`, overflow saturates at ±1e308 (`sqrt of -1` is `0`, not an error). Under `EIGS_STRICT=1` the domain clamps, a NaN result, wrong-typed builtin arguments and a malformed `json_path` document raise catchable errors instead — run graders/tests with it on.
- **Hex integer literals** (`0xFF`, `0X10`; digits only, ends at the first non-hex char) are a real lexed form since #378 (post-v0.24.0); hex-FLOAT forms (`0x1p4`, `0xA.8`) are loud parse errors. **On a v0.24.0-or-earlier pin hex is a strtod accident**: it works HOSTED (including hex floats) but the freestanding profile lexes `0xFF` as `0` + identifier `xFF` — don't use hex in code that must run freestanding on an old pin. No modulo keyword — the operator is `%` (`mod` is a parse error).

## Strings
Expand Down
273 changes: 273 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ LDFLAGS := -pie -Wl,-z,relro,-z,now -lm -lpthread
endif

SRC_DIR := src
SOURCES := $(SRC_DIR)/eigenscript.c $(SRC_DIR)/lexer.c $(SRC_DIR)/parser.c $(SRC_DIR)/builtins.c $(SRC_DIR)/builtins_host.c $(SRC_DIR)/builtins_tensor.c $(SRC_DIR)/hash.c $(SRC_DIR)/arena.c $(SRC_DIR)/state.c $(SRC_DIR)/strbuf.c $(SRC_DIR)/ext_store.c $(SRC_DIR)/fmt.c $(SRC_DIR)/lint.c $(SRC_DIR)/lint_host.c $(SRC_DIR)/chunk.c $(SRC_DIR)/compiler.c $(SRC_DIR)/vm.c $(SRC_DIR)/jit.c $(SRC_DIR)/trace.c $(SRC_DIR)/eigs_embed.c $(SRC_DIR)/repl.c $(SRC_DIR)/step.c $(SRC_DIR)/tape_read.c $(SRC_DIR)/bundle.c $(SRC_DIR)/main.c
SOURCES := $(SRC_DIR)/eigenscript.c $(SRC_DIR)/lexer.c $(SRC_DIR)/parser.c $(SRC_DIR)/builtins.c $(SRC_DIR)/builtins_buf.c $(SRC_DIR)/builtins_host.c $(SRC_DIR)/builtins_tensor.c $(SRC_DIR)/fsutil.c $(SRC_DIR)/hash.c $(SRC_DIR)/arena.c $(SRC_DIR)/state.c $(SRC_DIR)/strbuf.c $(SRC_DIR)/ext_store.c $(SRC_DIR)/fmt.c $(SRC_DIR)/lint.c $(SRC_DIR)/lint_host.c $(SRC_DIR)/chunk.c $(SRC_DIR)/compiler.c $(SRC_DIR)/vm.c $(SRC_DIR)/task.c $(SRC_DIR)/jit.c $(SRC_DIR)/trace.c $(SRC_DIR)/eigs_embed.c $(SRC_DIR)/repl.c $(SRC_DIR)/step.c $(SRC_DIR)/tape_read.c $(SRC_DIR)/bundle.c $(SRC_DIR)/main.c
BINARY := $(SRC_DIR)/eigenscript

# CLI-only translation units: linked into the binary, never into the
Expand Down
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,14 @@ unobserved:
i is i + 1
```

Inside the block, assignments to plain variables skip the observer and
mutate the existing `Value` in place. Outside, normal behavior resumes.
Measured 2.7x on a 2M-iteration accumulator loop (834ms → 307ms, n=5
medians); iLambdaAi saw ~22% end-to-end on an 18-hour training run.
Inside the block, assignments to plain variables skip the observer's
entropy walk and mutate the existing `Value` in place. Outside, normal
behavior resumes. Measured 2.7x on a 2M-iteration accumulator loop
(834ms → 307ms, n=5 medians); iLambdaAi saw ~22% end-to-end on an
18-hour training run. A scalar assignment inside the block still drops its
O(1) sample into the value window (#1049), so the verdicts `report` and the
predicates give a numeric binding are the same with the block as without
it — only the entropy channel (`why`/`how`, the dH window) is elided.

The block only helps **plain variables** — `x is ...`. A dict field or
list element (`d.k is ...`, `xs[i] is ...`) is never observed in the
Expand Down Expand Up @@ -268,6 +272,11 @@ Builtins: `matmul`, `add`, `subtract`, `multiply`, `divide`, `softmax`,
`log_softmax`, `relu`, `leaky_relu`, `zeros`, `random_normal`, `shape`,
`numerical_grad`, `sgd_update`, `tensor_save`, `tensor_load`.

Each of them takes a nested list, a flat list, or a flat numeric **`buffer`**,
and returns a buffer when every tensor operand was one. `zeros of n` returns a
buffer (`zeros of [rows, cols]` still returns the nested list) — numeric work
wants the flat container, and that is the name it reaches for.

EigenScript numbers are finite by construction. Operations that would create
`NaN` return `0`; operations that would overflow to infinity saturate at
`+/-1e308`; domain-limited functions clamp their inputs where appropriate
Expand Down Expand Up @@ -507,7 +516,7 @@ Full map: **[docs/README.md](docs/README.md)**. Highlights:
- [docs/SYNTAX.md](docs/SYNTAX.md) — tutorial-style language guide
- [docs/GRAMMAR.md](docs/GRAMMAR.md) — formal EBNF grammar
- [docs/LANGUAGE_CONTRACT.md](docs/LANGUAGE_CONTRACT.md) — edge-case promises
- [docs/BUILTINS.md](docs/BUILTINS.md) — 250+ builtin functions (199 core + ~60 extensions)
- [docs/BUILTINS.md](docs/BUILTINS.md) — 348 builtin functions (261 core + 87 extensions; `eigenscript --api` prints the live index)
- [docs/STDLIB.md](docs/STDLIB.md) — standard library guide
- [docs/DIAGNOSTICS.md](docs/DIAGNOSTICS.md) — error format and exit codes
- [docs/TRACE.md](docs/TRACE.md) — execution trace, deterministic replay, temporal interrogatives
Expand Down
152 changes: 150 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,39 @@ observer/deterministic-replay niche instead of diluting it.**
- [x] `--bundle` single-file distribution, optional attached tape =
a self-replaying bug report — shipped 0.30.0
([#413](https://github.com/InauguralSystems/EigenScript/issues/413))
- [ ] `ext_net` raw TCP/UDP sockets as tape-recorded nondet inputs —
- [x] `ext_net` raw TCP/UDP sockets as tape-recorded nondet inputs —
record/replay networking no incumbent stdlib has
([#414](https://github.com/InauguralSystems/EigenScript/issues/414))
- [ ] **Container-keyed observer trajectory** — dict fields and list
elements carrying their own observer slot, keyed by (container
identity, key) ([#1048](https://github.com/InauguralSystems/EigenScript/issues/1048)).
*Mechanism today:* trajectory lives on an environment slot
(`env_obs_slot(Env *e, int idx)` → `e->obs[idx]`; the Value carries no
observer state), so per-entity observation needs one persistent
binding per entity — a named local or a closure per entity (the
recommended form; docs/PREDICATES.md "What carries a trajectory").
*The ask:* let `fleet[i][2] is v` / `ch.a is v` update a slot owned by
the container entry, so `diverging of fleet[i][2]` answers about that
entity — the form a consumer reaches for first (phugoid rung 4), whose
current failure is silent: one binding rebound per entity carries the
round-robin interleave and manufactures verdicts (lint `W024` now
names it; the module-level `for`-body `local` answers `equilibrium`
instead, the same rule from the other side). *Layers it touches:* the
compiler (new predicate/`report`/`trajectory` operand forms over
index/field expressions, today `E005` for the report words), the VM
(`OP_INDEX_SET`/`OP_DOT_SET` observer update + reader opcodes and the
observer gate's reader scan, #915), the JIT inline caches on dict
fields and indexed stores, `trajectory of` snapshots, the tape /
`--step` / DAP / SIGUSR1 dump (a slot per entry to record and
replay), and the AOT mirror in ouroboros. *Open design questions:*
list insert/remove shifts identities (is the slot keyed by position
or by the element's identity, and what does `sort` do to a history?);
lazy slot allocation keyed by statically-named fields only (`ch.a`)
versus every dynamic key (memory: a slot per entry of every observed
container, or an opt-in `observed` container); whether the container
or the entry owns the slot when the entry is itself a container; and
the tape format for per-entry observer records. Deliberately not
built in the same round as `W024` — needs its own design pass.

### Design decisions (cheap to decide, expensive to defer)

Expand All @@ -150,6 +180,124 @@ observer/deterministic-replay niche instead of diluting it.**
"num IS f64" so a future wider kind stays possible
([#417](https://github.com/InauguralSystems/EigenScript/issues/417))

- [ ] **Value-level invalidity taint for the observer** — **DEFERRED, by
evidence, not omission.** #971 item 1 proposed threading
`math_flags & INVALID` into the `ObserverSlot` so a binding produced by
an invalid op refuses the rest bands the way saturation does. It was
built (a consumed per-state `math_invalid_pending` edge, a
`v_invalid` slot bit OR'd into the five band functions) and
**reverted**: attribution is positional — "the next binding observed
after an invalid op" — and holds only when the invalid op is lexically
the last thing evaluated before the observed assignment. Executed:
`a is sqrt of (0 - 1.0)` reads `diverging`, but `local t is sqrt of
(0 - 1.0)` / `a is t` and a `safe_sqrt` wrapper both certify
**`converged`** on the fabricated 0 (false negatives), and the mirror
false positive — a discarded invalid op tainting an honestly converged
neighbour — is equally reachable. Saturation needs no state because it
is derivable from `last_value` alone; a clamped NaN lands mid-band and
is not. So the bit must **travel with the value**: a taint on `Value`
propagated through copies, returns and the NaN-boxed `EigsSlot`
immediates, **and carried on the tape** — `tape_read.c`/`step.c`/
`eigsdap.c` rebuild slots from recorded values, so the live runtime
reported `diverging` where `--step` reported `[converged]` on the same
program (a missing key in an old dump must not read as "valid"). That
is a design pass of its own, not a sub-bullet. What shipped instead:
under `EIGS_STRICT=1` every reachable NaN source raises (item 3 of the
strict ladder), so a grader that needs invalidity loud has it without
the taint.
([#971](https://github.com/InauguralSystems/EigenScript/issues/971))
- [ ] **A `matmul` BUFFER result is stored raw — `inf` reads back above
`1e308`, and a `NaN` reads back as `null`.** The boxed roads go
through `make_num`, whose `num_guard` saturates an
infinity and collapses a `NaN` to `0` + `math_flags.invalid`. The
buffer fast path writes the kernel's accumulator straight into the
result buffer instead, so both survive: `r[0] > 1e308` is `1`, and a
`NaN` element is not a number the program can even see — its bit
pattern IS the boxed-slot tag for null (0xFFF8… == `SLOT_NULL_BITS`),
so `r[0]` reads `null` out of a buffer of numbers, and
`math_flags.invalid` stays `0`.
**#971 built the NaN half of the fix and then reverted it, on
purpose.** Collapsing NaN there is two lines and passed every test,
but it changes the DEFAULT path (`null` -> `0`, `invalid` 0 -> 1), and
the one claim the strict reform makes is that with the flag off
nothing changed — proven by `tools/strict_differential.sh` against the
previous release binary. Shipping it meant carrying a waived
divergence in that tool, i.e. the proof with a hole in it, for an
incidental fix that was never what #971 was about. So strict raises on
both paths (`STRICT_DOMAIN`, which cannot touch the soft path) and the
default answer is byte-identical to v0.43.0; `tests/test_strict_math.sh`
SM49a/SM49b pin both halves so neither moves by accident.
**The writer is not the place to fix it.** `matmul` is not the only
road to a `NaN` buffer element: `ext_store` round-trips one on
purpose (`store_nonfinite_sentinel` encodes `"nan"`/`"inf"`/`"-inf"`
because JSON has no literal for them), and the embed API's
`eigs_value_buffer_set` takes a raw `double` from the host. Whatever
is decided has to be decided at the READ, where every road meets.
Doing it properly is its own change: decide the buffer contract for
BOTH non-finites together (saturate the `inf` too, or keep both raw and
make the buffer read report a NaN as a number rather than as `null`),
mirror it in the AOT — ouroboros `aot_rt.h`'s `aot_tensor_matmul`
reads the same raw buffer and its round-187 fixture PINS the `inf`
read — and run the differential over both.
([#971](https://github.com/InauguralSystems/EigenScript/issues/971))
- [ ] **Flip `EIGS_STRICT` to the default?** — **DEFERRED; the evidence
says it is now cheap, the decision is still open.** Measured
2026-09-06 on the v0.43.0 tree with the #971 Phase C/D + NaN work
applied: **94 consumer entry points** (DMG `test_cpu`/`test_memory` +
the 500K-cycle canary, EigenMiniSat DPLL/CDCL solves, EigenRegex S1–S12
+ smoke, EigenGauntlet's 11 labs at size 1, Tidepool, dynamics,
liferaft, tidelog, phugoid, polymethod, DeslanStudio's 24 headless
tests, iLambdaAi, eddy) run twice on the same binary, flag off and
`EIGS_STRICT=1`: **0 of 94 change exit status under strict**; the six
that fail do so identically in both modes for load-path reasons
unrelated to the flag. (Re-spot-checked 2026-09-07 on the final
binary — DMG `test_cpu`, EigenMiniSat `test_solver`, EigenGauntlet
`tensor`/`io`, Tidepool `test_game`, dynamics `solve`, liferaft
`test_prng`, EigenRegex `test_smoke`: 8 of 8 unchanged.) The runtime's own suite is a different story —
it PINS the soft answers (`sqrt of -1` is `0`, `cos of "hello"` is
`0`, the `fs:ANSWER` pins) in dozens of sections, so flipping the
default means rewriting those pins as `EIGS_STRICT=0` rows and
re-deciding which stand-ins survive as documented answers (the
classification ledger in `tools/failsoft_classify_check.sh` is the
input). Two things must land first: the AOT mirror (ouroboros
`aot_rt.h` carries its own inlined `num_guard`, `op_div`-shaped
`aot_ddiv` and a raw-`inf` matmul read pinned by its round-187
fixture — a default flip without the mirror flipping recreates the
#975 div0 fossil), and a decision on the **raw non-finite in a
`matmul` buffer result** (the entry below). Until then:
strict stays opt-in, graders and CI lanes turn it on, and the
differential (`tools/strict_differential.sh <parent-build>`) keeps
the default path byte-identical.
([#971](https://github.com/InauguralSystems/EigenScript/issues/971))

- [ ] **Per-layer headers — break up the 1253-line `eigenscript.h` umbrella.**
Item 3 of [#744](https://github.com/InauguralSystems/EigenScript/issues/744),
the one part of that issue deliberately NOT done in the same round; items
1, 2, 4 and 5 landed (dead extension includes, stale externs, `fsutil.c`,
the `task.c` / `builtins_buf.c` splits). The measured facts, from the
2026-07 modularity review: there is no `lexer.h`, `parser.h`,
`compiler.h`, `chunk.h` or `builtins.h` — only `vm.h`, `jit.h`,
`trace.h`, `state.h` (plus, since #744, `fsutil.h`, `task.h` and
`ext_register.h`). `eigenscript.h` spans the tokenizer, the AST, values,
the arena, `EigsThread`, env, the parser, registration, the MODEL tensor
kernels, the handle table, the store, step, and fmt+lint: **26 structs
with every field visible, 167 declarations, included by 29 of ~30 TUs**.
Two consequences are measured, not asserted: a lexer change forces a full
rebuild of everything, and the layer order is violable and violated —
`compiler.c` increments the PARSER's `g_parse_depth` `EigsThread` field
as its own recursion guard, and lexer, parser and compiler all write
`g_parse_errors`, the front end mutating runtime thread state.
What makes this its own round rather than a follow-up commit: 29 TUs,
`tools/amalgamate.sh` (which concatenates them in SOURCES order and would
have to keep an acyclic include order across the split), and the
freestanding profile's two-stage symbol gate. Note the header GRAPH is
already clean and acyclic (`eigenscript.h -> value_slot.h`, `vm.h ->
value_slot.h`, everything else -> `eigenscript.h`), so this is a hub
problem, not a tangle — the split is mechanical once someone commits to
doing all 29 at once. `#744` showed the cheap version works: `fsutil.h`
moved 8 declarations out of the umbrella and 7 TUs now say they read
files, and nothing else changed.

### AOT (ouroboros — the native-perf path; not the JIT)

- [x] Close the F-OURO-23 envelope via `lib/checksum.eigs` (CRC-32) as
Expand Down Expand Up @@ -248,7 +396,7 @@ when picked up:
TLS) — **deliberately deferred** per the 2026-07 survey critic:
vendored crypto is a solo-maintainer security liability with zero
consumers needing AEAD; revisit when one does.
- [ ] Raw TCP/UDP sockets — now specced as tape-recorded nondet inputs,
- [x] Raw TCP/UDP sockets — tape-recorded nondet inputs,
liferaft as forcing function
([#414](https://github.com/InauguralSystems/EigenScript/issues/414))
- [ ] Additional DB drivers (MySQL, NoSQL; SQLite folds into the #415
Expand Down
5 changes: 3 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ and drives `loop while not converged` termination.
Observation uses lazy evaluation: `OP_OBSERVE_ASSIGN` marks values dirty
(O(1)), and entropy is computed on demand when observer state is read.
The last observed value is tracked via a thread-local pointer
(`g_last_observer`). `unobserved` blocks skip observer marking entirely.
(`g_last_observer`). `unobserved` blocks skip the entropy update; a scalar
assignment inside one still records its value-window sample (#1049).

Loop stall detection (`OP_LOOP_STALL_CHECK`) exits while-loops after 100
consecutive iterations with `|dH| < threshold`, setting `__loop_exit__`
Expand Down Expand Up @@ -255,7 +256,7 @@ The minimal build (`make build`) sets all flags to 0. The full build

## Standard Library

The 77 modules in `lib/` are pure EigenScript — no C code. They are loaded at
The 78 modules in `lib/` are pure EigenScript — no C code. They are loaded at
runtime via `load_file of "lib/module.eigs"`. Both loaders use absolute paths
as-is; relative paths search the containing file's directory, the `eigs_modules`
walk, the nearest `eigs.json` project root, then the executable-relative and
Expand Down
Loading
Loading