Thank you for considering a contribution. This document covers everything you need to go from a fresh checkout to an open pull request.
- Development setup
- Running tests
- Code standards
- Adding a new adapter
- Submitting a pull request
- Versioning and releases
- Node.js 20 or later (
node --version) - One or both CLI agents on your
PATH(for integration / e2e tests):cursor-agentclaude
git clone https://github.com/casabre/coding-agent-a2a
cd coding-agent-a2a
npm installnpm run build # compile TypeScript once
npm run dev # tsx watch — rebuilds on save, no need to restartcp .env.example .env
# Edit .env: set AGENT_ADAPTER and AGENT_REPO_PATH at minimumThe server starts with npm start (compiled build) or npm run dev (watch mode).
npm test # run once
npm run test:watch # rerun on file change
npm run test:coverage # run with coverage report (must reach 100%)The test suite uses Vitest. All external processes and I/O are mocked; no CLI agent is required.
CURSOR_E2E=1 npm run test:e2eThese tests spawn the actual CLI agent. Guard them behind CURSOR_E2E=1 so they never run in CI (where the CLI is unavailable).
CI enforces 100% line, branch, and function coverage on all src/ files (except src/types.ts and src/adapters/base.ts, which are type-only). Any pull request that reduces coverage below 100% will fail the CI gate.
When you add a new source file, add the corresponding tests before opening a PR.
strict: trueis enforced. Do not useanywithout explicit justification.- Use
import typefor type-only imports to keep the compiled output clean. - The project uses ES modules (
"type": "module"). All local imports must include the.jsextension.
npm run lint # ESLint 9 with @typescript-eslint/recommendedLint and typecheck run in CI. A PR with errors in either will not be merged.
- Keep functions under 50 lines. Extract helpers when logic grows.
- Keep files focused on one responsibility. See docs/architecture.md for module boundaries.
Write comments to explain why, not what. Public interfaces and exported functions must have TSDoc (/** */ blocks). Inline comments are for non-obvious invariants, workarounds, or constraints.
Use console.warn or console.error only for operational messages (startup, warnings, genuine errors). Remove debug console.log calls before opening a PR.
An adapter is a stateless class that implements CodingAgentAdapter from src/adapters/base.ts. It teaches the server how to invoke a specific CLI tool.
touch src/adapters/my-agent.ts// src/adapters/my-agent.ts
import type { CodingAgentAdapter, RunOptions, AdapterCapabilities, AgentEvent } from './base.js';
import { parseSharedNdjsonEvent } from './ndjson-helpers.js';
export class MyAgentAdapter implements CodingAgentAdapter {
readonly name = 'my-agent';
readonly capabilities: AdapterCapabilities = {
streaming: true,
sessionResume: false,
shellApproval: false,
};
resolveBinary(): string {
return process.env['MY_AGENT_PATH']?.trim() || 'my-agent';
}
buildArgv(options: RunOptions): string[] {
const args = ['--stream-json'];
if (options.model) args.push('--model', options.model);
args.push(options.task);
return args;
}
parseEvent(line: string): AgentEvent | null {
// Re-use the shared parser if the CLI uses the same NDJSON schema,
// or write a custom parser here.
return parseSharedNdjsonEvent(line);
}
isApprovalPrompt(_line: string): boolean {
return false;
}
approvalResponse(): string {
return 'y';
}
}If your CLI uses a different NDJSON schema, implement parseEvent directly rather than delegating to parseSharedNdjsonEvent.
// src/adapters/index.ts — add your adapter to the registry
import { MyAgentAdapter } from './my-agent.js';
const registry: Record<string, CodingAgentAdapter> = {
cursor: new CursorAdapter(),
'claude-code': new ClaudeCodeAdapter(),
'my-agent': new MyAgentAdapter(), // ← add this line
};Create tests/unit/adapters/my-agent.test.ts. Aim for full coverage of:
resolveBinary(default path and env override)buildArgv(all option combinations)parseEvent(valid events, unknown types, malformed JSON)isApprovalPrompt(if applicable)
Use the existing tests/unit/adapters/cursor.test.ts as a template.
- Add your adapter to the
AGENT_ADAPTERrow in docs/deployment.md. - Add a row to the adapters table in docs/development.md.
- If the adapter has a custom env var for the binary path, add it to
.env.example(commented out).
-
Fork the repository and create a branch from
main:git checkout -b feat/my-feature
-
Make your changes following the standards above.
-
Verify locally before pushing:
npm run lint && npm run typecheck && npm run test:coverage
All three must pass with no errors and 100% coverage.
-
Update docs if you changed any user-facing behaviour, environment variables, MCP tools, or A2A responses.
-
Update CHANGELOG.md — add your changes under
## [Unreleased]. -
Open a PR against
main. Fill in the PR template:- What changed and why
- How to test it manually (if applicable)
- Any breaking changes
-
CI will run lint, typecheck, and test:coverage automatically. A PR is only eligible to merge when all checks pass.
Versions follow Semantic Versioning.
The version is not stored in package.json — it is derived from the git tag at publish time (analogous to Python's setuptools-scm).
To release:
# Update CHANGELOG.md: rename [Unreleased] → [x.y.z] — YYYY-MM-DD
git add CHANGELOG.md
git commit -m "chore: release v1.0.0"
git tag v1.0.0
git push origin main --tagsPushing the tag triggers the publish GitHub Actions workflow, which:
- Runs the full CI gate.
- Sets
package.jsonversion from the tag. - Builds and publishes to npm with provenance attestation.