Thank you for your interest in contributing to StackFast! This document provides guidelines and instructions for contributing.
Be respectful, inclusive, and constructive in all interactions.
- Fork the repository
- Clone your fork:
git clone https://github.com/yourusername/stackfast.git - Install dependencies:
npm install - Create a branch:
git checkout -b feature/your-feature-name
Follow the project standards documented in .kiro/steering/stackfast-standards.md:
- Use TypeScript strictly (no
anytypes) - Follow WCAG 2.1 AA accessibility guidelines
- Write tests for new features
- Keep functions under 50 lines
- Use descriptive variable names
# Run all tests
npm test
# Run type checking
npm run type-check
# Run linting
npm run lint
# Test in browser
npm run devUse clear, descriptive commit messages:
git commit -m "feat: add support for new database tool"
git commit -m "fix: resolve worker timeout issue"
git commit -m "docs: update catalog update process"git push origin feature/your-feature-nameThen open a Pull Request on GitHub with:
- Clear description of changes
- Link to related issues
- Screenshots for UI changes
- Test results
Add your tool to public/catalog/v1/tools.json:
{
"id": "my-tool",
"name": "My Tool",
"categoryId": "database",
"description": "A great database for modern apps",
"languages": ["javascript", "typescript"],
"supports": {
"runtime": ["node", "bun"],
"dbs": [],
"frameworks": []
},
"integrations": ["prisma", "drizzle"],
"hosted": true,
"selfHostable": false,
"pricing": {
"model": "free-tier",
"note": "Free up to 1GB",
"url": "https://example.com/pricing",
"lastVerified": "2024-01-15"
},
"docsUrl": "https://example.com/docs"
}Required Fields:
id: Unique identifier (kebab-case)name: Display namecategoryId: One of the canonical category IDsdescription: Brief description (1-2 sentences)languages: Supported languagessupports: Compatibility informationintegrations: Array of tool IDs this integrates with
Optional Fields:
hosted: Boolean for hosted servicesselfHostable: Boolean for self-hosting optionpricing: Pricing informationdocsUrl: Link to documentation
Add rules to public/catalog/v1/rules.json:
{
"id": "my-tool-synergy-prisma",
"version": "1.0.0",
"kind": "synergy",
"toolA": "my-tool",
"toolB": "prisma",
"reason": "First-class integration with Prisma ORM",
"weight": 8
}Rule Types:
conflict: Tools that don't work together (negative weight)synergy: Tools that work great together (positive weight)requirement: Tool A requires Tool BcapabilityCompat: Compatibility based on capabilitiescategoryCoverage: Bonus for filling required categories
Weight Guidelines:
- Conflicts: -15 to -5
- Synergies: +3 to +10
- Requirements: 0 (just diagnostic)
- Capability compat: +2 to +5 (capped at +12 total)
Create src/data/recipes/my-tool.ts:
import type { ExportRecipe } from '@/types';
export const myToolRecipe: ExportRecipe = {
id: 'my-tool',
version: '1.0.0',
appliesWhen: (tools) => tools.some(t => t.id === 'my-tool'),
targets: {
packageJson: {
deps: {
'my-tool-client': '^1.0.0',
},
devDeps: {
'@types/my-tool': '^1.0.0',
},
scripts: {
'db:start': 'my-tool start',
'db:migrate': 'my-tool migrate',
},
},
files: [
{
path: 'my-tool.config.js',
templateId: 'my-tool-config',
mergeStrategy: 'create',
},
],
env: {
example: {
MY_TOOL_URL: 'postgresql://localhost:5432/mydb',
MY_TOOL_API_KEY: 'your-api-key-here',
},
},
postInstallSteps: [
'Sign up at https://example.com',
'Copy your API key to .env',
'Run `npm run db:migrate` to set up database',
],
},
conflicts: [], // Optional: recipe IDs that conflict
};Add to src/data/recipes/index.ts:
import { myToolRecipe } from './my-tool';
export const recipes: ExportRecipe[] = [
// ... existing recipes
myToolRecipe,
];If your recipe needs a config file, create src/templates/config-files/my-tool-config.ts:
import type { Tool } from '@/types';
export function generateMyToolConfig(tools: Tool[]): string {
return `// My Tool Configuration
module.exports = {
// Configuration here
};
`;
}Register in src/templates/config-files/index.ts.
Update public/catalog/v1/manifest.json:
{
"version": "1.1.0",
"updatedAt": "2024-01-15T12:00:00Z",
"files": {
"categories": "/catalog/v1/categories.json",
"tools": "/catalog/v1/tools.json",
"rules": "/catalog/v1/rules.json"
},
"etag": "new-unique-hash"
}# Run tests
npm test
# Test in browser
npm run dev
# Select your tool and verify:
# - Tool appears in correct category
# - Compatibility rules work
# - Export generates correct files
# - No console errorsTest individual functions in isolation:
import { describe, it, expect } from 'vitest';
import { myFunction } from './my-module';
describe('myFunction', () => {
it('should handle valid input', () => {
expect(myFunction('valid')).toBe('expected');
});
it('should handle edge cases', () => {
expect(myFunction('')).toBe('default');
});
});Test component interactions:
import { render, screen, fireEvent } from '@testing-library/react';
import { MyComponent } from './MyComponent';
it('should update when button clicked', () => {
render(<MyComponent />);
fireEvent.click(screen.getByRole('button'));
expect(screen.getByText('Updated')).toBeInTheDocument();
});Test export outputs:
import { generateExport } from '@/lib/export-generator';
it('should generate consistent package.json', async () => {
const result = await generateExport(tools, diagnostics, 'zip');
const packageJson = result.files.find(f => f.path === 'package.json');
expect(JSON.parse(packageJson.content)).toMatchSnapshot();
});// ✅ Good
function calculateScore(diagnostics: Diagnostic[]): number {
const bonuses = diagnostics.filter(d => (d.weight ?? 0) > 0);
return Math.min(sum(bonuses), 40);
}
// ❌ Bad
function calculateScore(diagnostics: any) {
return diagnostics.reduce((a, b) => a + b.weight, 0);
}// ✅ Good
interface MyComponentProps {
title: string;
onClose: () => void;
}
export function MyComponent({ title, onClose }: MyComponentProps) {
return <div>{title}</div>;
}
// ❌ Bad
export default function MyComponent(props) {
return <div>{props.title}</div>;
}- Components: PascalCase (
ToolSelector) - Functions: camelCase (
calculateScore) - Constants: UPPER_SNAKE_CASE (
MAX_SCORE) - Files: kebab-case (
tool-selector.tsx) - Types: PascalCase (
ToolId,CategoryId)
Add JSDoc for all exported functions:
/**
* Calculate compatibility score from diagnostics
*
* @param diagnostics - Array of diagnostic results
* @returns Score between 0 and 100
*/
export function calculateScore(diagnostics: Diagnostic[]): number {
// Implementation
}Explain "why", not "what":
// ✅ Good
// Cap capability bonuses to prevent score inflation
const cappedBonuses = Math.min(capabilityBonuses, 12);
// ❌ Bad
// Set cappedBonuses to minimum of capabilityBonuses and 12
const cappedBonuses = Math.min(capabilityBonuses, 12);Use conventional commit format:
feat: add PostgreSQL supportfix: resolve worker timeout on mobiledocs: update catalog update processtest: add tests for rules enginerefactor: simplify score calculationperf: optimize rule evaluation
Include:
- What: What changes were made
- Why: Why these changes were needed
- How: How the changes work
- Testing: How you tested the changes
- Screenshots: For UI changes
Example:
## What
Adds support for PostgreSQL database with Prisma integration
## Why
PostgreSQL is a popular choice for full-stack apps and was requested by users
## How
- Added PostgreSQL to tools.json
- Created compatibility rules with Prisma and Drizzle
- Added export recipe with connection string setup
- Created config template
## Testing
- ✅ Unit tests pass
- ✅ Integration tests pass
- ✅ Manual testing in browser
- ✅ Export generates correct files
## Screenshots
[Screenshot of tool selection]
[Screenshot of export output]- Automated checks must pass (tests, type check, lint)
- At least one maintainer approval required
- Address review feedback
- Squash commits before merge
Issue: Property 'dbs' does not exist on type 'Supports'
Solution: Check if property exists before accessing:
if (Array.isArray(tool.supports?.dbs)) {
// Use tool.supports.dbs
}Issue: Worker is not defined
Solution: Guard worker creation:
if (typeof window !== 'undefined' && 'Worker' in window) {
worker = new Worker(url);
}Issue: Tests fail with "Cannot find module"
Solution: Check tsconfig.json path aliases match test setup
- 📖 Read the README
- 🐛 Check existing issues
- 💬 Ask in Discussions
- 📧 Email: support@stackfast.dev
Contributors will be:
- Listed in README
- Mentioned in release notes
- Credited in commit history
Thank you for contributing to StackFast! 🚀