Skip to content

Changelog

The canonical changelog lives at the repository root. It is included below for convenience when browsing the docs site.

Changelog

All notable changes to the Spikard project are documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

[0.16.0-rc.3] - 2026-06-21

Fixed

  • Publish: Hex release assets. The upload-release-assets glob in publish.yaml used spikard_nif-*.tar.gz, but the Elixir NIF build emits libspikard_nif-*.tar.gz (with the lib prefix), so zero NIF tarballs were attached to the release and the Publish Hex job failed downloading them. Corrected the glob.

Changed

  • Regenerated all language bindings against alef v0.25.58 (from 0.25.50) and pinned the binding generator to that release tag. This carries the latest upstream codegen fixes — backend binding-generation hardening, Go capsule host-import aliasing, and preserving backend-aware parameter types in long function signatures — across every binding.
  • Upgraded all dependencies to their latest versions (task upgrade): Rust crates (cargo upgrade --incompatible), per-language ecosystem dependencies (alef update --latest), and Zig module dependencies, with all lock files refreshed.
  • Excluded the service_http_additions.ts HTTP-extension fragment — a class-body splice fragment, not a valid standalone TS module — from the node oxfmt format command in alef.toml, so task alef:format no longer fails parsing it.
  • Dropped Intel macOS (x86_64-apple-darwin). Removed the Intel-mac Node binding target (@spikard/node-darwin-x64) via alef.toml exclude_platforms = ["darwin-x64"], and removed the Intel-mac build-node matrix entry and the macos-15-intel Homebrew bottle from publish.yaml. The publish pipeline now ships Apple Silicon (arm64) macOS only.

[0.16.0-rc.2] - 2026-06-20

Fixed

  • Regenerated all language bindings against alef v0.25.50 and pinned the binding generator to that release. This carries the upstream codegen fix for serde-default markers in generated struct Default impls: fields annotated with #[serde(default = "path")] were emitted verbatim as field: serde(default = "path"), which is invalid Rust and broke the Ruby/magnus binding build (packages/ruby/ext/spikard_rb/src/lib.rs). The generator now falls through to the field's type-zero value (e.g. String::new()) when the default is an unresolved serde marker.

Changed

  • Reserved the four previously-unpublished npm platform packages (@spikard/node-linux-arm64-gnu, @spikard/node-linux-x64-musl, @spikard/node-linux-arm64-musl, @spikard/node-win32-arm64-msvc) on the registry so trusted publishing can be configured; CI publishes the real per-version artifacts from rc.2 onward.

[0.16.0-rc.1] - 2026-06-18

Changed

  • Bumped to 0.16.0-rc.1 and regenerated all language bindings against alef v0.25.41 (from v0.25.21). The regeneration carries several upstream codegen fixes into the bindings: PyO3 None-guards the coercion comprehension for optional Vec<enum> fields (no more TypeError when the value is None) and drops a redundant visitor # type: ignore; PHP DTO instance methods returning Vec/Map now emit an @return array<T> PHPDoc (PHPStan max); Swift generated noop shims drop the explicit -> () and no longer emit duplicate definitions for own-block opaque types; and the curated Java PMD ruleset suppresses rules inherent to generated DTOs (AvoidUsingHardCodedIP, ArrayIsStoredDirectly, MethodReturnsInternalArray, TooManyFields, CommentRequired, and the rest).

Fixed

  • Pointed the pmd pre-commit hook at the curated packages/java/pmd-ruleset.xml (via -R) instead of PMD's built-in quickstart.xml, so the generated Java bindings pass PMD with the generated-DTO-inherent rules excluded.

[0.15.6-rc.22] - 2026-06-11

Fixed

  • Regenerated against alef v0.24.8, which bundles fixes for the CI E2E reds that have been stable since rc.10: Java App.config(host, port) now generated and called from the e2e harness (fixes "Harness did not become reachable at 127.0.0.1:8000"); Swift App.config(host:port:) bridge emitted and invoked before app.run() (fixes nil URLResponse); Go cgo StartBackground plumbs host/port through Config(...) and polls socket-bind before returning (fixes "connection refused"); Dart FRB post-build rewriter is scope-aware and no longer corrupts inherited BaseHandler.executeSync calls (~62 generated methods); Ruby e2e tests now emit _req.body = ... for multipart fixtures; NAPI base registration accepts &JsRouteBuilder wrapper and unwraps to the core type so napi-rs can synthesize FromNapiValue, with TS wrappers calling positional this._app.route(builder, fn) instead of [builder].
  • Elixir publish workflow now uploads NIF artifacts with the correct glob spikard_nif-*.tar.gz (was libspikard_nif-*.tar.gz). The build-elixir-natives@v1 action emits the artifact without the lib prefix, so the previous glob never matched, leaving the GitHub release with no NIF assets. rustler_precompiled then 404'd on every install, surfacing as (ArgumentError) could not load module Spikard.App due to reason :nofile at runtime even though the Hex publish job itself succeeded.

[0.15.6-rc.9] - 2026-05-27

Fixed

  • Resolved a tungstenite version skew that broke every FFI and wheel build (CI Bindings, CI E2E, Deploy Documentation, and all Publish Release build jobs). spikard-http pinned tungstenite = "=0.28.0" directly while axum/axum-test pull tungstenite 0.29 transitively, so testing.rs mixed two incompatible CloseFrame types (error[E0308]: expected CloseFrame, found tungstenite::protocol::CloseFrame). The direct dependency is now tungstenite = "0.29", collapsing the graph to a single version. The break surfaced only in CI because the root Cargo.lock is not committed, so CI re-resolves to the newest compatible transitive releases.
  • task version:set now runs alef scaffold as part of the sync step, so a version bump re-emits the napi platform optionalDependencies in crates/spikard-node/package.json at the new version. Previously they lagged the prior release, which (combined with the lockfile) failed CI's pnpm install.

Changed

  • CI Node installs now honor frozen-lockfile: false via kreuzberg-dev/actions v1.8.5: the napi platform optionalDependencies pin the under-release version, which is not yet published and therefore cannot be recorded in the lockfile.

[0.15.6-rc.8] - 2026-05-27

Fixed

  • The generated Go packages/go/embed_ffi.go now declares package spikard (matching binding.go) instead of an erroneous hardcoded package tspack, fixed upstream in alef v0.19.23. The file is //go:build ignore so go build was unaffected, but the foreign package name was incorrect for vendoring tooling.

Changed

  • Regenerated all bindings, e2e, scaffolding, and docs against alef v0.19.23 (from 0.19.21), bumping the alef.toml pin and the alef pre-commit hook. Generation now runs with alef's own post-generation formatters so committed output matches alef verify hashes.

[0.15.6-rc.7] - 2026-05-26

Fixed

  • CI E2E is now fully green across all 14 language suites. The Kotlin e2e Gradle test worker failed to fork (Cannot abort process 'Gradle Test Executor N' because it is not in started or detached state) because the generated build.gradle.kts set the test workingDir to an absent test_documents/ directory and passed no jvmArgs; fixed in alef v0.19.21 by guarding workingDir on existence and enabling --enable-preview --enable-native-access=ALL-UNNAMED on the Panama test fork (Kotlin e2e runs on JDK 25). The Dart and Zig e2e teardowns no longer run pkill -f 'mock-server …', which matched and SIGTERM-ed their own parent sh -c on Linux (the command's argv contains the pattern), failing green runs.

Changed

  • Regenerated all bindings, e2e, scaffolding, and docs against alef v0.19.21 (from 0.19.18), bumping the alef.toml pin and the .pre-commit-config.yaml alef hook. The committed Swift RustBridgeC.h is now a small placeholder that the cargo build -p spikard-swift step populates, rather than a checked-in build artifact.

[0.15.6-rc.6] - 2026-05-26

Changed

  • Regenerated all bindings, e2e, scaffolding, and docs against alef v0.19.18 (from 0.19.14), bumping the alef.toml pin and the .pre-commit-config.yaml alef hook to 0.19.18. 0.19.15–0.19.18 land codegen fixes: napi omits a redundant ..Default::default() in single-field streaming requests, wasm filters binding_excluded fields out of input-DTO codegen, and the extractor auto-excludes fields with trait-object types.

Fixed

  • CI E2E: the fixture mock-server (and each language's native binding) is now built inside the per-language test before-hooks instead of relying on a stale runner binary, fixing connection refused against localhost across go/node/ruby/elixir/java/dart/wasm.
  • CI E2E: bumped the Kotlin e2e Gradle to 9.5.1, and made the Zig e2e command self-diagnosing — it echoes the resolved MOCK_SERVER_URL and fails fast with the server log if the mock-server never starts.

[0.15.6-rc.4] - 2026-05-25

Fixed

  • Ruby gem and Elixir Hex source builds now install standalone (issue #87, "Ruby gem does not install"). The published gem/Hex native Cargo.toml previously shipped both path and version deps; cargo prefers the absent path, so the native extension failed to build on consumers. alef.toml no longer overrides vendor_mode for Ruby/Elixir, so both use alef 0.19.9's registry default — alef publish prepare rewrites workspace path-deps to crates.io version-deps for every source-installed language (Ruby, Elixir, Python sdist, PHP).

Changed

  • Regenerated all bindings, e2e, scaffolding, and docs against alef v0.19.9, bumping the alef.toml pin and the .pre-commit-config.yaml alef hook to 0.19.9. 0.19.9 makes the version pin self-syncing (alef generate/all/scaffold write [workspace] alef_version and the pre-commit hook rev), defaults the Python update/upgrade commands to --no-install-project, and lands the registry test-app codegen fixes below.
  • Added test_apps/ — a registry-mode verification harness (one app per language channel + a Homebrew app) generated by alef test-apps generate. alef test-apps run installs each published package from its registry and exercises it against the fixture mock-server, verifying a release end-to-end. The runner builds and shares one mock-server across all apps via MOCK_SERVER_URL; harnesses honor a pre-set URL instead of self-spawning. Registry coordinates corrected: the Kotlin artifact is dev.spikard:spikard-kotlin, the Zig/Dart published-package native-lib loaders resolve their assets by release URL / package path.
  • publish.yaml: pinned all GitHub Actions to major versions (gau --pin-style major), persisted via .gh-actions-updater.toml.

[0.15.6-rc.3] - 2026-05-25

Changed

  • Regenerated all bindings, e2e, and scaffolding against alef v0.19.6, bumping the alef.toml pin and the .pre-commit-config.yaml alef hook to 0.19.6. 0.19.6 lands a batch of codegen/sync fixes so the generated tree is both formatter-clean against the pinned hooks and fully version-consistent — making several previously hand-patched items native: version requirements on crates/spikard-ffi's internal path-deps (so cargo publish accepts the crate), the Elixir Hex mix.exs files list shipping ../../crates/spikard-elixir/src (NIF lib.rs + .ex modules), de-duplicated Python __init__.py re-exports, pyproject-fmt-canonical pyproject.toml, the PSR-12 blank line after <?php, and sync-versions now also bumping the Kotlin build.gradle.kts version, the Elixir NIF Cargo.lock, and the docs/reference/api-*.md version badges.

Fixed

  • publish.yaml: the Hex publish now generates the RustlerPrecompiled checksum file via kreuzberg-dev/actions/generate-elixir-checksums@v1 before build-elixir-hex@v1. mix.exs lists checksum-*.exs in its package files, so mix hex.build failed at rc.2 with Missing files: checksum-*.exs. The step downloads the per-platform NIF tarballs from the GitHub Release, so publish-hex is now gated on a real tag release (is_tag == 'true') and on upload-release-assets succeeding.
  • publish.yaml: widened the upload-release-assets NIF glob from libspikard_nif-*.so.tar.gz to libspikard_nif-*.tar.gz so the macOS .dylib and Windows .dll tarballs are attached to the Release (previously only the four Linux .so assets were uploaded, which would have starved the checksum step).
  • publish.yaml: removed the self-defeating swift_exists/zig_exists registry-existence gate on publish-swift/publish-zig. Swift and Zig have no central registry, so check-registry falls back to a GitHub release-tag lookup — which the prepare job's draft Release always satisfies, so both jobs always skipped. They now gate only on the release being requested and the build succeeding, and rely on each action's own dry-run handling for idempotency.

[0.15.6-rc.2] - 2026-05-24

Fixed

  • crates/spikard-ffi/Cargo.toml: added version requirements to the internal spikard-core, spikard-graphql, and spikard-http deps (they were path-only). cargo publish rejects path-only deps ("all dependencies must have a version requirement specified when publishing"), which broke crates.io publishing of spikard-ffi — a regression latent since the alef 0.18.1 regen, first hit when 0.15.6-rc.1 became the first release to publish crates since 0.15.4. The other six crates published at rc.1 before spikard-ffi failed; rc.2 republishes the full set.
  • crates/spikard-py/src/pyproject.toml: bumped the [project] version to the PEP 440-normalized 0.15.6rc2 to match the generated packages/python/pyproject.toml. The alef Python source template's literal version is not bumped by alef sync-versions and must use the normalized (not canonical -rc.N) form, so alef validate versions failed on the real (is_tag=true) release path that runs the check.
  • Commit the Elixir NIF crate's packages/elixir/native/spikard_nif/Cargo.lock (added a .gitignore exception, matching the Ruby ext). mix.exs lists it in the Hex package files, so mix hex.build failed when it was absent in CI.

Changed

  • Regenerated all bindings/e2e against alef v0.19.4: the Dart and Zig e2e harnesses now spawn the mock server (fixing their localhost:8080 connection-refused failures), and alef validate versions PEP 440-normalizes both sides of the Python pyproject check. Bumped the .pre-commit-config.yaml alef hook and alef.toml pin to 0.19.4.
  • publish.yaml: the Hex publish now runs kreuzberg-dev/actions/build-elixir-hex@v1 before publish-hex, rewriting the NIF crate's workspace path-deps to registry version-deps so the published Hex source tarball compiles standalone on consumers without a precompiled NIF (the Hex analog of the Python sdist / Ruby gem dep rewrite). Skipped on dry-runs.
  • publish.yaml: Homebrew now builds real bottles via homebrew-build-bottles@v1 + homebrew-merge-bottles@v1 (macOS arm64_sequoia/sequoia + Linux x86_64_linux/arm64_linux, with install-homebrew-linux@v1 on Linux), replacing the synthetic build-homebrew-bottle@v1. The formula source URL/SHA is updated by scripts/publish/update-homebrew-formula.sh, then bottles are built (brew install --build-bottle) and the bottle DSL is merged into the tap formula. Matches the sibling repos (html-to-markdown, tree-sitter-language-pack, kreuzberg).

Notes

  • 0.15.6-rc.1 published partially (PyPI, npm, NuGet, Kotlin, Maven, Packagist, and six of seven crates) before spikard-ffi failed; rc.2 is the first complete 0.15.6 release candidate.

[0.15.6-rc.1] - 2026-05-24

Fixed

  • Regenerated all polyglot bindings against alef v0.19.2. The Swift packages/swift/rust/Cargo.toml now emits version requirements on the internal spikard-{core,graphql,http} path-deps, so the bridge crate resolves when the Swift package is built from a tag; the Swift bridge surface (RustBridgeC.h / Spikard.swift) expands to match the current core API.

Added

  • Alef-scaffolded .gitattributes marking generated trees (crates/spikard-{ffi,node,php,py,wasm}, packages/*, e2e/) as linguist-generated so GitHub language stats and diffs collapse them.

Changed

  • publish.yaml: source-build jobs (Python sdist, Ruby gem, PHP extension, Elixir NIF) now skip the --require-registry path-dep → registry-dep rewrite on dry-runs and build via workspace path-deps instead. The rewrite resolves the in-flight version against crates.io, which doesn't exist until a real release publishes the core crates, so dry-runs previously failed at cargo generate-lockfile. Real releases are unaffected (the rewrite still runs).

[0.15.5] - 2026-05-21

Changed

  • Split pub.dev publish into a dedicated publish-pubdev.yaml workflow triggered by push: tags: v*. pub.dev OIDC trusted publishing rejects tokens from release events; the new workflow produces an accepted token.

[0.15.4] - 2026-05-21

Fixed

  • crates/spikard-ffi/Cargo.toml: Restored workspace inheritance (version.workspace = true, license.workspace = true, repository.workspace = true, and spikard{,-core,-graphql,-http} = { workspace = true }). The alef v0.17.17 regen in v0.15.3 reverted these back to path-only deps and literal package metadata, which broke cargo publish for spikard-ffi (all dependencies must have a version requirement specified when publishing. dependency \spikard` does not specify a version). 6 of 7 crates published at v0.15.3; this release republishes all seven (existing 0.15.3 versions are immutable, so the registry now carries both 0.15.3 and 0.15.4 for the six that already shipped). alef itself has no per-crate dependency-style override yet, but on this manifest alef leaves an existingworkspace = true` declaration alone — the v0.15.3 regression was specifically because the previous regen rewrote a literal-versioned manifest, not because alef actively prefers path-only.

[0.15.3] - 2026-05-21

Fixed

  • Regenerated all polyglot bindings against alef v0.17.17. The Elixir mix.exs files: list is now correctly emitted as ~w(.formatter.exs mix.exs README* native ../../crates/spikard-elixir/src/*.ex) — no lib/ (the directory never exists for spikard; the NIF source lives in crates/spikard-elixir/src/) and no checksum-*.exs (no rustler_precompiled upload step is scaffolded). v0.15.2 claimed this fix but the alef v0.17.15 ship that release pinned to was incomplete; v0.17.17 is the first release where regen actually produces the correct list, unblocking mix hex.publish.
  • Picks up the alef v0.17.17 de-hardcoding sweep: codegen no longer carries literal consumer-repo names (spikard, kreuzberg, liter-llm, html-to-markdown, tree-sitter-language-pack, kreuzcrawl) in production source paths. spikard regen is unaffected in surface behavior; the change just makes alef a fully generic generator.
  • Swift Rust bridge: serde_json::from_str(&s).unwrap_or(Value::String(s)) replaced with unwrap_or_else(|_| Value::String(s)) across four config builders. The eagerly-evaluated form would move s before the fallback could read it; the lazy form keeps the original raw string available when JSON parsing fails (e.g. for plain identifiers like tts-1).

Changed

  • Taskfile.yaml: task upgrade guards the optional gh-actions-updater call with command -v so missing binaries print a single skip line instead of an "executable file not found" error.
  • .pre-commit-config.yaml: ai-rulez v4.1.6 → v4.2.1, kreuzberg-dev/pre-commit-hooks v1.1.12 → v1.1.17, kreuzberg-dev/alef v0.17.15 → v0.17.17.

[0.15.2] - 2026-05-21

Added

  • spikard-ffi added to the crates.io publish list in publish.yaml. The internal FFI crate now ships alongside the other six (spikard, spikard-core, spikard-http, spikard-graphql, spikard-codegen, spikard-cli). crates/spikard-ffi/Cargo.toml switched to workspace inheritance for version, license, and repository, and the internal spikard* deps now resolve via workspace = true so future bumps stay in sync.

Fixed

  • Regenerated all polyglot bindings against alef v0.17.15. Picks up the Elixir mix.exs packaging fix — emitted files: ~w(…) no longer lists lib/ (the directory never exists; Elixir module lives in crates/spikard-elixir/src/) or checksum-*.exs (no rustler_precompiled upload step is scaffolded), unblocking mix hex.publish.
  • publish.yaml: Pass nuget-user: nhirschfeld to the publish-nuget step so the OIDC token exchange with nuget.org includes the package owner's profile name (required by the /api/v2/token protocol).
  • Three protocol bugs in kreuzberg-dev/actions@v1 shipped alongside this release: publish-nuget OIDC exchange now hits /api/v2/token with audience https://www.nuget.org and body {username, tokenType: "ApiKey"} (matches the official NuGet/login@v1); publish-rubygems falls back to BUNDLE_GEM__PUSH_KEY when the caller's step-level GEM_HOST_API_KEY shadows the OIDC-exported value with an empty secret; publish-maven imports MAVEN_GPG_PRIVATE_KEY into the keyring (accepts armored or base64) before invoking mvn deploy; publish-maven-gradle accepts base64-encoded GPG keys; finalize-release retries the release lookup 6× to absorb GH API propagation race.

[0.15.1] - 2026-05-21

Fixed

  • publish.yaml: Build and upload Homebrew bottles before invoking publish-homebrew. The previous job pointed bottles-dir at the cli-assets artifact (source tarball only) and the publish step aborted with No bottle artifacts found matching spikard-*.bottle.tar.gz. Replaced with the canonical 3-job pattern: check-homebrewhomebrew-bottles (matrix across macos-latest/macos-15-intel/ubuntu-latest/ubuntu-24.04-arm) → upload-homebrew-bottlespublish-homebrew. Bottles now cover macOS arm64, macOS x86_64, Linux x86_64, and Linux arm64. Publish step now uses HOMEBREW_TOKEN to push across-repo to Goldziher/homebrew-tap.
  • publish.yaml: Use the existing CENTRAL_USERNAME / CENTRAL_PASSWORD / GPG_PRIVATE_KEY / GPG_PASSPHRASE repo secrets for the Maven Central publish job (the workflow previously referenced MAVEN_* secret names that don't exist in this repo).
  • Regenerated all polyglot bindings against alef v0.17.14. Picks up the napi package.json repository field fix (unblocks npm provenance for @spikard/node) and the C# error-enum dedupe fix (prevents corrupt regen for overlapping variant names like ValidationError / DepthLimitExceeded / ComplexityLimitExceeded spread across the GraphQLError and SchemaError enums).
  • Shared kreuzberg-dev/actions@v1 upgrades: publish-hex now runs mix deps.get before mix hex.publish; publish-pypi switched from pypa/gh-action-pypi-publish (which failed with ghcr.io: denied when invoked from a composite) to uv publish --trusted-publishing automatic; publish-npm strips empty NODE_AUTH_TOKEN + .npmrc _authToken= lines so npm CLI v11 OIDC fallback engages; publish-rubygems invokes rubygems/configure-rubygems-credentials when GEM_HOST_API_KEY is empty.

[0.15.0] - 2026-05-20

Added

  • SQL → HTTP handler codegen (spikard generate sql). Consumes SQL files annotated with @http GET /path, @http_auth bearer:jwt, @http_param email body, etc. and emits route metadata, an OpenAPI 3.1 spec, and a per-language sidecar describing how to call into scythe-generated query functions. Built on scythe-core 0.7's generic custom-annotation IR; HTTP vocabulary lives entirely in spikard so scythe stays library-agnostic. See docs/guides/sql-codegen.md.
  • spikard-graphql::GraphQLError::is_transient and ::error_type exposed as public #[must_use] const fn so language bindings can surface retryability and error-type identifiers alongside the human-readable message.
  • Root README.md — project landing page with badges, language support matrix, quick-start examples, architecture sketch, and links to per-binding READMEs.

Changed

  • scythe-core dependency switched from path to crates.io version 0.7 so the workspace builds against published artifacts.
  • tower-http bumped to 0.6.11 via cargo upgrade --incompatible.

Changed

  • Cleaned Alef binding/e2e test commands to run real generated Node e2e tests, avoid duplicate Java/C# e2e builds, suppress local NuGet audit network warnings, and keep no-test harness backends quiet.

Fixed

  • Marked internal runtime/cache fields as Alef-skipped so generated bindings no longer expose non-bridgeable implementation details.

[0.14.0] - 2026-04-22

Added

  • Alef-generated polyglot bindings: All language bindings (Python, TypeScript, Ruby, PHP, Elixir) are now generated by alef from the Rust API surface. 34 public types, 4 functions, 4 enums are extracted and bound automatically.
  • HTTP fixture-driven e2e tests: 410 HTTP fixtures converted to alef format across 24 categories. 135 test files generated across 5 languages.
  • CI alef verification: Added alef-verify job and pre-commit hook to catch stale generated bindings.
  • Taskfile alef integration: Added alef:generate, alef:verify, alef:extract, alef:scaffold, alef:sync tasks.
  • Derives for binding generation: Added Serialize, Deserialize, Clone, Default derives to 25+ public types for alef extraction compatibility.
  • From<Method> for http::method::Method: Enables spikard_core::Method to convert to the http crate's Method type.

Changed

  • Documentation toolchain: Migrated from mkdocs + mike + mkdocstrings to zensical + alef docs.
  • Test apps: Replaced hand-written tests/test_apps/ with alef-generated test_apps/ using registry mode.
  • Internal module visibility: Moved internal modules behind pub(crate) in spikard-core and spikard-http.
  • Public API surface: Restored spikard facade to original public API. Bindings import directly from source crates via extra_dependencies.

Removed

  • Hand-written binding code: ~315K lines of hand-written bindings, shared FFI layer, and language packages replaced by alef-generated equivalents.
  • Benchmark harness: tools/benchmark-harness/ and all 18 third-party framework benchmark apps.
  • Legacy tooling: tools/test-generator/, tools/app-generator/, scripts/sync_versions.py, scripts/generate_readme.py, debug modules, and 13 dead functions.
  • mkdocs: Replaced by zensical.

[0.13.0] - 2026-03-19

Added

  • CLI-equivalent MCP server: Added spikard mcp with typed tools for project initialization, OpenAPI/AsyncAPI/OpenRPC/GraphQL/Protobuf code generation, AsyncAPI testing helpers, schema validation, and feature discovery.
  • First-class Elixir codegen and runtime support: Added Elixir project scaffolding plus Elixir OpenAPI, OpenRPC/JSON-RPC, GraphQL, Protobuf/gRPC, and AsyncAPI generation, along with a real Elixir gRPC runtime path for unary and streaming services.
  • Full AsyncAPI test-app parity: Added Rust and Elixir AsyncAPI test-app and bundle generation so fixtures, handlers, test apps, and bundled outputs now cover Python, TypeScript, Rust, Ruby, PHP, and Elixir.
  • Documentation snippet validation: Added the snippet-runner workspace tool and documentation snippet validation commands, enabling syntax-level validation of polyglot docs examples.

Changed

  • spikard init now creates real starter projects: Initialization now produces idiomatic, runnable project layouts for every binding. Python uses pyproject.toml with uv, TypeScript uses package.json + tsconfig.json + src/, Rust uses a valid Cargo app scaffold, Ruby and PHP include runnable server entrypoints, and Elixir now generates a proper Mix application structure.
  • Code generation is now validator-backed across bindings: The CLI codegen path now enforces syntax/type/lint validation for generated output and has been hardened across all supported schema families: OpenAPI 3.1, AsyncAPI, OpenRPC/JSON-RPC, GraphQL, and Protobuf/gRPC.
  • Generated output is more idiomatic across languages: Generated Rust, Python, TypeScript, Ruby, PHP, and Elixir code now uses stronger schema-derived typing, cleaner naming, binding-appropriate project structure, and lint-/format-clean defaults out of the box.
  • README maintenance is template-driven: The root README and package/crate READMEs are now generated from shared templates, keeping CLI/MCP/codegen surface documentation aligned across the repo.

Fixed

  • Cross-binding codegen parity gaps: Closed numerous real generation issues across OpenAPI, OpenRPC, GraphQL, Protobuf, and AsyncAPI, including nested object typing, enum handling, semantic date/UUID types, inline model promotion, streaming service scaffolding, and generated file composition for all targets.
  • MCP/CLI parity and defaults: MCP tool defaults now match CLI defaults for init and core codegen commands, and the MCP surface is now covered by parity tests plus live stdio round-trip smoke tests.
  • gRPC runtime behavior across bindings: Fixed gRPC registration, wire framing, request compression, stream termination, status propagation, mixed-mode service handling, and helper/status normalization across the shared runtime and Node, Python, Ruby, PHP, and Elixir bindings.
  • Binding startup/runtime correctness: Fixed Python auto-reload, async startup, upload-file async I/O, Python 3.14 loop handling, Node/Ruby startup API cleanup, lifecycle hook wiring in PHP and generated Ruby apps, route metadata preservation, and DI error handling.
  • Docs and install guidance: Corrected installation, runtime, and codegen docs to match the shipped surface, including wheel/prebuilt guidance, AsyncAPI 3 support, OpenRPC support, GraphQL introspection JSON support, Protobuf include paths, and current startup/config APIs.

[0.12.0] - 2026-02-28

Added

  • Elixir benchmark apps: Added 3 Elixir benchmark applications to the benchmark harness — spikard-elixir, plug-bandit (Plug + Bandit), and phoenix (Phoenix API-only) — bringing the total to 21 benchmarked frameworks.
  • Benchmark Taskfile integration: New bench:update:elixir, bench:build:elixir, bench:run:spikard-elixir, bench:run:plug-bandit, and bench:run:phoenix tasks.
  • GraphQL testing parity across bindings: Unified GraphQL testing interface across Python, Node.js, Ruby, PHP, and Elixir with dedicated helper methods for queries, mutations, and subscriptions.
  • Elixir test client: New Spikard.TestClient module with full GraphQL helper parity matching other bindings.
  • PHP JsonRpcConfig tests: Comprehensive test coverage for JsonRpcConfig and JsonRpcConfigBuilder classes.
  • Python type stubs: Shipped _spikard.pyi in wheels for IDE/type-checker support (mypy, pyright, Pylance).

Fixed

  • axum-test v19 API migration: Updated spikard-rb and spikard-node test clients after TestServer::new() stopped returning Result in axum-test v19.
  • gRPC test fixture streaming detection: Fixed incorrect routing of unary RPCs through the bidirectional streaming path in Python and PHP gRPC fixture tests.
  • Python lint compliance: Resolved ruff PYI021 (docstrings in stubs), PYI044 (__future__ annotations in stubs), and RUF022 (unsorted __all__) in _spikard.pyi.
  • Biome lint/format: Fixed import ordering and unused imports in Node.js examples and test apps.
  • PHP coverage threshold: Brought PHP test coverage above the 85% CI gate.
  • CI pipeline stability: All 6 CI workflows (Rust, Python, Node, Ruby, PHP, Validate) now pass on main.

Changed

  • Dependency updates: Updated workspace dependencies including jsonschema 0.42→0.43, maturin 1.12.4→1.12.5, and refreshed lock files across all ecosystems.
  • CI actions: Bumped actions/upload-artifact to v7, actions/download-artifact to v8, dawidd6/action-download-artifact to v16.
  • Pre-commit alignment: Aligned .pre-commit-config.yaml with latest hook versions (shfmt, cargo-deny, cargo-machete, mypy mirrors).
  • Release publishing: Added idempotent publish checks across all package registries.

[0.11.0] - 2026-02-16

Added

  • Elixir bindings (first release): Introduced official Elixir support via Rustler NIF bindings, including typed Elixir-facing APIs, tuple-based error semantics ({:ok, value} / {:error, reason}), and ExUnit coverage for binding behavior.

Changed

  • Version synchronization for release: Bumped the workspace to 0.11.0 and synchronized package manifests across supported ecosystems for the Elixir release.

[0.10.2] - 2026-02-03

Fixed

  • Ruby gem packaging: Fixed vendor-crates.sh to patch ext/spikard_rb/Cargo.toml path from workspace-relative (../../../../crates/spikard-rb) to vendored (../../vendor/crates/spikard-rb). This was causing the published gem to fail installation with "failed to load manifest for dependency spikard-rb".

[0.10.1] - 2026-02-02

Performance

  • Static response fast-path with HashMap router: Added optimized routing path for all bindings, reducing dispatch overhead for static routes.
  • Reduced per-request allocations across HTTP pipeline: Minimized allocations in the core HTTP pipeline and all language bindings.
  • Sync benchmark handlers: Converted benchmark handlers from async to sync where unnecessary, improving benchmark accuracy.

Fixed

  • Python API: Critical fixes to align Python bindings with new API surface; resolved mypy errors in SSE and testing modules.
  • Python TestClient: Added cookies support to TestClient.
  • Node.js bindings: Fixed build configuration and tsx dependency for benchmark app.
  • Ruby bindings: Stabilized lockfile for CI.
  • Linting: Applied ruff and biome auto-fixes across Python and TypeScript.

Documentation

  • Comprehensive docs audit: Deleted orphaned files, consolidated duplicates, expanded all binding docs (Python, TypeScript, Ruby, PHP) with full API coverage.
  • New GraphQL guide: Added complete user guide with multi-language examples.
  • Navigation cleanup: Added 10 missing pages to mkdocs nav, fixed 15+ broken internal links.
  • Removed WASM references: Cleaned all user-facing docs of stale WASM mentions.
  • Benchmark results: Updated benchmark results and added performance comparison tables to all README files.

[0.10.0] - 2026-01-30

Removed

  • WASM bindings: The spikard-wasm crate has been removed from the workspace. WASM support will return in a future release targeting WASIp3 HTTP components.

Fixed

  • Node.js bindings: Resolved JSON deserialization regression in Node.js bindings.
  • Ruby bindings: Fixed lifecycle hooks implementation and improved test coverage.

Changed

  • Dependencies: Updated across all ecosystems (Rust, Python, Node.js, Ruby, PHP).

[0.9.2] - 2026-01-22

Performance

  • Arc::try_unwrap Pattern Across All Bindings: Eliminates unnecessary clones when Arc has unique ownership
  • Applied to Python, Ruby, PHP, and Node.js bindings for all RequestData fields
  • 30-40% reduction in FFI conversion overhead measured in Python bindings
  • Static singletons for empty collections in request_extraction.rs (shared Arc instances)
  • OnceLock caching optimizations in Python handler_request.rs (eliminated double-clone patterns)

Changed

  • BREAKING: RequestData Value Fields Arc-Wrapped (Phase 2 Performance Optimization)
  • query_params: Valuequery_params: Arc<Value> reduces per-request clones from 24-30 to 0-6
  • body: Valuebody: Arc<Value> enables zero-copy when refcount is 1 via Arc::try_unwrap
  • validated_params: Option<Value>validated_params: Option<Arc<Value>> maintains consistency
  • All RequestData construction sites updated across spikard-http crate
  • Test helpers and fixtures updated to wrap Value fields in Arc::new()
  • This is a pre-1.0 experimental breaking change; language bindings updated in this release

Fixed

  • Ruby handler: Eliminated double-clone in validated_params handling (line 274)
  • Ruby handler: Fixed raw body clone by using serde_json::from_slice directly
  • PHP response: Changed with_cookies to use &mut self (PHP heap-allocated object constraint)
  • All binding tests: Updated to match Arc-wrapped RequestData struct changes
  • Clippy lints: Fixed ptr_cast_constness and unnecessary_option_map_or_else warnings

[0.9.1] - 2026-01-12

Fixed

  • Added skill descriptions to AI-Rulez metadata so Codex skill YAML satisfies the required description field.

[0.9.0] - 2026-01-10

Added

  • Ruby gRPC Streaming Handlers: Full implementation of all 4 streaming modes for Ruby bindings
  • RubyGrpcRequest/RubyGrpcResponse wrapper types for Ruby FFI
  • RubyGrpcHandler implementing GrpcHandler trait with unary, server, client, and bidirectional streaming
  • DOS protection limits (payload size, metadata entries/size, stream message count, total bytes)
  • Handler timeout (30 seconds) to prevent hung handlers
  • SAFETY-documented unsafe blocks for Ruby GVL handling
  • Error message sanitization (log full errors, return generic messages to clients)
  • Magnus Ruby FFI integration with Opaque for proper GVL management

  • PHP gRPC Streaming Handlers: Full implementation of all 4 streaming modes for PHP bindings

  • PhpGrpcRequest/PhpGrpcResponse wrapper types for PHP FFI
  • PhpGrpcHandler implementing GrpcHandler trait
  • DOS protection and timeout handling matching Ruby implementation

  • gRPC Streaming Fixture Integration (Phases 1-4): Complete end-to-end fixture infrastructure for all 4 streaming modes

  • 30+ JSON fixture files covering Unary, ServerStreaming, ClientStreaming, and BidirectionalStreaming modes
  • Schema validation with semantic cross-reference checks (testing_data/grpc/schema_definitions.json)
  • Fixture validation script (scripts/validate_fixtures.py) with comprehensive error reporting
  • Cross-language parity tests verifying identical behavior across all 5 languages
  • Metadata and timeout support in all gRPC clients (Python, TypeScript, Ruby, PHP, Rust)
  • Stream generators for large fixture tests (sequential, random, timestamp-based patterns)
  • Helper functions eliminating test duplication across languages
  • CI workflow (ci-grpc-fixtures.yaml) for automated gRPC fixture validation

  • gRPC Fixture Testing Suite (120+ cross-language tests):

  • Python: 30+ parametrized pytest tests with fixture loading, 80%+ code coverage
  • TypeScript: 30+ vitest tests with metadata support and stream assertions
  • Ruby: 30+ RSpec tests with block-based connection management and cleanup
  • PHP: 30+ PHPUnit tests with PSR-12 compliance and 85%+ coverage
  • All tests use shared fixture files for consistent validation across ecosystems

  • gRPC server streaming support: Full server streaming RPC implementation

  • Added RpcMode enum for declaring handler capabilities (Unary, ServerStreaming, ClientStreaming, BidirectionalStreaming)
  • Added call_server_stream() trait method to GrpcHandler for streaming implementations
  • Added StreamingResponse type with optional trailers field for response metadata after stream completion
  • GrpcRegistry now stores (handler, RpcMode) tuples for proper request routing
  • Streaming utilities: message_stream_from_vec(), empty_message_stream(), single_message_stream(), error_stream() helpers

Changed

  • GrpcHandler trait breaking changes (semver major):
  • Removed supports_streaming_requests() method (replaced by rpc_mode())
  • Removed supports_streaming_responses() method (replaced by rpc_mode())
  • Added rpc_mode() -> RpcMode method with default implementation returning RpcMode::Unary
  • Added call_server_stream() method with UNIMPLEMENTED default for backward compatibility
  • GrpcRegistry registration now requires RpcMode parameter: registry.register(handler, rpc_mode)

Migration Guide

For existing unary handlers:

// Before
if !handler.supports_streaming_requests() {
    // route to unary
}

// After
match handler.rpc_mode() {
    RpcMode::Unary => { /* unary routing */ }
    RpcMode::ServerStreaming => { /* server streaming routing */ }
    _ => { /* other modes */ }
}

For implementing server streaming:

fn rpc_mode(&self) -> RpcMode {
    RpcMode::ServerStreaming
}

fn call_server_stream(
    &self,
    request: GrpcRequestData,
) -> Pin<Box<dyn Future<Output = Result<MessageStream, tonic::Status>> + Send>> {
    Box::pin(async {
        // Create and return message stream
    })
}
  • gRPC Client Streaming Support: Full implementation of gRPC client streaming mode
  • call_client_stream() trait method for handlers receiving message streams
  • StreamingRequest type containing service/method names, message stream, and metadata
  • HTTP/2 gRPC frame parsing with per-message size validation
  • Smart routing dispatches ClientStreaming mode to appropriate handler
  • Frame parser enforces max_message_size on each message in stream (not total body)
  • Helper utilities: parse_grpc_client_stream() for frame parsing with validation

Security

  • Per-message size enforcement: Client streaming now validates each gRPC frame against max_message_size, preventing resource exhaustion from large individual messages in multi-message streams
  • Stream resource limits: Handlers can return early errors without consuming entire stream, preventing memory buildup

Migration Guide - Client Streaming

To implement a client streaming handler:

use futures_util::StreamExt;

impl GrpcHandler for MyHandler {
    fn rpc_mode(&self) -> RpcMode {
        RpcMode::ClientStreaming
    }

    fn call_client_stream(
        &self,
        request: StreamingRequest,
    ) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
        Box::pin(async move {
            let mut stream = request.message_stream;
            let mut total = 0;

            // Consume stream message-by-message
            while let Some(msg_result) = stream.next().await {
                match msg_result {
                    Ok(message) => {
                        // Process message (size already validated)
                        total += decode_number(&message);
                    }
                    Err(status) => {
                        // Stream error (e.g., size limit exceeded)
                        return Err(status);
                    }
                }
            }

            Ok(GrpcResponseData {
                payload: encode_result(total),
                metadata: MetadataMap::new(),
            })
        })
    }
}

// Register with RpcMode::ClientStreaming
registry.register("mypackage.MyService", Arc::new(MyHandler), RpcMode::ClientStreaming);
  • gRPC Bidirectional Streaming Support: Full implementation of gRPC bidirectional streaming mode
  • call_bidi_stream() trait method for handlers with full-duplex message streams
  • Full-duplex communication: both client and server send streams of messages concurrently
  • Independent request and response streams with proper backpressure handling
  • Smart routing dispatches BidirectionalStreaming mode to appropriate handler
  • Reuses HTTP/2 frame parser with per-message size validation from client streaming
  • Supports chat, collaborative editing, and real-time bidirectional data flows

Migration Guide - Bidirectional Streaming

To implement a bidirectional streaming handler:

use futures_util::StreamExt;

impl GrpcHandler for MyHandler {
    fn rpc_mode(&self) -> RpcMode {
        RpcMode::BidirectionalStreaming
    }

    fn call_bidi_stream(
        &self,
        request: StreamingRequest,
    ) -> Pin<Box<dyn Future<Output = Result<MessageStream, Status>> + Send>> {
        Box::pin(async move {
            let request_stream = request.message_stream;

            // Echo each message back as it arrives
            let response_stream = request_stream.map(|msg_result| {
                match msg_result {
                    Ok(msg) => Ok(msg), // Echo back
                    Err(e) => Err(e),
                }
            });

            Ok(Box::pin(response_stream) as MessageStream)
        })
    }
}

// Register with RpcMode::BidirectionalStreaming
registry.register("mypackage.ChatService", Arc::new(MyHandler), RpcMode::BidirectionalStreaming);

Advanced pattern with async processing:

fn call_bidi_stream(
    &self,
    request: StreamingRequest,
) -> Pin<Box<dyn Future<Output = Result<MessageStream, Status>> + Send>> {
    Box::pin(async move {
        let mut request_stream = request.message_stream;
        let (tx, rx) = tokio::sync::mpsc::channel(100);

        // Spawn task to process incoming messages asynchronously
        tokio::spawn(async move {
            while let Some(msg_result) = request_stream.next().await {
                match msg_result {
                    Ok(msg) => {
                        let response = process_message(msg);
                        let _ = tx.send(Ok(response)).await;
                    }
                    Err(e) => {
                        let _ = tx.send(Err(e)).await;
                        break;
                    }
                }
            }
        });

        // Convert mpsc receiver to MessageStream
        let stream = tokio_stream::wrappers::ReceiverStream::new(rx);
        Ok(Box::pin(stream) as MessageStream)
    })
}

[0.8.3] - 2026-01-05

Fixed

  • Workspace lints: Added proper workspace lints configuration to eliminate unexpected tarpaulin_include cfg condition errors across all crates
  • Clippy warnings: Resolved 500+ clippy warnings in core crates (spikard, spikard-core, spikard-codegen, benchmark-harness, spikard-bindings-shared) to maintain zero-warning policy
  • Language binding lifetime parameters: Fixed lifetime parameter errors in all language binding crates (Python/PyO3, TypeScript/NAPI-RS, Ruby/Magnus, PHP/ext-php-rs, WASM/wasm-bindgen)
  • FFI binding crates: Added comprehensive clippy allow attributes for FFI-specific crates to suppress intentional FFI-related warnings
  • Ruby vendoring: Updated vendoring script to preserve workspace lints configuration during gem dependency vendoring
  • Code formatting: Fixed formatting inconsistencies across all binding crates to comply with project standards

[0.8.2] - 2026-01-02

Fixed

  • Homebrew bottles: Fixed bottle directory structure to include formula name and version path, resolving installation failures

[0.8.1] - 2026-01-01

Added

  • Homebrew bottles: Pre-built binaries for macOS arm64 (Sonoma, Sequoia) for faster brew install/upgrade

Fixed

  • Homebrew formula: Fixed missing SHA256 checksum that was causing installation failures

[0.8.0] - 2025-12-31

Added

  • gRPC/Protobuf Support: Full gRPC server implementation with code generation
  • Supports all 5 languages: Python, TypeScript, Ruby, PHP, Rust
  • Complete proto3 schema parsing and type generation
  • All 17 gRPC status codes with proper error handling
  • Unary RPC support (streaming modes planned for future releases)
  • FFI bindings for all language runtimes using Tonic
  • Comprehensive test coverage (672+ tests across all layers)
  • Complete documentation suite with 9 guides and 3 ADRs

  • Documentation Enhancements: Complete documentation transformation

  • Created 239 reusable code snippets across 5 languages
  • Added comprehensive testing guide with all languages
  • Added troubleshooting and code generation guides
  • Refactored all major guides with snippet extraction (77% line reduction)
  • Achieved full language parity (Python, TypeScript, Ruby, PHP, Rust) in all guides
  • Added 9 gRPC-specific guides with complete examples
  • Split init command into quickstart and reference documentation

[0.7.5] - 2025-12-31

Fixed

  • Ruby bindings: Avoid double-defining StreamingResponse to eliminate runtime redefinition warnings.
  • Release automation: Use Packagist username + token when triggering refreshes and skip gracefully if missing.
  • Test apps: Use registry-only pnpm installs, add uv fallback to pip, and validate npm/Packagist endpoints correctly.

[0.7.4] - 2025-12-31

Fixed

  • Ruby TestClient: Always execute handler calls under the GVL so Ruby VM access is valid when invoked from Rust threads.

[0.7.3] - 2025-12-31

Fixed

  • Ruby gem vendoring: Align tower-http features, tracing-subscriber env-filter, and tower-governor version so native builds match the workspace.
  • Cloudflare test generator: Keep workers-types version aligned with the lockfile to avoid frozen install failures.

[0.7.2] - 2025-12-30

Fixed

  • Ruby vendoring: Avoid rewriting spikard-http as http so the Ruby gem builds against the vendored crates.

[0.7.1] - 2025-12-30

Fixed

  • WASM test client: Load bundled WASM bindings from the package dist output so published builds work in tests.

[0.7.0] - 2025-12-30

Added

  • GraphQL code generation: Full support for generating typed GraphQL server code from schema files
  • Supports all 5 languages: Python, TypeScript, Ruby, PHP, Rust
  • Generates three output types: types, resolvers, and schema
  • Type-safe resolver signatures with proper parent/context/info parameters
  • Automatic RBS type definitions for Ruby
  • Strict type checking compliance (no Any types in Python, TypeScript)
  • Quality validation with mypy, TypeScript compiler, Steep, PHPStan
  • SDL schema reconstruction for runtime validation

  • spikard init command: Project scaffolding for new Spikard projects

  • Supports all 5 languages: Python, TypeScript, Ruby, PHP, Rust
  • Language-specific project structure generation
  • Automatic dependency initialization (pip, npm/pnpm, gem, composer, cargo)
  • Example handler files following language-specific patterns
  • Optional schema file integration for code generation

  • Quality validation framework: Automated validation of generated code

  • Language-specific syntax validation
  • Type checking integration (mypy, TypeScript, Steep, PHPStan)
  • Linting with native tools (Ruff, Biome, Rubocop, PHP-CS-Fixer)
  • Structured validation reports with detailed error messages

Changed

  • Code generation architecture refactored: All generators now use shared utilities
  • Centralized case conversion (snake_case, camelCase, PascalCase, kebab-case)
  • Unified string escaping for different contexts (JSON, GraphQL SDL, docstrings)
  • Consistent identifier sanitization with language-specific rules
  • Improved code quality and consistency across all generators

Fixed

  • OpenAPI generators: Critical bug fixes affecting generated code quality
  • Ruby: Fixed multi-line comment handling causing syntax errors
  • PHP: Corrected parameter ordering violations
  • TypeScript: Resolved forward reference errors in type definitions

  • OpenRPC generators: Fixed serialization issues causing double JSON encoding

  • AsyncAPI generators: Fixed critical type mapping issues across all languages

[0.6.2] - 2025-12-28

Fixed

  • Version bump and test app updates for consistency

[0.6.1] - Previous Release

See git history for detailed changes.


Project Structure

Codegen Modules

crates/spikard-cli/src/codegen/
├── common/              # Shared utilities (case conversion, escaping, sanitization)
├── quality/             # Quality validation framework
├── formatters/          # Language-specific formatters
├── graphql/             # GraphQL schema generators
├── openapi.rs           # OpenAPI generators
├── openrpc/             # OpenRPC generators
├── asyncapi/            # AsyncAPI generators
└── [language].rs        # Individual language generators

Init Module

crates/spikard-cli/src/init/
├── engine.rs            # Core initialization orchestration
├── scaffolder.rs        # ProjectScaffolder trait
└── [language].rs        # Language-specific scaffolders

Contributing

When adding new features or generators:

  1. Use shared utilities in codegen/common/ for case conversion and escaping
  2. Validate generated code using codegen/quality/QualityValidator
  3. Add fixtures to testing_data/ for new scenarios
  4. Update this changelog with all changes
  5. Run task test to ensure quality gates pass

For detailed guidelines, see:

Edit this page on GitHub