Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions .agents/guidelines/project-owned-files.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Project-owned files

## Rule for this repository

This repository has no `.gitmodules`, so it has no submodule contents or
`config`-distributed files to exclude. Treat every tracked path as project-owned,
including `buildSrc/`, `AGENTS.md`, `CLAUDE.md`, `gradle.properties`, and
`.github/copilot-instructions.md`. Its `config/` directory contains project-owned
Detekt configuration.

Do not apply the reusable skip rules below in this repository.

## Why ownership matters

Some skills edit or stamp files across a whole repository. For example,
`proofread` rewrites prose. These skills must touch only the files the
repository **owns**. Editing an upstream file is worse than a no-op: the
Expand All @@ -10,14 +22,12 @@ This page defines *project-owned* and the two kinds of upstream-owned paths to
skip. Each consuming skill applies the rule to its file types; this page defines
the shared mechanism, while the file filter remains in the consuming skill.

Determine submodule ownership from `.gitmodules`, never from a directory name.
This repository currently has no `.gitmodules`. Its `config/` directory contains
project-owned Detekt configuration, so neither skip rule applies here.

## What to skip
## Reusable rules for repositories with submodules

A repository does **not** own two kinds of tracked files. Skip both in every
mode: full sweeps, scoped sweeps, and incremental branch-diff runs alike.
Apply these rules only when `.gitmodules` declares the relevant submodule.
Determine submodule ownership from `.gitmodules`, never from a directory name.
A repository does **not** own the two kinds of tracked files below. Skip them
in every mode: full sweeps, scoped sweeps, and incremental branch-diff runs alike.

### 1. Submodule contents

Expand Down
19 changes: 9 additions & 10 deletions .agents/skills/proofread/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,15 @@ wrong fix.** When a fix is not clearly correct, leave the text and report it.
untracked, non-ignored files (new prose not yet `git add`ed) alongside tracked
changes. Keep them, then intersect the whole list with the file-type filter.

**Then drop everything the project does not own**, per
`.agents/guidelines/project-owned-files.md` — submodule contents and, in
a repo that consumes `config`, the config-distributed files. This skill
processes prose, so of the config-distributed set it encounters the
Markdown members (`AGENTS.md`, `CLAUDE.md`, `CODE_OF_CONDUCT.md`,
`.junie/guidelines.md`, `.github/copilot-instructions.md`, and the
conditional `CONTRIBUTING.md`) and the source members under `buildSrc/`
(except `buildSrc/src/main/kotlin/module.gradle.kts`). Apply the skip in
every mode; in the `config` and `agents` source repos the config rule is
inert (those files are project-owned there).
**Apply the repository-specific ownership conclusion.** This repository has
no `.gitmodules`, so it has no project-ownership exclusions. Keep every
candidate that survives the file filter, including Markdown files at the
repository root and eligible source files under `buildSrc/`.

If this skill is reused in another repository, follow
`.agents/guidelines/project-owned-files.md`. Drop submodule contents only
when `.gitmodules` declares them, and drop `config`-distributed files only
when `.gitmodules` declares the `config` submodule.

3. **Scan and fix each file.** Restrict edits to **prose only**, per the
“Where English prose lives” and “Never edit” sections of
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
run: >-
./gradlew
-Dorg.gradle.java.installations.paths="$EMBED_CODE_JAVA_17_HOME"
build :buildSrc:test :gradle-plugin:publishToMavenLocal
build :buildSrc:check :gradle-plugin:publishToMavenLocal

- name: Verify Dependency Reports
if: runner.os == 'Linux'
Expand Down
20 changes: 11 additions & 9 deletions PROJECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ Embed Code configuration file and do not need to install the executable or Kotli
- `version.gradle.kts`: the plugin version and default Embed Code application version.
- `gradle.properties`: Gradle runtime, parallelism, build-cache, and configuration-cache
settings, plus Kotlin style and dependency defaults.
- `buildSrc/`: build settings, dependency coordinates, and the shared `jvm-module`
convention. Its focused unit tests cover custom report logic; Detekt analyzes the plugin module
through the convention but does not analyze the standalone `buildSrc` build.
- `buildSrc/`: build settings, dependency coordinates, and the shared `jvm-module` and Dokka
conventions. Its focused unit tests cover custom report logic, and its own Detekt configuration
analyzes the build logic.
- `gradle-plugin/build.gradle.kts`: plugin declaration, generated version source, functional
test source set, publication metadata, and Plugin Portal configuration.
test source set, Dokka API documentation, publication metadata, and Plugin Portal
configuration.
- `gradle-plugin/src/main/kotlin/`: extension, plugin, task, platform, version, JSON, checksum,
download, installation, and execution logic.
- `gradle-plugin/src/main/templates/`: generated default-version source template.
Expand Down Expand Up @@ -65,9 +66,10 @@ configuration → executable flow instead of patching only the first visible sym
- `gradle-plugin/build.gradle.kts` uses the Kotlin runtime supplied by Gradle;
it does not publish `kotlin-stdlib`.
- Settings plugin versions are pinned in the root `settings.gradle.kts`;
`buildSrc/settings.gradle.kts` reuses that plugin classpath. Bootstrap plugin versions live
in `buildSrc/build.gradle.kts`, while reusable library coordinates live in Kotlin objects
under `buildSrc`. Do not introduce a version catalog as an unrelated refactor.
`buildSrc/settings.gradle.kts` reuses that plugin classpath. Bootstrap and convention-plugin
dependency versions live in `buildSrc/build.gradle.kts`, while reusable library coordinates
live in Kotlin objects under `buildSrc`. Do not introduce a version catalog as an unrelated
refactor.
- The Foojay resolver may contact `api.foojay.io` and let Gradle download a missing JDK
toolchain for contributor builds. CI provisions its required JDKs before invoking Gradle.
This build-time provisioning is outside the plugin's SHA-256 verification of Embed Code
Expand All @@ -85,8 +87,8 @@ version numbers into agent guidance where a durable source path is sufficient.
- Keep tests offline with deterministic fixtures, temporary directories, and loopback HTTP servers.
- Use `./gradlew test` for unit tests, `./gradlew functionalTest` for TestKit tests, and
`./gradlew check` for both plus plugin validation.
- Use `./gradlew :buildSrc:test` for the build-logic tests. `buildSrc` is a separate build,
so `check` does not reach them; CI runs the task explicitly.
- Use `./gradlew :buildSrc:check` for the build-logic tests and Detekt analysis. `buildSrc` is a
separate build, so the root `check` task does not reach them; CI runs the task explicitly.
- Use `./gradlew generateDependencyReports` after changing dependencies or the plugin version.
CI verifies that the aggregate dependency POM and module license inventory match `pom.xml`
and `dependencies.md`.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Add the following configuration to the project's `build.gradle.kts`:

```kotlin
plugins {
id("io.spine.embed-code") version "0.1.0" // Specify the actual version here.
id("io.spine.embed-code") version "0.1.1" // Specify the actual version here.
}

embedCode {
Expand Down
24 changes: 23 additions & 1 deletion buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,27 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import dev.detekt.gradle.Detekt as DetektTask
import dev.detekt.gradle.extensions.DetektExtension
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
`kotlin-dsl`
// Detekt 1.23.8 does not support JDK 25. Keep this version in sync with
// `detektVersion` below.
id("dev.detekt") version "2.0.0-alpha.5"
}

// These bootstrap versions are declared here because `buildSrc` needs them
// before its sources compile.
val kotlinVersion = "2.4.10"
val pluginPublishVersion = "2.1.1"
val licenseReportVersion = "3.1.4"
val dokkaVersion = "2.2.0"
// Keep in sync with `io.spine.embedcode.gradle.dependency.JUnit.version`, which supplies
// the same version to the project's own modules.
val junitVersion = "6.1.2"
// The alpha version is used because the latest stable version does not support JDK 25.
// Keep in sync with the `dev.detekt` plugin version above.
val detektVersion = "2.0.0-alpha.5"

dependencies {
Expand All @@ -51,6 +57,7 @@ dependencies {
implementation(
"dev.detekt:dev.detekt.gradle.plugin:$detektVersion",
)
implementation("org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion")
testImplementation(platform("org.junit:junit-bom:$junitVersion"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
Expand All @@ -71,6 +78,21 @@ java {
targetCompatibility = JavaVersion.VERSION_17
}

extensions.configure<DetektExtension> {
config.setFrom(layout.projectDirectory.file("../config/detekt/detekt.yml"))
buildUponDefaultConfig.set(true)
source.setFrom(
files(
"src/main/kotlin",
"src/test/kotlin",
),
)
}

tasks.withType<DetektTask>().configureEach {
jvmTarget.set(JavaVersion.VERSION_17.majorVersion)
}

tasks.test {
useJUnitPlatform()
}
Expand Down
35 changes: 35 additions & 0 deletions buildSrc/src/main/kotlin/dokka-configuration.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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.
*/

plugins {
id("org.jetbrains.dokka")
}

dokka {
dokkaPublications.html {
moduleName.set(project.name)
}
}
Comment on lines +31 to +35

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit — a shared convention hardcodes one module's name.

dokka-configuration lives in buildSrc alongside jvm-module, so it reads as reusable, but moduleName is pinned to embed-code-gradle-plugin. A second module applying it would publish its API documentation under the wrong name, silently.

Either derive it (base.archivesName, which gradle-plugin/build.gradle.kts:121 already sets to the same value), or leave the convention to apply and configure the plugin and move the moduleName override into gradle-plugin/build.gradle.kts where the name belongs. Not blocking while there is exactly one consumer.

Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* 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.embedcode.gradle.report

/** Coordinates that identify the project described by the aggregate POM. */
internal data class PomCoordinates(
val group: String,
val artifact: String,
val version: String,
)

/** A first-level external dependency and its representative Maven scope. */
internal data class PomDependency(
val group: String,
val artifact: String,
val version: String,
val scope: MavenScope,
)

/** Maven scope used to group dependencies in the generated report. */
internal enum class MavenScope(
val xmlValue: String,
val outputOrder: Int,
val selectionOrder: Int,
) {
COMPILE("compile", 0, 0),
RUNTIME("runtime", 1, 1),
TEST("test", 2, 3),
PROVIDED("provided", 3, 2),
}

internal fun mavenScope(configurationName: String): MavenScope =
when (configurationName) {
"implementation",
"api",
->
MavenScope.COMPILE
"runtimeOnly",
"runtimeClasspath",
"default",
->
MavenScope.RUNTIME
"compileOnly",
"compileOnlyApi",
"annotationProcessor",
->
MavenScope.PROVIDED
else -> {
if (
configurationName.startsWith("test", ignoreCase = true) ||
configurationName.startsWith("functionalTest", ignoreCase = true)
) {
MavenScope.TEST
} else {
MavenScope.PROVIDED
}
}
}

internal fun renderDependencyPom(
coordinates: PomCoordinates,
dependencies: List<PomDependency>,
): String =
buildString {
appendLine("""<?xml version="1.0" encoding="UTF-8"?>""")
appendLine("""<project xmlns="http://maven.apache.org/POM/4.0.0"""")
appendLine(""" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"""")
appendLine(
""" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 """ +
"""https://maven.apache.org/xsd/maven-4.0.0.xsd">""",
)
appendLine(" <modelVersion>4.0.0</modelVersion>")
appendLine(" <!--")
appendLine(" This file is generated by the `generateDependencyReports` Gradle task.")
appendLine(" It documents first-level dependencies from all plugin-module")
appendLine(" configurations and is not suitable for Maven build tasks.")
appendLine(" -->")
appendLine(" <groupId>${coordinates.group.escapeXml()}</groupId>")
appendLine(" <artifactId>${coordinates.artifact.escapeXml()}</artifactId>")
appendLine(" <version>${coordinates.version.escapeXml()}</version>")
appendLine(" <licenses>")
appendLine(" <license>")
appendLine(" <name>The Apache License, Version 2.0</name>")
appendLine(" <url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>")
appendLine(" <distribution>repo</distribution>")
appendLine(" </license>")
appendLine(" </licenses>")
appendLine(" <dependencies>")
dependencies.forEach { dependency ->
appendLine(" <dependency>")
appendLine(" <groupId>${dependency.group.escapeXml()}</groupId>")
appendLine(" <artifactId>${dependency.artifact.escapeXml()}</artifactId>")
appendLine(" <version>${dependency.version.escapeXml()}</version>")
appendLine(" <scope>${dependency.scope.xmlValue}</scope>")
appendLine(" </dependency>")
}
appendLine(" </dependencies>")
appendLine("</project>")
}

private fun String.escapeXml(): String =
replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&apos;")
Loading