v0.4.3 - Germany Update #14
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Claude.ai Zips | |
| on: | |
| workflow_dispatch: | |
| release: | |
| types: [created] | |
| permissions: | |
| contents: write | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Build zips | |
| run: | | |
| mkdir -p dist | |
| # Stage 1: Per-region zips (existing structure) | |
| for region in africa americas asia europe oceania; do | |
| zip -r dist/cultures-${region}.zip regions/${region}/ | |
| done | |
| # Stage 2: Full cultures zip -- built as flat zip in Stage 5 (Python block below) | |
| # Stage 3: Per-engine platform zips (shared stack + platform-specific instructions) | |
| for platform in claude copilot gemini; do | |
| zip -j dist/engine_${platform}.zip \ | |
| engine/${platform}/instructions.md \ | |
| engine/stack.md \ | |
| engine/process_world_is_spinning.md | |
| done | |
| # Stage 4: Culture-specific zips for Claude consumption (new) | |
| # Auto-detect complete cultures (has position.md + personas + place + piece + README) | |
| # and create flat zips with all culture files + engine files | |
| python3 << 'PYTHON_EOF' | |
| import os | |
| import re | |
| import zipfile | |
| import shutil | |
| from pathlib import Path | |
| def is_complete_culture(culture_dir): | |
| """Check if culture folder has all required components.""" | |
| try: | |
| culture_path = Path(culture_dir) | |
| # Must have position file | |
| has_position = any(culture_path.glob('culture_*_position.md')) | |
| # Must have at least 2 personas | |
| personas = list(culture_path.glob('culture_*_persona_*.md')) + list(culture_path.glob('persona_*.md')) | |
| has_personas = len(personas) >= 2 | |
| # Must have at least 1 place | |
| places = list(culture_path.glob('culture_*_place_*.md')) | |
| has_places = len(places) >= 1 | |
| # Must have at least 1 piece | |
| pieces = list(culture_path.glob('culture_*_piece_*.md')) | |
| has_pieces = len(pieces) >= 1 | |
| # Must have README | |
| has_readme = (culture_path / 'README.md').exists() | |
| return has_position and has_personas and has_places and has_pieces and has_readme | |
| except: | |
| return False | |
| def is_region_manifest(region_dir): | |
| """Check if region folder is a manifest (has README.md at root).""" | |
| return (region_dir / 'README.md').exists() | |
| def rewrite_links_for_flat_zip(content): | |
| """Rewrite links from nested folder structure to flat structure. | |
| Culture files are stored in regions/europe/germany/ etc. (nested depth varies). | |
| Their links reference other files using relative paths like: | |
| - ../../engine/stack.md (traverses up to repo root, then into engine/) | |
| - ../../../engine/instructions.md (varies by depth) | |
| In the flat zip structure (culture/ subfolder), all culture files are together. | |
| Links should be simplified to just filenames: | |
| - ../../engine/stack.md → stack.md | |
| - Removes all ../ traversal and keeps just the filename | |
| """ | |
| import re | |
| # Strip all ../ or ..\\ traversal and keep just the filename | |
| # Pattern: [text](../ or .../ traversal/path/filename.md) → [text](filename.md) | |
| content = re.sub( | |
| r'\]\((?:\.\./)+([^)]+\.md)\)', | |
| lambda m: f']({m.group(1).split("/")[-1]})', | |
| content | |
| ) | |
| return content | |
| def create_culture_zip(staging_dir, zip_name): | |
| """Helper to create a single culture zip from staging directory.""" | |
| zip_path = Path(f'dist/{zip_name}.zip') | |
| with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zf: | |
| # Add known root files | |
| for root_file in ['LICENSE', 'README.md', 'REFERENCES.md']: | |
| file_path = staging_dir / root_file | |
| if file_path.exists(): | |
| zf.write(file_path, arcname=root_file) | |
| # Add any hofstede_* files at root (country zips) | |
| for hofstede_file in sorted(staging_dir.glob('hofstede_*')): | |
| zf.write(hofstede_file, arcname=hofstede_file.name) | |
| # Add hofstede/ subfolder files (regional zips) | |
| hofstede_dir = staging_dir / 'hofstede' | |
| if hofstede_dir.exists(): | |
| for hofstede_file in sorted(hofstede_dir.glob('*')): | |
| zf.write(hofstede_file, arcname=f'hofstede/{hofstede_file.name}') | |
| # Add engine/ subdirectory files | |
| for engine_file in (staging_dir / 'engine').glob('*.md'): | |
| zf.write(engine_file, arcname=f'engine/{engine_file.name}') | |
| # Add culture/ subdirectory files | |
| culture_files = list((staging_dir / 'culture').glob('*.md')) | |
| for culture_file in culture_files: | |
| zf.write(culture_file, arcname=f'culture/{culture_file.name}') | |
| return len(culture_files) | |
| def stage_culture_files(staging_dir, culture_dir, culture_name): | |
| """Stage culture files into the staging directory.""" | |
| engine_path = Path('engine') | |
| # Copy instructions.md from claude-specific or fallback to generic | |
| claude_instructions = engine_path / 'claude' / 'instructions.md' | |
| if claude_instructions.exists(): | |
| shutil.copy2(claude_instructions, staging_dir / 'engine' / 'instructions.md') | |
| # Copy stack.md | |
| stack_src = engine_path / 'stack.md' | |
| if stack_src.exists(): | |
| shutil.copy2(stack_src, staging_dir / 'engine' / 'stack.md') | |
| # Copy culture/ files (all culture_*.md files from country dir) | |
| culture_count = 0 | |
| for culture_file in culture_dir.glob('culture_*.md'): | |
| shutil.copy2(culture_file, staging_dir / 'culture' / culture_file.name) | |
| culture_count += 1 | |
| # Rewrite links in CULTURE FILES ONLY (strip ../ traversal for flat structure) | |
| for markdown_file in (staging_dir / 'culture').glob('*.md'): | |
| try: | |
| content = markdown_file.read_text(encoding='utf-8') | |
| content = rewrite_links_for_flat_zip(content) | |
| markdown_file.write_text(content, encoding='utf-8') | |
| except: | |
| pass | |
| return culture_count | |
| # Stage 4a: Individual country culture zips | |
| regions_path = Path('regions') | |
| for region_dir in regions_path.iterdir(): | |
| if not region_dir.is_dir(): | |
| continue | |
| for country_dir in region_dir.iterdir(): | |
| if not country_dir.is_dir(): | |
| continue | |
| if is_complete_culture(country_dir): | |
| # Determine culture name from folder | |
| culture_name = country_dir.name | |
| # Create temp staging directory with structure | |
| staging_dir = Path(f'dist/staging-{culture_name}') | |
| if staging_dir.exists(): | |
| shutil.rmtree(staging_dir) | |
| staging_dir.mkdir(parents=True, exist_ok=True) | |
| # Create subdirectories: engine/, culture/ | |
| (staging_dir / 'engine').mkdir(exist_ok=True) | |
| (staging_dir / 'culture').mkdir(exist_ok=True) | |
| # Copy root scaffold files (LICENSE, README, REFERENCES) | |
| if Path('LICENSE').exists(): | |
| shutil.copy2('LICENSE', staging_dir / 'LICENSE') | |
| # Copy README from country dir (baseline for each culture) | |
| readme_src = country_dir / 'README.md' | |
| if readme_src.exists(): | |
| readme_content = readme_src.read_text(encoding='utf-8') | |
| # Strip Download section: remove everything up to and including first --- divider | |
| # This keeps Install + Content sections | |
| if '---' in readme_content: | |
| parts = readme_content.split('---', 2) # Split on first two occurrences | |
| if len(parts) >= 3: | |
| readme_content = '---'.join(parts[1:]).lstrip('\n') # Keep from second --- onward | |
| # Rewrite links (flatten nested paths) | |
| readme_content = rewrite_links_for_flat_zip(readme_content) | |
| (staging_dir / 'README.md').write_text(readme_content, encoding='utf-8') | |
| # Copy REFERENCES from country dir (baseline for each culture) | |
| references_src = country_dir / 'REFERENCES.md' | |
| if references_src.exists(): | |
| shutil.copy2(references_src, staging_dir / 'REFERENCES.md') | |
| # Copy hofstede files from country dir to zip root | |
| for hofstede_file in country_dir.glob('hofstede_*'): | |
| shutil.copy2(hofstede_file, staging_dir / hofstede_file.name) | |
| # Stage engine + culture files | |
| culture_count = stage_culture_files(staging_dir, country_dir, culture_name) | |
| # Create structured zip | |
| create_culture_zip(staging_dir, culture_name) | |
| # Cleanup staging | |
| shutil.rmtree(staging_dir) | |
| print(f'Created {culture_name}.zip with structured layout (LICENSE + README + REFERENCES + engine/ + culture/{culture_count} files)') | |
| # Stage 4b: Regional combined zips (manifests that combine multiple countries) | |
| for region_dir in regions_path.iterdir(): | |
| if not region_dir.is_dir(): | |
| continue | |
| if is_region_manifest(region_dir): | |
| region_name = region_dir.name | |
| # Find all complete cultures in this region | |
| complete_cultures = [] | |
| for country_dir in region_dir.iterdir(): | |
| if country_dir.is_dir() and is_complete_culture(country_dir): | |
| complete_cultures.append(country_dir) | |
| # Only create regional zip if there are complete cultures | |
| if complete_cultures: | |
| # Create staging directory for combined regional zip | |
| staging_dir = Path(f'dist/staging-{region_name}') | |
| if staging_dir.exists(): | |
| shutil.rmtree(staging_dir) | |
| staging_dir.mkdir(parents=True, exist_ok=True) | |
| # Create subdirectories | |
| (staging_dir / 'engine').mkdir(exist_ok=True) | |
| (staging_dir / 'culture').mkdir(exist_ok=True) | |
| (staging_dir / 'hofstede').mkdir(exist_ok=True) | |
| # Copy root files | |
| if Path('LICENSE').exists(): | |
| shutil.copy2('LICENSE', staging_dir / 'LICENSE') | |
| # Copy region README (manifest) | |
| region_readme = region_dir / 'README.md' | |
| if region_readme.exists(): | |
| readme_content = region_readme.read_text(encoding='utf-8') | |
| readme_content = rewrite_links_for_flat_zip(readme_content) | |
| (staging_dir / 'README.md').write_text(readme_content, encoding='utf-8') | |
| # Copy engine files | |
| engine_path = Path('engine') | |
| claude_instructions = engine_path / 'claude' / 'instructions.md' | |
| if claude_instructions.exists(): | |
| shutil.copy2(claude_instructions, staging_dir / 'engine' / 'instructions.md') | |
| stack_src = engine_path / 'stack.md' | |
| if stack_src.exists(): | |
| shutil.copy2(stack_src, staging_dir / 'engine' / 'stack.md') | |
| # Combine all culture files from all complete countries | |
| total_culture_count = 0 | |
| for country_dir in complete_cultures: | |
| for culture_file in country_dir.glob('culture_*.md'): | |
| dest_name = culture_file.name | |
| shutil.copy2(culture_file, staging_dir / 'culture' / dest_name) | |
| total_culture_count += 1 | |
| # Copy hofstede files with country prefix: hofstede_{country}_{suffix} | |
| for hofstede_file in sorted(country_dir.glob('hofstede_*')): | |
| suffix = hofstede_file.name[len('hofstede_'):] | |
| dest_name = f'hofstede_{country_dir.name}_{suffix}' | |
| shutil.copy2(hofstede_file, staging_dir / 'hofstede' / dest_name) | |
| # Rewrite links in all culture files | |
| for markdown_file in (staging_dir / 'culture').glob('*.md'): | |
| try: | |
| content = markdown_file.read_text(encoding='utf-8') | |
| content = rewrite_links_for_flat_zip(content) | |
| markdown_file.write_text(content, encoding='utf-8') | |
| except: | |
| pass | |
| # Create regional combined zip | |
| culture_count = create_culture_zip(staging_dir, region_name) | |
| # Cleanup staging | |
| shutil.rmtree(staging_dir) | |
| print(f'Created {region_name}.zip as regional combined manifest ({len(complete_cultures)} countries, {total_culture_count} culture files)') | |
| # Stage 5: World flat zip (all complete cultures across all regions) | |
| staging_dir = Path('dist/staging-world') | |
| if staging_dir.exists(): | |
| shutil.rmtree(staging_dir) | |
| staging_dir.mkdir(parents=True, exist_ok=True) | |
| (staging_dir / 'engine').mkdir(exist_ok=True) | |
| (staging_dir / 'culture').mkdir(exist_ok=True) | |
| (staging_dir / 'hofstede').mkdir(exist_ok=True) | |
| # Root files | |
| if Path('LICENSE').exists(): | |
| shutil.copy2('LICENSE', staging_dir / 'LICENSE') | |
| if Path('README.md').exists(): | |
| readme_content = Path('README.md').read_text(encoding='utf-8') | |
| readme_content = rewrite_links_for_flat_zip(readme_content) | |
| (staging_dir / 'README.md').write_text(readme_content, encoding='utf-8') | |
| # Engine files | |
| claude_instructions = Path('engine') / 'claude' / 'instructions.md' | |
| if claude_instructions.exists(): | |
| shutil.copy2(claude_instructions, staging_dir / 'engine' / 'instructions.md') | |
| stack_src = Path('engine') / 'stack.md' | |
| if stack_src.exists(): | |
| shutil.copy2(stack_src, staging_dir / 'engine' / 'stack.md') | |
| # All complete cultures across all regions | |
| world_culture_count = 0 | |
| world_hofstede_count = 0 | |
| for region_dir in regions_path.iterdir(): | |
| if not region_dir.is_dir(): | |
| continue | |
| for country_dir in region_dir.iterdir(): | |
| if not country_dir.is_dir() or not is_complete_culture(country_dir): | |
| continue | |
| for culture_file in country_dir.glob('culture_*.md'): | |
| shutil.copy2(culture_file, staging_dir / 'culture' / culture_file.name) | |
| world_culture_count += 1 | |
| for hofstede_file in sorted(country_dir.glob('hofstede_*')): | |
| suffix = hofstede_file.name[len('hofstede_'):] | |
| dest_name = f'hofstede_{country_dir.name}_{suffix}' | |
| shutil.copy2(hofstede_file, staging_dir / 'hofstede' / dest_name) | |
| world_hofstede_count += 1 | |
| # Rewrite links in all culture files | |
| for markdown_file in (staging_dir / 'culture').glob('*.md'): | |
| try: | |
| content = markdown_file.read_text(encoding='utf-8') | |
| content = rewrite_links_for_flat_zip(content) | |
| markdown_file.write_text(content, encoding='utf-8') | |
| except: | |
| pass | |
| create_culture_zip(staging_dir, 'cultures-full') | |
| shutil.rmtree(staging_dir) | |
| print(f'Created cultures-full.zip as world flat zip ({world_culture_count} culture files, {world_hofstede_count} hofstede files)') | |
| PYTHON_EOF | |
| - name: Upload to release | |
| if: github.event_name == 'release' | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: dist/*.zip | |
| - name: Upload as artifacts | |
| if: github.event_name == 'workflow_dispatch' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: cultures-zips | |
| path: dist/*.zip | |
| retention-days: 7 |