Amplifying Development with Junie: AI-Driven Guidelines and Double Loop TDD

Amplifying Development with Junie: AI-Driven Guidelines and Double Loop TDD

Developing my latest .NET project has been a masterclass in human-AI collaboration. By combining the precision of .junie/guidelines.md with the reasoning power of Junie (powered by JetBrains), we’ve created a development workflow that is both highly disciplined and incredibly fast.

🛠 The Power of Living Guidelines

The .junie/guidelines.md file isn't just documentation; it's a "source of truth" that Junie internalizes before every task. It encodes our architectural DNA, ensuring that the AI doesn't just write code, but writes our code.

Here is a look at the “rules of engagement” we’ve defined:

## .junie/guidelines.md (Snippets)

### 4) Object Calisthenics Rules
Apply these rules to all code:
1. One level of indentation per method.
2. Don't use the ELSE keyword - use early returns.
3. Wrap all primitives and strings (Value Objects).
7. Keep all entities small (Classes < 50 lines, Methods < 10 lines).
9. No getters/setters/properties - Tell, Don't Ask.

### 6) Double Loop TDD
1. **Outer Loop**: Write a failing System Test (Testcontainers) to verify end-to-end behavior.
2. **Inner Loop**: Write failing Unit Tests for domain logic or handlers.
3. Implement and refactor until all tests pass.

🔄 Seamless Transitions: From UseCase to Features

A prime example of this collaboration was the recent refactoring of the Application layer. As the project grew, we realized the UseCase folder structure was becoming a bottleneck.

Through the AI chat, we discussed the move to a Feature-Based Organization (Screaming Architecture). Once the decision was made, Junie didn’t just move files; it reorganized the entire Application layer to group Commands, Handlers, and DTOs by feature.

The result is a highly cohesive structure where each feature is a self-contained unit:

// Example of an AI-generated handler following the Guidelines
namespace Project.Application.Features.ProjectFeature.UpdateDetails;

public record UpdateDetailsCommand(DetailDto Details);

public static class UpdateDetailsHandler
{
    [WolverinePut("/api/resource/{id}/details")]
    public static async Task<IResult> Handle(Guid id, UpdateDetailsCommand command, IDocumentSession session)
    {
        var entity = await session.LoadAsync<DomainEntity>(id);
        if (entity == null) return Results.NotFound(); // Early return (No ELSE)
        entity.UpdateDetails(command.Details); // Tell, Don't Ask
        
        session.Store(entity);
        await session.SaveChangesAsync();
        return Results.Ok();
    }
}

🤖 Why Junie?

Using Junie within the IDE feels like having a Senior Architect who never sleeps. It handles the “heavy lifting” of boilerplate and refactoring while strictly adhering to the Object Calisthenics and SOLID principles we’ve defined.

When I ask the AI chat “How should we implement X?”, it doesn’t just give a generic answer; it looks at our Directory.Packages.props, checks our Marten configuration, and suggests a solution that fits our specific stack.

📈 The Result

The result is a codebase that is exceptionally clean, fully tested, and architecturally consistent. By offloading the enforcement of patterns to Junie, I can focus on the high-level design and business logic, knowing that the “how” is handled by an AI that understands my project’s rules as well as I do.

Alexandre Cuva

Alexandre Cuva

Nyon, Switzerland