From 51a217fe8406fe4c7bb3a395d487843847fc9ac8 Mon Sep 17 00:00:00 2001 From: HuitaePark Date: Mon, 27 Jul 2026 10:59:24 +0900 Subject: [PATCH] =?UTF-8?q?fix(release):=20Maven=20=EB=A9=94=ED=83=80?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20MIT=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=84=A0=EC=8A=A4=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 5 ++- README.md | 2 +- build.gradle | 104 ++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 103 insertions(+), 8 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 183de21..84de65e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -331,7 +331,7 @@ The active checklist is in `docs/30_DAY_MVP_REPORT.md`; detailed long-term works - Current budget flow is check-then-add, is not an atomic reservation, and may not enforce `BLOCK` before provider invocation. - Current Micrometer `ai.token.*` metrics may duplicate Spring AI Observability; preserve compatibility while deciding default suppression or replacement. - Spring AI 2.0.0 documentation cannot be assumed to describe the current 1.1.4 runtime exactly; capability detection and supported-version tests are release requirements. -- Current `LICENSE` is MIT while published POM metadata declares Apache-2.0; release is blocked until they match. +- The repository, README, JReleaser configuration, and every published module POM use the MIT License. `verifyPublicationMetadata` guards this release contract and ensures the sample app is not published. - Sample app E2E uses a fake Spring AI `ChatModel`; real provider API behavior is not yet verified. - `token-pilot-spring-ai-starter` does not exist in the current build; never use it as an install instruction until implemented and published. - Maven Central release consumption must be re-verified for both core and starter paths before announcing `0.1.0`. @@ -394,6 +394,9 @@ Stage and deploy a Central release: - Removed per-request cost rounding, added explicit external-boundary rounding, and made `Cost` amount/currency invariants explicit. - Migrated budget policy, decision, state-store, notification, Spring AI, autoconfigure, and sample money interfaces to `Cost` without removing the existing `BudgetKey` and Clock-based monthly-window design. +- Standardized repository and Maven publication metadata on the MIT License. +- Added artifact-specific POM descriptions and structured generated-POM verification for all seven public modules. +- Added the publication metadata check to the normal Gradle `check` lifecycle and verified that the sample app remains unpublished. ### 2026-07-22 diff --git a/README.md b/README.md index 9dfd6e0..7112260 100644 --- a/README.md +++ b/README.md @@ -56,4 +56,4 @@ reconciliation. ## License -MIT +Token Pilot is licensed under the [MIT License](LICENSE). diff --git a/build.gradle b/build.gradle index aaadb64..53dd989 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,5 @@ +import groovy.xml.XmlSlurper + plugins { id 'java' id 'org.springframework.boot' version '4.0.5' apply false @@ -5,6 +7,16 @@ plugins { id 'org.jreleaser' version '1.24.0' } +def publishedModuleDescriptions = [ + 'token-pilot-core': 'Framework-independent LLM usage normalization, pricing, cost calculation, and ledger contracts.', + 'token-pilot-spring-ai': 'Optional Spring AI adapter for recording provider-reported LLM usage through Token Pilot.', + 'token-pilot-micrometer': 'Optional Micrometer publisher for Token Pilot cost and usage accounting events.', + 'token-pilot-budget': 'Budget policy evaluation and in-memory budget state components for Token Pilot.', + 'token-pilot-notification': 'Budget notification events, handlers, and deduplication components for Token Pilot.', + 'token-pilot-autoconfigure': 'Spring Boot auto-configuration for Token Pilot core and optional adapters.', + 'token-pilot-starter': 'Spring AI convenience starter that assembles Token Pilot runtime modules.' +] + allprojects { group = 'cloud.token-pilot' version = providers.gradleProperty('projectVersion').orElse('0.0.1-SNAPSHOT').get() @@ -36,8 +48,9 @@ jreleaser { project { copyright = '2026 Token Pilot' authors = ['Token Pilot'] - description = 'Token Pilot modules for Spring AI token usage tracking, cost calculation, metrics, and budget enforcement.' + description = 'Framework-independent Java LLM usage control and accounting core with optional adapters.' inceptionYear = '2026' + license = 'MIT' links { homepage = 'https://github.com/tokenpliot/tokenpilot' } @@ -139,6 +152,11 @@ subprojects { } if (name != 'token-pilot-sample-app') { + description = publishedModuleDescriptions[name] + if (!description) { + throw new GradleException("Missing publication description for ${project.path}") + } + apply plugin: 'maven-publish' apply plugin: 'signing' @@ -164,26 +182,26 @@ subprojects { pom { name = project.name - description = 'Token Pilot modules for Spring AI token usage tracking, cost calculation, metrics, and budget enforcement.' + description = publishedModuleDescriptions[project.name] url = 'https://github.com/tokenpliot/tokenpilot' inceptionYear = '2026' organization { name = 'Token Pilot' - url = 'https://github.com/tokenpliot/tokenpilot' + url = 'https://github.com/tokenpliot' } licenses { license { - name = 'Apache License 2.0' - url = 'https://www.apache.org/licenses/LICENSE-2.0.txt' + name = 'MIT License' + url = 'https://opensource.org/license/mit' distribution = 'repo' } } developers { developer { - id = 'token-pilot' + id = 'tokenpliot' name = 'Token Pilot' url = 'https://github.com/tokenpliot' } @@ -253,3 +271,77 @@ subprojects { } } } + +def publicationPomTasks = publishedModuleDescriptions.keySet().collect { + ":${it}:generatePomFileForMavenJavaPublication" +} + +tasks.register('verifyPublicationMetadata') { + group = 'verification' + description = 'Verifies generated Maven POM metadata for every published Token Pilot module.' + dependsOn publicationPomTasks + inputs.file(layout.projectDirectory.file('LICENSE')) + + doLast { + def expectedPublishedModules = publishedModuleDescriptions.keySet() as Set + def actualPublishedModules = subprojects + .findAll { it.plugins.hasPlugin('maven-publish') } + .collect { it.name } as Set + + if (actualPublishedModules != expectedPublishedModules) { + throw new GradleException( + "Published modules differ from the expected set. Expected ${expectedPublishedModules}, actual ${actualPublishedModules}" + ) + } + + if (file('LICENSE').readLines('UTF-8').first().trim() != 'MIT License') { + throw new GradleException('Root LICENSE must use the MIT License.') + } + + def requirePomValue = { String moduleName, String field, String actual, String expected -> + if (actual != expected) { + throw new GradleException( + "${moduleName} POM ${field} must be '${expected}', but was '${actual}'." + ) + } + } + + publishedModuleDescriptions.each { moduleName, moduleDescription -> + def moduleProject = project(":${moduleName}") + def pomFile = moduleProject.layout.buildDirectory + .file('publications/mavenJava/pom-default.xml') + .get() + .asFile + def pom = new XmlSlurper(false, false).parse(pomFile) + + requirePomValue(moduleName, 'name', pom.name.text(), moduleName) + requirePomValue(moduleName, 'description', pom.description.text(), moduleDescription) + requirePomValue(moduleName, 'url', pom.url.text(), 'https://github.com/tokenpliot/tokenpilot') + requirePomValue(moduleName, 'license name', pom.licenses.license.name.text(), 'MIT License') + requirePomValue(moduleName, 'license URL', pom.licenses.license.url.text(), 'https://opensource.org/license/mit') + requirePomValue(moduleName, 'license distribution', pom.licenses.license.distribution.text(), 'repo') + requirePomValue(moduleName, 'organization name', pom.organization.name.text(), 'Token Pilot') + requirePomValue(moduleName, 'organization URL', pom.organization.url.text(), 'https://github.com/tokenpliot') + requirePomValue(moduleName, 'developer id', pom.developers.developer.id.text(), 'tokenpliot') + requirePomValue(moduleName, 'developer name', pom.developers.developer.name.text(), 'Token Pilot') + requirePomValue(moduleName, 'developer URL', pom.developers.developer.url.text(), 'https://github.com/tokenpliot') + requirePomValue( + moduleName, + 'SCM connection', + pom.scm.connection.text(), + 'scm:git:https://github.com/tokenpliot/tokenpilot.git' + ) + requirePomValue( + moduleName, + 'SCM developer connection', + pom.scm.developerConnection.text(), + 'scm:git:ssh://git@github.com/tokenpliot/tokenpilot.git' + ) + requirePomValue(moduleName, 'SCM URL', pom.scm.url.text(), 'https://github.com/tokenpliot/tokenpilot') + } + } +} + +tasks.named('check') { + dependsOn tasks.named('verifyPublicationMetadata') +}