Imported from mportilho/runestone-forge (
AGENTS.md). Install upstream withnpx skills add mportilho/runestone-forge. Copyright stays with the author.
AGENTS.md
This file provides guidance to AI agents (Gemini CLI, Claude Code, etc.) when working with code in this repository.
Agent skills
Issue tracker
Issues and specs are published to GitHub in mportilho/runestone-forge. See docs/agents/issue-tracker.md.
Triage labels
The five canonical roles use needs-triage, needs-info, ready-for-agent, ready-for-human, and wontfix. See docs/agents/triage-labels.md.
Domain docs
The repository uses multi-context documentation indexed by CONTEXT-MAP.md, with glossaries and ADRs alongside each module. See docs/agents/domain.md.
Project Overview
runestone-forge is a multi-module Java development toolkit with four modules:
- runestone-toolkit — Core utilities (memoization, caching, data conversion, assertions). No Spring dependency.
- dynamic-filter-resolver — Dynamic filtering framework for Spring Data JPA repositories, with OpenAPI integration.
- expression-evaluator — Redesigned expression evaluator with a multi-phase compilation pipeline, richer type system (scalar, vector, unknown), and Caffeine-based expression cache.
Build & Test Commands
# Build all modules
mvn clean install
# Run all tests
mvn clean test
# Run integration tests
mvn verify
# Run tests for a single module
mvn clean test -pl expression-evaluator
# Run a single test class
mvn clean test -pl expression-evaluator -Dtest=ExpressionCalculatorTest
# Run a single test method
mvn clean test -pl expression-evaluator -Dtest=ExpressionCalculatorTest#methodName
# Update project version
mvn versions:set -DnewVersion=1.2.0 versions:commit
JDK 21 test note: Tests require
-XX:+EnableDynamicAgentLoading -Dnet.bytebuddy.experimental=truefor Mockito/ByteBuddy. These flags are already configured in each module'spom.xmlundermaven-surefire-plugin.Sandbox note: In restricted sandboxed environments, the full Maven test suite may still fail during Mockito initialization with ByteBuddy self-attach errors even with the Surefire flags configured. If that happens, rerun the same Maven test command outside the sandbox before treating it as a code regression.
Architecture
runestone-toolkit
Foundation library used by the other modules. Key packages:
com.runestone.memoization—MemoizedFunction,MemoizedSupplierfor caching function results.com.runestone.converters— Data type conversion service.com.runestone.assertions—AssertsandCertifyfor design-by-contract and validations.
Uses Caffeine for caching internal structures.
dynamic-filter-resolver
Enables annotating REST controller parameters with filter definitions that are automatically resolved to JPA Specification objects.
com.runestone.dynafilter.core— Framework-agnostic filter core: models, operations, statement generation, and theDynamicFilterResolverinterface.com.runestone.dynafilter.modules.jpa— Spring Data JPA integration:DynamicFilterJpaRepository, auto-configuration,ArgumentResolver,WebMvcConfigurer.com.runestone.dynafilter.modules.openapi— SpringDoc OpenAPI integration.
Key pattern: FilterOperation<R> strategy interface with multiple JPA Specification implementations (Equals, Like, Between, Greater, etc.).
expression-evaluator
A redesigned expression evaluator with a multi-phase compilation pipeline and richer type system.
Public API (package com.runestone.expeval.api, environment and catalog):
ExpressionEnvironment— built viaExpressionEnvironmentBuilder; configures the runtime (function catalog, external symbols, data conversion). Scoped byExpressionEnvironmentIdfor cache keying.MathExpression,LogicalExpression— compiled, reusable expression objects.FunctionCatalog,ExternalSymbolCatalog— central repositories for available functions and external symbols.
Compilation pipeline (all internal, coordinated by ExpressionCompiler):
- Parse —
ExpressionEvaluatorParserFacadewraps the ANTLR grammar; supportsSLLwithLLfallback viaPredictionStrategy. - AST —
SemanticAstBuildermaps the ANTLR parse tree to typedNodesubclasses (BinaryOperationNode,FunctionCallNode,ConditionalNode,VectorLiteralNode, etc.) ininternal.ast. - Semantic resolution —
SemanticResolverwalks the AST againstFunctionCatalogandExternalSymbolCatalog, producing aSemanticModelwithSymbolRefbindings and typedResolvedTypeannotations. - Execution plan —
ExecutionPlanBuilderconverts the resolved AST to anExecutionPlanofExecutableNodeobjects (e.g.,ExecutableBinaryOp,ExecutableFunctionCall,ExecutableConditional). - Evaluation —
MathEvaluator/LogicalEvaluatorwalk theExecutionPlanwithin anExecutionScope, returningRuntimeValue.
Type system: ResolvedType hierarchy with ScalarType, VectorType, and UnknownType; runtime values are RuntimeValue objects coerced via RuntimeCoercionService.
Compiled expressions are cached in ExpressionCompiler by (source, environmentId, resultType) using Caffeine (max 1 024 entries).
Tech Stack
- Java 21, Maven 3.9+
- Spring Boot 4.0.2 (dynamic-filter-resolver only)
- SpringDoc OpenAPI 3.0.1 (dynamic-filter-resolver only)
- ANTLR 4.13.1
- JUnit 5, AssertJ, Mockito 5 — testing
- JMH 1.37 — microbenchmarks (in
benchmark/andperf/test packages)
Performance Requirements
Performance is a first-class concern in this repository. The modules in this project are intended to be used as reusable infrastructure, often on request paths, expression-evaluation paths, conversion paths, and filtering/query-building paths. Small implementation choices can compound significantly for downstream applications.
When generating or modifying code:
- Treat allocation rate, dispatch overhead, repeated conversions, repeated path resolution, and unnecessary intermediate objects as design concerns.
- Prefer direct Java constructs for hot-path code. A plain
forloop is often preferable to aStreampipeline when mapping small arrays or collections in frequently called methods. - Avoid abstraction for abstraction's sake. Add helpers, layers, or generic mechanisms only when they improve correctness, reuse, or clarity enough to justify their runtime cost.
- Preserve behavioral compatibility while optimizing. Null handling, exception messages, conversion semantics, validation order, and public API behavior must not change accidentally.
- Use caching deliberately for expensive repeated work, but avoid caches that introduce stale data, memory leaks, or unnecessary synchronization.
- Benchmark meaningful performance work with JMH when the impact is not obvious or when changing code in known hot paths.
Key Reference Documents
expression-evaluator/docs/runtime-internals.md— Verified findings about the expression-evaluator runtime: compilation pipeline, type system,RuntimeValuevariants,RuntimeCoercionServicecoercion order, array-parameter coercion fix, overload disambiguation rules,RuntimeValueFactorywrapping logic, grammar syntax for date/datetime literals and type-hinted variables, andExpressionEnvironmentBuilderconvenience methods. Read this before exploring the expression-evaluator internals from scratch.expression-evaluator/docs/perf/benchmark-organization.md— Package layout, classification, and JMH commands forexpression-evaluatorperformance benchmarks. Read this before adding, moving, or running expression-evaluator benchmarks.
Agent Skills
- ALWAYS load the java-guidelines skill if present when working with Java files on this project.
Regenerating the ANTLR Grammar
The default Maven build no longer regenerates the grammar. Generated Java sources remain versioned in
expression-evaluator/src/main/java/com/runestone/expeval/internal/grammar, so normal mvn test / mvn compile
does not touch them or recreate stray .tokens files under src/main/java.
Regenerate the committed Java sources
mvn -pl expression-evaluator -Pantlr-generate generate-sources
This profile generates into expression-evaluator/target/generated-sources/antlr4 and then copies only the
generated .java files back into
expression-evaluator/src/main/java/com/runestone/expeval/internal/grammar.
For ANTLR diagnostics
Important: The jar at
~/dev/git/temp/antlr4-4.13.1.jaris not a self-contained uber-jar and does not work directly — even with the sibling jars from~/dev/git/temp/antlr-lib/on the classpath, the JVM cannot initializeorg.antlr.v4.Tool. Use the Maven local cache instead.
mvn dependency:get -Dartifact=org.antlr:antlr4:4.13.1
ANTLR_TOOL=~/.m2/repository/org/antlr/antlr4/4.13.1/antlr4-4.13.1.jar
ANTLR_RT=~/.m2/repository/org/antlr/antlr4-runtime/4.13.1/antlr4-runtime-4.13.1.jar
ANTLR3_RT=~/.m2/repository/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar
ST4=~/.m2/repository/org/antlr/ST4/4.3.4/ST4-4.3.4.jar
ICU4J=$(find ~/.m2/repository -name "icu4j-*.jar" | head -1)
GRAMMAR_OUT=expression-evaluator/src/main/java/com/runestone/expeval/internal/grammar
TMPDIR=$(mktemp -d)
java -cp "${ANTLR_TOOL}:${ANTLR_RT}:${ANTLR3_RT}:${ST4}:${ICU4J}" \
org.antlr.v4.Tool \
-Dlanguage=Java \
-visitor \
-listener \
-Xexact-output-dir \
-o "${TMPDIR}" \
expression-evaluator/src/main/antlr4/com/runestone/expeval/internal/grammar/ExpressionEvaluator.g4
cp "${TMPDIR}"/*.java "${GRAMMAR_OUT}/"
rm -rf "${TMPDIR}"
For diagnostics such as -Xlog, omit the cp/rm steps and inspect ${TMPDIR} directly.
Decision-making and clarification policy
When a task requires a decision, assumption, trade-off, or interpretation that is not explicitly defined in the current instructions, repository documentation, or user request, do not guess silently.
Instead, pause and open a clarification round with the user before proceeding. Present:
- the question that must be decided;
- the relevant context or uncertainty;
- the viable options, with pros and cons when useful;
- your recommended option, if there is enough evidence;
- the impact of each option on implementation, tests, security, maintainability, or delivery.
Proceed only after the user delegates or confirms the decision.
Exception: if the decision is low-risk, easily reversible, and follows established project conventions, make the smallest reasonable choice, document the assumption in your final response, and continue.