@@ -392,6 +392,66 @@ def test_dry_run_requires_existing_db(self):
392392 self .assertNotEqual (result .exit_code , 0 )
393393 self .assertIn ("Database not found" , result .output )
394394
395+ @patch ("explainshell.util.collect_gz_files" )
396+ def test_reason_required_for_bulk_extraction (self , mock_collect ):
397+ mock_collect .return_value = [f"/fake/page-{ i } .1.gz" for i in range (101 )]
398+ with _temp_db () as db_path :
399+ Store .create (db_path ).close ()
400+ runner = CliRunner ()
401+ result = runner .invoke (
402+ cli ,
403+ [
404+ "--db" ,
405+ db_path ,
406+ "extract" ,
407+ "--mode" ,
408+ "llm:test-model" ,
409+ * [f"/fake/page-{ i } .1.gz" for i in range (101 )],
410+ ],
411+ )
412+ self .assertNotEqual (result .exit_code , 0 )
413+ self .assertIn ("--reason is required" , result .output )
414+ self .assertIn ("got 101" , result .output )
415+
416+ @patch ("explainshell.util.collect_gz_files" )
417+ def test_reason_gate_skipped_for_dry_run (self , mock_collect ):
418+ mock_collect .return_value = [f"/fake/page-{ i } .1.gz" for i in range (101 )]
419+ with _temp_db () as db_path :
420+ Store .create (db_path ).close ()
421+ runner = CliRunner ()
422+ result = runner .invoke (
423+ cli ,
424+ [
425+ "--db" ,
426+ db_path ,
427+ "extract" ,
428+ "--mode" ,
429+ "llm:test-model" ,
430+ "--dry-run" ,
431+ * [f"/fake/page-{ i } .1.gz" for i in range (101 )],
432+ ],
433+ )
434+ self .assertNotIn ("--reason is required" , result .output )
435+
436+ @patch ("explainshell.util.collect_gz_files" )
437+ def test_reason_gate_skipped_under_threshold (self , mock_collect ):
438+ mock_collect .return_value = [f"/fake/page-{ i } .1.gz" for i in range (100 )]
439+ with _temp_db () as db_path :
440+ Store .create (db_path ).close ()
441+ runner = CliRunner ()
442+ result = runner .invoke (
443+ cli ,
444+ [
445+ "--db" ,
446+ db_path ,
447+ "extract" ,
448+ "--mode" ,
449+ "llm:test-model" ,
450+ * [f"/fake/page-{ i } .1.gz" for i in range (100 )],
451+ ],
452+ )
453+ self .assertNotIn ("--reason is required" , result .output )
454+
395455 @patch ("explainshell.extraction.common.gz_sha256" , side_effect = lambda p : p )
396456 @patch ("explainshell.manager.run" )
397457 @patch ("explainshell.manager.make_extractor" )
@@ -2257,6 +2317,32 @@ def test_show_events_extraction(self):
22572317 self .assertIn ("model: openai/gpt-5" , result .output )
22582318 self .assertIn ("result: ok=10 skip=5 fail=1" , result .output )
22592319 self .assertIn ("db: 110(+10) mappings=220(+20)" , result .output )
2320+ self .assertIn ("reason: (no reason provided)" , result .output )
2321+
2322+ def test_show_events_extraction_with_reason (self ):
2323+ self .store .log_event (
2324+ "extraction" ,
2325+ {
2326+ "version" : 1 ,
2327+ "command" : "extract" ,
2328+ "timestamp" : "2026-05-04T10:00:00+00:00" ,
2329+ "git" : {"commit" : "abc123" , "commit_short" : "abc" , "dirty" : False },
2330+ "config" : {"mode" : "llm" , "model" : "openai/gpt-5" },
2331+ "elapsed_seconds" : 5.0 ,
2332+ "summary" : {"succeeded" : 10 , "skipped" : 0 , "failed" : 0 },
2333+ "db_before" : {"manpages" : 100 , "mappings" : 200 },
2334+ "db_after" : {"manpages" : 110 , "mappings" : 220 },
2335+ "reason" : "fix garbled emphasis after mandoc 89d8f45" ,
2336+ },
2337+ )
2338+
2339+ runner = CliRunner ()
2340+ result = runner .invoke (cli , ["--db" , self .db_path , "show" , "events" ])
2341+
2342+ self .assertEqual (result .exit_code , 0 )
2343+ self .assertIn (
2344+ "reason: fix garbled emphasis after mandoc 89d8f45" , result .output
2345+ )
22602346
22612347 def test_show_events_limit (self ):
22622348 for i in range (5 ):
@@ -2531,6 +2617,16 @@ def test_none_fields_excluded(self) -> None:
25312617
25322618 data = self ._read_report ()
25332619 self .assertNotIn ("batch_manifest" , data )
2620+ self .assertNotIn ("reason" , data )
2621+
2622+ def test_reason_embedded (self ) -> None :
2623+ """reason string is included in the JSON when provided."""
2624+
2625+ report = self ._make_report (reason = "bulk re-run after mandoc emphasis fix" )
2626+ _write_report (self ._run_dir , report )
2627+
2628+ data = self ._read_report ()
2629+ self .assertEqual (data ["reason" ], "bulk re-run after mandoc emphasis fix" )
25342630
25352631 def test_batch_manifest_embedded (self ) -> None :
25362632 """batch_manifest dict is included when provided."""
0 commit comments