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.