[docs]defmaterialize_exploop_instructions(repo_dir:Path)->ExploopInstructionFiles:"""Copy exploop agent guidance into a workspace without any git setup."""repo_dir.mkdir(parents=True,exist_ok=True)guide=load_exploop_guide()ifnotguide.strip():raiseFileNotFoundError("Missing packaged exploop AGENTS.md guide.")agents_path=repo_dir/EXPLOOP_AGENTS_FILENAME_write_authoritative_file(agents_path,guide)alias_paths:list[Path]=[]foralias_nameinEXPLOOP_AGENTS_ALIAS_FILENAMES:alias_path=repo_dir/alias_name_ensure_agents_alias(agents_path,alias_path)alias_paths.append(alias_path)returnExploopInstructionFiles(agents_path=agents_path,alias_paths=tuple(alias_paths),)
def_write_authoritative_file(path:Path,content:str)->None:ifpath.exists()orpath.is_symlink():existing_text=_safe_read_text(path)ifnotpath.is_symlink()andpath.is_file()andexisting_text==content:returnifpath.is_dir()andnotpath.is_symlink():raiseFileExistsError(f"Cannot write exploop AGENTS.md over directory: {path}")ifexisting_textandexisting_text!=content:_backup_existing_content(path)path.unlink()path.write_text(content,encoding="utf-8")def_ensure_agents_alias(agents_path:Path,alias_path:Path)->None:ifalias_path.exists()oralias_path.is_symlink():if_symlink_points_to(alias_path,agents_path):returnifalias_path.is_file()and_safe_read_text(alias_path)==_safe_read_text(agents_path):alias_path.unlink()elifalias_path.is_dir()andnotalias_path.is_symlink():raiseFileExistsError(f"Cannot write exploop AGENTS.md alias over directory: {alias_path}")else:_backup_existing_content(alias_path)alias_path.unlink()_create_relative_symlink_or_copy(agents_path,alias_path)def_create_relative_symlink_or_copy(source_path:Path,target_path:Path)->None:relative_target=Path(os.path.relpath(source_path,start=target_path.parent))try:target_path.symlink_to(relative_target)exceptOSError:shutil.copy2(source_path,target_path)def_symlink_points_to(target_path:Path,source_path:Path)->bool:ifnottarget_path.is_symlink():returnFalsereturn(target_path.parent/target_path.readlink()).resolve()==source_path.resolve()def_backup_existing_content(path:Path)->None:backup_path=_next_backup_path(path)try:content=path.read_text(encoding="utf-8",errors="replace")exceptOSError:returnbackup_path.write_text(content,encoding="utf-8")def_next_backup_path(path:Path)->Path:first=path.with_name(path.name+EXPLOOP_BACKUP_SUFFIX)ifnotfirst.exists()andnotfirst.is_symlink():returnfirstindex=1whileTrue:candidate=path.with_name(f"{path.name}{EXPLOOP_BACKUP_SUFFIX}.{index}")ifnotcandidate.exists()andnotcandidate.is_symlink():returncandidateindex+=1def_safe_read_text(path:Path)->str:try:returnpath.read_text(encoding="utf-8",errors="replace")exceptOSError:return""