Contributing
Contribution guide for the Pumpkin Hub project.
Welcome
All contributions are welcome, whether it's a bug fix, a new feature,
a documentation improvement or a bug report. Please take a moment to read
this guide before submitting your contribution.
Git Workflow
- Fork the repository on GitHub
- Clone your fork locally
- Create a branch from
develop(never frommaster) - Implement changes with atomic commits
- Push the branch and open a Pull Request targeting
develop
# Workflow example
git clone https://github.com/YOUR-USERNAME/pumpkin-hub.git
cd pumpkin-hub
git checkout -b feature/my-feature develop
# ... development ...
git push origin feature/my-feature
Branches
| Branch | Role |
|---|---|
master | Stable production, tagged releases |
develop | Integration, PR base |
feature/* | New features |
fix/* | Bug fixes |
docs/* | Documentation improvements |
Code Conventions
Rust (API)
- Formatting with
cargo fmt(non-negotiable) - Static analysis:
cargo clippy -- -D warningswith zero warnings - HTTP types via
axum::http, not thehttpcrate directly - New routes in
routes/{feature}.rs, merged intov1_routes() - Error handling via
AppError— never useunwrap()in production code
TypeScript (Frontend)
- Linting with ESLint (Next.js config):
npm run lint - Strict type-check:
npx tsc --noEmit - Components in the
app/directory (App Router) - Styles via Tailwind CSS, following the brutalist design system
General Principles
- Boy Scout Rule — Leave the code cleaner than you found it
- SOLID — Single Responsibility, minimal interfaces, inverted dependencies
- KISS — The simplest solution is often the best
- DRY — Factor out duplication without over-engineering
- YAGNI — Don't implement features "just in case"
Commit Messages
Commit messages follow the Conventional Commits convention:
type(scope): short description
[optional body]
[optional footer]
Allowed Types
| Type | Description |
|---|---|
feat | New feature |
fix | Bug fix |
docs | Documentation only |
style | Formatting, semicolons (no logic change) |
refactor | Refactoring without behavior change |
test | Adding or modifying tests |
chore | Maintenance tasks (CI, deps, config) |
Common Scopes
api, frontend, docs, docker,
ci, auth, plugins, search,
dashboard, api-keys, notifications
Examples
feat(api): add plugin CRUD endpoints
fix(frontend): correct search input focus state
docs: update roadmap with Phase 2
chore(ci): add Rust cache to workflow
Pull Requests
- Title follows the commit convention (
feat(scope): ...) - Clear description of the change and its motivation
- References related issues (e.g.,
Closes #42) - CI must pass (lint, clippy, tests, build)
- At least 1 approving review before merge
Tip
For significant changes, open an issue first to discuss the approach
before starting the implementation. This avoids wasted effort and enables
early feedback.
Code Quality
Before submitting, make sure that:
Backend
# Formatting
cargo fmt --all -- --check
# Static analysis
cargo clippy -- -D warnings
# Tests
cargo test
# Coverage (LCOV output for SonarQube)
cargo tarpaulin --out lcov
Frontend
# Linting (zero warnings enforced)
npm run lint
# Type-checking
npx tsc --noEmit
# Build
npm run build
# Tests with coverage (hard gate: lines 80%, functions 80%, branches 75%)
npm run test:coverage
Important
CI blocks the merge if any of these checks fail. Coverage thresholds are enforced as
hard gates — PRs that drop below the minimums will not pass CI. Run them locally before
pushing to save time.
SonarQube
Both frontend and backend coverage reports are uploaded to SonarQube for unified
code quality analysis. The combined report runs automatically after both CI stages complete.
Add a New Feature (API)
Procedure to add a new route module:
- Create the file
api/src/routes/{feature}.rs - Implement handlers using
AppStateandAppErrortypes - Declare the module in
routes/mod.rs(mod {feature};) - Merge routes into
v1_routes() - Write unit and/or integration tests
// api/src/routes/plugins.rs
use axum::{routing::get, Json, Router};
use crate::state::AppState;
pub fn routes() -> Router<AppState> {
Router::new()
.route("/plugins", get(list_plugins))
}
async fn list_plugins() -> Json<serde_json::Value> {
// implementation
todo!()
}
// api/src/routes/mod.rs
mod health;
mod plugins; // <-- add here
fn v1_routes() -> Router<AppState> {
Router::new()
.merge(health::routes())
.merge(plugins::routes()) // <-- and here
}