Pick the shape. The harness enforces it.
Each blueprint is a YAML file with modules, depends_on, forbidden_imports, a package map and mandatory trade-offs. Adding an architecture touches no skill, agent or prompt, and a CI job checks that on every push.
hexagonalDomain at the center, ports as interfaces, input and output adapters at the edges, one Maven module per adapter.
More than one input or output channel: REST, messaging, batch.
hexagonal-single-moduleThe same ports and adapters as packages of one Maven module. Adapters may not import each other.
Several channels, without one POM per adapter.
clean-architecture-multi-moduleDomain, application and infrastructure as three Maven modules; the build enforces the dependency rule.
Business rules that must stay isolated from infrastructure.
clean-architecture-single-moduleThe same separation in one Maven module; modules become packages. bank-app uses it.
Three POMs would be overhead nobody amortizes.
layeredcontroller → service → repository, in the vocabulary most Spring Boot tutorials use.
The team already speaks it, and port/adapter naming would be pure translation.
onionPalermo's own terms: Application Core and Gateway, one Maven module per ring.
The team prefers them over hexagonal or clean terms.
onion-single-modulePalermo's rings as packages of one Maven module. Persistence and presentation stay strangers.
Application Core and Gateway, without one POM per ring.
vertical-sliceCode grouped by feature: Command/Query, Handler, Endpoint and Response together.
Features diverge more than they overlap.
modular-monolithSeveral bounded contexts in one deployable, with a boundary stronger than a polite request.
Contexts that do not yet justify separate deployables.
custom-templateYour own architecture, in YAML.
None of the nine fits.
forbidden_imports
# blueprints/hexagonal.yaml modules: - id: domain depends_on: [] forbidden_imports: - "org.springframework.." - "jakarta.persistence.." - "jakarta.validation.." - id: application depends_on: [domain, commons] forbidden_imports: - "jakarta.persistence.." - "org.springframework.web.."
domain/CLAUDE.mdthe model knows.claude/forbidden-imports.txtthe check hook blocks the write, exit 2pom.xml · depends_onthe compiler refuses, in multi-module layouts
Norm globs (paths) are rewritten from the package map at generation time, so a norm loads only for the packages this blueprint actually has.
Validated as data, then generated and built
On every push, validate.yml runs these checks over the blueprints:
- Every blueprint exports the same tree twice, with no dead citation.
- Every
templates.<role>resolves to a real file. - A new blueprint file changes no skill, agent or prompt.
hexagonal-single-modulekeeps its adapters apart, and its matrix of ten packages is complete.
validate.yml never builds a project, so blueprints.yml does. For each blueprint a change can affect, it generates a skeleton from the YAML, with no language model, and builds it with Maven and with Gradle: nine blueprints, two tools, 18 jobs.
- The skeleton is the Initializr's starter, shaped by the blueprint: a reactor from
modules[]anddepends_onwhen multi-module, onepackage-info.javaper role of the package map. ArchHook.jar boundary-mapandexportwrite the project's.claude/, then./mvnw verifyor./gradlew buildruns, anddoctor gatemust be green.- Then one class per boundary rule imports what the rule forbids:
doctor gatemust fail naming every such file, andcheckmust block one of them with exit 2. - It runs only when a blueprint,
project-bootstrap, the hook or the schema changes. A change under one blueprint generates only that one. It is not a required check.
In the three -single-module blueprints the compiler does not enforce the boundary. The check hook on each edit, doctor gate in the project's CI and ArchUnit do. The skeleton is not the full generation: the test writes its own minimal build files, so the POMs project-bootstrap adapts from templates/pom.*.xml.example, the feature dependencies and the agent's own steps are not proven, and nothing here claims an end-to-end run of /init-project.