# 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`](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.`. - **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.` 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 ```bash # 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.