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-assetsglob inpublish.yamlusedspikard_nif-*.tar.gz, but the Elixir NIF build emitslibspikard_nif-*.tar.gz(with thelibprefix), so zero NIF tarballs were attached to the release and thePublish Hexjob 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.tsHTTP-extension fragment — a class-body splice fragment, not a valid standalone TS module — from the nodeoxfmtformat command inalef.toml, sotask alef:formatno longer fails parsing it. - Dropped Intel macOS (
x86_64-apple-darwin). Removed the Intel-mac Node binding target (@spikard/node-darwin-x64) viaalef.tomlexclude_platforms = ["darwin-x64"], and removed the Intel-macbuild-nodematrix entry and themacos-15-intelHomebrew bottle frompublish.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
Defaultimpls: fields annotated with#[serde(default = "path")]were emitted verbatim asfield: 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.1and 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 optionalVec<enum>fields (no moreTypeErrorwhen the value isNone) and drops a redundant visitor# type: ignore; PHP DTO instance methods returningVec/Mapnow 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
pmdpre-commit hook at the curatedpackages/java/pmd-ruleset.xml(via-R) instead of PMD's built-inquickstart.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"); SwiftApp.config(host:port:)bridge emitted and invoked beforeapp.run()(fixes nilURLResponse); Go cgoStartBackgroundplumbs host/port throughConfig(...)and polls socket-bind before returning (fixes "connection refused"); Dart FRB post-build rewriter is scope-aware and no longer corrupts inheritedBaseHandler.executeSynccalls (~62 generated methods); Ruby e2e tests now emit_req.body = ...for multipart fixtures; NAPI base registration accepts&JsRouteBuilderwrapper and unwraps to the core type so napi-rs can synthesizeFromNapiValue, with TS wrappers calling positionalthis._app.route(builder, fn)instead of[builder]. - Elixir publish workflow now uploads NIF artifacts with the correct glob
spikard_nif-*.tar.gz(waslibspikard_nif-*.tar.gz). Thebuild-elixir-natives@v1action emits the artifact without thelibprefix, so the previous glob never matched, leaving the GitHub release with no NIF assets.rustler_precompiledthen 404'd on every install, surfacing as(ArgumentError) could not load module Spikard.App due to reason :nofileat runtime even though the Hex publish job itself succeeded.
[0.15.6-rc.9] - 2026-05-27¶
Fixed¶
- Resolved a
tungsteniteversion skew that broke every FFI and wheel build (CI Bindings, CI E2E, Deploy Documentation, and all Publish Release build jobs).spikard-httppinnedtungstenite = "=0.28.0"directly whileaxum/axum-testpulltungstenite 0.29transitively, sotesting.rsmixed two incompatibleCloseFrametypes (error[E0308]: expected CloseFrame, found tungstenite::protocol::CloseFrame). The direct dependency is nowtungstenite = "0.29", collapsing the graph to a single version. The break surfaced only in CI because the rootCargo.lockis not committed, so CI re-resolves to the newest compatible transitive releases. task version:setnow runsalef scaffoldas part of the sync step, so a version bump re-emits the napi platformoptionalDependenciesincrates/spikard-node/package.jsonat 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: falsevia kreuzberg-dev/actions v1.8.5: the napi platformoptionalDependenciespin 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.gonow declarespackage spikard(matchingbinding.go) instead of an erroneous hardcodedpackage tspack, fixed upstream in alef v0.19.23. The file is//go:build ignoresogo buildwas 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.tomlpin and the alef pre-commit hook. Generation now runs with alef's own post-generation formatters so committed output matchesalef verifyhashes.
[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 generatedbuild.gradle.ktsset the testworkingDirto an absenttest_documents/directory and passed nojvmArgs; fixed in alef v0.19.21 by guardingworkingDiron existence and enabling--enable-preview --enable-native-access=ALL-UNNAMEDon the Panama test fork (Kotlin e2e runs on JDK 25). The Dart and Zig e2e teardowns no longer runpkill -f 'mock-server …', which matched and SIGTERM-ed their own parentsh -con 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.tomlpin and the.pre-commit-config.yamlalef hook. The committed SwiftRustBridgeC.his now a small placeholder that thecargo build -p spikard-swiftstep 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.tomlpin and the.pre-commit-config.yamlalef 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 filtersbinding_excludedfields 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 refusedagainstlocalhostacross 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_URLand 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.tomlpreviously shipped bothpathandversiondeps; cargo prefers the absentpath, so the native extension failed to build on consumers.alef.tomlno longer overridesvendor_modefor Ruby/Elixir, so both use alef 0.19.9'sregistrydefault —alef publish preparerewrites 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.tomlpin and the.pre-commit-config.yamlalef hook to 0.19.9. 0.19.9 makes the version pin self-syncing (alef generate/all/scaffoldwrite[workspace] alef_versionand the pre-commit hookrev), 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 byalef test-apps generate.alef test-apps runinstalls 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 viaMOCK_SERVER_URL; harnesses honor a pre-set URL instead of self-spawning. Registry coordinates corrected: the Kotlin artifact isdev.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.tomlpin and the.pre-commit-config.yamlalef 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:versionrequirements oncrates/spikard-ffi's internal path-deps (socargo publishaccepts the crate), the Elixir Hexmix.exsfileslist shipping../../crates/spikard-elixir/src(NIFlib.rs+.exmodules), de-duplicated Python__init__.pyre-exports,pyproject-fmt-canonicalpyproject.toml, the PSR-12 blank line after<?php, andsync-versionsnow also bumping the Kotlinbuild.gradle.ktsversion, the Elixir NIFCargo.lock, and thedocs/reference/api-*.mdversion badges.
Fixed¶
publish.yaml: the Hex publish now generates the RustlerPrecompiled checksum file viakreuzberg-dev/actions/generate-elixir-checksums@v1beforebuild-elixir-hex@v1.mix.exslistschecksum-*.exsin its packagefiles, somix hex.buildfailed at rc.2 withMissing files: checksum-*.exs. The step downloads the per-platform NIF tarballs from the GitHub Release, sopublish-hexis now gated on a real tag release (is_tag == 'true') and onupload-release-assetssucceeding.publish.yaml: widened theupload-release-assetsNIF glob fromlibspikard_nif-*.so.tar.gztolibspikard_nif-*.tar.gzso the macOS.dyliband Windows.dlltarballs are attached to the Release (previously only the four Linux.soassets were uploaded, which would have starved the checksum step).publish.yaml: removed the self-defeatingswift_exists/zig_existsregistry-existence gate onpublish-swift/publish-zig. Swift and Zig have no central registry, socheck-registryfalls back to a GitHub release-tag lookup — which thepreparejob'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 owndry-runhandling for idempotency.
[0.15.6-rc.2] - 2026-05-24¶
Fixed¶
crates/spikard-ffi/Cargo.toml: addedversionrequirements to the internalspikard-core,spikard-graphql, andspikard-httpdeps (they were path-only).cargo publishrejects path-only deps ("all dependencies must have a version requirement specified when publishing"), which broke crates.io publishing ofspikard-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 beforespikard-ffifailed; rc.2 republishes the full set.crates/spikard-py/src/pyproject.toml: bumped the[project]version to the PEP 440-normalized0.15.6rc2to match the generatedpackages/python/pyproject.toml. The alef Python source template's literal version is not bumped byalef sync-versionsand must use the normalized (not canonical-rc.N) form, soalef validate versionsfailed 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.gitignoreexception, matching the Ruby ext).mix.exslists it in the Hex packagefiles, somix hex.buildfailed 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:8080connection-refused failures), andalef validate versionsPEP 440-normalizes both sides of the Python pyproject check. Bumped the.pre-commit-config.yamlalef hook andalef.tomlpin to0.19.4. publish.yaml: the Hex publish now runskreuzberg-dev/actions/build-elixir-hex@v1beforepublish-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 viahomebrew-build-bottles@v1+homebrew-merge-bottles@v1(macOSarm64_sequoia/sequoia+ Linuxx86_64_linux/arm64_linux, withinstall-homebrew-linux@v1on Linux), replacing the syntheticbuild-homebrew-bottle@v1. The formula source URL/SHA is updated byscripts/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-ffifailed; 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.tomlnow emitsversionrequirements on the internalspikard-{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
.gitattributesmarking generated trees (crates/spikard-{ffi,node,php,py,wasm},packages/*,e2e/) aslinguist-generatedso 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-registrypath-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 atcargo 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.yamlworkflow triggered bypush: tags: v*. pub.dev OIDC trusted publishing rejects tokens fromreleaseevents; 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, andspikard{,-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 brokecargo publishforspikard-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.exsfiles:list is now correctly emitted as~w(.formatter.exs mix.exs README* native ../../crates/spikard-elixir/src/*.ex)— nolib/(the directory never exists for spikard; the NIF source lives incrates/spikard-elixir/src/) and nochecksum-*.exs(norustler_precompiledupload 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, unblockingmix 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 withunwrap_or_else(|_| Value::String(s))across four config builders. The eagerly-evaluated form would movesbefore the fallback could read it; the lazy form keeps the original raw string available when JSON parsing fails (e.g. for plain identifiers liketts-1).
Changed¶
Taskfile.yaml:task upgradeguards the optionalgh-actions-updatercall withcommand -vso missing binaries print a single skip line instead of an "executable file not found" error..pre-commit-config.yaml:ai-rulezv4.1.6 → v4.2.1,kreuzberg-dev/pre-commit-hooksv1.1.12 → v1.1.17,kreuzberg-dev/alefv0.17.15 → v0.17.17.
[0.15.2] - 2026-05-21¶
Added¶
spikard-ffiadded to the crates.io publish list inpublish.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.tomlswitched to workspace inheritance forversion,license, andrepository, and the internalspikard*deps now resolve viaworkspace = trueso future bumps stay in sync.
Fixed¶
- Regenerated all polyglot bindings against alef v0.17.15. Picks up the Elixir
mix.exspackaging fix — emittedfiles: ~w(…)no longer listslib/(the directory never exists; Elixir module lives incrates/spikard-elixir/src/) orchecksum-*.exs(norustler_precompiledupload step is scaffolded), unblockingmix hex.publish. publish.yaml: Passnuget-user: nhirschfeldto thepublish-nugetstep so the OIDC token exchange with nuget.org includes the package owner's profile name (required by the/api/v2/tokenprotocol).- Three protocol bugs in
kreuzberg-dev/actions@v1shipped alongside this release:publish-nugetOIDC exchange now hits/api/v2/tokenwith audiencehttps://www.nuget.organd body{username, tokenType: "ApiKey"}(matches the officialNuGet/login@v1);publish-rubygemsfalls back toBUNDLE_GEM__PUSH_KEYwhen the caller's step-levelGEM_HOST_API_KEYshadows the OIDC-exported value with an empty secret;publish-mavenimportsMAVEN_GPG_PRIVATE_KEYinto the keyring (accepts armored or base64) before invokingmvn deploy;publish-maven-gradleaccepts base64-encoded GPG keys;finalize-releaseretries 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 invokingpublish-homebrew. The previous job pointedbottles-dirat thecli-assetsartifact (source tarball only) and the publish step aborted withNo bottle artifacts found matching spikard-*.bottle.tar.gz. Replaced with the canonical 3-job pattern:check-homebrew→homebrew-bottles(matrix acrossmacos-latest/macos-15-intel/ubuntu-latest/ubuntu-24.04-arm) →upload-homebrew-bottles→publish-homebrew. Bottles now cover macOS arm64, macOS x86_64, Linux x86_64, and Linux arm64. Publish step now usesHOMEBREW_TOKENto push across-repo toGoldziher/homebrew-tap.publish.yaml: Use the existingCENTRAL_USERNAME/CENTRAL_PASSWORD/GPG_PRIVATE_KEY/GPG_PASSPHRASErepo secrets for the Maven Central publish job (the workflow previously referencedMAVEN_*secret names that don't exist in this repo).- Regenerated all polyglot bindings against alef v0.17.14. Picks up the napi
package.jsonrepositoryfield fix (unblocks npm provenance for@spikard/node) and the C# error-enum dedupe fix (prevents corrupt regen for overlapping variant names likeValidationError/DepthLimitExceeded/ComplexityLimitExceededspread across theGraphQLErrorandSchemaErrorenums). - Shared
kreuzberg-dev/actions@v1upgrades:publish-hexnow runsmix deps.getbeforemix hex.publish;publish-pypiswitched frompypa/gh-action-pypi-publish(which failed withghcr.io: deniedwhen invoked from a composite) touv publish --trusted-publishing automatic;publish-npmstrips emptyNODE_AUTH_TOKEN+.npmrc_authToken=lines so npm CLI v11 OIDC fallback engages;publish-rubygemsinvokesrubygems/configure-rubygems-credentialswhenGEM_HOST_API_KEYis 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 onscythe-core0.7's generic custom-annotation IR; HTTP vocabulary lives entirely in spikard so scythe stays library-agnostic. Seedocs/guides/sql-codegen.md. spikard-graphql::GraphQLError::is_transientand::error_typeexposed as public#[must_use] const fnso 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-coredependency switched from path to crates.io version0.7so the workspace builds against published artifacts.tower-httpbumped to 0.6.11 viacargo 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-verifyjob and pre-commit hook to catch stale generated bindings. - Taskfile alef integration: Added
alef:generate,alef:verify,alef:extract,alef:scaffold,alef:synctasks. - Derives for binding generation: Added
Serialize,Deserialize,Clone,Defaultderives to 25+ public types for alef extraction compatibility. From<Method>for http::method::Method: Enablesspikard_core::Methodto convert to thehttpcrate'sMethodtype.
Changed¶
- Documentation toolchain: Migrated from mkdocs + mike + mkdocstrings to zensical + alef docs.
- Test apps: Replaced hand-written
tests/test_apps/with alef-generatedtest_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 mcpwith 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-runnerworkspace tool and documentation snippet validation commands, enabling syntax-level validation of polyglot docs examples.
Changed¶
spikard initnow creates real starter projects: Initialization now produces idiomatic, runnable project layouts for every binding. Python usespyproject.tomlwithuv, TypeScript usespackage.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
alltargets. - 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, andbench:run:phoenixtasks. - 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.TestClientmodule with full GraphQL helper parity matching other bindings. - PHP JsonRpcConfig tests: Comprehensive test coverage for
JsonRpcConfigandJsonRpcConfigBuilderclasses. - Python type stubs: Shipped
_spikard.pyiin wheels for IDE/type-checker support (mypy, pyright, Pylance).
Fixed¶
- axum-test v19 API migration: Updated
spikard-rbandspikard-nodetest clients afterTestServer::new()stopped returningResultin 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-artifactto v7,actions/download-artifactto v8,dawidd6/action-download-artifactto v16. - Pre-commit alignment: Aligned
.pre-commit-config.yamlwith 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.0and synchronized package manifests across supported ecosystems for the Elixir release.
[0.10.2] - 2026-02-03¶
Fixed¶
- Ruby gem packaging: Fixed
vendor-crates.shto patchext/spikard_rb/Cargo.tomlpath 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 dependencyspikard-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-wasmcrate 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: Value→query_params: Arc<Value>reduces per-request clones from 24-30 to 0-6body: Value→body: Arc<Value>enables zero-copy when refcount is 1 via Arc::try_unwrapvalidated_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
descriptionfield.
[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
RpcModeenum for declaring handler capabilities (Unary, ServerStreaming, ClientStreaming, BidirectionalStreaming) - Added
call_server_stream()trait method toGrpcHandlerfor streaming implementations - Added
StreamingResponsetype 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 byrpc_mode()) - Removed
supports_streaming_responses()method (replaced byrpc_mode()) - Added
rpc_mode() -> RpcModemethod with default implementation returningRpcMode::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 streamsStreamingRequesttype 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_sizeon 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_includecfg 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
StreamingResponseto 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-httpashttpso 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
Anytypes in Python, TypeScript) - Quality validation with mypy, TypeScript compiler, Steep, PHPStan
-
SDL schema reconstruction for runtime validation
-
spikard initcommand: 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:
- Use shared utilities in
codegen/common/for case conversion and escaping - Validate generated code using
codegen/quality/QualityValidator - Add fixtures to
testing_data/for new scenarios - Update this changelog with all changes
- Run
task testto ensure quality gates pass
For detailed guidelines, see: