CONTRIBUTING.md 4.83 KB

Contributing to vibe_erp

vibe_erp is a framework that other developers will build on. The rules below exist so the framework stays reusable across customers and upgrade-safe across releases. They are not negotiable.

For the full architectural reasoning, see docs/superpowers/specs/2026-04-07-vibe-erp-architecture-design.md and CLAUDE.md.

The dependency rule

The Gradle build enforces this. CI fails on violations.

api/api-v1            depends on: nothing (Kotlin stdlib + jakarta.validation only)
platform/*            depends on: api/api-v1 + Spring + libs
pbc/*                 depends on: api/api-v1 + platform/*  (NEVER another pbc)
plugins (incl. ref)   depend on:  api/api-v1 only

Consequences:

  • PBCs never import each other. pbc-orders-sales cannot declare pbc-inventory as a dependency. Cross-PBC interaction goes through (a) the event bus or (b) service interfaces declared in api.v1.ext.<pbc>.
  • Plug-ins only see api.v1. Importing anything from platform.* or any PBC's internal package fails the plug-in linter at install time.
  • Reaching into internal packages via reflection is Grade D and rejected by the loader.

Adding to api.v1 is a one-way door — don't

api.v1 is the only semver-governed surface in the codebase. Once a class, method, or behavior is in api.v1, removing or renaming it requires a major version bump and a deprecation window of at least one major release.

When in doubt, keep things out of api.v1. It is much easier to grow the API deliberately later than to maintain a regretted symbol forever.

Adding to api.v1 is OK when:

  • The reference printing-shop plug-in (or a plausible second customer plug-in) cannot express its requirement without it.
  • The addition is a new type or a default-implemented method on an existing interface (binary-compatible).
  • The addition has a written KDoc contract and at least one consuming test.

Adding to api.v1 is not OK when:

  • It exists only to make one PBC easier to write — that belongs in platform.*.
  • It leaks an implementation detail (Hibernate type, Spring annotation, JPA entity).
  • It is "we might need it later" — wait until you do.

Commit message convention

Conventional Commits, scoped by module:

feat(pbc-identity): add SCIM provisioning endpoint
fix(api-v1): tolerate missing locale on Translator.format
chore(platform-plugins): bump PF4J to 3.x
docs(architecture): clarify outbox seam
test(pbc-catalog): cover JSONB ext field round-trip
refactor(platform-persistence): extract tenant filter

Allowed types: feat, fix, chore, docs, test, refactor, build, ci, perf, revert. Scope is the module name. Breaking changes use ! after the scope (e.g. feat(api-v1)!: …) and require a BREAKING CHANGE: footer.

Pull request checklist

Every PR must satisfy all of these:

  • Tests cover the new behavior. New PBC code has unit tests; new endpoints have integration tests against a real Postgres.
  • Every public api.v1 type has KDoc on the type and on every method, including parameter, return, and exception contracts.
  • Liquibase changesets ship with rollback blocks. CI rejects PRs without them.
  • Any user-facing string is referenced via an i18n key, never concatenated. Default en-US translation is added in the same PR.
  • No printing-specific terminology in pbc/* or platform/*. Printing concepts live in reference-customer/plugin-printing-shop/.
  • No new cross-PBC dependency. If you reached for one, design an api.v1.ext.<pbc> interface or a DomainEvent instead.
  • Commit messages follow Conventional Commits.
  • Documentation under docs/ is updated in the same PR if you added or changed a public seam.

Build, test, and plug-in load commands

# Build everything
./gradlew build

# Run the full test suite
./gradlew test

# Run a single PBC's tests
./gradlew :pbc:pbc-identity:test

# Build the api-v1 jar (the contract that plug-ins consume)
./gradlew :api:api-v1:jar

# Build the reference plug-in
./gradlew :reference-customer:plugin-printing-shop:jar

# Boot the distribution and load the reference plug-in
cp reference-customer/plugin-printing-shop/build/libs/*.jar /tmp/vibeerp/plugins/
./gradlew :distribution:bootRun

The plug-in loader logs every plug-in it scans, accepts, and rejects, with the reason. If a plug-in fails to load, the cause is in the boot log under the platform-plugins logger.

Style notes

  • Kotlin: idiomatic Kotlin, no !!, no lateinit outside test fixtures, prefer data classes for value objects.
  • Public API: KDoc is required, not optional. Internal helpers may skip KDoc but should still be self-explanatory.
  • No printing terminology in core, ever. "Item", "document", "operation", "work order" are generic. "Plate", "ink", "press", "color proof" are not.