init: project structure, templates, build system
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
156
build.py
Executable file
156
build.py
Executable file
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build script: Generate .yaml and .json from persona .md files."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
print("PyYAML required: pip install pyyaml")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def parse_persona_md(filepath: Path) -> dict:
|
||||
"""Parse a persona markdown file into structured data."""
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
|
||||
# Extract YAML frontmatter
|
||||
fm_match = re.match(r"^---\n(.*?)\n---\n(.*)$", content, re.DOTALL)
|
||||
if not fm_match:
|
||||
print(f" WARN: No frontmatter in {filepath}")
|
||||
return {}
|
||||
|
||||
frontmatter = yaml.safe_load(fm_match.group(1))
|
||||
body = fm_match.group(2).strip()
|
||||
|
||||
# Extract sections from body
|
||||
sections = {}
|
||||
current_section = None
|
||||
current_content = []
|
||||
|
||||
for line in body.split("\n"):
|
||||
if line.startswith("## "):
|
||||
if current_section:
|
||||
sections[current_section] = "\n".join(current_content).strip()
|
||||
current_section = line[3:].strip().lower().replace(" ", "_").replace("&", "and")
|
||||
current_content = []
|
||||
else:
|
||||
current_content.append(line)
|
||||
|
||||
if current_section:
|
||||
sections[current_section] = "\n".join(current_content).strip()
|
||||
|
||||
return {
|
||||
"metadata": frontmatter,
|
||||
"sections": sections,
|
||||
"raw_body": body,
|
||||
}
|
||||
|
||||
|
||||
def build_persona(persona_dir: Path, output_dir: Path):
|
||||
"""Build all variants for a persona directory."""
|
||||
md_files = sorted(persona_dir.glob("*.md"))
|
||||
if not md_files:
|
||||
return
|
||||
|
||||
persona_name = persona_dir.name
|
||||
out_path = output_dir / persona_name
|
||||
out_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Load _meta.yaml if exists
|
||||
meta_file = persona_dir / "_meta.yaml"
|
||||
meta = {}
|
||||
if meta_file.exists():
|
||||
meta = yaml.safe_load(meta_file.read_text(encoding="utf-8")) or {}
|
||||
|
||||
for md_file in md_files:
|
||||
if md_file.name.startswith("_"):
|
||||
continue
|
||||
|
||||
variant = md_file.stem
|
||||
parsed = parse_persona_md(md_file)
|
||||
if not parsed:
|
||||
continue
|
||||
|
||||
# Merge meta into parsed data
|
||||
output = {**meta, **parsed["metadata"], "variant": variant, "sections": parsed["sections"]}
|
||||
|
||||
# Write YAML
|
||||
yaml_out = out_path / f"{variant}.yaml"
|
||||
yaml_out.write_text(
|
||||
yaml.dump(output, allow_unicode=True, default_flow_style=False, sort_keys=False),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write JSON
|
||||
json_out = out_path / f"{variant}.json"
|
||||
json_out.write_text(json.dumps(output, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
# Write plain system prompt (just the body)
|
||||
prompt_out = out_path / f"{variant}.prompt.md"
|
||||
prompt_out.write_text(parsed["raw_body"], encoding="utf-8")
|
||||
|
||||
print(f" Built: {persona_name}/{variant} -> .yaml .json .prompt.md")
|
||||
|
||||
|
||||
def build_catalog(personas_dir: Path, output_dir: Path):
|
||||
"""Generate CATALOG.md from all personas."""
|
||||
catalog_lines = ["# Persona Catalog\n", "_Auto-generated by build.py_\n"]
|
||||
|
||||
for persona_dir in sorted(personas_dir.iterdir()):
|
||||
if not persona_dir.is_dir() or persona_dir.name.startswith((".", "_")):
|
||||
continue
|
||||
|
||||
meta_file = persona_dir / "_meta.yaml"
|
||||
if not meta_file.exists():
|
||||
continue
|
||||
|
||||
meta = yaml.safe_load(meta_file.read_text(encoding="utf-8")) or {}
|
||||
variants = [f.stem for f in sorted(persona_dir.glob("*.md")) if not f.name.startswith("_")]
|
||||
|
||||
catalog_lines.append(f"## {meta.get('codename', persona_dir.name)} — {meta.get('role', 'Unknown')}")
|
||||
catalog_lines.append(f"- **Domain:** {meta.get('domain', 'N/A')}")
|
||||
catalog_lines.append(f"- **Hitap:** {meta.get('address_to', 'N/A')}")
|
||||
catalog_lines.append(f"- **Variants:** {', '.join(variants)}")
|
||||
catalog_lines.append("")
|
||||
|
||||
catalog_path = personas_dir / "CATALOG.md"
|
||||
catalog_path.write_text("\n".join(catalog_lines), encoding="utf-8")
|
||||
print(f" Catalog: {catalog_path}")
|
||||
|
||||
|
||||
def main():
|
||||
root = Path(__file__).parent
|
||||
personas_dir = root / "personas"
|
||||
|
||||
if not personas_dir.exists():
|
||||
print("No personas/ directory found.")
|
||||
sys.exit(1)
|
||||
|
||||
output_dir = root / ".generated"
|
||||
|
||||
# Find all persona directories
|
||||
persona_dirs = [
|
||||
d for d in sorted(personas_dir.iterdir()) if d.is_dir() and not d.name.startswith((".", "_"))
|
||||
]
|
||||
|
||||
if not persona_dirs:
|
||||
print("No persona directories found.")
|
||||
sys.exit(1)
|
||||
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
print(f"Building {len(persona_dirs)} personas -> {output_dir}\n")
|
||||
|
||||
for pdir in persona_dirs:
|
||||
build_persona(pdir, output_dir)
|
||||
|
||||
build_catalog(personas_dir, output_dir)
|
||||
print(f"\nDone. {len(persona_dirs)} personas built.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user