Thank you for your interest in contributing to Visor! This guide provides everything you need to get started with development, understand the codebase, and submit high-quality contributions.
- Getting Started
- Development Workflow
- Code Standards
- Testing
- Adding New Features
- Pull Request Process
- Architecture Overview
- Common Tasks
- Getting Help
- Node.js: Version 18 or higher (20 recommended)
- npm: Comes with Node.js
- Git: For version control
Verify your setup:
node --version # Should be v18.x or higher
npm --version # Should be v9.x or higher
git --versiongit clone https://github.com/probelabs/visor.git
cd visornpm installThis will also run the prepare script which sets up Husky for Git hooks.
npm run buildThis command:
- Cleans the dist directory
- Runs
patch-packagefor any dependency patches - Generates the configuration schema
- Builds the CLI bundle with
@vercel/ncc - Builds the SDK with
tsup
# Run the test suite
npm test
# Run the CLI
./dist/index.js --help
# Check linting
npm run lint| Command | Description |
|---|---|
npm run build |
Build CLI and SDK |
npm run build:cli |
Build CLI only |
npm run build:sdk |
Build SDK only |
npm run clean |
Clean dist directory |
npm test |
Run all tests (Jest + YAML tests) |
npm run test:watch |
Run tests in watch mode |
npm run test:coverage |
Generate coverage report |
npm run test:yaml |
Run YAML-based tests only |
npm run test:yaml:parallel |
Run YAML tests with parallelism |
npm run lint |
Lint TypeScript files |
npm run lint:fix |
Auto-fix linting issues |
npm run format |
Format code with Prettier |
npm run format:check |
Check code formatting |
npm run docs:validate |
Validate README links |
After building, you can run the CLI directly:
# Show help
./dist/index.js --help
# Run all checks
./dist/index.js --check all
# Run specific checks with debug output
./dist/index.js --check security,performance --debug
# Run with custom config
./dist/index.js --config my-config.yaml --check all
# Output in different formats
./dist/index.js --check all --output json
./dist/index.js --check all --output markdown
./dist/index.js --check all --output sarifFor rapid development, use watch mode for tests:
npm run test:watchNote: There is no hot-reload for the CLI itself. You need to run npm run build
after making changes to source files.
-
Enable debug mode:
./dist/index.js --debug --config .visor.yaml
-
Use the
log()function in JavaScript expressions (if,fail_if,transform_js):if: | log("Debug:", outputs); return outputs.length > 0;
-
Use the
jsonfilter in Liquid templates:prompt: | Debug: {{ outputs | json }}
-
Use the logger check type:
checks: debug-flow: type: logger message: | Outputs: {{ outputs | json }}
-
Enable OpenTelemetry tracing:
VISOR_TELEMETRY_ENABLED=true \ VISOR_TELEMETRY_SINK=otlp \ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces \ ./dist/index.js --config .visor.yaml
For more debugging techniques, see docs/debugging.md.
- Target: ES2022
- Module: CommonJS
- Strict mode: Enabled
- All source code is in the
src/directory - Tests are in the
tests/directory
Key ESLint rules enforced:
prefer-const: Useconstwhen variables are not reassignedno-var: Useletorconstinstead ofvar@typescript-eslint/no-unused-vars: No unused variables (allows_prefix for intentionally unused)- Import
liquidjsthroughcreateExtendedLiquid()fromsrc/liquid-extensions
The project uses Prettier with these settings:
| Setting | Value |
|---|---|
semi |
true |
trailingComma |
es5 |
singleQuote |
true |
printWidth |
100 |
tabWidth |
2 |
useTabs |
false |
bracketSpacing |
true |
arrowParens |
avoid |
endOfLine |
lf |
For markdown files, printWidth is 80 with proseWrap: always.
- Files: Use kebab-case for file names (
check-provider.interface.ts) - Classes: Use PascalCase (
CheckProvider,ConfigManager) - Functions/Methods: Use camelCase (
executeCheck,validateConfig) - Constants: Use UPPER_SNAKE_CASE for true constants
- Interfaces: Use PascalCase, prefix with
Ionly if needed for clarity
src/
index.ts # GitHub Action entry point
cli-main.ts # CLI entry point
config.ts # Configuration loading
types/
config.ts # Configuration types
engine.ts # Engine state types
execution.ts # Execution result types
cli.ts # CLI option types
providers/
check-provider.interface.ts # Provider base class
check-provider-registry.ts # Provider registry
ai-check-provider.ts # AI provider
command-check-provider.ts # Command provider
... # Other providers
state-machine/
runner.ts # State machine runner
states/ # State handlers
utils/ # Utility functions
telemetry/ # OpenTelemetry integration
debug-visualizer/ # Debug UI server
The project uses Husky with lint-staged to run checks before commits:
- ESLint with auto-fix on
src/**/*.tsandtests/**/*.ts - Prettier formatting on all staged files
Tests are organized by type:
tests/
unit/ # Unit tests for individual components
integration/ # Integration tests with mocked APIs
e2e/ # End-to-end tests
scenarios/ # Complex workflow scenarios
performance/ # Performance and stress tests
fixtures/ # Test data and mock responses
errors/ # Error handling tests
edge-cases/ # Edge case tests
setup.ts # Global test setup
Tests use Jest with SWC for TypeScript transformation. Example test:
import { ConfigManager } from '../../src/config';
describe('ConfigManager', () => {
beforeEach(() => {
// Setup for each test
});
afterEach(() => {
// Cleanup
});
it('should load configuration from file', async () => {
const manager = new ConfigManager();
const config = await manager.loadConfig('path/to/config.yaml');
expect(config).toBeDefined();
expect(config.checks).toBeDefined();
});
it('should throw on invalid configuration', async () => {
const manager = new ConfigManager();
await expect(manager.loadConfig('invalid.yaml')).rejects.toThrow();
});
});Place test fixtures in tests/fixtures/:
// tests/fixtures/sample-pr.ts
export const samplePR = {
number: 123,
title: 'Test PR',
body: 'Test description',
author: 'test-user',
// ...
};# Run a specific test file
npm test -- tests/unit/config.test.ts
# Run tests matching a pattern
npm test -- --testNamePattern="ConfigManager"
# Run tests in a specific directory
npm test -- tests/unit/
# Run with verbose output
npm test -- --verbose
# Update snapshots
npm test -- --updateSnapshotKey Jest settings (from jest.config.js):
- testTimeout: 10 seconds (increased from default)
- forceExit: true (prevents hanging on async operations)
- logHeapUsage: Enabled in CI for memory monitoring
- maxWorkers: 1 in CI, 50% locally
Generate coverage reports:
npm run test:coverageCoverage reports are generated in the coverage/ directory. While there are no
strict coverage requirements, aim to maintain or improve coverage when adding
new code.
Providers are pluggable components that implement check types. To add a new provider:
- Create the provider file in
src/providers/:
// src/providers/my-custom-provider.ts
import { CheckProvider, CheckProviderConfig, ExecutionContext } from './check-provider.interface';
import { PRInfo } from '../pr-analyzer';
import { ReviewSummary } from '../reviewer';
export class MyCustomProvider extends CheckProvider {
getName(): string {
return 'my-custom';
}
getDescription(): string {
return 'My custom check provider';
}
async validateConfig(config: unknown): Promise<boolean> {
// Validate provider-specific configuration
const cfg = config as CheckProviderConfig;
return cfg.type === 'my-custom' && !!cfg.myRequiredField;
}
async execute(
prInfo: PRInfo,
config: CheckProviderConfig,
dependencyResults?: Map<string, ReviewSummary>,
context?: ExecutionContext
): Promise<ReviewSummary> {
// Implement your check logic here
return {
issues: [],
summary: 'Check completed successfully',
};
}
getSupportedConfigKeys(): string[] {
return ['type', 'myRequiredField', 'optionalField'];
}
async isAvailable(): Promise<boolean> {
// Check if required dependencies are available
return true;
}
getRequirements(): string[] {
return ['MY_API_KEY environment variable'];
}
}- Register the provider in
src/providers/check-provider-registry.ts:
import { MyCustomProvider } from './my-custom-provider';
// In the registry initialization
registry.register(new MyCustomProvider());-
Add TypeScript types if needed in
src/types/config.ts -
Write tests in
tests/unit/providers/my-custom-provider.test.ts -
Add documentation in
docs/providers/my-custom.md
- Update the types in
src/types/config.ts:
export interface CheckConfig {
// ... existing fields
myNewOption?: string;
}- Update the schema generator by running:
npm run prebuild-
Update documentation in
docs/configuration.md -
Add tests for the new option
CLI commands are defined in src/cli-main.ts using Commander:
program
.command('my-command')
.description('Description of my command')
.option('-f, --flag <value>', 'Option description')
.action(async (options) => {
// Command implementation
});When adding new features:
- Update relevant documentation in
docs/ - Add examples in
examples/if appropriate - Update
CLAUDE.mdif the feature affects development workflow - Keep README examples accurate
Use descriptive branch names:
feature/add-new-provider- New featuresfix/config-loading-error- Bug fixesdocs/update-contributing- Documentation updatesrefactor/simplify-engine- Code refactoringtest/add-provider-tests- Test additions
Write clear, descriptive commit messages:
<type>: <short description>
<optional longer description>
<optional footer with issue references>
Types:
feat: New featurefix: Bug fixdocs: Documentation changesrefactor: Code refactoringtest: Test additions or changeschore: Build process or auxiliary tool changes
Examples:
feat: add HTTP client provider for external API calls
Implements a new provider type that can make HTTP requests
to external APIs and process the responses.
Closes #123
fix: resolve config loading race condition
The configuration loader was not awaiting async operations
properly, causing intermittent failures.
-
Run the test suite:
npm test -
Run linting and formatting:
npm run lint npm run format
-
Build the project:
npm run build
-
Test your changes manually:
./dist/index.js --check all --debug
When creating a PR, include:
## Summary
Brief description of what this PR does.
## Changes
- List of specific changes
- Include any breaking changes
## Testing
How the changes were tested:
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] Manual testing performed
## Related Issues
Closes #123- PRs require at least one approval before merging
- All CI checks must pass
- Address review comments or explain why changes are not needed
- Keep PRs focused - prefer smaller, incremental changes
The following checks run on PRs:
- Lint: ESLint checks
- Format: Prettier format check
- Test: Full test suite
- Build: Verify the project builds successfully
For a detailed architecture overview, see docs/architecture.md.
| File | Description |
|---|---|
src/index.ts |
GitHub Action entry point |
src/cli-main.ts |
CLI entry point |
src/config.ts |
Configuration loading and validation |
src/check-execution-engine.ts |
Main orchestration engine |
src/state-machine-execution-engine.ts |
State machine runner |
src/providers/check-provider.interface.ts |
Provider base class |
src/types/config.ts |
Configuration TypeScript types |
- Entry Points:
src/index.ts(GitHub Action) andsrc/cli-main.ts(CLI) - Configuration:
src/config.tsloads and validates YAML configuration - Execution Engine:
src/state-machine-execution-engine.tsorchestrates check execution using a state machine - Providers:
src/providers/contains pluggable check implementations - Types:
src/types/contains TypeScript type definitions - Utilities:
src/utils/contains helper functions
# Update a specific dependency
npm update <package-name>
# Update all dependencies (be careful)
npm update
# Check for outdated packages
npm outdatedAfter updating dependencies:
- Run the full test suite
- Build the project
- Test the CLI manually
The configuration schema is auto-generated from TypeScript types:
npm run prebuildThis runs scripts/generate-config-schema.js which generates the JSON schema
from src/types/config.ts.
- Edit files in
docs/ - Validate links:
npm run docs:validate - Keep examples in
examples/synchronized with documentation
Test GitHub event handling locally:
# Simulate issue event
npm run simulate:issue
# Simulate comment event
npm run simulate:commentReleases are managed through npm scripts:
# Interactive release
npm run release
# Specific version bumps
npm run release:patch # 0.1.42 -> 0.1.43
npm run release:minor # 0.1.42 -> 0.2.0
npm run release:major # 0.1.42 -> 1.0.0The release script handles:
- Version bumping in
package.json - Building the project
- Creating a git tag
- Pushing to the repository
- Publishing to npm
- Documentation: Check the
docs/directory - Issues: Search or create issues on GitHub
- Debugging: See docs/debugging.md
- Troubleshooting: See docs/troubleshooting.md
- Configuration Reference
- Architecture Overview
- Debugging Guide
- Troubleshooting
- Dev Playbook
- CI/CLI Mode
Thank you for contributing to Visor!