doctor: clarify orphan transcript archive prompt

This commit is contained in:
Gustavo Madeira Santana
2026-03-18 04:14:47 +00:00
parent e5eda19db2
commit f842de046c
2 changed files with 31 additions and 6 deletions

View File

@@ -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.<timestamp>."),
);
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.<timestamp>."),
}),
);
const files = fs.readdirSync(sessionsDir);

View File

@@ -27,6 +27,19 @@ type DoctorPrompterLike = {
}) => Promise<boolean>;
};
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.<timestamp>.",
` 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.<timestamp>.`,
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.`,
);
}
}
}