feat: add conversational quickstart and diff studio

This commit is contained in:
yaojingang
2026-04-14 14:39:52 +08:00
parent 881a8317c6
commit b73fff0253
10 changed files with 210 additions and 22 deletions
+2 -2
View File
@@ -22,11 +22,11 @@ It turns rough workflows, transcripts, prompts, notes, and runbooks into reusabl
- a front-loaded intent dialogue that starts from the user's real work, desired outcome, and quality bar
- an automatic GitHub benchmark scan that pulls the top three public repositories first, extracts borrow or avoid patterns, and then asks whether the user has references worth learning from
- a generated visual HTML overview for each newly initialized skill
- a compact HTML review viewer for first-pass human review
- a side-by-side HTML review studio for first-pass human review
- three high-value next iteration directions after the first package is created
- a lightweight feedback log that does not require a full promotion cycle
- a baseline compare report for with-skill vs baseline review
- an archetype-aware quickstart that steers new packages toward scaffold, production, library, or governed fits
- a conversation-style, archetype-aware quickstart that steers new packages toward scaffold, production, library, or governed fits
- neutral source metadata plus client-specific adapters
- governance, promotion, and portability checks built into the default flow
+1 -1
View File
@@ -17,7 +17,7 @@ Il transforme des workflows bruts, des transcripts, des prompts, des notes et de
- un review viewer HTML compact pour accélérer la première revue humaine
- un feedback log léger pour éviter de lancer tout le flux de promotion à chaque tour
- un rapport with-skill vs baseline pour visualiser rapidement le gain incrémental
- un quickstart sensible aux archetypes pour orienter un nouveau skill vers scaffold, production, library ou governed
- un quickstart conversationnel, sensible aux archetypes, pour orienter un nouveau skill vers scaffold, production, library ou governed
- des métadonnées sources neutres et des adaptateurs spécifiques au client
- des contrôles de gouvernance, de promotion et de portabilité intégrés au flux standard
+1 -1
View File
@@ -17,7 +17,7 @@
- 初回の人間レビューを助ける、コンパクトな HTML review viewer
- 毎回フル promotion flow を通さずに使える軽量 feedback log
- 増分価値をすばやく確認できる with-skill vs baseline 比較レポート
- scaffold / production / library / governed を案内する archetype-aware quickstart
- 会話型 discovery flow として scaffold / production / library / governed を案内する archetype-aware quickstart
- 中立的なソースメタデータとクライアント別アダプタ
- ガバナンス、昇格判定、portability チェックを標準フローに内蔵
+1 -1
View File
@@ -17,7 +17,7 @@
- компактным HTML review viewer для быстрой первой ручной оценки
- легким feedback log, чтобы не запускать полный promotion flow на каждом цикле
- отчетом with-skill vs baseline для быстрого сравнения инкрементальной пользы
- archetype-aware quickstart, который помогает выбрать scaffold, production, library или governed форму
- conversation-style archetype-aware quickstart, который ведет пользователя как discovery flow и помогает выбрать scaffold, production, library или governed форму
- нейтральными исходными метаданными и клиентскими адаптерами
- встроенными проверками governance, promotion и portability в стандартном потоке
+1 -1
View File
@@ -17,7 +17,7 @@
- 提供一个紧凑的 HTML review viewer,方便第一次人工理解和评审
- 提供一个轻量 feedback log,不必每次都走完整 promotion 流程
- 提供一个 with-skill vs baseline 的对比报告,便于快速看增量收益
- 提供一个 archetype-aware quickstart,引导新 skill 落到 scaffold、production、library 或 governed 的合适形态
- 提供一个更像对话发现流程的 archetype-aware quickstart,引导新 skill 落到 scaffold、production、library 或 governed 的合适形态
- 中性的源元数据以及面向不同客户端的适配层
- 内建的治理、晋升和 portability 检查
+2 -2
View File
@@ -6,9 +6,9 @@
"context_budget_tier": "production",
"context_budget_limit": 1000,
"skill_body_tokens": 824,
"other_text_tokens": 274311,
"other_text_tokens": 276100,
"estimated_initial_load_tokens": 1000,
"estimated_total_text_tokens": 275135,
"estimated_total_text_tokens": 276924,
"relevant_file_count": 165,
"unused_resource_dirs": [],
"quality_signal_points": 140,
+149 -1
View File
@@ -2,6 +2,7 @@
import argparse
import html
import json
import re
from pathlib import Path
from render_intent_dialogue import render_intent_dialogue
@@ -132,6 +133,67 @@ def benchmark_cards(benchmark: dict) -> list[dict]:
return cards
def split_sentences(text: str) -> list[str]:
if not text:
return []
parts = [item.strip() for item in re.split(r"(?<=[.!?])\s+", " ".join(text.split())) if item.strip()]
return parts
def metric_delta(current: int | float, baseline: int | float) -> str:
delta = current - baseline
if delta == 0:
return "0"
return f"{delta:+}"
def variant_diff_cards(compare: dict) -> list[dict]:
baseline = compare.get("baseline", {})
current = compare.get("current_candidate", {})
winner = compare.get("winner", {})
variants = [
("Baseline", baseline),
("Current", current),
(f"Winner — {winner.get('label', 'Winner')}", winner),
]
baseline_sentences = split_sentences(baseline.get("description", ""))
baseline_set = set(baseline_sentences)
baseline_dev = baseline.get("dev", {}).get("total_errors", 0)
baseline_holdout = baseline.get("holdout", {}).get("total_errors", 0)
cards = []
seen = set()
for label, payload in variants:
if not payload:
continue
unique_key = (payload.get("description"), payload.get("strategy"), label)
if unique_key in seen:
continue
seen.add(unique_key)
description = payload.get("description", "")
sentences = split_sentences(description)
sentence_set = set(sentences)
added = [item for item in sentences if item not in baseline_set][:3]
removed = [item for item in baseline_sentences if item not in sentence_set][:2]
dev_errors = payload.get("dev", {}).get("total_errors", 0)
holdout_errors = payload.get("holdout", {}).get("total_errors", 0)
cards.append(
{
"label": label,
"strategy": payload.get("strategy", "existing"),
"description": description,
"tokens": payload.get("estimated_tokens", 0),
"dev_errors": dev_errors,
"holdout_errors": holdout_errors,
"token_delta": metric_delta(payload.get("estimated_tokens", 0), baseline.get("estimated_tokens", 0)),
"dev_delta": metric_delta(dev_errors, baseline_dev),
"holdout_delta": metric_delta(holdout_errors, baseline_holdout),
"added": added if label != "Baseline" else baseline_sentences[:3],
"removed": removed,
}
)
return cards
def render_html(report: dict) -> str:
overview = report["overview"]
intent = report["intent"]
@@ -146,6 +208,7 @@ def render_html(report: dict) -> str:
architecture = architecture_steps(overview)
compare_table_rows = compare_rows(compare)
benchmark_rows = benchmark_cards(benchmark)
variant_cards = variant_diff_cards(compare)
strength_items = "".join(f"<li>{html.escape(item)}</li>" for item in overview.get("strengths", []))
logic_items = "".join(f"<li>{html.escape(item)}</li>" for item in overview.get("logic_steps", []))
@@ -264,6 +327,38 @@ def render_html(report: dict) -> str:
else:
benchmark_html = "<p class='minor'>No GitHub benchmark scan has been attached to this package yet.</p>"
variant_diff_html = ""
if variant_cards:
variant_diff_html = "".join(
(
"<div class='variant-card'>"
f"<div class='variant-head'><h3>{html.escape(item['label'])}</h3><span>{html.escape(item['strategy'])}</span></div>"
f"<p class='variant-description'>{html.escape(item['description'])}</p>"
"<div class='variant-metrics'>"
f"<span>tokens {html.escape(str(item['tokens']))} ({html.escape(item['token_delta'])})</span>"
f"<span>dev {html.escape(str(item['dev_errors']))} ({html.escape(item['dev_delta'])})</span>"
f"<span>holdout {html.escape(str(item['holdout_errors']))} ({html.escape(item['holdout_delta'])})</span>"
"</div>"
"<div class='variant-cues'>"
"<p><strong>Adds relative to baseline</strong></p>"
+ (
"<ul>" + "".join(f"<li>{html.escape(value)}</li>" for value in item["added"]) + "</ul>"
if item["added"]
else "<p class='minor'>No added cues.</p>"
)
+ "<p><strong>Drops from baseline</strong></p>"
+ (
"<ul>" + "".join(f"<li>{html.escape(value)}</li>" for value in item["removed"]) + "</ul>"
if item["removed"]
else "<p class='minor'>No dropped cues.</p>"
)
+ "</div></div>"
)
for item in variant_cards
)
else:
variant_diff_html = "<p class='minor'>No description optimization compare payload is attached yet.</p>"
return f"""<!DOCTYPE html>
<html lang="en">
<head>
@@ -365,6 +460,12 @@ def render_html(report: dict) -> str:
gap: 16px;
margin-top: 16px;
}}
.variant-grid {{
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
margin-top: 16px;
}}
.direction-card {{
padding: 18px;
}}
@@ -376,6 +477,48 @@ def render_html(report: dict) -> str:
color: var(--muted);
font-size: 13px;
}}
.variant-card {{
border: 1px solid var(--line);
background: var(--white);
padding: 18px;
}}
.variant-head {{
display: flex;
justify-content: space-between;
gap: 12px;
align-items: baseline;
}}
.variant-head span {{
color: var(--muted);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.06em;
}}
.variant-description {{
margin: 14px 0;
padding-left: 14px;
border-left: 2px solid var(--line);
color: var(--text);
}}
.variant-metrics {{
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 14px;
}}
.variant-metrics span {{
border: 1px solid var(--line);
background: var(--soft);
padding: 6px 10px;
font-size: 12px;
}}
.variant-cues p {{
margin: 8px 0 6px;
}}
.variant-cues ul {{
margin: 0 0 12px;
padding-left: 18px;
}}
table {{
width: 100%;
border-collapse: collapse;
@@ -395,7 +538,7 @@ def render_html(report: dict) -> str:
color: var(--muted);
}}
@media (max-width: 1000px) {{
.arch-grid, .direction-grid, .grid {{
.arch-grid, .direction-grid, .variant-grid, .grid {{
grid-template-columns: 1fr;
}}
}}
@@ -453,6 +596,11 @@ def render_html(report: dict) -> str:
</div>
</section>
<section>
<h2>Variant diff studio</h2>
<div class="variant-grid">{variant_diff_html}</div>
</section>
<section class="grid">
<div class="panel">
<h2>Reference coach</h2>
+50 -12
View File
@@ -123,6 +123,13 @@ def prompt_with_default(label: str, default: str) -> str:
return value or default
def prompt_optional(label: str, default: str = "skip") -> str:
sys.stderr.write(f"{label} [{default}]: ")
sys.stderr.flush()
value = sys.stdin.readline().strip()
return value or default
def prompt_optional_entries(label: str) -> list[str]:
sys.stderr.write(f"{label} [none]: ")
sys.stderr.flush()
@@ -173,6 +180,17 @@ def archetype_guidance(archetype: str) -> dict:
return mapping.get(archetype, mapping["scaffold"])
def discovery_summary(job: str, primary_output: str, archetype: str, guidance: dict) -> str:
return (
"\nHere's the shape I'm hearing so far:\n"
f"- Repeated job: {job}\n"
f"- Desired hand-back: {primary_output}\n"
f"- Best starting archetype: {archetype}\n"
f"- First gate: {guidance['first_gate']}\n"
f"- Current focus: {guidance['focus']}\n"
)
def command_init(args: argparse.Namespace) -> int:
cmd = [
args.name,
@@ -203,27 +221,47 @@ def command_init(args: argparse.Namespace) -> int:
def command_quickstart(args: argparse.Namespace) -> int:
sys.stderr.write("Let's start gently and shape this skill around the real work, the outcome, and the standards you actually care about.\n")
sys.stderr.write("You do not need a perfect brief. A rough description is enough, and I will help tighten it.\n")
sys.stderr.write("I will also look at a few strong public GitHub references first, so we can borrow proven patterns without copying the source.\n")
sys.stderr.write("Let's start gently. You do not need a polished brief here.\n")
sys.stderr.write("Give me the real work in your own words, and I will help turn it into a clean first-pass skill.\n")
sys.stderr.write("Before we deepen the package, I will also look at a few strong public GitHub references so we can borrow patterns without copying them.\n")
name = args.name or prompt_with_default("Skill name", "my-skill")
job = args.job or prompt_with_default("What repeated work do you most want this skill to quietly take off your hands", "Turn a repeated workflow into a reusable skill.")
primary_output = args.primary_output or prompt_with_default("If it works well, what should it hand back so you can keep moving", "A reusable skill package.")
job = args.job or prompt_with_default(
"In your own words, what repeated work do you most want this skill to quietly take over",
"Turn a repeated workflow into a reusable skill.",
)
primary_output = args.primary_output or prompt_with_default(
"If it works beautifully, what should it hand back so you or the next person can keep moving",
"A reusable skill package.",
)
description = args.description or f"{job.rstrip('.')} Primary output: {primary_output.rstrip('.')}."
inferred_archetype, archetype_reason = infer_archetype(job, description)
archetype = args.archetype or prompt_with_default("Archetype (scaffold/production/library/governed)", inferred_archetype)
guidance = archetype_guidance(inferred_archetype)
sys.stderr.write(discovery_summary(job, primary_output, inferred_archetype, guidance))
correction = prompt_optional(
"If I am off, what is the first thing I should correct before I package this idea",
"looks right",
)
if correction.lower() not in {"looks right", "skip", "none", "no"}:
description = f"{description.rstrip('.')} Keep this correction in scope: {correction.rstrip('.')}."
inferred_archetype, archetype_reason = infer_archetype(job, description)
guidance = archetype_guidance(inferred_archetype)
sys.stderr.write("\nThanks. I tightened the frame before moving on.\n")
sys.stderr.write(discovery_summary(job, primary_output, inferred_archetype, guidance))
archetype = args.archetype or prompt_with_default("I would start with this archetype (scaffold/production/library/governed)", inferred_archetype)
archetype = archetype if archetype in ARCHETYPE_MODE else inferred_archetype
default_mode = ARCHETYPE_MODE[archetype]
mode = args.mode or prompt_with_default("Mode (scaffold/production/library/governed)", default_mode)
mode = args.mode or prompt_with_default("For the first pass, I would keep the mode here (scaffold/production/library/governed)", default_mode)
mode = mode if mode in ARCHETYPE_MODE.values() else default_mode
guidance = archetype_guidance(archetype)
sys.stderr.write(
f"\nGood. I will treat this as `{archetype}` in `{mode}` mode, so the first pass stays focused on {guidance['focus']}.\n"
)
user_references = args.user_reference or prompt_optional_entries(
"Your own reference examples to learn from as pattern hints (repo, product, page, workflow; comma-separated)"
)
external_references = args.external_reference or prompt_optional_entries(
"Additional public benchmark objects you already know you want to borrow from (comma-separated)"
"If there is anything you admire and want me to learn from as pattern hints, send it here (repo, product, page, workflow; comma-separated)"
)
external_references = args.external_reference or []
local_constraints = args.local_constraint or prompt_optional_entries(
"Local constraints that must still be respected (privacy, naming, compatibility; comma-separated)"
"Tell me any local constraints I must keep in view (privacy, naming, compatibility; comma-separated)"
)
github_query = args.github_query or build_query(" ".join(filter(None, [job, primary_output, description])))
sys.stderr.write(f"GitHub benchmark query: {github_query}\n")
+1
View File
@@ -45,6 +45,7 @@ def main() -> None:
html_text = html_path.read_text(encoding="utf-8")
assert "Architecture at a glance" in html_text, html_text[:500]
assert "Compare view" in html_text, html_text[:500]
assert "Variant diff studio" in html_text, html_text[:900]
assert "Reference coach" in html_text, html_text[:900]
assert "Top three next moves" in html_text, html_text[:500]
print(json.dumps({"ok": True}, ensure_ascii=False, indent=2))
+2 -1
View File
@@ -50,7 +50,7 @@ def main() -> None:
str(tmp_root),
"--github-fixture-dir",
str(BENCHMARK_FIXTURE_DIR),
input_text="quickstart-skill\nTurn rough notes into a reusable package.\nA reusable markdown workflow.\nproduction\nproduction\nA top-tier internal workflow product\nhigh-star GitHub repo, official docs\nprivacy and naming\n",
input_text="quickstart-skill\nTurn rough notes into a reusable package.\nA reusable markdown workflow.\nlooks right\nproduction\nproduction\nA top-tier internal workflow product\nprivacy and naming\n",
)
assert quickstart_result["ok"], quickstart_result
quickstart_root = Path(quickstart_result["payload"]["root"])
@@ -59,6 +59,7 @@ def main() -> None:
assert quickstart_result["payload"]["archetype"] == "production", quickstart_result
assert len(quickstart_result["payload"]["references"]["benchmark_repositories"]) == 3, quickstart_result
assert quickstart_result["payload"]["references"]["user_references"] == ["A top-tier internal workflow product"], quickstart_result
assert quickstart_result["payload"]["guidance"]["experience_note"], quickstart_result
validate_result = run("validate", str(created))
assert validate_result["ok"], validate_result