From f842de046c35115e243f0d45c8598d2e2748db49 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Wed, 18 Mar 2026 04:14:47 +0000 Subject: [PATCH] doctor: clarify orphan transcript archive prompt --- src/commands/doctor-state-integrity.test.ts | 9 ++++--- src/commands/doctor-state-integrity.ts | 28 ++++++++++++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/commands/doctor-state-integrity.test.ts b/src/commands/doctor-state-integrity.test.ts index f2d0d5ec1fc..5d800f15bc1 100644 --- a/src/commands/doctor-state-integrity.test.ts +++ b/src/commands/doctor-state-integrity.test.ts @@ -145,13 +145,16 @@ describe("doctor state integrity oauth dir checks", () => { const sessionsDir = resolveSessionTranscriptsDirForAgent("main", process.env, () => tempHome); fs.writeFileSync(path.join(sessionsDir, "orphan-session.jsonl"), '{"type":"session"}\n'); const confirmSkipInNonInteractive = vi.fn(async (params: { message: string }) => - params.message.includes("orphan transcript file"), + params.message.includes("This only renames them to *.deleted.."), ); await noteStateIntegrity(cfg, { confirmSkipInNonInteractive }); - expect(stateIntegrityText()).toContain("orphan transcript file"); + expect(stateIntegrityText()).toContain( + "These .jsonl files are no longer referenced by sessions.json", + ); + expect(stateIntegrityText()).toContain("Examples: orphan-session.jsonl"); expect(confirmSkipInNonInteractive).toHaveBeenCalledWith( expect.objectContaining({ - message: expect.stringContaining("orphan transcript file"), + message: expect.stringContaining("This only renames them to *.deleted.."), }), ); const files = fs.readdirSync(sessionsDir); diff --git a/src/commands/doctor-state-integrity.ts b/src/commands/doctor-state-integrity.ts index b01998d2cdc..ee10f16bb2f 100644 --- a/src/commands/doctor-state-integrity.ts +++ b/src/commands/doctor-state-integrity.ts @@ -27,6 +27,19 @@ type DoctorPrompterLike = { }) => Promise; }; +function countLabel(count: number, singular: string, plural = `${singular}s`): string { + return `${count} ${count === 1 ? singular : plural}`; +} + +function formatFilePreview(paths: string[], limit = 3): string { + const names = paths.slice(0, limit).map((filePath) => path.basename(filePath)); + const remaining = paths.length - names.length; + if (remaining > 0) { + return `${names.join(", ")}, and ${remaining} more`; + } + return names.join(", "); +} + function existsDir(dir: string): boolean { try { return fs.existsSync(dir) && fs.statSync(dir).isDirectory(); @@ -770,11 +783,18 @@ export async function noteStateIntegrity( .map((entry) => path.resolve(path.join(sessionsDir, entry.name))) .filter((filePath) => !referencedTranscriptPaths.has(filePath)); if (orphanTranscriptPaths.length > 0) { + const orphanCount = countLabel(orphanTranscriptPaths.length, "orphan transcript file"); + const orphanPreview = formatFilePreview(orphanTranscriptPaths); warnings.push( - `- Found ${orphanTranscriptPaths.length} orphan transcript file(s) in ${displaySessionsDir}. They are not referenced by sessions.json and can consume disk over time.`, + [ + `- Found ${orphanCount} in ${displaySessionsDir}.`, + " These .jsonl files are no longer referenced by sessions.json, so they are not part of any active session history.", + " Doctor can archive them safely by renaming each file to *.deleted..", + ` Examples: ${orphanPreview}`, + ].join("\n"), ); const archiveOrphans = await prompter.confirmSkipInNonInteractive({ - message: `Archive ${orphanTranscriptPaths.length} orphan transcript file(s) in ${displaySessionsDir}?`, + message: `Archive ${orphanCount} in ${displaySessionsDir}? This only renames them to *.deleted..`, initialValue: false, }); if (archiveOrphans) { @@ -792,7 +812,9 @@ export async function noteStateIntegrity( } } if (archived > 0) { - changes.push(`- Archived ${archived} orphan transcript file(s) in ${displaySessionsDir}`); + changes.push( + `- Archived ${countLabel(archived, "orphan transcript file")} in ${displaySessionsDir} as .deleted timestamped backups.`, + ); } } }