feat: Implement ai_blueprint.md action plan — architectural review & optimisations

Steps 1–7 of the ai_blueprint.md action plan executed:

DOCUMENTATION (Steps 1–3, 6–7):
- docs/current_state_analysis.md: Phase-by-phase cost/quality mapping of existing pipeline
- docs/alternatives_analysis.md: 15 alternative approaches with testable hypotheses
- docs/experiment_design.md: 7 controlled A/B experiment specifications (CPC, HQS, CER metrics)
- ai_blueprint_v2.md: New recommended architecture with cost projections and experiment roadmap

CODE IMPROVEMENTS (Step 4 — Experiments 1–4 implemented):
- story/writer.py: Extract build_persona_info() — persona loaded once per book, not per chapter
- story/writer.py: Adaptive scoring thresholds — SCORE_PASSING scales 6.5→7.5 by chapter position
- story/writer.py: Beat expansion skip — if beats >100 words, skip Director's Treatment expansion
- story/planner.py: validate_outline() — pre-generation gate checks missing beats, continuity, pacing
- story/planner.py: Enrichment field validation — warn on missing title/genre after enrich()
- cli/engine.py: Wire persona cache, outline validation gate, chapter_position threading

Expected savings: ~285K tokens per 30-chapter novel (~7% cost reduction)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-22 22:01:30 -05:00
parent 6684ec2bf5
commit 2100ca2312
8 changed files with 1143 additions and 32 deletions

View File

@@ -9,6 +9,7 @@ from ai import models as ai_models
from ai import setup as ai_setup
from story import planner, writer as story_writer, editor as story_editor
from story import style_persona, bible_tracker, state as story_state
from story.writer import build_persona_info
from marketing import assets as marketing_assets
from export import exporter
@@ -99,6 +100,13 @@ def process_book(bp, folder, context="", resume=False, interactive=False):
raise
utils.log("TIMING", f"Chapter Planning: {time.time() - t_step:.1f}s")
# 4b. Outline Validation Gate (Alt 2-B: pre-generation quality check)
if chapters and not resume:
try:
planner.validate_outline(events, chapters, bp, folder)
except Exception as _e:
utils.log("ARCHITECT", f"Outline validation skipped: {_e}")
# 5. Writing Loop
ms_path = os.path.join(folder, "manuscript.json")
loaded_ms = utils.load_json(ms_path) if (resume and os.path.exists(ms_path)) else []
@@ -147,6 +155,10 @@ def process_book(bp, folder, context="", resume=False, interactive=False):
session_chapters = 0
session_time = 0
# Pre-load persona once for the entire writing phase (Alt 3-D: persona cache)
# Rebuilt after each refine_persona() call to pick up bio updates.
cached_persona = build_persona_info(bp)
i = len(ms)
while i < len(chapters):
ch_start = time.time()
@@ -178,7 +190,8 @@ def process_book(bp, folder, context="", resume=False, interactive=False):
else:
summary_ctx = summary[-8000:] if len(summary) > 8000 else summary
next_hint = chapters[i+1]['title'] if i + 1 < len(chapters) else ""
txt = story_writer.write_chapter(ch, bp, folder, summary_ctx, tracking, prev_content, next_chapter_hint=next_hint)
chap_pos = i / max(len(chapters) - 1, 1) if len(chapters) > 1 else 0.5
txt = story_writer.write_chapter(ch, bp, folder, summary_ctx, tracking, prev_content, next_chapter_hint=next_hint, prebuilt_persona=cached_persona, chapter_position=chap_pos)
except Exception as e:
utils.log("SYSTEM", f"Chapter generation failed: {e}")
if interactive:
@@ -199,6 +212,7 @@ def process_book(bp, folder, context="", resume=False, interactive=False):
if (i == 0 or i % 5 == 0) and txt:
bp['book_metadata']['author_details'] = style_persona.refine_persona(bp, txt, folder)
with open(bp_path, "w") as f: json.dump(bp, f, indent=2)
cached_persona = build_persona_info(bp) # Rebuild cache with updated bio
# Look ahead for context
next_info = ""