Contributing to the Kindgi SDK
Source in the repository.
Status: the SDK is in preview and we are not accepting external contributions yet. Issues and security reports are welcome (see SECURITY.md for vulnerabilities). This document describes how the repository works for maintainers.
Requires Node.js 22.12+ and pnpm (the version pinned in package.json's
packageManager field; corepack enable picks it up).
pnpm installpnpm run ci # lint, border check, spec validation, build, typecheck, test, publish checksGitHub runs CI on every pull request that isn't a draft: lint and specs, the build, typecheck, tests and publish checks, the docs site and its samples, and the Python SDK on 3.11 and 3.13. Merging requires them.
One step runs only on your machine: the docs tutorials, which start
kindgi dev with the private runtime image (pnpm run docs:tutorials).
pnpm run ci:local runs all of CI, the tutorials included, on the clean,
pushed commit. It needs pnpm, uv and Docker.
With your coding agent
Section titled “With your coding agent”Changes here are made by coding agents and supervised by people: the agent
does the work, and you read it before it's submitted.
.claude/skills/kindgi-contributing/SKILL.md
tells your agent how. Claude Code loads it by itself in a clone, and
AGENTS.md points other agents to it. It covers:
- where a docs page's source is;
- the checks to run for what changed;
- the pull request's format.
Every pull request carries:
- the agent's
Co-Authored-Bytrailer on its commits; - a
## Checkslist of the checks it ran, with their results; - a
Supervised-by:line with your own name and email.
A check on the pull request asks for any of them that's missing.
-
License headers. Every source file starts with:
// SPDX-License-Identifier: Apache-2.0// Copyright (C) 2026 Kindgi Inc.The pre-commit hook (
scripts/hooks/pre-commit, installed bypnpm install) andpnpm run check:headers(CI) enforce this. -
Self-contained packages. Packages depend only on other packages in this repository and on third-party libraries. Runtime capabilities are expressed as binding interfaces that the host supplies — never as a dependency on a runtime implementation.
pnpm run check:borderenforces this. -
HTTP routes. Adding or changing a route in
@kindgi/apifollows docs/ADDING-A-ROUTE.md. -
Specs.
@kindgi/specs(packages/specs/schemas/) holds the canonical wire contracts. Change a schema there first, then every package that bundles a copy (drift tests enforce equality);pnpm run spec:validatechecks the whole set. -
Public text. The repository's files, and a pull request's title, description and commit messages (the squash commit's message is the title and description), leave out internal process notes: development- phase ids, scratch paths, unresolved placeholders. They also leave out a few names the project doesn't use in public, the runtime's internal name among them: describe things by their role ("the runtime", "the runtime's release").
pnpm run check:refs(CI) checks files; the PR text check checks a pull request's own text, and runs again when you edit the description. A pull request's text also leaves out internal tracking references (ticket and step ids, process rule numbers, release batch names, working session names), and its commits are public too: the commit-msg hook (scripts/hooks/commit-msg, installed bypnpm install) refuses such a message before the commit exists, while rewording is still easy. A real term that looks like one goes inALLOWED_TERMS(scripts/text-scan.mjs), with why. Both say where a name is, never which; the list is kept hashed (scripts/forbidden-names.json, generated outside this repository). The pre-push hook (scripts/hooks/pre-push) also runs a local check when the clone names one (git config kindgi.prePushCheck <program>), and does nothing otherwise.
Releases
Section titled “Releases”All @kindgi/* packages share one version (Changesets fixed group), and
the Python SDK (kindgi on PyPI, sdks/python) ships with that same
version. Nobody sets the Python version by hand:
pnpm run version-packages runs scripts/sync-python-version.mjs after
changeset version, which writes the new version to pyproject.toml and
re-locks uv.lock (it needs uv). CI fails when they differ
(pnpm run check:python-version), and so do both publish jobs. A
prerelease is -alpha.N, -beta.N or -rc.N (aN, bN, rcN on
PyPI); the script refuses any other.
The JVM SDKs (sdks/java) ship with that version too, spelled as npm
spells it (Maven orders it the same way; VersionOrderTest in
sdks/java/codegen holds it to that). pnpm run version-packages runs
scripts/sync-jvm-version.mjs --release, which sets the root pom's own
version and each module's parent version (no other <version>), the
Scala module's version.sbt once it exists, and turns
sdks/java/CHANGELOG.md's Unreleased heading into the version. A
branch whose poms lag main runs the script without --release: that
moves the versions and stamps nothing. CI fails when they differ
(pnpm run check:jvm-version).
- Every user-visible change adds a changeset:
pnpm changeset. Before 1.0, and while release candidates are out, it's apatch: oneminormoves every package to the next minor, which no installed project reaches. A changeset that means to move the release says so in its body:Release-decision: <the version, who decided, and when>, naming a role (the maintainers), never a person, since the body becomes the changelog entry (pnpm run check:changeset-bumps, in CI). Changesets don't reach the JVM SDKs (sdks/java): a user-visible change there adds its line under Unreleased insdks/java/CHANGELOG.md, in the same pull request. - Merging to
mainupdates the "Version Packages" pull request (opened by the org's release GitHub App, so CI runs on it like any other pull request). - Merging that pull request bumps versions and changelogs. Run
pnpm run ci:localon its branch first, so the docs tutorials pass against the release. - Publishing is a manual, approved run of the Release workflow (npm trusted publishing with provenance, and PyPI trusted publishing with attestations — no tokens). The JVM SDKs go to Maven Central first, and npm waits for them: the CLI scaffolds Java and Scala packs on kindgi-pack at its own version.
Maven Central
Section titled “Maven Central”Central takes signed artifacts through the Central Portal, and a version
it has is permanent: it can't be changed or deleted. The Release
workflow's publish-maven job, in the maven-publish environment
(required reviewer):
- Maven (
./mvnw -P release deploy) and sbt (sbt +publishSigned) build, test, sign and stage the artifacts on disk: every jar with its sources and javadoc jars, every file with its.asc. scripts/check-jars.mjschecks those files: the artifacts we publish and no others, their version, the pom metadata Central requires, each pom's runtime dependencies against its list, classes only undercom/kindgi/and no test code, the LICENSE and NOTICE, and no forbidden name in any class, source or page. The Java and Scala workflows run it on every pull request, on an unsigned staging.scripts/central-bundle.mjsbundles the same files, verifying every signature against the release key's fingerprint, and uploads them as one deployment, so a version's Java and Scala artifacts publish together or not at all. A dry run has the Portal validate the deployment and then drops it; nothing is published. A version Central already has is skipped.- Before npm publishes, its job waits until Central serves every JVM
artifact at the CLI's version (
central-bundle.mjs on-central), so npm never gets a CLI whose kindgi-pack doesn't resolve. A release that skipped Maven stops there.
The environment holds the Portal user token (MAVEN_CENTRAL_USERNAME,
MAVEN_CENTRAL_PASSWORD), the signing key (MAVEN_GPG_PRIVATE_KEY,
MAVEN_GPG_PASSPHRASE) and the key's public fingerprint (the variable
MAVEN_GPG_FINGERPRINT). The key is imported into a keyring of the job's
own and deleted at its end. A new module is published only once it's listed
in check-jars.mjs's ARTIFACTS, with its runtime dependencies.
To stage and check locally (a JDK 17 and sbt): ./mvnw -P release -Dgpg.skip=true deploy in sdks/java, sbt +publish in sdks/scala, then
node scripts/check-jars.mjs sdks/java/target/central-staging sdks/scala/target/sona-staging.
Release candidates
Section titled “Release candidates”A release can go out first as release candidates, X.Y.Z-rc.N, that no
one gets without asking for them by version:
- npm: they publish under the dist-tag
next, neverlatest(scripts/release-dist-tag.mjs), and the Release workflow checks thatlatestis still a release afterwards. - PyPI: they're
X.Y.ZrcN, which pip and uv install only when asked for by version. - Maven Central: they're
X.Y.Z-rc.N, published like a release (Central has no tags). A build gets one only by naming it, or through a version range: Maven doesn't keep pre-releases out of ranges. They stay on Central: an rc CLI'skindgi init --template=javaandkindgi buildneed its kindgi-pack there. - The public docs come from releases only.
- An rc CLI keeps its set together:
kindgi initpins the rc packages, a Python pack getskindgi>=X.Y.ZrcN,<…, and its hints name@kindgi/cli@X.Y.Z-rc.N.
The steps:
- Start:
pnpm changeset pre enter rcin a pull request (.changeset/pre.json). From then on, the "Version Packages" pull request versionsX.Y.Z-rc.0, thenrc.1, and so on. Publish each as above. While candidates are out, every changeset is apatch: aminorwould move the release itself. - Release:
pnpm changeset pre exitin a pull request. The next "Version Packages" pull request versionsX.Y.Z, its changelog gathering every rc's changesets, and that publishes underlatest.
The runtime image follows the same versions: the runtime's release takes
X.Y.Z-rc.N, and never moves preview for one.