From 9488865b9e407bec637df5e9b38ba7c4d2dbea33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=82CHES?= Date: Tue, 17 Mar 2026 18:36:11 -0600 Subject: [PATCH] refactor(gsd): unify duplicate padRight/truncate into shared format-utils (#1045) Co-authored-by: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/history.ts | 11 ++--------- src/resources/extensions/gsd/session-forensics.ts | 14 ++++++-------- src/resources/extensions/shared/format-utils.ts | 8 ++++++++ 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/resources/extensions/gsd/history.ts b/src/resources/extensions/gsd/history.ts index c50e7eaa5..79da6782e 100644 --- a/src/resources/extensions/gsd/history.ts +++ b/src/resources/extensions/gsd/history.ts @@ -2,7 +2,7 @@ // Human-readable display of past auto-mode unit executions. import type { ExtensionCommandContext } from "@gsd/pi-coding-agent"; -import { formatDuration } from "../shared/format-utils.js"; +import { formatDuration, padRight, truncateWithEllipsis } from "../shared/format-utils.js"; import { getLedger, getProjectTotals, formatCost, formatTokenCount, aggregateBySlice, aggregateByPhase, aggregateByModel, loadLedgerFromDisk, @@ -58,7 +58,7 @@ export async function handleHistory(args: string, ctx: ExtensionCommandContext, lines.push( padRight(formatRelativeTime(u.finishedAt), 14) + padRight(u.type, 20) + - padRight(truncate(u.id, 15), 16) + + padRight(truncateWithEllipsis(u.id, 15), 16) + padRight(shortModel(u.model), 14) + padRight(formatCost(u.cost), 10) + padRight(formatTokenCount(u.tokens.total), 10) + @@ -141,10 +141,3 @@ function shortModel(model: string): string { return model.replace(/^claude-/, "").replace(/^anthropic\//, ""); } -function truncate(s: string, maxLen: number): string { - return s.length > maxLen ? s.slice(0, maxLen - 1) + "…" : s; -} - -function padRight(s: string, len: number): string { - return s.length >= len ? s.slice(0, len) : s + " ".repeat(len - s.length); -} diff --git a/src/resources/extensions/gsd/session-forensics.ts b/src/resources/extensions/gsd/session-forensics.ts index 7e293eab9..f389b6ad5 100644 --- a/src/resources/extensions/gsd/session-forensics.ts +++ b/src/resources/extensions/gsd/session-forensics.ts @@ -20,6 +20,7 @@ import { readFileSync, readdirSync, existsSync, statSync } from "node:fs"; import { basename, join } from "node:path"; +import { truncateWithEllipsis } from "../shared/format-utils.js"; import { nativeParseJsonlTail } from "./native-parser-bridge.js"; import { MAX_JSONL_BYTES, parseJSONL } from "./jsonl-utils.js"; import { nativeWorkingTreeStatus, nativeDiffStat } from "./native-git-bridge.js"; @@ -366,7 +367,7 @@ function formatRecoveryPrompt( sections.push("", "### Commands Already Run"); for (const c of significantCommands.slice(-10)) { const status = c.failed ? " ❌" : " ✓"; - sections.push(`- \`${truncate(c.command, 120)}\`${status}`); + sections.push(`- \`${truncateWithEllipsis(c.command, 121)}\`${status}`); } } @@ -374,7 +375,7 @@ function formatRecoveryPrompt( if (trace.errors.length > 0) { sections.push( "", "### Errors Before Interruption", - ...trace.errors.slice(-3).map(e => `- ${truncate(e, 200)}`), + ...trace.errors.slice(-3).map(e => `- ${truncateWithEllipsis(e, 201)}`), ); } @@ -440,7 +441,7 @@ function compressToolCallTrace(calls: ToolCall[]): string { if (call.name === "write" || call.name === "edit") { lines.push(`${num}. ${call.name} \`${call.input.path || "?"}\`${err}`); } else if (call.name === "bash" || call.name === "bg_shell") { - const cmd = truncate(String(call.input.command || ""), 80); + const cmd = truncateWithEllipsis(String(call.input.command || ""), 81); lines.push(`${num}. ${call.name}: \`${cmd}\`${err}`); } else { lines.push(`${num}. ${call.name}${err}`); @@ -459,7 +460,7 @@ function formatTraceSummary(trace: ExecutionTrace): string { parts.push(`Files written: ${trace.filesWritten.map(f => `\`${f}\``).join(", ")}`); } if (trace.commandsRun.length > 0) { - const cmds = trace.commandsRun.slice(-5).map(c => `\`${truncate(c.command, 80)}\`${c.failed ? " ❌" : ""}`); + const cmds = trace.commandsRun.slice(-5).map(c => `\`${truncateWithEllipsis(c.command, 81)}\`${c.failed ? " ❌" : ""}`); parts.push(`Commands run: ${cmds.join(", ")}`); } if (trace.errors.length > 0) { @@ -517,7 +518,7 @@ function redactInput(name: string, input: Record): Record = {}; for (const [key, value] of Object.entries(input)) { if (key === "content" || key === "oldText" || key === "newText") { - safe[key] = typeof value === "string" ? truncate(value, 100) : "[redacted]"; + safe[key] = typeof value === "string" ? truncateWithEllipsis(value, 101) : "[redacted]"; } else { safe[key] = value; } @@ -533,6 +534,3 @@ function findLast(arr: T[], predicate: (item: T) => boolean): T | undefined { return undefined; } -function truncate(s: string, max: number): string { - return s.length > max ? s.slice(0, max) + "…" : s; -} diff --git a/src/resources/extensions/shared/format-utils.ts b/src/resources/extensions/shared/format-utils.ts index 53b5656d0..9aefd48a8 100644 --- a/src/resources/extensions/shared/format-utils.ts +++ b/src/resources/extensions/shared/format-utils.ts @@ -70,6 +70,14 @@ export function fitColumns(parts: string[], width: number, separator = " "): st return truncateToWidth(result, width); } +// ─── Text Truncation ───────────────────────────────────────────────────────── + +/** Truncate a string to `maxLength` characters, replacing the last character with an ellipsis if needed. */ +export function truncateWithEllipsis(text: string, maxLength: number): string { + if (text.length <= maxLength) return text + return text.slice(0, maxLength - 1) + "…" +} + // ─── Data Visualization ─────────────────────────────────────────────────────── /**