feat: add conversational quickstart and diff studio
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 チェックを標準フローに内蔵
|
||||
|
||||
|
||||
@@ -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 в стандартном потоке
|
||||
|
||||
|
||||
@@ -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 检查
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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")
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user