Contributing to KindLM
Thank you for considering contributing to KindLM. This document covers how to set up your development environment, the coding standards we follow, and how to submit changes.
Quick Start
# Clone
git clone https://github.com/kindlm/kindlm.git
cd kindlm
# Install
npm install
# Build all packages
npm run build
# Run tests
npm run test
# Run the CLI locally
cd packages/cli
npm run dev -- test ../../examples/basic-prompt-test/kindlm.yaml
Repository Structure
packages/
core/ # @kindlm/core — shared logic, assertions, engine
cli/ # @kindlm/cli — CLI entry point
cloud/ # @kindlm/cloud — Cloudflare Workers API
See 01-PROJECT_STRUCTURE.md for full details on package boundaries.
Development Workflow
1. Pick an Issue
- Check the issue tracker for
good first issuelabels - If you want to work on something that doesn't have an issue, open one first to discuss the approach
- Assign yourself to the issue so others know it's being worked on
2. Branch
git checkout -b feat/your-feature # for features
git checkout -b fix/your-fix # for bug fixes
git checkout -b docs/your-docs # for documentation
3. Code
- Write code following the standards below
- Add or update tests for any logic changes
- Run
npm run typecheckandnpm run lintbefore committing
4. Test
# Run all tests
npm run test
# Run tests for a specific package
cd packages/core && npx vitest
# Run a specific test file
cd packages/core && npx vitest src/assertions/schema.test.ts
5. Submit a Pull Request
- Fill out the PR template
- Link the related issue
- Ensure CI passes
- Request review from a maintainer
Coding Standards
TypeScript
- Strict mode —
"strict": truein tsconfig. Noanyunless absolutely necessary (and documented why). - Explicit return types on exported functions.
- Prefer
interfaceovertypefor object shapes that might be extended. - Prefer
constoverlet. Never usevar. - No classes unless needed — prefer plain functions and objects. Classes are acceptable for adapters (OpenAI, Anthropic) where statefulness makes sense.
- Named exports only — no default exports (except in
bin/kindlm.ts).
Naming
| Thing | Convention | Example |
|---|---|---|
| Files | kebab-case | tool-calls.ts |
| Functions | camelCase | evaluateGates() |
| Interfaces/Types | PascalCase | AssertionResult |
| Constants | UPPER_SNAKE_CASE | MAX_TURNS |
| Zod schemas | PascalCase + Schema | ModelSchema |
| Test files | *.test.ts | schema.test.ts |
Error Handling
- Use typed errors (
ProviderError,ConfigError) not genericError - Always include an error code for programmatic handling
- Include context in error messages: what was expected vs. what happened
- Never swallow errors silently
Testing
- Use Vitest
- Test files live next to the source file:
schema.ts→schema.test.ts - Every assertion type needs tests for: pass, fail, edge cases
- Mock provider adapters in engine tests — never call real APIs in unit tests
- Integration tests (in
packages/cli/tests/integration/) can use fixture configs
// Example test
import { describe, it, expect } from "vitest";
import { SchemaAssertion } from "./schema";
describe("SchemaAssertion", () => {
it("passes when JSON is valid against schema", async () => {
const assertion = new SchemaAssertion("json", "./fixtures/test.schema.json");
const results = await assertion.evaluate({
outputText: '{"name": "test", "value": 42}',
toolCalls: [],
configDir: __dirname,
});
expect(results).toHaveLength(2); // parse + schema
expect(results.every((r) => r.passed)).toBe(true);
});
it("fails with SCHEMA_PARSE_ERROR when output is not JSON", async () => {
const assertion = new SchemaAssertion("json", "./fixtures/test.schema.json");
const results = await assertion.evaluate({
outputText: "not json at all",
toolCalls: [],
configDir: __dirname,
});
expect(results[0].passed).toBe(false);
expect(results[0].failureCode).toBe("SCHEMA_PARSE_ERROR");
});
});
Commits
Follow Conventional Commits:
feat(core): add tool call argument schema validation
fix(cli): correct exit code on provider timeout
docs: update config reference for judge assertions
test(core): add edge case tests for PII detection
chore: update dependencies
Adding a New Assertion Type
- Create
packages/core/src/assertions/your-assertion.ts - Implement the
Assertioninterface - Add it to
packages/core/src/assertions/registry.ts - Add the Zod schema for its config to
packages/core/src/config/schema.ts - Write tests in
your-assertion.test.ts - Add documentation to
docs/assertions.md - Add an example to a template config in
templates/
Adding a New Provider
- Create
packages/core/src/providers/your-provider.ts - Implement the
ProviderAdapterinterface - Register it in
packages/core/src/providers/registry.ts - Add its config schema to the
ProvidersSchemainschema.ts - Add pricing data if available
- Write tests with mocked HTTP responses
- Add documentation to
docs/providers.md
Release Process
We use Changesets for versioning:
# After making changes, create a changeset
npx changeset
# Follow the prompts to describe your change and select the bump type
# Commit the changeset file with your PR
Releases are automated via GitHub Actions when changesets are merged to main.
CI/CD
CI Workflow
Every push to main and every pull request triggers the CI workflow (.github/workflows/ci.yml):
- Checkout + Node.js 20 setup
npm ci— install dependenciesnpx turbo run build— build all packagesnpx turbo run typecheck— type-check all packagesnpx turbo run lint— lint all packagesnpx turbo run test— run all test suites
The workflow uses concurrency groups to cancel in-progress CI runs for the same branch when a new commit is pushed.
Release Workflow
Merging changesets to main triggers the release workflow (.github/workflows/release.yml):
- If there are pending changesets, the workflow opens/updates a "Version Packages" PR
- When that PR is merged, the workflow publishes updated packages to npm
- Uses
changesets/action@v1for automated version bumping and publishing - Requires
NPM_TOKENsecret to be set in the repository
Reusable GitHub Action
The project includes a reusable GitHub Action at .github/kindlm-action/action.yml for running KindLM in CI:
# In your project's CI workflow:
- uses: kindlm/kindlm/.github/kindlm-action@main
with:
config: kindlm.yaml # Path to config file
reporter: junit # Output format
gate: 95 # Minimum pass rate
compliance: true # Generate compliance report
token: ${{ secrets.KINDLM_TOKEN }} # Upload to Cloud
Code of Conduct
Be kind. Be constructive. Assume good intent. We're building a tool about reliability — let's be reliable collaborators too.
License
KindLM uses a dual-license open-core model:
@kindlm/core+@kindlm/cli— MIT License. Free forever.@kindlm/cloud— AGPL-3.0. Source available, but commercial use requires a license.
By contributing to core or cli, you agree that your contributions will be licensed under MIT. Contributions to cloud are licensed under AGPL-3.0. This split ensures the CLI remains fully open while the Cloud business model is sustainable.