v0.6.5The TokenEconomics benchmark now reports the composite compactness score it always computed instead of raw token count alone — an honest measurement fix that lifts the category to 1.42× and the overall advantage to 1.32×, with the v0.7 gate recalibrated against the corrected metric.See what's new

Changelog

All notable changes to Calor, organized by release.


[Unreleased]

[0.6.5] - 2026-06-30

Fixed

  • The TokenEconomics benchmark metric now reports the composite it computes (the discarded-composite bug, #668). The calculator computed a composite advantage — the geometric mean of the token, character, and line ratios — and then discarded it, reporting the raw token-count ratio only despite the metric being named CompositeTokenEconomics. The category now reports the composite. The metric is deterministic, so its 95% CI equals its point estimate. This lifts the headline numbers — TokenEconomics from 1.11× (token-only) to 1.42× (composite) and overall from 1.28× to 1.32× — purely as a measurement correction, not a Calor improvement. The honest caveat stands: Calor still uses more raw tokens than C# on small programs (the §-sigil premium), but is more compact once character and line counts are included.

Changed

  • The deferred v0.7 TokenEconomics gate was recalibrated against the corrected metric. The old token-only target (lower-95%-CI > 1.122) is superseded by a composite gate of ≥ 1.40×, anchored to the measured 1.42× baseline. The public Token Economics metric page was corrected — it had previously and incorrectly reported the category as "C# wins".

[0.6.4] - 2026-06-16

Fixed

  • Parser: an elided §C call no longer steals the parent block's terminating Dedent. An elided §C{X} / §C{X} §A arg call that was the last statement of a function body followed by a sibling declaration (e.g. another §F) previously failed with Calor0100: Expected statement but found Func. Discovered while modernizing the TypeSystem sample for this release.

Added

  • 7 new TokenEconomics benchmark fixtures broadening corpus coverage of v0.6.3 expression-context call elision and v0.6 bind-inference (parser, formatter, delegation, and aggregation shapes), plus two neutral controls. Honest note: the TokenEconomics category scores raw token count only, and Calor's §-sigils cost more tokens than the equivalent C# on small programs, so these representative fixtures left the category at 1.11x — the v0.7 TokenEconomics gate remains open.

Internal

  • The TypeSystem sample and the 04_option_result E2E scenario were modernized to canonical v0.6.3 syntax — a legacy triply-nested §OK{§ARR…} array form (an artifact of mass C# → Calor conversion) was replaced with the canonical §OK value / §ERR "msg" short form, fixing the generated C# from Result.Ok<object, string>(new object[]{…}) to Result.Ok<int, string>(100).

Documentation

  • v0.6 bind-inference RFC §7 — the Calor0250 open question is resolved. The diagnostic has always shipped as a hard error; the "promote warning→error in v0.7?" bullet was a stale carry-over and is now marked resolved, backed by a permanent corpus-clean test pinning zero bind-inference firings across the sample and benchmark corpus.

[0.6.3] - 2026-06-13

Added

  • calor fix --elide-call-closers bulk migrator. New calor fix subcommand that rewrites existing .calr source trees to the v0.6.x call-closer-elided form: zero-arg §C{X} §/C§C{X} and same-line one-arg §C{X} §A arg §/C§C{X} arg. Multi-line forms, named-arg (§A[name] x), multi-arg, and ref/out/in arg modifiers are left untouched. Includes a canonical-emit safety net (re-parse the migrated source, re-emit both ASTs, drop the file's edits on any divergence) and --revert --log <file> for byte-for-byte round-trip. Mutually exclusive with --drop-structural-ids and --compact-ids; supports --dry-run.
  • LSP quick-fixes for strict bind-inference diagnostics Calor0251/Calor0252/Calor0253. Each diagnostic now ships a SuggestedFix that inserts the recommended :type annotation right before the closing } of the bind's attribute block. Templates: :Option<object> (for §NN), :object? (for null), :Vec<object> / :Map<object, object> / etc. arity-aware per the matched generic factory, and :f64 (for ambiguous numeric).
  • Calor.LanguageServer.DocumentState.Reanalyze now runs BindValidationPass so strict-bind diagnostics (and their quick-fixes) surface in editors; previously the LSP only ran the lexer/parser/binder and these diagnostics were CLI-only.

Changed

  • Expression-context §C calls now elide §/C by default for one-argument forms. CalorEmitter.Visit(CallExpressionNode) extends the v0.6.1 zero-arg elision and the v0.6.2 stmt-context one-arg elision to expression context: §C{target} arg (no §A, no §/C) when the argument is unnamed, the rendered first token is in the StartsWithExpressionStarter whitelist, and we are not inside an inline-sibling context.
  • Strict bind-inference diagnostics Calor0251/Calor0252/Calor0253 are now default-on. These flag bindings that cannot infer a concrete type without an explicit :type annotation: untyped §NN/null, well-known generic factory calls (Vec.empty, List.empty, etc.), and binary ops mixing integer and floating-point literals. Opt out for one release with --no-strict-bind-inference (CLI) or CompilationOptions.StrictBindInference = false (SDK).

Fixed

  • Parser: Calor0150 no longer fires across sibling-statement boundaries. When the next expression-start token after a one-arg elided call is on a different line, it is a sibling statement, not an ambiguous second positional arg.
  • Emitter: §LAM body, §WITH target, and §LIST/§HSET element emit sites now use AcceptInInlineSibling. These same-line sibling positions previously used raw node.X.Accept(this), which could silently corrupt the AST after the one-arg expression-context elision landed.

[0.6.2] - 2026-06-10

Added

  • Elision-aware TokenEconomics benchmark fixtures (VoidSequence, LogPipeline, PairLogger) exercise the new statement-context §/C elision path. Corpus is now 210 programs.

Changed

  • Statement-context §C calls now elide §/C by default (when safe). CalorEmitter.Visit(CallStatementNode) rewrites zero-argument calls as §C{target} and one-argument unnamed calls (with safe-prefix arguments) as §C{target} arg. Mirrors the v0.6.1 behavior for expression-context calls. Elision is gated by UseImplicitCallCloser and suppressed in inline-sibling contexts.

Removed

  • calor diagnose CLI command removed. Deprecated in v0.5.x with a stated removal target of v0.6.0; v0.6.2 completes that deprecation. For machine-readable diagnostics use the calor_check MCP tool with action: "diagnose" (or calor_compile with automatic fix application).

Fixed

  • Contract verifier: class methods, user-defined types, and visibility preservation. ContractSimplificationPass now preserves Visibility so the verifier reaches §MT members. ContractVerificationPass walks class-method bodies. Z3 translator gained support for user-defined types and dot-path field access (a.b.c).

[0.6.1] - 2026-06-09

Changed

  • ConversionContext.UseImplicitCallCloser now defaults to true (was false in v0.6.0). The C# → Calor converter now elides §/C for zero-argument calls by default, producing more idiomatic Calor output. The opt-out (UseImplicitCallCloser = false) is preserved.

Compatibility

  • Calor source emitted by v0.6.1 may not parse on v0.6.0 or earlier calor toolchains. The new default emits more zero-arg §C calls without explicit §/C. Sources that exercise the newly-fixed parser layouts (zero-arg §C immediately before Dedent, or followed by a same-column sibling opener) will mis-parse on v0.6.0. To produce v0.6.0-compatible output from v0.6.1, use any of:

    • CLI single-file: calor convert --explicit-call-closers <input.cs>
    • CLI project migration: calor migrate --explicit-call-closers <path>
    • MCP calor_convert / calor_migrate: "explicitCallClosers": true
    • SDK: new ConversionOptions { UseImplicitCallCloser = false }

    Round-trip (C# → Calor → C#) remains semantic/structural; the intermediate .calr is intentionally not byte-identical to v0.6.0 converter output unless the opt-out is used.

Fixed

  • Parser: §C standard form no longer swallows trailing Dedent. Previously, a zero-arg §C immediately followed by the end of its enclosing block could corrupt the structural parse of the surrounding method or §IF body. Now correctly distinguished from indent-aware block terminators.
  • Parser: §C no longer absorbs a same-column sibling structural opener on the next line. A sibling §IF / §MATCH / §NEW on the next line at the same indent is no longer silently absorbed as the call's inline argument. Both ParseCallExpression and ParseCallStatement now gate the inline-arg form on the candidate argument starting on the same source line as §C{target}. See Calls reference rule 6.
  • Emitter: zero-arg §C inside an inline-sibling context now keeps explicit §/C. With the new default, naively eliding §/C from a zero-arg call emitted inside another call's §A chain or inside an array/§ARR/§ROW/§SALLOC/§IDX2D initializer caused silent AST corruption (e.g. M(A(), 2) round-tripping as M(A(2))). The emitter now tracks an inline-sibling-context counter; zero-arg §/C elision is suppressed whenever a call is emitted inside such a context. Top-level / leaf-position calls (binding initializers, return values) still elide as before.
  • Parser: §C statement form now supports zero-arg implicit close before sibling statements. Previously a zero-arg §C{target} followed by a sibling statement on the next line at the same indent reported Calor0100. The statement-form parser now recognizes the zero-arg implicit close when the current token is not §A, §/C, Dedent, or Eof.

[0.6.0] - 2026-06-04

Added

  • §C call-closer elision (RFC v0.6-call-closer-elision). Expression-context §C{target} calls may now omit the trailing §/C in two cases: (1) zero arguments — §B{n} §C{items.Count} is equivalent to §B{n} §C{items.Count} §/C; (2) exactly one inline argument (no §A) — §B{y} §C{Math.Abs} x is equivalent to §B{y} §C{Math.Abs} §A x §/C. The parser disambiguates nested elided calls (e.g., §C{Foo.bar} §C{Baz.qux} yFoo.bar(Baz.qux(y))) by counting consecutive §/C closers relative to enclosing §A depth. Trailing member access on inline arguments binds to the argument (§C{Identity} obj?.LengthIdentity(obj?.Length)); trailing member access on zero-arg calls binds to the call result (§C{Maybe}?.LengthMaybe()?.Length). The explicit §A / §/C form continues to parse unchanged. See Calls reference.
  • Calor0150 AmbiguousCallContinuation — New diagnostic in the reserved Calor0150-0159 range. Fires when an elided §C already consumed one inline argument and is followed by either a second expression-start token or a §A token. The fix message recommends the explicit form.
  • ConversionContext.UseImplicitCallCloser emitter flag. Opt-in property on Migration/ConversionContext. When true, CalorEmitter elides §/C for zero-argument calls. Default false for backward compatibility. One-argument elision is intentionally deferred to v0.6.1 pending context-aware tracking inside Lisp argument lists.
  • §B bind-inference formalization (RFC v0.6-bind-inference-formalization). The four supported §B forms — §B{name} (requires initializer), §B{name} initializer (inferred), §B{name:type} (explicit, no initializer), §B{name:type} initializer (explicit wins) — and the binder's shallow inference rule are now documented at Bindings.
  • Calor0250 BindRequiresTypeOrInitializer§B{name} with no :type and no initializer is now a hard error reported through BindValidationPass. Replaces the pre-v0.6 silent fallback that bound x as INT and produced wrong-typed C# with no diagnostic.
  • Calor0251 / Calor0252 / Calor0253 strict-mode bind-inference diagnostics (opt-in via --strict-bind-inference). Three new diagnostics in the Calor0250-0259 range, each silenced by an explicit :type annotation, scheduled to become default-on in v0.7:
    • Calor0251 BindCannotInferNullLiteral — fires on §B{x} §NN or §B{x} null.
    • Calor0252 BindCannotInferGenericReturn — fires on §B{x} §C{Vec.empty} §/C and other well-known generic factory targets.
    • Calor0253 BindAmbiguousNumeric — fires on §B{x} (+ INT:0 FLOAT:0.0) — a binary op mixing integer and floating-point literal operands.
  • Two new syntax-reference pagesBindings and Calls, with full disambiguation tables, examples, and diagnostic catalogues.
  • v6 compact stable identifiers (default). IdGenerator.Generate(IdKind) now mints 12-char Crockford-lowercase compact IDs (f_7k9m2npqrstv). The legacy 26-char Crockford-uppercase ULID form (f_01J5X7K9M2NPQRSTABWXYZ12) remains accepted by the parser, validator, and migration tooling. Saves ~9.7 tokens per ID in agent-facing serialisations.
  • calor fix --compact-ids <root> — bulk repo-wide migrator from legacy ULID payloads to v6 compact payloads. Two-pass design with deterministic compact derivation, within-file and cross-file collision detection, and byte-exact revert via --revert --log <file>. Idempotent on already-migrated source.
  • IdValidator accepts both compact and legacy ULID forms. New predicates IsCompactId, IsLegacyUlidId, and IsCanonicalId. New Calor0821 LegacyUlidPayload diagnostic code reserved for the opt-in lint that flags ULID payloads.

Changed

  • Migration/CalorEmitter.Visit(CallExpressionNode) — Zero-argument calls in expression context now conditionally elide §/C when ConversionContext.UseImplicitCallCloser is true. Multi-argument and one-argument paths are unchanged in v0.6.0 (zero-arg-only elision) pending the v0.6.1 context-aware enablement.

Fixed

  • Binder no longer silently defaults §B{x} to INT. A §B{name} with neither a :type annotation nor an initializer was silently treated as INT by the pre-v0.6 binder, producing wrong-typed C# with no diagnostic. v0.6 surfaces this as Calor0250.

[0.5.1] - 2026-06-03

Added

  • Indent-only Calor source — Calor source is now indent-delimited end to end. The parser, migration emitter (Migration/CalorEmitter), and calor format all produce/accept indent form as canonical. Closer-form structural tags (§/F{id}, §/M{id}, §/I{id}, §/L{id}, …) have been removed from the emitter and rejected by the strict CLI compile path. The 09_codegen_bugfixes self-test fixture was migrated alongside; round-trip emission stays byte-identical.
  • calor --input … --output … --allow-legacy-closers — Escape hatch on the CLI compile path for users mid-migration. Default is strict (legacy closer form produces Calor0830); calor format rewrites a file in canonical indent form.
  • CompilationOptions.RejectLegacyClosers — Opt-in compilation flag; the CLI sets it to true by default, other API surfaces (MSBuild <CompileCalor>, MCP tools, LSP) keep the lax default for now.
  • calor fix --drop-structural-ids <root> — Bulk, mechanical, byte-reversible source rewriter that strips {id} from structural closing tags. Records every removal in a migration.log.json and supports --revert --log <file> to restore the original bytes exactly. See docs/cli/fix.md.
  • Calor0820 LegacyStructuralId / Calor0830 LegacyCloserForm — Opt-in lints flagging legacy IDs on closing tags and legacy closer-form structural tags, with fix patches pointing at calor fix and calor format respectively.
  • Optional closing-tag IDs everywhere — Structural closing tags (§/M, §/F, §/AF, §/L, §/I, §/TR, §/CL, §/IN, §/PR, §/MT) may omit the trailing {id} block. Both forms continue to parse; the parser pairs closers with their nearest matching opener by structural nesting.
  • Phase 5 — Product docs migrated to indent-only syntax. README, docs/, and website/content/ now teach indent-form Calor as the canonical surface; closer-form is mentioned only in legacy callouts that point at calor fix for migration.

Changed

  • Lint no longer flags leading indentation or blank lines. With indent form now canonical, the two formatting lint rules introduced for the closer-form "agent-optimized" surface (leading whitespace, blank lines) have been removed from both calor lint and the check MCP tool. Indentation and blank lines are first-class.
  • Benchmark metric calculators score indent form, not closer tags. The four heuristic calculators under tests/Calor.Evaluation/Metrics/ used to award credit for the presence of paired structural closing tags as a proxy for "scope boundaries are explicit". With indent form canonical, the dedent IS the scope-boundary signal, so those bonuses now reward indented body lines per structural opener. Net score magnitude is preserved.

Fixed

  • Migration/CalorEmitter.Visit(CatchClauseNode) — Catch filters now emit §WHEN (matching the token form the parser produces and the §WHEN already emitted by match-arm guards). Previously emitted a bare lowercase WHEN that did not round-trip cleanly.
  • MatchExpressionNode as a §B binding initializer — match expressions used as binding initializers now emit indented §K arms relative to the enclosing block. Previously hardcoded 2/4-space indents could trigger Calor0099 dedent errors when the binding lived inside a deeper function body.

[0.5.0] - 2026-04-22

Added

  • Roslyn 5.3.0 upgrade — Migration pipeline now uses Roslyn 5.3.0 (C# 14 support), enabling conversion of modern C# files using lambda parameter modifiers, out in lambda parameters, and other C# 13/14 features.
  • LanguageVersion.Preview parse option — The C# parser now accepts the broadest possible C# syntax.

Changed

  • Non-exhaustive match on Option<T> / Result<T,E> is now an error — exhaustive match on known sum types is mandatory syntax (Calor0500).
  • Microsoft.CodeAnalysis.CSharp upgraded from 4.8.0 to 5.3.0 across all projects.

[0.4.9] - 2026-04-21

Added

  • Cross-assembly IL analysis — Opt-in compile-time analysis that traces method calls through referenced .NET assemblies to discover effects not covered by manifests. Enabled via <CalorEnableILAnalysis>true</CalorEnableILAnalysis>. Handles async state machines, iterator methods, delegate creation, and virtual dispatch. See Cross-Assembly IL Analysis guide.
  • Cross-module effect propagation — Multi-file Calor projects now verify effect contracts at file boundaries. A caller that invokes a public function declared in another module must declare that callee's effects. Violations produce Calor0410; public functions without §E produce the new Calor0417 warning.
  • Multi-file CLIcalor --input a.calr --input b.calr compiles multiple files and runs the cross-module effect pass across them. Single-file invocations are unchanged.
  • MSBuild cross-module enforcement — The CompileCalor task runs the cross-module pass over every .calr in the project. Works correctly on warm builds via persistent per-module effect summaries in the build cache.
  • Effect summary cache (schema v2.0) — Each module's public function declarations, internal names, and call-site listings are cached alongside the content hash, so incremental builds retain complete cross-module coverage without re-parsing skipped files.
  • Cross-Module Effect Propagation guide — Contract model, bare-name vs. qualified calls, incremental build semantics, CLI + MSBuild integration.

Changed

  • --input option in the calor CLI now accepts multiple values.
  • Build state cache format bumped from 1.0 to 2.0 — existing caches auto-invalidate on first build after upgrade.
  • Options hash includes EffectKind enum shape — future enum changes automatically invalidate caches, preventing stale summaries from silently dropping effects.

[0.4.8] - 2026-04-20

Added

  • Incremental compilationCompileCalor MSBuild task now owns all incremental logic with a two-level cache gate: (mtime, size) stat check then SHA256 content hash. Global invalidation on compiler DLL, options, effect manifest, or output directory changes.
  • calor effects suggest CLI command — Analyzes Calor source files and generates a .calor-effects.suggested.json manifest template for unresolved external calls. Supports --json for agent consumption, --merge for additive updates to existing manifests.
  • Shared ExternalCallCollector — Extracted from InteropEffectCoverageCalculator, extended to walk class methods and constructors. Resolves variable types via §NEW initializer scanning.
  • Incremental build benchmark — Measures cold, warm (no changes), and warm (1 file changed) build times
  • Effect manifests .NET ecosystem guide — ~170 covered types, resolution mechanics, custom manifest authoring, CLI tools

[0.4.7] - 2026-04-20

Added

  • Static analysis for class members — The --analyze flag now examines methods, constructors, property accessors, operators, indexers, and event accessors (previously only top-level functions were analyzed)
  • Verification-gated reporting--analyze only reports proven findings by default (Z3-confirmed or constant analysis); use --all-findings for lower-confidence results
  • Taint hop-count tracking — Taint analysis tracks propagation steps; single-hop parameter-to-sink flows filtered by default to reduce false positives
  • Bug pattern detection in class members — Division by zero, null dereference, integer overflow, off-by-one, path traversal, command injection, and SQL injection detection now covers all class member bodies
  • Arity-aware overload resolution — Correct overload resolved by argument count, preventing wrong return types from flowing into Z3
  • Constructor initializer binding: base()/: this() arguments visible to bug pattern checkers
  • 33 new unit tests for class member binding, scope, overloads, dataflow, and end-to-end analysis
  • New --all-findings CLI flag for showing all analysis findings including inconclusive results
  • New static analysis documentation page

Fixed

  • False positive elimination — Unhandled expression types return opaque expressions instead of literal zero, eliminating false division-by-zero reports
  • this.field shadowingthis.field resolves from class scope, not method scope
  • Throw-to-catch CFG edges — Throw statements inside try blocks now flow to catch blocks
  • Assignment dataflowx = 1 no longer reports x as used before write

Validated

  • 47 open-source projects scanned — 23 verified findings, ~90% true positive rate
  • Real findings: ILSpy null dereferences, FluentFTP path traversal, ASP.NET Core path traversal

[0.4.6] - 2026-04-18

Added

  • Effect system: .NET framework manifests — Tier B effect manifests for 30+ common .NET framework interfaces (ILogger, DbContext, HttpClient, ControllerBase, etc.)
  • Effect system: ecosystem library manifests — Manifests for Serilog, Newtonsoft.Json, Dapper, MediatR, AutoMapper, FluentValidation, Polly
  • Effect system: BCL manifest expansion — New manifests for System.Text.Json, Regex, Concurrent collections, Crypto types
  • Effect system: variable type resolution — Enforcement pass resolves instance method calls via initializer tracking
  • 95 new enforcement tests (210 total)

Fixed

  • Effect system: unified resolver — Consolidated three parallel effect systems into a single manifest-based resolver
  • Parser: compound effect codes — Fixed silent mis-parsing of chained compound codes

[0.4.5] - 2026-04-16

Fixed

  • Restrict expression-only match case to lambda-only patterns
  • PLIST nested call depth, call-as-pattern, OPTION compaction

Earlier Releases

See the full changelog on GitHub for versions prior to 0.4.5.