feat: add artifact design quality profiles

This commit is contained in:
yaojingang
2026-05-01 14:27:51 +08:00
parent e3ca33c962
commit 98d2593647
46 changed files with 1649 additions and 740 deletions
+9 -1
View File
@@ -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"],
+346
View File
@@ -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()
+1 -1
View File
@@ -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 = []
+45
View File
@@ -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>
+34
View File
@@ -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>
+23
View File
@@ -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.",