Initial commit: trueref v0.1.0-SNAPSHOT
Some checks failed
Build and publish Docker image / Build and push (push) Failing after 1m27s

Java 21 / Spring Boot 3.5.3 multi-module Maven project.
Hybrid BM25+HNSW search with RRF, cross-encoder reranker,
ONNX Runtime 1.22.0 (CPU + CUDA 12 GPU variants).
This commit is contained in:
moze
2026-05-06 00:49:16 +02:00
commit c5f950c2c0
132 changed files with 11287 additions and 0 deletions

68
trueref-bootstrap/pom.xml Normal file
View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.trueref</groupId>
<artifactId>trueref-parent</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>
<artifactId>trueref-bootstrap</artifactId>
<name>trueref-bootstrap</name>
<description>Spring Boot entry point. Wires beans across modules. Produces the executable fat JAR.</description>
<dependencies>
<dependency>
<groupId>com.trueref</groupId>
<artifactId>trueref-domain</artifactId>
</dependency>
<dependency>
<groupId>com.trueref</groupId>
<artifactId>trueref-application</artifactId>
</dependency>
<dependency>
<groupId>com.trueref</groupId>
<artifactId>trueref-adapters</artifactId>
</dependency>
<dependency>
<groupId>com.trueref</groupId>
<artifactId>trueref-frontend</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>trueref</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.trueref.bootstrap.TrueRefApplication</mainClass>
</configuration>
<executions>
<execution>
<goals><goal>repackage</goal></goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,89 @@
package com.trueref.bootstrap;
import com.trueref.application.catalog.CatalogService;
import com.trueref.application.ingest.DiscoveryService;
import com.trueref.application.ingest.IngestionOrchestrator;
import com.trueref.application.observability.InMemoryJobEventBus;
import com.trueref.application.observability.JobObservationService;
import com.trueref.application.resolve.LibraryResolver;
import com.trueref.application.search.HybridSearchService;
import com.trueref.domain.port.out.ChunkStore;
import com.trueref.domain.port.out.CodeParser;
import com.trueref.domain.port.out.EmbeddingCache;
import com.trueref.domain.port.out.EmbeddingService;
import com.trueref.domain.port.out.GitClient;
import com.trueref.domain.port.out.JobStore;
import com.trueref.domain.port.out.RepositoryStore;
import com.trueref.domain.port.out.RerankerService;
import java.nio.file.Path;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Explicit bean wiring for the application layer (which stays Spring-annotation-free).
* We expose only concrete beans; Spring resolves interface dependencies against the single
* concrete implementation.
*/
@Configuration
public class ApplicationBeans {
@Bean
Path trueRefHome(@Value("${trueref.home:./data}") String home) {
return Path.of(home);
}
@Bean
InMemoryJobEventBus jobEventBus() {
return new InMemoryJobEventBus();
}
@Bean
CatalogService catalogService(RepositoryStore store, Path trueRefHome) {
return new CatalogService(store, trueRefHome);
}
@Bean
DiscoveryService discoveryService(RepositoryStore store, GitClient git) {
return new DiscoveryService(store, git);
}
@Bean(destroyMethod = "shutdown")
IngestionOrchestrator ingestionOrchestrator(
RepositoryStore repoStore,
JobStore jobStore,
ChunkStore chunkStore,
EmbeddingService embeddings,
EmbeddingCache embeddingCache,
GitClient git,
CodeParser parser,
InMemoryJobEventBus bus,
@Value("${trueref.ingestion.max-parse-jobs:4}") int maxParseJobs,
@Value("${trueref.ingestion.embed-queue-capacity:4}") int embedQueueCapacity) {
return new IngestionOrchestrator(
repoStore, jobStore, chunkStore, embeddings, embeddingCache, git, parser, bus,
maxParseJobs, embedQueueCapacity);
}
@Bean
LibraryResolver libraryResolver(RepositoryStore store, IngestionOrchestrator indexer) {
return new LibraryResolver(store, indexer);
}
@Bean
HybridSearchService hybridSearchService(
ChunkStore chunks,
EmbeddingService embedder,
RerankerService reranker,
RepositoryStore repos,
@Value("${trueref.search.rrf-k:60}") int rrfK,
@Value("${trueref.reranker.top-k:50}") int rerankTopK,
@Value("${trueref.search.final-top-k:20}") int finalTopK) {
return new HybridSearchService(chunks, embedder, reranker, repos, rrfK, rerankTopK, finalTopK);
}
@Bean
JobObservationService jobObservationService(JobStore jobs, InMemoryJobEventBus bus) {
return new JobObservationService(jobs, bus);
}
}

View File

@@ -0,0 +1,57 @@
package com.trueref.bootstrap;
import com.trueref.application.ingest.DiscoveryService;
import com.trueref.domain.model.Repository;
import com.trueref.domain.model.Version;
import com.trueref.domain.model.VersionStatus;
import com.trueref.domain.port.in.IndexVersion;
import com.trueref.domain.port.out.RepositoryStore;
import java.time.Instant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/** Periodically fetches tags for registered repos and enqueues indexing for new ones. */
@Component
@EnableScheduling
@ConditionalOnProperty(name = "trueref.ingestion.poller-enabled", havingValue = "true", matchIfMissing = true)
public class ScheduledPoller {
private static final Logger log = LoggerFactory.getLogger(ScheduledPoller.class);
private final RepositoryStore repoStore;
private final DiscoveryService discovery;
private final IndexVersion indexer;
public ScheduledPoller(RepositoryStore repoStore, DiscoveryService discovery, IndexVersion indexer) {
this.repoStore = repoStore;
this.discovery = discovery;
this.indexer = indexer;
}
@Scheduled(fixedDelayString = "${trueref.ingestion.poll-interval-default:PT1H}")
public void pollAll() {
Instant start = Instant.now();
int scanned = 0;
int enqueued = 0;
for (Repository repo : repoStore.findAll()) {
try {
discovery.discover(repo.id());
for (Version v : repoStore.findVersionsByRepo(repo.id())) {
if (v.status() == VersionStatus.DISCOVERED) {
indexer.enqueue(repo.id(), v.id(), false);
enqueued++;
}
}
scanned++;
} catch (Exception e) {
log.warn("poll failed for repo={}: {}", repo.name(), e.toString());
}
}
log.info("poll completed in {}ms: repos scanned={} jobs enqueued={}",
java.time.Duration.between(start, Instant.now()).toMillis(), scanned, enqueued);
}
}

View File

@@ -0,0 +1,59 @@
package com.trueref.bootstrap;
import com.trueref.domain.port.out.JobStore;
import com.trueref.domain.port.out.RepositoryStore;
import java.time.Instant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
/**
* On-startup cleanup for stale job state left by a previous crash or SIGKILL.
*
* <p>Any job that is RUNNING or QUEUED when the application starts must have been orphaned by a
* previous JVM exit (clean or unclean). We fail them all atomically before accepting traffic so
* the UI and API never show phantom RUNNING jobs. Matching INDEXING versions are also reset to
* FAILED so they can be re-queued immediately.
*
* <p>This fires <em>after</em> Flyway migrations and all beans are initialised, but before the
* application starts accepting HTTP requests (ApplicationReadyEvent fires before the embedded
* Tomcat connector starts accepting connections).
*/
@Component
class StaleJobCleanupStartup {
private static final Logger log = LoggerFactory.getLogger(StaleJobCleanupStartup.class);
private static final String RESTART_REASON = "interrupted by server restart";
private final JobStore jobStore;
private final RepositoryStore repositoryStore;
StaleJobCleanupStartup(JobStore jobStore, RepositoryStore repositoryStore) {
this.jobStore = jobStore;
this.repositoryStore = repositoryStore;
}
@EventListener(ApplicationReadyEvent.class)
public void cleanupStaleJobs() {
Instant now = Instant.now();
int failedJobs = jobStore.failStaleJobs(now);
if (failedJobs > 0) {
log.warn(
"Startup cleanup: marked {} orphaned job(s) as FAILED (were RUNNING or QUEUED at shutdown).",
failedJobs);
} else {
log.info("Startup cleanup: no stale jobs found.");
}
int failedVersions = repositoryStore.failStaleIndexingVersions(RESTART_REASON);
if (failedVersions > 0) {
log.warn(
"Startup cleanup: reset {} INDEXING version(s) to FAILED (their jobs did not complete).",
failedVersions);
}
}
}

View File

@@ -0,0 +1,17 @@
package com.trueref.bootstrap;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Trueref entry point. The only place where Spring component scanning is allowed across the
* {@code com.trueref} package tree. Adapters and application modules expose explicit
* {@code @Configuration} classes that this class imports via component scanning.
*/
@SpringBootApplication(scanBasePackages = "com.trueref")
public class TrueRefApplication {
public static void main(String[] args) {
SpringApplication.run(TrueRefApplication.class, args);
}
}

View File

@@ -0,0 +1,114 @@
spring:
application:
name: trueref
threads:
virtual:
enabled: true
datasource:
url: jdbc:h2:file:${trueref.home:./data}/h2/trueref;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MV_STORE=TRUE
username: sa
password: ""
driver-class-name: org.h2.Driver
hikari:
# Embedded H2 serialises writes internally; 8 connections is ample for virtual-thread
# workloads. 32 is wasteful and causes unnecessary H2 lock contention.
maximum-pool-size: 8
minimum-idle: 2
flyway:
enabled: true
locations: classpath:db/migration
mvc:
async:
request-timeout: 0 # SSE streams must not time out
# Spring AI MCP server. In Spring AI 1.0.0 the WebMVC transport is SSE-based
# (WebMvcSseServerTransportProvider) — the closest available transport to the 2025-03-26
# "Streamable HTTP" spec; there is no separate "protocol: streamable" property in this
# starter. JSON-RPC POSTs land on `sse-message-endpoint` (/mcp); server-initiated
# notifications stream over `sse-endpoint` (/sse). See com.trueref.adapter.in.mcp.
ai:
mcp:
server:
enabled: true
name: trueref
version: 0.1.0
type: SYNC
sse-message-endpoint: /mcp
sse-endpoint: /sse
server:
port: 8080
shutdown: graceful
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
springdoc:
api-docs:
path: /v3/api-docs
swagger-ui:
path: /swagger-ui.html
trueref:
home: ${TRUEREF_HOME:./data}
ingestion:
poll-interval-default: PT1H
tag-cap-default: 100
max-file-size-bytes-default: 1048576
watched-folder: ${trueref.home}/watched
# Max parallel parse jobs (FETCH/CLONE → CHECKOUT → DISCOVER → DIFF → PARSE).
# Parse is I/O + CPU only — no GPU. 4 is safe on this machine (Ryzen 9 3900X, 62 GB RAM);
# increase for repos with small files, decrease if git I/O saturates disk.
max-parse-jobs: 4
# Max parsed batches buffered between parse workers and the embed worker.
# When the embed worker is busy, parse workers block here — natural backpressure.
# Total peak in-memory batches = max-parse-jobs + embed-queue-capacity.
embed-queue-capacity: 4
embedding:
model: bge-base-en-v1.5
onnx-providers: cuda,directml,cpu
session-count: 1
batch-size: 32
max-seq-len: 512
# Which CUDA device to bind ONNX sessions to. Passed directly to ORT's CUDA EP
# as the physical device index — ORT uses the CUDA driver/NVML API which can bypass
# CUDA_VISIBLE_DEVICES remapping. The ./trueref script sets this to $TRUEREF_GPU (default: 1 = RTX 3060).
gpu-device-id: 0
# Per-session GPU memory cap in bytes. 0 = unbounded. With session-count=1 there
# is no pool contention, so leave this unbounded — capping it risks exhausting the
# BFC arena during model-weight loading before inference starts. The ./trueref script
# defaults to 0 and can be overridden with TRUEREF_MEM_LIMIT.
gpu-mem-limit-bytes: 0
# Override download URLs per (model, file). The built-in defaults (in ModelDownloader)
# cover bge-base-en-v1.5, ms-marco-MiniLM-L6-v2, bge-m3, and bge-reranker-v2-m3.
# Set HF_TOKEN in the environment for higher rate limits or gated models.
# model-sources:
# bge-base-en-v1.5:
# model.onnx:
# - https://huggingface.co/BAAI/bge-base-en-v1.5/resolve/main/onnx/model.onnx
reranker:
model: ms-marco-MiniLM-L6-v2
top-k: 100
embedding-cache:
# Must match the embedding model's output dimension. Changing this automatically
# wipes the stale .f32 files in the cache directory on next startup.
dimension: 768
search:
rrf-k: 60
final-top-k: 20
mcp:
tokens-default: 5000
tokens-min: 500
tokens-max: 50000
logging:
level:
root: INFO
com.trueref: INFO
org.eclipse.jgit: WARN
org.apache.lucene: WARN

View File

@@ -0,0 +1,62 @@
#!/usr/bin/env bash
# trueref launcher — wraps the fat JAR with the JVM flags required to silence
# the FFM (foreign linker) restricted-method warning emitted by JNA-based
# tokenizer libraries and to make Lucene's Vector API path readable.
#
# --enable-native-access=ALL-UNNAMED
# Lucene 10 + DJL HuggingFace Tokenizers use the new java.lang.foreign
# Linker API; on Java 21 this requires explicit native-access opt-in.
# --add-modules jdk.incubator.vector
# Lucene 10 ships an incubator-vector codepath that is significantly
# faster for cosine/dot-product math but only loads if the module is
# made readable from the unnamed module.
#
# Usage:
# bin/trueref # default settings
# bin/trueref --server.port=18080 # forward Spring properties
# TRUEREF_JAR=/path/to/trueref.jar bin/trueref
#
# Environment overrides:
# TRUEREF_JAR Path to the fat JAR (default: <script-dir>/../trueref.jar)
# JAVA Path to the java binary (default: ${JAVA_HOME:-}/bin/java or `java` on PATH)
# JAVA_OPTS Extra JVM flags (e.g. -Xmx16g, -XX:+UseZGC)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
JAR_DEFAULT="${SCRIPT_DIR}/../trueref.jar"
JAR="${TRUEREF_JAR:-$JAR_DEFAULT}"
if [[ ! -f "$JAR" ]]; then
echo "trueref: jar not found at $JAR" >&2
echo "trueref: set TRUEREF_JAR or place trueref.jar next to this script" >&2
exit 1
fi
if [[ -n "${JAVA:-}" ]]; then
:
elif [[ -n "${JAVA_HOME:-}" && -x "${JAVA_HOME}/bin/java" ]]; then
JAVA="${JAVA_HOME}/bin/java"
else
JAVA="$(command -v java || true)"
fi
if [[ -z "${JAVA:-}" || ! -x "${JAVA}" ]]; then
echo "trueref: java not found; set JAVA_HOME or install JDK 21+" >&2
exit 1
fi
# ONNX Runtime CUDA EP needs cuDNN 9 on LD_LIBRARY_PATH. Many distros only ship
# cuDNN via the system package manager or via a Python wheel (nvidia-cudnn-cu12).
# If the user sets TRUEREF_CUDNN_LIB we trust it; otherwise we leave LD_LIBRARY_PATH
# alone and let CUDA fall back to CPU with a logged warning.
if [[ -n "${TRUEREF_CUDNN_LIB:-}" ]]; then
export LD_LIBRARY_PATH="${TRUEREF_CUDNN_LIB}${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
fi
exec "$JAVA" \
--enable-native-access=ALL-UNNAMED \
--add-modules=jdk.incubator.vector \
${JAVA_OPTS:-} \
-jar "$JAR" \
"$@"