@@ -798,6 +798,125 @@ def annotate_foo_files_in_place():
798798 except Exception as e :
799799 logger .print (f"Warning: Failed to auto-annotate foo_files.py: { e } " )
800800
801+ # ============================================================================
802+ # --- Orphanage & Repository Profiling ---
803+ # ============================================================================
804+ STORY_EXTENSIONS = {
805+ '.py' , '.js' , '.css' , '.html' , '.md' , '.markdown' , '.txt' ,
806+ '.json' , '.nix' , '.sh' , '.ipynb' , '.toml' , '.in' , '.cfg' ,
807+ '.svg' , '.xsd' ,
808+ }
809+
810+ def collect_repo_files (repo_root : str ) -> set :
811+ """Use `git ls-files` to get only tracked, non-ignored files."""
812+ try :
813+ result = subprocess .run (
814+ ['git' , 'ls-files' ],
815+ capture_output = True , text = True , cwd = repo_root , check = True
816+ )
817+ repo_files = set ()
818+ for line in result .stdout .strip ().splitlines ():
819+ line = line .strip ()
820+ if not line :
821+ continue
822+ ext = os .path .splitext (line )[1 ].lower ()
823+ if ext in STORY_EXTENSIONS :
824+ repo_files .add (line )
825+ return repo_files
826+ except (subprocess .CalledProcessError , FileNotFoundError ):
827+ logger .print ("⚠️ `git ls-files` failed. Cannot run Orphanage check.\n " )
828+ return set ()
829+
830+ def update_orphanage_in_place ():
831+ """Finds unmapped files in the repo and injects them into the Orphanage section of foo_files.py."""
832+ foo_path = os .path .join (REPO_ROOT , "foo_files.py" )
833+ if not os .path .exists (foo_path ):
834+ return
835+
836+ try :
837+ with open (foo_path , "r" , encoding = "utf-8" ) as f :
838+ lines = f .readlines ()
839+
840+ # Phase 1: Parse the current map to see what is already "claimed"
841+ in_story_section = False
842+ all_claimed_files = set ()
843+
844+ for line in lines :
845+ line = line .strip ()
846+ if "AI_PHOOEY_CHOP =" in line :
847+ in_story_section = True
848+ continue
849+ if not in_story_section :
850+ continue
851+ if "VIII. THE ORPHANAGE" in line :
852+ break # Stop before parsing the orphans themselves
853+
854+ clean_line = line .lstrip ("#" ).strip ()
855+ if (not clean_line or clean_line .startswith ("=" ) or
856+ clean_line .startswith ("CHAPTER" ) or clean_line .startswith ("THE 404" ) or
857+ clean_line .startswith ("!" ) or clean_line .startswith ("http" )):
858+ continue
859+
860+ file_path = clean_line .split ()[0 ]
861+ ext = os .path .splitext (file_path )[1 ].lower ()
862+ if ext in STORY_EXTENSIONS or ('/' in file_path and '.' in file_path ):
863+ if os .path .isabs (file_path ):
864+ if file_path .startswith (REPO_ROOT ):
865+ rel_path = os .path .relpath (file_path , REPO_ROOT )
866+ all_claimed_files .add (os .path .normpath (rel_path ))
867+ else :
868+ all_claimed_files .add (os .path .normpath (file_path ))
869+
870+ # Phase 2: Diff the map against the territory
871+ repo_files = collect_repo_files (REPO_ROOT )
872+ if not repo_files :
873+ return # Bail if git failed
874+
875+ orphans = sorted (repo_files - all_claimed_files )
876+
877+ # Phase 3: Inject the orphans idempotently
878+ with open (foo_path , "r" , encoding = "utf-8" ) as f :
879+ foo_content = f .read ()
880+
881+ ORPHAN_MARKER = "# ============================================================================\n # VIII. THE ORPHANAGE (Uncovered Files)\n # ============================================================================"
882+ marker_index = foo_content .find (ORPHAN_MARKER )
883+
884+ if marker_index != - 1 :
885+ base_content = foo_content [:marker_index ].rstrip () + "\n \n "
886+ else :
887+ end_quote_idx = foo_content .rfind ('"""' )
888+ base_content = foo_content [:end_quote_idx ].rstrip () + "\n \n "
889+
890+ if not orphans :
891+ with open (foo_path , "w" , encoding = "utf-8" ) as f :
892+ f .write (base_content + '\n """\n ' )
893+ return # Clean exit, no orphans
894+
895+ orphan_lines = [
896+ ORPHAN_MARKER ,
897+ "# Files tracked by git but not listed in any chapter above." ,
898+ "# Move these into the active chapters to grant the AI visibility.\n "
899+ ]
900+
901+ logger .print (f"👻 Injecting { len (orphans )} unmapped files into the Orphanage..." )
902+ for orphan_path in orphans :
903+ full_path = os .path .join (REPO_ROOT , orphan_path )
904+ try :
905+ with open (full_path , "r" , encoding = "utf-8" ) as f :
906+ content = f .read ()
907+ tokens = count_tokens (content )
908+ b_size = len (content .encode ('utf-8' ))
909+ orphan_lines .append (f"# { orphan_path } # [{ tokens :,} tokens | { b_size :,} bytes]" )
910+ except Exception :
911+ orphan_lines .append (f"# { orphan_path } # [Error reading file]" )
912+
913+ final_content = base_content + "\n " .join (orphan_lines ) + '\n """\n '
914+
915+ with open (foo_path , "w" , encoding = "utf-8" ) as f :
916+ f .write (final_content )
917+
918+ except Exception as e :
919+ logger .print (f"Warning: Failed to update the Orphanage: { e } " )
801920
802921# ============================================================================
803922# --- Main Execution Logic ---
@@ -862,7 +981,8 @@ def main():
862981 with open ("prompt.md" , 'r' , encoding = 'utf-8' ) as f : prompt_content = f .read ()
863982
864983 # 2. Process all specified files
865- annotate_foo_files_in_place () # <-- ADD THIS LINE
984+ annotate_foo_files_in_place ()
985+ update_orphanage_in_place () # <-- THE NEW SENSOR PING
866986 files_to_process = parse_file_list_from_config ()
867987 processed_files_data = []
868988
0 commit comments