Skip to content

Commit 10fface

Browse files
committed
feat(logger): add session summary logging
- Implement summary() method to log phase/task completion stats - Add formatNum() helper for compact number display (1.2M, 500K) - Add formatDurationMs() helper for readable duration display - Display tool calls, token usage, cost, and file changes in summary - Write structured summary entries to log files for analysis Signed-off-by: leocavalcante <leo@cavalcante.dev>
1 parent 25a773e commit 10fface

File tree

1 file changed

+56
-1
lines changed

1 file changed

+56
-1
lines changed

src/logger.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import { appendFileSync, existsSync, readdirSync, renameSync, statSync, unlinkSync } from "node:fs"
66
import { join } from "node:path"
7-
import type { Paths } from "./types.ts"
7+
import type { Paths, SessionStats } from "./types.ts"
88

99
/** ANSI escape codes for terminal formatting */
1010
const ANSI = {
@@ -338,6 +338,61 @@ export class Logger {
338338
this.writeToBuffer(this.formatForFile(`[ACTIVITY] ${action} ${detail || ""}`))
339339
}
340340

341+
/**
342+
* Log a session/operation summary (shows stats at phase end)
343+
*/
344+
summary(stats: SessionStats, phase?: string): void {
345+
this.stopSpinner()
346+
const duration = Date.now() - stats.startTime
347+
const durationStr = this.formatDurationMs(duration)
348+
349+
// Format token counts
350+
const tokensStr = `${this.formatNum(stats.inputTokens)} in / ${this.formatNum(stats.outputTokens)} out`
351+
352+
// Format cost
353+
const costStr =
354+
stats.costUsd < 0.01 ? `$${stats.costUsd.toFixed(4)}` : `$${stats.costUsd.toFixed(2)}`
355+
356+
// Build summary line
357+
const phaseStr = phase ? `${phase} ` : ""
358+
const filesStr = stats.filesModified.length > 0 ? `, ${stats.filesModified.length} files` : ""
359+
360+
console.log(
361+
`${ANSI.dim} ${phaseStr}Summary: ${stats.toolCalls} tools, ${tokensStr}, ${costStr}, ${durationStr}${filesStr}${ANSI.reset}`,
362+
)
363+
this.writeToBuffer(
364+
this.formatForFile(
365+
`[SUMMARY] ${phaseStr}tools=${stats.toolCalls} tokens=${stats.inputTokens}/${stats.outputTokens} cost=${costStr} duration=${durationStr} files=${stats.filesModified.length}`,
366+
),
367+
)
368+
}
369+
370+
/**
371+
* Format a number for display (e.g., 1234 -> "1.2K")
372+
*/
373+
private formatNum(n: number): string {
374+
if (n >= 1_000_000) {
375+
return `${(n / 1_000_000).toFixed(1)}M`
376+
}
377+
if (n >= 1_000) {
378+
return `${(n / 1_000).toFixed(1)}K`
379+
}
380+
return n.toLocaleString()
381+
}
382+
383+
/**
384+
* Format duration in milliseconds
385+
*/
386+
private formatDurationMs(ms: number): string {
387+
const seconds = Math.floor(ms / 1000)
388+
const minutes = Math.floor(seconds / 60)
389+
if (minutes > 0) {
390+
const remainingSeconds = seconds % 60
391+
return `${minutes}m ${remainingSeconds}s`
392+
}
393+
return `${seconds}s`
394+
}
395+
341396
/**
342397
* Format message for file output with timestamp
343398
*/

0 commit comments

Comments
 (0)