@@ -80,6 +80,7 @@ def parse_repository(
8080 name : str = None ,
8181 diff_manifest : str | None = None ,
8282 fresh : bool = False ,
83+ library_mode : bool = False ,
8384) -> ParseResult :
8485 """Parse a repository into an OpenAnt dataset.
8586
@@ -96,6 +97,8 @@ def parse_repository(
9697 fresh: If True, delete existing dataset.json before parsing so all
9798 units are regenerated from scratch. Only dataset.json is deleted;
9899 other artifacts in output_dir (e.g. analyzer outputs) are preserved.
100+ library_mode: If True, seed the public API surface as reachability
101+ entry points (opt-in, union-only).
99102
100103 Returns:
101104 ParseResult with paths to generated files and stats.
@@ -127,19 +130,19 @@ def parse_repository(
127130
128131 # Dispatch to the right parser
129132 if language == "python" :
130- result = _parse_python (repo_path , output_dir , processing_level , skip_tests , name )
133+ result = _parse_python (repo_path , output_dir , processing_level , skip_tests , name , library_mode )
131134 elif language == "javascript" :
132- result = _parse_javascript (repo_path , output_dir , processing_level , skip_tests , name )
135+ result = _parse_javascript (repo_path , output_dir , processing_level , skip_tests , name , library_mode )
133136 elif language == "go" :
134- result = _parse_go (repo_path , output_dir , processing_level , skip_tests , name )
137+ result = _parse_go (repo_path , output_dir , processing_level , skip_tests , name , library_mode )
135138 elif language == "c" :
136- result = _parse_c (repo_path , output_dir , processing_level , skip_tests , name )
139+ result = _parse_c (repo_path , output_dir , processing_level , skip_tests , name , library_mode )
137140 elif language == "ruby" :
138- result = _parse_ruby (repo_path , output_dir , processing_level , skip_tests , name )
141+ result = _parse_ruby (repo_path , output_dir , processing_level , skip_tests , name , library_mode )
139142 elif language == "php" :
140- result = _parse_php (repo_path , output_dir , processing_level , skip_tests , name )
143+ result = _parse_php (repo_path , output_dir , processing_level , skip_tests , name , library_mode )
141144 elif language == "zig" :
142- result = _parse_zig (repo_path , output_dir , processing_level , skip_tests , name )
145+ result = _parse_zig (repo_path , output_dir , processing_level , skip_tests , name , library_mode )
143146 else :
144147 raise ValueError (f"Unsupported language: { language } " )
145148
@@ -207,11 +210,18 @@ def _maybe_apply_diff_filter(
207210# Reachability filter (shared by Python path; JS/Go handle it internally)
208211# ---------------------------------------------------------------------------
209212
213+ # library_seed_ids is now shared in utilities/agentic_enhancer/entry_point_detector.py
214+ # so every parser pipeline (not just Python) can seed the public API. It is loaded
215+ # below via the same importlib path as EntryPointDetector to dodge the heavy
216+ # utilities/__init__ imports.
217+
218+
210219def apply_reachability_filter (
211220 dataset : dict ,
212221 output_dir : str ,
213222 processing_level : str ,
214223 extra_entry_points : "set[str] | None" = None ,
224+ library_mode : bool = False ,
215225) -> dict :
216226 """Filter dataset units to only those reachable from entry points.
217227
@@ -254,6 +264,8 @@ def _load_module(name, filename):
254264 _epd = _load_module ("entry_point_detector" , "entry_point_detector.py" )
255265 _ra = _load_module ("reachability_analyzer" , "reachability_analyzer.py" )
256266 EntryPointDetector = _epd .EntryPointDetector
267+ blackout_warning = _epd .blackout_warning
268+ library_seed_ids = _epd .library_seed_ids
257269 ReachabilityAnalyzer = _ra .ReachabilityAnalyzer
258270
259271 call_graph_path = os .path .join (output_dir , "call_graph.json" )
@@ -277,6 +289,11 @@ def _load_module(name, filename):
277289 entry_points = detector .detect_entry_points ()
278290 if extra_entry_points :
279291 entry_points = entry_points | extra_entry_points
292+ # Library-mode (opt-in): the public API is the entry surface. Union-only —
293+ # never demotes a structurally-detected app entry point, so an app scan with
294+ # the flag on can only gain reachable units, never lose one.
295+ if library_mode :
296+ entry_points = entry_points | library_seed_ids (functions )
280297
281298 units = dataset .get ("units" , [])
282299 original_count = len (units )
@@ -349,6 +366,12 @@ def _load_module(name, filename):
349366 file = sys .stderr ,
350367 )
351368
369+ _blackout = blackout_warning (detector .entry_point_details , original_count ,
370+ len (filtered_units ), library_mode = library_mode )
371+ if _blackout :
372+ dataset ["metadata" ]["reachability_filter" ]["warning" ] = _blackout
373+ print (f" [Warning] { _blackout } " , file = sys .stderr )
374+
352375 # Warn about unimplemented higher-level filters
353376 if processing_level == "codeql" :
354377 print (
@@ -374,7 +397,7 @@ def _load_module(name, filename):
374397# Python parser
375398# ---------------------------------------------------------------------------
376399
377- def _parse_python (repo_path : str , output_dir : str , processing_level : str , skip_tests : bool = True , name : str = None ) -> ParseResult :
400+ def _parse_python (repo_path : str , output_dir : str , processing_level : str , skip_tests : bool = True , name : str = None , library_mode : bool = False ) -> ParseResult :
378401 """Invoke the Python parser.
379402
380403 The Python parser has a clean `parse_repository()` function that we can
@@ -402,7 +425,8 @@ def _parse_python(repo_path: str, output_dir: str, processing_level: str, skip_t
402425
403426 # Apply reachability filter if processing_level requires it
404427 if processing_level != "all" :
405- dataset = _apply_reachability_filter (dataset , output_dir , processing_level )
428+ dataset = _apply_reachability_filter (dataset , output_dir , processing_level ,
429+ library_mode = library_mode )
406430
407431 # Write outputs
408432 write_json (dataset_path , dataset )
@@ -523,7 +547,7 @@ def _file_lock(lock_path: Path):
523547 f .close ()
524548
525549
526- def _parse_javascript (repo_path : str , output_dir : str , processing_level : str , skip_tests : bool = True , name : str = None ) -> ParseResult :
550+ def _parse_javascript (repo_path : str , output_dir : str , processing_level : str , skip_tests : bool = True , name : str = None , library_mode : bool = False ) -> ParseResult :
527551 """Invoke the JavaScript/TypeScript parser.
528552
529553 The JS parser is a PipelineTest class that runs Node.js subprocesses.
@@ -547,6 +571,8 @@ def _parse_javascript(repo_path: str, output_dir: str, processing_level: str, sk
547571 cmd .extend (["--name" , name ])
548572 if skip_tests :
549573 cmd .append ("--skip-tests" )
574+ if library_mode :
575+ cmd .append ("--library-mode" )
550576
551577 result = subprocess .run (
552578 cmd ,
@@ -582,7 +608,7 @@ def _parse_javascript(repo_path: str, output_dir: str, processing_level: str, sk
582608# Go parser
583609# ---------------------------------------------------------------------------
584610
585- def _parse_go (repo_path : str , output_dir : str , processing_level : str , skip_tests : bool = True , name : str = None ) -> ParseResult :
611+ def _parse_go (repo_path : str , output_dir : str , processing_level : str , skip_tests : bool = True , name : str = None , library_mode : bool = False ) -> ParseResult :
586612 """Invoke the Go parser.
587613
588614 The Go parser is a PipelineTest class that calls a compiled Go binary.
@@ -603,6 +629,8 @@ def _parse_go(repo_path: str, output_dir: str, processing_level: str, skip_tests
603629 cmd .extend (["--name" , name ])
604630 if skip_tests :
605631 cmd .append ("--skip-tests" )
632+ if library_mode :
633+ cmd .append ("--library-mode" )
606634
607635 result = subprocess .run (
608636 cmd ,
@@ -638,7 +666,7 @@ def _parse_go(repo_path: str, output_dir: str, processing_level: str, skip_tests
638666# C/C++ parser
639667# ---------------------------------------------------------------------------
640668
641- def _parse_c (repo_path : str , output_dir : str , processing_level : str , skip_tests : bool = True , name : str = None ) -> ParseResult :
669+ def _parse_c (repo_path : str , output_dir : str , processing_level : str , skip_tests : bool = True , name : str = None , library_mode : bool = False ) -> ParseResult :
642670 """Invoke the C/C++ parser.
643671
644672 The C parser uses tree-sitter for function extraction and call graph
@@ -661,6 +689,8 @@ def _parse_c(repo_path: str, output_dir: str, processing_level: str, skip_tests:
661689 cmd .extend (["--name" , name ])
662690 if skip_tests :
663691 cmd .append ("--skip-tests" )
692+ if library_mode :
693+ cmd .append ("--library-mode" )
664694
665695 result = subprocess .run (
666696 cmd ,
@@ -697,7 +727,7 @@ def _parse_c(repo_path: str, output_dir: str, processing_level: str, skip_tests:
697727# Ruby parser
698728# ---------------------------------------------------------------------------
699729
700- def _parse_ruby (repo_path : str , output_dir : str , processing_level : str , skip_tests : bool = True , name : str = None ) -> ParseResult :
730+ def _parse_ruby (repo_path : str , output_dir : str , processing_level : str , skip_tests : bool = True , name : str = None , library_mode : bool = False ) -> ParseResult :
701731 """Invoke the Ruby parser.
702732
703733 The Ruby parser uses tree-sitter for function extraction and call graph
@@ -720,6 +750,8 @@ def _parse_ruby(repo_path: str, output_dir: str, processing_level: str, skip_tes
720750 cmd .extend (["--name" , name ])
721751 if skip_tests :
722752 cmd .append ("--skip-tests" )
753+ if library_mode :
754+ cmd .append ("--library-mode" )
723755
724756 result = subprocess .run (
725757 cmd ,
@@ -756,7 +788,7 @@ def _parse_ruby(repo_path: str, output_dir: str, processing_level: str, skip_tes
756788# PHP parser
757789# ---------------------------------------------------------------------------
758790
759- def _parse_php (repo_path : str , output_dir : str , processing_level : str , skip_tests : bool = True , name : str = None ) -> ParseResult :
791+ def _parse_php (repo_path : str , output_dir : str , processing_level : str , skip_tests : bool = True , name : str = None , library_mode : bool = False ) -> ParseResult :
760792 """Invoke the PHP parser.
761793
762794 The PHP parser uses tree-sitter for function extraction and call graph
@@ -779,6 +811,8 @@ def _parse_php(repo_path: str, output_dir: str, processing_level: str, skip_test
779811 cmd .extend (["--name" , name ])
780812 if skip_tests :
781813 cmd .append ("--skip-tests" )
814+ if library_mode :
815+ cmd .append ("--library-mode" )
782816
783817 result = subprocess .run (
784818 cmd ,
@@ -815,7 +849,7 @@ def _parse_php(repo_path: str, output_dir: str, processing_level: str, skip_test
815849# Zig parser
816850# ---------------------------------------------------------------------------
817851
818- def _parse_zig (repo_path : str , output_dir : str , processing_level : str , skip_tests : bool = True , name : str = None ) -> ParseResult :
852+ def _parse_zig (repo_path : str , output_dir : str , processing_level : str , skip_tests : bool = True , name : str = None , library_mode : bool = False ) -> ParseResult :
819853 """Invoke the Zig parser.
820854
821855 The Zig parser uses tree-sitter for function extraction and call graph
@@ -838,6 +872,8 @@ def _parse_zig(repo_path: str, output_dir: str, processing_level: str, skip_test
838872 cmd .extend (["--name" , name ])
839873 if skip_tests :
840874 cmd .append ("--skip-tests" )
875+ if library_mode :
876+ cmd .append ("--library-mode" )
841877
842878 result = subprocess .run (
843879 cmd ,
0 commit comments