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

190 lines
6.4 KiB
Markdown

# 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
```bash
# 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`.
```java
// 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).
```java
// 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.
```java
// 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.
```java
// 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`.
```java
@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:
```java
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.