Thanks for your interest in contributing to Codeep! This document covers the practical bits you need to get a change merged: setup, the build/test loop, code style, and how to propose a change.
Codeep is an autonomous AI coding agent that runs in the terminal and integrates with editors via the Agent Client Protocol. It's written in TypeScript, built with Node.js, and distributed as an npm package, standalone binaries, and a Homebrew formula.
git clone https://github.com/VladoIvankovic/Codeep.git
cd Codeep
npm install
npm test # 1300+ tests, should be green
npm run build # tsc + import fixup → dist/You need Node.js ≥ 20 (see engines in package.json). The distributed
standalone binaries are produced in CI with bun build --compile (see
.github/workflows/release-binaries.yml) — there is no local binary build.
npm is the canonical package manager. package-lock.json is committed;
other lockfiles (bun.lock, yarn.lock, pnpm-lock.yaml) are .gitignored.
- Install dependencies:
npm install - Install in CI / from a clean checkout:
npm ci(strictly respects the lockfile) - Add a dependency:
npm install <pkg>(updatespackage-lock.jsontoo)
Do not introduce a bun.lock / yarn.lock — the dual-lockfile drift that
prompted the single-manager decision is exactly what we're avoiding.
| Task | Command |
|---|---|
| Run the TUI in development | npm run dev |
| Run the test suite once | npm test |
| Run tests in watch mode | npm run test:watch |
| Run tests with coverage | npm run test:coverage |
| Type-check without emitting | npx tsc --noEmit |
Build to dist/ |
npm run build |
| Build standalone binaries | CI only — bun build --compile (see release-binaries.yml) |
npx tsc --noEmitis clean (0 errors). The project usesstrict: trueand"types": ["node"]; anyCannot find name 'fs'/'path'/…error means something regressed in the type config.npm testis green. If you add a feature, add a test — the suite is the regression net that lets us refactor aggressively.npm run buildsucceeds. The build wipesdist/first (rm -rf dist) so a stale output can't mask a real failure.- No
as anyin new code without a justifying comment. The settings screen, for example, was recently de-as any-ed; keep that bar.
- Pure helpers and data-shaping functions (
toolCallMeta,buildRawOutput, registry lookups, formatters) → always. Cheap to test, high regression value. - Filesystem bootstrap and config mutations (
initWorkspace, checkpoint creation, settings writes) → always, using an isolated tmpdir per test (seevitest.setup.ts+ the pattern incheckpoints.test.ts). - Agent-loop integration (end-to-end prompt → tool call → response) → covered
by
toolExecution.test.ts; don't duplicate that in a new test. - ACP server request handlers (
session/new,session/prompt, …) → currently hard to unit-test because they live inside thestartAcpServer()closure. There's a tracked refactor to extract them into a testable class; until then, prefer testing the helpers they delegate to.
- Indentation: 2 spaces.
- Functions: prefer arrow functions; use
functiondeclarations only for hoisted helpers or when you needthis. - TypeScript:
strict: true. Avoidas any; when a cast is unavoidable (e.g. a heterogeneous config write), centralise it in one typed helper and document why. - Imports: ESM (
import … from '…'), notrequire(). Node built-ins use thenode:prefix inside dependencies but plain'fs'/'path'is fine in our own code (the@types/noderesolution handles both). - Comments: explain why, not what. The codebase leans heavily on
inline rationale comments (see
acp/transport.tsfor the house style); when you add a non-obvious decision, leave a note for the next reader. - Naming:
camelCasefor variables/functions,PascalCasefor types and interfaces,UPPER_SNAKE_CASEfor constants.
If you're new to the codebase, read in this order:
README.md— feature tour and the "Architecture" / "Libraries" sections.src/renderer/main.ts— TUI entry point; the dispatch loop lives here.src/renderer/commands/registry.ts— the single source of truth for slash-command metadata;App.tsandHelp.tsboth derive from it.src/config/index.ts— the on-disk config (Conf<ConfigSchema>) and every accessor/mutator.ConfigSchemais the exported type that other modules (e.g.Settings.ts) key off.src/utils/agent.ts— the agent loop: prompt → tool calls → response.src/acp/server.ts— the ACP adapter that editors (Zed, VS Code) talk to over newline-delimited JSON-RPC on stdio.CHANGELOG.md— recent history; the[Unreleased]section lists in-flight changes.
- Open an issue first if the fix isn't obvious — saves wasted work.
- Branch from
main, commit with a clear message (fix: …,feat: …,docs: …,refactor: …,test: …). - Open a PR against
main. CI runstsc --noEmit+ the full vitest suite.
- Open an issue describing the problem and the shape of the solution. We'll discuss scope before you write code.
- Add tests as you go — a PR that lands untested code sets a precedent we then have to clean up.
- If your change is user-visible, add a
CHANGELOG.mdentry under[Unreleased](see the existing entries for format).
Adding support for a new LLM provider is a common contribution. The flow:
- Add the provider to
src/config/providers.ts(id, name, models, pricing). - Wire its API adapter in
src/api/(most providers are OpenAI-compatible; reuseopenaiCompatible.tswhere possible). - Add the env-var name and any auth quirks to the provider definition.
- Test the chat path against a real key if you can; otherwise document the
expected request/response shape and mark it
[needs testing]in the PR.
Releases are tag-driven: pushing a v* tag triggers
.github/workflows/release-binaries.yml, which builds cross-platform
binaries, publishes to npm, and bumps the Homebrew formula. You don't need
to run a release to contribute — this is here for context.
Before tagging a release:
- Bump
versioninpackage.json. - Move
[Unreleased]→[ vX.Y.Z — date ]inCHANGELOG.md. - Ensure
npm test,npx tsc --noEmit, andnpm run buildare all clean.
By contributing, you agree that your contributions are licensed under the Apache 2.0 License that covers the project.