Skip to content

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

  1. Code Style
  2. Documentation
  3. Mermaid Diagrams
  4. Testing
  5. Git Practices
  6. Pull Requests
  7. Architecture Guidelines
  8. Dependency Management
  9. 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

# Check formatting
./gradlew spotlessCheck

# Apply formatting
./gradlew spotlessApply

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 flowchart instead of the legacy graph keyword. Both work in Mermaid, but flowchart is 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

  1. One concept per diagram — split complex flows into multiple focused diagrams
  2. No special characters inside square brackets — avoid double quotes (") and parentheses (()) inside [...] labels; use <br/> for formatting instead
  3. Keep diagrams readable — limit to ~15 nodes per diagram; if larger, split into sub-diagrams
  4. Use consistent directionTD (top-down) for hierarchies and architectures, LR (left-right) for pipelines and sequences
  5. Label edges when the relationship isn't obvious — use -->|"label"| syntax

Examples from This Project

Good examples of Mermaid usage in this project:


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:

<type>(<scope>): <description>

[optional body]

[optional footer(s)]

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

  1. Atomic Commits: Each commit should represent one logical change
  2. Present Tense: Use "add feature" not "added feature"
  3. Imperative Mood: Use "add" not "adds" or "adding"
  4. No Trailing Period: Don't end the subject line with a period
  5. Separate Subject and Body: Use a blank line between them
  6. 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:

<type>(<scope>): <description>

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

  1. Self-Review: Review your own changes before requesting review
  2. CI Checks: Ensure all CI checks pass
  3. Address Feedback: Respond to all review comments
  4. 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

  1. Single Responsibility: Each class should have one reason to change
  2. Dependency Injection: Pass dependencies, don't create them
  3. Interface Segregation: Use interfaces for cross-mod compatibility
  4. 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:

  1. Necessity: Can the functionality be implemented with existing dependencies or the standard library?
  2. Maintenance: Is the library actively maintained? Check last commit date and issue response times.
  3. License: Is the license compatible with the project? (Prefer MIT, Apache 2.0, or similar permissive licenses)
  4. Size: Does the dependency add significant JAR size? Minecraft mods should stay lightweight.
  5. 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 modApi configuration for compile-time mod dependencies
  • Use modRuntimeOnly for optional runtime dependencies (e.g., recipe viewers in dev)
  • Use include to bundle (jar-in-jar) required dependencies that users may not have

Updating Dependencies

  1. Update the version in gradle.properties
  2. Run ./gradlew build to verify compatibility
  3. Run ./gradlew test to verify no regressions
  4. Test manually in the dev client (gradlew runClient`)
  5. 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

  1. Use Access Widener First: Prefer access widener over mixin for simple access
  2. Minimal Injection: Inject only what's necessary
  3. 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

  1. Thread Safety: Always handle packets on the appropriate thread
  2. Validation: Validate all packet data on the server
  3. 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