Contributing to factory
Thanks for considering a contribution. This guide covers everything you need to develop, test, and submit changes.
Setup
Requirements: Node 22+, npm 10+. The repo pins Node via .nvmrc, so nvm use picks the right version.
git clone https://github.com/vilaca/factory.git
cd factory
nvm use # picks Node 22 from .nvmrc
npm install
npm link # makes `factory` available on PATH for local testingTo run a development build with auto-rebuild on save:
npm run devTo run the binary directly without npm link:
node dist/index.js [...flags]Branch and commit conventions
- Branch from
main. Use a feature-style name:feature/<short-slug>,fix/<short-slug>,docs/<short-slug>. - Commits follow Conventional Commits (
feat:,fix:,refactor:,test:,docs:,chore:). The commit history shows the style this project uses. - Keep commits focused. A bug fix is one commit; refactors that touch many files can be split per concern.
- One commit per feature on
main(squash-on-merge is the project default).
Testing
npm test # full suite: lint + typecheck + unit + e2e
npm run test:unit # unit tests only (fast, ~6s)
npm run test:e2e # end-to-end tests via PTY harness
npm run test:e2e:fast # skip the tsc compile step (only valid after a recent build)
npm run coverage # generates HTML + lcov coverage in coverage/The unit tests use node:test (Node's built-in runner). E2E tests spawn the compiled binary in a real PTY via test/cli-harness.ts. Mock servers for Ollama and GitHub Copilot live in test/mock-*.ts.
Before opening a PR, run npm run lint && npx tsc --noEmit && npm test.
Adding a provider
The codebase lives in src/providers/. There are two cases.
OpenAI-compatible (most providers — Cerebras, Groq, Mistral, Vercel, OpenRouter, Workers AI, llama.cpp, Google AI Studio, etc.):
- Implement the
Providerinterface fromsrc/providers/types.tsusing the shared helpers insrc/providers/openai/(buildChatBody,sendOpenAiChat,streamOpenAiChat). Look atsrc/providers/cerebras.tsfor a minimal example. - Add an entry to
src/providers/descriptors.tswith label, aliases, env-var names, and default host. - Wire up the factory in
src/providers/registry.ts. - Add unit tests in
test/unit/providers/<provider>-provider.test.ts. Follow the patterns in the existing provider tests. - Document the provider's env vars in
docs/providers.mdand add an example to--helpinsrc/cli/args.ts:printUsage.
Native protocol (Anthropic, Ollama, Cohere):
These don't share the OpenAI surface and have their own request/response shapes. Look at src/providers/anthropic.ts for the cleanest example. HuggingFace is a hybrid — its transport is the @huggingface/inference SDK but it reuses openai/tool-calls.ts for tool-call parsing. Define narrow types for the SDK responses; do not use any (the lint rule rejects it).
Filing issues
Use the issue templates in .github/ISSUE_TEMPLATE/. For bug reports, include:
factory --versionoutput- Operating system and Node version
- Provider and model
- Repro steps
- Relevant excerpt from the session log (
~/.factory/sessions/) — redact tokens
For security issues, do not open a public issue. See SECURITY.md for the disclosure process.
Versioning
The project follows Semantic Versioning. Pre-1.0, breaking changes still bump the minor version (0.x.0); patch versions are reserved for non-breaking fixes. Tag releases as vX.Y.Z.
Code style
- TypeScript strict mode is on, plus
noUncheckedIndexedAccessandnoImplicitReturns. Treat warnings fromtsc --noEmitas errors. - ESLint enforces complexity ceilings (
max-lines: 700,max-lines-per-function: 300,max-statements: 60,complexity: 25,cognitive-complexity: 30). Functions that exceed limits use// eslint-disable-next-line ... -- TODO(complexity): <plan>. Don't add new debt. anyiserrorinsrc/. Use proper types or narrowunknown. Tests are allowed to useanyfor mocks.- Prefer editing existing files over creating new ones. New helper modules need a clear domain reason.
Architecture overview
See ARCHITECTURE.md for a full module map and data-flow walkthrough.