diff --git a/.agents/documentation-guidelines.md b/.agents/documentation-guidelines.md
index 914dcc8737..6c9c1bae76 100644
--- a/.agents/documentation-guidelines.md
+++ b/.agents/documentation-guidelines.md
@@ -9,6 +9,6 @@
## Avoid widows, runts, orphans, or rivers
Agents should **AVOID** text flow patters illustrated
-on [this diagram](widow-runt-orphan-river.jpg).
+on [this diagram](widow-runt-orphan.jpg).
[todo-comments]: https://github.com/SpineEventEngine/documentation/wiki/TODO-comments
diff --git a/.agents/skills/writer/SKILL.md b/.agents/skills/writer/SKILL.md
new file mode 100644
index 0000000000..5c720265b3
--- /dev/null
+++ b/.agents/skills/writer/SKILL.md
@@ -0,0 +1,51 @@
+---
+name: writer
+description: >
+ Write, edit, and restructure user-facing and developer-facing documentation.
+ Use when asked to create/update docs such as `README.md`, `docs/**`, and
+ other Markdown documentation;
+ when drafting tutorials, guides, troubleshooting pages, or migration notes; and
+ when improving inline API documentation (KDoc) and examples.
+---
+
+# Write documentation (repo-specific)
+
+## Decide the target and audience
+
+- Identify the target reader: end user, contributor, maintainer, or tooling/automation.
+- Identify the task type: new doc, update, restructure, or documentation audit.
+- Identify the acceptance criteria: “what is correct when the reader is done?”
+
+## Choose where the content should live
+
+- Prefer updating an existing doc over creating a new one.
+- Place content in the most discoverable location:
+ - `README.md`: project entry point and “what is this?”.
+ - `docs/`: longer-form docs (follow existing conventions in that tree).
+ - Source KDoc: API usage, examples, and semantics that belong with the code.
+
+## Follow local documentation conventions
+
+- Follow `.agents/documentation-guidelines.md` and `.agents/documentation-tasks.md`.
+- Use fenced code blocks for commands and examples; format file/dir names as code.
+- Avoid widows, runts, orphans, and rivers by reflowing paragraphs when needed.
+
+## Make docs actionable
+
+- Prefer steps the reader can execute (commands + expected outcome).
+- Prefer concrete examples over abstract descriptions.
+- Include prerequisites (versions, OS, environment) when they are easy to miss.
+- Use consistent terminology (match code identifiers and existing docs).
+
+## KDoc-specific guidance
+
+- For public/internal APIs, include at least one example snippet demonstrating common usage.
+- When converting from Javadoc/inline comments to KDoc:
+ - Remove HTML like `
` and preserve meaning.
+ - Prefer short paragraphs and blank lines over HTML formatting.
+
+## Validate changes
+
+- For code changes, follow `.agents/running-builds.md`.
+- For documentation-only changes in Kotlin/Java sources, prefer `./gradlew dokka`.
+
diff --git a/.agents/skills/writer/agents/openai.yaml b/.agents/skills/writer/agents/openai.yaml
new file mode 100644
index 0000000000..44eaa4e241
--- /dev/null
+++ b/.agents/skills/writer/agents/openai.yaml
@@ -0,0 +1,5 @@
+interface:
+ display_name: "Writer"
+ short_description: "Write and update user/developer docs"
+ default_prompt: "Write or revise documentation in this repository (for example: README.md, docs/**, CONTRIBUTING.md, and API documentation/KDoc). Follow local documentation guidelines in .agents/*.md, keep changes concise and actionable, and include concrete examples and commands where appropriate."
+
diff --git a/.agents/skills/writer/assets/templates/doc-page.md b/.agents/skills/writer/assets/templates/doc-page.md
new file mode 100644
index 0000000000..f405b71e15
--- /dev/null
+++ b/.agents/skills/writer/assets/templates/doc-page.md
@@ -0,0 +1,23 @@
+# Title
+
+## Goal
+
+State what the reader will accomplish.
+
+## Prerequisites
+
+- List versions/tools the reader needs.
+
+## Steps
+
+1. Do the first thing.
+2. Do the next thing.
+
+## Verify
+
+Show how the reader can confirm success.
+
+## Troubleshooting
+
+- Common failure: likely cause → fix.
+
diff --git a/.agents/skills/writer/assets/templates/kdoc-example.md b/.agents/skills/writer/assets/templates/kdoc-example.md
new file mode 100644
index 0000000000..fdbd9b6a0d
--- /dev/null
+++ b/.agents/skills/writer/assets/templates/kdoc-example.md
@@ -0,0 +1,11 @@
+````kotlin
+/**
+ * Explain what this API does in one sentence.
+ *
+ * ## Example
+ * ```kotlin
+ * // Show the typical usage pattern.
+ * val result = doThing()
+ * ```
+ */
+````
diff --git a/.agents/skills/writer/assets/templates/kotlin-java-example.md b/.agents/skills/writer/assets/templates/kotlin-java-example.md
new file mode 100644
index 0000000000..5517516f56
--- /dev/null
+++ b/.agents/skills/writer/assets/templates/kotlin-java-example.md
@@ -0,0 +1,13 @@
+{{< code-tabs langs="Kotlin, Java">}}
+
+{{< code-tab lang="Kotlin" >}}
+```kotlin
+```
+{{< /code-tab >}}
+
+{{< code-tab lang="Java" >}}
+```java
+```
+{{< /code-tab >}}
+
+{{< /code-tabs >}}
diff --git a/.agents/widow-runt-orphan.jpg b/.agents/widow-runt-orphan.jpg
new file mode 100644
index 0000000000..284b02a47d
Binary files /dev/null and b/.agents/widow-runt-orphan.jpg differ
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 31f80aec85..5358548ef6 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,3 @@
-
@@ -41,4 +40,4 @@
-
+
\ No newline at end of file
diff --git a/base/src/main/java/io/spine/type/KnownTypes.java b/base/src/main/java/io/spine/type/KnownTypes.java
index 4847fe2f9a..213acbe793 100644
--- a/base/src/main/java/io/spine/type/KnownTypes.java
+++ b/base/src/main/java/io/spine/type/KnownTypes.java
@@ -52,7 +52,6 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import static io.spine.string.Strings.joinByLines;
import static io.spine.util.Predicates2.distinctBy;
-import static java.lang.String.format;
import static java.lang.System.lineSeparator;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toSet;
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index 227e6d2625..fdcb94aee2 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -192,6 +192,15 @@ dependencies {
).forEach {
implementation(it)
}
+
+ testImplementation(platform("org.junit:junit-bom:5.11.4"))
+ testImplementation("org.junit.jupiter:junit-jupiter")
+ testImplementation("io.kotest:kotest-assertions-core:6.0.4")
+ testRuntimeOnly("org.junit.platform:junit-platform-launcher")
+}
+
+tasks.test {
+ useJUnitPlatform()
}
dependOnBuildSrcJar()
diff --git a/buildSrc/src/main/kotlin/BuildExtensions.kt b/buildSrc/src/main/kotlin/BuildExtensions.kt
index 4122975f62..f7a11e3e04 100644
--- a/buildSrc/src/main/kotlin/BuildExtensions.kt
+++ b/buildSrc/src/main/kotlin/BuildExtensions.kt
@@ -227,6 +227,8 @@ fun Project.configureTaskDependencies() {
"compileTestFixturesKotlin".dependOn("kspTestFixturesKotlin")
"javadocJar".dependOn(dokkaGeneratePublicationJavadoc)
"htmlDocsJar".dependOn(dokkaGenerate)
+
+ "kspTestKotlin".dependOn("launchTestSpineCompiler")
}
}
diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/build/Ksp.kt b/buildSrc/src/main/kotlin/io/spine/dependency/build/Ksp.kt
index b1146e97a5..b81c2b2896 100644
--- a/buildSrc/src/main/kotlin/io/spine/dependency/build/Ksp.kt
+++ b/buildSrc/src/main/kotlin/io/spine/dependency/build/Ksp.kt
@@ -35,7 +35,7 @@ import io.spine.dependency.Dependency
*/
@Suppress("unused")
object Ksp : Dependency() {
- override val version = "2.3.0"
+ override val version = "2.3.6"
val dogfoodingVersion = version
override val group = "com.google.devtools.ksp"
diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt
index af6c40095f..b999a75d2c 100644
--- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt
+++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt
@@ -33,8 +33,8 @@ package io.spine.dependency.local
*/
@Suppress("ConstPropertyName", "unused")
object Base {
- const val version = "2.0.0-SNAPSHOT.383"
- const val versionForBuildScript = "2.0.0-SNAPSHOT.383"
+ const val version = "2.0.0-SNAPSHOT.386"
+ const val versionForBuildScript = "2.0.0-SNAPSHOT.386"
const val group = Spine.group
private const val prefix = "spine"
const val libModule = "$prefix-base"
diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/BaseTypes.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/BaseTypes.kt
index 23c7da75bd..27147386e1 100644
--- a/buildSrc/src/main/kotlin/io/spine/dependency/local/BaseTypes.kt
+++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/BaseTypes.kt
@@ -33,7 +33,7 @@ package io.spine.dependency.local
*/
@Suppress("ConstPropertyName")
object BaseTypes {
- const val version = "2.0.0-SNAPSHOT.212"
+ const val version = "2.0.0-SNAPSHOT.223"
const val group = Spine.group
const val artifact = "spine-base-types"
const val lib = "$group:$artifact:$version"
diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Change.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Change.kt
index 24365800d3..7809c9cd6c 100644
--- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Change.kt
+++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Change.kt
@@ -33,7 +33,7 @@ package io.spine.dependency.local
*/
@Suppress("ConstPropertyName")
object Change {
- const val version = "2.0.0-SNAPSHOT.200"
+ const val version = "2.0.0-SNAPSHOT.206"
const val group = Spine.group
const val artifact = "spine-change"
const val lib = "$group:$artifact:$version"
diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt
index c3ca673886..31cbfb3fac 100644
--- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt
+++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Compiler.kt
@@ -72,7 +72,7 @@ object Compiler : Dependency() {
* The version of the Compiler dependencies.
*/
override val version: String
- private const val fallbackVersion = "2.0.0-SNAPSHOT.035"
+ private const val fallbackVersion = "2.0.0-SNAPSHOT.041"
/**
* The distinct version of the Compiler used by other build tools.
@@ -81,7 +81,7 @@ object Compiler : Dependency() {
* transitive dependencies, this is the version used to build the project itself.
*/
val dogfoodingVersion: String
- private const val fallbackDfVersion = "2.0.0-SNAPSHOT.035"
+ private const val fallbackDfVersion = "2.0.0-SNAPSHOT.041"
/**
* The artifact for the Compiler Gradle plugin.
diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvm.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvm.kt
index 2e224754f8..2e0b6973b5 100644
--- a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvm.kt
+++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvm.kt
@@ -39,7 +39,7 @@ typealias CoreJava = CoreJvm
@Suppress("ConstPropertyName", "unused")
object CoreJvm {
const val group = Spine.group
- const val version = "2.0.0-SNAPSHOT.358"
+ const val version = "2.0.0-SNAPSHOT.372"
const val coreArtifact = "spine-core"
const val clientArtifact = "spine-client"
diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt
index 582272c8a9..344e8a13fc 100644
--- a/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt
+++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/CoreJvmCompiler.kt
@@ -26,9 +26,6 @@
package io.spine.dependency.local
-import io.spine.dependency.local.CoreJvmCompiler.dogfoodingVersion
-import io.spine.dependency.local.CoreJvmCompiler.version
-
/**
* Dependencies on the CoreJvm Compiler artifacts.
*
@@ -49,12 +46,12 @@ object CoreJvmCompiler {
/**
* The version used to in the build classpath.
*/
- const val dogfoodingVersion = "2.0.0-SNAPSHOT.042"
+ const val dogfoodingVersion = "2.0.0-SNAPSHOT.058"
/**
* The version to be used for integration tests.
*/
- const val version = "2.0.0-SNAPSHOT.042"
+ const val version = "2.0.0-SNAPSHOT.058"
/**
* The ID of the Gradle plugin.
diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/ModelCompiler.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/ModelCompiler.kt
index 5f9e54117d..fb4359d891 100644
--- a/buildSrc/src/main/kotlin/io/spine/dependency/local/ModelCompiler.kt
+++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/ModelCompiler.kt
@@ -33,7 +33,7 @@ package io.spine.dependency.local
*/
@Suppress("ConstPropertyName")
object ModelCompiler {
- const val version = "2.0.0-SNAPSHOT.133"
+ const val version = "2.0.0-SNAPSHOT.200"
const val group = Spine.toolsGroup
const val artifact = "spine-model-compiler"
const val lib = "$group:$artifact:$version"
diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Time.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Time.kt
index 4bfe22cdfc..363e8a3056 100644
--- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Time.kt
+++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Time.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2025, TeamDev. All rights reserved.
+ * Copyright 2026, TeamDev. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,18 +26,42 @@
package io.spine.dependency.local
+import io.spine.dependency.Dependency
+
/**
* Spine Time library.
*
* @see spine-time
*/
-@Suppress("ConstPropertyName")
-object Time {
- const val version = "2.0.0-SNAPSHOT.220"
- const val group = Spine.group
- const val artifact = "spine-time"
- const val lib = "$group:$artifact:$version"
- const val javaExtensions = "$group:$artifact-java:$version"
- const val kotlinExtensions = "$group:$artifact-kotlin:$version"
- const val testLib = "${Spine.toolsGroup}:spine-time-testlib:$version"
+@Suppress(
+ "unused" /* Some subprojects do not use all Time artifacts. */,
+ "ConstPropertyName" /* We use custom convention for artifact properties. */,
+ "MemberVisibilityCanBePrivate" /* The properties are used directly by other subprojects. */,
+)
+object Time : Dependency() {
+ override val group = Spine.group
+ override val version = "2.0.0-SNAPSHOT.234"
+ private const val infix = "spine-time"
+
+ fun lib(version: String): String = "$group:$infix:$version"
+ val lib get() = lib(version)
+
+ fun javaExtensions(version: String): String = "$group:$infix-java:$version"
+ val javaExtensions get() = javaExtensions(version)
+
+ fun kotlinExtensions(version: String): String = "$group:$infix-kotlin:$version"
+ val kotlinExtensions get() = kotlinExtensions(version)
+
+ fun testLib(version: String): String = "${Spine.toolsGroup}:time-testlib:$version"
+ val testLib get() = testLib(version)
+
+ override val modules: List
+ get() = listOf(
+ lib,
+ javaExtensions,
+ kotlinExtensions,
+ testLib
+ ).map {
+ it.split(":").let { (g, artifact) -> "$g:$artifact" }
+ }
}
diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt
index 1df8dd1f47..872127af22 100644
--- a/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt
+++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/ToolBase.kt
@@ -34,8 +34,8 @@ package io.spine.dependency.local
@Suppress("ConstPropertyName", "unused")
object ToolBase {
const val group = Spine.toolsGroup
- const val version = "2.0.0-SNAPSHOT.371"
- const val dogfoodingVersion = "2.0.0-SNAPSHOT.371"
+ const val version = "2.0.0-SNAPSHOT.375"
+ const val dogfoodingVersion = "2.0.0-SNAPSHOT.375"
const val lib = "$group:tool-base:$version"
const val classicCodegen = "$group:classic-codegen:$version"
diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt b/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt
index 92a71d798c..cf429bf432 100644
--- a/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt
+++ b/buildSrc/src/main/kotlin/io/spine/dependency/local/Validation.kt
@@ -36,7 +36,7 @@ object Validation {
/**
* The version of the Validation library artifacts.
*/
- const val version = "2.0.0-SNAPSHOT.378"
+ const val version = "2.0.0-SNAPSHOT.408"
/**
* The last version of Validation compatible with ProtoData.
@@ -53,6 +53,9 @@ object Validation {
fun runtime(version: String) = "$runtimeModule:$version"
val runtime = runtime(version)
+ @Deprecated("Use `runtime` instead.", ReplaceWith("runtime"))
+ const val oldRuntime = "io.spine.validation:spine-validation-java-runtime:2.0.0-SNAPSHOT.354"
+
const val javaModule = "$group:$prefix-java"
const val java = "$javaModule:$version"
const val javaBundleModule = "$group:$prefix-java-bundle"
diff --git a/buildSrc/src/main/kotlin/io/spine/dependency/test/JUnit.kt b/buildSrc/src/main/kotlin/io/spine/dependency/test/JUnit.kt
index 6e9021fb0c..a4962175ae 100644
--- a/buildSrc/src/main/kotlin/io/spine/dependency/test/JUnit.kt
+++ b/buildSrc/src/main/kotlin/io/spine/dependency/test/JUnit.kt
@@ -33,7 +33,7 @@ import io.spine.dependency.DependencyWithBom
@Suppress("unused", "ConstPropertyName")
object JUnit : DependencyWithBom() {
- override val version = "6.0.0"
+ override val version = "6.0.3"
override val group: String = "org.junit"
/**
diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/docs/UpdatePluginVersion.kt b/buildSrc/src/main/kotlin/io/spine/gradle/docs/UpdatePluginVersion.kt
new file mode 100644
index 0000000000..a7f269f94a
--- /dev/null
+++ b/buildSrc/src/main/kotlin/io/spine/gradle/docs/UpdatePluginVersion.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2026, TeamDev. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Redistribution and use in source and/or binary forms, with or without
+ * modification, must retain the above copyright notice and the following
+ * disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package io.spine.gradle.docs
+
+import java.io.File
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputDirectory
+import org.gradle.api.tasks.Optional
+import org.gradle.api.tasks.TaskAction
+
+/**
+ * Updates the version of a Gradle plugin in `build.gradle.kts` files.
+ *
+ * The task searches for plugin declarations in the format
+ * `id("plugin-id") version "version-number"` and replaces
+ * the version number with the one found in the version script file.
+ *
+ * @property directory
+ * The directory to scan recursively for `build.gradle.kts` files.
+ * @property version
+ * The version number to set for the plugin.
+ * @property pluginId
+ * The ID of the plugin whose version should be updated.
+ * @property kotlinVersion
+ * Optional. If set, updates the version of the Kotlin plugin declared with
+ * `kotlin("…") version "…"` syntax in the `plugins` block.
+ * This option works in combination with the [version] and [pluginId] properties.
+ */
+abstract class UpdatePluginVersion : DefaultTask() {
+
+ @get:InputDirectory
+ abstract val directory: DirectoryProperty
+
+ @get:Input
+ abstract val version: Property
+
+ @get:Input
+ abstract val pluginId: Property
+
+ @get:Input
+ @get:Optional
+ abstract val kotlinVersion: Property
+
+ /**
+ * Updates plugin versions in build files within the path in the [directory].
+ */
+ @TaskAction
+ fun update() {
+ val rootDir = directory.get().asFile
+
+ val kotlinVersionSet = kotlinVersion.isPresent
+ val kotlinVer = kotlinVersion.orNull
+ val id = pluginId.get()
+ val ver = version.get()
+
+ rootDir.walkTopDown()
+ .filter { it.name == "build.gradle.kts" }
+ .forEach { file ->
+ if (kotlinVersionSet && kotlinVer != null) {
+ updateKotlinPluginVersion(file, kotlinVer)
+ }
+ updatePluginVersion(file, id, ver)
+ }
+ }
+
+ @Suppress("MemberNameEqualsClassName")
+ private fun updatePluginVersion(file: File, id: String, version: String) {
+ val content = file.readText()
+ // Regex to match: id("plugin-id") version "version-number"
+ val regex = """id\("$id"\)\s+version\s+"([^"]+)"""".toRegex()
+
+ if (regex.containsMatchIn(content)) {
+ val updatedContent = regex.replace(content) {
+ "id(\"$id\") version \"$version\""
+ }
+ if (content != updatedContent) {
+ file.writeText(updatedContent)
+ logger.info("Updated version of '$id' in `${file.absolutePath}`.")
+ }
+ }
+ }
+
+ private fun updateKotlinPluginVersion(file: File, kotlinVersion: String) {
+ val content = file.readText()
+ // Regex to match Kotlin plugin declarations like: kotlin("jvm") version "1.9.0"
+ val regex = """kotlin\("([^"]+)"\)\s+version\s+"([^"]+)"""".toRegex()
+ if (regex.containsMatchIn(content)) {
+ val updatedContent = regex.replace(content) { matchResult ->
+ val plugin = matchResult.groupValues[1]
+ "kotlin(\"$plugin\") version \"$kotlinVersion\""
+ }
+ if (content != updatedContent) {
+ file.writeText(updatedContent)
+ logger.info("Updated Kotlin plugin version in `${file.absolutePath}`.")
+ }
+ }
+ }
+}
diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/kotlin/KotlinConfig.kt b/buildSrc/src/main/kotlin/io/spine/gradle/kotlin/KotlinConfig.kt
index f6bed9a066..92b7a38743 100644
--- a/buildSrc/src/main/kotlin/io/spine/gradle/kotlin/KotlinConfig.kt
+++ b/buildSrc/src/main/kotlin/io/spine/gradle/kotlin/KotlinConfig.kt
@@ -27,10 +27,10 @@
package io.spine.gradle.kotlin
import org.gradle.jvm.toolchain.JavaLanguageVersion
+import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonCompilerOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
-import org.jetbrains.kotlin.gradle.dsl.JvmDefaultMode
/**
* Sets [Java toolchain](https://kotlinlang.org/docs/gradle.html#gradle-java-toolchains-support)
@@ -56,7 +56,7 @@ fun KotlinJvmProjectExtension.applyJvmToolchain(version: String) =
@Suppress("unused")
fun KotlinCommonCompilerOptions.setFreeCompilerArgs() {
if (this is KotlinJvmCompilerOptions) {
- jvmDefault.set(JvmDefaultMode.ENABLE)
+ jvmDefault.set(JvmDefaultMode.NO_COMPATIBILITY)
}
freeCompilerArgs.addAll(
listOf(
diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/publish/SpinePublishing.kt b/buildSrc/src/main/kotlin/io/spine/gradle/publish/SpinePublishing.kt
index 480175fef1..37fdf5f7db 100644
--- a/buildSrc/src/main/kotlin/io/spine/gradle/publish/SpinePublishing.kt
+++ b/buildSrc/src/main/kotlin/io/spine/gradle/publish/SpinePublishing.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2025, TeamDev. All rights reserved.
+ * Copyright 2026, TeamDev. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@
package io.spine.gradle.publish
import io.spine.gradle.repo.Repository
+import java.util.Locale
import org.gradle.api.Project
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
import org.gradle.kotlin.dsl.apply
@@ -145,9 +146,10 @@ import org.gradle.kotlin.dsl.findByType
* @see [artifacts]
* @see SpinePublishing
*/
-fun Project.spinePublishing(block: SpinePublishing.() -> Unit) {
+fun Project.spinePublishing(block: SpinePublishing.() -> Unit): SpinePublishing {
apply()
val name = SpinePublishing::class.java.simpleName
+ .replaceFirstChar { it.lowercase(Locale.getDefault()) }
val extension = with(extensions) {
findByType() ?: create(name, project)
}
@@ -155,6 +157,7 @@ fun Project.spinePublishing(block: SpinePublishing.() -> Unit) {
block()
configured()
}
+ return extension
}
/**
@@ -182,6 +185,12 @@ open class SpinePublishing(private val project: Project) {
* The default prefix added before a module name when publishing artifacts.
*/
const val DEFAULT_PREFIX = "spine-"
+
+ /**
+ * The reserved value that means that no prefix should be added
+ * to a tool module's artifact ID.
+ */
+ const val NONE_PREFIX = "NONE"
}
private val testJar = TestJar()
@@ -250,10 +259,23 @@ open class SpinePublishing(private val project: Project) {
lateinit var destinations: Set
/**
- * A prefix to be added before the name of each artifact.
+ * A prefix to be added before the name of each artifact, if it does not belong
+ * to the Maven group `"io.spine.tools"`.
+ *
+ * @see toolArtifactPrefix
*/
var artifactPrefix: String = DEFAULT_PREFIX
+ /**
+ * A prefix to be added before a module name if it belongs to
+ * the Maven group `"io.spine.tools"`.
+ *
+ * Use `"NONE"` if you need no prefix before tool module names.
+ *
+ * @see artifactPrefix
+ */
+ var toolArtifactPrefix: String = ""
+
/**
* Allows enabling publishing of [testJar] artifact, containing compilation output
* of "test" source set.
@@ -389,10 +411,26 @@ open class SpinePublishing(private val project: Project) {
/**
* Obtains an artifact ID for the given project.
*
- * It consists of a project's name and [prefix][artifactPrefix]:
- * ``.
+ * @see artifactPrefix
+ * @see toolArtifactPrefix
*/
- fun artifactId(project: Project): String = "$artifactPrefix${project.name}"
+ fun artifactId(project: Project): String {
+ if (project.isTool) {
+ check(!toolArtifactPrefix.isEmpty()) {
+ "Artifact prefix cannot be empty for tool modules. " +
+ "Please set the `toolArtifactPrefix` property in `spinePublishing`. " +
+ "Use `\"NONE\"` to have an empty prefix for tool modules."
+ }
+ val prefix =
+ if (toolArtifactPrefix == NONE_PREFIX) { "" }
+ else { toolArtifactPrefix }
+ return "$prefix${project.name}"
+ }
+ return "$artifactPrefix${project.name}"
+ }
+
+ private val Project.isTool: Boolean
+ get() = group == "io.spine.tools"
/**
* Ensures that all modules, marked as included into [testJar] publishing,
diff --git a/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repositories.kt b/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repositories.kt
index de91e33ff6..e6c3f677d9 100644
--- a/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repositories.kt
+++ b/buildSrc/src/main/kotlin/io/spine/gradle/repo/Repositories.kt
@@ -96,11 +96,14 @@ fun RepositoryHandler.spineArtifacts(): MavenArtifactRepository = maven {
val RepositoryHandler.intellijReleases: MavenArtifactRepository
get() = maven("https://www.jetbrains.com/intellij-repository/releases")
+val RepositoryHandler.jetBrainsCacheRedirector: MavenArtifactRepository
+ get() = maven("https://cache-redirector.jetbrains.com/intellij-dependencies")
+
val RepositoryHandler.intellijDependencies: MavenArtifactRepository
get() = maven("https://packages.jetbrains.team/maven/p/ij/intellij-dependencies") {
content {
- includeGroupByRegex("com\\.jetbrains\\.intellij.*")
- includeGroupByRegex("org\\.jetbrains\\.intellij.*")
+ includeGroupByRegex("com\\.jetbrains.*")
+ includeGroupByRegex("org\\.jetbrains.*")
includeGroupByRegex("com\\.intellij.*")
}
}
@@ -129,6 +132,7 @@ fun RepositoryHandler.standardToSpineSdk() {
}
intellijReleases
+ jetBrainsCacheRedirector
intellijDependencies
maven {
diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/docs/UpdatePluginVersionTest.kt b/buildSrc/src/test/kotlin/io/spine/gradle/docs/UpdatePluginVersionTest.kt
new file mode 100644
index 0000000000..b34ca71fce
--- /dev/null
+++ b/buildSrc/src/test/kotlin/io/spine/gradle/docs/UpdatePluginVersionTest.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2026, TeamDev. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Redistribution and use in source and/or binary forms, with or without
+ * modification, must retain the above copyright notice and the following
+ * disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package io.spine.gradle.docs
+
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.string.shouldContain
+import java.io.File
+import org.gradle.testfixtures.ProjectBuilder
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.io.TempDir
+
+class UpdatePluginVersionTest {
+
+ @TempDir
+ lateinit var tempDir: File
+
+ private lateinit var buildFile: File
+
+ @BeforeEach
+ fun setUp() {
+ val subDir = File(tempDir, "subproject")
+ subDir.mkdir()
+ buildFile = File(subDir, "build.gradle.kts")
+ buildFile.writeText("""
+ plugins {
+ id("io.spine.validation") version "1.0.0"
+ id("other-plugin") version "0.1.0"
+ }
+ """.trimIndent())
+ }
+
+ @Test
+ fun `update plugin version in build file`() {
+ val project = ProjectBuilder.builder().build()
+ val task = project.tasks.register("updatePluginVersion", UpdatePluginVersion::class.java) {
+ directory.set(tempDir)
+ version.set("2.0.0-TEST")
+ pluginId.set("io.spine.validation")
+ }
+ task.get().update()
+
+ val updatedContent = buildFile.readText()
+ updatedContent shouldContain """id("io.spine.validation") version "2.0.0-TEST""""
+ updatedContent shouldContain """id("other-plugin") version "0.1.0""""
+ }
+
+ @Test
+ fun `update 'kotlin' plugin version when 'kotlinVersion' is set`() {
+ // Overwrite with a file that uses kotlin("jvm") syntax
+ buildFile.writeText(
+ """
+ plugins {
+ kotlin("jvm") version "1.9.10"
+ id("io.spine.validation") version "1.0.0"
+ }
+ """.trimIndent()
+ )
+
+ val project = ProjectBuilder.builder().build()
+ val task = project.tasks.register("updatePluginVersion", UpdatePluginVersion::class.java) {
+ directory.set(tempDir)
+ version.set("2.0.0-TEST")
+ pluginId.set("io.spine.validation")
+ kotlinVersion.set("2.2.21")
+ }
+ task.get().update()
+
+ val updatedContent = buildFile.readText()
+ updatedContent shouldContain """kotlin("jvm") version "2.2.21""""
+ updatedContent shouldContain """id("io.spine.validation") version "2.0.0-TEST""""
+ }
+
+ @Test
+ fun `handle multiple spaces between id and version`() {
+ buildFile.writeText("""
+ plugins {
+ id("io.spine.validation") version "1.0.0"
+ }
+ """.trimIndent())
+
+ val project = ProjectBuilder.builder().build()
+ val task = project.tasks.register("updatePluginVersion", UpdatePluginVersion::class.java) {
+ directory.set(tempDir)
+ version.set("2.0.0-TEST")
+ pluginId.set("io.spine.validation")
+ }
+
+ task.get().update()
+
+ val updatedContent = buildFile.readText()
+ updatedContent shouldContain """id("io.spine.validation") version "2.0.0-TEST""""
+ }
+}
diff --git a/buildSrc/src/test/kotlin/io/spine/gradle/publish/SpinePublishingTest.kt b/buildSrc/src/test/kotlin/io/spine/gradle/publish/SpinePublishingTest.kt
new file mode 100644
index 0000000000..37f58e0403
--- /dev/null
+++ b/buildSrc/src/test/kotlin/io/spine/gradle/publish/SpinePublishingTest.kt
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2026, TeamDev. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Redistribution and use in source and/or binary forms, with or without
+ * modification, must retain the above copyright notice and the following
+ * disclaimer.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package io.spine.gradle.publish
+
+import io.kotest.assertions.throwables.shouldThrow
+import io.kotest.matchers.collections.shouldContain
+import io.kotest.matchers.collections.shouldHaveSize
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.string.shouldContain
+import io.spine.gradle.repo.Repository
+import org.gradle.api.Project
+import org.gradle.kotlin.dsl.create
+import org.gradle.testfixtures.ProjectBuilder
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.DisplayName
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+
+@DisplayName("`SpinePublishing` should")
+class SpinePublishingTest {
+
+ private lateinit var project: Project
+ private lateinit var extension: SpinePublishing
+
+ @BeforeEach
+ fun setUp() {
+ project = ProjectBuilder.builder().build()
+ extension = project.spinePublishing { }
+ }
+
+ @Nested
+ inner class `calculate 'artifactId'` {
+
+ @Test
+ fun `with default prefix for non-tool projects`() {
+ val subproject = ProjectBuilder.builder()
+ .withParent(project)
+ .withName("base")
+ .build()
+ subproject.group = "io.spine"
+
+ extension.artifactId(subproject) shouldBe "spine-base"
+ }
+
+ @Test
+ fun `with custom prefix for non-tool projects`() {
+ extension.artifactPrefix = "custom-"
+ val subproject = ProjectBuilder.builder()
+ .withParent(project)
+ .withName("core")
+ .build()
+ subproject.group = "io.spine"
+
+ extension.artifactId(subproject) shouldBe "custom-core"
+ }
+
+ @Test
+ fun `with tool prefix for tool projects`() {
+ extension.toolArtifactPrefix = "tool-"
+ val toolProject = ProjectBuilder.builder()
+ .withParent(project)
+ .withName("model-compiler")
+ .build()
+ toolProject.group = "io.spine.tools"
+
+ extension.artifactId(toolProject) shouldBe "tool-model-compiler"
+ }
+
+ @Test
+ fun `without prefix for tool projects if 'NONE' is specified`() {
+ extension.toolArtifactPrefix = "NONE"
+ val toolProject = ProjectBuilder.builder()
+ .withParent(project)
+ .withName("proto-js")
+ .build()
+ toolProject.group = "io.spine.tools"
+
+ extension.artifactId(toolProject) shouldBe "proto-js"
+ }
+
+ @Test
+ fun `throwing IllegalStateException if tool prefix is empty for tool projects`() {
+ extension.toolArtifactPrefix = ""
+ val toolProject = ProjectBuilder.builder()
+ .withParent(project)
+ .withName("tool")
+ .build()
+ toolProject.group = "io.spine.tools"
+
+ shouldThrow {
+ extension.artifactId(toolProject)
+ }
+ }
+ }
+
+ @Nested
+ inner class `validate configuration` {
+
+ @Test
+ fun `ensuring 'testJar' inclusions are published`() {
+ extension.modules = setOf("pub-module")
+ extension.testJar {
+ inclusions = setOf("non-pub-module")
+ }
+
+ val exception = shouldThrow {
+ extension.configured()
+ }
+ exception.message!! shouldContain "non-pub-module"
+ }
+
+ @Test
+ fun `ensuring 'customPublishing' is not misused with modules`() {
+ extension.modules = setOf("some-module")
+ extension.customPublishing = true
+
+ val exception = shouldThrow {
+ extension.configured()
+ }
+ exception.message!! shouldContain "customPublishing"
+ }
+
+ @Test
+ fun `ensuring modules are not duplicated in root and subproject`() {
+ val rootProject = project
+ val subproject = ProjectBuilder.builder()
+ .withParent(rootProject)
+ .withName("sub")
+ .build()
+
+ // Root project already has the 'spinePublishing' extension created in 'setUp'.
+ // Let's use it instead of creating a second one with a different name.
+ extension.modules = setOf("sub")
+
+ // Subproject's extension must be named 'SpinePublishing'
+ // to be found by SpinePublishing::class.java.simpleName
+ val extensionName = SpinePublishing::class.java.simpleName
+ val subExtension = subproject.extensions.create(extensionName, subproject)
+
+ val exception = shouldThrow {
+ subExtension.configured()
+ }
+ exception.message!! shouldContain "already configured in a root project"
+ }
+ }
+
+ @Nested
+ inner class `identify 'projectsToPublish'` {
+
+ @Test
+ fun `as the project itself if no modules are specified`() {
+ val projects = extension.invokeProjectsToPublish()
+ projects shouldHaveSize 1
+ projects shouldContain project
+ }
+
+ @Test
+ fun `as the specified modules`() {
+ val sub1 = ProjectBuilder.builder().withParent(project).withName("sub1").build()
+ val sub2 = ProjectBuilder.builder().withParent(project).withName("sub2").build()
+
+ extension.modules = setOf("sub1", "sub2")
+
+ val projects = extension.invokeProjectsToPublish()
+ projects shouldHaveSize 2
+ projects shouldContain sub1
+ projects shouldContain sub2
+ }
+
+ @Test
+ fun `including modules with custom publishing`() {
+ val sub1 = ProjectBuilder.builder().withParent(project).withName("sub1").build()
+ val sub2 = ProjectBuilder.builder().withParent(project).withName("sub2").build()
+
+ extension.modules = setOf("sub1")
+ extension.modulesWithCustomPublishing = setOf("sub2")
+
+ val projects = extension.invokeProjectsToPublish()
+ projects shouldHaveSize 2
+ projects shouldContain sub1
+ projects shouldContain sub2
+ }
+ }
+
+ @Nested
+ inner class `resolve 'publishTo' repositories` {
+
+ @Test
+ fun `from the extension itself if destinations are initialized`() {
+ val repo = Repository(
+ "test-repo",
+ "https://example.com/release",
+ "https://example.com/snapshot"
+ )
+ extension.destinations = setOf(repo)
+
+ val repos = project.invokePublishTo(extension)
+ repos shouldHaveSize 1
+ repos shouldContain repo
+ }
+
+ @Test
+ fun `from the parent project if not specified locally`() {
+ val repo = Repository(
+ "parent-repo",
+ "https://example.com/release",
+ "https://example.com/snapshot"
+ )
+ // Root project has its extension named 'spinePublishing' from setUp.
+ extension.destinations = setOf(repo)
+
+ val subproject = ProjectBuilder.builder().withParent(project).withName("sub").build()
+ // Subproject has no local 'spinePublishing' extension.
+
+ val repos = subproject.invokePublishTo(extension)
+ repos shouldHaveSize 1
+ repos shouldContain repo
+ }
+ }
+}
+
+/**
+ * Accesses private/internal methods of [SpinePublishing] for testing purposes.
+ */
+private fun SpinePublishing.invokeProjectsToPublish(): Collection {
+ val method = SpinePublishing::class.java.getDeclaredMethod("projectsToPublish")
+ method.isAccessible = true
+ @Suppress("UNCHECKED_CAST")
+ return method.invoke(this) as Collection
+}
+
+private fun Project.invokePublishTo(extension: SpinePublishing): Set {
+ val method = SpinePublishing::class.java.getDeclaredMethod("publishTo", Project::class.java)
+ method.isAccessible = true
+ @Suppress("UNCHECKED_CAST")
+ return method.invoke(extension, this) as Set
+}
diff --git a/config b/config
index 21366d370b..3986ebdd1e 160000
--- a/config
+++ b/config
@@ -1 +1 @@
-Subproject commit 21366d370b26c28690a42b313c172d6dbb4f4d36
+Subproject commit 3986ebdd1ebece40e9433122ab9d46b4253fca03
diff --git a/dependencies.md b/dependencies.md
index 41fc1053f1..e6a0426694 100644
--- a/dependencies.md
+++ b/dependencies.md
@@ -1,6 +1,6 @@
-# Dependencies of `io.spine:spine-annotations:2.0.0-SNAPSHOT.386`
+# Dependencies of `io.spine:spine-annotations:2.0.0-SNAPSHOT.387`
## Runtime
1. **Group** : org.jetbrains. **Name** : annotations. **Version** : 26.0.2.
@@ -681,7 +681,7 @@
* **Project URL:** [http://jspecify.org/](http://jspecify.org/)
* **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
-1. **Group** : org.junit. **Name** : junit-bom. **Version** : 6.0.0.
+1. **Group** : org.junit. **Name** : junit-bom. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
@@ -689,27 +689,27 @@
* **Project URL:** [https://junit-pioneer.org/](https://junit-pioneer.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-api. **Version** : 6.0.0.
+1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-api. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-engine. **Version** : 6.0.0.
+1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-engine. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-params. **Version** : 6.0.0.
+1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-params. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.platform. **Name** : junit-platform-commons. **Version** : 6.0.0.
+1. **Group** : org.junit.platform. **Name** : junit-platform-commons. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.platform. **Name** : junit-platform-engine. **Version** : 6.0.0.
+1. **Group** : org.junit.platform. **Name** : junit-platform-engine. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.platform. **Name** : junit-platform-launcher. **Version** : 6.0.0.
+1. **Group** : org.junit.platform. **Name** : junit-platform-launcher. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
@@ -764,14 +764,14 @@
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Mon Mar 23 19:28:01 WET 2026** using
+This report was generated on **Fri Apr 03 16:59:29 WEST 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
-# Dependencies of `io.spine:spine-base:2.0.0-SNAPSHOT.386`
+# Dependencies of `io.spine:spine-base:2.0.0-SNAPSHOT.387`
## Runtime
1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2.
@@ -909,11 +909,11 @@ This report was generated on **Mon Mar 23 19:28:01 WET 2026** using
* **Project URL:** [https://github.com/google/gson](https://github.com/google/gson)
* **License:** [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0.txt)
-1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing. **Version** : 2.3.0.
+1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing. **Version** : 2.3.6.
* **Project URL:** [https://goo.gle/ksp](https://goo.gle/ksp)
* **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
-1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-api. **Version** : 2.3.0.
+1. **Group** : com.google.devtools.ksp. **Name** : symbol-processing-api. **Version** : 2.3.6.
* **Project URL:** [https://goo.gle/ksp](https://goo.gle/ksp)
* **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
@@ -1533,7 +1533,7 @@ This report was generated on **Mon Mar 23 19:28:01 WET 2026** using
* **Project URL:** [http://jspecify.org/](http://jspecify.org/)
* **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
-1. **Group** : org.junit. **Name** : junit-bom. **Version** : 6.0.0.
+1. **Group** : org.junit. **Name** : junit-bom. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
@@ -1541,27 +1541,27 @@ This report was generated on **Mon Mar 23 19:28:01 WET 2026** using
* **Project URL:** [https://junit-pioneer.org/](https://junit-pioneer.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-api. **Version** : 6.0.0.
+1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-api. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-engine. **Version** : 6.0.0.
+1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-engine. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-params. **Version** : 6.0.0.
+1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-params. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.platform. **Name** : junit-platform-commons. **Version** : 6.0.0.
+1. **Group** : org.junit.platform. **Name** : junit-platform-commons. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.platform. **Name** : junit-platform-engine. **Version** : 6.0.0.
+1. **Group** : org.junit.platform. **Name** : junit-platform-engine. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.platform. **Name** : junit-platform-launcher. **Version** : 6.0.0.
+1. **Group** : org.junit.platform. **Name** : junit-platform-launcher. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
@@ -1616,14 +1616,14 @@ This report was generated on **Mon Mar 23 19:28:01 WET 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Mon Mar 23 19:28:01 WET 2026** using
+This report was generated on **Fri Apr 03 16:59:29 WEST 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
-# Dependencies of `io.spine:spine-environment:2.0.0-SNAPSHOT.386`
+# Dependencies of `io.spine:spine-environment:2.0.0-SNAPSHOT.387`
## Runtime
1. **Group** : com.google.code.findbugs. **Name** : jsr305. **Version** : 3.0.2.
@@ -2363,7 +2363,7 @@ This report was generated on **Mon Mar 23 19:28:01 WET 2026** using
* **Project URL:** [http://jspecify.org/](http://jspecify.org/)
* **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
-1. **Group** : org.junit. **Name** : junit-bom. **Version** : 6.0.0.
+1. **Group** : org.junit. **Name** : junit-bom. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
@@ -2371,27 +2371,27 @@ This report was generated on **Mon Mar 23 19:28:01 WET 2026** using
* **Project URL:** [https://junit-pioneer.org/](https://junit-pioneer.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-api. **Version** : 6.0.0.
+1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-api. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-engine. **Version** : 6.0.0.
+1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-engine. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-params. **Version** : 6.0.0.
+1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-params. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.platform. **Name** : junit-platform-commons. **Version** : 6.0.0.
+1. **Group** : org.junit.platform. **Name** : junit-platform-commons. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.platform. **Name** : junit-platform-engine. **Version** : 6.0.0.
+1. **Group** : org.junit.platform. **Name** : junit-platform-engine. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.platform. **Name** : junit-platform-launcher. **Version** : 6.0.0.
+1. **Group** : org.junit.platform. **Name** : junit-platform-launcher. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
@@ -2446,14 +2446,14 @@ This report was generated on **Mon Mar 23 19:28:01 WET 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Mon Mar 23 19:28:02 WET 2026** using
+This report was generated on **Fri Apr 03 16:59:29 WEST 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
-# Dependencies of `io.spine:spine-format:2.0.0-SNAPSHOT.386`
+# Dependencies of `io.spine:spine-format:2.0.0-SNAPSHOT.387`
## Runtime
1. **Group** : com.fasterxml.jackson. **Name** : jackson-bom. **Version** : 2.20.0.
@@ -3269,7 +3269,7 @@ This report was generated on **Mon Mar 23 19:28:02 WET 2026** using
* **Project URL:** [http://jspecify.org/](http://jspecify.org/)
* **License:** [The Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
-1. **Group** : org.junit. **Name** : junit-bom. **Version** : 6.0.0.
+1. **Group** : org.junit. **Name** : junit-bom. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
@@ -3277,27 +3277,27 @@ This report was generated on **Mon Mar 23 19:28:02 WET 2026** using
* **Project URL:** [https://junit-pioneer.org/](https://junit-pioneer.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-api. **Version** : 6.0.0.
+1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-api. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-engine. **Version** : 6.0.0.
+1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-engine. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-params. **Version** : 6.0.0.
+1. **Group** : org.junit.jupiter. **Name** : junit-jupiter-params. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.platform. **Name** : junit-platform-commons. **Version** : 6.0.0.
+1. **Group** : org.junit.platform. **Name** : junit-platform-commons. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.platform. **Name** : junit-platform-engine. **Version** : 6.0.0.
+1. **Group** : org.junit.platform. **Name** : junit-platform-engine. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
-1. **Group** : org.junit.platform. **Name** : junit-platform-launcher. **Version** : 6.0.0.
+1. **Group** : org.junit.platform. **Name** : junit-platform-launcher. **Version** : 6.0.3.
* **Project URL:** [https://junit.org/](https://junit.org/)
* **License:** [Eclipse Public License v2.0](https://www.eclipse.org/legal/epl-v20.html)
@@ -3356,6 +3356,6 @@ This report was generated on **Mon Mar 23 19:28:02 WET 2026** using
The dependencies distributed under several licenses, are used according their commercial-use-friendly license.
-This report was generated on **Mon Mar 23 19:28:01 WET 2026** using
+This report was generated on **Fri Apr 03 16:59:29 WEST 2026** using
[Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under
[Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE).
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 93bcb6923a..49ad30c59b 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -9,7 +9,7 @@ org.gradle.parallel=true
#org.gradle.caching=true
# Dokka plugin eats more memory than usual. Therefore, all builds should have enough.
-org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:+UseParallelGC
+org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:+UseParallelGC -Dfile.encoding=UTF-8
# suppress inspection "UnusedProperty"
# The below property enables generation of XML reports for tests.
diff --git a/pom.xml b/pom.xml
index d3dee4449b..4927f6f70e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@ all modules and does not describe the project structure per-subproject.
-->
io.spine
base-libraries
-2.0.0-SNAPSHOT.386
+2.0.0-SNAPSHOT.387
2015
@@ -164,7 +164,7 @@ all modules and does not describe the project structure per-subproject.
org.junit
junit-bom
- 6.0.0
+ 6.0.3
test
@@ -176,19 +176,19 @@ all modules and does not describe the project structure per-subproject.
org.junit.jupiter
junit-jupiter-api
- 6.0.0
+ 6.0.3
test
org.junit.jupiter
junit-jupiter-engine
- 6.0.0
+ 6.0.3
test
org.junit.jupiter
junit-jupiter-params
- 6.0.0
+ 6.0.3
test
@@ -200,12 +200,12 @@ all modules and does not describe the project structure per-subproject.
com.google.devtools.ksp
symbol-processing
- 2.3.0
+ 2.3.6
com.google.devtools.ksp
symbol-processing-api
- 2.3.0
+ 2.3.6
com.google.errorprone
diff --git a/version.gradle.kts b/version.gradle.kts
index b1478b8ba9..6c1deba47d 100644
--- a/version.gradle.kts
+++ b/version.gradle.kts
@@ -24,4 +24,4 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-val versionToPublish: String by extra("2.0.0-SNAPSHOT.386")
+val versionToPublish: String by extra("2.0.0-SNAPSHOT.387")