feat: add artifact design quality profiles
This commit is contained in:
@@ -8,6 +8,7 @@ from github_benchmark_scan import run_github_benchmark_scan
|
||||
from render_intent_confidence import render_intent_confidence
|
||||
from render_intent_dialogue import render_intent_dialogue
|
||||
from render_iteration_directions import render_iteration_directions
|
||||
from render_artifact_design_profile import render_artifact_design_profile
|
||||
from render_output_risk_profile import render_output_risk_profile
|
||||
from render_reference_scan import parse_reference, render_reference_scan
|
||||
from render_reference_synthesis import render_reference_synthesis
|
||||
@@ -31,7 +32,9 @@ description: {description}
|
||||
## Output Quality Guardrails
|
||||
|
||||
- Before final output, apply the likely failure modes in `reports/output-risk-profile.md` when that report is present.
|
||||
- Before rendering reports, tutorials, review pages, dashboards, or visual artifacts, apply the artifact direction and visual quality gates in `reports/artifact-design-profile.md` when that report is present.
|
||||
- Repair generic headings, cluttered notes, fragile visual assumptions, weak tables, and missing verification cues before handing work back.
|
||||
- Let the artifact's content choose the visual system; do not copy a fixed palette or report style from another skill without a clear reason.
|
||||
- If output-specific evidence is missing, state the gap instead of inventing screenshots, citations, data, or examples.
|
||||
|
||||
## Honest Boundaries
|
||||
@@ -61,7 +64,8 @@ README_TEMPLATE = """# {title}
|
||||
7. Check `reports/skill-overview.html` if you want a fast visual explanation of the package.
|
||||
8. Open `reports/review-viewer.html` for a compact visual review of the package.
|
||||
9. Check `reports/output-risk-profile.md` to see likely output mistakes and self-repair checks.
|
||||
10. Review `reports/iteration-directions.md` for the three most valuable next moves.
|
||||
10. Check `reports/artifact-design-profile.md` to see the intended artifact direction, layout patterns, visual quality gates, and anti-patterns.
|
||||
11. Review `reports/iteration-directions.md` for the three most valuable next moves.
|
||||
|
||||
## Honest Boundaries
|
||||
|
||||
@@ -80,6 +84,7 @@ README_TEMPLATE = """# {title}
|
||||
- `reports/reference-scan.md`: benchmark notes from public references, user references, and local constraints
|
||||
- `reports/reference-synthesis.md`: a combined view of GitHub benchmarks plus curated world-class pattern tracks
|
||||
- `reports/output-risk-profile.md`: predicted output failure modes and self-repair constraints for this skill
|
||||
- `reports/artifact-design-profile.md`: artifact-specific design direction, layout patterns, visual quality gates, and anti-patterns
|
||||
- `reports/skill-overview.html`: visual overview report
|
||||
- `reports/review-viewer.html`: compact review page for architecture, usage, feedback, and next steps
|
||||
- `reports/iteration-directions.md`: the top three next iteration directions
|
||||
@@ -246,6 +251,7 @@ def initialize_skill(
|
||||
)
|
||||
reference_synthesis = render_reference_synthesis(root)
|
||||
output_risk_profile = render_output_risk_profile(root)
|
||||
artifact_design_profile = render_artifact_design_profile(root)
|
||||
overview = render_skill_overview(root)
|
||||
iteration_directions = render_iteration_directions(root)
|
||||
review_viewer = render_review_viewer(root)
|
||||
@@ -265,6 +271,8 @@ def initialize_skill(
|
||||
"reference_synthesis_json": reference_synthesis["artifacts"]["json"],
|
||||
"output_risk_profile_md": output_risk_profile["artifacts"]["markdown"],
|
||||
"output_risk_profile_json": output_risk_profile["artifacts"]["json"],
|
||||
"artifact_design_profile_md": artifact_design_profile["artifacts"]["markdown"],
|
||||
"artifact_design_profile_json": artifact_design_profile["artifacts"]["json"],
|
||||
"iteration_directions_md": iteration_directions["artifacts"]["markdown"],
|
||||
"iteration_directions_json": iteration_directions["artifacts"]["json"],
|
||||
"review_viewer_html": review_viewer["artifacts"]["html"],
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except ImportError: # pragma: no cover
|
||||
yaml = None
|
||||
|
||||
|
||||
ARTIFACT_FAMILIES = [
|
||||
{
|
||||
"key": "tutorial",
|
||||
"label": "Tutorial or guide",
|
||||
"keywords": ["tutorial", "guide", "lesson", "course", "walkthrough", "how to", "教程", "指南", "课程"],
|
||||
"direction": "Progressive instructional layout with domain-specific section names, short success checks, and examples close to the user's real input.",
|
||||
"layout": ["opening promise", "task-specific sections", "worked example", "success checks", "common mistakes"],
|
||||
"quality_gates": [
|
||||
"Replace generic headings with learner- and domain-specific headings.",
|
||||
"Pair every major step with a visible success check.",
|
||||
"Do not add screenshots unless they are real, current, and action-relevant.",
|
||||
],
|
||||
},
|
||||
{
|
||||
"key": "report",
|
||||
"label": "Report or brief",
|
||||
"keywords": ["report", "brief", "analysis", "summary", "memo", "white paper", "报告", "简报", "分析", "总结"],
|
||||
"direction": "High-trust editorial report with a clear first-screen thesis, compact evidence blocks, and decisions separated from supporting detail.",
|
||||
"layout": ["thesis", "evidence blocks", "decision table", "risks", "next actions"],
|
||||
"quality_gates": [
|
||||
"Keep the first screen useful without requiring the reader to parse every detail.",
|
||||
"Use tables only for comparisons; move explanations below the table.",
|
||||
"Keep source notes readable without flooding the body with markers.",
|
||||
],
|
||||
},
|
||||
{
|
||||
"key": "review_viewer",
|
||||
"label": "Review viewer",
|
||||
"keywords": ["review", "viewer", "compare", "diff", "audit", "评审", "对比", "审查"],
|
||||
"direction": "Side-by-side reviewer studio with explicit tradeoffs, evidence readiness, and fast paths for approving, blocking, or requesting one focused fix.",
|
||||
"layout": ["summary", "variant comparison", "evidence", "risks", "review decision"],
|
||||
"quality_gates": [
|
||||
"Make differences visible instead of hiding them in prose.",
|
||||
"Separate author-facing recommendations from reviewer-only evidence.",
|
||||
"Surface conflicts clearly and keep routine benchmark synthesis quiet.",
|
||||
],
|
||||
},
|
||||
{
|
||||
"key": "dashboard",
|
||||
"label": "Dashboard or metrics page",
|
||||
"keywords": ["dashboard", "metric", "score", "kpi", "chart", "table", "scorecard", "仪表盘", "指标", "评分", "图表", "表格"],
|
||||
"direction": "Metric-first dashboard with stable dimensions, short labels, visible deltas, and narrative callouts only where they change interpretation.",
|
||||
"layout": ["metric board", "ranked signals", "comparison rows", "interpretation", "action queue"],
|
||||
"quality_gates": [
|
||||
"Avoid paragraph-heavy table cells.",
|
||||
"Keep charts tied to one analytical question each.",
|
||||
"Preserve stable color meaning across metrics and entities.",
|
||||
],
|
||||
},
|
||||
{
|
||||
"key": "visual_capture",
|
||||
"label": "Screenshot or visual evidence",
|
||||
"keywords": ["screenshot", "screen capture", "image evidence", "figma", "截图", "图片", "截屏", "视觉证据"],
|
||||
"direction": "Evidence-led visual artifact that records source, viewport, crop intent, and the exact region the reader should inspect.",
|
||||
"layout": ["source context", "visual frame", "inspection notes", "limitations", "next capture"],
|
||||
"quality_gates": [
|
||||
"Never invent missing screenshots or visual states.",
|
||||
"Record source, viewport, and crop intent.",
|
||||
"Describe the action-relevant region instead of only saying an image exists.",
|
||||
],
|
||||
},
|
||||
{
|
||||
"key": "slide_like",
|
||||
"label": "Slide-like narrative",
|
||||
"keywords": ["slide", "ppt", "deck", "presentation", "one-pager", "演示", "幻灯片", "一页纸"],
|
||||
"direction": "Narrative page rhythm with strong section roles, controlled density, and a visual system chosen from the topic rather than a default template.",
|
||||
"layout": ["hero claim", "supporting proof", "structured contrast", "implication", "closing action"],
|
||||
"quality_gates": [
|
||||
"Plan role, density, and rhythm before writing HTML.",
|
||||
"Avoid three or more repeated surfaces in a row.",
|
||||
"Use structure, spacing, and type before decoration.",
|
||||
],
|
||||
},
|
||||
{
|
||||
"key": "code_or_cli",
|
||||
"label": "Code, CLI, or implementation guide",
|
||||
"keywords": ["code", "cli", "script", "api", "command", "terminal", "代码", "脚本", "命令", "接口"],
|
||||
"direction": "Execution-focused technical artifact with environment assumptions, copyable commands, expected outputs, and side effects made explicit.",
|
||||
"layout": ["prerequisites", "commands", "expected output", "failure handling", "rollback or cleanup"],
|
||||
"quality_gates": [
|
||||
"Name the working directory and required inputs for commands.",
|
||||
"Mark destructive, networked, or external side-effect operations.",
|
||||
"Prefer the smallest runnable snippet over broad framework scaffolding.",
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
DESIGN_TOKENS = {
|
||||
"type": [
|
||||
"Use a distinctive display face or serif for major claims when the artifact is editorial.",
|
||||
"Use a restrained sans for dense body text and technical details.",
|
||||
"Use mono only for metadata, paths, commands, labels, and evidence tags.",
|
||||
],
|
||||
"color": [
|
||||
"Choose colors from the artifact's domain, brand, or evidence mood.",
|
||||
"Do not default to Kami parchment, purple gradients, or generic SaaS blue unless the content justifies it.",
|
||||
"Keep accent color limited to decisions, active states, risk, or section anchors.",
|
||||
],
|
||||
"spacing": [
|
||||
"Prefer clear grid rhythm over floating decorative cards.",
|
||||
"Increase whitespace around decisions and shrink whitespace around supporting metadata.",
|
||||
"Split dense content instead of shrinking type or adding scroll traps.",
|
||||
],
|
||||
"components": [
|
||||
"Use cards for grouped evidence, tables for comparisons, callouts for decisions, and timelines for sequence.",
|
||||
"Avoid cards inside cards.",
|
||||
"Keep reviewer-only detail visible but visually quieter than user-facing guidance.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def parse_frontmatter(text: str) -> tuple[dict, str]:
|
||||
lines = text.splitlines()
|
||||
if not lines or lines[0].strip() != "---":
|
||||
return {}, text
|
||||
try:
|
||||
end_index = lines[1:].index("---") + 1
|
||||
except ValueError:
|
||||
return {}, text
|
||||
frontmatter_text = "\n".join(lines[1:end_index])
|
||||
body = "\n".join(lines[end_index + 1 :]).lstrip()
|
||||
if yaml is not None:
|
||||
payload = yaml.safe_load(frontmatter_text) or {}
|
||||
return payload if isinstance(payload, dict) else {}, body
|
||||
data = {}
|
||||
for line in frontmatter_text.splitlines():
|
||||
if ":" not in line:
|
||||
continue
|
||||
key, value = line.split(":", 1)
|
||||
data[key.strip()] = value.strip().strip('"')
|
||||
return data, body
|
||||
|
||||
|
||||
def load_json(path: Path) -> dict:
|
||||
if not path.exists():
|
||||
return {}
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def normalized_text(skill_dir: Path) -> str:
|
||||
skill_text = (skill_dir / "SKILL.md").read_text(encoding="utf-8")
|
||||
frontmatter, body = parse_frontmatter(skill_text)
|
||||
intent = load_json(skill_dir / "reports" / "intent-confidence.json")
|
||||
output_risk = load_json(skill_dir / "reports" / "output-risk-profile.json")
|
||||
context = intent.get("context", {}) if isinstance(intent, dict) else {}
|
||||
risk_labels = " ".join(item.get("label", "") for item in output_risk.get("risk_families", []))
|
||||
parts = [
|
||||
skill_dir.name,
|
||||
str(frontmatter.get("name", "")),
|
||||
str(frontmatter.get("description", "")),
|
||||
body,
|
||||
str(context.get("job", "")),
|
||||
str(context.get("primary_output", "")),
|
||||
" ".join(context.get("real_inputs", []) or []),
|
||||
" ".join(context.get("constraints", []) or []),
|
||||
" ".join(context.get("standards", []) or []),
|
||||
risk_labels,
|
||||
]
|
||||
return " ".join(parts).lower()
|
||||
|
||||
|
||||
def match_family(text: str, family: dict) -> tuple[int, list[str]]:
|
||||
hits = []
|
||||
for keyword in family["keywords"]:
|
||||
if keyword.lower() in text:
|
||||
hits.append(keyword)
|
||||
return len(hits), hits
|
||||
|
||||
|
||||
def dedupe(items: list[str], limit: int) -> list[str]:
|
||||
seen = set()
|
||||
output = []
|
||||
for item in items:
|
||||
if not item or item in seen:
|
||||
continue
|
||||
seen.add(item)
|
||||
output.append(item)
|
||||
if len(output) == limit:
|
||||
break
|
||||
return output
|
||||
|
||||
|
||||
def design_system_name(matched: list[dict]) -> str:
|
||||
keys = {item["key"] for item in matched}
|
||||
if "dashboard" in keys:
|
||||
return "metric editorial"
|
||||
if "review_viewer" in keys:
|
||||
return "review studio"
|
||||
if "slide_like" in keys:
|
||||
return "narrative visual brief"
|
||||
if "tutorial" in keys:
|
||||
return "guided learning document"
|
||||
if "visual_capture" in keys:
|
||||
return "evidence frame"
|
||||
return "content-led editorial"
|
||||
|
||||
|
||||
def build_summary(skill_dir: Path) -> dict:
|
||||
text = normalized_text(skill_dir)
|
||||
matched = []
|
||||
for family in ARTIFACT_FAMILIES:
|
||||
score, hits = match_family(text, family)
|
||||
if score:
|
||||
matched.append({**family, "score": score, "matched_keywords": hits})
|
||||
if not matched:
|
||||
fallback = next(item for item in ARTIFACT_FAMILIES if item["key"] == "report")
|
||||
matched = [{**fallback, "score": 0, "matched_keywords": ["general-artifact"]}]
|
||||
matched = sorted(matched, key=lambda item: item["score"], reverse=True)[:5]
|
||||
primary = matched[0]
|
||||
layout_patterns = []
|
||||
gates = []
|
||||
for item in matched:
|
||||
layout_patterns.extend(item["layout"])
|
||||
gates.extend(item["quality_gates"])
|
||||
anti_patterns = [
|
||||
"Do not copy Kami's fixed parchment background as a default.",
|
||||
"Do not use generic purple gradients, glass cards, or stock SaaS hero sections unless the content calls for them.",
|
||||
"Do not let Markdown tables become the default shape for every comparison or explanation.",
|
||||
"Do not turn reviewer evidence into user-facing clutter.",
|
||||
"Do not invent screenshots, citations, charts, or UI states.",
|
||||
]
|
||||
return {
|
||||
"skill_name": skill_dir.name,
|
||||
"design_system": design_system_name(matched),
|
||||
"primary_artifact": {
|
||||
"key": primary["key"],
|
||||
"label": primary["label"],
|
||||
"direction": primary["direction"],
|
||||
"matched_keywords": primary["matched_keywords"],
|
||||
},
|
||||
"artifact_families": [
|
||||
{
|
||||
"key": item["key"],
|
||||
"label": item["label"],
|
||||
"score": item["score"],
|
||||
"matched_keywords": item["matched_keywords"],
|
||||
"direction": item["direction"],
|
||||
}
|
||||
for item in matched
|
||||
],
|
||||
"layout_patterns": dedupe(layout_patterns, 8),
|
||||
"design_tokens": DESIGN_TOKENS,
|
||||
"quality_gates": dedupe(gates, 8),
|
||||
"anti_patterns": anti_patterns,
|
||||
"reviewer_note": "Use this profile to judge whether the generated artifacts feel designed for their job, not merely rendered.",
|
||||
}
|
||||
|
||||
|
||||
def render_markdown(summary: dict) -> str:
|
||||
lines = [
|
||||
"# Artifact Design Profile",
|
||||
"",
|
||||
f"Skill: `{summary['skill_name']}`",
|
||||
f"Design system: `{summary['design_system']}`",
|
||||
"",
|
||||
"## Primary Artifact Direction",
|
||||
"",
|
||||
f"**{summary['primary_artifact']['label']}**",
|
||||
"",
|
||||
summary["primary_artifact"]["direction"],
|
||||
"",
|
||||
"## Matched Artifact Families",
|
||||
"",
|
||||
]
|
||||
for family in summary["artifact_families"]:
|
||||
lines.extend(
|
||||
[
|
||||
f"### {family['label']}",
|
||||
f"- Matched keywords: {', '.join(family['matched_keywords'])}",
|
||||
f"- Score: `{family['score']}`",
|
||||
f"- Direction: {family['direction']}",
|
||||
"",
|
||||
]
|
||||
)
|
||||
lines.extend(["## Layout Patterns To Prefer", ""])
|
||||
for item in summary["layout_patterns"]:
|
||||
lines.append(f"- {item}")
|
||||
lines.extend(["", "## Design Tokens", ""])
|
||||
for key, values in summary["design_tokens"].items():
|
||||
lines.append(f"### {key.title()}")
|
||||
for value in values:
|
||||
lines.append(f"- {value}")
|
||||
lines.append("")
|
||||
lines.extend(["## Quality Gates", ""])
|
||||
for item in summary["quality_gates"]:
|
||||
lines.append(f"- {item}")
|
||||
lines.extend(["", "## Anti-Patterns", ""])
|
||||
for item in summary["anti_patterns"]:
|
||||
lines.append(f"- {item}")
|
||||
lines.extend(["", "## Reviewer Note", "", summary["reviewer_note"], ""])
|
||||
return "\n".join(lines).strip() + "\n"
|
||||
|
||||
|
||||
def render_artifact_design_profile(
|
||||
skill_dir: Path,
|
||||
output_md: 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_md = output_md or reports_dir / "artifact-design-profile.md"
|
||||
output_json = output_json or reports_dir / "artifact-design-profile.json"
|
||||
summary = build_summary(skill_dir)
|
||||
output_md.write_text(render_markdown(summary), encoding="utf-8")
|
||||
output_json.write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
return {
|
||||
"ok": True,
|
||||
"skill_dir": str(skill_dir),
|
||||
"artifacts": {
|
||||
"markdown": str(output_md),
|
||||
"json": str(output_json),
|
||||
},
|
||||
"summary": summary,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Render artifact design direction and visual quality gates for a skill package.")
|
||||
parser.add_argument("skill_dir", nargs="?", default=".")
|
||||
parser.add_argument("--output-md")
|
||||
parser.add_argument("--output-json")
|
||||
args = parser.parse_args()
|
||||
result = render_artifact_design_profile(
|
||||
Path(args.skill_dir),
|
||||
Path(args.output_md).resolve() if args.output_md else None,
|
||||
Path(args.output_json).resolve() if args.output_json else None,
|
||||
)
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -203,7 +203,7 @@ def build_summary(skill_dir: Path) -> dict:
|
||||
"matched_keywords": ["general-output-risk"],
|
||||
},
|
||||
]
|
||||
matched = sorted(matched, key=lambda item: item["score"], reverse=True)[:4]
|
||||
matched = sorted(matched, key=lambda item: item["score"], reverse=True)[:5]
|
||||
top_risks = []
|
||||
constraints = []
|
||||
self_repair = []
|
||||
|
||||
@@ -8,6 +8,7 @@ from pathlib import Path
|
||||
from render_intent_confidence import render_intent_confidence
|
||||
from render_intent_dialogue import render_intent_dialogue
|
||||
from render_iteration_directions import render_iteration_directions
|
||||
from render_artifact_design_profile import render_artifact_design_profile
|
||||
from render_output_risk_profile import render_output_risk_profile
|
||||
from render_reference_scan import render_reference_scan
|
||||
from render_reference_synthesis import render_reference_synthesis
|
||||
@@ -62,6 +63,11 @@ def load_output_risk_summary(skill_dir: Path) -> dict:
|
||||
return payload if isinstance(payload, dict) else {}
|
||||
|
||||
|
||||
def load_artifact_design_summary(skill_dir: Path) -> dict:
|
||||
payload = load_json(skill_dir / "reports" / "artifact-design-profile.json")
|
||||
return payload if isinstance(payload, dict) else {}
|
||||
|
||||
|
||||
def ensure_report_inputs(skill_dir: Path) -> dict:
|
||||
overview_json = skill_dir / "reports" / "skill-overview.json"
|
||||
intent_confidence_json = skill_dir / "reports" / "intent-confidence.json"
|
||||
@@ -69,6 +75,7 @@ def ensure_report_inputs(skill_dir: Path) -> dict:
|
||||
reference_json = skill_dir / "reports" / "reference-scan.json"
|
||||
reference_synthesis_json = skill_dir / "reports" / "reference-synthesis.json"
|
||||
output_risk_json = skill_dir / "reports" / "output-risk-profile.json"
|
||||
artifact_design_json = skill_dir / "reports" / "artifact-design-profile.json"
|
||||
directions_json = skill_dir / "reports" / "iteration-directions.json"
|
||||
|
||||
overview_payload = load_json(overview_json) if overview_json.exists() else {}
|
||||
@@ -77,6 +84,7 @@ def ensure_report_inputs(skill_dir: Path) -> dict:
|
||||
reference_payload = load_json(reference_json) if reference_json.exists() else {}
|
||||
reference_synthesis_payload = load_json(reference_synthesis_json) if reference_synthesis_json.exists() else {}
|
||||
output_risk_payload = load_json(output_risk_json) if output_risk_json.exists() else {}
|
||||
artifact_design_payload = load_json(artifact_design_json) if artifact_design_json.exists() else {}
|
||||
directions_payload = load_json(directions_json) if directions_json.exists() else {}
|
||||
|
||||
intent_confidence = intent_confidence_payload or render_intent_confidence(skill_dir)["summary"]
|
||||
@@ -84,6 +92,7 @@ def ensure_report_inputs(skill_dir: Path) -> dict:
|
||||
reference = reference_payload or render_reference_scan(skill_dir, [])["summary"]
|
||||
reference_synthesis = reference_synthesis_payload or render_reference_synthesis(skill_dir)["summary"]
|
||||
output_risk = output_risk_payload or render_output_risk_profile(skill_dir)["summary"]
|
||||
artifact_design = artifact_design_payload or render_artifact_design_profile(skill_dir)["summary"]
|
||||
overview = overview_payload or render_skill_overview(skill_dir)["summary"]
|
||||
iteration = directions_payload.get("summary", {}) or render_iteration_directions(skill_dir)["summary"]
|
||||
feedback = load_feedback_summary(skill_dir)
|
||||
@@ -93,6 +102,7 @@ def ensure_report_inputs(skill_dir: Path) -> dict:
|
||||
benchmark = load_benchmark_summary(skill_dir)
|
||||
reference_synthesis = load_reference_synthesis_summary(skill_dir)
|
||||
output_risk = load_output_risk_summary(skill_dir) or output_risk
|
||||
artifact_design = load_artifact_design_summary(skill_dir) or artifact_design
|
||||
return {
|
||||
"overview": overview,
|
||||
"intent_confidence": intent_confidence,
|
||||
@@ -106,6 +116,7 @@ def ensure_report_inputs(skill_dir: Path) -> dict:
|
||||
"benchmark": benchmark,
|
||||
"reference_synthesis": reference_synthesis,
|
||||
"output_risk": output_risk,
|
||||
"artifact_design": artifact_design,
|
||||
}
|
||||
|
||||
|
||||
@@ -238,6 +249,7 @@ def evidence_readiness(report: dict) -> dict:
|
||||
intent_confidence = report.get("intent_confidence", {})
|
||||
reference_synthesis = report.get("reference_synthesis", {})
|
||||
output_risk = report.get("output_risk", {})
|
||||
artifact_design = report.get("artifact_design", {})
|
||||
benchmark = report.get("benchmark", {})
|
||||
synthesis = reference_synthesis.get("synthesis", {}) if isinstance(reference_synthesis, dict) else {}
|
||||
pattern_gate = synthesis.get("pattern_gate", {}) if isinstance(synthesis, dict) else {}
|
||||
@@ -269,6 +281,11 @@ def evidence_readiness(report: dict) -> dict:
|
||||
"status": "ready" if output_risk.get("risk_families") else "needs review",
|
||||
"detail": f"{len(output_risk.get('risk_families', []))} output risk families attached.",
|
||||
},
|
||||
{
|
||||
"label": "Artifact design profile",
|
||||
"status": "ready" if artifact_design.get("primary_artifact") else "needs review",
|
||||
"detail": artifact_design.get("primary_artifact", {}).get("direction", "No artifact design profile attached."),
|
||||
},
|
||||
]
|
||||
ready_count = sum(1 for item in checks if item["status"] == "ready")
|
||||
return {
|
||||
@@ -292,6 +309,7 @@ def render_html(report: dict) -> str:
|
||||
benchmark = report.get("benchmark", {})
|
||||
reference_synthesis = report.get("reference_synthesis", {})
|
||||
output_risk = report.get("output_risk", {})
|
||||
artifact_design = report.get("artifact_design", {})
|
||||
architecture = architecture_steps(overview)
|
||||
compare_table_rows = compare_rows(compare)
|
||||
benchmark_rows = benchmark_cards(benchmark)
|
||||
@@ -329,6 +347,21 @@ def render_html(report: dict) -> str:
|
||||
if not output_risk_items:
|
||||
output_risk_items = "<li>No output risk profile attached yet. Generate one before approving example outputs.</li>"
|
||||
|
||||
artifact_design_items = "".join(
|
||||
(
|
||||
"<li>"
|
||||
f"<strong>{html.escape(item.get('label', item.get('key', 'Artifact')))}</strong><br>"
|
||||
f"<span>{html.escape(item.get('direction', ''))}</span>"
|
||||
"</li>"
|
||||
)
|
||||
for item in artifact_design.get("artifact_families", [])[:3]
|
||||
)
|
||||
if not artifact_design_items:
|
||||
artifact_design_items = "<li>No artifact design profile attached yet. Generate one before approving visual or document outputs.</li>"
|
||||
design_gate_items = "".join(
|
||||
f"<li>{html.escape(item)}</li>" for item in artifact_design.get("quality_gates", [])[:5]
|
||||
) or "<li>No artifact design quality gates attached yet.</li>"
|
||||
|
||||
readiness_html = "".join(
|
||||
(
|
||||
"<li>"
|
||||
@@ -757,6 +790,18 @@ def render_html(report: dict) -> str:
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="grid">
|
||||
<div class="panel">
|
||||
<h2>Artifact design profile</h2>
|
||||
<p class="minor">Design system: {html.escape(str(artifact_design.get('design_system', 'not generated')))}</p>
|
||||
<ul>{artifact_design_items}</ul>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<h2>Visual quality gates</h2>
|
||||
<ul>{design_gate_items}</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="grid">
|
||||
<div class="panel">
|
||||
<h2>Reference coach</h2>
|
||||
|
||||
@@ -74,6 +74,10 @@ def load_reference_synthesis(skill_dir: Path) -> dict:
|
||||
return load_json(skill_dir / "reports" / "reference-synthesis.json")
|
||||
|
||||
|
||||
def load_artifact_design(skill_dir: Path) -> dict:
|
||||
return load_json(skill_dir / "reports" / "artifact-design-profile.json")
|
||||
|
||||
|
||||
def extract_title(body: str, fallback: str) -> str:
|
||||
for line in body.splitlines():
|
||||
if line.startswith("# "):
|
||||
@@ -212,6 +216,15 @@ def synthesis_highlights(synthesis: dict) -> list[str]:
|
||||
return synthesis.get("synthesis", {}).get("borrow_now", [])[:3]
|
||||
|
||||
|
||||
def artifact_design_highlights(profile: dict) -> list[str]:
|
||||
primary = profile.get("primary_artifact", {})
|
||||
highlights = []
|
||||
if primary.get("direction"):
|
||||
highlights.append(primary["direction"])
|
||||
highlights.extend(profile.get("quality_gates", [])[:3])
|
||||
return highlights[:4]
|
||||
|
||||
|
||||
def build_summary(skill_dir: Path) -> dict:
|
||||
skill_text = (skill_dir / "SKILL.md").read_text(encoding="utf-8")
|
||||
frontmatter, body = parse_frontmatter(skill_text)
|
||||
@@ -230,6 +243,7 @@ def build_summary(skill_dir: Path) -> dict:
|
||||
strengths = derive_strengths(skill_dir, manifest)
|
||||
benchmark = load_benchmark(skill_dir)
|
||||
reference_synthesis = load_reference_synthesis(skill_dir)
|
||||
artifact_design = load_artifact_design(skill_dir)
|
||||
|
||||
return {
|
||||
"name": name,
|
||||
@@ -244,6 +258,11 @@ def build_summary(skill_dir: Path) -> dict:
|
||||
"introduction": introduction_lines(description),
|
||||
"benchmark_highlights": benchmark_highlights(benchmark),
|
||||
"synthesis_highlights": synthesis_highlights(reference_synthesis),
|
||||
"artifact_design": {
|
||||
"design_system": artifact_design.get("design_system", "content-led editorial"),
|
||||
"primary_label": artifact_design.get("primary_artifact", {}).get("label", "General artifact"),
|
||||
"highlights": artifact_design_highlights(artifact_design),
|
||||
},
|
||||
"metadata": {
|
||||
"canonical_format": interface_data.get("compatibility", {}).get("canonical_format", "agent-skills"),
|
||||
"targets": interface_data.get("compatibility", {}).get("adapter_targets", []),
|
||||
@@ -288,6 +307,8 @@ def render_html(summary: dict) -> str:
|
||||
for item in summary.get("benchmark_highlights", [])
|
||||
)
|
||||
synthesis_html = "".join(f"<li>{html.escape(item)}</li>" for item in summary.get("synthesis_highlights", []))
|
||||
artifact_design = summary.get("artifact_design", {})
|
||||
artifact_design_html = "".join(f"<li>{html.escape(item)}</li>" for item in artifact_design.get("highlights", []))
|
||||
|
||||
return f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@@ -519,6 +540,19 @@ def render_html(summary: dict) -> str:
|
||||
<ul class="strengths">{synthesis_html or "<li>No synthesis has been generated yet. Run the reference synthesis after the benchmark scan.</li>"}</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<h2>Artifact design direction</h2>
|
||||
<p>Use this to understand how the generated reports, tutorials, viewers, or visual artifacts should feel before changing the layout.</p>
|
||||
</div>
|
||||
<div>
|
||||
<p><strong>{html.escape(str(artifact_design.get("primary_label", "General artifact")))}</strong> · {html.escape(str(artifact_design.get("design_system", "content-led editorial")))}</p>
|
||||
<ul class="strengths">{artifact_design_html or "<li>No artifact design profile has been generated yet.</li>"}</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -536,6 +536,7 @@ def command_quickstart(args: argparse.Namespace) -> int:
|
||||
"artifacts": {
|
||||
"benchmark_scan": payload.get("artifacts", {}).get("github_benchmark_scan_md"),
|
||||
"reference_synthesis": payload.get("artifacts", {}).get("reference_synthesis_md"),
|
||||
"artifact_design_profile": payload.get("artifacts", {}).get("artifact_design_profile_md"),
|
||||
"review_viewer": payload.get("artifacts", {}).get("review_viewer_html"),
|
||||
},
|
||||
},
|
||||
@@ -648,6 +649,7 @@ def command_report(args: argparse.Namespace) -> int:
|
||||
run_script("render_context_reports.py", []),
|
||||
run_script("render_portability_report.py", []),
|
||||
run_script("render_reference_synthesis.py", [str(ROOT)]),
|
||||
run_script("render_artifact_design_profile.py", [str(ROOT)]),
|
||||
]
|
||||
)
|
||||
report = {
|
||||
@@ -664,6 +666,7 @@ def command_report(args: argparse.Namespace) -> int:
|
||||
"context_budget": "reports/context_budget.json",
|
||||
"portability_score": "reports/portability_score.json",
|
||||
"reference_synthesis": "reports/reference-synthesis.json",
|
||||
"artifact_design_profile": "reports/artifact-design-profile.json",
|
||||
},
|
||||
}
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2))
|
||||
@@ -777,6 +780,17 @@ def command_output_risk_profile(args: argparse.Namespace) -> int:
|
||||
return 0 if result["ok"] else 2
|
||||
|
||||
|
||||
def command_artifact_design_profile(args: argparse.Namespace) -> int:
|
||||
cmd = [str(Path(args.skill_dir).resolve())]
|
||||
if args.output_md:
|
||||
cmd.extend(["--output-md", args.output_md])
|
||||
if args.output_json:
|
||||
cmd.extend(["--output-json", args.output_json])
|
||||
result = run_script("render_artifact_design_profile.py", cmd)
|
||||
print(json.dumps(result["payload"] if result["payload"] is not None else result, ensure_ascii=False, indent=2))
|
||||
return 0 if result["ok"] else 2
|
||||
|
||||
|
||||
def command_iteration_directions(args: argparse.Namespace) -> int:
|
||||
skill_dir = str(Path(args.skill_dir).resolve())
|
||||
cmd = [skill_dir]
|
||||
@@ -1145,6 +1159,15 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
output_risk_cmd.add_argument("--output-json")
|
||||
output_risk_cmd.set_defaults(func=command_output_risk_profile)
|
||||
|
||||
artifact_design_cmd = subparsers.add_parser(
|
||||
"artifact-design-profile",
|
||||
help="Render artifact design direction and visual quality gates for a skill package.",
|
||||
)
|
||||
artifact_design_cmd.add_argument("skill_dir", nargs="?", default=".")
|
||||
artifact_design_cmd.add_argument("--output-md")
|
||||
artifact_design_cmd.add_argument("--output-json")
|
||||
artifact_design_cmd.set_defaults(func=command_artifact_design_profile)
|
||||
|
||||
iteration_directions_cmd = subparsers.add_parser(
|
||||
"iteration-directions",
|
||||
help="Render the top three next iteration directions for a skill package.",
|
||||
|
||||
Reference in New Issue
Block a user