@@ -94,6 +94,35 @@ def _spin(label: str, fn):
9494 return fn ()
9595
9696
97+ def _outline_gate (uid , project_id , abstract , create_fn , * , is_article , gate_on : bool ):
98+ """Manual-mode checkpoint after `new`: show the outline (and thesis) BEFORE any
99+ prose is written. Reviewing 6 headings costs the human 30 seconds; a bad outline
100+ costs the whole run. Enter accepts; 'r' regenerates; 'g' regenerates with guidance.
101+ Skipped for autonomous runs and non-interactive stdin (tests, pipes, CI)."""
102+ if not gate_on or not sys .stdin .isatty ():
103+ return project_id
104+ for _round in range (3 ): # cap regenerations - the planner won't improve forever
105+ paths = ArticlePaths (project_id , uid ) if is_article else BookPaths (project_id , uid )
106+ outline_md = brain .read_text (paths .outline_md if is_article else paths .toc ) or ""
107+ print ("\n " + outline_md .strip ())
108+ thesis_md = brain .read_text (paths .root / "thesis.md" ) if is_article else None
109+ if thesis_md :
110+ claim = next ((ln for ln in thesis_md .splitlines () if ln .startswith ("**Claim:**" )), "" )
111+ if claim :
112+ print (f"\n { claim } " )
113+ ans = input ("\n [Enter] start writing · r regenerate outline · "
114+ "g regenerate with guidance: " ).strip ().lower ()
115+ if ans not in ("r" , "g" ):
116+ return project_id
117+ if ans == "g" :
118+ guidance = input ("guidance for the planner: " ).strip ()
119+ if guidance :
120+ abstract = f"{ abstract } \n \n Author guidance (honor exactly): { guidance } "
121+ orchestrator .delete_book (uid , project_id )
122+ project_id = create_fn (abstract )
123+ return project_id
124+
125+
97126def _cmd_new_book (args , cfg , settings , uid , abstract ):
98127 directions = _spin ("planning directions" , lambda : nodes .planner_directions (cfg , abstract )).directions
99128 for i , d in enumerate (directions , 1 ):
@@ -102,11 +131,16 @@ def _cmd_new_book(args, cfg, settings, uid, abstract):
102131 chosen = directions [idx - 1 ]
103132 chapters = args .chapters or settings .num_chapters
104133 max_rev = args .max_revisions if args .max_revisions is not None else settings .max_revisions
134+ autonomous = _autonomous_value (args , settings )
105135 print (f"\n -> { chosen .title } " )
106- book_id = _spin ("building plan + TOC" , lambda : orchestrator .start_book (
107- cfg , settings , uid , abstract , chosen , args .book_id , chapters , max_rev ,
108- autonomous = _autonomous_value (args , settings ),
109- humanize = (False if getattr (args , "no_humanize" , False ) else None )))
136+
137+ def _create (abs_ ):
138+ return _spin ("building plan + TOC" , lambda : orchestrator .start_book (
139+ cfg , settings , uid , abs_ , chosen , args .book_id , chapters , max_rev ,
140+ autonomous = autonomous ,
141+ humanize = (False if getattr (args , "no_humanize" , False ) else None )))
142+ book_id = _outline_gate (uid , _create (abstract ), abstract , _create ,
143+ is_article = False , gate_on = not autonomous )
110144 print (f"\n [OK] Created book '{ book_id } '." )
111145 print (f" Next: python book.py run --book-id { book_id } " )
112146
@@ -119,11 +153,16 @@ def _cmd_new_article(args, cfg, settings, uid, abstract):
119153 chosen = angles [idx - 1 ]
120154 num_sections = args .chapters or settings .num_sections
121155 max_rev = args .max_revisions if args .max_revisions is not None else settings .max_revisions
156+ autonomous = _autonomous_value (args , settings )
122157 print (f"\n -> { chosen .title } " )
123- article_id = _spin ("building outline" , lambda : orchestrator .start_article (
124- cfg , settings , uid , abstract , chosen , args .book_id , num_sections , max_rev ,
125- autonomous = _autonomous_value (args , settings ),
126- humanize = (False if getattr (args , "no_humanize" , False ) else None )))
158+
159+ def _create (abs_ ):
160+ return _spin ("building outline" , lambda : orchestrator .start_article (
161+ cfg , settings , uid , abs_ , chosen , args .book_id , num_sections , max_rev ,
162+ autonomous = autonomous ,
163+ humanize = (False if getattr (args , "no_humanize" , False ) else None )))
164+ article_id = _outline_gate (uid , _create (abstract ), abstract , _create ,
165+ is_article = True , gate_on = not autonomous )
127166 print (f"\n [OK] Created article '{ article_id } '." )
128167 print (f" Next: python book.py run --book-id { article_id } " )
129168
@@ -303,7 +342,8 @@ def cmd_write(args, cfg, settings, uid):
303342
304343def cmd_run (args , cfg , settings , uid ):
305344 orchestrator .run (cfg , uid , _resolve_book (uid , args .book_id ),
306- force = getattr (args , "force" , False ))
345+ force = getattr (args , "force" , False ),
346+ autonomous = getattr (args , "autonomous" , None ))
307347
308348
309349def cmd_status (args , cfg , settings , uid ):
@@ -360,6 +400,15 @@ def cmd_review(args, cfg, settings, uid):
360400 f"--book-id { book_id } " )
361401
362402
403+ def cmd_revise (args , cfg , settings , uid ):
404+ """Post-completion revision: rewrite one committed chapter/section to an instruction."""
405+ if args .chapter is None or not args .instruction :
406+ sys .exit ('revise needs --chapter and --instruction (e.g. revise --chapter 3 '
407+ '--instruction "more technical, add a benchmark table")' )
408+ book_id = _resolve_book (uid , args .book_id )
409+ orchestrator .revise_unit (cfg , uid , book_id , args .chapter , args .instruction )
410+
411+
363412def cmd_read (args , cfg , settings , uid ):
364413 # Articles and books store a manuscript/sections at different paths; pick the
365414 # right one so `read --manuscript` works for both project types.
@@ -504,6 +553,7 @@ def cmd_config(args, cfg, settings, uid):
504553
505554_COMMANDS = {
506555 "new" : cmd_new , "write" : cmd_write , "run" : cmd_run , "status" : cmd_status , "review" : cmd_review ,
556+ "revise" : cmd_revise ,
507557 "read" : cmd_read , "memory" : cmd_memory , "produce" : cmd_produce ,
508558 "consolidate" : cmd_consolidate , "skills" : cmd_skills , "config" : cmd_config ,
509559 "list" : cmd_list , "export" : cmd_export , "seed-skills" : cmd_seed_skills ,
@@ -544,12 +594,23 @@ def build_parser(settings):
544594
545595 p_run = sub .add_parser ("run" , parents = [common ], help = "Drive the pipeline until done or escalation" )
546596 p_run .add_argument ("--force" , action = "store_true" , help = "Proceed past a consolidation review" )
597+ # Tri-state (default None): switch the project's run mode as it resumes. --autonomous
598+ # also unblocks an escalated unit so the run finishes without pausing.
599+ p_run .add_argument ("--autonomous" , dest = "autonomous" , action = "store_const" , const = True ,
600+ default = None , help = "Stop pausing for review; commit best drafts and finish" )
601+ p_run .add_argument ("--manual" , dest = "autonomous" , action = "store_const" , const = False ,
602+ help = "Re-enable human-in-the-loop review at each chapter/section" )
547603 sub .add_parser ("status" , parents = [common ], help = "Show run state + open reviews" )
548604
549605 p_rev = sub .add_parser ("review" , parents = [common ], help = "Answer an escalation" )
550606 p_rev .add_argument ("--chapter" , type = int )
551607 p_rev .add_argument ("--instruction" )
552608
609+ p_revise = sub .add_parser ("revise" , parents = [common ],
610+ help = "Rewrite one committed chapter/section of a finished piece" )
611+ p_revise .add_argument ("--chapter" , type = int , help = "Chapter/section number to rewrite" )
612+ p_revise .add_argument ("--instruction" , help = "What to change, in your words" )
613+
553614 p_read = sub .add_parser ("read" , parents = [common ], help = "Print chapter/summary/manuscript" )
554615 p_read .add_argument ("--chapter" , type = int )
555616 p_read .add_argument ("--summary" , action = "store_true" )
0 commit comments