refactor(ux): consolidate BMAD skills, update design system, and clean up Prisma generated client
This commit is contained in:
277
.agent/skills/bmad-agent-builder/assets/init-sanctum-template.py
Normal file
277
.agent/skills/bmad-agent-builder/assets/init-sanctum-template.py
Normal file
@@ -0,0 +1,277 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
First Breath — Deterministic sanctum scaffolding.
|
||||
|
||||
This script runs BEFORE the conversational awakening. It creates the sanctum
|
||||
folder structure, copies template files with config values substituted,
|
||||
copies all capability files and their supporting references into the sanctum,
|
||||
and auto-generates CAPABILITIES.md from capability prompt frontmatter.
|
||||
|
||||
After this script runs, the sanctum is fully self-contained — the agent does
|
||||
not depend on the skill bundle location for normal operation.
|
||||
|
||||
Usage:
|
||||
python3 init-sanctum.py <project-root> <skill-path>
|
||||
|
||||
project-root: The root of the project (where _bmad/ lives)
|
||||
skill-path: Path to the skill directory (where SKILL.md, references/, assets/ live)
|
||||
"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
import shutil
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
# --- Agent-specific configuration (set by builder) ---
|
||||
|
||||
SKILL_NAME = "{skillName}"
|
||||
SANCTUM_DIR = SKILL_NAME
|
||||
|
||||
# Files that stay in the skill bundle (only used during First Breath)
|
||||
SKILL_ONLY_FILES = {"{skill-only-files}"}
|
||||
|
||||
TEMPLATE_FILES = [
|
||||
{template-files-list}
|
||||
]
|
||||
|
||||
# Whether the owner can teach this agent new capabilities
|
||||
EVOLVABLE = {evolvable}
|
||||
|
||||
# --- End agent-specific configuration ---
|
||||
|
||||
|
||||
def parse_yaml_config(config_path: Path) -> dict:
|
||||
"""Simple YAML key-value parser. Handles top-level scalar values only."""
|
||||
config = {}
|
||||
if not config_path.exists():
|
||||
return config
|
||||
with open(config_path) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
if ":" in line:
|
||||
key, _, value = line.partition(":")
|
||||
value = value.strip().strip("'\"")
|
||||
if value:
|
||||
config[key.strip()] = value
|
||||
return config
|
||||
|
||||
|
||||
def parse_frontmatter(file_path: Path) -> dict:
|
||||
"""Extract YAML frontmatter from a markdown file."""
|
||||
meta = {}
|
||||
with open(file_path) as f:
|
||||
content = f.read()
|
||||
|
||||
match = re.match(r"^---\s*\n(.*?)\n---", content, re.DOTALL)
|
||||
if not match:
|
||||
return meta
|
||||
|
||||
for line in match.group(1).strip().split("\n"):
|
||||
if ":" in line:
|
||||
key, _, value = line.partition(":")
|
||||
meta[key.strip()] = value.strip().strip("'\"")
|
||||
return meta
|
||||
|
||||
|
||||
def copy_references(source_dir: Path, dest_dir: Path) -> list[str]:
|
||||
"""Copy all reference files (except skill-only files) into the sanctum."""
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
copied = []
|
||||
|
||||
for source_file in sorted(source_dir.iterdir()):
|
||||
if source_file.name in SKILL_ONLY_FILES:
|
||||
continue
|
||||
if source_file.is_file():
|
||||
shutil.copy2(source_file, dest_dir / source_file.name)
|
||||
copied.append(source_file.name)
|
||||
|
||||
return copied
|
||||
|
||||
|
||||
def copy_scripts(source_dir: Path, dest_dir: Path) -> list[str]:
|
||||
"""Copy any scripts the capabilities might use into the sanctum."""
|
||||
if not source_dir.exists():
|
||||
return []
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
copied = []
|
||||
|
||||
for source_file in sorted(source_dir.iterdir()):
|
||||
if source_file.is_file() and source_file.name != "init-sanctum.py":
|
||||
shutil.copy2(source_file, dest_dir / source_file.name)
|
||||
copied.append(source_file.name)
|
||||
|
||||
return copied
|
||||
|
||||
|
||||
def discover_capabilities(references_dir: Path, sanctum_refs_path: str) -> list[dict]:
|
||||
"""Scan references/ for capability prompt files with frontmatter."""
|
||||
capabilities = []
|
||||
|
||||
for md_file in sorted(references_dir.glob("*.md")):
|
||||
if md_file.name in SKILL_ONLY_FILES:
|
||||
continue
|
||||
meta = parse_frontmatter(md_file)
|
||||
if meta.get("name") and meta.get("code"):
|
||||
capabilities.append({
|
||||
"name": meta["name"],
|
||||
"description": meta.get("description", ""),
|
||||
"code": meta["code"],
|
||||
"source": f"{sanctum_refs_path}/{md_file.name}",
|
||||
})
|
||||
return capabilities
|
||||
|
||||
|
||||
def generate_capabilities_md(capabilities: list[dict], evolvable: bool) -> str:
|
||||
"""Generate CAPABILITIES.md content from discovered capabilities."""
|
||||
lines = [
|
||||
"# Capabilities",
|
||||
"",
|
||||
"## Built-in",
|
||||
"",
|
||||
"| Code | Name | Description | Source |",
|
||||
"|------|------|-------------|--------|",
|
||||
]
|
||||
for cap in capabilities:
|
||||
lines.append(
|
||||
f"| [{cap['code']}] | {cap['name']} | {cap['description']} | `{cap['source']}` |"
|
||||
)
|
||||
|
||||
if evolvable:
|
||||
lines.extend([
|
||||
"",
|
||||
"## Learned",
|
||||
"",
|
||||
"_Capabilities added by the owner over time. Prompts live in `capabilities/`._",
|
||||
"",
|
||||
"| Code | Name | Description | Source | Added |",
|
||||
"|------|------|-------------|--------|-------|",
|
||||
"",
|
||||
"## How to Add a Capability",
|
||||
"",
|
||||
'Tell me "I want you to be able to do X" and we\'ll create it together.',
|
||||
"I'll write the prompt, save it to `capabilities/`, and register it here.",
|
||||
"Next session, I'll know how.",
|
||||
"Load `./references/capability-authoring.md` for the full creation framework.",
|
||||
])
|
||||
|
||||
lines.extend([
|
||||
"",
|
||||
"## Tools",
|
||||
"",
|
||||
"Prefer crafting your own tools over depending on external ones. A script you wrote "
|
||||
"and saved is more reliable than an external API. Use the file system creatively.",
|
||||
"",
|
||||
"### User-Provided Tools",
|
||||
"",
|
||||
"_MCP servers, APIs, or services the owner has made available. Document them here._",
|
||||
])
|
||||
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def substitute_vars(content: str, variables: dict) -> str:
|
||||
"""Replace {var_name} placeholders with values from the variables dict."""
|
||||
for key, value in variables.items():
|
||||
content = content.replace(f"{{{key}}}", value)
|
||||
return content
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: python3 init-sanctum.py <project-root> <skill-path>")
|
||||
sys.exit(1)
|
||||
|
||||
project_root = Path(sys.argv[1]).resolve()
|
||||
skill_path = Path(sys.argv[2]).resolve()
|
||||
|
||||
# Paths
|
||||
bmad_dir = project_root / "_bmad"
|
||||
memory_dir = bmad_dir / "memory"
|
||||
sanctum_path = memory_dir / SANCTUM_DIR
|
||||
assets_dir = skill_path / "assets"
|
||||
references_dir = skill_path / "references"
|
||||
scripts_dir = skill_path / "scripts"
|
||||
|
||||
# Sanctum subdirectories
|
||||
sanctum_refs = sanctum_path / "references"
|
||||
sanctum_scripts = sanctum_path / "scripts"
|
||||
|
||||
# Fully qualified path for CAPABILITIES.md references
|
||||
sanctum_refs_path = "./references"
|
||||
|
||||
# Check if sanctum already exists
|
||||
if sanctum_path.exists():
|
||||
print(f"Sanctum already exists at {sanctum_path}")
|
||||
print("This agent has already been born. Skipping First Breath scaffolding.")
|
||||
sys.exit(0)
|
||||
|
||||
# Load config
|
||||
config = {}
|
||||
for config_file in ["config.yaml", "config.user.yaml"]:
|
||||
config.update(parse_yaml_config(bmad_dir / config_file))
|
||||
|
||||
# Build variable substitution map
|
||||
today = date.today().isoformat()
|
||||
variables = {
|
||||
"user_name": config.get("user_name", "friend"),
|
||||
"communication_language": config.get("communication_language", "English"),
|
||||
"birth_date": today,
|
||||
"project_root": str(project_root),
|
||||
"sanctum_path": str(sanctum_path),
|
||||
}
|
||||
|
||||
# Create sanctum structure
|
||||
sanctum_path.mkdir(parents=True, exist_ok=True)
|
||||
(sanctum_path / "capabilities").mkdir(exist_ok=True)
|
||||
(sanctum_path / "sessions").mkdir(exist_ok=True)
|
||||
print(f"Created sanctum at {sanctum_path}")
|
||||
|
||||
# Copy reference files (capabilities + techniques + guidance) into sanctum
|
||||
copied_refs = copy_references(references_dir, sanctum_refs)
|
||||
print(f" Copied {len(copied_refs)} reference files to sanctum/references/")
|
||||
for name in copied_refs:
|
||||
print(f" - {name}")
|
||||
|
||||
# Copy any supporting scripts into sanctum
|
||||
copied_scripts = copy_scripts(scripts_dir, sanctum_scripts)
|
||||
if copied_scripts:
|
||||
print(f" Copied {len(copied_scripts)} scripts to sanctum/scripts/")
|
||||
for name in copied_scripts:
|
||||
print(f" - {name}")
|
||||
|
||||
# Copy and substitute template files
|
||||
for template_name in TEMPLATE_FILES:
|
||||
template_path = assets_dir / template_name
|
||||
if not template_path.exists():
|
||||
print(f" Warning: template {template_name} not found, skipping")
|
||||
continue
|
||||
|
||||
# Remove "-template" from the output filename and uppercase it
|
||||
output_name = template_name.replace("-template", "").upper()
|
||||
# Fix extension casing: .MD -> .md
|
||||
output_name = output_name[:-3] + ".md"
|
||||
|
||||
content = template_path.read_text()
|
||||
content = substitute_vars(content, variables)
|
||||
|
||||
output_path = sanctum_path / output_name
|
||||
output_path.write_text(content)
|
||||
print(f" Created {output_name}")
|
||||
|
||||
# Auto-generate CAPABILITIES.md from references/ frontmatter
|
||||
capabilities = discover_capabilities(references_dir, sanctum_refs_path)
|
||||
capabilities_content = generate_capabilities_md(capabilities, evolvable=EVOLVABLE)
|
||||
(sanctum_path / "CAPABILITIES.md").write_text(capabilities_content)
|
||||
print(f" Created CAPABILITIES.md ({len(capabilities)} built-in capabilities discovered)")
|
||||
|
||||
print()
|
||||
print("First Breath scaffolding complete.")
|
||||
print("The conversational awakening can now begin.")
|
||||
print(f"Sanctum: {sanctum_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user