|
|
@@ -322,17 +322,30 @@ export class StateManager {
|
|
|
}
|
|
|
|
|
|
async isCompleteBookDirectory(bookDir: string): Promise<boolean> {
|
|
|
- const requiredPaths = [
|
|
|
+ // Phase 5 cleanup: prefer outline/* paths, fall back to legacy flat files
|
|
|
+ // so older books on disk still resolve as complete.
|
|
|
+ const requiredSingle = [
|
|
|
join(bookDir, "book.json"),
|
|
|
- join(bookDir, "story", "story_bible.md"),
|
|
|
- join(bookDir, "story", "volume_outline.md"),
|
|
|
join(bookDir, "story", "book_rules.md"),
|
|
|
join(bookDir, "story", "current_state.md"),
|
|
|
join(bookDir, "story", "pending_hooks.md"),
|
|
|
join(bookDir, "chapters", "index.json"),
|
|
|
];
|
|
|
|
|
|
- for (const requiredPath of requiredPaths) {
|
|
|
+ const eitherOr: Array<ReadonlyArray<string>> = [
|
|
|
+ // story_frame (new) OR story_bible (legacy)
|
|
|
+ [
|
|
|
+ join(bookDir, "story", "outline", "story_frame.md"),
|
|
|
+ join(bookDir, "story", "story_bible.md"),
|
|
|
+ ],
|
|
|
+ // volume_map (new) OR volume_outline (legacy)
|
|
|
+ [
|
|
|
+ join(bookDir, "story", "outline", "volume_map.md"),
|
|
|
+ join(bookDir, "story", "volume_outline.md"),
|
|
|
+ ],
|
|
|
+ ];
|
|
|
+
|
|
|
+ for (const requiredPath of requiredSingle) {
|
|
|
try {
|
|
|
await stat(requiredPath);
|
|
|
} catch {
|
|
|
@@ -340,6 +353,20 @@ export class StateManager {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ for (const alternatives of eitherOr) {
|
|
|
+ let found = false;
|
|
|
+ for (const candidate of alternatives) {
|
|
|
+ try {
|
|
|
+ await stat(candidate);
|
|
|
+ found = true;
|
|
|
+ break;
|
|
|
+ } catch {
|
|
|
+ // try next alternative
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!found) return false;
|
|
|
+ }
|
|
|
+
|
|
|
return true;
|
|
|
}
|
|
|
|