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 Maven wrapper.
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
java --version && git --version && curl --version | head -1
ls ${CLAUDE_SKILL_DIR}/templates/*.example
Check that the exemplars the procedure cites are there —
pom.parent.xml.example and pom.module.xml.example (step 4),
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), Dockerfile.example and docker-compose.yml.example (4.10),
root.CLAUDE.md.example and module.CLAUDE.md.example (step 6),
settings.json.example and audit-pricing.json.example (step 7), ci.yml.example
(step 8), 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 or invent Spring Boot versions from what you think
you know — the version you have in memory is stale by construction.
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.
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.
3 · Generate the base with Spring Initializr
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 .
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 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:
grep -m1 '<version>' pom.xml && grep -m1 'java.version' pom.xml
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
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:
Convert the root
pom.xmlinto a parent (<packaging>pom</packaging>+<modules>), usingtemplates/pom.parent.xml.exampleas 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.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.xmlfromrepo1.maven.org— the repository itself. Don't usesearch.maven.org'ssolrsearch: 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.Create a
pom.xmlper module fromtemplates/pom.module.xml.example, with the dependencies that module'sdepends_onauthorizes — and only those.Move the
@SpringBootApplicationclass to the module withcontains_main: true.Move
application.ymlinto that module'sresources.
Mandatory order: parent → modules → main class → configuration → docs → CI. A swapped order leaves the build broken halfway through generation.
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 POM doesn't carry spring-boot-starter-web even if the
exemplar shows it in another module.
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.
Resolve the versions on Maven Central, same rule as step 3:
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-parentdoes not manageorg.jacoco:jacoco-maven-plugin(confirmed empty against the parent'sdependencyManagement) — leaving it unpinned resolves whatever's newest at build time, non-reproducibly: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.Write
<project>/config/checkstyle/checkstyle.xmlwith the shape oftemplates/checkstyle.xml.example. Fixed path — it's what the root POM'sconfigLocationpoints to, via${maven.multiModuleProjectDirectory}. Don't swap it for a relative path: it resolves at the root and fails in every submodule.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.Don't add formatting checks (imports, braces, spacing): Spotless handles that, already configured in the root POM. A duplicate check breaks the build over something the formatter would have fixed on its own.
Runs at validate, before compiling — 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 brings JaCoCo with prepare-agent and report, and
without the check execution: 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 check execution yourself: a project with no business
classes either passes the gate 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:
/**
* 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.
/**
* 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 POM declares org.projectlombok:lombok as optional, inherited by all
modules. Without this file, @Data and @Setter compile — and
@.claude/rules/lombok.md stays prose, the same way code-quality.md stayed without
Checkstyle.
- Write
<project>/lombok.configwith the shape oftemplates/lombok.config.example. One only, at the root, next to the rootpom.xml. Lombok climbs the folder tree untilconfig.stopBubbling = true, so the root covers every module. Copying the file into each module adds nothing and creates four places to diverge. - Don't remove lines from it. Each
flagUsage = ERRORmirrors a rule's prohibition; removing one makes the rule and the build disagree, same as in step 4.6. - 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. If you write
a <version> in the POM, 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.
- Write
<module>/src/main/resources/logback-spring.xmlwith the shape oftemplates/logback-spring.xml.example, where<module>is the module withcontains_main: true. Unlikelombok.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 undersrc/main/resources, in the module that actually gets packaged, and namedlogback-spring.xml(not plainlogback.xml) so Spring Boot's own initialization picks it up. - 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 or chained from persistence-architect /
messaging-architect / test-architect, 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.
- Write
<project>/Dockerfilewith the shape oftemplates/Dockerfile.example, filling{{JAVA_VERSION}}with the version resolved in step 3 and{{MAIN_MODULE_JAR_PATH}}with the jar path of the module withcontains_main: true—target/<artifactId>-<version>.jarinsingle-module,<module-path>/ target/<artifactId>-<version>.jarinmulti-module. Inmulti-module, replace{{MODULE_POM_COPIES}}with oneCOPY <module>/pom.xml <module>/line per module, so the dependency-resolution layer caches correctly; insingle-module, delete that placeholder line — there's nothing to copy beyond the rootpom.xmlalready copied above. - Write
<project>/docker-compose.ymlwith the shape oftemplates/docker-compose.yml.example, verbatim — no substitution needed, it has no{{...}}placeholders. This is the baseappservice only. - For each active feature with a matching service template, apply
docker-architect's own merge procedure (itsSKILL.mdsteps 3-5) against thedocker-compose.ymljust written —docker-architectstays 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 engineapplication.yml.example'sdatasource.urlalready assumes, so this doesn't introduce a new decision, it makes the two files agree. Ifpersistence-architectlater designs a different engine for a real use case, that's adocker-architectre-invocation like any other, swapping the service the normal way.observability→docker-architect/templates/otel-collector-service.yml.example, plus its init scripttemplates/otel-collector-config.yml.examplemounted perdocker-architect/SKILL.mdstep 6.- Wire the
appservice's environment for each service added, same asdocker-architect/SKILL.mdstep 5 —SPRING_DATASOURCE_URLpointing atpostgres's compose hostname,OTLP_ENDPOINTpointing atotel-collector's. - A feature with no service template (
rest,openapi,testcontainers,flyway— Flyway rides on the same Postgres connection,archunit) adds nothing here.
- 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. 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 templates/ci.yml.example, 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 as
§ 6.6/6.7/7 — cut it, 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 · Copy the rules into the project
The generated project lives on its own: nothing inside <project>/ can depend on
claude-spring-architect existing on the machine of whoever clones the repository. The root
CLAUDE.md cites .claude/rules/00-index.md, and rules cite each other by path — if
the files aren't there, each citation is a silent dead end.
Copy into <project>/.claude/rules/ all files from this repo's .claude/rules/:
| File | How to copy |
|---|---|
naming.md |
paths verbatim — the **/*.java glob doesn't depend on the blueprint. Body not verbatim: the active blueprint's naming-convention comment block is written, as a bulleted list, under ## Architecture vocabulary, replacing the HTML comment there. Without it the generated project has no use case vocabulary at all — the blueprint doesn't travel |
error-handling.md |
verbatim — same |
code-quality.md |
verbatim — same |
lombok.md |
verbatim — same |
logging.md |
verbatim — the **/*.java glob doesn't depend on the blueprint |
testing.md |
verbatim — the **/src/test/** glob doesn't depend on the blueprint |
value-objects.md |
derived paths: — see § Rules with a territory |
api-rest.md |
derived paths: — see § Rules with a territory |
persistence.md |
derived paths: — see § Rules with a territory |
observability.md |
derived paths: — see § Rules with a territory |
messaging.md |
derived paths: — see § Rules with a territory |
architecture-ddd.md |
paths: derived from architecture_paths — see below |
00-index.md |
one paragraph rewritten — see below |
Verbatim means byte for byte, frontmatter included. Don't summarize, don't adapt to the
blueprint, don't cut sections: a hand-rewritten rule stops being the same rule and
diverges from the original on the next update. In the rules with derived paths:, the
only thing that changes is the frontmatter's paths: block — the body is still copied
byte for byte.
One more exception, for the same reason as paths:: cut any @.claude/decisions/NNNN- ....md citation found in the body — value-objects.md, persistence.md,
observability.md, and testing.md all carry one or more. decisions/ never enters
the generated project (invariant 9, @CLAUDE.md) and, unlike a blueprints/ citation
(§ 6.7), a decision record has no counterpart inside the project to rewrite it to.
Remove only the citing clause (e.g. a trailing "Record: @.claude/decisions/...." or a
parenthetical "(see @.claude/decisions/...)") — leave the rest of the sentence and the
rule's meaning intact. This is the same class of dead reference as blueprints/; § 6.7
and § 7 apply the identical cut wherever the citation resurfaces.
Rules with a territory — the paths: comes from packages.map, not from the original file
A rule with an identifiable territory auto-loads when someone touches files in that territory. If the glob names a package this blueprint doesn't use, the rule never enters context — and the failure mode is silent: nothing breaks, the rule simply doesn't show up, and whoever edits a controller by hand goes without it.
The glob is never copied from the original file nor written from memory. It's derived
from the active blueprint's packages.map, with the same discipline architecture_paths
already gets:
| Rule | packages.map key |
Glob to write |
|---|---|---|
api-rest.md |
the one ending in .rest (adapter.in.rest, infrastructure.rest, …) |
**/<value with .→/>/** |
persistence.md |
the one ending in .persistence |
**/<value>/** plus **/db/migration/**, which doesn't come from the blueprint |
value-objects.md |
domain.model, or domain if the blueprint doesn't declare a sub-package |
**/<value>/**/*.java |
observability.md |
the one ending in .rest and the one ending in .config |
one glob for each |
messaging.md |
the one ending in .messaging (adapter.out.messaging, infrastructure.messaging, …), inbound and outbound if the blueprint splits them |
one glob for each; missing key → rule copied without paths (rule 3 below) |
Rules of the derivation:
- The value comes from
packages.map, not the key. The two coincide in every blueprint today, but it's the value that becomes a package on disk. - Dot becomes slash.
infrastructure.rest→infrastructure/rest. - Missing key, rule without
paths. If the blueprint has no messaging package, a rule about messaging is copied withoutpaths:— not with an invented glob. Withoutpathsthe rule can still be cited; with a dead glob both paths are lost. - No defensive union. Write this blueprint's glob and no other. Listing the names of every layout was the patch that produced the divergence this step closes.
Check before moving on: for each rule with paths, at least one file or directory
created in step 4.7 matches at least one of the globs. A glob that matches nothing is
dead enforcement and is a failure of this step, not of the project.
architecture-ddd.md — write the paths: frontmatter using the active
blueprint's architecture_paths literally — don't invent, don't infer from
modules[].path. It's that paths that makes the rule auto-load when someone, in the
generated project, touches files in domain, application, adapters/
infrastructure, or bootstrap — whatever they're called for this blueprint.
---
paths:
- "domain/**/*.java"
- "application/**/*.java"
- "adapters/**/*.java"
- "bootstrap/**/*.java"
---
(example for hexagonal; each blueprint declares its own list —
clean-architecture-multi-module has only 3 entries, domain/application/infrastructure,
and clean-architecture-single-module, being layout: single-module, uses package
globs instead of module folders)
00-index.md — the original explains that architecture-ddd.md has no paths of
its own because the globs come from the blueprint's architecture_paths, and cites
@.claude/blueprints/_schema.md. There's no blueprints/ in the generated project:
replace that paragraph with a line saying the paths was fixed at generation time from
blueprint <id> and that changing it by hand disables the auto-loading. Everything
else — the map of who covers what, the loading mechanism, the table of planned rules,
the states — copy verbatim.
If you add a rule to this repo's .claude/rules/, add it to this table too and to the
## Skill contract list. A rule that exists here and doesn't reach the generated
project is exactly the failure this step closes.
6.7 · Copy the development skills into the project
Same reason as the previous step, applied to procedure: the root CLAUDE.md routes to
skills by name, and a name with no SKILL.md behind it makes the model search and fail
silently. Copy only SKILL.md, templates/, and references/ from each
development skill into <project>/.claude/skills/<name>/. Not the entire directory
verbatim: use-case-design/examples/ is meta-repo documentation — pipeline test
fixtures for this repository's own /new-feature and java-spring-boot-developer —
and stays out of the generated project.
| Skill | Goes to the project? | Why |
|---|---|---|
arch-doctor |
✅ | Diagnoses the project's hooks and enforcement; the only place where running it makes sense. Prefixed name so it doesn't collide with Claude Code's native /doctor |
use-case-design |
✅ | Designs the use case before implementing it. Only makes sense once the project exists; carries templates/use-case-spec.md.example, templates/backlog.md.example, and references/scope-boundary.md |
domain-modeling |
✅ | Details domain and application for an already-designed use case. Carries templates/domain-spec.md.example and the twelve Java shape exemplars: seven model ones (VO, VO catalog, shared guards, aggregate, event, ports, command) and the five from the exception family, which moved here when the bootstrap stopped emitting code |
java-patterns |
✅ | Design patterns during development |
persistence-architect |
✅ | Designs the schema, mapping, and migrations for an already-modeled use case. Carries templates/persistence-spec.md.example, the three Java exemplars (entity, adapter with mapper, Spring Data interface), the idempotency trio (IdempotencyKeyTable.sql.example, IdempotencyKeyStore.java.example, IdempotentExecution.java.example), V1__create_table.sql.example, application-persistence.yml.example, and the two references/ (SQL diagnosis and external links) |
rest-api-architect |
✅ | Designs the endpoints, DTOs, error map, and OpenAPI contract for an already-modeled use case. Carries templates/rest-spec.md.example, the six Java shape exemplars (annotated controller, DTOs, mapper, PageResponse, Idempotency-Key interceptor, ApiExceptionHandler), the two JSON fixtures (error-responses, page-response), and references/best-practices-links.md. The contract test exemplar lives in test-architect |
test-architect |
✅ | Two modes: design (the 40-testes.md partial, per use case, inline) and setup (installs ArchUnit, once per project, delegated to the archunit-installer agent — see 6.8). Carries templates/{test-spec.md,ArchitectureTest.java,TestFixtures.java,DomainTest.java,UseCaseTest.java,ControllerTest.java,PersistenceIT.java}.example and references/best-practices-links.md. It's the sole owner of test-code shape — no other skill carries a test exemplar |
new-feature |
✅ | Orchestrates the six skills above into a single UC-NNN-spec.md. Only makes sense once the project exists; carries templates/feature-spec.md.example, templates/feature-spec-short.md.example, and templates/commons/*.example (the thirteen logging/masking exemplars commons-logging-installer translates and writes — owned here because new-feature's pre-flight check is what triggers that agent, same as test-architect owns ArchitectureTest.java.example for archunit-installer). Its own ## Entry into generated projects section already documents it travels here |
docker-architect |
✅ | Extends docker-compose.yml/Dockerfile after the base pair exists, chained by persistence-architect/test-architect/messaging-architect or invoked by hand. Only makes sense once the project (and the base pair from 4.10) exists. Carries templates/{postgres,mysql,kafka}-service.yml.example |
messaging-architect |
✅ | Designs the Kafka producer/consumer adapter, topic, delivery semantics, and retry/DLQ for an already-modeled domain event. Carries templates/messaging-spec.md.example, the two Java exemplars (producer adapter, consumer adapter), and application-kafka.yml.example |
git-publish |
✅ | Offers git init/commit and gh create+push, behind two confirmations. Chained by /new-feature after every future java-spring-boot-developer run inside the project — not just at bootstrap time. Carries no templates/ or references/ |
audit-usage |
✅ | Reads the trail ArchHook.java audit writes into .claude/audit-usage/ (step 7.4) through ArchHook.java audit summary, and consolidates spend per skill and agent, duration, and failures across runs. Only makes sense where the trail exists — the generated project, never this meta-repo, which creates no audit-usage/. Carries no templates/ or references/, and cites neither blueprints/ nor decisions/: it copies with none of the three corrections below |
project-bootstrap |
❌ | Builds the project. Inside it there's nothing left for it to do, and it invites the model to re-generate on top of live code |
init-project |
❌ | Same reason: it's the creation ritual, not the maintenance one |
claude-code-architect-designer |
❌ | Designs this repository's own extensions — skills, agents, rules. Whoever clones an already-generated project has no extensions to design, and the invariants it applies are this repo's |
Three corrections during the copy, because the generated project has neither
blueprints/ nor decisions/:
java-patterns/SKILL.md,use-case-design/SKILL.md,domain-modeling/SKILL.md,rest-api-architect/SKILL.md, andmessaging-architect/SKILL.mdsay Reads... and the active blueprint's packages.map. Replace with: the project's packages, documented in the rootCLAUDE.mdand the moduleCLAUDE.mdfiles. Inuse-case-design, the same correction applies in procedure step 5 and in the "Active blueprint" line oftemplates/use-case-spec.md.example.This list has gone stale before — silently, since a rule/skill citing a dead path breaks nothing at generation time, only for whoever reads it in the generated project. Before moving to step 6.8, confirm the list above is exhaustive:
grep -rl "active blueprint's \`packages.map\`" \ .claude/skills/{arch-doctor,use-case-design,domain-modeling,persistence-architect,java-patterns,rest-api-architect,test-architect,messaging-architect,audit-usage}/Every file this returns needs the same correction, whether or not it's named above.
Any other citation to
@.claude/blueprints/**in a copied skill points to nothing. Rewrite it to the equivalent source inside the project, or cut the sentence. Don't leave the dead path there.Every citation to
@.claude/decisions/NNNN-....mdin a copied skill'sSKILL.md, `tem
…(truncated)