#!/usr/bin/env python3 import argparse import html import json from pathlib import Path from review_viewer_data import ( architecture_steps, benchmark_cards, compare_rows, ensure_report_inputs, evidence_readiness, synthesis_cards, variant_diff_cards, ) def render_html(report: dict) -> str: overview = report["overview"] intent = report["intent"] intent_confidence = report.get("intent_confidence", {}) reference = report["reference"] iteration = report["iteration"] directions = iteration.get("directions", [])[:3] feedback = report.get("feedback", {}) baseline = report.get("baseline", {}) compare = report.get("compare", {}) promotion = report.get("promotion", {}) benchmark = report.get("benchmark", {}) reference_synthesis = report.get("reference_synthesis", {}) output_risk = report.get("output_risk", {}) artifact_design = report.get("artifact_design", {}) prompt_quality = report.get("prompt_quality", {}) architecture = architecture_steps(overview) compare_table_rows = compare_rows(compare) benchmark_rows = benchmark_cards(benchmark) synthesis_rows = synthesis_cards(reference_synthesis) variant_cards = variant_diff_cards(compare) readiness = evidence_readiness(report) strength_items = "".join(f"
  • {html.escape(item)}
  • " for item in overview.get("strengths", [])) logic_items = "".join(f"
  • {html.escape(item)}
  • " for item in overview.get("logic_steps", [])) usage_items = "".join(f"
  • {html.escape(item)}
  • " for item in overview.get("usage_steps", [])) question_items = "".join( f"
  • {html.escape(item['question'])}
    {html.escape(item['why'])}
  • " for item in intent.get("questions", [])[:5] ) reference_items = "".join( ( f"
  • {html.escape(item['name'])} · {html.escape(item['category'])}
    " f"Borrow: {html.escape(item['borrow'])}
    " f"Avoid: {html.escape(item['avoid'])}
  • " ) for item in reference.get("external_references", [])[:4] ) if not reference_items: reference_items = "
  • No external benchmark objects recorded yet. Add 2 to 5 references before deepening the package.
  • " output_risk_items = "".join( ( "
  • " f"{html.escape(item.get('label', item.get('key', 'Risk')))}
    " f"{html.escape('; '.join(item.get('risks', [])[:2]))}" "
  • " ) for item in output_risk.get("risk_families", [])[:3] ) if not output_risk_items: output_risk_items = "
  • No output risk profile attached yet. Generate one before approving example outputs.
  • " artifact_design_items = "".join( ( "
  • " f"{html.escape(item.get('label', item.get('key', 'Artifact')))}
    " f"{html.escape(item.get('direction', ''))}" "
  • " ) for item in artifact_design.get("artifact_families", [])[:3] ) if not artifact_design_items: artifact_design_items = "
  • No artifact design profile attached yet. Generate one before approving visual or document outputs.
  • " design_gate_items = "".join( f"
  • {html.escape(item)}
  • " for item in artifact_design.get("quality_gates", [])[:5] ) or "
  • No artifact design quality gates attached yet.
  • " prompt_quality_items = "".join( ( "
  • " f"{html.escape(item.get('label', item.get('key', 'Quality')))} · " f"{html.escape(str(item.get('score', 'n/a')))} / 100
    " f"{html.escape(item.get('repair', ''))}" "
  • " ) for item in prompt_quality.get("quality_matrix", [])[:5] ) or "
  • No prompt quality profile attached yet.
  • " rtf_items = "".join( ( "
  • " f"{html.escape(key.title())}
    " f"{html.escape(str(value))}" "
  • " ) for key, value in prompt_quality.get("rtf_to_skill", {}).items() ) or "
  • No RTF mapping attached yet.
  • " readiness_html = "".join( ( "
  • " f"{html.escape(item['label'])} · {html.escape(item['status'])}
    " f"{html.escape(item['detail'])}" "
  • " ) for item in readiness["checks"] ) direction_cards = "".join( ( "
    " f"

    {html.escape(item['title'])}

    " f"

    {html.escape(item['why'])}

    " "" f"
    Unlocks: {html.escape(item['unlocks'])}
    " "
    " ) for item in directions ) architecture_html = "".join( ( "
    " f"
    {html.escape(item['label'])}
    " f"
    {html.escape(item['detail'])}
    " "
    " ) for item in architecture ) feedback_entries = feedback.get("entries", [])[-3:] feedback_html = "" if feedback_entries: feedback_html = "".join( ( "
  • " f"{html.escape(entry.get('category', 'general'))} · " f"rating {html.escape(str(entry.get('rating', 'n/a')))}
    " f"{html.escape(entry.get('note', ''))}" "
  • " ) for entry in reversed(feedback_entries) ) else: feedback_html = "
  • No lightweight feedback captured yet. Use `yao.py feedback` to record quick review notes.
  • " baseline_html = "" if compare_table_rows: compare_rows_html = "".join( f"{html.escape(item['label'])}{html.escape(str(item['tokens']))}{html.escape(str(item['dev_errors']))}{html.escape(str(item['holdout_errors']))}{html.escape(item['strategy'])}" for item in compare_table_rows ) selection_logic = compare.get("selection_logic", "Choose the smallest candidate that improves routing without adding unnecessary weight.") winner_label = compare.get("summary", {}).get("winner_label", compare.get("winner", {}).get("label", "Current")) baseline_html = ( "
    " f"

    Winner: {html.escape(str(winner_label))}

    " f"

    {html.escape(str(selection_logic))}

    " "" f"{compare_rows_html}
    VariantTokensDev ErrorsHoldout ErrorsStrategy
    " "
    " ) elif baseline: summary = baseline.get("summary", {}) baseline_html = ( "
    " f"

    Targets: {html.escape(str(summary.get('target_count', 0)))}

    " f"

    Baseline errors: {html.escape(str(summary.get('baseline_total_errors', 0)))}

    " f"

    Current errors: {html.escape(str(summary.get('current_total_errors', 0)))}

    " f"

    Winner errors: {html.escape(str(summary.get('winner_total_errors', 0)))}

    " "
    " ) else: baseline_html = "

    No baseline comparison has been recorded for this package yet.

    " promotion_html = "" if promotion: summary = promotion.get("summary", {}) promotion_html = ( "
    " f"

    Promote: {html.escape(str(summary.get('promote', 0)))}

    " f"

    Keep current: {html.escape(str(summary.get('keep_current', 0)))}

    " f"

    Blocked: {html.escape(str(summary.get('blocked', 0)))}

    " "
    " ) else: promotion_html = "

    No promotion summary is attached to this package yet.

    " benchmark_html = "" if benchmark_rows: benchmark_html = "".join( ( "
    " f"

    {html.escape(item['name'])}

    " "

    Borrow now

    " + ("" if item.get("borrow") else "

    No borrow cues recorded.

    ") + "

    Avoid

    " + ("" if item.get("avoid") else "

    No avoid cues recorded.

    ") + "
    " ) for item in benchmark_rows ) else: benchmark_html = "

    No GitHub benchmark scan has been attached to this package yet.

    " synthesis_html = "" if synthesis_rows: synthesis_html = "".join( ( "
    " f"

    {html.escape(item['name'])}

    " "

    Borrow now

    " + ("" if item.get("borrow") else "

    No borrow cues recorded.

    ") + "

    Avoid

    " + ("" if item.get("avoid") else "

    No avoid cues recorded.

    ") + "
    " ) for item in synthesis_rows ) else: synthesis_html = "

    No multi-source synthesis has been generated yet.

    " variant_diff_html = "" if variant_cards: variant_diff_html = "".join( ( "
    " f"

    {html.escape(item['label'])}

    {html.escape(item['strategy'])}
    " f"

    {html.escape(item['description'])}

    " "
    " f"tokens {html.escape(str(item['tokens']))} ({html.escape(item['token_delta'])})" f"dev {html.escape(str(item['dev_errors']))} ({html.escape(item['dev_delta'])})" f"holdout {html.escape(str(item['holdout_errors']))} ({html.escape(item['holdout_delta'])})" "
    " "
    " "

    Adds relative to baseline

    " + ( "" if item["added"] else "

    No added cues.

    " ) + "

    Drops from baseline

    " + ( "" if item["removed"] else "

    No dropped cues.

    " ) + "
    " ) for item in variant_cards ) else: variant_diff_html = "

    No description optimization compare payload is attached yet.

    " return f""" {html.escape(overview.get('display_name', overview.get('title', 'Skill Review')))} Review Viewer

    Review Viewer

    {html.escape(overview.get('display_name', overview.get('title', 'Skill Review')))}

    {html.escape(overview.get('description', ''))}

    maturity: {html.escape(str(overview.get('metadata', {}).get('maturity_tier', 'scaffold')))} archetype: {html.escape(str(overview.get('metadata', {}).get('skill_archetype', 'scaffold')))} format: {html.escape(str(overview.get('metadata', {}).get('canonical_format', 'agent-skills')))} updated: {html.escape(str(overview.get('metadata', {}).get('updated_at', 'n/a')))} intent confidence: {html.escape(str(intent_confidence.get('score', 'n/a')))} / 100

    Architecture at a glance

    {architecture_html}

    Core logic

      {logic_items}

    How to use it

      {usage_items}

    Intent questions

      {question_items}

    Why this package is strong

      {strength_items}

    Borrow plan

      {reference_items}

    Compare view

    {baseline_html}

    Variant diff studio

    {variant_diff_html}

    Evidence readiness

    Readiness score: {html.escape(str(readiness['score']))}/100

      {readiness_html}

    Honest boundary check

    • Are the known limits visible before the package deepens?
    • Does the evidence support the borrowed patterns?
    • Should uncertainty become a clarification question instead of more structure?

    Output risk profile

      {output_risk_items}

    Self-repair checks

      {"".join(f"
    • {html.escape(item)}
    • " for item in output_risk.get('self_repair_checks', [])[:5]) or "
    • No self-repair checks attached yet.
    • "}

    Artifact design profile

    Design system: {html.escape(str(artifact_design.get('design_system', 'not generated')))}

      {artifact_design_items}

    Visual quality gates

      {design_gate_items}

    Prompt quality profile

    Relevance: {html.escape(str(prompt_quality.get('relevance', 'not generated')))} · score {html.escape(str(prompt_quality.get('overall_quality_score', 'n/a')))} / 100 · complexity {html.escape(str(prompt_quality.get('complexity', {}).get('band', 'n/a')))}

      {prompt_quality_items}

    RTF to skill mapping

      {rtf_items}

    Reference coach

    {benchmark_html}

    Decide before you deepen

    • Choose one pattern to borrow on purpose, not three at once.
    • State one thing this skill will not inherit from the benchmark objects.
    • Only deepen the package after that choice is visible in the boundary or execution flow.

    Reference synthesis

    {synthesis_html}

    Borrow now

      {"".join(f"
    • {html.escape(item)}
    • " for item in reference_synthesis.get('synthesis', {}).get('borrow_now', [])[:4]) or "
    • No synthesis borrow cues recorded yet.
    • "}

    {html.escape(reference_synthesis.get('synthesis', {}).get('decision_prompt', 'Run the reference synthesis after the benchmark scan to decide what to borrow next.'))}

    Top three next moves

    {direction_cards}

    Recent feedback

      {feedback_html}

    Promotion status

    {promotion_html}

    Package map

      {"".join(f"
    • {html.escape(item['path'])} — {html.escape(item['label'])}
    • " for item in overview.get('package_map', [])[:8])}

    First-pass review frame

    • Does the trigger stay narrow enough for the intended job?
    • Does the archetype match the real reuse level?
    • Are we adding structure faster than we are adding reliability?
    • Should the next step be trigger tightening, execution assets, or portability hardening?

    Authoring discipline

    • Name unresolved assumptions before deepening the package.
    • Keep the package no larger than the recurring job requires.
    • Touch only files that directly support the requested change.
    • Tie every meaningful new artifact to a check or reviewer note.

    Reviewer guardrails

    • Block speculative features that are not backed by real workflow variation.
    • Move unverifiable ideas into next-step candidates instead of baseline structure.
    • Reject decorative folders, reports, or governance that do not reduce risk.
    • Ask for one high-leverage clarification when job, output, or exclusion is still fuzzy.
    """ def render_review_viewer(skill_dir: Path, output_html: Path | None = None, output_json: Path | None = None) -> dict: skill_dir = skill_dir.resolve() reports_dir = skill_dir / "reports" reports_dir.mkdir(parents=True, exist_ok=True) output_html = output_html or reports_dir / "review-viewer.html" output_json = output_json or reports_dir / "review-viewer.json" report = ensure_report_inputs(skill_dir) report["evidence_readiness"] = evidence_readiness(report) output_html.write_text(render_html(report), encoding="utf-8") output_json.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8") return { "ok": True, "skill_dir": str(skill_dir), "artifacts": { "html": str(output_html), "json": str(output_json), }, "summary": { "title": report["overview"].get("title", skill_dir.name), "maturity_tier": report["overview"].get("metadata", {}).get("maturity_tier", "scaffold"), "directions": len(report["iteration"].get("directions", [])), "feedback_entries": len(report.get("feedback", {}).get("entries", [])), }, } def main() -> None: parser = argparse.ArgumentParser(description="Render a compact HTML review viewer for a skill package.") parser.add_argument("skill_dir", nargs="?", default=".") parser.add_argument("--output-html") parser.add_argument("--output-json") args = parser.parse_args() result = render_review_viewer( Path(args.skill_dir), output_html=Path(args.output_html).resolve() if args.output_html else None, output_json=Path(args.output_json).resolve() if args.output_json else None, ) print(json.dumps(result, ensure_ascii=False, indent=2)) if __name__ == "__main__": main()