@@ -319,6 +319,11 @@ def generate_narration_via_llm(
319319 "that satisfy the revision notes over a full rewrite."
320320 )
321321 else :
322+ if not source_texts :
323+ raise ValueError (
324+ "generate mode requires source documentation — pick files in the wizard "
325+ "or set narration_from_source.context.paths"
326+ )
322327 user_parts = [
323328 f"Write a narration script focused on: { focus } ." ,
324329 "Do not mention segment numbers, file stems, ordinals, or any 'segment NN' phrasing." ,
@@ -558,6 +563,12 @@ def api_generate_narration():
558563 except Exception :
559564 topic_label = None
560565
566+ effective_mode = str (mode or "generate" ).strip ().lower ()
567+ if effective_mode not in ("generate" , "revise" ):
568+ effective_mode = "generate"
569+ if effective_mode == "generate" and current_narration .strip () and revision_notes .strip ():
570+ effective_mode = "revise"
571+
561572 root = cfg .repo_root if cfg else Path .cwd ()
562573 wiz = cfg .wizard_config if cfg else {}
563574
@@ -587,10 +598,19 @@ def api_generate_narration():
587598 fpath = (root / rel_s ).resolve ()
588599 if not _is_under_root (fpath , root ):
589600 return jsonify ({"error" : f"path escapes repo_root: { rel_s } " }), 400
590- if fpath .exists () and fpath .is_file ():
591- source_texts .append (
592- f"## File: { rel_s } \n { fpath .read_text (encoding = 'utf-8' , errors = 'replace' )} "
601+ if not fpath .is_file ():
602+ return jsonify ({"error" : f"file not found: { rel_s } " }), 400
603+ source_texts .append (
604+ f"## File: { rel_s } \n { fpath .read_text (encoding = 'utf-8' , errors = 'replace' )} "
605+ )
606+
607+ if effective_mode != "revise" and not source_texts :
608+ return jsonify ({
609+ "error" : (
610+ "generate mode requires source files — pick files in the wizard "
611+ "or set narration_from_source.context.paths / hint wiring"
593612 )
613+ }), 400
594614
595615 try :
596616 narration = generate_narration_via_llm (
@@ -602,7 +622,7 @@ def api_generate_narration():
602622 revision_notes = revision_notes ,
603623 topic_label = topic_label ,
604624 current_narration = current_narration ,
605- mode = mode ,
625+ mode = effective_mode ,
606626 cfg = cfg ,
607627 )
608628 except ValueError as exc :
@@ -615,12 +635,6 @@ def api_generate_narration():
615635 out = narration_dir / f"{ segment_name } .md"
616636 out .write_text (narration + "\n " , encoding = "utf-8" )
617637
618- effective_mode = str (mode or "generate" ).strip ().lower ()
619- if effective_mode not in ("generate" , "revise" ):
620- effective_mode = "generate"
621- if effective_mode == "generate" and current_narration .strip () and revision_notes .strip ():
622- effective_mode = "revise"
623-
624638 return jsonify ({
625639 "narration" : narration ,
626640 "path" : str (out ),
@@ -856,10 +870,14 @@ def _run_segment_step(cfg: Any, step: str, segment_id: str) -> dict[str, Any]:
856870 if out .is_file ():
857871 try :
858872 timing = _json .loads (out .read_text (encoding = "utf-8" ))
859- except _json .JSONDecodeError :
860- timing = {}
873+ except _json .JSONDecodeError as exc :
874+ raise RuntimeError (
875+ f"{ out .name } is not valid JSON; fix or delete it before timestamps"
876+ ) from exc
861877 if not isinstance (timing , dict ):
862- timing = {}
878+ raise RuntimeError (
879+ f"{ out .name } root must be a JSON object, not { type (timing ).__name__ } "
880+ )
863881 timing [mp3 .stem ] = block
864882 out .parent .mkdir (parents = True , exist_ok = True )
865883 out .write_text (
0 commit comments