cloudronin's picture
push build context (uofa source + packs + space app)
a28ec65 verified
Raw
History Blame Contribute Delete
8.7 kB
"""uofa init β€” scaffold a new UofA project."""
from __future__ import annotations
import json
import re
import shutil
from pathlib import Path
from uofa_cli.integrity import generate_keypair
from uofa_cli.output import step_header, result_line, info
from uofa_cli import paths
HELP = "scaffold a new UofA project (template + keys + uofa.toml)"
def add_arguments(parser):
parser.add_argument("name", help="project name (e.g., my-turbine-study)")
parser.add_argument("--profile", choices=["minimal", "complete"], default="complete",
help="starting profile (default: complete)")
parser.add_argument("--dir", type=Path, default=Path("."), help="parent directory (default: cwd)")
parser.add_argument("--provider", default="ollama",
help="LLM provider for uofa extract (default: ollama)")
parser.add_argument("--model", default="llama3.2",
help="LLM model for uofa extract (default: llama3.2)")
def run(args) -> int:
project_dir = args.dir / args.name
if project_dir.exists():
raise FileExistsError(f"Directory already exists: {project_dir}")
step_header(f"Initializing UofA project: {args.name}")
# ── Create directories ───────────────────────────────────
project_dir.mkdir(parents=True)
keys_dir = project_dir / "keys"
keys_dir.mkdir()
evidence_dir = project_dir / "evidence"
evidence_dir.mkdir()
(evidence_dir / ".gitkeep").touch()
# ── Copy and customize JSON-LD template ───────────────────
template_name = f"uofa-{'minimal' if args.profile == 'minimal' else 'complete'}-skeleton.jsonld"
template = paths.templates_dir() / template_name
if not template.exists():
raise FileNotFoundError(f"Template not found: {template}")
with open(template, "r", encoding="utf-8") as f:
content = f.read()
content = content.replace("example.org/my-project", f"example.org/{args.name}")
content = content.replace("YOUR PROJECT NAME", args.name)
content = content.replace(
'"../../spec/context/v0.5.jsonld"',
'"https://raw.githubusercontent.com/cloudronin/uofa/main/spec/context/v0.5.jsonld"'
)
content = content.replace(
'"../../../spec/context/v0.5.jsonld"',
'"https://raw.githubusercontent.com/cloudronin/uofa/main/spec/context/v0.5.jsonld"'
)
output_file = project_dir / f"{args.name}-cou1.jsonld"
with open(output_file, "w") as f:
f.write(content)
result_line(f"Template ({args.profile})", True, str(output_file))
# ── Generate keypair ──────────────────────────────────────
key_path = keys_dir / f"{args.name}.key"
key_path, pub_path = generate_keypair(key_path)
result_line("Keypair generated", True, str(key_path.name))
# ── Copy Excel template from pack ─────────────────────────
active_pack = paths.resolve_active_packs(args)
pack_name = active_pack[0] if active_pack else "vv40"
xlsx_template = _find_pack_template(pack_name)
if pack_name == "vv40" or pack_name == "core":
xlsx_dest_name = "uofa-template.xlsx"
else:
xlsx_dest_name = f"uofa-template-{pack_name}.xlsx"
if xlsx_template and xlsx_template.exists():
xlsx_dest = project_dir / xlsx_dest_name
shutil.copy2(xlsx_template, xlsx_dest)
result_line("Excel template", True, xlsx_dest_name)
else:
xlsx_dest_name = "uofa-template.xlsx"
info(" (no Excel template found for this pack)")
# ── Write uofa.toml ──────────────────────────────────────
toml_content = _generate_toml(
name=args.name,
pack=pack_name,
profile=args.profile,
template=xlsx_dest_name,
provider=args.provider,
model=args.model,
)
toml_path = project_dir / "uofa.toml"
toml_path.write_text(toml_content)
result_line("uofa.toml", True)
# ── Write README.md ───────────────────────────────────────
readme_content = _generate_readme(
project_name=args.name,
pack_name=pack_name,
template_filename=xlsx_dest_name,
)
(project_dir / "README.md").write_text(readme_content)
result_line("README.md", True)
# ── Create .gitignore ─────────────────────────────────────
gitignore = project_dir / ".gitignore"
gitignore.write_text(
"# Private keys β€” never commit\n"
"*.key\n"
)
result_line(".gitignore", True)
# ── Next steps ────────────────────────────────────────────
print()
info("Next steps:")
info(f" 1. Fill in {xlsx_dest_name}")
info(f" 2. Import: cd {args.name} && uofa import")
info(f" 3. Check: uofa check {args.name}-cou1.jsonld")
return 0
def _find_pack_template(pack_name: str) -> Path | None:
"""Find the Excel template for a given pack."""
try:
manifest = paths.pack_manifest(pack_name)
template_rel = manifest.get("template")
if template_rel:
template_path = paths.pack_dir(pack_name) / template_rel
if template_path.exists():
return template_path
except (FileNotFoundError, KeyError):
pass
# Fallback: core pack template
try:
manifest = paths.pack_manifest("core")
template_rel = manifest.get("template")
if template_rel:
template_path = paths.pack_dir("core") / template_rel
if template_path.exists():
return template_path
except (FileNotFoundError, KeyError):
pass
return None
def _generate_toml(name, pack, profile, template, provider, model):
"""Generate uofa.toml content."""
return (
"# UofA Project Configuration\n"
f"# Generated by: uofa init {name}\n"
"\n"
"[project]\n"
f'name = "{name}"\n'
f'pack = "{pack}"\n'
f'profile = "{profile}"\n'
"\n"
"[paths]\n"
'output = "."\n'
'evidence = "evidence"\n'
f'template = "{template}"\n'
"\n"
"[extract]\n"
f'provider = "{provider}"\n'
f'model = "{model}"\n'
)
_PACK_DISPLAY = {
"vv40": "ASME V&V 40-2018",
"nasa-7009b": "NASA-STD-7009B",
"core": "Core (standards-agnostic)",
}
def _generate_readme(project_name, pack_name, template_filename):
"""Generate project README.md content."""
pack_display = _PACK_DISPLAY.get(pack_name, pack_name)
return (
f"# {project_name} β€” UofA Project\n"
"\n"
"## Quick Start\n"
"\n"
"1. **Fill in the Excel template:**\n"
f" Open `{template_filename}` and fill in your assessment data.\n"
"\n"
"2. **Import to JSON-LD:**\n"
" ```bash\n"
f" uofa import {template_filename}\n"
" ```\n"
" (Pack and signing key are read from `uofa.toml` β€” no flags needed.)\n"
"\n"
"3. **Check your evidence package:**\n"
" ```bash\n"
f" uofa check {project_name}-cou1.jsonld\n"
" ```\n"
"\n"
"4. **Fix any weakeners**, re-import, and check again until clean.\n"
"\n"
"## Project Structure\n"
"\n"
"- `uofa.toml` β€” Project configuration (pack, profile, paths)\n"
"- `evidence/` β€” Place your evidence files here\n"
f"- `{template_filename}` β€” Excel template for your credibility assessment\n"
"- `keys/` β€” Signing keys (keep `*.key` private, share `*.pub`)\n"
"\n"
"## Commands\n"
"\n"
"All commands auto-detect project settings from `uofa.toml`:\n"
"\n"
"```bash\n"
f"uofa import {template_filename} # Excel -> signed JSON-LD\n"
f"uofa check {project_name}-cou1.jsonld # Full C1+C2+C3 pipeline\n"
f"uofa rules {project_name}-cou1.jsonld # Weakener detection only\n"
"uofa diff cou1.jsonld cou2.jsonld # Compare two COUs\n"
"```\n"
"\n"
f"## Pack: {pack_display}\n"
"\n"
f"This project uses the **{pack_display}** standards pack.\n"
)