feat: add version migration check to doctor command

Detects books using legacy pre-v0.6 format (no story/state/ directory)
and advises running write next to auto-migrate.

Addresses #95
This commit is contained in:
Ma
2026-03-28 20:42:35 +08:00
parent 75a70ece5d
commit 4c0ffed7ff
2 changed files with 35 additions and 5 deletions
+30
View File
@@ -117,6 +117,36 @@ export const doctorCommand = new Command("doctor")
checks.push({ name: "Books", ok: true, detail: "0 books" });
}
// 5b. Check version migration status
{
const { existsSync } = await import("node:fs");
const hasStructuredState = existsSync(join(root, "books"));
if (hasStructuredState) {
const { StateManager } = await import("@actalk/inkos-core");
const sm = new StateManager(root);
const bookIds = await sm.listBooks();
let legacyCount = 0;
for (const bid of bookIds) {
const stateDir = join(sm.bookDir(bid), "story", "state");
const hasNewState = existsSync(stateDir);
if (!hasNewState) legacyCount++;
}
if (legacyCount > 0) {
checks.push({
name: "Version Migration",
ok: false,
detail: `${legacyCount} book(s) using legacy format (pre-v0.6). Run 'inkos write next' on each to auto-migrate, or re-init with 'inkos init'.`,
});
} else if (bookIds.length > 0) {
checks.push({
name: "Version Migration",
ok: true,
detail: "All books use current format",
});
}
}
}
// 6. API connectivity test
try {
const { createLLMClient, chatCompletion, LLMConfigSchema, isApiKeyOptionalForEndpoint } = await import("@actalk/inkos-core");
@@ -430,11 +430,11 @@ describe("StateManager", () => {
await writeFile(join(chapDir, "0001_ch1.md"), "# Chapter 1\nContent 1", "utf-8");
await writeFile(join(chapDir, "0002_ch2.md"), "# Chapter 2\nContent 2", "utf-8");
await writeFile(join(chapDir, "0003_ch3.md"), "# Chapter 3\nContent 3", "utf-8");
const fullIndex = [
{ number: 1, title: "Ch1", status: "approved", wordCount: 100, createdAt: "", updatedAt: "", auditIssues: [] },
{ number: 2, title: "Ch2", status: "approved", wordCount: 100, createdAt: "", updatedAt: "", auditIssues: [] },
{ number: 3, title: "Ch3", status: "approved", wordCount: 100, createdAt: "", updatedAt: "", auditIssues: [] },
];
const mkEntry = (n: number) => ({
number: n, title: `Ch${n}`, status: "approved" as const, wordCount: 100,
createdAt: "", updatedAt: "", auditIssues: [] as string[], lengthWarnings: [] as string[],
});
const fullIndex = [mkEntry(1), mkEntry(2), mkEntry(3)];
await manager.saveChapterIndex(rwBookId, fullIndex);
// Snapshot state at chapter 1 (before chapter 2)