backend/AGENTS.md
Rodrigo Verdiani 90ee74b3b9 docs: add AI module documentation and AGENTS.md
Comprehensive documentation for the AI module covering architecture,
API design, data model, RAG pipeline, configuration, integration points,
and frontend integration guide. Includes IDA5 extraction points reference
and AGENTS.md with project conventions and coding standards.
2026-06-08 20:13:21 -03:00

6.4 KiB

AGENTS.md — decompile-ai

Project Overview

decompile-ai is a backend service for binary analysis automation. It accepts uploaded binaries, runs them through multiple analysis engines (IDA Pro, Ghidra, DiE) inside Docker containers, and provides AI-powered chat over the decompiled code via Ollama + RAG (pgvector embeddings).

  • Java 25, Spring Boot 4.0.6, Maven wrapper (./mvnw)
  • PostgreSQL + Flyway migrations; H2 in-memory for tests
  • RabbitMQ for async job dispatch
  • Spring Modulith for module boundaries, event publication, and architecture verification
  • Spring AI (Ollama) for chat and embedding generation
  • Spotless (Google Java Format) for code formatting

Build & Run Commands

# Compile
./mvnw compile

# Run tests
./mvnw test

# Run a specific test class
./mvnw test -Dtest=ChatServiceTest

# Package
./mvnw package -DskipTests

# Run the app locally (needs PostgreSQL + RabbitMQ)
./mvnw spring-boot:run

# Format code
./mvnw spotless:apply

# Check formatting (CI-style)
./mvnw spotless:check

Project Structure

src/main/java/ai/decompile/
├── DecompileAiApplication.java    # Spring Boot entry point
├── WebConfig.java                 # CORS configuration
├── workspace/                     # Workspace, Project, Binary CRUD
│   ├── controller/
│   ├── event/                     # Domain events (BinaryUploadedEvent)
│   ├── model/{dto,entity,repository,specification}/
│   └── service/
├── analysis/                      # Static analysis results (functions, xrefs, labels)
│   ├── controller/
│   ├── event/                     # StaticAnalysisRequestedEvent, StaticAnalysisCompletedEvent
│   ├── model/{dto,entity,repository}/
│   └── service/
├── engine/                        # Analysis engines: IDA v5, Ghidra (Docker-managed)
│   ├── config/
│   ├── model/
│   └── service/
├── die/                           # DiE (Detect It Easy) file type detection (Docker)
├── docker/                        # Docker container management via docker-java
├── job/                           # Async job queue: create, dispatch, execute, track status
│   ├── controller/
│   ├── messaging/                 # RabbitMQ publisher + listener
│   ├── model/{dto,entity,enums,repository,specification}/
│   └── service/handler/           # JobType handlers (AnalyzeFile, StaticAnalysis, Embedding)
├── ai/                            # AI chat + RAG embeddings (Spring AI + Ollama)
│   ├── config/
│   ├── controller/
│   ├── model/{dto,entity,jdbc,repository}/
│   └── service/
└── common/                        # Shared config and exceptions (NotFoundException, ConflictException)
    └── exception/

Coding Conventions

Null Checking

Always use java.util.Objects.isNull() and java.util.Objects.nonNull()never use raw == null or != null.

// Correct
if (Objects.isNull(value)) { ... }
if (Objects.nonNull(value)) { ... }

// Wrong
if (value == null) { ... }
if (value != null) { ... }

If Statements

Always use braces, even for single-line bodies. Always include a blank line after the closing } of an if block (before the next statement).

// Correct
if (condition) {
  doSomething();
}

nextThing();

// Wrong
if (condition) doSomething();

// Wrong (no blank line after if)
if (condition) {
  doSomething();
}
nextThing();

Classes and Records

No blank line between the class/record opening brace and the first member.

// Correct
public class Job {
  @Id
  @GeneratedValue(strategy = GenerationType.UUID)
  private UUID id;

// Correct
public record ChatRequest(@NotBlank String content) {}

// Wrong (blank line after brace)
public class Job {

  @Id
  private UUID id;

Type Inference (var)

Use var for local variables whenever the type is obvious from the right-hand side.

// Correct
var session = chatService.createSession(binaryId, projectId, model);
var messages = new ArrayList<Message>();
var binary = binaryService.getBinary(binaryId);

// Acceptable (type not obvious from RHS, or lambda target typing needed)
StreamingChatModel streamingModel = resolveStreamingChatModel(session.getChatModel());

Dependency Injection

Constructor injection via Lombok @RequiredArgsConstructor. Declare dependencies as private final.

@Service
@RequiredArgsConstructor
public class MyService {
  private final DependencyA depA;
  private final DependencyB depB;
}

Transactions

  • @Transactional on write operations
  • @Transactional(readOnly = true) on read-only queries
  • @Transactional(propagation = Propagation.REQUIRES_NEW) for independent sub-transactions

Testing

  • JUnit 5 + Mockito (unit tests)
  • MockMvc + H2 (integration tests)
  • Unit tests: @ExtendWith(MockitoExtension.class), @Mock / @InjectMocks
  • Integration tests: @SpringBootTest, @ActiveProfiles("test")
  • Test class naming: *Test.java (unit), *IntegrationTest.java (integration)
  • Static imports for assertions: assertEquals(...), assertThrows(...), assertTrue(...)

Logging

Lombok @Log4j2 on all non-trivial classes. Use parameterized messages:

log.info("Processing binary={} with engine={}", binaryId, engineName);
log.error("Failed to stream chat for session {}", sessionId, error);

Domain Events (Spring Modulith)

Event listeners use @ApplicationModuleListener. Events are published via ApplicationEventPublisher.publishEvent().

Flyway Migrations

  • Files in src/main/resources/db/migration/, naming: V001__description.sql
  • DDL changes go through Flyway; Hibernate ddl-auto=validate only

Design Principles

  • DRY — Extract repeated logic. The refactoring of ChatController.sendMessage() is the canonical example: duplicated error formatting was extracted into reusable event() and errorEvent() helpers.
  • SOLID — Each module (workspace, analysis, engine, job, ai) has a single responsibility. Dependencies between modules are explicitly declared in package-info.java.
  • Clean Code — Small methods, meaningful names, no comments explaining what (the code does that), only comments explaining why when non-obvious.
  • Module boundaries — Cross-module communication via domain events, not direct service calls. Use @NamedInterface for fine-grained intra-module access control.