Thank you for your interest in contributing to AgentX! This guide will help you get started with the development workflow.
- Node.js >= 20.0.0
- pnpm >= 11.0.0
- Docker (for PostgreSQL, Redis, and other services)
# Clone the repository
git clone https://github.com/your-org/agentx.git
cd agentx
# Install dependencies
pnpm install
# Copy environment variables
cp .env.example .env
# Start development services (PostgreSQL, Redis)
docker compose up -d# Build all packages
pnpm build
# Start development mode (watch & rebuild)
pnpm dev
# Run type checking
pnpm typecheck
# Run linting
pnpm lint
# Run tests
pnpm testUse descriptive branch names with prefix with the type of change:
feat/- New features (e.g.,feat/add-auth-middleware)fix/- Bug fixes (e.g.,fix/resolve-login-error)docs/- Documentation updates (e.g.,docs/update-api-docs)chore/- Maintenance tasks (e.g.,chore/update-dependencies)refactor/- Code refactoring (e.g.,refactor/simplify-auth-flow)test/- Adding or updating tests (e.g.,test/add-unit-tests)
We use Conventional Commits for all commit messages. This enables automatic changelog generation and semantic versioning.
Format:
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
Types:
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code style changes (formatting, missing semicolons, etc.)refactor: Code refactoring without functionality changesperf: Performance improvementstest: Adding or correcting testschore: Build process or auxiliary tool changesci: CI/CD changes
Examples:
feat(auth): add JWT token refresh mechanism
fix(api-server): resolve null pointer in user handler
docs(readme): update installation instructions
chore(deps): upgrade TypeScript to 5.4.5
- Create a branch from
mainfollowing the naming convention - Make your changes in small, focused commits
- Write tests for new functionality
- Update documentation if needed
- Push your branch and create a PR
- Fill out the PR template completely
- Request review from maintainers
- Address feedback promptly
- Squash and merge once approved
All packages use TypeScript strict mode. Key rules:
- No
anytypes in public interfaces - Explicit return types for exported functions
- Use
unknowninstead ofanywhen type is uncertain - Leverage TypeScript's type inference where appropriate
// Good
export function processUser(user: User): ProcessedUser {
// implementation
}
// Bad
export function processUser(user: any): any {
// implementation
}We use ESLint with the following configuration:
@typescript-eslint/eslint-pluginfor TypeScript-specific ruleseslint-config-prettierto disable formatting ruleseslint-plugin-prettierto run Prettier as an ESLint rule
Run linting:
pnpm lintFix auto-fixable issues:
pnpm lint --fixAll code is formatted with Prettier. Configuration is in .prettierrc.js:
module.exports = {
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
printWidth: 100,
bracketSpacing: true,
arrowParens: 'always',
};Run formatting:
pnpm formatCheck formatting without making changes:
pnpm format:checkUse absolute imports with the @agentx/ prefix for internal packages:
// Good
import { UserService } from '@agentx/auth';
import { Logger } from '@agentx/shared';
// Bad
import { UserService } from '../../packages/auth/src';
import { Logger } from '../../../shared/src';Import order:
- Node.js built-in modules
- External packages
- Internal packages (
@agentx/*) - Local modules (relative imports)
# Run all tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Run tests with coverage
pnpm test:coverage
# Run end-to-end tests
pnpm test:e2eWe use Vitest for testing. Tests should be:
- Fast: Unit tests should run in milliseconds
- Isolated: No dependencies on external services
- Deterministic: Same result every time
- Readable: Clear test names and assertions
Test file structure:
packages/
auth/
src/
services/
user.service.ts
user.service.test.ts # Test file next to source
Example test:
import { describe, it, expect, vi } from 'vitest';
import { UserService } from './user.service';
describe('UserService', () => {
describe('createUser', () => {
it('should create a user with valid data', async () => {
const service = new UserService();
const user = await service.createUser({
email: 'test@example.com',
name: 'Test User',
});
expect(user).toBeDefined();
expect(user.email).toBe('test@example.com');
});
it('should throw error for invalid email', async () => {
const service = new UserService();
await expect(
service.createUser({
email: 'invalid',
name: 'Test User',
}),
).rejects.toThrow('Invalid email');
});
});
});- Minimum coverage threshold: 80%
- Focus on testing business logic and edge cases
- Don't chase 100% coverage at the expense of test quality
All internal packages use the @agentx/ scope:
@agentx/shared
@agentx/auth
@agentx/api-server
@agentx/runtime
@agentx/provider
agentx/
├── apps/ # Applications
│ └── api-server/ # Main API server
├── packages/ # Shared packages
│ ├── shared/ # Common utilities
│ │ ├── shared/ # @agentx/shared
│ │ ├── cache/ # @agentx/cache
│ │ └── ...
│ ├── auth/ # Authentication
│ ├── runtime/ # Runtime engine
│ └── ...
├── tooling/ # Development tools
│ └── eslint-plugin/ # Custom ESLint rules
├── tests/ # End-to-end tests
└── docs/ # Documentation
-
Create the package directory:
mkdir -p packages/<category>/<package-name>
-
Initialize package.json:
{ "name": "@agentx/<package-name>", "version": "0.1.0", "private": true, "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", "scripts": { "build": "tsc --build", "typecheck": "tsc --noEmit", "test": "vitest run", "test:coverage": "vitest run --coverage", "lint": "eslint src/" }, "devDependencies": { "@vitest/coverage-v8": "1.6.0", "typescript": "5.4.5", "vitest": "1.6.0" } } -
Add to pnpm-workspace.yaml (if new category):
packages: - 'packages/*/*' - 'apps/*' - 'tooling/*'
-
Create tsconfig.json:
{ "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", "rootDir": "./src" }, "include": ["src/**/*"] } -
Create src/index.ts as the entry point
Use workspace protocol for internal dependencies:
{
"dependencies": {
"@agentx/shared": "workspace:*",
"@agentx/auth": "workspace:*"
}
}Dependency rules:
- Shared packages should not depend on application packages
- Follow the dependency hierarchy:
shared→core→features→apps - Avoid circular dependencies
All public exports must have JSDoc comments:
/**
* Processes a user authentication request.
*
* @param credentials - The user credentials to validate
* @returns Promise resolving to the authentication result
* @throws {AuthenticationError} If credentials are invalid
*
* @example
* ```typescript
* const result = await authenticate({
* email: 'user@example.com',
* password: 'securePassword123'
* });
* ```
*/
export async function authenticate(credentials: Credentials): Promise<AuthResult> {
// implementation
}Each package should have a README.md with:
- Package description
- Installation instructions
- Usage examples
- API reference (if applicable)
Major architectural decisions should be documented in:
ARCHITECTURE.md- High-level system architecturedocs/design/- Detailed design documentsdocs/guides/- Development guides
Before submitting a PR, ensure:
- Typecheck passes:
pnpm typecheck - Lint passes:
pnpm lint - Tests pass:
pnpm test - Coverage threshold met:
pnpm test:coverage(minimum 80%) - Formatting correct:
pnpm format:check - Documentation updated (if applicable)
- Changeset added (if publishing packages):
pnpm changeset - Commit messages follow Conventional Commits
- PR description filled out completely
- No secrets or credentials in code
- No
anytypes in public interfaces
- Issues: Report bugs or request features via GitHub Issues
- Discussions: Use GitHub Discussions for questions and ideas
- Documentation: Check
docs/directory for detailed guides
Please read and follow our Code of Conduct.
By contributing, you agree that your contributions will be licensed under the project's MIT License.