From 790935774719e89b3f7ab52733bd22e59158a48e Mon Sep 17 00:00:00 2001 From: kami Date: Tue, 23 Jun 2026 18:59:38 +0000 Subject: [PATCH] fix(cli): format stats with Locale.ROOT so output is locale-independent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit renderStats used the default-locale String.format, so under a comma-decimal locale (e.g. ru_RU) percentages/rates rendered as "58,3" — both producing locale-dependent CLI output and failing StatsRenderTest. Format all five numeric rows with Locale.ROOT. Co-Authored-By: Claude Opus 4.8 --- .../kotlin/com/correx/apps/cli/commands/StatsCommand.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/cli/src/main/kotlin/com/correx/apps/cli/commands/StatsCommand.kt b/apps/cli/src/main/kotlin/com/correx/apps/cli/commands/StatsCommand.kt index 2683febf..adc12fa4 100644 --- a/apps/cli/src/main/kotlin/com/correx/apps/cli/commands/StatsCommand.kt +++ b/apps/cli/src/main/kotlin/com/correx/apps/cli/commands/StatsCommand.kt @@ -13,6 +13,7 @@ import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.request.get import io.ktor.client.statement.bodyAsText import io.ktor.serialization.kotlinx.json.json +import java.util.Locale import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @@ -110,9 +111,10 @@ fun renderStats(report: StatsReportDto): String { lines += " calls: ${report.inferenceCount}" lines += " tokens: ${report.promptTokens} prompt + ${report.completionTokens} completion = " + "${report.promptTokens + report.completionTokens}" - lines += " time: ${report.inferenceMs} ms (%.1f tok/s)".format(report.tokensPerSecond) + lines += " time: ${report.inferenceMs} ms (%.1f tok/s)".format(Locale.ROOT, report.tokensPerSecond) for (p in report.perProvider) { lines += " %-20s %4d calls %6d tok %8d ms (%.1f tok/s)".format( + Locale.ROOT, p.provider, p.completedCount, p.promptTokens + p.completionTokens, p.totalLatencyMs, p.tokensPerSecond, ) } @@ -121,7 +123,7 @@ fun renderStats(report: StatsReportDto): String { lines += "Tools" lines += " calls: ${report.toolCount} time: ${report.toolMs} ms" for (t in report.perTool) { - lines += " %-24s %4d %8d ms".format(t.toolName, t.completedCount, t.totalDurationMs) + lines += " %-24s %4d %8d ms".format(Locale.ROOT, t.toolName, t.completedCount, t.totalDurationMs) } lines += "" @@ -131,6 +133,7 @@ fun renderStats(report: StatsReportDto): String { lines += " total wait: ${report.approvalWaitMs} ms (avg ${report.avgApprovalWaitMs} ms)" for (tier in report.perTier) { lines += " %-4s req %d res %d wait %d ms (avg %d ms)".format( + Locale.ROOT, tier.tier, tier.requestedCount, tier.resolvedCount, tier.totalWaitMs, tier.avgWaitMs, ) } @@ -145,6 +148,7 @@ fun renderStats(report: StatsReportDto): String { lines += "Time accounting (% of session wall time)" lines += " inference: %.1f%% tools: %.1f%% approval-wait: %.1f%% other: %.1f%%".format( + Locale.ROOT, report.inferencePct, report.toolPct, report.approvalWaitPct, otherPct(report), )