CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project intent
vibe_erp is an ERP framework (not an ERP application) aimed at the printing industry. The goal is a pluggable platform on which any printing business — with its own workflows, forms, roles, and rules — can be assembled by configuration and plug-in modules, not by forking or rewriting the core.
The reference business in raw/业务流程设计文档/ is one example customer, not the spec. It must be treated as a fixture to validate that the framework can express such a workflow, never as a source of hard-coded entities or features in the core.
Top-level domains in the reference doc (use only as inspiration / test cases):
销售管理 (sales) · 采购管理 (purchasing) · 材料仓库管理 (raw material warehouse) · 成品仓库管理 (finished goods warehouse) · 生产管理 (production) · 工艺管理 (process/craft) · 车间管理 (workshop) · 设备管理 (equipment) · 品质管理 (quality) · 财务管理 (finance) · 项目管理 (project)
Architectural guardrails
These rules exist because the whole point of the project is reusability across customers. Violating them defeats the project.
- Core stays domain-agnostic. No printing-specific entity, field, status, or workflow step belongs in the framework core. "Plate", "ink", "press", "color proof", etc. live in plug-in modules or configuration — never in core tables, core types, or core code paths.
- Workflows are data, not code. State machines, approval chains, document routing, and form definitions must be declarative (config / DB / DSL) so a new customer can be onboarded by editing definitions, not by editing source.
- Extensibility seams come first. Before adding any feature, identify the extension point (hook, plug-in interface, event, custom field, scripting hook) it should hang off of. If no seam exists yet, design the seam first, then implement the reference customer's behavior on top of it.
-
The reference customer is a test, not a requirement. When implementing something inspired by
raw/业务流程设计文档/, ask: "Could a different printing shop with different rules also be expressed here without code changes?" If no, redesign. - Multi-tenant from day one in spirit. Even if deployment is single-tenant, data models and APIs should not assume one company, one workflow, or one form layout.
- Global / i18n from day one. This codebase is the foundation of an ERP/EBC product intended to be sold worldwide. No hard-coded user-facing strings, no hard-coded currency, date format, time zone, number format, address shape, tax model, or language. All such concerns must go through localization, formatting, and configuration layers — even in the very first prototype. The reference docs being in Chinese does not mean Chinese is the primary or default locale; it is just one supported locale among many.
-
Clean Core. Borrowed from SAP S/4HANA's vocabulary: extensions never modify the core. The core is stable, generic, and upgrade-safe; everything customer-specific lives in plug-ins or in metadata rows. Extensions are graded A/B/C/D by upgrade safety:
- A = Tier-1 metadata only (custom fields, forms, workflows, rules) — always upgrade-safe
-
B = Tier-2 plug-in using only the public
api.v1.*surface — safe within a major version -
C = Tier-2 plug-in using deprecated
api.v1symbols — safe until next major; loader emits warnings -
D = Tier-2 plug-in reaching into
platform.*orpbc.*.internal.*via reflection — UNSUPPORTED, rejected by the plug-in linter at install time
-
Two-tier extensibility, both first-class. The framework offers two extension paths and both must be designed for, not bolted on:
-
Tier 1 — key user / no-code: business analysts add fields, forms, list views, workflows, automation rules, custom entities, reports, and translations through the web UI. Everything is stored as rows in the
metadata__*tables (Doctype-style), taggedsource = 'user', scoped to their tenant. No build, no restart. The OpenAPI spec, REST API, and AI-agent function catalog auto-update from the metadata. -
Tier 2 — developer / pro-code: PF4J JAR plug-ins. Plug-ins see only
org.vibeerp.api.v1.*. Importing anything fromplatform.*or any PBC's internal package fails the plug-in linter.
-
Tier 1 — key user / no-code: business analysts add fields, forms, list views, workflows, automation rules, custom entities, reports, and translations through the web UI. Everything is stored as rows in the
-
PBC boundaries are sacred. Each core capability (
pbc-identity,pbc-catalog,pbc-inventory,pbc-orders-sales,pbc-production, …) is its own Packaged Business Capability: own Gradle subproject, own table-name prefix, own bounded context, own public API surface. PBCs never import each other. Cross-PBC interaction goes through (a) the event bus or (b) service interfaces declared inapi.v1.ext.<pbc>. The Gradle build enforces this —pbc-orders-salescannot declarepbc-inventoryas a dependency. This is what makes "modular monolith now, splittable later" real instead of aspirational. -
api.v1is the only stable contract. The packageorg.vibeerp.api.v1.*is the single semver-governed surface that plug-ins consume. It is published asapi-v1.jarto Maven Central. Everything else in the codebase (platform.*,pbc.*.internal.*, every concrete Spring bean) is internal and may change in any release. Adding toapi.v1is allowed within a major version; renaming, removing, or behavior-changing anything inapi.v1is a major version bump. When in doubt, keep things OUT ofapi.v1; it is easier to grow the API deliberately than to maintain a regretted symbol forever. - AI agents are a first-class client. The framework is built so that the same business operations exposed via REST/OpenAPI are also callable by LLM-driven agents through an MCP server (or equivalent function-calling surface). The MCP endpoint itself is a v1.1 deliverable, but v1.0 must not architect it out — operations must be discoverable, typed, permission-checked, and locale-aware in a way that lets an agent call them safely. AI agents are listed alongside humans, web clients, mobile clients, and integrations as supported callers.
Working with the reference docs
-
raw/业务流程设计文档/is in Chinese. Module and process names are domain terms (e.g.工艺= process/craft,车间= workshop floor,品质= QC). Preserve original terminology when quoting; do not silently rename concepts when discussing them. - Treat these documents as read-only reference material. Do not move, restructure, or "clean up"
raw/. - When the user asks for a feature "from the doc", confirm whether they want it built as a reference plug-in / fixture on top of the framework, or as a core capability — the answer is almost always the former.
Documentation discipline
Because this is a framework that other developers (and future-you) will build on, documentation is part of "done", not an afterthought. As the repo grows:
- Maintain a top-level
docs/tree alongside the code. At minimum it should cover: architecture overview, extension points / plug-in API, workflow & form definition format, configuration reference, i18n/localization guide, and a getting-started guide for onboarding a new customer. - Every new extension point, plug-in interface, config key, or workflow primitive added to the core must ship with documentation in the same change. A PR that adds a public seam without docs is incomplete.
- Keep
raw/业务流程设计文档/separate fromdocs/.raw/is one customer's business reference (read-only fixture).docs/is the framework's own documentation, written in English by default with localization where appropriate. - When a doc and the code disagree, treat it as a bug — fix whichever is wrong, do not silently let docs rot.
Stack and shape (decided, not yet built)
The architecture has been brainstormed, validated against 2026 ERP/EBC SOTA, and approved. The full design lives at docs/superpowers/specs/2026-04-07-vibe-erp-architecture-design.md and is the source of truth for everything below.
- Backend: Kotlin on the JVM, Spring Boot, single fat-JAR / single Docker image
- Workflow engine: Embedded Flowable (BPMN 2.0)
- Persistence: PostgreSQL (the only mandatory external dependency)
-
Multi-tenancy: row-level
tenant_id+ Postgres Row-Level Security (defense in depth, same code path for self-host and hosted) -
Custom fields: JSONB
extcolumn on every business table, described bymetadata__custom_fieldrows; GIN-indexed - Plug-in framework: PF4J + Spring Boot child contexts (classloader isolation per plug-in)
-
i18n: ICU MessageFormat (ICU4J) + Spring
MessageSource - Reporting: JasperReports
- Auth: Built-in JWT + OIDC (Keycloak-compatible)
- Web client (v1): React + TypeScript SPA; mobile (React Native) is v2
- API: REST + OpenAPI; MCP server is v1.1 (seam exists in v1.0)
Topology: one Spring Boot process per instance, one Docker image per release, one mounted volume (/opt/vibe-erp/) for config/, plugins/, i18n-overrides/, files/, logs/. Customer extensions live outside the image. Upgrading vibe_erp = swapping the image; extensions and config stay put.
Module layout (Gradle multi-project):
vibe-erp/
├── api/api-v1/ ← THE CONTRACT (semver, published to Maven Central)
├── platform/* ← Framework runtime (internal, not exposed to plug-ins)
├── pbc/* ← Core PBCs: identity, catalog, partners, inventory,
│ warehousing, orders-sales, orders-purchase,
│ production, quality, finance
├── reference-customer/
│ └── plugin-printing-shop/ ← Reference plug-in expressing raw/业务流程设计文档/.
│ Built and CI-tested; NOT loaded by default.
├── web/ ← React + TypeScript SPA
└── docs/ ← Framework documentation
Dependency rule (enforced by the Gradle build):
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
Repository state
The repository currently contains the reference business documents under raw/, this CLAUDE.md, and the architecture spec at docs/superpowers/specs/2026-04-07-vibe-erp-architecture-design.md. There is no source code, build system, package manifest, test suite, or CI configuration yet. The next concrete step is an implementation plan, then bootstrapping the Gradle multi-project skeleton.
When source code is added, this file should be updated with: build/test/lint commands, the actual package name root (org.vibeerp is assumed but not yet committed), and any deviations from the design spec.
What to do when asked to "just add" a printing-specific feature
Stop and reframe. The correct shape is almost always:
- a generic mechanism in the core (e.g. "documents with configurable line items and a configurable approval workflow"), plus
- a plug-in / config bundle that uses that mechanism to express the specific printing-shop behavior.
If you cannot see how to split a request that way, ask the user before writing code.
The reference plug-in is the executable acceptance test
reference-customer/plugin-printing-shop/ is the framework's primary correctness test. It expresses the workflows in raw/业务流程设计文档/ using only api.v1. Two consequences:
- If a feature in
pbc/*exists only to make this plug-in work, the design has failed guardrail #1 — that feature must move into the plug-in. - If the plug-in needs to reach into a
platform.*orpbc.*internal class, the seam is wrong andapi.v1needs to grow (deliberately, with a version bump consideration).
CI must build and load this plug-in in an integration test environment against a real Postgres on every PR. A green core build with a red printing-shop integration test is a release-blocker.