From 4e6e02d08d0bc04d507aca0f9cba44faf21e7bd9 Mon Sep 17 00:00:00 2001 From: Nihal Jain Date: Fri, 3 Jul 2026 16:32:59 +0530 Subject: [PATCH] fix(overview): pin number grouping to en-US so output is locale-independent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `codeburn overview` formatted token/call/session counts with a bare `toLocaleString()`, which groups digits according to the host's locale. On an Indian-locale machine (en-IN) a value like 2,002,000,000 renders as 2,00,20,00,000, so the rendered summary — and its snapshot test — differed by machine. (formatCost already avoided this via an explicit thousands regex.) Pin `formatTokens` to 'en-US' and route the remaining count columns through a new `formatCount` helper that does the same, so the overview output is byte-identical everywhere. Verify: `npm test -- overview` (previously failed on non-US locales; the "thousands separators" case now passes regardless of $LANG / ICU default). --- src/overview.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/overview.ts b/src/overview.ts index bec118a0..a08ac198 100644 --- a/src/overview.ts +++ b/src/overview.ts @@ -14,7 +14,14 @@ function formatCost(usd: number): string { return baseCost(usd).replace(/(\d)(?=(\d{3})+(\.|$))/g, '$1,') } function formatTokens(n: number): string { - return Math.round(n).toLocaleString() + // Pin the locale so grouping is deterministic regardless of the host's + // locale (e.g. en-IN groups as 2,00,20,00,000 instead of 2,002,000,000). + return Math.round(n).toLocaleString('en-US') +} +// Integer counts (calls, sessions, turns, tool uses) — same locale pin so the +// overview output is byte-identical across machines. +function formatCount(n: number): string { + return n.toLocaleString('en-US') } function isAbsoluteProjectPath(path: string): boolean { return path.startsWith('/') || path.startsWith('\\') || /^[a-zA-Z]:[/\\]/.test(path) @@ -150,7 +157,7 @@ export function renderOverview( const kv = (k: string, v: string): string => ' ' + c.dim(k.padEnd(11)) + v out.push(kv('Cost', c.bold(formatCost(cost)))) out.push(kv('Tokens', formatTokens(totalTokens) + c.dim(' (breakdown below)'))) - out.push(kv('Calls', calls.toLocaleString() + c.dim(' sessions ') + sessions.toLocaleString())) + out.push(kv('Calls', formatCount(calls) + c.dim(' sessions ') + formatCount(sessions))) out.push(kv('Cache hit', `${cacheHit.toFixed(1)}%`)) if (savings > 0) out.push(kv('Savings', formatCost(savings) + c.dim(' (local models)'))) out.push('') @@ -191,7 +198,7 @@ export function renderOverview( out.push(heading('Top models')) out.push(renderTable(c, [{ header: 'Model' }, { header: 'Cost', right: true }, { header: 'Calls', right: true }, { header: 'Tokens', right: true }], - modelRows.map(([m, v]) => [getShortModelName(m), formatCost(v.cost), v.calls.toLocaleString(), formatTokens(v.tokens)]), + modelRows.map(([m, v]) => [getShortModelName(m), formatCost(v.cost), formatCount(v.calls), formatTokens(v.tokens)]), )) out.push('') } @@ -213,7 +220,7 @@ export function renderOverview( out.push(heading('Top projects')) out.push(renderTable(c, [{ header: 'Project' }, { header: 'Cost', right: true }, { header: 'Sessions', right: true }], - projRows.map(([name, v]) => [name, formatCost(v.cost), v.sessions.toLocaleString()]), + projRows.map(([name, v]) => [name, formatCost(v.cost), formatCount(v.sessions)]), )) out.push('') } @@ -235,7 +242,7 @@ export function renderOverview( out.push(heading('By activity')) out.push(renderTable(c, [{ header: 'Activity' }, { header: 'Cost', right: true }, { header: 'Turns', right: true }], - catRows.map(([cat, v]) => [CATEGORY_LABELS[cat as TaskCategory] ?? cat, formatCost(v.cost), v.turns.toLocaleString()]), + catRows.map(([cat, v]) => [CATEGORY_LABELS[cat as TaskCategory] ?? cat, formatCost(v.cost), formatCount(v.turns)]), )) out.push('') } @@ -246,7 +253,7 @@ export function renderOverview( out.push(heading('Tools')) out.push(renderTable(c, [{ header: 'Tool' }, { header: 'Calls', right: true }], - toolRows.map(([t, n]) => [t, n.toLocaleString()]), + toolRows.map(([t, n]) => [t, formatCount(n)]), )) out.push('') }