---
name: project-bootstrap
description: >
  Creates the complete initial structure of a Spring Boot project from a declarative
  architecture blueprint. Use when the user asks to create a project from scratch,
  start a new project, set up a project, scaffolding, "new Spring project",
  bootstrap, generate a module structure, create a multi-module Maven or Gradle
  project, build a hexagonal architecture, clean architecture, onion, layered, vertical
  slice or modular monolith, or when the /init-project command is run.
allowed-tools: Read, Write, Edit, Bash, Glob
---

# Project Bootstrap

Turns an empty directory into a Spring Boot project with declared architectural
boundaries, active hooks, and a green build.

**Does not write business code.** No example, no seed, no demo aggregate. It emits
module structure, packages, POMs, configuration, enforcement, rules, and skills — and
stops there. Classes come from the `/new-feature` pipeline, from a real spec. A `User`
aggregate invented by the bootstrap competes with that spec and diverges from the
layer skills' exemplars, which own the shape. Record:
`@.claude/decisions/0011-bootstrap-without-business-code.md`.

The only Java in the freshly generated project is what the Initializr brings — the
`@SpringBootApplication` class and the context test — plus one `package-info.java` per
role in `packages.map` (step 4.7).

## Dependencies

`java` (JDK 21+) · `git` · `curl`. **Nothing else.** `mvn`/`gradle` on the PATH are not
required — the Initializr's `starter.tgz` already brings the matching wrapper (`mvnw` or
`gradlew`, whichever `build.tool` resolved to).

No Python, no template engines, no bash. The hooks are a single Java file run in
single-file source mode, which makes them identical on Linux, macOS, and Windows — the
same dependency the target audience already has mandatorily.

## When NOT to use

`pom.xml` or `build.gradle` already exists at the root. This skill **does not migrate**
existing projects nor overwrite structure. In that case: stop, report, suggest
`/new-feature` instead.

## Preconditions

```bash
java --version && git --version && curl --version | head -1
ls ${CLAUDE_SKILL_DIR}/templates/*.example
```

Check that the exemplars the procedure cites are there. Some are conditional on the
active blueprint's `build.tool` — **check only the ones the resolved tool needs**, per
the table below:

| Build tool | Exemplars (step 4) |
|---|---|
| `maven` | `pom.parent.xml.example` and `pom.module.xml.example` |
| `gradle` | `settings.gradle.example`, `build.gradle.parent.example`, and `build.gradle.module.example` |

The rest are shared regardless of `build.tool`: `checkstyle.xml.example` (4.6),
`lombok.config.example` (4.8), `Application.java.example`
and `application.yml.example` (step 3), `features/actuator/application-actuator.yml.example`
and `features/observability/application-observability.yml.example` and
`features/observability/ApplicationTests-tracer.java.example` (4.7),
`docker-compose.yml.example` (4.10) plus **either** `Dockerfile.example` (`maven`) **or**
`Dockerfile-gradle.example` (`gradle`) — never both,
`root.CLAUDE.md.example` and `module.CLAUDE.md.example` (step 6),
`settings.json.example` and `audit-pricing.json.example` (step 6.6, written by the
`export` mode, not by hand), **either**
`ci.yml.example` (`maven`) **or** `ci-gradle.yml.example` (`gradle`) — never both (step
6.5), `README.md.example` and `README.pt-br.md.example` (step 8.5), and
`GENESIS.md.example` (step 8.6) — plus whatever the
active blueprint's `templates:` declares. Don't count files against a fixed number: the
folder grows, the number falls behind, and the precondition starts failing for nothing.
If a cited exemplar is missing, or a tool is missing: **stop and report**, naming what's
missing. Don't improvise a root POM/build.gradle or invent Spring Boot versions from
what you think you know — the version you have in memory is stale by construction.

**This skill generates exactly one build system per project — Maven or Gradle, never
both.** `build.tool` in the blueprint YAML picks it, and it's overridable once at
initialization (step 1's interview); once resolved, every later step branches on that
single value and only ever touches that tool's exemplars. Writing a `pom.xml` and a
`build.gradle` side by side (or vice versa) in the same generated project is a bug in
this procedure, not a harmless extra — Maven and Gradle disagree about which one is
authoritative, `./mvnw` and `./gradlew` both exist and diverge on the first dependency
bump, and every downstream skill that shells out to a build tool (`test-architect`,
`archunit-installer`, CI) has to guess which one is real.

Step 4.10 additionally reads `docker-architect`'s own `templates/postgres-service.yml.example`
and `templates/otel-collector-service.yml.example` (plus its
`templates/otel-collector-config.yml.example`) when `persistence-jpa` or `observability`
is active — see § 4.10. Missing either when the matching feature is active: same
stop-and-report rule.

Naming convention: the `.example` suffix always comes **last**
(`pom.parent.xml.example`, `DomainException.java.example`). An exemplar with the suffix
in the middle escapes the glob above and the precondition turns green without the file
existing.

## Procedure

### 1 · Select the blueprint

List `.claude/blueprints/*/*.yaml` dynamically (never a fixed list in the prompt) —
each architecture lives at `.claude/blueprints/<id>/<id>.yaml`; folders without a
`.yaml` (architecture not yet written) don't appear in the list. If the user hasn't
indicated which one, show `references/blueprint-selection.md` with each one's
`when_to_choose` and `trade_offs`, and ask. **Never assume** the architecture: it's the
most expensive decision to reverse in the whole project.

The blueprint's `build.tool` (`maven` or `gradle`) is a default, not a lock-in — if the
user hasn't said which build tool they want, confirm the blueprint's default with them
here, once, same interview. Don't ask again later: step 2 resolves the final value and
every step from step 3 on assumes it's already settled.

### 2 · Validate the blueprint

Read the YAML and check, one by one:

| # | Rule | If it fails |
|---|---|---|
| 1 | `id`, `name`, `build`, `modules`, `packages`, `dependency_rules`, `features`, `architecture_paths` all exist | Stop and name the missing field |
| 2 | The `depends_on` graph is acyclic | Stop and show the cycle |
| 3 | Exactly **one** module with `contains_main: true` | Stop and list the candidates |
| 4 | Every `feature` referenced by a module exists in `features` | Stop and name the feature |
| 5 | Every `templates.<role>` points to an existing file | Stop and name the path |
| 6 | `architecture_paths` is not an empty list | Stop — without this, step 6.6 has nothing to write |

Don't invent defaults for `dependency_rules` — it's the backbone of the enforcement.

**Resolve the effective build tool now, before step 3.** `build.tool` in the blueprint
is the default; `@.claude/blueprints/_schema.md` documents it as "Overridable at
initialization" — if the user asked for the other tool during step 1's interview, that
override wins. Whatever the final value — `maven` or `gradle` — every step from here on
branches on it once and stays with that choice. Never generate both.

### 3 · Generate the base with Spring Initializr

The Initializr's `type=` parameter is what actually picks the build system — everything
downstream just follows the file shapes it produces:

```bash
# build.tool: maven
curl -sS https://start.spring.io/starter.tgz \
  -d type=maven-project \
  -d language=java \
  -d groupId=<groupId> \
  -d artifactId=<artifactId> \
  -d name=<name> \
  -d packageName=<packageBase> \
  -d javaVersion=21 \
  -d dependencies=<list derived from the features> \
  | tar -xzf - -C .

# build.tool: gradle — same parameters, only `type` changes. Groovy DSL
# (`gradle-project`), not Kotlin DSL (`gradle-project-kotlin`): this skill's own
# exemplars (§ 4) are Groovy, and mixing DSLs between what the Initializr emits and what
# this skill writes on top of it is its own silent divergence.
curl -sS https://start.spring.io/starter.tgz \
  -d type=gradle-project \
  -d language=java \
  -d groupId=<groupId> \
  -d artifactId=<artifactId> \
  -d name=<name> \
  -d packageName=<packageBase> \
  -d javaVersion=21 \
  -d dependencies=<list derived from the features> \
  | tar -xzf - -C .
```

Run **exactly one** of the two, matching the build tool resolved above. Maven's
`starter.tgz` extracts `pom.xml`, `mvnw`, `mvnw.cmd`, and `.mvn/wrapper/`; Gradle's
extracts `build.gradle`, `settings.gradle`, `gradlew`, `gradlew.bat`, and
`gradle/wrapper/`. There's no third case where both sets exist — if a stray `pom.xml` or
`build.gradle` is left over from a previous attempt in this directory, that's the "already
exists" case from § When NOT to use, not something to merge with the tool just generated.

`javaVersion=21` is not a pin from memory — it's this skill's own precondition (JDK
21+, see § Dependencies) made explicit. Without it the Initializr falls back to its own
default, which has been observed to be an older LTS than what this skill requires;
letting that happen means the generated `pom.xml`/`build.gradle` and
`dependency-catalog.md`'s reasoning about the target JDK silently disagree.

The Initializr **is** the version oracle for everything else: it returns the current
Spring Boot GA, with nothing pinned in this repository. Confirm what came back:

```bash
# maven
grep -m1 '<version>' pom.xml && grep -m1 'java.version' pom.xml

# gradle
grep -m1 "id 'org.springframework.boot'" build.gradle && grep -m1 'sourceCompatibility\|JavaVersion' build.gradle
```

If the network isn't available, **stop and ask** the user for the versions. Never write
them from memory. If you pin `bootVersion` explicitly, don't use the `.RELEASE`
suffix — see the note in `references/dependency-catalog.md`.

Feature → Initializr dependency mapping in `references/dependency-catalog.md`.

### 4 · Restructure according to the blueprint

Branch once on the build tool resolved before step 3, and stay on that branch for the
rest of this step — **write only the files of the tool actually chosen.** A `gradle`
project never gets a `pom.xml`, `mvnw`, or `.mvn/`; a `maven` project never gets a
`build.gradle`, `settings.gradle`, or `gradlew`.

#### build.tool: maven

If `build.layout: single-module`, the result of step 3 already works as the structure:
just create the packages from `packages.map`, there's no per-layer POM to write. The
Initializr's `pom.xml` still needs the `<build>` blocks from
`templates/pom.parent.xml.example` — Spotless, Checkstyle, failsafe, and JaCoCo —
because none of them come from the Initializr. Copy them into that `pom.xml` and move
on to step 4.6. Steps 4.6 through 4.8 apply to both layouts.

If `multi-module`:

1. Convert the root `pom.xml` into a parent (`<packaging>pom</packaging>` +
   `<modules>`), using `templates/pom.parent.xml.example` as the shape reference. The
   `<spotless.version>` property is there as a placeholder: resolve it on Maven
   Central, same rule as step 3, never from memory.

   ```bash
   curl -sS 'https://repo1.maven.org/maven2/com/diffplug/spotless/spotless-maven-plugin/maven-metadata.xml' \
     | grep -o '<release>[^<]*</release>'
   ```

   No network: **ask**, don't invent. A made-up number that doesn't exist in the
   repository breaks the build on the first `./mvnw`.

   The version oracle is `maven-metadata.xml` from `repo1.maven.org` — the repository
   itself. Don't use `search.maven.org`'s `solrsearch`: it's a separate index, returns
   versions that lag behind the repository (observed: Spotless 2.44.5 in the index,
   3.10.2 in the repository) and responds intermittently. The same holds for every
   version resolved in this skill.
2. Create a `pom.xml` per module from `templates/pom.module.xml.example`,
   with the dependencies that module's `depends_on` authorizes — **and only those**.
3. Move the `@SpringBootApplication` class to the module with `contains_main: true`.
4. Move `application.yml` into that module's `resources`.

Mandatory order: parent → modules → main class → configuration → docs → CI.
A swapped order leaves the build broken halfway through generation.

#### build.tool: gradle

If `build.layout: single-module`, the result of step 3 already works as the structure:
just create the packages from `packages.map`, there's no per-module `build.gradle` to
write and no `settings.gradle` beyond its existing `rootProject.name` line. The
Initializr's `build.gradle` still needs the `checkstyle`/`spotless`/`jacoco` blocks and
the `test`/`integrationTest` task wiring from `templates/build.gradle.parent.example`
(applied directly to the one project, not inside a `subprojects {}` block — there are no
subprojects) because none of them come from the Initializr. Copy them in and move on to
step 4.6. Steps 4.6 through 4.8 apply to both layouts.

If `multi-module`:

1. Write `settings.gradle` from `templates/settings.gradle.example`, with one `include`
   line per module in `modules[]`, colon-separated to match each module's folder path
   (`adapters/adapter-in-rest` → `include 'adapters:adapter-in-rest'`).
2. Convert the root `build.gradle` into the parent shape of
   `templates/build.gradle.parent.example` — the `plugins {}` block declares
   `org.springframework.boot`, `io.spring.dependency-management`, and
   `com.diffplug.spotless` with `apply false` (applied per-module instead, step 4.7's
   note on `contains_main` decides where the Boot plugin actually activates), and the
   `subprojects {}` block carries everything every module inherits — Lombok,
   Checkstyle, Spotless, JaCoCo, the `test`/`integrationTest` split. The
   `com.diffplug.spotless` version is a placeholder: resolve it the same way as step 3,
   never from memory, but against the **Gradle plugin's own artifact** — a different one
   from Maven's, don't reuse that URL:

   ```bash
   curl -sS 'https://repo1.maven.org/maven2/com/diffplug/spotless/spotless-plugin-gradle/maven-metadata.xml' \
     | grep -o '<release>[^<]*</release>'
   ```

   No network: **ask**, don't invent. A made-up number that doesn't exist in the
   repository breaks the build on the first `./gradlew`.

   Same version-oracle discipline as Maven: `maven-metadata.xml` from `repo1.maven.org`,
   never `search.maven.org`'s lagging index.
3. Create a `build.gradle` per module from `templates/build.gradle.module.example`,
   with the dependencies that module's `depends_on` authorizes — **and only those**. The
   module with `contains_main: true` additionally applies `org.springframework.boot`
   and declares the `spring-boot-starter-*` dependencies its features need — every
   other module stays a plain `java-library`.
4. Move the `@SpringBootApplication` class to the module with `contains_main: true`.
5. Move `application.yml` into that module's `resources`.

Mandatory order: settings → parent build.gradle → module build.gradles → main class →
configuration → docs → CI. A swapped order leaves the build broken halfway through
generation.

#### Both

The files in `templates/` are **real, compilable exemplars**, not molds to be
mechanically substituted. Read them, understand the shape, and write the equivalent for
this project. A `domain` module doesn't carry `spring-boot-starter-web` even if the
exemplar shows it in another module.

<a id="andaime"></a>**The exemplar's scaffolding doesn't go into the project.** This
applies to every step that copies from `templates/` — this one, 4.6, 4.7, 4.8, and 4.10. The
top comment block mixes two things:

| Stays | Goes |
|---|---|
| Why the file is shaped this way (e.g. "these two versions are distinct on purpose") | The word `EXEMPLAR` and anything describing the file as a template |
| Citations to rules (`.claude/rules/...`) | "Compilable as-is", "not pseudo-code" |
| Business rule or invariant the reader needs | "role `X` from `packages.map`", generation instructions, references to other `.example` files |

A comment that describes the template instead of the code is exactly what
`@.claude/rules/code-quality.md` forbids — in the generated project there's no template
to refer to.

### 4.5 · Domain exception family — not here

The five exemplars (`DomainException` and the four typed ones) live in
`.claude/skills/domain-modeling/templates/`. `domain-modeling` owns the shape of the
domain, and the `10-dominio.md` partial names the exceptions for each invariant; the
code comes from the executor, with the first feature.

The bootstrap doesn't write them because it has no invariant to tie them to: five
exception classes in a project with no domain are dead code the user either deletes or,
worse, keeps by mistake. `@.claude/rules/error-handling.md` is still copied in step
6.6, and it's the one that sets the taxonomy — in particular the prohibition on
throwing generic `RuntimeException`/`Exception`/`IllegalArgumentException` in any
module.

`ApiExceptionHandler` isn't from here either — transport-specific, an exemplar of
`rest-api-architect` (see `@.claude/rules/api-rest.md`).

### 4.6 · Generate the Checkstyle config

The limits from `@.claude/rules/code-quality.md` — method length, parameter count,
cyclomatic complexity, nesting, magic numbers — stay prose without Checkstyle. It's the
only automatic check the bootstrap installs. The config file itself
(`config/checkstyle/checkstyle.xml`) is identical either way — only where the version
numbers and the wiring go differs by build tool.

**build.tool: maven**

1. Resolve the versions on Maven Central, same rule as step 3:

   ```bash
   curl -sS 'https://repo1.maven.org/maven2/org/apache/maven/plugins/maven-checkstyle-plugin/maven-metadata.xml' \
     | grep -o '<release>[^<]*</release>'
   curl -sS 'https://repo1.maven.org/maven2/com/puppycrawl/tools/checkstyle/maven-metadata.xml' \
     | grep -o '<release>[^<]*</release>'
   ```

   They go into `<checkstyle.plugin.version>` and `<checkstyle.version>` in the root
   POM. They're two versions distinct on purpose: the plugin's and the tool it runs —
   without the second, the plugin runs an old Checkstyle that doesn't know half the
   checks.

   **JaCoCo's version is resolved here too, not skipped.** Unlike failsafe,
   `spring-boot-starter-parent` does **not** manage `org.jacoco:jacoco-maven-plugin`
   (confirmed empty against the parent's `dependencyManagement`) — leaving it unpinned
   resolves whatever's newest at build time, non-reproducibly:

   ```bash
   curl -sS 'https://repo1.maven.org/maven2/org/jacoco/jacoco-maven-plugin/maven-metadata.xml' \
     | grep -o '<release>[^<]*</release>'
   ```

   Goes into `<jacoco.version>` in the root POM.
2. Write `<project>/config/checkstyle/checkstyle.xml` with the shape of
   `templates/checkstyle.xml.example`. Fixed path — it's what the root POM's
   `configLocation` points to, via `${maven.multiModuleProjectDirectory}`. Don't swap
   it for a relative path: it resolves at the root and fails in every submodule.

**build.tool: gradle**

1. Resolve the Checkstyle **tool** version the same way — Gradle's `checkstyle` plugin
   ships built into Gradle itself, so there's no separate "plugin version" to resolve,
   only the tool's:

   ```bash
   curl -sS 'https://repo1.maven.org/maven2/com/puppycrawl/tools/checkstyle/maven-metadata.xml' \
     | grep -o '<release>[^<]*</release>'
   ```

   Goes into `checkstyle { toolVersion = '...' }` in the root `build.gradle` (or, in
   `multi-module`, inside the `subprojects {}` block of `templates/build.gradle.parent.example`).

   **JaCoCo's version, same discipline, against its own artifact** — not the Maven
   plugin's:

   ```bash
   curl -sS 'https://repo1.maven.org/maven2/org/jacoco/org.jacoco.core/maven-metadata.xml' \
     | grep -o '<release>[^<]*</release>'
   ```

   Goes into `jacoco { toolVersion = '...' }`, same block.
2. Write `<project>/config/checkstyle/checkstyle.xml` with the shape of
   `templates/checkstyle.xml.example` — same file, same path, same content as the Maven
   case. It's what `checkstyle { configFile = rootProject.file('config/checkstyle/checkstyle.xml') }`
   points to in `templates/build.gradle.parent.example`.

**Both**

3. **Don't change the exemplar's numbers.** Each one mirrors a limit from
   `code-quality.md`; changing one side just makes the rule and the build disagree. If
   the limit has to change, change it in both files, in the same pass.
4. Don't add formatting checks (imports, braces, spacing): Spotless handles that,
   already configured in the root build file. A duplicate check breaks the build over
   something the formatter would have fixed on its own.

Runs before compiling in both build tools — Maven's `validate` phase, Gradle's `check`
task (wired into `build`, so it can't be skipped by running `build` alone) — one
violation stops the build immediately. If the freshly generated project already fails
here, the error is in the translated exemplar or in a class coming from the Initializr,
not in the limit: fix it before continuing.

**Architecture tests (ArchUnit) are not generated here.** The bootstrap delivers a
project with no business code, and a `classes().that()...should()` rule over zero
classes fails vacuously — the build would be born red for having nothing to check.
Writing them is the job of the `test-architect` skill, once classes exist for them to
apply to. The bootstrap only records this in the output contract. The blueprint's
`archunit` feature is still valid data: it's the `test-architect` skill that reads it,
not this step.

**The coverage gate goes out the same door, for the same reason.** The
`pom.parent.xml.example`/`build.gradle.parent.example` exemplar brings JaCoCo with
report generation (`prepare-agent`+`report` in Maven, `jacocoTestReport` in Gradle), and
**without** a coverage gate (Maven's `check` execution, Gradle's
`jacocoTestCoverageVerification`): report yes, gate no. The limits from
`@.claude/rules/testing.md` (80% lines / 70% branches) only make sense over code that
exists, and it's `test-architect`'s setup mode that wires them — in the same pass that
installs ArchUnit. Don't add the gate yourself: a project with no business classes
either passes it vacuously, which proves nothing, or breaks it, which is a false
negative.

### 4.7 · Materialize the packages and feature configuration

The step that replaced example generation. Two parts: the packages come to exist on
disk, and the features that have configuration receive it.

**a) One `package-info.java` per role in `packages.map`.**

An empty directory doesn't survive git, and a `packages.map` that only exists in the
YAML isn't a boundary at all: `ArchHook check` matches by path prefix, and a path that
doesn't exist never matches. For each role declared in `packages.map`, write
`<module>/src/main/java/<package>/package-info.java` with one sentence — the role, and
what that layer may import, derived from the module's `depends_on` and
`forbidden_imports`:

```java
/**
 * Domain. Aggregates, value objects, and invariants.
 *
 * <p>No framework: no {@code org.springframework}, {@code jakarta.*},
 * {@code com.fasterxml.jackson}, or {@code tools.jackson}. See {@code .claude/rules/architecture-ddd.md}.
 */
package com.example.demoapp.domain;
```

One sentence for the role, and the boundary line only when the module declares
`forbidden_imports`. Don't write more: `package-info.java` isn't the place to reproduce
a rule — cite it by path, invariant 2.

**`commons` (or the blueprint's equivalent, e.g. `modular-monolith`'s `shared.logging`)
is the one role that stays empty on purpose.** Its `package-info.java` still gets
written here, same as every other role — but the logging/masking annotations and AOP
aspects that belong in it are `.java` beyond `package-info.java`, which this skill never
writes (see the contract above). They're installed later, once, by the
`commons-logging-installer` agent, triggered from `/new-feature`'s pre-flight check —
not from here. Don't write them, and don't skip the empty `package-info.java` either:
without the package existing on disk, the installer has nowhere to write into.

```java
/**
 * Cross-cutting logging and masking infrastructure (annotations + AOP aspects). See
 * {@code .claude/rules/logging.md}. Empty until `commons-logging-installer` runs.
 */
package com.example.demoapp.commons.logging;
```

**b) Configuration for active features.**

| Feature | What this step does |
|---|---|
| `actuator` | Merges `templates/features/actuator/application-actuator.yml.example` into the `application.yml` of the module with `contains_main: true` (the same file from step 6, not a new one) |
| `observability` | Merges `templates/features/observability/application-observability.yml.example` into the same `application.yml` — the tracing bridge's export destination. Owned exemplar, same mechanism as `actuator`'s; the container it points at is provisioned in step 4.10, not here. Also adds the method in `templates/features/observability/ApplicationTests-tracer.java.example` to the generated `*ApplicationTests`: a context that starts without a `Tracer` bean is a generation gap, and nothing else catches it before the first use case |
| `flyway` | Creates `src/main/resources/db/migration/` in the module with the `infrastructure.persistence` role, **empty**. No `.gitkeep` and no `V1__`: `spring.flyway.fail-on-missing-locations` defaults to `false` (verified in `spring-boot-flyway`'s metadata), so a missing or empty location doesn't break startup. The first migration comes from `java-spring-boot-developer`, materialized from the SQL `persistence-architect` fixed in the partial |
| `persistence-jpa`, `rest`, `openapi`, `testcontainers`, `archunit` | Nothing here. They're dependencies (step 3) and POM configuration (step 4). The code that uses them comes from the first feature |
| `spring-modulith` | Writes `<main-module>/src/test/java/**/ModularityTests.java`, from `templates/features/spring-modulith/ModularityTests.java.example`, adjusting only the package and the `@SpringBootApplication` class reference. Safe to write now, unlike ArchUnit — `ApplicationModules.of(...).verify()` passes meaningfully over zero modules; it isn't gated behind business code existing |

**No business classes.** No entity, no controller, no use case, no migration with a
table. Each one's shape has an owner — `domain-modeling`, `persistence-architect`,
`rest-api-architect`, `test-architect` — and writing it here creates a second exemplar
of the same role, which diverges from the first. That's what happened:
`@.claude/decisions/0011-bootstrap-without-business-code.md`.

### 4.8 · Generate the `lombok.config`

The root build file declares `org.projectlombok:lombok` as `optional` (Maven) /
`compileOnly`+`annotationProcessor` (Gradle), inherited by all modules either way.
Without this file, `@Data` and `@Setter` compile — and `@.claude/rules/lombok.md` stays
prose, the same way `code-quality.md` stayed without Checkstyle.

1. Write `<project>/lombok.config` with the shape of `templates/lombok.config.example`
   — same file for both build tools, Lombok itself doesn't distinguish Maven from
   Gradle. **One only, at the root, next to the root `pom.xml`/`build.gradle`.** Lombok
   climbs the folder tree until `config.stopBubbling = true`, so the root covers every
   module. Copying the file into each module adds nothing and creates four places to
   diverge.
2. Don't remove lines from it. Each `flagUsage = ERROR` mirrors a rule's prohibition;
   removing one makes the rule and the build disagree, same as in step 4.6.
3. Don't add `lombok.fieldDefaults.defaultPrivate = true`. It would make implicit what
   the rule wants explicit — the `@FieldDefaults(level = AccessLevel.PRIVATE)`
   annotation at the top of the class is what's read in the file, and a global default
   hides it.

There's no version to resolve: `spring-boot-starter-parent` pins Lombok's in Maven, and
the imported `spring-boot-dependencies` BOM pins it the same way in Gradle
(`dependencyManagement { imports { mavenBom "org.springframework.boot:spring-boot-dependencies:..." } }`,
already in `templates/build.gradle.parent.example`). If you write a version yourself, in
either build file, it's the same mistake as step 3 — a number written from memory.

### 4.9 · Generate the `logback-spring.xml`

Without this file the project still logs — Spring Boot's default logback config is
enough to run — but the default pattern has no `traceId` field, and
`@.claude/rules/logging.md`'s per-line contract stays prose nobody's config actually
produces.

1. Write `<module>/src/main/resources/logback-spring.xml` with the shape of
   `templates/logback-spring.xml.example`, where `<module>` is the module with
   `contains_main: true`. **Unlike `lombok.config` (step 4.8), this file cannot sit at
   the project root** — logback only resolves its config from the classpath root, and a
   root-level file is never on it. It has to be under `src/main/resources`, in the
   module that actually gets packaged, and named `logback-spring.xml` (not plain
   `logback.xml`) so Spring Boot's own initialization picks it up.
2. Don't change the pattern line. It's `logging.md`'s contract in executable form; a
   project that needs structured JSON output changes the encoder and the rule's pattern
   line in the same pass, not this file alone.

### 4.10 · Generate the base `Dockerfile` and `docker-compose.yml`

The base pair, plus a container for every feature that's **already active in the
blueprint** and needs one to run. A container is not business code: provisioning the
Postgres that `application.yml`'s own datasource URL already points at (or the OTLP
collector its tracing endpoint already points at) invents nothing — the config from
step 4.7.b already committed to that dependency existing. What *does* stay deferred to
`docker-architect`, invoked later by hand — a design skill records the need and never chains
that skill, `@.claude/decisions/0058-skill-classes-territory-schema.md` — is anything a
**use case** decides that isn't
already implied by an active `features:` flag: a non-default engine, a broker, an
extra datastore. `@.claude/decisions/0011-bootstrap-without-business-code.md` governs
that second category — a made-up aggregate competing with a real spec — not this one.

1. Write `<project>/Dockerfile` from **either** `templates/Dockerfile.example`
   (`build.tool: maven`) **or** `templates/Dockerfile-gradle.example` (`build.tool:
   gradle`) — never both, same exemplar the resolved build tool has used since step 3.
   Fill `{{JAVA_VERSION}}` with the version resolved in step 3 and
   `{{MAIN_MODULE_JAR_PATH}}` with the jar path of the module with `contains_main:
   true`:
   - Maven: `target/<artifactId>-<version>.jar` in `single-module`,
     `<module-path>/target/<artifactId>-<version>.jar` in `multi-module`. In
     `multi-module`, replace `{{MODULE_POM_COPIES}}` with one `COPY <module>/pom.xml
     <module>/` line per module, so the dependency-resolution layer caches correctly;
     in `single-module`, delete that placeholder line — there's nothing to copy beyond
     the root `pom.xml` already copied above.
   - Gradle: `build/libs/<artifactId>-<version>.jar` in `single-module`,
     `<module-path>/build/libs/<artifactId>-<version>.jar` in `multi-module`. In
     `multi-module`, replace `{{MODULE_BUILD_GRADLE_COPIES}}` with one `COPY
     <module>/build.gradle <module>/` line per module, same caching reason; in
     `single-module`, delete that placeholder line — there's nothing to copy beyond the
     root `build.gradle`/`settings.gradle` already copied above.
2. Write `<project>/docker-compose.yml` with the shape of
   `templates/docker-compose.yml.example`, verbatim — no substitution needed, it has no
   `{{...}}` placeholders. This is the base `app` service only.
3. **For each active feature with a matching service template, apply
   `docker-architect`'s own merge procedure (its `SKILL.md` steps 3-5) against the
   `docker-compose.yml` just written** — `docker-architect` stays the single owner of
   every service block, this step only decides *when* to call it for features the
   blueprint already turned on:
   - `persistence-jpa` → `docker-architect/templates/postgres-service.yml.example`.
     Postgres, not a placeholder: it's the engine `application.yml.example`'s
     `datasource.url` already assumes, so this doesn't introduce a new decision, it
     makes the two files agree. If `persistence-architect` later designs a different
     engine for a real use case, that's a `docker-architect` re-invocation like any
     other, swapping the service the normal way.
   - `observability` → `docker-architect/templates/otel-collector-service.yml.example`,
     plus its init script `templates/otel-collector-config.yml.example` mounted per
     `docker-architect/SKILL.md` step 6. **Skip its step 2.5**: generation stays
     non-interactive, so the collector is born exporting to `debug` and no
     visualization backend is chosen here. Naming that gap is step 8's job, below.
   - Wire the `app` service's environment for each service added, same as
     `docker-architect/SKILL.md` step 5 — `SPRING_DATASOURCE_URL` pointing at
     `postgres`'s compose hostname, and **both** OTLP variables pointing at
     `otel-collector`'s: `OTLP_ENDPOINT` (`/v1/traces`) and `OTLP_METRICS_ENDPOINT`
     (`/v1/metrics`). Two, not one: the observability fragment declares a placeholder
     per signal, and an unwired metrics endpoint falls back to the app container's own
     `localhost`, silently.
   - A feature with no service template (`rest`, `openapi`, `testcontainers`, `flyway`
     — Flyway rides on the same Postgres connection, `archunit`) adds nothing here.
4. Same rule as § andaime (step 4): the exemplar's top comment explaining *why* stays;
   anything describing the file as a template goes.

### 5 · Generate the boundary map

For each module with `forbidden_imports`, write one line per prefix in
`.claude/forbidden-imports.txt`, in the format `<module-path>|<prefix>`:

```
# Generated by /init-project — blueprint: <id> — do not edit by hand
domain|org.springframework.
domain|jakarta.persistence.
application|jakarta.persistence.
```

**`layout: single-module` has no `forbidden_imports` field to iterate** — it's declared
per package there, not per module (see the blueprint's own comment above its `modules:`
block). In that case derive the lines instead: for each `dependency_rules.forbidden`
entry, take `from` as the line's prefix — resolved through `packages.map` to its real
package path — and pair it with the framework roots `architecture-ddd.md` § Domain bans
(`org.springframework.`, `jakarta.`, `com.fasterxml.jackson.`, `tools.jackson.`) for a
`from: domain` entry, or with the concrete `to` layers' package paths for any other
entry. The file format doesn't care whether the left side is a module path or a package
prefix — `ArchHook.java check` matches with `rel.startsWith(prefix + "/")` either way.

Don't write the literal string `blueprints/` into this header, or into any file this
step generates — step 8's autonomy test greps for exactly that string across the whole
project, and a header written that way fails the generator's own verification.

**This file is what gives `ArchHook.java check` its teeth.** Without it the hook warns
that enforcement is off, but blocks nothing. Generating it is not optional.

### 6 · Generate the root CLAUDE.md and the module ones

**Root** — write `<project>/CLAUDE.md` with the shape of
`templates/root.CLAUDE.md.example`, replacing the `{{...}}` placeholders with the
resolved data: name, versions from step 3, real build commands, and the list of
modules with their `depends_on`. `{{buildCmd}}`/`{{testCmd}}`/`{{runCmd}}`/`{{formatCmd}}`
resolve from the same build tool every earlier step branched on — never a mix:

| Placeholder | `maven` | `gradle` |
|---|---|---|
| `{{buildCmd}}` | `./mvnw clean verify` | `./gradlew build` |
| `{{testCmd}}` | `./mvnw test` | `./gradlew test` |
| `{{runCmd}}` | `./mvnw spring-boot:run` | `./gradlew bootRun` |
| `{{formatCmd}}` | `./mvnw spotless:apply` | `./gradlew spotlessApply` |

Target under 200 lines: it's an index and invariants, not a manual. No `rules/` rule is
reproduced **inside** this file — only cited by path. The rule files themselves are
copied into the project in step 6.6.

**Per module** — for each module with non-empty `forbidden_imports`, write
`<module>/CLAUDE.md` with the shape of `templates/module.CLAUDE.md.example`, filled
with that module's data. The content **derives from the blueprint** — if you write it
by hand, it diverges from what the hook enforces.

### 6.5 · Generate CI

The Initializr doesn't generate CI. Write `.github/workflows/build.yml` with the shape
of **either** `templates/ci.yml.example` (`build.tool: maven`) **or**
`templates/ci-gradle.yml.example` (`build.tool: gradle`) — never both — adjusted to the
Java version resolved in step 3. Without this, the `.github/workflows/*` declared in the
skill's § Contract has no real counterpart. The exemplar's comment "(see
decisions/0011)" is the same dead reference the `export` mode cuts in § 6.6 — cut it
here too, the reasoning it points to has no counterpart in the project.

The `.gitignore` is **not** generated here: the Initializr already delivers a correct
one in step 3. Confirm it exists; if it's missing, write it then. It's not in `owns`
for that exact reason.

### 6.6 · Write the project's `.claude/`

The generated project lives on its own: nothing inside `<project>/` may depend on
`claude-spring-architect` existing on the machine of whoever clones it. The root
`CLAUDE.md` cites `.claude/rules/00-index.md`, rules cite each other by path, and
`settings.json` points at `.claude/hooks/ArchHook.java` — if those files aren't there,
each citation is a silent dead end and the hooks fail to start.

One command writes all of it:

```bash
CLAUDE_PROJECT_DIR=<this repository> \
  java <this repository>/.claude/hooks/ArchHook.java export <project> --blueprint <id>
```

`CLAUDE_PROJECT_DIR` is the **source**, the same convention every other mode of the hook
follows; `<project>` is the destination, and the mode refuses to run when the two are the
same path. Add `--dry-run` to list what it would write without writing anything.

What it writes, per the `export` block of `@.claude/schemas/extensions.json`:

- **Every rule**, with the `paths` of those that have a territory derived from the
  blueprint's `packages.map` — the value, `.` becoming `/`, this blueprint's glob and no
  other. An architecture that names no such package (`vertical-slice` puts REST and
  persistence inside each slice) falls back to the matching `architecture_paths` entry;
  one that has no such territory at all (`messaging.md` where nothing is a broker) gets
  the rule without `paths`, still citable. `naming.md` receives the blueprint's
  naming-convention block as a list, and `00-index.md`'s two paragraphs about
  `blueprints/` are rewritten to name the blueprint instead.
- **The development skills**, each reduced to `SKILL.md`, `templates/` and
  `references/` — a subdirectory that only serves this repository, like
  `use-case-design/examples/`, stays behind.
- **The development agents**, whole.
- **`ArchHook.java`, `schemas/extensions.json`, `settings.json`**, plus
  `.claude/audit-usage/` with its `pricing.json` and the one `.gitignore` line the trail
  needs.
- **`.claude/.arch-provenance.json`** — where this `.claude/` came from (source, ref,
  commit, blueprint) and a digest per written file. `arch-doctor` recomputes those
  digests and names anything edited since, which is what a later update would overwrite.
- **`.mcp.json` and `MCP-SETUP.md`**, only if `templates/mcp.json.example` exists here.
  Most bootstraps have nothing to copy and the mode says nothing — correct, not
  incomplete. That template is written only by `claude-code-architect-designer`, when a
  real server was designed with its axis 13 answered "the generated project" or "both".

Every citation to `.claude/decisions/` and `.claude/blueprints/` is cut on the way out:
neither directory travels (invariant 9), and unlike a rule path there is nothing inside
the project to rewrite them to. What survives the cut is printed at the end of the run,
by file — **read that report**. A dead path breaks nothing at generation time and
everything for whoever follows it six months later.

`settings.json` is written whole, not merged: re-serializing it would reorder its keys
and drop its comments. On a fresh bootstrap there is nothing to preserve; when the mode
runs again over an existing project, its previous content is one `git diff` away.

**The manifest is the owner of what travels.** Adding a rule, skill or agent to this
repository is only complete once it is in `export`'s `include` — or in its `exclude`,
with the creation skills (`project-bootstrap`, `init-project`,
`claude-code-architect-designer`) and `project-initializer`, which only serve before the
project exists and, inside one, invite the model to regenerate on top of live code.
`ArchHook.java schema` fails by name on anything listed in neither, so this is not a step
to remember: it is a check that runs.

### 7 · What the enforcement you just installed does

Nothing to run here — step 6.6 already wrote it. This section is what the final report
(§ Output contract) has to be able to say.

**The hooks.** `settings.json` arrives with the `schema` mode's triggers, the `audit`
triggers, the `guard` triggers, and the permissions: the pipeline skills in `allow`,
`Bash(git push:*)` in `ask`. Preserving a project's own entries never means widening
`git push` into an allow. The hooks run in **exec form** (`command: java` + `args`), with
no shell and no execute bit, which is what makes them identical on Linux, macOS and
Windows — there is no `chmod` anywhere in this skill.

**The trail.** `.claude/audit-usage/` is the on/off switch: `ArchHook.java audit` returns
immediately when the directory is absent. Every skill under `.claude/skills/` and every
agent under `.claude/agents/` is recorded, whoever invoked it; a top-level invocation
gets one Markdown report, and what it chains becomes sections of that report plus a line
each in `nodes.jsonl`. Plugin skills and runtime agents (`Explore`, `general-purpose`)
have no file in the project and are not recorded. It is a hook and not a skill because
the record has to exist even when the model forgets, the session dies, or the user
interrupts — `@CLAUDE.md` invariant 6.

**The guard.** Two boundaries of the `/new-feature` pipeline that its skills state in
prose and a real run broke anyway: while a design skill runs, nothing is written under
`src/` except from inside the executor agent; and the files of a use case whose
`UC-NNN-spec.md` is `approved` or `implemented` are frozen, except the executor's single
`approved → implemented` status edit. The lists are data in the `guard` block of the
`extensions.json` that step 6.6 copied. No `guard` block, no guard.

**`pricing.json` ages on its own.** It ships pre-filled with the official per-model rates
as of the date in its own `$comment`, never from memory — same discipline as invariant 8.
A model missing from it, or a rate that has since changed, prints "não configurado"
instead of a confident `US$ 0.00` nobody checked. Tell the user, in the final report, to
compare that date against
`platform.claude.com/docs/en/about-claude/pricing` and update the file if it has gone
stale; it does not refresh itself.

### 8 · Verify

Everything in this step branches on the same build tool resolved before step 3 — run
**only** the column that matches.

**build.tool: maven** — the `starter.tgz` from step 3 **already brings** `mvnw`,
`mvnw.cmd`, and `.mvn/wrapper/` — don't run `mvn -N wrapper:wrapper`. Confirm the
wrapper exists and works before anything else:

```bash
./mvnw -v                       # failure here = starter.tgz didn't extract the wrapper
./mvnw -q clean test-compile    # boundaries + Checkstyle (validate phase)
./mvnw -q test                  # Initializr smoke test — only the `*Test`s
./mvnw -q checkstyle:check      # Checkstyle only, to isolate violations
./mvnw clean verify             # includes the `*IT`s via failsafe
```

In `verify`, the freshly generated project **has no `*IT` at all** — it doesn't
generate business code, so it doesn't generate integration tests. Zero ITs discovered
is the expected result here, unlike what held when this step used to generate
examples. What you're confirming is that `maven-failsafe-plugin` made it into the
POM: without it, the first `*IT` that `test-architect` designs compiles and never runs.

```bash
grep -c maven-failsafe-plugin pom.xml    # must be ≥ 1
```

With `features.observability` active, confirm the piece that registers the `Tracer` bean
made it in — the bridge and the exporter alone don't, and the gap otherwise surfaces only
at the first use case that injects it:

```bash
./mvnw -q dependency:tree | grep -E 'micrometer-tracing-bridge|opentelemetry-exporter-otlp|spring-boot-starter-opentelemetry'
# all three lines present on Spring Boot 4 — see references/dependency-catalog.md
```

**build.tool: gradle** — the `starter.tgz` from step 3 **already brings** `gradlew`,
`gradlew.bat`, and `gradle/wrapper/` — don't run `gradle wrapper`. Confirm the wrapper
exists and works before anything else:

```bash
./gradlew -v                              # failure here = starter.tgz didn't extract the wrapper
./gradlew -q --no-daemon compileTestJava  # boundaries + Checkstyle (part of `check`, see below)
./gradlew -q --no-daemon test             # Initializr smoke test — only the `*Test`s
./gradlew -q --no-daemon checkstyleMain checkstyleTest  # Checkstyle only, to isolate violations
./gradlew --no-daemon build               # includes the `*IT`s via the `integrationTest` task
```

In `build`, the freshly generated project **has no `*IT` at all** — same expected
zero-ITs result as Maven, for the same reason. What you're confirming is that the
`integrationTest` task made it into `build.gradle` and is wired into `check`: without
it, the first `*IT` that `test-architect` designs compiles and never runs.

```bash
grep -c "tasks.register('integrationTest'" build.gradle    # must be ≥ 1 (root, or the subprojects block)
```

With `features.observability` active, same confirmation, Gradle's dependency report
instead of Maven's:

```bash
./gradlew -q --no-daemon dependencies | grep -E 'micrometer-tracing-bridge|opentelemetry-exporter-otlp|spring-boot-starter-opentelemetry'
# all three lines present on Spring Boot 4 — see references/dependency-catalog.md
```

**Both, from here on**

Boundary test, mandatory before reporting success: temporarily write
`<domain-module>/src/main/java/<domain-package>/ArchHookProbe.java` with an
`import org.springframework.stereotype.Component;`, confirm the hook blocks the write,
and delete the file if one was left. If it doesn't block, `forbidden-imports.txt` is
wrong. The probe is the only business class this procedure ever writes, and it exists
only for the duration of the test.

`lombok.config` test, for the same reason and the same way: temporarily put a
`@Setter` with a field on the `@SpringBootApplication` class — the only one that
exists —, run `./mvnw -q test-compile` (Maven) or `./gradlew -q --no-daemon
compileTestJava` (Gradle), confirm the compilation stops with `Use of @Setter is
flagged according to lombok configuration`, and remove it. Compiling green means the
file isn't at the root, or `flagUsage` didn't make it in — the `lombok.md` rule would
run with no enforcement at all.

If any test depends on Testcontainers (Spring context with a real DB), guard it with a
condition that checks the Docker daemon (e.g. `@EnabledIf("dockerAvailable")`). Without
that, `./mvnw test`/`./gradlew test` goes red on any machine without local Docker — a
false negative that says nothing about the code. The test actually runs in CI, where
the runner has a daemon.

If the build fails, **fix it before reporting**. A bootstrap that delivers a red build
isn't finished.

**Autonomy test**, also mandatory: nothing in `<project>/` can cite a path that only
exists in `claude-spring-architect`.

```bash
ls .claude/rules/ .claude/skills/ .claude/agents/ .claude/hooks/ArchHook.java .claude/schemas/extensions.json
grep -rn "blueprints/" .claude/ CLAUDE.md */CLAUDE.md   # must return nothing
grep -rn "decisions/" .claude/ CLAUDE.md */CLAUDE.md    # must return nothing — § 6.6 cuts these
java .claude/hooks/ArchHook.java schema </dev/null      # must exit 0, and without warning
```

The `schema` check above fails two different ways: exits 2 if some copy ended up with
invalid frontmatter, and exits 0 **with a warning** if `extensions.json` didn't make it
into the project. Read the output, not just the exit code.

Every skill the root `CLAUDE.md` routes to must have `SKILL.md` in the project. Every
agent the root `CLAUDE.md` delegates to must have `<name>.md` in `.claude/agents/`. A
dead path here is exactly the failure § 6.6 exists to prevent, and the `export` mode
already reported any citation that survived its cut — this is the second reading of the
same question, from the project's side.

### 8.5 · Generate the project README

Runs **after** Verify, on purpose: everything this step writes — the build status, the
boundary and Lombok probe results — only exists once step 8 has finished. A README
written earlier would either lie about the build or need a second pass to patch it.
Without this step the generated project ships with no README at all: `HELP.md` is the
Initializr's own boilerplate, and `CLAUDE.md` targets an AI reader, not a human seeing
the repository for the first time — `@.claude/lessons-learned/lessons-learned-004.md`
Gap 2.

Write two files at `<project>/`, from `templates/README.md.example` (English, the
default) and `templates/README.pt-br.md.example` (Portuguese) — same shape rule as every
other exemplar in this skill: read it, understand it, write the equivalent, don't
mechanically substitute. Same andaime rule as step 4/6: the top comment explaining the
file is an exemplar is cut; a citation to a rule or invariant stays.

Every `{{...}}` placeholder resolves from data this procedure already computed —
**nothing here is invented**:

- `{{projectName}}`, `{{groupId}}`, `{{artifactId}}`, `{{targetDirectory}}` — step 3's
  inputs.
- `{{initCommand}}` — the literal `/init-project` invocation that started this run,
  arguments included, exactly as the user or the command line gave it. This is the
  README's only source for "how was this made" — the generated project has no other
  record of it, since git history starts at `git-publish`'s first commit, after
  generation.
- `{{blueprint.id}}`, `{{blueprint.oneLineDescription}}` — step 1/2, the `name` and a
  one-line cut of the blueprint YAML's own description. Don't paraphrase past what the
  YAML says.
- `{{javaVersion}}`, `{{springBootVersion}}`, `{{buildTool}}` — step 3's resolved
  versions, the same ones already in the Output contract below. Never re-resolve, never
  restate from memory.
- `{{featuresList}}` — the active features from step 3/4.7, one bullet each.
- `{{boundedContext}}` — step 3's input, defaulting to `{{artifactId}}` when the caller
  gave none. It is the first segment of every topic name
  (`@.claude/rules/messaging.md` § Topics and serialization) and nothing else in the
  project declares it, so it is written into the root `CLAUDE.md` even when messaging is
  not an active feature: the case that adds Kafka later reads the line instead of choosing
  a prefix inside one use case.
- `{{skillsList}}`, `{{agentsList}}` — one bullet per entry actually written into the
  project (`ls .claude/skills/`, `ls .claude/agents/` — read the disk, not the `export`
  manifest: the manifest lists what *can* travel, not what a given blueprint's feature
  set produced), each with a one-line "Why" taken from that skill's or agent's own
  `description`, not invented here.
- `{{outputContractBlock}}` — the exact block this step's successor (§ Output contract)
  renders, pasted verbatim. The README and the report given to the user are the same
  text; this is not a second, independently-written summary that can drift from the
  first.
- `{{nextStepsBlock}}` — the same "Next steps" list as the Output contract, verbatim.

Both files are self-contained: reading only `README.md` (or only `README.pt-br.md`)
answers "what is this, how was it made, what can I do with it" with no need to open
`CLAUDE.md` or ask the meta-repo. The cross-link at the top of each is the only coupling
between the two.

### 8.6 · Write the audit genesis record

Runs **after** the README (8.5), for the same reason: the Output contract block it
embeds only exists once step 8 has finished. Fixes a different gap than 8.5 —
`@.claude/lessons-learned/lessons-learned-004.md` Gap 1: `.claude/audit-usage/` (§ 6.6,
`ensure_dirs`) exists in the freshly generated project, but the hook that fills it
(`ArchHook.java audit`) has never run there, because that hook only fires from a live
session rooted at the *generated* project, and the run that creates the project happens
from a session rooted at the *meta-repo* instead. Without this step the trail is
structurally empty on day one — not a missing report, a run nobody could have recorded.

Write `<project>/.claude/audit-usage/GENESIS.md` from
`templates/GENESIS.md.example`, same read-it-understand-it-write-the-equivalent rule as
every other exemplar here. `{{initCommand}}`, `{{blueprint.id}}`,
`{{blueprint.oneLineDescription}}`, `{{groupId}}`, `{{artifactId}}`, `{{buildTool}}`, and
`{{outputContractBlock}}` resolve exactly as documented for the same placeholders in
step 8.5 — same values, second destination. `{{startIso}}` is this run's own start
timestamp (interview's first question, step 1); `{{endIso}}` is now, at the moment this
step runs; `{{buildStatus}}` is step 8's own `PASSED`/`FAILED` verdict, restated, never
re-derived.

**Never overclaim fidelity.** This file names itself a reconstruction, not a hook
report, and stays that way — no invented per-tool-call timeline, no cost, no ranked
stages, none of the fields `ArchHook.java audit`'s own reports carry. A later
`/audit-usage` reader must be able to tell this entry apart from every report that
follows it in the same directory. Written once; a second `/init-project` run never
overwrites it (idempotence — § Preconditions already stops before this step if the
project exists).

## Output contract

```
✅ Project <name> created — blueprint <id>

Modules:
  <path> → depends on <list>
  ...

Versions (resolved by the Initializr): Java <x> · Spring Boot <y> · <build tool>
Active features: <list>
Business code: none — by design. <n> package-info.java written
Boundaries: <n> rules in .claude/forbidden-imports.txt — blocking verified ✓
Checkstyle: config/checkstyle/checkstyle.xml — plugin <v> · tool <v>, validate phase
Lombok: lombok.config at the root — @Data and @Setter stop compilation
ArchUnit: to be installed — `test-architect` skill (see Next steps)
Coverage: JaCoCo generates a report; the 80%/70% gate comes in with `test-architect`
Self-contained: <n> rules + <n> skills + <n> agents + ArchHook.java + extensions.json written by `ArchHook.java export` — no dead paths ✓
Provenance: .claude/.arch-provenance.json — blueprint <id>, ref <ref>, commit <short sha>. `/arch-doctor` reports anything edited since
Audit trail: .claude/audit-usage/ active — one report per skill or agent invocation from now on, by `/command` or by the model. GENESIS.md records this run itself. Fill pricing.json to see cost
Docker: Dockerfile + docker-compose.yml — <list: app, plus one entry per service `docker-architect` merged in step 4.10 for an active feature, e.g. "postgres (persistence-jpa)", "otel-collector (observability)"> — extend with `docker-architect` for anything a future use case adds
Observability UI: <omit this line entirely when `observability` is not active> none — the collector exports to `debug`, which writes spans and metrics to its own stdout and is not a dashboard. Run `/docker-architect` to add one: Jaeger (traces, one container) or Grafana + Tempo + Prometheus (traces and metrics, three)
MCP: <none — no server designed for this project yet | <n> server(s) copied to .mcp.json, see MCP-SETUP.md>
Build: <PASSED | FAILED: reason>
Docs: README.md (English, default) + README.pt-br.md — origin, blueprint, stack, skills/agents, this report

Next steps:
  1. /use-case-design <first-use-case-name>
  2. Install the architecture tests (ArchUnit) and wire up the coverage gate with the
     `test-architect` skill as soon as business classes exist. Until then boundaries
     are guaranteed only by the hook (inside Claude Code)<, and by the POMs'
     `depends_on` (in the build) — only if `layout: multi-module`>.
```

The part between `<>` in line 2 only appears if the blueprint is `layout:
multi-module`. In `single-module` there's no per-layer POM and the compiler enforces no
boundary at all: outside Claude Code the project has no enforcement whatsoever until
ArchUnit is installed. Write that, in those words — the generic sentence promises a
build guarantee that doesn't exist.

The ArchUnit and coverage lines aren't optional: without them the user ends up thinking
`code-quality.md`, `architecture-ddd.md`, and `testing.md` already have full automatic
verification. They have part of it — Checkstyle and `lombok.config`. The other two come
in with `test-architect`, and until they do, `verify` checks neither dependency
direction nor coverage.

Neither is the business-code line: a user used to scaffolds expects to find an example
controller. Writing "none — by design" is what keeps them from looking for it and
concluding the generation failed.

## Applicable rules

All rules in `.claude/rules/` are master content: they guide the generation **and**
are copied into the project in step 6.6, so it lives without this repository.

Architecture: `@.claude/rules/architecture-ddd.md` — copied with `paths` coming from
the blueprint's `architecture_paths`; the rest go verbatim.
Naming: `@.claude/rules/naming.md`
Error handling: `@.claude/rules/error-handling.md` — doesn't guide any step since the
exception family moved to `domain-modeling` (see 4.5), but is copied.
Testing: `@.claude/rules/testing.md` — also doesn't guide: the coverage gate it sets is
wired by `test-architect` (see 4.6), not here.
Code quality: `@.claude/rules/code-quality.md` — SOLID and Clean Code limits; the
mechanical half becomes Checkstyle in step 4.6. The other half (ArchUnit) belongs to
the `test-architect` skill.
REST API: `@.claude/rules/api-rest.md` — doesn't guide any bootstrap step (no
`ApiExceptionHandler` is generated here, see 4.5), but is copied so the project is
complete.
Lombok: `@.claude/rules/lombok.md` — the mechanical half (forbidding `@Data` and
`@Setter`) becomes the root `lombok.config` in step 4.8.

Don't reproduce these rules here. If you need one that doesn't exist in `rules/`,
create the rule file first — don't write it inside this skill.

## Why this is a skill and not an agent

Form 1 (auto-invocable skill): it is a long generation procedure with no interview of its
own, and `/init-project` already isolates the verbose part inside the `project-initializer`
agent. Form 3 was rejected for this file because the three reasons for an agent (preserve
context, restrict tools, change model) are satisfied by that caller, not by this procedure —
two nested agents would only add a second boundary to pass the blueprint across.

## Contract

**Class:** build — the territory is `skill_classes.build`'s override for this skill in
`@.claude/schemas/extensions.json`: the whole tree of the project being generated. It is the
only skill with that reach, and it has it because the tree does not exist yet when it runs.
`ArchHook.java guard` enforces it.

**Reads before generating** — none of these auto-load at this point: the directory
doesn't have any `.java` files touched yet, so `naming.md` and `error-handling.md`'s
`paths` don't trigger. Read them explicitly before step 4:

- `.claude/blueprints/<id>/<id>.yaml` — the blueprint chosen in step 1
- `.claude/rules/00-index.md`
- `.claude/rules/architecture-ddd.md`
- `.claude/rules/naming.md`
- `.claude/rules/error-handling.md`
- `.claude/rules/code-quality.md`
- `.claude/rules/api-rest.md`
- `.claude/rules/lombok.md`
- `.claude/rules/value-objects.md`
- `.claude/rules/persistence.md`
- `.claude/rules/testing.md`
- `.claude/rules/observability.md`
- `.claude/rules/messaging.md`

All of them are read, not just the ones that guide the generation: step 6.6 copies
them into the project, and a partial copy isn't the rule.

**Writes** (paths relative to the **generated project**, not this repository) — no
other skill touches these files:

- Exactly one of: `pom.xml` and `*/pom.xml` (`build.tool: maven`), or `settings.gradle`,
  `build.gradle`, and `*/build.gradle` (`build.tool: gradle`) — never both in the same
  project, see step 4
- `CLAUDE.md` and `*/CLAUDE.md`
- `.claude/forbidden-imports.txt`
- `.claude/rules/*.md` — every rule of this repo, `paths` derived, see step 6.6
- `.claude/skills/**` and `.claude/agents/**` — whatever `export`'s `include` lists, see
  step 6.6. The list is data in `@.claude/schemas/extensions.json`, not prose here
- `Dockerfile` and `docker-compose.yml` — base pair, step 4.10, plus (via
  `docker-architect`'s own templates and merge procedure, called from the same step)
  one service per blueprint feature that's already active and needs a container
  (`persistence-jpa`, `observability` today). Every service a **use case** adds
  afterward is `docker-architect`'s alone, invoked on its own thread, not this skill's
- `.claude/hooks/ArchHook.java` — copy, see step 6.6
- `.claude/schemas/extensions.json` — copy without its own `export` block, see step 6.6
- `.claude/settings.json` — written whole, see step 6.6
- `.claude/audit-usage/pricing.json` and the `.claude/audit-usage/` directory itself —
  step 6.6. The reports and `history.jsonl` inside it are written afterwards by
  `ArchHook.java audit`, never by this skill
- `.claude/audit-usage/GENESIS.md` — step 8.6, once, the only report in that directory
  this skill ever writes itself. Everything else in that directory after it is
  `ArchHook.java audit`'s alone
- `.mcp.json` and `MCP-SETUP.md` — **only if** `templates/mcp.json.example` exists, step
  6.6 as an optional copy. Absent in most bootstraps, on purpose
- `config/checkstyle/checkstyle.xml`
- `lombok.config`
- `src/main/resources/application*.yml`
- `src/main/java/**/package-info.java` — one per role in `packages.map`, step 4.7. **No
  other `.java`**: business classes come from the `/new-feature` pipeline
- `.github/workflows/*`
- `README.md` and `README.pt-br.md` — step 8.5, after Verify. English is the default,
  Portuguese the linked option

`.gitignore` is deliberately left out — it comes from the Initializr (step 6.5).

**Hands off to** `use-case-design`, `domain-modeling`, `persistence-architect`,
`rest-api-architect`, and `test-architect`, which design the first feature. The
architecture tests and the coverage gate belong to `test-architect`'s setup mode —
this bootstrap deliberately doesn't generate them.

**Does not write business code, and this is the § of the contract that says so.** No
`.java` beyond the `package-info.java` files, no `.sql`, no file in `src/test/**`. If a
future step needs to emit a class, the right question is which layer skill owns that
shape — not how to add one more exemplar here.
