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
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,4 @@ reconciliation.

## License

MIT
Token Pilot is licensed under the [MIT License](LICENSE).
104 changes: 98 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import groovy.xml.XmlSlurper

plugins {
id 'java'
id 'org.springframework.boot' version '4.0.5' apply false
id 'io.spring.dependency-management' version '1.1.7' apply false
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()
Expand Down Expand Up @@ -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'
}
Expand Down Expand Up @@ -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'

Expand All @@ -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'
}
Expand Down Expand Up @@ -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')
}
Loading