Thank you for contributing to KODE SDK. This guide explains the requirements and process for submitting PRs.
- Code changes
- Documentation changes
- Example changes
- Test changes
- Release-related changes
- Search existing issues and documentation to avoid duplicate work.
- For major features or behavioral changes, consider opening an issue or discussion first.
- Each PR should focus on one thing; avoid mixing unrelated changes.
- Create a new branch from the
mainbranch. - Suggested branch naming:
feat/<short-desc>,fix/<short-desc>,docs/<short-desc>.
- Required: purpose, scope of changes, impact/compatibility, test results.
- Recommended: related issue/requirement links, screenshots or logs (if applicable).
- Avoid mixing unrelated changes in a single PR.
- Avoid unnecessary formatting or large-scale reorganization unless necessary and explained.
- Tests related to your changes must pass.
- New features must include tests or explain why not.
- Avoid obvious performance regressions and security risks.
- Follow existing TypeScript style, module boundaries, and public API stability.
- Use only one package manager per PR and update only the corresponding lock file:
package-lock.jsonorpnpm-lock.yaml. - Do not commit build artifacts like
dist/unless required for release or requested by maintainers.
- Avoid breaking changes in principle.
- If unavoidable, mark
BREAKINGin the PR title or description and submit a detailed report. - Provide transition solutions such as compatibility layers, deprecation periods, and migration steps.
- The report should include: scope of impact, migration steps, transition strategy, risks, and rollback plan.
npm run test:unitmust pass.- When involving DB, provider, sandbox, or cross-module flows, run
test:integrationortest:e2e. - New features require at least unit tests; add integration or end-to-end tests when necessary.
- Place test files in
tests/unit,tests/integration, ortests/e2e. - Use
*.test.tsnaming convention. - Use
TestRunnerandexpectfromtests/helpers/utils.ts. - Use
createUnitTestAgentandcreateIntegrationTestAgentfromtests/helpers/setup.tswhen needed. - Each test file exports
export async function run() { ... }. - For complex flows, use
tests/helpers/integration-harness.ts. - Refer to
../../tests/README.mdas the specification reference.
- Cover normal paths, critical boundaries, and failure paths.
- New features should cover core behavior and key boundary scenarios at minimum.
- Unit tests should avoid real API/network dependencies; use integration or e2e for real model testing.
- Assertions must verify results or side effects (return status, events, persisted results, etc.).
- Use
cleanupmechanisms to clean up temporary directories and resources. - Avoid flaky factors (randomness, time dependencies); fix inputs or use mocks when necessary.
Unit test example (from tests/unit/utils/agent-id.test.ts):
import { generateAgentId } from '../../../src/utils/agent-id';
import { TestRunner, expect } from '../../helpers/utils';
const runner = new TestRunner('AgentId');
// Crockford Base32 character set (used for timestamp encoding)
const CROCKFORD32 = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
runner
.test('Generated AgentId is unique and contains timestamp', async () => {
const id1 = generateAgentId();
const id2 = generateAgentId();
// Verify uniqueness
expect.toEqual(id1 !== id2, true);
// Verify format: agt-{timestamp 10 chars}{random 16 chars}
expect.toContain(id1, 'agt-');
expect.toEqual(id1.length, 4 + 10 + 16); // agt- + timestamp + random
// Verify timestamp part (first 10 chars) is valid Crockford Base32
const timePart = id1.slice(4, 14);
for (const char of timePart) {
expect.toEqual(
CROCKFORD32.includes(char),
true,
`Timestamp character '${char}' is not valid Crockford Base32`
);
}
});
export async function run() {
return await runner.run();
}Integration test example (from tests/integration/features/events.test.ts):
import { collectEvents } from '../../helpers/setup';
import { TestRunner, expect } from '../../helpers/utils';
import { IntegrationHarness } from '../../helpers/integration-harness';
const runner = new TestRunner('Integration Test - Event System');
runner.test('Subscribe to progress and monitor events', async () => {
console.log('\n[Event Test] Test objectives:');
console.log(' 1) Verify progress stream contains text_chunk and done events');
console.log(' 2) Verify monitor channel broadcasts state_changed');
const harness = await IntegrationHarness.create();
const monitorEventsPromise = collectEvents(harness.getAgent(), ['monitor'], (event) => event.type === 'state_changed');
const { events } = await harness.chatStep({
label: 'Event Test',
prompt: 'Please introduce yourself briefly',
});
const progressTypes = events
.filter((entry) => entry.channel === 'progress')
.map((entry) => entry.event.type);
expect.toBeGreaterThan(progressTypes.length, 0);
expect.toBeTruthy(progressTypes.includes('text_chunk'));
expect.toBeTruthy(progressTypes.includes('done'));
const monitorEvents = await monitorEventsPromise;
expect.toBeGreaterThan(monitorEvents.length, 0);
await harness.cleanup();
});
export async function run() {
return runner.run();
}End-to-end test example (from tests/e2e/scenarios/long-run.test.ts):
import path from 'path';
import fs from 'fs';
import { createUnitTestAgent, collectEvents } from '../../helpers/setup';
import { TestRunner, expect } from '../../helpers/utils';
const runner = new TestRunner('E2E - Long-running Flow');
runner
.test('Todo, events, and snapshots work together', async () => {
const { agent, cleanup, storeDir } = await createUnitTestAgent({
enableTodo: true,
mockResponses: ['First turn', 'Second turn', 'Final response'],
});
const monitorEventsPromise = collectEvents(agent, ['monitor'], (event) => event.type === 'todo_reminder');
await agent.setTodos([{ id: 't1', title: 'Write tests', status: 'pending' }]);
await agent.chat('Start task');
await agent.chat('Continue execution');
const todos = agent.getTodos();
expect.toEqual(todos.length, 1);
const reminderEvents = await monitorEventsPromise;
expect.toBeGreaterThan(reminderEvents.length, 0);
await agent.updateTodo({ id: 't1', title: 'Write tests', status: 'completed' });
await agent.deleteTodo('t1');
const snapshotId = await agent.snapshot();
expect.toBeTruthy(snapshotId);
const snapshotPath = path.join(storeDir, agent.agentId, 'snapshots', `${snapshotId}.json`);
expect.toEqual(fs.existsSync(snapshotPath), true);
await cleanup();
});
export async function run() {
return await runner.run();
}- User-visible changes require updating
docs. - Keep
docs/enanddocs/zh-CNin sync. - Behavior or API changes require updating examples.
- If documentation cannot be synced, explain why and provide a catch-up plan.
- Use Markdown with a single
#title at the top; organize content with##/###without skipping levels. - Code blocks must specify the language (e.g.,
ts,bash,json). - Use relative path links for in-project documentation.
- Public API references must match exports in
src/index.ts. - New documentation should be added to the README documentation table.
- No strict format required, but must clearly describe the changes.
- Use
.github/pull_request_template.md.
- At least 1 maintainer approval is required before merging.
- High-risk changes should have additional reviewers.
CHANGELOGis not currently maintained.- Change history is based on
git log. - Version numbers are handled by maintainers.
- Never commit keys, tokens, or private data.
- New dependencies require justification and license compatibility verification.
- DCO or CLA is not currently required.