Ansible Crafting Development Guidelines¶
This document outlines the development standards, best practices, and conventions for the Ansible Crafting project.
Related documentation: - Development Guide — Building, testing, Gradle tasks, multi-version support - Architecture Overview — Package structure, key classes, data flows - Release Process — Release scripts, splash taglines, changelog generation - CI/CD Pipeline — GitLab CI/CD stages and jobs - Configuration Reference — All mod config options - Debug Logging — Enabling debug output - Full Documentation Index
Table of Contents¶
- Code Style
- Documentation
- Mermaid Diagrams
- Testing
- Git Practices
- Pull Requests
- Architecture Guidelines
- Dependency Management
- Minecraft Mod Specifics
Code Style¶
License Headers¶
All Java source files must include the MPL-2.0 license header at the top of the file.
Spotless enforces this automatically — run ./gradlew spotlessApply to add missing headers.
The header format:
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Copyright (c) 2026 Ghent Starshadow
*/
Java Code Style¶
We use Spotless with the Palantir Java Format for consistent code formatting.
Formatting Rules¶
- Indentation: Use tabs for Java, spaces (4) for Gradle
- Line Length: 120 characters maximum
- Import Order:
java.*javax.*net.minecraft.*net.fabricmc.*com.ansiblecrafting.*- Everything else
Naming Conventions¶
| Type | Convention | Example |
|---|---|---|
| Classes | PascalCase | InventoryScanner |
| Methods | camelCase | scanInventories() |
| Constants | SCREAMING_SNAKE_CASE | MAX_SCAN_RANGE |
| Fields | camelCase | scanRange |
| Packages | lowercase | com.ansiblecrafting.inventory |
Code Organization¶
/**
* Class-level Javadoc describing the purpose.
*/
public class ExampleClass {
// 1. Static constants
public static final int CONSTANT = 100;
// 2. Instance fields
private final Map<String, Object> data = new HashMap<>();
// 3. Constructors
public ExampleClass() {
// Constructor logic
}
// 4. Public methods
public void publicMethod() {
// Implementation
}
// 5. Private methods
private void privateMethod() {
// Implementation
}
// 6. Inner classes
public static class InnerClass {
// Inner class content
}
}
Running the Formatter¶
JSON Style¶
- Use 2-space indentation
- Sort keys alphabetically where applicable
- No trailing whitespace
Gradle Style¶
- Use 4-space indentation
- Use single quotes for strings where possible
- Group related configurations together
Documentation¶
Javadoc Standards¶
All public and protected members must have Javadoc comments.
Class Documentation¶
/**
* Scans nearby inventories and aggregates their contents.
*
* <p>This class is responsible for finding all inventory-containing blocks
* within a configurable range of the player and combining their contents
* into a unified view.</p>
*
* <p>Example usage:</p>
* <pre>{@code
* InventoryScanner scanner = new InventoryScanner();
* scanner.scanInventories(player);
* AggregatedInventory inventory = scanner.getAggregatedInventory(player);
* }</pre>
*
* @see AggregatedInventory
* @see InventoryProvider
*/
public class InventoryScanner {
// ...
}
Method Documentation¶
/**
* Scans all inventories within range of the player.
*
* @param player The server player to scan around
* @throws NullPointerException if player is null
*/
public void scanInventories(ServerPlayerEntity player) {
// ...
}
Parameter Documentation¶
- Document all parameters, even if they seem obvious
- Document return values
- Document thrown exceptions
README Updates¶
When adding new features, update the about.md with: - Feature description - Configuration options - Usage examples
Changelog¶
Update changelog.md following Keep a Changelog format:
## [Unreleased]
### Added
- New feature description
### Changed
- Changed feature description
### Fixed
- Bug fix description
Documentation File Conventions¶
File Naming¶
- Use lowercase-kebab-case for all documentation files (e.g.,
debug-logging.md,ci-cd.md) - Use
.md(Markdown) for all documentation
File Locations¶
| Type | Location | Examples |
|---|---|---|
| Project-root docs | ansible-crafting/ |
about.md, contributing.md, GUIDELINES.md, changelog.md |
| Technical docs | ansible-crafting/docs/ |
development.md, architecture.md, releasing.md |
| Plans & designs | ansible-crafting/docs/plans/ |
guidelines-documentation-plan.md |
Markdown Style¶
- Headings: Use ATX-style (
#,##,###) — not underline style - Code blocks: Always use fenced code blocks with language tags (
```java,```bash, etc.) - Tables: Use Markdown tables for structured data; align columns with pipes
- Links: Use relative paths for cross-references between docs (e.g.,
[Architecture](architecture.md)) - Line length: No hard wrap for prose in Markdown (per
.editorconfig)
When to Update Documentation¶
Documentation must be updated alongside code changes when:
- Adding or removing a feature
- Changing configuration options or defaults
- Modifying public APIs or class interfaces
- Changing build or test procedures
- Altering the CI/CD pipeline
Index Maintenance¶
When adding a new document to docs/, always update docs/index.md with a link and description.
Mermaid Diagrams¶
The project uses Mermaid diagrams extensively in documentation. Follow these conventions for consistency.
When to Use Each Diagram Type¶
| Type | Syntax | Use For | Example |
|---|---|---|---|
| Flowchart (top-down) | flowchart TD |
Architecture overviews, process flows, decision trees | Release workflow, architecture diagrams |
| Flowchart (left-right) | flowchart LR |
Simple pipelines, linear processes | Build types, plugin registration |
| Sequence Diagram | sequenceDiagram |
Component interactions, request/response flows, time-ordered events | Inventory sync, crafting flow |
Note: Always use
flowchartinstead of the legacygraphkeyword. Both work in Mermaid, butflowchartis the modern syntax and supports additional features like subgraph direction.
Node ID Conventions¶
- Node IDs: Use
SCREAMING_SNAKE_CASE— short, descriptive identifiers - Node labels: Use descriptive text; use
<br/>for line breaks within labels
flowchart LR
%% Good: SCREAMING_SNAKE IDs with descriptive labels
SYNC_PKT["InventorySyncPacket<br/>Item data → client"]
BUILD_MV["build:multi-version<br/>All MC versions"]
%% Bad: lowercase IDs, no labels
%% syncpkt --> buildmv
Subgraphs¶
Use subgraphs to group related components by logical boundary (e.g., client/server, pipeline stages):
flowchart TB
subgraph Server Side
SCANNER["InventoryScanner"]
AGG["AggregatedInventory"]
end
subgraph Client Side
CACHE["ClientInventoryCache"]
PANEL["InventoryPanelWidget"]
end
SCANNER --> AGG
AGG --> CACHE
CACHE --> PANEL
Style Rules¶
- One concept per diagram — split complex flows into multiple focused diagrams
- No special characters inside square brackets — avoid double quotes (
") and parentheses (()) inside[...]labels; use<br/>for formatting instead - Keep diagrams readable — limit to ~15 nodes per diagram; if larger, split into sub-diagrams
- Use consistent direction —
TD(top-down) for hierarchies and architectures,LR(left-right) for pipelines and sequences - Label edges when the relationship isn't obvious — use
-->|"label"|syntax
Examples from This Project¶
Good examples of Mermaid usage in this project:
- Architecture Overview — High-level architecture with subgraphs, sequence diagrams for data flows
- Release Process — Flowchart with decision nodes for the release workflow
- CI/CD Pipeline — Pipeline stages as a left-to-right graph
- Development Guide — Build type comparison, Stonecutter multi-version flow
Testing¶
Test Structure¶
Tests should mirror the source structure:
src/main/java/com/ansiblecrafting/
└── inventory/
└── InventoryScanner.java
src/test/java/com/ansiblecrafting/
└── inventory/
└── InventoryScannerTest.java
Test Naming¶
Use descriptive test names following the pattern:
methodName_stateUnderTest_expectedBehavior
@Test
@DisplayName("scanInventories with valid player should find nearby chests")
void scanInventories_validPlayer_findsNearbyChests() {
// ...
}
Test Structure (AAA Pattern)¶
@Test
@DisplayName("Description of what is being tested")
void methodName_stateUnderTest_expectedBehavior() {
// Arrange - Set up test data and conditions
int scanRange = 16;
InventoryScanner scanner = new InventoryScanner();
// Act - Execute the code under test
var result = scanner.scanInventories(player);
// Assert - Verify the results
assertThat(result).isNotEmpty();
assertThat(result.size()).isEqualTo(expectedCount);
}
Test Categories¶
Unit Tests¶
- Test individual methods in isolation
- Use mocks for external dependencies
- Fast execution (< 100ms per test)
Integration Tests¶
- Test multiple components together
- May require Minecraft runtime
- Tagged with
@Tag("integration")
Running Tests¶
# Run all tests
./gradlew test
# Run specific test class
./gradlew test --tests "InventoryScannerTest"
# Run with verbose output
./gradlew test --info
Test Coverage¶
Aim for: - 80%+ line coverage for business logic - 100% coverage for critical paths - Focus on meaningful tests, not just coverage numbers
Git Practices¶
Branch Naming¶
Use descriptive branch names with prefixes:
| Prefix | Purpose | Example |
|---|---|---|
feature/ |
New features | feature/remote-crafting |
fix/ |
Bug fixes | fix/inventory-sync-crash |
refactor/ |
Code refactoring | refactor/scanner-performance |
docs/ |
Documentation | docs/api-documentation |
test/ |
Test improvements | test/scanner-coverage |
chore/ |
Maintenance | chore/update-gradle |
Commit Message Format¶
Follow Conventional Commits:
Types¶
| Type | Description |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation only |
style |
Code style changes (formatting, etc.) |
refactor |
Code refactoring |
test |
Adding or modifying tests |
chore |
Maintenance tasks |
perf |
Performance improvements |
Examples¶
feat(scanner): add configurable scan range
Add ability to configure the inventory scan range through
the mod configuration. Default range is 16 blocks.
Closes #42
fix(networking): prevent crash on missing inventory
Handle the case where an inventory is removed between
scan and craft request.
Fixes #87
refactor(inventory): extract inventory aggregation logic
Move aggregation logic to separate class for better
testability and single responsibility.
Commit Best Practices¶
- Atomic Commits: Each commit should represent one logical change
- Present Tense: Use "add feature" not "added feature"
- Imperative Mood: Use "add" not "adds" or "adding"
- No Trailing Period: Don't end the subject line with a period
- Separate Subject and Body: Use a blank line between them
- Wrap Body at 72 Characters: Keep lines readable
Commit Checklist¶
- [ ] Code compiles without errors
- [ ] All tests pass
- [ ] Code is formatted (spotlessCheck passes)
- [ ] Commit message follows convention
- [ ] Changes are logically grouped
Pull Requests¶
PR Title Format¶
Same as commit message format:
PR Description Template¶
## Description
Brief description of what this PR does and why.
## Changes
- List of specific changes made
- Another change
- And another
## Testing
Describe how these changes were tested:
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] Manual testing performed
## Screenshots
If applicable, add screenshots of UI changes.
## Checklist
- [ ] Code compiles without errors
- [ ] All tests pass
- [ ] Code follows style guidelines
- [ ] Documentation updated
- [ ] changelog.md updated
- [ ] No new warnings introduced
## Related Issues
Closes #issue_number
PR Review Process¶
- Self-Review: Review your own changes before requesting review
- CI Checks: Ensure all CI checks pass
- Address Feedback: Respond to all review comments
- Squash Commits: Squash related commits before merge
PR Size Guidelines¶
| Size | Lines Changed | Recommendation |
|---|---|---|
| XS | 1-10 | Quick review |
| S | 11-50 | Normal review |
| M | 51-200 | Detailed review |
| L | 201-500 | Consider splitting |
| XL | 500+ | Must split into smaller PRs |
Architecture Guidelines¶
Package Structure¶
com.ansiblecrafting/
├── AnsibleCraftingMod.java # Main entry point
├── client/ # Client-only code
│ └── ui/ # UI components
├── config/ # Configuration
├── crafting/ # Crafting logic
├── integration/ # Mod integrations
│ ├── emi/
│ └── rei/
├── inventory/ # Inventory handling
├── mixin/ # Mixin classes
│ └── client/
└── network/ # Networking packets
Design Principles¶
- Single Responsibility: Each class should have one reason to change
- Dependency Injection: Pass dependencies, don't create them
- Interface Segregation: Use interfaces for cross-mod compatibility
- Fail Fast: Validate inputs early, throw meaningful exceptions
Error Handling¶
// Good: Specific exception with context
public void scanInventories(ServerPlayerEntity player) {
if (player == null) {
throw new IllegalArgumentException("Player cannot be null");
}
// ...
}
// Good: Graceful degradation
public AggregatedInventory getAggregatedInventory(PlayerEntity player) {
return playerInventories.getOrDefault(player, new AggregatedInventory());
}
Logging¶
Use SLF4J for logging:
public class ExampleClass {
private static final Logger LOGGER = LoggerFactory.getLogger("ansiblecrafting");
public void doSomething() {
LOGGER.info("Starting operation");
LOGGER.debug("Processing item: {}", itemId);
LOGGER.warn("Unexpected state detected", exception);
LOGGER.error("Critical failure", exception);
}
}
Log Levels: - ERROR: Failures that prevent operation - WARN: Unexpected but recoverable situations - INFO: Important lifecycle events - DEBUG: Detailed information for debugging
Dependency Management¶
Evaluating New Dependencies¶
Before adding a new dependency, consider:
- Necessity: Can the functionality be implemented with existing dependencies or the standard library?
- Maintenance: Is the library actively maintained? Check last commit date and issue response times.
- License: Is the license compatible with the project? (Prefer MIT, Apache 2.0, or similar permissive licenses)
- Size: Does the dependency add significant JAR size? Minecraft mods should stay lightweight.
- Transitive dependencies: What does the library pull in? Minimize the dependency tree.
Where Dependencies Are Declared¶
| What | Where | Example |
|---|---|---|
| Version numbers | gradle.properties |
cloth_config_version=12.0.119 |
| Dependency declarations | build.gradle |
modApi "me.shedaniel.cloth:cloth-config-fabric:${project.cloth_config_version}" |
| Minecraft/Fabric versions | gradle.properties |
minecraft_version=1.20.1 |
Version Pinning¶
- Always pin exact versions in
gradle.properties— no dynamic version ranges (e.g.,1.2.+) - Use the
modApiconfiguration for compile-time mod dependencies - Use
modRuntimeOnlyfor optional runtime dependencies (e.g., recipe viewers in dev) - Use
includeto bundle (jar-in-jar) required dependencies that users may not have
Updating Dependencies¶
- Update the version in
gradle.properties - Run
./gradlew buildto verify compatibility - Run
./gradlew testto verify no regressions - Test manually in the dev client (gradlew runClient`)
- Commit with
chore: update <dependency> to <version>
Minecraft Mod Specifics¶
Side-Specific Code¶
Always separate client and server code:
// Use @Environment annotation
@Environment(EnvType.CLIENT)
public class ClientInventoryData {
// Client-only code
}
// Use entrypoints in fabric.mod.json
"entrypoints": {
"main": ["com.ansiblecrafting.AnsibleCraftingMod"],
"client": ["com.ansiblecrafting.client.AnsibleCraftingClient"]
}
Mixin Best Practices¶
- Use Access Widener First: Prefer access widener over mixin for simple access
- Minimal Injection: Inject only what's necessary
- Document Mixins: Add comments explaining why the mixin is needed
/**
* Mixin to CraftingScreenHandler to enable remote crafting.
* Required because the fillInputSlots method doesn't support
* external inventory sources.
*/
@Mixin(CraftingScreenHandler.class)
public abstract class CraftingScreenHandlerMixin {
// ...
}
Networking¶
- Thread Safety: Always handle packets on the appropriate thread
- Validation: Validate all packet data on the server
- Size Limits: Keep packets small (< 32KB recommended)
public static void handleServer(MinecraftServer server, ServerPlayerEntity player,
PacketByteBuf buf) {
// Read and validate packet
CraftRequestPacket packet = read(buf);
if (packet.quantity() < 1 || packet.quantity() > 64) {
LOGGER.warn("Invalid quantity in craft request from {}", player.getName());
return;
}
// Execute on server thread
server.execute(() -> {
// Process the request
});
}
Configuration¶
Use Cloth Config for user-facing configuration:
public class ModConfig {
private int scanRange = 16; // Default value
// Validate on load
public void load() {
if (scanRange < 1) scanRange = 1;
if (scanRange > 64) scanRange = 64;
}
}
Quick Reference¶
Gradle Commands¶
| Command | Description |
|---|---|
./gradlew build |
Build the project |
./gradlew test |
Run tests |
./gradlew spotlessCheck |
Check formatting |
./gradlew spotlessApply |
Apply formatting |
./gradlew runClient |
Run Minecraft client |
./gradlew runServer |
Run Minecraft server |
File Locations¶
| File | Purpose |
|---|---|
build.gradle |
Build configuration |
gradle.properties |
Project properties |
fabric.mod.json |
Mod metadata |
*.mixins.json |
Mixin configuration |
*.accesswidener |
Access widener config |