Skip to content

Releases: tompassarelli/beagle

v0.18.0

28 Jun 17:58

Choose a tag to compare

Minor release. 142 commits since v0.17.1 — new surface/types + breaking removals (SQL target, ~"…" tilde-strings) + Apache-2.0 relicense. Highlights below.

Value-semantics JS runtime (emit-js)

  • HAMT persistent map + set: value-keyed, tree-shakeable, value-dedup.
  • Type-directed representation selection — provably-scalar → native, else → HAMT — with rep-aware/polymorphic reads ($bc.get/keys/vals, kw-access).
  • Value-equality runtime: =/==/not=/distinct/contains?/into/conj route to value semantics.
  • lite/full runtime split (size leg) + configurable $$bc import specifier.
  • Cross-target conformance harness (babashka oracle) over a real semantic corpus.
  • Fixes: set!-mutated let → let not const, doseq IIFE in expr position, arrow-fn object-literal parens, unary (- x) lowering.

Code-as-claims / graph-native pipeline

  • emit-claims backend + reverse-path round-trip gate (code as canonical claims).
  • --build-edn: compile straight from claim triples, byte-identical to text (#33).
  • emit-edn-typed: typed-AST layer as derived claims (GLASS prep).
  • claims → clj DIRECT emit (graph-native, no .bclj round-trip) + fail-closed claims-check-emit gate (claims → AST → type-check → emit).
  • Rented chartroom code-intelligence engine; beagle-callgraph from the converged cross-module resolver.
  • Scope-correct rename/delete as gated authoring verbs (large adversarial gate sweep).

Type system (G-series)

  • G1 defalias, G2 parametric (Atom T) (invariant), G3 HVec heterogeneous tuple, G4 map-pattern destructuring (bind + narrow), G5 defenum membership enforcement, G7 for/doseq :- binders.
  • Typed JVM-class interop (receiver-typed class table), ^:dynamic vars + typed binding, typed arrays, cross-module binding of an imported ^:dynamic var.

Surface / stewardship

  • Collapse two readtables → ONE source of truth (#19).
  • Remove ~"…"/~''…'' tilde-strings — ~ = unquote, uniform across ALL targets (#25); interpolation is (s …)/(ms …).
  • if-let/when-let accept destructuring + :- typed binders (#22).
  • Prefix hygiene / hallucination-firewall stewardship: bare namespace = Clojure-only, prefix = target.
  • Hallucination log + beagle-halluc scanner (data-driven surface pruning).
  • Reject multi-arity anonymous fn and (defn <combiner>) (were silent miscompiles); bracket fn-type return arity fix.

Build / chore

  • Remove the SQL target (unused, undogfooded).
  • Relicense under Apache-2.0 (+ fix beagle-lib license metadata, was stale MIT).
  • flake: pin racket + ship .zo so bytecode skew can't recur; _beagle-racket resolves the pin inside worktrees.

v0.17.1

16 Jun 08:41

Choose a tag to compare

A patch release of JS-target hardening, driven entirely by authoring a real downstream app (the gjoa Firefox fork) in #lang beagle/js. Each item is a silent miscompile or footgun the port hit — now an emit fix or a loud compile-time guard. Active suite 1377/1377.

Fixed

  • Async IIFEs are awaited in value/statement position (678bbd1): a try/loop/doseq containing js/await compiles to an async IIFE; bound in a let without an enclosing js/await, it was emitted without await, so the binding held a pending Promise that downstream code then read synchronously. emit-js now awaits async IIFEs in value/statement position — tail position correctly left alone, and the exact (async () => prefix match never double-awaits.
  • Macro-only :refers are no longer emitted as runtime imports (69c718a): a refer that resolves to a macro is compile-time only and has no runtime export, but it was emitted in import { … } — silently fine when a bundler tree-shakes the dead import, a load-time "does not provide an export named X" for any unbundled ESM consumer. emit-module-header now drops macro refers and omits the import line entirely when a require's refers are all macros.
  • The purity check now covers exported functions (5e18635): check-purity! descended only into list-shaped wrapper forms, so js/export / js/export-default (which parse to structs) hid every exported defn — the public API — from the !-effect check. Now descends into jst-export / jst-export-default / with-meta.

Added

  • swallowed-binding guard (E020) (004d291): a let binding one paren short silently absorbs the following name value pair as body forms, emitting the swallowed name as a bare name; statement → runtime ReferenceError, while beagle syntax reports "ok" (parens net-balance). A non-final body statement that is a bare unbound symbol now errors with a pointed message; tail-position bare symbols (legitimate returns) are not flagged.
  • % rejected as a call head — percent-not-modulo (2f7a441): (% a b) emitted _pct(a, b) (% is the #() anonymous-arg shorthand) → undefined call. Now a pointed parse error naming rem/mod for modulo; % inside #(…) lambdas is unaffected.
  • camelCase JS-export lint (7b5b6d5): kebab names mangle to snake_case and camelCase emits as-is, so a camelCase export referenced cross-module in kebab resolves to a different identifier (undefined in the bundle). A lint warns and names the kebab fix; it runs in lint-program! (fires at build and check) and is counted by count-lint-warnings, so the --agent repair loop surfaces it too.

v0.17.0

15 Jun 15:08

Choose a tag to compare

Where 0.16 locked the surface, 0.17 turns the compiler into something its own repair tooling can drive. Diagnostics now carry structured, machine-applicable data; beagle-doctor proves the repair loop works rather than merely runs; form dispatch unifies onto a single compile-time combiner registry; Odin joins as a live native target and the JS emitter returns to live. Five live targets: Clojure, ClojureScript, JavaScript, Nix, Odin.

Highlights

  • Repair loop is real and proven end-to-end: diagnostics carry structured types and machine-consumable conversion data (MessageData), beagle-repair applies them, and beagle-doctor demonstrates the loop functions, not just that the daemon is alive (d599fe1, 1cc1077, a0e6051).
  • Dispatch unified: one compile-time combiner registry resolves macros, builtins, and legacy forms; 21+ special forms plus the def/control/module/nix/js/sql families migrated onto it; the dead operative prototype was deleted (5d58d09b737821, 80c01a1).
  • Odin is a live native target, replacing the now-parked zig backend; the JS emitter is promoted back from dormant to live (34fd382, e782375).
  • Deterministic paren-balancing is auto-enforced via the PostToolUse hook, and hooks are distributed from tracked templates (bdaae9f, 8b13af3).
  • !-purity static pass (check-purity!) is on by default (c118f21, 0130145).

Added

  • In-compiler error-explanation registry with machine-applicable suggestions (1743404).
  • Structured types in diagnostics via MessageData; structural fix-plans carry machine-consumable conversion data that beagle-repair consumes (d599fe1, f36d18c, 1cc1077).
  • Exhaustive-match auto-fill: missing-case clause skeletons emitted as an applicable repair fix (cc30a6c).
  • Auto-apply replace-head suggestions in the repair loop (822fa13).
  • Types-as-view: beagle-explain-type projects inferred types through an extensible delaborator registry; numeric unions fold to Number, with --write promotion (4145ce4, 13847b3, f0ff58c).
  • beagle-doctor proves the repair loop works, with a dynamic target inventory and a correct raco probe (a0e6051, 2c5a56b).
  • Source positions carry origin/canonical with precise column propagation; macro expansions inherit the call-site source position (de155ba, 3a9af8f).
  • Generated, example-verified capability cheatsheet that can't rot (10d5024).
  • Odin backend: #lang beagle/odin, numeric width types, .bodin build support, defenum, fixed arrays, range loops, pointer types, struct literals, keyword→enum variants, non-string map keys (map[K]V), stdlib-odin math/casts, and defmacro incl. the ECS defcomponent pattern (34fd382 + series).
  • JS emitter live again: @x deref sugar, js/import-meta, js/export-default, async loop/recur via js/await, destructuring :or/:as, kebab-case property mangling, statement-position IIFE elimination (e782375 + series).
  • !-purity static pass (check-purity!), shipped dark then enabled by default as an error (c118f21, 0130145).
  • (:gen-class) in ns for clj AOT/native entry; batch declare-extern(declare-extern [a b c] Type) (f82e6fa, 47f093c).
  • Multi-module type awareness for package targets (odin); qualified-call resolution for clj/cljs with fixed sibling imports (f2b8f2f, 8b92761).
  • stdlib-bb babashka-runtime typed tranche (~130 entries) (da975a1).
  • Inline expected-diagnostic test harness with mechanical update (40da2b9).

Changed

  • Form dispatch unified onto one compile-time combiner registry — do/if seeded first, then the when/if conditional family, def, control, module, nix, js, and sql forms; a single resolver now handles macros, builtins, and legacy forms (5d58d09b737821).
  • Odin replaces zig as the native target; zig is parked under dormant/ (34fd382).
  • Real mode-2 macro hygiene: definition-site free-variable resolution, across all live targets including odin (3fe36b7, 06bedfc).
  • Numeric-preserving arithmetic with IntFloat widening in the checker (63b62ca).
  • nil-narrowing extended to and/or composition and not=, with soundness fixes and a deeper clj stdlib (d77855e).
  • clj emitter: lean release mode; dropped ^long/^double and unresolvable opaque-extern hints the JVM/AOT compiler rejects (b7ba4cc, 80233e0, a401115).
  • CLI consolidated onto beagle <cmd>: 12 missing subcommands wired, 8 dead tools removed, beagle init unified onto the canonical scaffolder (5419551, 8b7ac68, adf8262).
  • Hooks distributed from tracked templates; pool mode is portable and scaffolded, and --hooks idempotently merges into existing repos (8b13af3, c2319a9).
  • PostToolUse hook auto-enforces deterministic paren-balancing (bdaae9f).
  • Version metadata bumped 0.15.30.17.0 (info.rkt was never advanced for 0.16.0); pkg-desc corrected to the live target set.

Removed

  • Dormant py / rkt / scheme / zig targets (SQL kept as a dormant emitter with live schema-typing) (4497259).
  • Dead operative prototype deleted; the one-compiler ground truth is documented (80c01a1).
  • Game/kernel extracted out of the language repo to ~/code/games (8377383).

Fixed

  • Don't crash compiling nested macro calls (datum->syntax on a raw-datum srcloc) (8290e66).
  • Delaborator offset correctness across tabs/CRLF, with opt-in capture (45ab2a9).
  • Repair-loop clause insertion: single-line matches and string-decoy anchors (afff6c4).
  • Structural fix-plan blames the differing type argument (e6a6562).
  • clj regex emission and a blame-path destructure crash (0389b8b).
  • JS :as whole-map binding across all three let paths; record-ctor partial gated to real records (973dd9b).
  • Hardened (ns ...) name extraction in beagle-build (7437294).
  • Surface hardening: killed silent meaning-changers and closed LLM-prior gaps (2b38cad).

v0.16.0

01 Jun 02:53

Choose a tag to compare

The surface stopped accreting. v0.16 locks beagle's authoring layer to a three-statement spec — typed Clojure, load-bearing divergence or it dies, idiomatic per target — and converts the Clojure and ClojureScript emitters from dormant to live alongside Nix.

Highlights

  • Surface lock: typed Clojure + inference; inline :- annotations replace claim on def/defn/defonce/let (6fefc09).
  • Multi-target live loop: Nix, Clojure, ClojureScript all active; JS/Py/SQL/Rkt remain parked under BEAGLE_ALL_TARGETS=1 (ce51c1b).
  • Macros: defmacro + quasi-quote (` , ,@) shipped; legacy define-macro hard-removed (96e9138).
  • Reader conditionals: #?(:clj … :cljs … :nix … :default …) and #?@(…) splice across the live-target tier.
  • Sourcemap fidelity: diagnostics blame author position through every canonicalization — sourcemap-fidelity.rkt corpus 5/11 → 11/11 (2025b33).
  • Typo suggestions against the real 16k NixOS schema: 96.9% Top-1 at 130 ms/query, +1.1% end-to-end overhead on the firn-validate corpus.

Added

  • Inline :- type annotations on def, defn, defonce, and let bindings (parse.rkt:418, 1364).
  • defmacro with Scheme-style quasi-quote / unquote / unquote-splicing (parse.rkt:306, 2344).
  • Clojure threading family: ->, ->>, as->, cond->, cond->>, some->, some->> (parse.rkt:2147).
  • Reader conditionals #?(…) and splicing #?@(…) with :clj :cljs :nix :default tags (parse.rkt:450).
  • Quoted self-evaluating containers '[…], '{…}, '#{…} (2b2e258).
  • Keyword access canonicalization: (:k target) and (get target :k) both lower to a single kw-access AST node (2eb7baa).
  • Conditional family completed: when, when-not, if-not, unless, if-let, when-let, if-some, when-some, cond, condp.
  • Stdlib sugar: inc, dec, not= typed in stdlib-portable.rkt.
  • Per-target prefixes nix/, js/, sql/ for forms whose meaning diverges from Clojure (e.g. nix/assert, nix/with-cfg, js/await).
  • Structured diagnostic taxonomy: cause-class?, surface-divergence, type-error, logic-error exported from diagnostic-kind.rkt; consumed by bin/beagle-rejection-stats.
  • bin/beagle-rejection-stats <dir|glob> [verify-script] aggregates failure causes by class.
  • Schema-typed NixOS option paths: 16k options loaded into the typed environment via nixos-schema.rkt.

Changed

  • claim replaced by inline :- annotations on binding forms; same checker, less syntax (6fefc09).
  • Keyword access is a single canonical AST node regardless of spelling — emitters and checkers see one shape (2eb7baa).
  • Clj and Cljs emitters promoted to the active tier in beagle-test/tiers.rktd; default bin/beagle-test run now covers them.
  • Bare divergent forms now raise with a "use (prefix/...)" hint instead of silently emitting (parse.rkt:1577).
  • README reframed around the typed authoring IR and the three-statement generative spec.

Removed

  • claim form (superseded by inline :-).
  • Pipe threading family (replaced by Clojure -> ->> as-> cond-> some->) (1577987).
  • define-macro (replaced by defmacro + quasi-quote) (96e9138).
  • deftype residual surface; threading surface reconstruction completed (f24dcd4).
  • Bare aliases for prefix-divergent forms — must spell as nix/... / js/... / sql/... (91a3abc).

Fixed

  • Sourcemap drift through canonicalization passes: diagnostics now point at the author's original token across every rewrite (2025b33).
  • Validator false positives resolved by quarantining the experimental operative checker behind BEAGLE_EXPERIMENTAL_OPERATIVE=1.
  • Levenshtein typo suggester is now segment-aware against the real schema: 96.9% Top-1, latency cut 57% (306 ms → 130 ms/query).

Internal

  • Phase 0 instrumentation + Phase 1 + Phase 2 batch migrations across the corpus (e273c35).
  • Corpus migration tooling for the :- adoption pass (140 files touched in 6fefc09).
  • CLAUDE.md formalizes ten standing rules and the three-statement spec — the surface is now spec-determined, not negotiated.

Known limitations

  • Free-variable resolution at definition site: macros are datum-based, not syntax-object-based.
  • Bidirectional inference Layer 2 deferred until a corpus has enough defns to justify it.
  • Refinement types gated to a demo file behind a kill-switch.
  • Operative checker quarantined behind BEAGLE_EXPERIMENTAL_OPERATIVE=1; not shipping in the default tool surface.
  • JS / Py / SQL / Rkt emitters remain dormant; opt in with BEAGLE_ALL_TARGETS=1 for structural-only runs.

v0.15.3

30 May 11:58

Choose a tag to compare

read-beagle-syntax: target-aware readtable so build-all picks up ~''…''

bin/beagle-build loads each .bnix as a #lang module, so it always gets
the right reader from the lang directive. The build-all path
(used by bin/beagle-build-all and bin/firn-build) goes through
read-beagle-syntax in parse.rkt, which skipped the #lang line AND
hard-coded the base beagle-readtable. The base readtable doesn't know
about nix's ~"…" / ~''…'' reader macros, so any ~''…'' body
containing the first }, |, #, etc. failed to read with the
generic "unexpected }" / "unexpected dispatch sequence: #" errors.

v0.15.2's importer + normaliser both started emitting ~''…'', which
made this latent gap fatal for build-all consumers — including the
nixos-config firn-build pipeline.

Fix: read-beagle-syntax inspects the #lang target and, for nix files,
switches to beagle-nix-readtable (now exported from
beagle/nix/lang/reader-impl). Non-nix files keep the base readtable
so ~-as-interp stays scoped to the nix variant.

Regression test: tests/build-all-nix-reader.rkt feeds a ~''…'' body
through read-beagle-syntax with every char that previously crashed
the base reader.

v0.15.2

30 May 11:58

Choose a tag to compare

bnix multiline strings: importer emits ~''…''; lint hard-errors legacy cursed form

The pre-fix importer dumped indented Nix ''…'' literals as a single Racket
string with embedded \n escapes: (ms "#!/usr/bin/env bash\n\nset -e\n…").
Visually unreadable, and three latent bugs underneath:

  • the legacy emit-nix-multiline-string and operative nix-ms both
    concatenated (ms …) operands with no \n between them, so the
    canonical multi-operand form (ms "line1" "line2") rendered as one
    physical line in the output Nix. The cursed single-string-with-\n
    only worked because it happened to be one operand.
  • operative nix-ms wrapped non-string operands with ${…} unconditionally,
    so (ms (s "#!" pkgs.bash "/bin/bash") "rest") emitted as
    ${"#!${pkgs.bash}/bin/bash"}rest instead of inlined.
  • the ~''…'' reader's ''$ escape (literal $ in the body) was undone by
    split-line-interp's second pass, which re-treated ${X} as interp
    regardless of how the $ got there. ''${THEMES[@]} round-tripped to
    ${THEMES} — silent semantic regression.

Fixes:

  • bin/beagle-import-nix: emit (ms …) using a new emit-ms-tilde helper
    that produces ~''…'' with body indentation and per-line operand
    splitting. Both str-lit-ind and str-interp-ind go through it.
  • beagle-lib/private/emit-nix-strings.rkt (legacy emit): join (ms …)
    operands with \n before the re-split + indent pass.
  • beagle-lib/private/emit-operative.rkt (operative emit): same \n
    join for nix-ms, and inline (s …) operands as raw ${EXPR} markers
    in the body rather than wrapping the whole line in ${"…"}.
  • beagle-lib/nix/lang/reader-impl.rkt: read-multi-line-body emits a
    U+0001 sentinel before literal $ from ''$; split-line-interp drops
    the sentinel and passes the $ through as a chunk character without
    interp parsing. ''${X} now survives end-to-end.
  • beagle-lib/private/lint.rkt: new lint-nix-ms pass — any (ms …) with
    a string operand containing \n is a hard error pointing at ~''…''
    and bin/beagle-normalize-ms. Bypassable via BEAGLE_NO_LINT=1 during
    sweep.
  • bin/beagle-normalize-ms: one-shot rewriter for existing .bnix files.
    Parses (ms …) forms (single- and multi-operand), reassembles the
    body with ${EXPR} for interp operands, escapes for ~''…'', and
    emits in place. --dry-run and --check supported.
  • bin/beagle-daemon: bump start timeout from 5s to 30s. Cold-start
    require is ~8s without compiled bytecode (~0.5s with), so the
    previous timeout killed the daemon mid-startup on a clean checkout.

Tests:

  • beagle-test/tests/emit-nix.rkt: multi-operand (ms …) per-line
    splitting; interp inlining for multiple (s …) operands.
  • beagle-test/tests/nix-roundtrip.rkt + fixtures/nix-tilde-ms.bnix:
    ~''…'' reader form with literal '', literal ${X} via ''$, and
    real ${pkgs.bash} interp in the same heredoc.
  • beagle-test/tests/nix-import-roundtrip.rkt + fixtures/import-nix-
    source/heredoc.nix: importer end-to-end — no cursed (ms STR-WITH-\n)
    in output, real interp preserved, literal ${X}/${array[@]} survives
    .nix → .bnix → .nix.
  • beagle-test/tests/nix-lints.rkt: hard-error on single- and multi-
    operand cursed forms; canonical multi-operand passes.
  • beagle-test/tests/normalize-ms.rkt: normalizer rewrites both
    cursed shapes, leaves canonical alone, is idempotent, --dry-run
    preserves contents.

Also: beagle-lib/private/validate-nix.rkt picks up lib.mkDefault/mkForce/
mkMerge/mkOverride (dotted form, not just lib/mk*) as priority modifiers,
and coercedTo as a mergeable Nix-module type.

v0.15.1

29 May 00:56

Choose a tag to compare

Patch release — codegen bug fixes plus batch-build tooling.

Fixes

  • (module …) formals no longer emit ..., .... The parser stripped no marker before falling through to the formal-name path, so the user-written ... became a formal and the module form's hardcoded rest? flag then appended a second one. Hyphens and a literal now read clean.
  • (ms …) heredocs indent every physical line. Previously each list element was treated as one line regardless of embedded \n, so a single multi-newline string fragment broke the heredoc's leading indent. Parts are now flattened into one buffer, split on \n, and indented per physical line.

Tooling

  • beagle-build-all --in-place + .nix target. Writes <src>.nix next to each <src>.bnix in a single Racket process. Wiring firn-build (in the firnos config repo) through this path takes a full 216-file corpus regen from ~11min to ~9s.

Compatibility

No surface changes. Existing .bnix sources type-check and emit identically modulo the two bug fixes above. Regenerated .nix may differ cosmetically (empty heredoc lines no longer carry stray indent, trailing \n from source is now preserved).

v0.15.0

27 May 14:57

Choose a tag to compare

Highlights

  • bnix converter rewrite on rnix-parser. beagle-import-nix swaps
    nix-instantiate --parse for rnix-parser
    via a small Rust binary (tools/nix-parse-json). The lossy-normalization
    workarounds (float markers, path restoration, manual tokenizer) all go
    away — net –600 lines of Racket. 100% semantic correctness on two real
    corpora (Misterio77 nix-config + firnos nixos-config, 9/9 hosts).
  • Typed flake-input form ships, nix-ident removed. The last
    escape-hatch-by-another-name is now a parse-time error with a
    migration message. The "zero escape hatches" claim now matches code
    reality.
  • dockerTools stdlib coverage. buildLayeredImage, buildImage,
    streamLayeredImage, pullImage typed.
  • Tree prune. lab/, notebook/, self-host/ removed from this
    repo (~35k-line net deletion). The Bun-based self-host proof-of-concept
    is retired; production self-host direction is Cyclone Scheme.

Breaking

  • nix-ident is now a parse-time error. Migrate to (flake-input :NAME :NAMESPACE :path ...).
  • beagle-import-nix now requires tools/nix-parse-json to be built:
    cargo build --release --manifest-path tools/nix-parse-json/Cargo.toml.

Stats

  • 1190 active-tier tests passing (+ oracle/differential/bun-parity)
  • 116 commits since v0.14.0

Note (added post-release)

The Bun self-host bin scripts (beagle-bun, beagle-self-emit{,-clj,-rkt,-py,-nix}, beagle-ast) and the oracle-bun test are retired alongside the self-host/ deletion. If you need the working Bun PoC: git checkout v0.14.0. Cyclone Scheme is the production self-host destination.

v0.14.0

23 May 16:57

Choose a tag to compare

The Nix story landed.

Beagle is now a real typed authoring layer for NixOS configs (and still everything else). Schemas flow into the type checker, escape hatches are gone, the stdlib actually has teeth.

Highlights

Schema → type checker integration

config.services.openssh.enable now resolves to Bool from `.beagle-cache/schema.json`. Type errors surface at compile time:
```
(def msg : String config.services.openssh.enable)
;; ✗ def msg: expected String, got Bool
```
Also: `(let [cfg config.services.X] cfg.foo)` flows the alias through schema. Bare-symbol truthiness narrows nullable types (`(if config.X.optional ...)`).

Stdlib precision: 104 → 527 entries

Higher-order combinators get proper parametric types:
```
builtins/map [forall (A B) [[A → B] (List A) → (List B)]]
builtins/attrNames [forall (V) [(Map String V) → (List String)]]
lib/foldl [forall (A B) [[B A → B] B (List A) → B]]
```
Full lib.attrsets / lib.lists / lib.strings / lib.modules / lib.options / lib.types / lib.generators coverage.

Zero escape hatches

Deleted: `unsafe-nix`, `unsafe-js`, `unsafe-clj`, `unsafe-py`, `unsafe-rkt`, `unsafe`, `unsafe-expr`, `define-macro unsafe`, the `''...''` raw passthrough reader. All of them. When the stdlib doesn't cover something, add a one-line type signature. See `CLAUDE.md` for the design rule.

New nix surface forms

  • `(derivation {:pname ... :src ...})` — typed shape validation, did-you-mean on typos
  • `(flake {:description ... :inputs ... :outputs ...})` — required key check, outputs must be a function
  • `(overlay [final prev] body)` — curried (was attrset-pattern, caused infinite recursion at nix-build time)
  • `(with-cfg config.X BODY)` — AST-level cfg= let-binding (replaces the regex magic)
  • `~"hi ${name}"` — single-line interp reader
  • `~''multi\nline\n${name}''` — multi-line interp reader

Surface elegance — one canonical idiom per concept

```
inh → inherit rec-att → rec-attrs
inh-from → inherit-from assert-do → assert
with-do → with spath → search-path
impl → implies fn-set-rest → module
```
No aliases. Did-you-mean catches typos.

Tooling

  • `bin/beagle-ci` — full gate (tests + property + nixos-config validate)
  • `bin/beagle-nix-oracle` — emit + `nix-instantiate --parse` classifier
  • `bin/beagle-test-nix` / `bin/beagle-test-tag` — file-glob and tag-based test runners
  • LSP target-aware: completion suggests the right stdlib per file extension
  • `beagle-validate` 1-line output by default; `-v / --verbose` for full chatter

contrib/

  • `contrib/nvim-lspconfig/` — ready-to-PR stanza + standalone install instructions
  • `tree-sitter-beagle` grammar (separate repo, pending push)

Status

  • 1343 tests passing (was 1296)
  • Schema integration verified end-to-end: `nix build .#nixosConfigurations.whiterabbit.config.system.build.toplevel` succeeds from beagle-generated flake.nix
  • Dogfooded on a 220-file NixOS config (firnos)
  • Still pre-1.0 by design — no v1.0 until others have used it in anger

Breaking changes

  • `unsafe-*` parse-time error (was: target-specific verbatim emission)
  • `with-do` → `with` (shape-disambiguated)
  • `fn-set-rest` → `module`
  • `fn-set@` removed (use let-binding + fn-set)
  • `inh`/`inh-from`/`rec-att`/`assert-do`/`spath`/`impl` renamed to full words
  • `''...''` reader removed (use `~''...''` for beagle-interp, or write a sibling `.nix` file for raw Nix)
  • `define-macro unsafe` rejected (all macros are now `safe` and type-checked end-to-end)
  • `.nisp-cache/` → `.beagle-cache/` (rename, schema and config files)

Migration helper

  • For `firnos`-style configs: sed `with-do` → `with` and `fn-set-rest` → `module` across all `.bnix` files; the validator + check catches anything missed.

v0.13.1

22 May 10:36

Choose a tag to compare

Correctness & coverage

  • Inf/NaN emission fixed across all 4 emitters: JS→Infinity, CLJ→##Inf, Python→float('inf'), Nix→clear error
  • defenum keyword emission fix (emitted symbols instead of keywords in CLJ)
  • defmethod/deftype/extend-type return type annotation leak fix (3 instances of same parser bug)
  • CLJ behavioral test suite: 64 end-to-end tests (compile → Babashka → verify), including multi-module require round-trips and protocol/deftype edge cases
  • Bun oracle CI: 23/30 oracle fixtures pass raco make, 22/30 emission parity with Racket compiler

Macro expander

  • Expansion provenance: error messages now include macro name, depth, and full expansion chain (both Racket and self-hosted expanders)
  • beagle-expand --trace: prints each expansion step to stderr (input/output per step, depth-indented for nested macros)

Security hardening

  • Daemon port/pid files moved to $XDG_RUNTIME_DIR (fallback /var/tmp)
  • Repair command restricted to paths within watched directory
  • 0600 permissions on port/pid files

Nix validation

  • Flake-input HM programs (e.g., walker) no longer produce false-positive "unknown HM option" errors
  • 212 files, 0 false positives on real NixOS config

JS target

  • js-template splice sites now type-checked: collection types raise E016 diagnostic
  • js/quote confirmed complete (3 splice kinds: ~expr, ~@stmts, ~%json)

Housekeeping

  • 1278 → 1296 tests
  • Cancelled 18 zero-value todo items with documented reasoning
  • All 5 todo workstreams closed