From aa550e49c27d8b744e50e21cd59a32961f2d1c92 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Sat, 27 Jun 2026 00:23:04 +0100 Subject: [PATCH 1/8] Bump version -> `2.0.0-SNAPSHOT.418` --- version.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle.kts b/version.gradle.kts index c3a481122..623e94ecc 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.417") +val versionToPublish: String by extra("2.0.0-SNAPSHOT.418") From bf023cbf137da0ff3d85f9b76f26c62ba9d74b74 Mon Sep 17 00:00:00 2001 From: Alexander Yevsyukov Date: Sat, 27 Jun 2026 00:23:12 +0100 Subject: [PATCH 2/8] Update `config` --- .agents/_TOC.md | 16 - .agents/advanced-safety-rules.md | 6 - .agents/coding-guidelines.md | 39 -- .agents/common-tasks.md | 6 - .agents/documentation-guidelines.md | 14 - .agents/documentation-tasks.md | 20 - .agents/guidelines | 1 + .agents/project-structure-expectations.md | 21 - .agents/project.md | 1 + .agents/quick-reference-card.md | 10 - .agents/refactoring-guidelines.md | 3 - .agents/running-builds.md | 18 - .agents/safety-rules.md | 7 - .agents/scripts | 1 + .agents/shared | 1 + .agents/skills | 1 + .agents/skills/writer/SKILL.md | 51 --- .agents/skills/writer/agents/openai.yaml | 5 - .../writer/assets/templates/doc-page.md | 23 -- .../writer/assets/templates/kdoc-example.md | 11 - .../assets/templates/kotlin-java-example.md | 13 - .agents/testing.md | 8 - .agents/version-policy.md | 30 -- .agents/widow-runt-orphan.jpg | Bin 54071 -> 0 bytes .claude/agents | 1 + .claude/commands | 1 + .claude/settings.json | 102 +++++ .claude/settings.local.json | 21 +- .claude/skills | 1 + .codecov.yml | 6 +- .github/copilot-instructions.md | 49 ++- .github/workflows/build-on-ubuntu.yml | 72 +++- .github/workflows/build-on-windows.yml | 19 +- .github/workflows/check-links.yml | 216 ++++++++++ .github/workflows/ensure-reports-updated.yml | 26 +- .../workflows/gradle-wrapper-validation.yml | 6 +- .github/workflows/increment-guard.yml | 93 ++++- .github/workflows/publish.yml | 24 +- ...move-obsolete-artifacts-from-packages.yaml | 6 +- .github/workflows/revalidate-versions.yml | 57 +++ .github/workflows/secret-scan.yml | 70 ++++ .gitignore | 96 ++++- .gitmodules | 6 + .idea/inspectionProfiles/Project_Default.xml | 4 +- .idea/kotlinc.xml | 6 +- .idea/live-templates/README.md | 6 +- .idea/live-templates/User.xml | 2 +- .idea/misc.xml | 32 +- .junie/guidelines.md | 4 +- .junie/skills | 1 + AGENTS.md | 134 +++++- CLAUDE.md | 29 +- build.gradle.kts | 10 +- buildSrc/build.gradle.kts | 16 +- buildSrc/quality/pmd.xml | 4 +- buildSrc/src/main/kotlin/BuildExtensions.kt | 19 +- buildSrc/src/main/kotlin/DokkaExts.kt | 7 +- buildSrc/src/main/kotlin/LicenseSettings.kt | 50 +-- buildSrc/src/main/kotlin/Strings.kt | 4 +- .../src/main/kotlin/config-tester.gradle.kts | 9 +- .../kotlin/io/spine/dependency/Dependency.kt | 7 +- .../io/spine/dependency/boms/BomsPlugin.kt | 4 +- .../spine/dependency/build/AnimalSniffer.kt | 4 +- .../dependency/build/CheckerFramework.kt | 4 +- .../kotlin/io/spine/dependency/build/Dokka.kt | 1 - .../io/spine/dependency/build/ErrorProne.kt | 4 +- .../io/spine/dependency/build/GradleDoctor.kt | 4 +- .../kotlin/io/spine/dependency/build/Ksp.kt | 4 +- .../dependency/build/PluginPublishPlugin.kt | 4 +- .../kotlin/io/spine/dependency/build/Pmd.kt | 4 +- .../io/spine/dependency/kotlinx/AtomicFu.kt | 2 +- .../io/spine/dependency/kotlinx/Coroutines.kt | 2 +- .../io/spine/dependency/kotlinx/DateTime.kt | 4 +- .../spine/dependency/kotlinx/Serialization.kt | 2 +- .../kotlin/io/spine/dependency/lib/Aedile.kt | 4 +- .../kotlin/io/spine/dependency/lib/Asm.kt | 4 +- .../kotlin/io/spine/dependency/lib/Auto.kt | 4 +- .../io/spine/dependency/lib/Caffeine.kt | 4 +- .../kotlin/io/spine/dependency/lib/Clikt.kt | 4 +- .../io/spine/dependency/lib/CommonsCli.kt | 6 +- .../io/spine/dependency/lib/CommonsCodec.kt | 4 +- .../io/spine/dependency/lib/CommonsLogging.kt | 4 +- .../io/spine/dependency/lib/Firebase.kt | 4 +- .../kotlin/io/spine/dependency/lib/Flogger.kt | 4 +- .../io/spine/dependency/lib/GoogleApis.kt | 10 +- .../io/spine/dependency/lib/GoogleCloud.kt | 21 +- .../kotlin/io/spine/dependency/lib/Grpc.kt | 4 +- .../io/spine/dependency/lib/GrpcKotlin.kt | 4 +- .../kotlin/io/spine/dependency/lib/Gson.kt | 4 +- .../kotlin/io/spine/dependency/lib/Guava.kt | 4 +- .../io/spine/dependency/lib/HttpClient.kt | 4 +- .../io/spine/dependency/lib/IntelliJ.kt | 6 +- .../kotlin/io/spine/dependency/lib/J2ObjC.kt | 4 +- .../kotlin/io/spine/dependency/lib/Jackson.kt | 6 +- .../io/spine/dependency/lib/JavaDiffUtils.kt | 4 +- .../dependency/lib/JetBrainsAnnotations.kt | 2 +- .../kotlin/io/spine/dependency/lib/Kotlin.kt | 10 +- .../io/spine/dependency/lib/KotlinPoet.kt | 4 +- .../io/spine/dependency/lib/KotlinSemver.kt | 4 +- .../kotlin/io/spine/dependency/lib/Log4j2.kt | 4 +- .../kotlin/io/spine/dependency/lib/Netty.kt | 4 +- .../kotlin/io/spine/dependency/lib/Okio.kt | 4 +- .../dependency/lib/PalantirJavaFormat.kt | 4 +- .../io/spine/dependency/lib/PerfMark.kt | 34 ++ .../kotlin/io/spine/dependency/lib/Plexus.kt | 4 +- .../io/spine/dependency/lib/Protobuf.kt | 6 +- .../kotlin/io/spine/dependency/lib/Roaster.kt | 4 +- .../kotlin/io/spine/dependency/lib/Slf4J.kt | 5 +- .../kotlin/io/spine/dependency/local/Base.kt | 6 +- .../io/spine/dependency/local/Compiler.kt | 6 +- .../io/spine/dependency/local/CoreJvm.kt | 2 +- .../spine/dependency/local/CoreJvmCompiler.kt | 8 +- .../io/spine/dependency/local/Logging.kt | 4 +- .../io/spine/dependency/local/McJava.kt | 79 ---- .../io/spine/dependency/local/ProtoData.kt | 178 -------- .../io/spine/dependency/local/ProtoTap.kt | 6 +- .../kotlin/io/spine/dependency/local/Time.kt | 8 +- .../io/spine/dependency/local/ToolBase.kt | 6 +- .../io/spine/dependency/local/Validation.kt | 12 +- .../kotlin/io/spine/dependency/test/JUnit.kt | 6 +- .../kotlin/io/spine/dependency/test/Jacoco.kt | 4 +- .../dependency/test/KotlinCompileTesting.kt | 4 +- .../kotlin/io/spine/dependency/test/Kover.kt | 2 +- .../spine/dependency/test/Testcontainers.kt | 37 ++ .../kotlin/io/spine/dependency/test/Truth.kt | 4 +- .../src/main/kotlin/io/spine/gradle/Cli.kt | 25 +- .../kotlin/io/spine/gradle/ConfigTester.kt | 20 +- .../main/kotlin/io/spine/gradle/RunGradle.kt | 6 +- .../kotlin/io/spine/gradle/SpineTaskGroup.kt | 49 +++ .../io/spine/gradle/VersionComparator.kt | 115 ++++++ .../io/spine/gradle/VersionGradleFile.kt | 157 +++++++ .../io/spine/gradle/dart/DartEnvironment.kt | 6 +- .../io/spine/gradle/dart/DartExtension.kt | 6 +- .../kotlin/io/spine/gradle/dart/task/Build.kt | 9 +- .../io/spine/gradle/dart/task/DartTasks.kt | 19 +- .../spine/gradle/dart/task/IntegrationTest.kt | 8 +- .../io/spine/gradle/dart/task/Publish.kt | 9 +- .../kotlin/io/spine/gradle/git/Repository.kt | 116 +++++- .../github/pages/RepositoryExtensions.kt | 15 +- .../io/spine/gradle/github/pages/SshKey.kt | 4 +- .../io/spine/gradle/github/pages/TaskName.kt | 4 +- .../gradle/github/pages/UpdateGitHubPages.kt | 19 +- .../kotlin/io/spine/gradle/java/Linters.kt | 4 +- .../main/kotlin/io/spine/gradle/java/Tasks.kt | 4 +- .../gradle/javadoc/ExcludeInternalDoclet.kt | 12 +- .../spine/gradle/javascript/JsEnvironment.kt | 4 +- .../io/spine/gradle/javascript/JsExtension.kt | 6 +- .../spine/gradle/javascript/task/Assemble.kt | 13 +- .../io/spine/gradle/javascript/task/Check.kt | 11 +- .../io/spine/gradle/javascript/task/Clean.kt | 7 +- .../gradle/javascript/task/IntegrationTest.kt | 7 +- .../spine/gradle/javascript/task/JsTasks.kt | 22 +- .../gradle/javascript/task/LicenseReport.kt | 5 +- .../spine/gradle/javascript/task/Publish.kt | 11 +- .../spine/gradle/javascript/task/Webpack.kt | 5 +- .../gradle/publish/CheckVersionIncrement.kt | 163 ++++++-- .../publish/CustomPublicationHandler.kt | 4 +- .../io/spine/gradle/publish/GitHubPackages.kt | 8 +- .../io/spine/gradle/publish/IncrementGuard.kt | 195 ++++++--- .../kotlin/io/spine/gradle/publish/JarDsl.kt | 4 +- .../io/spine/gradle/publish/MavenMetadata.kt | 84 ++++ .../gradle/publish/PublicationHandler.kt | 20 +- .../io/spine/gradle/publish/PublishingExts.kt | 41 +- .../spine/gradle/publish/SpinePublishing.kt | 2 +- .../io/spine/gradle/repo/Repositories.kt | 75 +++- .../kotlin/io/spine/gradle/repo/Repository.kt | 8 +- .../gradle/report/coverage/KoverConfig.kt | 390 ++++++++++++++++++ .../gradle/report/coverage/SiblingCoverage.kt | 114 +++++ .../gradle/report/license/LicenseReporter.kt | 31 +- .../report/license/ModuleDataExtensions.kt | 4 +- .../io/spine/gradle/report/license/Paths.kt | 22 +- .../gradle/report/pom/DependencyWriter.kt | 43 +- .../spine/gradle/report/pom/PomGenerator.kt | 14 +- .../gradle/report/pom/ScopedDependency.kt | 36 +- .../kotlin/io/spine/gradle/testing/Tasks.kt | 7 +- .../spine/gradle/testing/TestKitCoverage.kt | 159 +++++++ .../src/main/kotlin/jacoco-kmm-jvm.gradle.kts | 17 +- .../main/kotlin/jacoco-kotlin-jvm.gradle.kts | 15 +- .../src/main/kotlin/jvm-module.gradle.kts | 5 +- .../src/main/kotlin/kmp-module.gradle.kts | 2 +- .../main/kotlin/uber-jar-module.gradle.kts | 7 +- .../src/main/kotlin/write-manifest.gradle.kts | 6 +- .../io/spine/gradle/VersionComparatorSpec.kt | 87 ++++ .../io/spine/gradle/VersionGradleFileSpec.kt | 86 ++++ .../gradle/publish/IncrementGuardTest.kt | 222 ++++++++++ .../spine/gradle/publish/MavenMetadataSpec.kt | 53 +++ .../report/coverage/FileExtensionsTest.kt | 128 ++++++ .../license/DependencyReportOutputTest.kt | 104 +++++ .../gradle/report/pom/DependencyWriterSpec.kt | 312 ++++++++++++++ config | 2 +- docs/project.md | 18 + gradle.properties | 26 +- gradle/wrapper/gradle-wrapper.jar | Bin 48966 -> 48462 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 2 +- gradlew.bat | 31 +- init-submodules | 99 +++++ 197 files changed, 4287 insertions(+), 1200 deletions(-) delete mode 100644 .agents/_TOC.md delete mode 100644 .agents/advanced-safety-rules.md delete mode 100644 .agents/coding-guidelines.md delete mode 100644 .agents/common-tasks.md delete mode 100644 .agents/documentation-guidelines.md delete mode 100644 .agents/documentation-tasks.md create mode 120000 .agents/guidelines delete mode 100644 .agents/project-structure-expectations.md create mode 120000 .agents/project.md delete mode 100644 .agents/quick-reference-card.md delete mode 100644 .agents/refactoring-guidelines.md delete mode 100644 .agents/running-builds.md delete mode 100644 .agents/safety-rules.md create mode 120000 .agents/scripts create mode 160000 .agents/shared create mode 120000 .agents/skills delete mode 100644 .agents/skills/writer/SKILL.md delete mode 100644 .agents/skills/writer/agents/openai.yaml delete mode 100644 .agents/skills/writer/assets/templates/doc-page.md delete mode 100644 .agents/skills/writer/assets/templates/kdoc-example.md delete mode 100644 .agents/skills/writer/assets/templates/kotlin-java-example.md delete mode 100644 .agents/testing.md delete mode 100644 .agents/version-policy.md delete mode 100644 .agents/widow-runt-orphan.jpg create mode 120000 .claude/agents create mode 120000 .claude/commands create mode 100644 .claude/settings.json create mode 120000 .claude/skills create mode 100644 .github/workflows/check-links.yml create mode 100644 .github/workflows/revalidate-versions.yml create mode 100644 .github/workflows/secret-scan.yml create mode 120000 .junie/skills create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/lib/PerfMark.kt delete mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/local/McJava.kt delete mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/local/ProtoData.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/dependency/test/Testcontainers.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/gradle/SpineTaskGroup.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/gradle/VersionComparator.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/gradle/VersionGradleFile.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/gradle/publish/MavenMetadata.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/KoverConfig.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/gradle/report/coverage/SiblingCoverage.kt create mode 100644 buildSrc/src/main/kotlin/io/spine/gradle/testing/TestKitCoverage.kt create mode 100644 buildSrc/src/test/kotlin/io/spine/gradle/VersionComparatorSpec.kt create mode 100644 buildSrc/src/test/kotlin/io/spine/gradle/VersionGradleFileSpec.kt create mode 100644 buildSrc/src/test/kotlin/io/spine/gradle/publish/IncrementGuardTest.kt create mode 100644 buildSrc/src/test/kotlin/io/spine/gradle/publish/MavenMetadataSpec.kt create mode 100644 buildSrc/src/test/kotlin/io/spine/gradle/report/coverage/FileExtensionsTest.kt create mode 100644 buildSrc/src/test/kotlin/io/spine/gradle/report/license/DependencyReportOutputTest.kt create mode 100644 buildSrc/src/test/kotlin/io/spine/gradle/report/pom/DependencyWriterSpec.kt create mode 100644 docs/project.md create mode 100755 init-submodules diff --git a/.agents/_TOC.md b/.agents/_TOC.md deleted file mode 100644 index 065df133a..000000000 --- a/.agents/_TOC.md +++ /dev/null @@ -1,16 +0,0 @@ -# Table of Contents - -1. [Quick Reference Card](quick-reference-card.md) -2. [Project overview](project-overview.md) -3. [Coding guidelines](coding-guidelines.md) -4. [Documentation & comments](documentation-guidelines.md) -5. [Documentation tasks](documentation-tasks.md) -6. [Running builds](running-builds.md) -7. [Version policy](version-policy.md) -8. [Project structure expectations](project-structure-expectations.md) -9. [Testing](testing.md) -10. [Safety rules](safety-rules.md) -11. [Advanced safety rules](advanced-safety-rules.md) -12. [Refactoring guidelines](refactoring-guidelines.md) -13. [Common tasks](common-tasks.md) -14. [Java to Kotlin conversion](java-kotlin-conversion.md) diff --git a/.agents/advanced-safety-rules.md b/.agents/advanced-safety-rules.md deleted file mode 100644 index e4105813f..000000000 --- a/.agents/advanced-safety-rules.md +++ /dev/null @@ -1,6 +0,0 @@ -# 🚨 Advanced safety rules - -- Do **not** auto-update external dependencies without explicit request. -- Do **not** inject analytics or telemetry code. -- Flag any usage of unsafe constructs (e.g., reflection, I/O on the main thread). -- Avoid generating blocking calls inside coroutines. diff --git a/.agents/coding-guidelines.md b/.agents/coding-guidelines.md deleted file mode 100644 index 3297d8ae4..000000000 --- a/.agents/coding-guidelines.md +++ /dev/null @@ -1,39 +0,0 @@ -# 🧾 Coding guidelines - -## Core principles - -- Adhere to [Spine Event Engine Documentation][spine-docs] for coding style. -- Generate code that compiles cleanly and passes static analysis. -- Respect existing architecture, naming conventions, and project structure. -- Write clear, incremental commits with descriptive messages. -- Include automated tests for any code change that alters functionality. - -## Kotlin best practices - -### βœ… Prefer -- **Kotlin idioms** over Java-style approaches: - - Extension functions - - `when` expressions - - Smart casts - - Data classes and sealed classes - - Immutable data structures -- **Simple nouns** over composite nouns (`user` > `userAccount`) -- **Generic parameters** over explicit variable types (`val list = mutableList()`) -- **Java interop annotations** only when needed (`@file:JvmName`, `@JvmStatic`) -- **Kotlin DSL** for Gradle files - -### ❌ Avoid -- Mutable data structures -- Java-style verbosity (builders with setters) -- Redundant null checks (`?.let` misuse) -- Using `!!` unless clearly justified -- Type names in variable names (`userObject`, `itemList`) -- String duplication (use constants in companion objects) -- Mixing Groovy and Kotlin DSLs in build logic -- Reflection unless specifically requested - -## Text formatting - - βœ… Remove double empty lines in the code. - - βœ… Remove trailing space characters in the code. - -[spine-docs]: https://github.com/SpineEventEngine/documentation/wiki diff --git a/.agents/common-tasks.md b/.agents/common-tasks.md deleted file mode 100644 index 5ee954d83..000000000 --- a/.agents/common-tasks.md +++ /dev/null @@ -1,6 +0,0 @@ -# πŸ“‹ Common tasks - -- **Adding a new dependency**: Update relevant files in `buildSrc` directory. -- **Creating a new module**: Follow existing module structure patterns. -- **Documentation**: Use KDoc style for public and internal APIs. -- **Testing**: Create comprehensive tests using Kotest assertions. diff --git a/.agents/documentation-guidelines.md b/.agents/documentation-guidelines.md deleted file mode 100644 index 6c9c1bae7..000000000 --- a/.agents/documentation-guidelines.md +++ /dev/null @@ -1,14 +0,0 @@ -# Documentation & comments - -## Commenting guidelines -- Avoid inline comments in production code unless necessary. -- Inline comments are helpful in tests. -- When using TODO comments, follow the format on the [dedicated page][todo-comments]. -- File and directory names should be formatted as code. - -## Avoid widows, runts, orphans, or rivers - -Agents should **AVOID** text flow patters illustrated -on [this diagram](widow-runt-orphan.jpg). - -[todo-comments]: https://github.com/SpineEventEngine/documentation/wiki/TODO-comments diff --git a/.agents/documentation-tasks.md b/.agents/documentation-tasks.md deleted file mode 100644 index 8ac4660db..000000000 --- a/.agents/documentation-tasks.md +++ /dev/null @@ -1,20 +0,0 @@ -# πŸ“„ Documentation tasks - -1. Ensure all public and internal APIs have KDoc examples. -2. Add in-line code blocks for clarity in tests. -3. Convert inline API comments in Java to KDoc in Kotlin: - ```java - // Literal string to be inlined whenever a placeholder references a non-existent argument. - private final String missingArgumentMessage = "[MISSING ARGUMENT]"; - ``` - transforms to: - ```kotlin - /** - * Literal string to be inlined whenever a placeholder references a non-existent argument. - */ - private val missingArgumentMessage = "[MISSING ARGUMENT]" - ``` - -4. Javadoc -> KDoc conversion tasks: - - Remove `

` tags in the line with text: `"

This"` -> `"This"`. - - Replace `

` with empty line if the tag is the only text in the line. diff --git a/.agents/guidelines b/.agents/guidelines new file mode 120000 index 000000000..6f9d96637 --- /dev/null +++ b/.agents/guidelines @@ -0,0 +1 @@ +shared/guidelines \ No newline at end of file diff --git a/.agents/project-structure-expectations.md b/.agents/project-structure-expectations.md deleted file mode 100644 index 81b8e1a62..000000000 --- a/.agents/project-structure-expectations.md +++ /dev/null @@ -1,21 +0,0 @@ -# πŸ“ Project structure expectations - -```yaml -.github -buildSrc/ - - src/ - β”œβ”€β”€ main/ - β”‚ β”œβ”€β”€ kotlin/ # Kotlin source files - β”‚ └── java/ # Legacy Java code - β”œβ”€β”€ test/ - β”‚ └── kotlin/ # Unit and integration tests - build.gradle.kts # Kotlin-based build configuration - - -build.gradle.kts # Kotlin-based build configuration -settings.gradle.kts # Project structure and settings -README.md # Project overview -AGENTS.md # Entry point for LLM agent instructions -version.gradle.kts # Declares the project version. -``` diff --git a/.agents/project.md b/.agents/project.md new file mode 120000 index 000000000..7e0bf9bd3 --- /dev/null +++ b/.agents/project.md @@ -0,0 +1 @@ +../docs/project.md \ No newline at end of file diff --git a/.agents/quick-reference-card.md b/.agents/quick-reference-card.md deleted file mode 100644 index 6c25b9a7f..000000000 --- a/.agents/quick-reference-card.md +++ /dev/null @@ -1,10 +0,0 @@ -# πŸ“ Quick Reference Card - -``` -πŸ”‘ Key Information: -- Kotlin/Java project with CQRS architecture -- Use ChatGPT for documentation, Codex for code generation, GPT-4o for complex analysis -- Follow coding guidelines in Spine Event Engine docs -- Always include tests with code changes -- Version bump required for all PRs -``` diff --git a/.agents/refactoring-guidelines.md b/.agents/refactoring-guidelines.md deleted file mode 100644 index 191db49f5..000000000 --- a/.agents/refactoring-guidelines.md +++ /dev/null @@ -1,3 +0,0 @@ -# βš™οΈ Refactoring guidelines - -- Do NOT replace Kotest assertions with standard Kotlin's built-in test assertions. diff --git a/.agents/running-builds.md b/.agents/running-builds.md deleted file mode 100644 index db0338d6f..000000000 --- a/.agents/running-builds.md +++ /dev/null @@ -1,18 +0,0 @@ -# Running builds - -1. When modifying code, run: - ```bash - ./gradlew build - ``` - -2. If Protobuf (`.proto`) files are modified run: - ```bash - ./gradlew clean build - ``` - -3. Documentation-only changes in Kotlin or Java sources run: - ```bash - ./gradlew dokka - ``` - -4. Documentation-only changes do not require running tests! diff --git a/.agents/safety-rules.md b/.agents/safety-rules.md deleted file mode 100644 index 08e9b33d1..000000000 --- a/.agents/safety-rules.md +++ /dev/null @@ -1,7 +0,0 @@ -# Safety rules - -- βœ… All code must compile and pass static analysis. -- βœ… Do not auto-update external dependencies. -- ❌ Never use reflection or unsafe code without an explicit approval. -- ❌ No analytics or telemetry code. -- ❌ No blocking calls inside coroutines. diff --git a/.agents/scripts b/.agents/scripts new file mode 120000 index 000000000..96bf06e12 --- /dev/null +++ b/.agents/scripts @@ -0,0 +1 @@ +shared/scripts \ No newline at end of file diff --git a/.agents/shared b/.agents/shared new file mode 160000 index 000000000..bda77f6a1 --- /dev/null +++ b/.agents/shared @@ -0,0 +1 @@ +Subproject commit bda77f6a178018569f3977878624adde279b41ec diff --git a/.agents/skills b/.agents/skills new file mode 120000 index 000000000..f14734dde --- /dev/null +++ b/.agents/skills @@ -0,0 +1 @@ +shared/skills \ No newline at end of file diff --git a/.agents/skills/writer/SKILL.md b/.agents/skills/writer/SKILL.md deleted file mode 100644 index 5c720265b..000000000 --- a/.agents/skills/writer/SKILL.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -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 deleted file mode 100644 index 44eaa4e24..000000000 --- a/.agents/skills/writer/agents/openai.yaml +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index f405b71e1..000000000 --- a/.agents/skills/writer/assets/templates/doc-page.md +++ /dev/null @@ -1,23 +0,0 @@ -# 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 deleted file mode 100644 index fdbd9b6a0..000000000 --- a/.agents/skills/writer/assets/templates/kdoc-example.md +++ /dev/null @@ -1,11 +0,0 @@ -````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 deleted file mode 100644 index 5517516f5..000000000 --- a/.agents/skills/writer/assets/templates/kotlin-java-example.md +++ /dev/null @@ -1,13 +0,0 @@ -{{< 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/testing.md b/.agents/testing.md deleted file mode 100644 index f81bdbf3d..000000000 --- a/.agents/testing.md +++ /dev/null @@ -1,8 +0,0 @@ -# πŸ§ͺ Testing - -- Do not use mocks, use stubs. -- Prefer [Kotest assertions][kotest-assertions] over assertions from JUnit or Google Truth. -- Generate unit tests for APIs (handles edge cases/scenarios). -- Supply scaffolds for typical Kotlin patterns (`when`, sealed classes). - -[kotest-assertions]: https://kotest.io/docs/assertions/assertions.html diff --git a/.agents/version-policy.md b/.agents/version-policy.md deleted file mode 100644 index 65dc45749..000000000 --- a/.agents/version-policy.md +++ /dev/null @@ -1,30 +0,0 @@ -# Version policy - -## We use semver -The version of the project is kept in the `version.gradle.kts` file in the root of the project. - -The version numbers in these files follow the conventions of -[Semantic Versioning 2.0.0](https://semver.org/). - -## Quick checklist for versioning -1. Increment the patch version in `version.gradle.kts`. - Retain zero-padding if applicable: - - Example: `"2.0.0-SNAPSHOT.009"` β†’ `"2.0.0-SNAPSHOT.010"` -2. Commit the version bump separately with this comment: - ```text - Bump version β†’ `$newVersion` - ``` -3. Rebuild using `./gradlew clean build`. -4. Update `pom.xml`, `dependencies.md` and commit changes with: `Update dependency reports` - -Remember: PRs without version bumps will fail CI (conflict resolution detailed above). - -## Resolving conflicts in `version.gradle.kts` -A branch conflict over the version number should be resolved as described below. - * If a merged branch has a number which is less than that of the current branch, the version of - the current branch stays. - * If the merged branch has the number which is greater or equal to that of the current branch, - the number should be increased by one. - -## When to bump the version? - - When a new branch is created. diff --git a/.agents/widow-runt-orphan.jpg b/.agents/widow-runt-orphan.jpg deleted file mode 100644 index 284b02a47d57121b3fa0356a6805428ad2030c8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54071 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 "$GITHUB_OUTPUT" + else + echo "available=false" >> "$GITHUB_OUTPUT" + fi + + # On `push` (master) always upload β€” these runs are the only source of + # the `target: auto` baseline, so an absent token there is a + # misconfiguration that should fail loudly rather than silently stop + # refreshing coverage. On `pull_request`, skip when the token is absent: + # forked and Dependabot PRs run without secrets, and `fail_ci_if_error` + # would otherwise redden a healthy PR (coverage gating is meaningless + # there anyway). - name: Upload code coverage report + if: steps.codecov.outputs.available == 'true' || github.event_name == 'push' uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: false + fail_ci_if_error: true verbose: true diff --git a/.github/workflows/build-on-windows.yml b/.github/workflows/build-on-windows.yml index 4e6b57f1f..0a07cfab6 100644 --- a/.github/workflows/build-on-windows.yml +++ b/.github/workflows/build-on-windows.yml @@ -1,22 +1,24 @@ -name: Build under Windows +name: Windows CI on: pull_request jobs: build: - name: Build under Windows runs-on: windows-latest + name: Build on Windows steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: - submodules: 'true' + submodules: recursive + fetch-depth: 0 - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v5 with: java-version: 17 distribution: zulu - cache: gradle + + - uses: gradle/actions/setup-gradle@v6 # See: https://github.com/al-cheb/configure-pagefile-action - name: Configure Pagefile @@ -24,8 +26,7 @@ jobs: - name: Build project and run tests shell: cmd - # For the reason on `--no-daemon` see https://github.com/actions/cache/issues/454 - run: gradlew.bat build --stacktrace --no-daemon + run: gradlew.bat build --stacktrace # See: https://github.com/marketplace/actions/junit-report-action - name: Publish Test Report @@ -33,4 +34,4 @@ jobs: if: always() # always run even if the previous step fails with: report_paths: '**/build/test-results/**/TEST-*.xml' - require_tests: true # will fail workflow if test reports not found + require_tests: true diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml new file mode 100644 index 000000000..6755ff910 --- /dev/null +++ b/.github/workflows/check-links.yml @@ -0,0 +1,216 @@ +name: Check Links + +# Trigger only when the docs site, the link checker config, or this workflow +# itself changes β€” unrelated PRs do not need to pay the build+check cost. +on: + pull_request: + paths: + - 'docs/**' + - 'site/**' + - 'lychee.toml' + - '.github/workflows/check-links.yml' + workflow_dispatch: + +env: + HUGO_VERSION: 0.161.1 + LYCHEE_RELEASE: "lychee-x86_64-unknown-linux-gnu.tar.gz" + LYCHEE_VERSION_TAG: "lychee-v0.24.2" + # SHA256 of the above tarball, pinned at download time. Update alongside + # LYCHEE_VERSION_TAG whenever the binary is upgraded. + LYCHEE_SHA256: "1f4e0ef7f6554a6ed33dd7ac144fb2e1bbed98598e7af973042fc5cd43951c9a" + # Force Hugo to write its module cache where the cache step actually + # restores from. Hugo's default on Linux is `~/.cache/hugo_cache` + # (or `$TMPDIR/hugo_cache_$USER`), neither of which matches the + # `path: /tmp/hugo_cache` cache step below β€” without this env var, + # the cache would silently never hit. + HUGO_CACHEDIR: /tmp/hugo_cache + +jobs: + check-links: + runs-on: ubuntu-latest + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + steps: + - name: Checkout + uses: actions/checkout@v6 + + # Detect the Hugo site root (`docs/` or `site/`) by looking for a Hugo + # config file. Hugo config may live directly in the site root or in a + # `config/` or `config/_default/` subdirectory (both layouts are valid). + # Outputs `present=true|false` and `work_dir` (the directory where + # `npm ci` / `hugo` commands should run β€” either `$dir/_preview` for + # repos that use a separate preview sub-tree, or `$dir` for repos whose + # Node/Hugo setup lives at the site root). + # When neither directory has a Hugo config, the job short-circuits to a + # success so that this shared workflow stays green on repos that do not + # host a Hugo site at all. + - name: Detect docs site + id: docs + run: | + for dir in docs site; do + for cfg in hugo.toml hugo.yaml \ + config/hugo.toml config/hugo.yaml \ + config/_default/hugo.toml config/_default/hugo.yaml; do + if [ -f "$dir/$cfg" ]; then + if [ -f "$dir/_preview/package-lock.json" ]; then + echo "work_dir=$dir/_preview" >> "$GITHUB_OUTPUT" + echo "present=true" >> "$GITHUB_OUTPUT" + echo "::notice::Hugo site found under $dir/ (work_dir: $dir/_preview)" + elif [ -f "$dir/package-lock.json" ]; then + echo "work_dir=$dir" >> "$GITHUB_OUTPUT" + echo "present=true" >> "$GITHUB_OUTPUT" + echo "::notice::Hugo site found under $dir/ (work_dir: $dir)" + else + echo "present=false" >> "$GITHUB_OUTPUT" + echo "::notice::Hugo config found in $dir/ but no package-lock.json found β€” skipping link check." + fi + exit 0 + fi + done + done + echo "present=false" >> "$GITHUB_OUTPUT" + echo "::notice::No Hugo site found under docs/ or site/ β€” skipping link check." + + - name: Setup Hugo + if: steps.docs.outputs.present == 'true' + uses: peaceiris/actions-hugo@v3 + with: + hugo-version: ${{ env.HUGO_VERSION }} + extended: true + + # `actions/setup-node@v4` ships with built-in npm caching that hashes + # the lockfile and restores `~/.npm`. We use that instead of a + # standalone `actions/cache@v4` block so there is only one source of + # truth for the cache key (no drift between two layers). + - name: Setup Node + if: steps.docs.outputs.present == 'true' + uses: actions/setup-node@v4 + with: + node-version: '26' + cache: 'npm' + cache-dependency-path: ${{ steps.docs.outputs.work_dir }}/package-lock.json + + # `HUGO_CACHEDIR=/tmp/hugo_cache` (set in `env:` above) makes Hugo + # actually write to the path this step restores from. The key hashes + # both possible go.sum locations so adding/removing a Hugo module + # invalidates the cache deterministically regardless of site root. + - name: Cache Hugo Modules + if: steps.docs.outputs.present == 'true' + uses: actions/cache@v4 + with: + path: /tmp/hugo_cache + key: ${{ runner.os }}-hugomod-${{ hashFiles('docs/**/go.sum', 'site/**/go.sum') }} + restore-keys: | + ${{ runner.os }}-hugomod- + + - name: Install Dependencies + if: steps.docs.outputs.present == 'true' + working-directory: ${{ steps.docs.outputs.work_dir }} + run: npm ci + + - name: Build docs preview site + if: steps.docs.outputs.present == 'true' + working-directory: ${{ steps.docs.outputs.work_dir }} + run: hugo -e development + + # Cache Lychee results to avoid hitting rate limits. + # Key on the lychee.toml hash so that exclude-list edits (e.g. removing + # an exclude pattern) invalidate the cache deterministically; otherwise + # stale `200 OK` entries for the now-checked URLs would be trusted until + # `max_cache_age` expires. + - name: Cache Lychee results + if: steps.docs.outputs.present == 'true' + uses: actions/cache@v4 + with: + path: .lycheecache + key: cache-lychee-${{ runner.os }}-${{ hashFiles('lychee.toml') }} + restore-keys: | + cache-lychee-${{ runner.os }}- + + # The cache key includes LYCHEE_VERSION_TAG so a version bump + # automatically pulls a fresh binary instead of reusing the old one. + # The restore-keys fallback lets a release-filename tweak (rare) reuse + # the existing cached binary for the same version-tag instead of paying + # for a fresh download. + - name: Cache Lychee executable + if: steps.docs.outputs.present == 'true' + id: cache-lychee + uses: actions/cache@v4 + with: + path: lychee + key: ${{ runner.os }}-${{ env.LYCHEE_VERSION_TAG }}-${{ env.LYCHEE_RELEASE }} + restore-keys: | + ${{ runner.os }}-${{ env.LYCHEE_VERSION_TAG }}- + + # We use Lychee directly instead of a GitHub Action because it + # must have access to the local Hugo server, which is not visible + # from the Docker-based action. + # + # `if:` gating uses `hashFiles('lychee/lychee')` rather than + # `steps.cache-lychee.outputs.cache-hit != 'true'`. Per `actions/cache` + # docs, `cache-hit` is only `'true'` on an EXACT key match β€” a restore + # via `restore-keys` reports `cache-hit == 'false'`, even though the + # binary is present in the workspace. Re-downloading in that case + # would defeat the point of the fallback. `hashFiles` returns an empty + # string when the file is absent, so this guard runs the download iff + # neither the exact key nor any restore-key restored the binary. + - name: Download Lychee executable + uses: robinraju/release-downloader@v1.7 + if: steps.docs.outputs.present == 'true' && hashFiles('lychee/lychee') == '' + with: + repository: "lycheeverse/lychee" + tag: ${{ env.LYCHEE_VERSION_TAG }} + fileName: ${{ env.LYCHEE_RELEASE }} + + - name: Verify Lychee checksum + if: steps.docs.outputs.present == 'true' && hashFiles('lychee/lychee') == '' + run: | + echo "${{ env.LYCHEE_SHA256 }} ${{ env.LYCHEE_RELEASE }}" | sha256sum --check --strict + + # The v0.24.2 tarball contains a top-level directory + # (e.g. `lychee-x86_64-unknown-linux-gnu/lychee`), so `--strip-components=1` + # flattens it to `lychee/lychee` β€” matching what the companion + # `check-links` skill does locally and what the next step expects. + - name: Extract Lychee executable + if: steps.docs.outputs.present == 'true' && hashFiles('lychee/lychee') == '' + run: | + mkdir -p lychee && + tar -xzf ${{ env.LYCHEE_RELEASE }} --strip-components=1 -C lychee + + # 1. In the generated HTML, some inner links will have absolute URLs and + # the link checker will attempt to fetch them. That's why we need + # a server. Sadly, link checkers have no settings to address this. + # 2. Output redirection is necessary for nohup in GitHub Actions. + # 3. Sleep + `curl` readiness check make sure the server is actually + # serving HTTP before the next step runs Lychee. Without the curl + # probe a silent startup failure (port already bound, missing + # Hugo module, build error surfacing after `nohup` returns 0) + # would manifest 60 s later as "every URL unreachable" Lychee + # errors instead of pointing at the real cause. Mirrors the + # `pgrep -F` guard in the companion `check-links` skill. + # 4. `--port 1313` is set explicitly (not relying on Hugo's default) so + # the coupling with `--base-url http://localhost:1313/` in the next + # Lychee step is visible β€” change one, change the other. + - name: Start Hugo server + if: steps.docs.outputs.present == 'true' + working-directory: ${{ steps.docs.outputs.work_dir }} + run: | + nohup hugo server \ + --environment development \ + --port 1313 \ + > nohup.out 2> nohup.err < /dev/null & + sleep 5 + if ! curl -sf http://localhost:1313/ > /dev/null; then + echo "ERROR: Hugo server did not respond on port 1313." >&2 + echo "--- stdout ---" >&2; cat nohup.out >&2 || true + echo "--- stderr ---" >&2; cat nohup.err >&2 || true + exit 1 + fi + + - name: Check links + if: steps.docs.outputs.present == 'true' + run: | + ./lychee/lychee --config lychee.toml --timeout 60 \ + --base-url http://localhost:1313/ \ + '${{ steps.docs.outputs.work_dir }}/public/**/*.html' diff --git a/.github/workflows/ensure-reports-updated.yml b/.github/workflows/ensure-reports-updated.yml index fdd8b8e67..20deb3f69 100644 --- a/.github/workflows/ensure-reports-updated.yml +++ b/.github/workflows/ensure-reports-updated.yml @@ -1,25 +1,35 @@ # Ensures that the license report files were modified in this PR. +# +# The check runs only for pull requests targeting a default (`master`/`main`) or +# a release-line (e.g. `2.x-jdk8-master`) branch. The report files embed the project +# version, so they are refreshed by the branches which bump it. Pull requests +# targeting auxiliary branches are not checked. +# +# The base branch is checked inside the job rather than via the `branches` filter: +# a workflow skipped by branch filtering leaves its check in the `Pending` state, +# blocking PRs which require it, while a job skipped via `if` reports `skipped`, +# which satisfies required status checks. -name: Ensure license reports updated +name: License Reports on: pull_request: - branches: - - '**' jobs: - build: - name: Ensure license reports updated + check: + name: Ensure license reports are updated runs-on: ubuntu-latest + # Default and release-line branches, e.g. `master`, `main`, `2.x-jdk8-master`. + if: endsWith(github.base_ref, 'master') || endsWith(github.base_ref, 'main') steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: - # Configure the checkout of all branches, so that it is possible to run the comparison. + # Configure the checkout of all branches so that it is possible to run the comparison. fetch-depth: 0 # Check out the `config` submodule to fetch the required script file. submodules: true - - name: Check that both `pom.xml` and license report files are modified + - name: Check that dependency report files are modified shell: bash run: chmod +x ./config/scripts/ensure-reports-updated.sh && ./config/scripts/ensure-reports-updated.sh diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 858cebbcc..fc0872b4c 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -1,4 +1,4 @@ -name: Validate Gradle Wrapper +name: Gradle Wrapper validation on: push: branches: @@ -9,11 +9,11 @@ on: jobs: validation: - name: Gradle Wrapper Validation + name: Validate Gradle Wrapper runs-on: ubuntu-latest steps: - name: Checkout latest code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Validate Gradle Wrapper uses: gradle/actions/wrapper-validation@v4 diff --git a/.github/workflows/increment-guard.yml b/.github/workflows/increment-guard.yml index 1993841a6..9a6333f0f 100644 --- a/.github/workflows/increment-guard.yml +++ b/.github/workflows/increment-guard.yml @@ -1,29 +1,100 @@ -# Ensures that the current lib version is not yet published but executing the Gradle -# `checkVersionIncrement` task. +# Guards the project version by executing the Gradle `checkVersionIncrement` task, +# which verifies that the version is both (a) strictly greater than the base branch +# version in `version.gradle.kts` and (b) not already published. The result is +# published as the `Version Guard` commit status β€” the context required by branch +# protection and re-published by `revalidate-versions.yml` on the heads of other open +# PRs when the base branch advances (so a stale duplicate bump turns red before merge). +# +# The check runs only for pull requests targeting a default (`master`/`main`) or +# a release-line (e.g. `2.x-jdk8-master`) branch. It is the responsibility of a branch +# which aims to merge into such a branch to bump the version. Auxiliary branches +# do not deal with the versions in the release cycle and are not guarded. +# +# The base branch is checked inside the job rather than via the `branches` filter: +# a workflow skipped by branch filtering leaves its check in the `Pending` state, +# blocking PRs which require it, while a job skipped via `if` reports `skipped`, +# which satisfies required status checks. -name: Check version increment +name: Version Guard on: - push: - branches: - - '**' + pull_request: + # Beyond the default activity types (`opened`, `synchronize`, `reopened`), two more are + # needed because they change what the guard must compare against without a new head SHA, + # which would otherwise leave a stale-green `Version Guard` status mergeable: + # * `ready_for_review` β€” a draft that went stale while in draft becomes ready; and + # * `edited` β€” the base branch is retargeted (e.g. a release line -> `master`), so the + # strict comparison must be recomputed against the new base. + types: [opened, synchronize, reopened, ready_for_review, edited] jobs: - build: + check: name: Check version increment runs-on: ubuntu-latest + # Default and release-line branches, e.g. `master`, `main`, `2.x-jdk8-master`. For an + # `edited` event, run only when the base actually changed (a retarget carries + # `changes.base.ref.from`); title/body edits carry no `changes.base` and are skipped, so + # the guard is not rebuilt needlessly. + if: >- + (endsWith(github.base_ref, 'master') || endsWith(github.base_ref, 'main')) + && (github.event.action != 'edited' || github.event.changes.base.ref.from != '') + + # `statuses: write` lets the job publish the `Version Guard` commit status that + # branch protection requires. + permissions: + contents: read + statuses: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: 'true' - - uses: actions/setup-java@v4 + # `checkVersionIncrement` reads `origin/:version.gradle.kts`. The pull request + # checkout does not include the base branch, so fetch its tip into the expected ref. + - name: Fetch the base branch + shell: bash + run: | + git fetch --no-tags --depth=1 \ + origin "+refs/heads/${GITHUB_BASE_REF}:refs/remotes/origin/${GITHUB_BASE_REF}" + + - uses: actions/setup-java@v5 with: java-version: 17 distribution: zulu - cache: gradle - - name: Check version is not yet published + - uses: gradle/actions/setup-gradle@v6 + + - name: Check version increment + id: guard shell: bash + # `VERSION_GUARD` enables the strict base-branch comparison in `checkVersionIncrement`. + # Only this workflow fetches the base ref (the step above), so the comparison is gated + # to it: other CI builds pull the task in via `publishToMavenLocal` on a shallow + # checkout and must not attempt to read `origin/`. + env: + VERSION_GUARD: "true" run: ./gradlew checkVersionIncrement --stacktrace + + # Publish the verdict as the `Version Guard` commit status on the PR head. Posting it + # on every run (success or failure) is what lets a later re-bump clear a failure that + # `revalidate-versions.yml` set when the base branch advanced. + # + # Skipped for fork PRs: `GITHUB_TOKEN` is read-only for them, so the status cannot be + # posted (and a fork head SHA is not in this repo). The Spine agent workflow pushes PR + # branches to the same repository; fork contributions are handled by a maintainer, who + # owns the version bump. + - name: Report the Version Guard status + if: always() && github.event.pull_request.head.repo.fork == false + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + state=failure + if [ "${{ steps.guard.outcome }}" = "success" ]; then + state=success + fi + gh api -X POST "repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }}" \ + -f state="${state}" \ + -f context="Version Guard" \ + -f description="Version increment check" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7de0c5101..27c11c0f8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -6,17 +6,20 @@ on: jobs: publish: + name: Publish to Maven repositories runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: 'true' - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v5 with: java-version: 17 distribution: zulu - cache: gradle + + - uses: gradle/actions/setup-gradle@v6 - name: Decrypt CloudRepo credentials run: ./config/scripts/decrypt.sh "$CLOUDREPO_CREDENTIALS_KEY" ./.github/keys/cloudrepo.properties.gpg ./cloudrepo.properties @@ -61,3 +64,18 @@ jobs: REPO_SLUG: ${{ github.repository }} # e.g. SpineEventEngine/core-jvm GOOGLE_APPLICATION_CREDENTIALS: ./maven-publisher.json NPM_TOKEN: ${{ secrets.NPM_SECRET }} + + # A failed publication on `master` is most often a version collision: a stale + # duplicate bump merged before `revalidate-versions.yml` could turn it red (the + # narrow auto-merge race). The artifact is safe β€” the registry rejects the + # overwrite β€” but the fix needs a human/agent, so make the failure loud and + # actionable instead of a quiet red run. + - name: Report a failed publication + if: failure() + shell: bash + run: | + echo "::error title=Publish failed::Publishing to Maven failed on the base branch. If this is a version collision, the version is already published (immutable). Bump 'version.gradle.kts' on the base branch (e.g. via a small PR) and re-run this workflow." + echo "Publish failed. If the cause is a version collision:" + echo " 1. Bump 'version.gradle.kts' on the base branch to the next free version." + echo " 2. Re-run this 'Publish' workflow." + echo "Stale duplicate bumps are normally caught before merge by 'revalidate-versions.yml'." diff --git a/.github/workflows/remove-obsolete-artifacts-from-packages.yaml b/.github/workflows/remove-obsolete-artifacts-from-packages.yaml index fe8ad849d..62242c7bb 100644 --- a/.github/workflows/remove-obsolete-artifacts-from-packages.yaml +++ b/.github/workflows/remove-obsolete-artifacts-from-packages.yaml @@ -34,12 +34,12 @@ env: jobs: retrieve-package-names: - name: Retrieve the package names published from this repository + name: Retrieve package names runs-on: ubuntu-latest outputs: package-names: ${{ steps.request-package-names.outputs.package-names }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: 'true' @@ -54,7 +54,7 @@ jobs: echo "package-names=$(<./package-names.json)" >> $GITHUB_OUTPUT delete-obsolete-artifacts: - name: Remove obsolete artifacts published from this repository to GitHub Packages + name: Delete obsolete artifacts needs: retrieve-package-names runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/revalidate-versions.yml b/.github/workflows/revalidate-versions.yml new file mode 100644 index 000000000..2d2ecb9c6 --- /dev/null +++ b/.github/workflows/revalidate-versions.yml @@ -0,0 +1,57 @@ +# Re-judges every other open pull request when the base branch advances. +# +# Publishing runs on every push to a release base branch, so once one pull request merges +# and bumps the version, any other open PR that bumped to the same (or a lower) value is +# now stale: its publish would collide. GitHub does not re-run a PR's checks when its base +# advances, so this workflow does it actively β€” for each other open PR whose +# `version.gradle.kts` version is `<=` the new base version, it posts a failing +# `Version Guard` commit status on the PR head, blocking the merge until the author +# re-bumps. The status self-clears: the re-bump push runs `increment-guard.yml`, which +# posts a fresh `success` on the new head. +# +# This narrows, but does not close, the race against auto-merge. A PR that is already +# mergeable can merge in the seconds before this fan-out marks it stale; that late merge +# produces a publish collision which the immutable Maven registry rejects (a loud, +# recoverable red Publish), never an overwrite. The deterministic guarantee is the +# registry's immutability, not this signal. + +name: Revalidate Versions + +on: + push: + # Matches the PR guard's `endsWith(base_ref, 'master'|'main')` and the same scope as + # `build-on-ubuntu.yml`. `**` (unlike `*`) also crosses `/`, so slash-named release + # lines such as `release/2.x-master` are covered. Keeping these definitions identical + # ensures every branch guarded on the PR side is also revalidated here. + branches: + - '**master' + - '**main' + +permissions: + contents: read + statuses: write + pull-requests: read + +concurrency: + # Only the newest tip of a given base matters; a later push to the same ref cancels an + # in-flight fan-out for it. Different bases run independently. + group: revalidate-versions-${{ github.ref }} + cancel-in-progress: true + +jobs: + revalidate: + name: Revalidate open PRs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + submodules: 'true' + + - name: Revalidate open PRs against the new base version + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + BASE_REF: ${{ github.ref_name }} + # Invoked via `bash` so it does not depend on the script's committed executable bit. + run: bash ./config/scripts/revalidate-versions.sh diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml new file mode 100644 index 000000000..6f0130e21 --- /dev/null +++ b/.github/workflows/secret-scan.yml @@ -0,0 +1,70 @@ +name: Secret scan + +# Defense-in-depth behind the local `secret-scan` pre-commit hook and the +# `.gitignore` secret patterns: if a credential is committed despite those, this +# fails the pull request before it can merge. Distributed to every Spine repo by +# `./config/pull`. + +on: + pull_request: + push: + branches: + - master + - main + +permissions: + contents: read + +jobs: + gitleaks: + name: gitleaks + runs-on: ubuntu-latest + env: + # Pinned gitleaks version β€” bump through the usual dependency-update process. + GITLEAKS_VERSION: "8.21.2" + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + # Full history so a pull request's commit range can be scanned. + fetch-depth: 0 + + - name: Install gitleaks + # Run gitleaks as the runner user against the checkout it owns β€” no + # container, so no "dubious ownership" git error and no GitHub Action + # org-licence requirement. + run: | + curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ + | tar -xzf - gitleaks + ./gitleaks version + + - name: Scan + env: + EVENT: ${{ github.event_name }} + BASE: ${{ github.event.pull_request.base.sha }} + HEAD: ${{ github.event.pull_request.head.sha }} + BEFORE: ${{ github.event.before }} + AFTER: ${{ github.sha }} + run: | + if [ "$EVENT" = pull_request ]; then + # Scan the PR's own commit RANGE: a secret added in one commit and + # deleted in a later commit of the same PR is still caught (a + # working-tree scan would miss it, yet merging keeps the secret-bearing + # commit reachable), while already-rotated secrets in older history + # outside base..head are not re-flagged. + ./gitleaks git --log-opts="$BASE..$HEAD" --redact --verbose --exit-code=1 . + else + # Push to a default branch: scan the pushed commit RANGE (before..after) + # so an add-then-remove batch is caught here too, not only on PRs β€” the + # leaked commit would otherwise stay reachable on the default branch. A + # branch's first push reports an all-zero `before` (no range); fall back + # to a working-tree scan then. + if [ -n "$BEFORE" ] && [ "$BEFORE" != "0000000000000000000000000000000000000000" ]; then + ./gitleaks git --log-opts="$BEFORE..$AFTER" --redact --verbose --exit-code=1 . + else + # Branch's first push (all-zero `before`): no range to diff against, so + # scan the whole history reachable from the pushed tip as the initial + # import β€” an add-then-remove within those commits is still caught. + ./gitleaks git --log-opts="$AFTER" --redact --verbose --exit-code=1 . + fi + fi diff --git a/.gitignore b/.gitignore index 48de9f27a..dfb4774d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +# >>> shared config (managed by ./config/pull -- do not edit inside this block) >>> # # Copyright 2025, TeamDev. All rights reserved. # @@ -39,6 +40,7 @@ # Internal tool directories. .fleet/ +.junie/memory/ # Kotlin temp directories. **/.kotlin/ @@ -102,14 +104,48 @@ gradle-app.setting # Spine internal directory for storing intermediate artifacts **/.spine/** -# Login details to Maven repository. -# Each workstation should have developer's login defined in this file. +# --------------------------------------------------------------------------- +# Secrets β€” NEVER commit these. +# +# Encrypted credentials live under `.github/keys/*.gpg` and ARE committed. +# `config/scripts/decrypt.sh` turns each into its PLAINTEXT twin at build / CI / +# publish time (e.g. `spine-dev-framework-ci.json.gpg` -> `spine-dev.json`). The +# decrypted twins below β€” and any private key or service-account file β€” must stay +# out of Git. The shared `secret-scan` pre-commit hook is the backstop if one ever +# slips past these patterns. +# --------------------------------------------------------------------------- + +# Maven repository login details; each workstation defines its own. credentials.tar credentials.properties cloudrepo.properties deploy_key_rsa gcs-auth-key.json +# Decrypted Google / GCP service-account keys (plaintext twins of *.gpg). +spine-dev.json +spine-dev-*.json +maven-publisher.json +firebase-sa.json +*-sa.json +*service-account*.json + +# Decrypted credential property files and portal / publisher secrets. +*.secret.properties + +# Private SSH keys (public keys are *.pub and remain committable). +*_rsa +*_dsa +*_ecdsa +*_ed25519 +id_rsa +id_dsa +id_ecdsa +id_ed25519 + +# ...but always keep the committed ENCRYPTED forms. +!*.gpg + # Log files *.log @@ -130,3 +166,59 @@ pubspec.lock /tmp .gradle-test-kit/ + +# Python cache +__pycache__/ +*.pyc + +# Claude working files +/.claude/worktrees/ + +# Auto-downloaded Lychee binary used by the `check-links` skill. +/.agents/skills/check-links/.cache/ + +# Lychee link-checker cache (created by the `check-links` skill and +# the `Check Links` workflow when run locally). +.lycheecache + +# Hugo docs preview site build artifacts (used by the `check-links` +# skill and the `Check Links` workflow in repos that contain a +# `docs/_preview` Hugo site). +docs/_preview/node_modules/ +docs/_preview/public/ +docs/_preview/resources/ +# <<< shared config <<< + +# >>> repo-local entries (preserved across ./config/pull) >>> +!.idea/misc.xml +!.idea/codeStyleSettings.xml +!.idea/codeStyles/ +!.idea/copyright/ +!**/src/**/build/** +!gradle-wrapper.jar +# Login details to Maven repository. +# Each workstation should have developer's login defined in this file. +# <<< repo-local entries <<< + +# >>> secret ignores re-asserted last (managed by ./config/pull -- do not edit) >>> +credentials.tar +credentials.properties +cloudrepo.properties +deploy_key_rsa +gcs-auth-key.json +spine-dev.json +spine-dev-*.json +maven-publisher.json +firebase-sa.json +*-sa.json +*service-account*.json +*.secret.properties +*_rsa +*_dsa +*_ecdsa +*_ed25519 +id_rsa +id_dsa +id_ecdsa +id_ed25519 +# <<< secret ignores <<< diff --git a/.gitmodules b/.gitmodules index 5072da7fd..cf4b76d15 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "config"] path = config url = https://github.com/SpineEventEngine/config.git +[submodule ".agents/shared"] + path = .agents/shared + url = https://github.com/SpineEventEngine/agents.git + branch = master + update = merge + ignore = all diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 7b7c321bc..7be402da6 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -653,7 +653,7 @@ - @@ -912,4 +912,4 @@ - + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 1b15ab447..f516735d4 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -3,12 +3,8 @@ - - - + \ No newline at end of file diff --git a/.idea/live-templates/README.md b/.idea/live-templates/README.md index 66713b347..950066731 100644 --- a/.idea/live-templates/README.md +++ b/.idea/live-templates/README.md @@ -5,12 +5,12 @@ This directory contains two live template groups: 1. `Spine.xml`: shortcuts for the repeated patterns used in the framework. 2. `User.xml`: a single shortcut to generate TODO comments. -### Instlallation +### Installation Live templates are not picked up by IDEA automatically. They should be added manually. In order to add these templates, perform the following steps: -1. Copy `*.xml` files from this directory to `templates` directory in the IntelliJ IDEA +1. Copy `*.xml` files from this directory to `templates` directory in the IntelliJ IDEA [settings folder][settings_folder]. 2. Restart IntelliJ IDEA: `File -> Invalidate Caches -> Just restart`. 3. Go to `Preferences -> Editor -> Live Templates`. @@ -23,5 +23,5 @@ In order to add these templates, perform the following steps: 1. Open the corresponding template: `Preferences -> Editor -> Live Templates -> User.todo`. 2. Click on `Edit variables`. 3. Set `USER` variable to your domain email address without `@teamdev.com` ending. For example, - for `jack.sparrow@teamdev.com` use the follwoing expression `"jack.sparrow"`. + for `jack.sparrow@teamdev.com` use the following expression `"jack.sparrow"`. 4. Verify that the template generates expected comments: `// TODO:2022-11-03:jack.sparrow: <...>`. diff --git a/.idea/live-templates/User.xml b/.idea/live-templates/User.xml index cc156507e..958e2ea9b 100644 --- a/.idea/live-templates/User.xml +++ b/.idea/live-templates/User.xml @@ -1,7 +1,7 @@