This guide provides everything you need to know to contribute to ProposalOS, from setting up your development environment to submitting your first pull request.
- Architecture Overview - System design and pipeline flow
- Branch Protection - Git workflow requirements
- Runbooks - Operational procedures
- Observability Setup - Monitoring and debugging
# Clone the repository
git clone https://github.com/danishxsethi/ProposalOS.git
cd ProposalOS
# Run setup (installs deps, starts Docker, seeds DB)
make setup
# OR if you don't have make:
./scripts/setup.sh
# Start development server
make dev
# OR: npm run devSee README.md for detailed setup instructions.
<type>/<description>-<issue-number>
Examples:
feature/audit-caching-123fix/proposal-pdf-generation-456docs/update-architecture-789test/add-pipeline-tests-101
| Type | Description |
|---|---|
feature |
New functionality |
fix |
Bug fix |
docs |
Documentation only |
test |
Adding or updating tests |
refactor |
Code refactoring (no behavior change) |
perf |
Performance improvements |
chore |
Maintenance tasks |
main (protected)
│
├──▶ feature/my-feature-123
│ │
│ └──▶ PR → main
│
├──▶ fix/bug-fix-456
│ │
│ └──▶ PR → main
│
└──▶ hotfix/critical-789
│
└──▶ PR → main (expedited)
-
Ensure your branch is up to date:
git checkout main git pull origin main git checkout your-branch git rebase main
-
Run quality checks:
npm run lint npm run typecheck npm test -
Test your changes locally:
- Run the dev server
- Test affected functionality
- For audit/pipeline changes, run a full audit
-
Push your branch:
git push origin your-branch
-
Create PR on GitHub:
- Use the PR template (auto-loaded)
- Fill in all sections
- Link related issues
All PRs must:
- Pass all CI checks (lint, typecheck, tests, build)
- Have at least 1 approval
- Address all review comments
- Include tests for new functionality
- Update documentation if needed
When creating a PR, ensure you complete:
## Description
[Brief description of changes]
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Documentation update
- [ ] etc.
## Checklist
### Code Quality
- [ ] npm run lint passes
- [ ] npm run typecheck passes
- [ ] No console.log in production code
- [ ] Follows coding standards
### Testing
- [ ] Tests added/updated
- [ ] npm test passes
- [ ] For LLM changes: adversarial tests run
### Documentation
- [ ] Relevant docs updated
- [ ] JSDoc comments added-
Unit Tests (
lib/**/*.test.ts)- Test individual functions/modules
- Mock external dependencies
- Fast execution
-
Integration Tests (
app/api/**/__tests__)- Test API endpoints
- Test database interactions
- Test multi-component flows
-
Property-Based Tests (
lib/**/__tests__/*.property.test.ts)- Test invariants and edge cases
- Use fast-check for property testing
- Critical for core logic
-
Adversarial QA Tests (
lib/qa/adversarial-tests.ts)- Test LLM outputs
- Validate proposal quality
- Check for hallucinations
# Run all tests
npm test
# Run specific test file
npm test -- path/to/test.test.ts
# Run tests in watch mode
npm run test:watch
# Run with coverage
npm run test:coverage
# Run specific test pattern
npm test -- -t "audit pipeline"| Component | Min Coverage |
|---|---|
| Core pipeline | 80% |
| API routes | 70% |
| Utilities | 70% |
| LLM orchestration | 60% (output validation) |
Unit Test Example:
import { describe, it, expect } from 'vitest';
import { calculateImpactScore } from '../scoring';
describe('calculateImpactScore', () => {
it('should return score between 1-10', () => {
const result = calculateImpactScore({ severity: 'high', reach: 'wide' });
expect(result).toBeGreaterThanOrEqual(1);
expect(result).toBeLessThanOrEqual(10);
});
it('should weight severity higher than reach', () => {
const highSeverity = calculateImpactScore({ severity: 'critical', reach: 'narrow' });
const lowSeverity = calculateImpactScore({ severity: 'low', reach: 'wide' });
expect(highSeverity).toBeGreaterThan(lowSeverity);
});
});Property-Based Test Example:
import { property } from 'fast-check';
import { sanitizeInput } from '../inputSanitizer';
describe('sanitizeInput', () => {
it('should always remove script tags', () => {
property(arbitraryString(), (input) => {
const sanitized = sanitizeInput(input);
expect(sanitized).not.toMatch(/<script/i);
});
});
});Audit modules collect and analyze data from specific sources.
Module Structure:
// lib/modules/myModule.ts
import { AuditModule, AuditContext } from './types';
export interface MyModuleResult {
// Define your result structure
metric1: number;
finding2: string;
}
export const myModule: AuditModule<MyModuleResult> = {
name: 'my-module',
version: '1.0.0',
async execute(context: AuditContext): Promise<MyModuleResult> {
// 1. Collect data
const data = await this.collectData(context);
// 2. Analyze data
const analysis = await this.analyze(data);
// 3. Generate findings
const findings = this.generateFindings(analysis);
// 4. Return structured result
return {
metric1: analysis.score,
finding2: findings.primary,
};
},
async collectData(context: AuditContext): Promise<unknown> {
// Implement data collection
},
async analyze(data: unknown): Promise<unknown> {
// Implement analysis logic
},
generateFindings(analysis: unknown): { primary: string } {
// Convert analysis to findings
return { primary: 'Finding description' };
},
};Add your module to the orchestrator:
// lib/orchestrator/auditOrchestrator.ts
import { myModule } from '../modules/myModule';
const modules = [
// ... existing modules
myModule,
];// lib/modules/__tests__/myModule.test.ts
import { describe, it, expect, vi } from 'vitest';
import { myModule } from '../myModule';
describe('myModule', () => {
it('should execute successfully', async () => {
const context = {
businessName: 'Test Business',
businessUrl: 'https://example.com',
city: 'Test City',
};
const result = await myModule.execute(context);
expect(result).toBeDefined();
expect(result.metric1).toBeDefined();
});
});- Error Handling: Always wrap external calls in try-catch
- Timeouts: Set reasonable timeouts for network calls
- Retry Logic: Implement retry for transient failures
- Logging: Use structured logging with context
- Caching: Cache expensive API calls
async execute(context: AuditContext): Promise<Result> {
const startTime = Date.now();
try {
const result = await this.fetchData(context);
this.logger.info('Module completed', {
duration: Date.now() - startTime,
businessName: context.businessName
});
return result;
} catch (error) {
this.logger.error('Module failed', {
error: error.message,
context
});
throw error;
}
}- No
anytypes without justification - Use interfaces for object shapes
- Use type aliases for unions
- Prefer
unknownoveranyfor uncertain types - Add JSDoc for public APIs
// ✅ Good
interface AuditConfig {
businessName: string;
businessUrl: string;
city: string;
}
// ✅ Good
type FindingType = 'PAINKILLER' | 'VITAMIN' | 'NEUTRAL';
// ❌ Avoid
function process(data: any) {}
// ✅ Better
function process(data: unknown) {
if (!isValidData(data)) {
throw new Error('Invalid data');
}
// ...
}// 1. External dependencies
import React from 'react';
import { NextRequest } from 'next/server';
// 2. Internal modules (lib/)
import { logger } from '@/lib/logger';
import { prisma } from '@/lib/prisma';
// 3. Type imports
import type { Audit } from '@prisma/client';
import type { ApiResponse } from '@/types';
// 4. Relative imports
import { Component } from './Component';
import styles from './styles.module.css';// Use custom error classes
export class AuditError extends Error {
constructor(
message: string,
public readonly auditId: string,
public readonly code: string
) {
super(message);
this.name = 'AuditError';
}
}
// Always include context in error messages
throw new AuditError(
`Failed to process audit ${auditId}: ${error.message}`,
auditId,
'PROCESSING_FAILED'
);import { logger } from '@/lib/logger';
// Use appropriate log levels
logger.debug('Detailed debug info', { context });
logger.info('Business event', { auditId, status });
logger.warn('Recoverable issue', { retryCount });
logger.error('Error with context', { error, auditId });
// Include request ID for tracing
logger.info('Processing request', {
requestId: context.requestId,
action: 'create_audit',
});Follow Conventional Commits:
<type>(<scope>): <description>
[optional body]
[optional footer]
Examples:
feat(audit): add caching for PageSpeed API calls
- Implement Redis-based caching for PageSpeed results
- Add cache invalidation on audit regeneration
- Configure 24-hour TTL
Closes #123
---
fix(proposal): correct pricing tier calculation
The Growth tier was incorrectly including Premium items.
---
docs: update architecture diagram
---
test(pipeline): add property tests for scoring engine
# Build locally
npm run build
# Run all checks
npm run lint && npm run typecheck && npm test
# Test production build locally
npm start- PR merged to main → CI/CD pipeline runs
- All checks pass → Automatic deployment to Cloud Run
- Database migrations → Applied automatically
- Health check → Verified before traffic switch
See docs/ROLLBACK.md for rollback procedures.
- Documentation: Check docs/ directory
- Existing Issues: Search for similar issues
- Team Chat: Ask in the team channel
- Runbooks: Consult docs/RUNBOOKS.md
- Keep PRs small and focused
- Write clear descriptions
- Link related issues
- Respond to all comments
- Review within 24 hours
- Be constructive
- Suggest alternatives, not just problems
- Approve when satisfied, don't linger
Contributors are recognized in:
- Release notes
- Team meetings
- Contributor leaderboard (coming soon)
Thank you for contributing to ProposalOS! 🚀