|
|
@@ -45,7 +45,26 @@ export interface ParsedBookRules {
|
|
|
readonly body: string;
|
|
|
}
|
|
|
|
|
|
-export function parseBookRules(raw: string): ParsedBookRules {
|
|
|
+/**
|
|
|
+ * Phase 5 cleanup #3: book_rules.md is now a compat pointer shim — the
|
|
|
+ * authoritative YAML frontmatter lives on outline/story_frame.md. Detect a
|
|
|
+ * shim by its architect-emitted heading + pointer line so we can avoid
|
|
|
+ * treating it as a legitimate (default-empty) rules source.
|
|
|
+ *
|
|
|
+ * Markers (must match buildBookRulesShim() in architect.ts):
|
|
|
+ * - 本书规则(兼容指针——已废弃) / Book Rules (compat pointer — deprecated)
|
|
|
+ * - 本文件仅为外部读取保留 / This file is kept for external readers only
|
|
|
+ */
|
|
|
+export function isBookRulesShim(raw: string): boolean {
|
|
|
+ return (
|
|
|
+ /本书规则(兼容指针——已废弃)/.test(raw)
|
|
|
+ || /Book Rules \(compat pointer — deprecated\)/.test(raw)
|
|
|
+ || /本文件仅为外部读取保留/.test(raw)
|
|
|
+ || /This file is kept for external readers only/.test(raw)
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+export function parseBookRules(raw: string): ParsedBookRules | null {
|
|
|
// Strip markdown code block wrappers if present (LLM often wraps output in ```md ... ```)
|
|
|
const stripped = raw.replace(/^```(?:md|markdown|yaml)?\s*\n/, "").replace(/\n```\s*$/, "");
|
|
|
|
|
|
@@ -58,11 +77,21 @@ export function parseBookRules(raw: string): ParsedBookRules {
|
|
|
const body = fmMatch[2].trim();
|
|
|
return { rules, body };
|
|
|
} catch {
|
|
|
- // YAML parse failed — fall through to default
|
|
|
+ // YAML parse failed — fall through to shim/default check.
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // No valid frontmatter found — return default rules with the raw content as body
|
|
|
+ // Phase hotfix 1: refuse to silently zero out rules when reading a Phase 5
|
|
|
+ // compat shim. The shim has no real rules; pretending it parses as
|
|
|
+ // "default empty" wipes protagonist / prohibitions / genreLock for any
|
|
|
+ // caller that fell back to it after a broken story_frame frontmatter.
|
|
|
+ if (isBookRulesShim(stripped)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // No valid frontmatter found and not a shim — return default rules with
|
|
|
+ // the raw content as body. Keeps backward compat for legacy book_rules.md
|
|
|
+ // files that hold only narrow narrative guidance prose.
|
|
|
const rules = BookRulesSchema.parse({});
|
|
|
return { rules, body: stripped.trim() };
|
|
|
}
|