mapstruct-installer
Records the project's mapper mechanism once: writes the manual-mappers paragraph, or wires MapStruct and converts every existing mapper.
mapstruct-installer.md, section by section
The sections of the agent's own file, excerpted. Hover or click one to see which project rules and templates it cites. The sections are also chips: click one to jump to it.
## Why this is an agent (Form 3)
Preserves context. Same shape as commons-logging-installer: resolve two versions from Maven Central, merge a fixed fragment into the build, then — the part those two installers never had — rewrite every manual mapper of the project and the classes that call it, and run the test suite before and after. The file list, the diffs and two full build logs are the longest output an implement run can produce, and none of it is needed by the /new-feature-implement run that asked: it needs one summary line and the project fact this agent writes. Invariant 5 requires one reason; this is the one that applies.
No tool or model reason applies on its own — reads and writes stay inside the project, and the conversion follows a fixed table (.claude/rules/mapping.md § What MapStruct generates and what stays written), which sonnet already handles for java-spring-boot-developer.
omitClaudeMd: true: the root CLAUDE.md is a declared input, Read explicitly at step 1 and edited at the last step; auto-loading it too paid for the same file twice (@.claude/decisions/0083-unused-ecosystem-fields-and-real-allow-list.md).
### Mode `manual`
- Read the root
CLAUDE.md. A**Mappers:paragraph already there → report "already decided", - Insert, right after the
**Bounded context:paragraph (or after the first paragraph under the
- Return the summary. No build runs: nothing compiled changed.
### Mode `install`
- Preconditions — stop on any, having changed nothing:
- Baseline. Run the project's test command once —
./mvnw -q verify(Gradle:
- Resolve the two versions on Maven Central, never from memory:
… cut here, the rest is in the file
## Failure mode
Already decided: `` ℹ️ mapstruct-installer — nothing to do: the root CLAUDE.md already says "Mappers: manual." ``
Dirty source tree: `` ❌ mapstruct-installer stopped — uncommitted changes under src/ or in a build file src/main/java/.../OrderController.java (modified) The conversion must be revertible with git restore. Commit or stash, then run /new-feature-implement again. ``
Red baseline: `` ❌ mapstruct-installer stopped — the test suite is red before any change OrderPersistenceIT.updatesTotal: expected 120.00 but was 100.00 Nothing installed. ``
Red after conversion: `` ❌ UserRestMapperImpl: Unmapped target property: "departmentId" Conversion red after 2 fixes. Revert: git restore src pom.xml ``
No rollback by the agent itself — the precondition makes the revert one command for the user.
… cut here, the rest is in the file
## Summary format returned to the caller
✅ mapstruct-installer complete — Mappers: MapStruct
- Build: maven · mapstruct 1.6.3 · lombok-mapstruct-binding 0.2.0 (resolved from Maven Central)
- Processor path: lombok, lombok-mapstruct-binding, mapstruct-processor, spring-boot-configuration-processor
- Options: defaultComponentModel=spring, defaultInjectionStrategy=constructor, unmappedTargetPolicy=ERROR
- Converted: 4 mappers (UserRestMapper, UserPersistenceMapper, OrderPersistenceMapper, PaymentClientMapper) — 3 default methods kept written (rehydrate ×2, OrderEntity update)
- Callers changed: 3 · tests changed: 2
- Left as is: JacksonObjectMapper (not a mapper)
- Suite: green before and after · doctor gate: green
Mode manual: `` ✅ mapstruct-installer complete — Mappers: manual (paragraph written, no build run) ``
---
## References
- Invariant 5 — agent only for one of three reasons (context, tools, model)
@.claude/agents/commons-logging-installer.md— same shape, same pre-flight trigger@.claude/rules/mapping.md— the norm every converted mapper follows@.claude/decisions/0160-mapstruct-as-a-per-project-mapper-choice.md— why MapStruct is a
What it reads, writes and refuses
The skill's own contract, from SKILL.md, in the original wording.
Class
installer — the territory is agent_classes.installer.overrides.mapstruct-installer in @.claude/schemas/extensions.json: the build files (pom.xml, build.gradle, build.gradle.kts, root and modules), the root CLAUDE.md, and src/main/java/ plus src/test/java/. The source trees are there for one reason — converting a manual mapper changes its callers (a static RestMapper.toX(...) becomes an injected bean) and the tests that built it. It is the widest installer territory in this repository on purpose, and the procedure narrows it in practice: only mapper files, their callers and their tests are touched. ArchHook.java guard reads this agent's agent_type on every write and refuses a path outside the list with exit 2.
Executor
yes — writes under src/ while a design phase may still be open. The class grants it (executor: true), and ArchHook.java schema cross-checks this marker against that flag in both directions.
Pattern catalog
not injected — follows a fixed conversion table, designs no code (agent_classes.installer.pattern_catalog: false; schema cross-checks this line).
Input (required)
project root and a mode — manual or install. The caller (/new-feature-implement's pre-flight) asked the user which; this agent never asks.
Reads
the root CLAUDE.md, the build files, the active blueprint's YAML (its forbidden_imports per module and packages.map), .claude/rules/mapping.md, .claude/rules/lombok.md, .claude/skills/new-feature-implement/templates/mapstruct/*.example (the build fragments), and, for the conversion, the *MapStructMapper.java.example exemplar of each layer it converts — rest-api-architect, graphql-api-architect, persistence-architect, http-client-architect, messaging-architect.
Writes
mode manual — the Mappers: manual. paragraph in the root CLAUDE.md, nothing else. Mode install — the build fragment (properties, dependencyManagement, the processor path, the three processor options in the root build; the org.mapstruct:mapstruct dependency in every module that may hold a mapper); every manual mapper rewritten as a MapStruct interface, plus the uses = conversion class an adapter needs; the callers and tests those conversions change; and the Mappers: MapStruct. paragraph.
Does not write
docs/** (an approved spec stays as it is — it never named a mechanism), a class that neither is a mapper nor calls one, docker-compose.yml, application*.yml, any version from memory. Never adds a test of its own: a mapper has none (.claude/rules/mapping.md § Common to both mechanisms).
Does not invoke other agents or skills — no `Agent` tool
A red baseline, a dirty source tree or an unreachable Maven Central is reported, and the caller decides.
Returns
one structured summary (shape at the bottom), not the raw ./mvnw or curl output.
Source: the agent's § Contract, at nerviz e4cae27.
What the guard does with it
installer is the agent's class. ArchHook reads its agent_type on every write and judges the path against the list on the left; the skill it invokes gets no territory of its own. The text on the right is the hook's own.
write_allow of class mapstruct-installer
- ✓
pom.xml - ✓
**/pom.xml - ✓
build.gradle - ✓
**/build.gradle - ✓
build.gradle.kts - ✓
**/build.gradle.kts - ✓
CLAUDE.md - ✓
src/main/java/** - ✓
**/src/main/java/** - ✓
src/test/java/** - ✓
**/src/test/java/** any path outside this list: refused, exit 2
A refusal exits with code 2 and the message goes back to the model.
PreToolUse Agent(mapstruct-installer) audit call exit 0 guard call exit 0 SubagentStart context subagent exit 0 tests agent-start exit 0 # installer · sonnet · write_allow: pom.xml, **/pom.xml, build.gradle, **/build.gradle, build.gradle.kts, **/build.gradle.kts, CLAUDE.md, src/main/java/**, **/src/main/java/**, src/test/java/**, **/src/test/java/**
SubagentStop audit agent exit 0 tests agent-end exit 0 Stop audit flush Recording the execution trail... exit 0 schema Validating frontmatter... exit 0 guard sweep Sweeping what this turn wrote... exit 0 compose gate Checking compose services... exit 0 tests Running the verify gate on changed modules... exit 0
Where it sits on the rail
Each card is a step of /new-feature-implement. The executor runs three times, one block group each; between two groups the orchestrator carries the reports forward. Dashed steps run only when the spec needs them.
/new-feature-implement orchestrator · sonnet