#!/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'])}
"
"
"
+ "".join(f"- {html.escape(action)}
" for action in item.get("actions", [])[:3])
+ "
"
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))}
"
"
| Variant | Tokens | Dev Errors | Holdout Errors | Strategy |
"
f"{compare_rows_html}
"
"
"
)
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
"
+ ("
" + "".join(f"- {html.escape(borrow)}
" for borrow in item.get('borrow', [])) + "
" if item.get("borrow") else "
No borrow cues recorded.
")
+ "
Avoid
"
+ ("
" + "".join(f"- {html.escape(avoid)}
" for avoid in item.get('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
"
+ ("
" + "".join(f"- {html.escape(borrow)}
" for borrow in item.get('borrow', [])) + "
" if item.get("borrow") else "
No borrow cues recorded.
")
+ "
Avoid
"
+ ("
" + "".join(f"- {html.escape(avoid)}
" for avoid in item.get('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
"
+ (
"
" + "".join(f"- {html.escape(value)}
" for value in item["added"]) + "
"
if item["added"]
else "
No added cues.
"
)
+ "
Drops from baseline
"
+ (
"
" + "".join(f"- {html.escape(value)}
" for value in item["removed"]) + "
"
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}
Why this package is strong
Compare view
{baseline_html}
Variant diff studio
{variant_diff_html}
Evidence readiness
Readiness score: {html.escape(str(readiness['score']))}/100
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?
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')))}
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')))}
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}
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()