From f19b64237c902e9824412b4fea183bb78fd9deb5 Mon Sep 17 00:00:00 2001 From: yaojingang Date: Wed, 8 Apr 2026 13:53:35 +0800 Subject: [PATCH] feat: add intent dialogue and iteration directions --- .gitignore | 1 + Makefile | 9 +- README.md | 13 +- SKILL.md | 9 +- docs/README.fr-FR.md | 9 +- docs/README.ja-JP.md | 9 +- docs/README.ru-RU.md | 9 +- docs/README.zh-CN.md | 9 +- references/intent-dialogue.md | 48 +++++ references/iteration-philosophy.md | 30 +++ references/skill-engineering-method.md | 47 +++-- scripts/init_skill.py | 22 ++- scripts/render_intent_dialogue.py | 206 +++++++++++++++++++++ scripts/render_iteration_directions.py | 246 +++++++++++++++++++++++++ scripts/yao.py | 42 +++++ tests/verify_iteration_directions.py | 47 +++++ tests/verify_skill_overview.py | 10 + tests/verify_yao_cli.py | 10 + 18 files changed, 740 insertions(+), 36 deletions(-) create mode 100644 references/intent-dialogue.md create mode 100644 references/iteration-philosophy.md create mode 100644 scripts/render_intent_dialogue.py create mode 100644 scripts/render_iteration_directions.py create mode 100644 tests/verify_iteration_directions.py diff --git a/.gitignore b/.gitignore index 8c4e8bd..9b628fc 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ tests/tmp_cli/ tests/tmp_skill_overview/ tests/tmp_skill_preview/ tests/tmp_reference_scan/ +tests/tmp_iteration_directions/ reports/release_snapshots/ diff --git a/Makefile b/Makefile index 92cf944..e338c1e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ PYTHON ?= python3 -.PHONY: eval eval-suite route-scorecard route-confusion-check description-optimization judge-blind-eval description-optimization-check promotion-check yao-cli-check skill-overview-check reference-scan-check description-drift-history iteration-ledger results-panel regression-history context-reports portability-report portability-check failure-regression-check package-check package-failure-check snapshot-check validate lint governance-check resource-boundary-check quality-check test clean +.PHONY: eval eval-suite route-scorecard route-confusion-check description-optimization judge-blind-eval description-optimization-check promotion-check yao-cli-check skill-overview-check reference-scan-check iteration-directions-check description-drift-history iteration-ledger results-panel regression-history context-reports portability-report portability-check failure-regression-check package-check package-failure-check snapshot-check validate lint governance-check resource-boundary-check quality-check test clean eval: $(PYTHON) scripts/trigger_eval.py --description-file evals/improved_description.txt --cases evals/trigger_cases.json --baseline-description-file evals/baseline_description.txt @@ -35,6 +35,9 @@ skill-overview-check: reference-scan-check: $(PYTHON) tests/verify_reference_scan.py +iteration-directions-check: + $(PYTHON) tests/verify_iteration_directions.py + description-drift-history: $(PYTHON) scripts/render_description_drift_history.py @@ -83,7 +86,7 @@ resource-boundary-check: quality-check: $(PYTHON) tests/verify_quality_checks.py -test: eval eval-suite route-scorecard route-confusion-check description-optimization description-optimization-check promotion-check yao-cli-check skill-overview-check reference-scan-check description-drift-history iteration-ledger regression-history context-reports portability-report portability-check failure-regression-check package-check package-failure-check snapshot-check validate lint governance-check resource-boundary-check quality-check +test: eval eval-suite route-scorecard route-confusion-check description-optimization description-optimization-check promotion-check yao-cli-check skill-overview-check reference-scan-check iteration-directions-check description-drift-history iteration-ledger regression-history context-reports portability-report portability-check failure-regression-check package-check package-failure-check snapshot-check validate lint governance-check resource-boundary-check quality-check clean: - rm -rf dist tests/tmp tests/tmp_snapshot + rm -rf dist tests/tmp tests/tmp_snapshot tests/tmp_cli tests/tmp_skill_overview tests/tmp_reference_scan tests/tmp_iteration_directions diff --git a/README.md b/README.md index 0bbfb4e..7d002f4 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,10 @@ It turns rough workflows, transcripts, prompts, notes, and runbooks into reusabl - a clear trigger surface - a lean `SKILL.md` - optional references, scripts, and evals +- a front-loaded intent dialogue before deep authoring - a controlled benchmark scan before deep authoring - a generated visual HTML overview for each newly initialized skill +- three high-value next iteration directions after the first package is created - neutral source metadata plus client-specific adapters - governance, promotion, and portability checks built into the default flow @@ -47,18 +49,21 @@ Read it in 10 seconds: ## Quick Start 1. Describe the workflow, prompt set, or repeated task you want to turn into a skill. -2. Run a short reference scan with external benchmarks first, then use local files only for fit and privacy checks. -3. Use `yao-meta-skill` to generate or improve the package in scaffold, production, or library mode. -4. Run `context_sizer.py`, `resource_boundary_check.py`, `governance_check.py`, `trigger_eval.py`, and `cross_packager.py` as needed to validate and export the result. +2. Start with a short intent dialogue so the real job, outputs, exclusions, and constraints are explicit. +3. Run a short reference scan with external benchmarks first, then use local files only for fit and privacy checks. +4. Use `yao-meta-skill` to generate or improve the package in scaffold, production, or library mode. +5. Review the generated `reports/intent-dialogue.md`, `reports/skill-overview.html`, and `reports/iteration-directions.md` before adding more structure. Or use the unified authoring CLI: ```bash python3 scripts/yao.py init my-skill --description "Describe what the skill does." +python3 scripts/yao.py intent-dialogue my-skill python3 scripts/yao.py reference-scan my-skill \ --external-reference "World Class Method::method::Borrow a tight evaluation loop.::Do not copy heavy process." \ --local-constraint "Current Library Naming::structure::Keep naming aligned with the local skill library.::Do not inherit private references." python3 scripts/yao.py skill-report my-skill +python3 scripts/yao.py iteration-directions my-skill python3 scripts/yao.py package . --platform generic --output-dir dist ``` @@ -201,9 +206,11 @@ The design logic is simple: The repository now treats method as a first-class asset instead of scattered guidance. - [Skill Engineering Method](references/skill-engineering-method.md) +- [Intent Dialogue](references/intent-dialogue.md) - [Reference Scan Strategy](references/reference-scan.md) - [Skill Archetypes](references/skill-archetypes.md) - [Gate Selection](references/gate-selection.md) +- [Iteration Philosophy](references/iteration-philosophy.md) - [Non-Skill Decision Tree](references/non-skill-decision-tree.md) - [Regression Cause Taxonomy](references/regression-cause-taxonomy.md) - [Human Review Template](references/human-review-template.md) diff --git a/SKILL.md b/SKILL.md index 101dddf..d0a5875 100644 --- a/SKILL.md +++ b/SKILL.md @@ -28,12 +28,13 @@ Mode rules: [Operating Modes](references/operating-modes.md), [QA Ladder](refere ## Compact Workflow 1. Decide whether the request should become a skill, then choose the lightest archetype. -2. Run a short reference scan with external benchmark objects first, then use local files only for fit, privacy, and compatibility calibration. -3. Capture the recurring job, outputs, trigger phrases, and exclusions. +2. Run a short intent dialogue to capture the recurring job, outputs, trigger phrases, exclusions, and constraints. +3. Run a short reference scan with external benchmark objects first, then use local files only for fit, privacy, and compatibility calibration. 4. Write the `description` early, then test route quality before expanding the package. 5. Add only the folders and gates that earn their keep: `trigger_eval.py`, `optimize_description.py`, `judge_blind_eval.py`, `resource_boundary_check.py`, `governance_check.py`, `cross_packager.py`. +6. After the first package exists, surface the top three next iteration directions instead of expanding the skill in every direction at once. -Playbooks: [Skill Engineering Method](references/skill-engineering-method.md), [Reference Scan Strategy](references/reference-scan.md), [Skill Archetypes](references/skill-archetypes.md), [Gate Selection](references/gate-selection.md), [Non-Skill Decision Tree](references/non-skill-decision-tree.md), [Operating Modes](references/operating-modes.md), [Trigger And Eval Playbook](references/eval-playbook.md). +Playbooks: [Skill Engineering Method](references/skill-engineering-method.md), [Intent Dialogue](references/intent-dialogue.md), [Reference Scan Strategy](references/reference-scan.md), [Skill Archetypes](references/skill-archetypes.md), [Gate Selection](references/gate-selection.md), [Iteration Philosophy](references/iteration-philosophy.md), [Non-Skill Decision Tree](references/non-skill-decision-tree.md), [Operating Modes](references/operating-modes.md), [Trigger And Eval Playbook](references/eval-playbook.md). ## Output Contract @@ -49,8 +50,10 @@ Unless the user asks otherwise, produce: - [Skill Engineering Method](references/skill-engineering-method.md) - [Reference Scan Strategy](references/reference-scan.md) +- [Intent Dialogue](references/intent-dialogue.md) - [Skill Archetypes](references/skill-archetypes.md) - [Gate Selection](references/gate-selection.md) +- [Iteration Philosophy](references/iteration-philosophy.md) - [Non-Skill Decision Tree](references/non-skill-decision-tree.md) - [Operating Modes](references/operating-modes.md) - [Governance Model](references/governance.md) diff --git a/docs/README.fr-FR.md b/docs/README.fr-FR.md index 1c4967f..903fe20 100644 --- a/docs/README.fr-FR.md +++ b/docs/README.fr-FR.md @@ -9,8 +9,10 @@ Il transforme des workflows bruts, des transcripts, des prompts, des notes et de - une surface de déclenchement claire - un `SKILL.md` léger - des references, scripts et evals optionnels +- un court dialogue d'intention avant l'authoring approfondi - un benchmark/reference scan contrôlé avant l'authoring profond - un rapport HTML minimaliste en fond blanc généré automatiquement pour chaque nouveau skill +- trois directions d'itération à plus forte valeur après la première création - 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 @@ -37,9 +39,10 @@ Lecture en 10 secondes : ## Quick Start 1. Décrivez le workflow, l'ensemble de prompts ou la tâche répétée que vous voulez transformer en skill. -2. Lancez un court reference scan en privilégiant GitHub et les objets publics de référence ; les fichiers locaux ne servent ensuite qu'à l'ajustement, à la confidentialité et à la compatibilité. -3. Utilisez `yao-meta-skill` pour générer ou améliorer le paquet en mode scaffold, production ou library. -4. Chaque nouveau skill reçoit aussi `reports/skill-overview.html` et `reports/reference-scan.md`, pour comprendre rapidement son architecture, sa logique, son usage et ses références utiles. +2. Commencez par un court dialogue d'intention pour clarifier le vrai job, les sorties, les exclusions et les contraintes. +3. Lancez ensuite un short reference scan en privilégiant GitHub et les objets publics de référence ; les fichiers locaux ne servent ensuite qu'à l'ajustement, à la confidentialité et à la compatibilité. +4. Utilisez `yao-meta-skill` pour générer ou améliorer le paquet en mode scaffold, production ou library. +5. Chaque nouveau skill reçoit aussi `reports/intent-dialogue.md`, `reports/skill-overview.html`, `reports/reference-scan.md` et `reports/iteration-directions.md`. ## Results diff --git a/docs/README.ja-JP.md b/docs/README.ja-JP.md index d719f82..62fc4cc 100644 --- a/docs/README.ja-JP.md +++ b/docs/README.ja-JP.md @@ -9,8 +9,10 @@ - 明確なトリガー面 - 軽量な `SKILL.md` - 必要に応じた references、scripts、evals +- 深い authoring の前に行う短い intent dialogue - 深い authoring の前に、制御された benchmark/reference scan を 1 回行う - 新しい skill ごとに、白背景の簡潔な HTML overview を自動生成 +- 初回作成後に自動で提示される 3 つの高価値な次の iteration direction - 中立的なソースメタデータとクライアント別アダプタ - ガバナンス、昇格判定、portability チェックを標準フローに内蔵 @@ -37,9 +39,10 @@ flowchart LR ## Quick Start 1. skill 化したい workflow、prompt 集合、または反復タスクを説明します。 -2. まず短い reference scan を行い、GitHub や世界トップ級の公開対象を主参照にし、ローカル資産は適合確認と privacy 調整だけに使います。 -3. `yao-meta-skill` を使って scaffold、production、library のいずれかのモードでパッケージを生成または改善します。 -4. 新しく作成した skill には `reports/skill-overview.html` と `reports/reference-scan.md` が付き、構造、ロジック、使い方、参考対象をすぐ確認できます。 +2. まず短い intent dialogue で、実際の job、outputs、boundary、constraints を明確にします。 +3. 次に短い reference scan を行い、GitHub や世界トップ級の公開対象を主参照にし、ローカル資産は適合確認と privacy 調整だけに使います。 +4. `yao-meta-skill` を使って scaffold、production、library のいずれかのモードでパッケージを生成または改善します。 +5. 新しく作成した skill には `reports/intent-dialogue.md`、`reports/skill-overview.html`、`reports/reference-scan.md`、`reports/iteration-directions.md` が付きます。 ## Results diff --git a/docs/README.ru-RU.md b/docs/README.ru-RU.md index 99b3f1f..5b2d01a 100644 --- a/docs/README.ru-RU.md +++ b/docs/README.ru-RU.md @@ -9,8 +9,10 @@ - понятной поверхностью срабатывания - компактным `SKILL.md` - необязательными references, scripts и evals +- коротким intent dialogue перед глубокой разработкой - контролируемым benchmark/reference scan до глубокого authoring - автоматически создаваемым минималистичным HTML-обзором на белом фоне для каждого нового skill +- тремя наиболее ценными направлениями следующей итерации после первого создания - нейтральными исходными метаданными и клиентскими адаптерами - встроенными проверками governance, promotion и portability в стандартном потоке @@ -37,9 +39,10 @@ flowchart LR ## Quick Start 1. Опишите workflow, набор prompts или повторяющуюся задачу, которую хотите превратить в skill. -2. Сначала выполните короткий reference scan, где основными источниками будут GitHub и сильные публичные эталоны, а локальные файлы будут использоваться только для адаптации, приватности и совместимости. -3. Используйте `yao-meta-skill`, чтобы сгенерировать или улучшить пакет в режиме scaffold, production или library. -4. Каждый новый skill также получает `reports/skill-overview.html` и `reports/reference-scan.md`, чтобы быстро понять архитектуру, логику, способ использования и полезные ориентиры. +2. Сначала проведите короткий intent dialogue, чтобы уточнить реальную job-to-be-done, outputs, exclusions и constraints. +3. Затем выполните короткий reference scan, где основными источниками будут GitHub и сильные публичные эталоны, а локальные файлы будут использоваться только для адаптации, приватности и совместимости. +4. Используйте `yao-meta-skill`, чтобы сгенерировать или улучшить пакет в режиме scaffold, production или library. +5. Каждый новый skill также получает `reports/intent-dialogue.md`, `reports/skill-overview.html`, `reports/reference-scan.md` и `reports/iteration-directions.md`. ## Results diff --git a/docs/README.zh-CN.md b/docs/README.zh-CN.md index c11cf16..93ca1df 100644 --- a/docs/README.zh-CN.md +++ b/docs/README.zh-CN.md @@ -9,8 +9,10 @@ - 清晰的触发面 - 精简的 `SKILL.md` - 可选的 references、scripts 和 evals +- 深度起草前先做一轮意图对话,补齐真实任务、输出物和边界 - 深度起草前先做一轮受控的 benchmark/reference scan - 新建 skill 时自动生成一份极简白底 HTML 可视化说明 +- 首次建包后会自动给出 3 个最有价值的下一步迭代方向 - 中性的源元数据以及面向不同客户端的适配层 - 内建的治理、晋升和 portability 检查 @@ -37,9 +39,10 @@ flowchart LR ## Quick Start 1. 先描述你想沉淀成 skill 的 workflow、prompt 集合或重复任务。 -2. 先做一轮简短的参考扫描,以 GitHub 和世界级公开对象为主来源,本地文件只做适配和隐私校准。 -3. 使用 `yao-meta-skill` 以 scaffold、production 或 library 模式生成或改进 skill 包。 -4. 新建 skill 后,会默认附带 `reports/skill-overview.html` 和 `reports/reference-scan.md`,方便理解架构、逻辑、使用方法和可借鉴对象。 +2. 先做一轮简短的意图对话,把真实任务、输出物、边界和约束说清楚。 +3. 再做一轮参考扫描,以 GitHub 和世界级公开对象为主来源,本地文件只做适配和隐私校准。 +4. 使用 `yao-meta-skill` 以 scaffold、production 或 library 模式生成或改进 skill 包。 +5. 新建 skill 后,会默认附带 `reports/intent-dialogue.md`、`reports/skill-overview.html`、`reports/reference-scan.md` 和 `reports/iteration-directions.md`,方便理解架构、逻辑、使用方法、可借鉴对象以及下一步最值的 3 个迭代方向。 ## Results diff --git a/references/intent-dialogue.md b/references/intent-dialogue.md new file mode 100644 index 0000000..060dfa8 --- /dev/null +++ b/references/intent-dialogue.md @@ -0,0 +1,48 @@ +# Intent Dialogue + +Use a short intent dialogue before deep authoring so the first version of the skill is anchored in the real job rather than in a guessed prompt shape. + +## Why This Step Exists + +- raw workflow material is often incomplete, mixed, or ambiguous +- the wrong boundary chosen early is expensive to repair later +- good trigger design depends on knowing what should not route here +- execution assets should follow confirmed outputs, not assumptions + +## What To Capture + +Ask only the questions that change the package design. + +1. What recurring job should this skill own? +2. What inputs will people actually hand to it? +3. What outputs should it produce every time? +4. What near-neighbor requests should stay out of scope? +5. What quality bar matters most: speed, consistency, auditability, portability, or governance? +6. What assets already exist: docs, scripts, templates, examples, or prior prompts? +7. What constraints matter: privacy, naming, local library fit, or target environments? + +## Interview Rule + +- prefer `5-7` sharp questions over a long discovery questionnaire +- ask boundary questions early +- ask output questions before architecture questions +- stop once the skill can be described clearly in one sentence + +## Output + +The dialogue should produce: + +- one clear capability sentence +- a list of real inputs +- a list of required outputs +- a short exclusion list +- one recommended archetype +- one recommended first evaluation target + +## Failure Pattern + +Do not continue into full authoring when the dialogue still leaves these unresolved: + +- whether the request is really reusable +- which near-neighbor requests should not trigger +- what concrete deliverable the skill must return diff --git a/references/iteration-philosophy.md b/references/iteration-philosophy.md new file mode 100644 index 0000000..04a24f8 --- /dev/null +++ b/references/iteration-philosophy.md @@ -0,0 +1,30 @@ +# Iteration Philosophy + +The first skill package is a routeable baseline, not the final answer. + +## Principle + +Improve the skill by the smallest change that increases reliability more than it increases context cost. + +## What Good Iteration Looks Like + +- tighten boundary before adding prose +- improve description before enlarging the file tree +- add references only when they remove ambiguity +- add scripts only when deterministic logic repeats +- add gates only when risk justifies maintenance cost +- prefer one strong next step over five vague upgrades + +## Default Priority Order + +1. trigger clarity and exclusions +2. execution assets that remove repeated manual work +3. promotion, governance, and portability only when reuse justifies them + +## First-Version Rule + +After the initial package is created, always surface the three highest-value next iteration directions. This keeps the author focused on the best improvement path instead of expanding the skill in every direction at once. + +## Anti-Pattern + +Do not treat iteration as "make the package bigger." A larger package is only better when routing, execution, or governance becomes materially more reliable. diff --git a/references/skill-engineering-method.md b/references/skill-engineering-method.md index 348b7d1..9945dd8 100644 --- a/references/skill-engineering-method.md +++ b/references/skill-engineering-method.md @@ -5,11 +5,13 @@ This doctrine defines the default method for turning messy workflow material int ## Core Loop 1. Decide whether the request should become a skill at all. -2. Choose the smallest viable archetype. -3. Set one clear capability boundary. -4. Write and test the trigger description before expanding the body. -5. Add only the gates that match the risk. -6. Package and govern the skill only as far as real reuse demands. +2. Run a short intent dialogue to capture the real job, outputs, exclusions, and constraints. +3. Choose the smallest viable archetype. +4. Set one clear capability boundary. +5. Write and test the trigger description before expanding the body. +6. Add only the gates that match the risk. +7. Ship the first routeable package, then pick the three highest-value next iteration directions. +8. Package and govern the skill only as far as real reuse demands. ## Phase 1: Qualification @@ -31,7 +33,19 @@ Reject skill creation when the request is only: See [Non-Skill Decision Tree](non-skill-decision-tree.md). -## Phase 2: Archetype Selection +## Phase 2: Intent Dialogue + +Before deep authoring, ask only the questions that change the package design. + +- what recurring job should the skill own +- what real inputs will users hand to it +- what outputs must it produce +- what near-neighbor requests should stay out of scope +- what constraints matter: privacy, naming, portability, governance, or local fit + +See [Intent Dialogue](intent-dialogue.md). + +## Phase 3: Archetype Selection Choose the lightest archetype that fits the job. @@ -42,7 +56,7 @@ Choose the lightest archetype that fits the job. See [Skill Archetypes](skill-archetypes.md). -## Phase 3: Boundary Design +## Phase 4: Boundary Design Every skill should answer four questions clearly: @@ -53,7 +67,7 @@ Every skill should answer four questions clearly: Boundary work comes before polishing prose. -## Phase 4: Reference Scan +## Phase 5: Reference Scan Run a short benchmark pass before deep authoring. @@ -66,7 +80,7 @@ Run a short benchmark pass before deep authoring. See [Reference Scan Strategy](reference-scan.md). -## Phase 5: Trigger-First Authoring +## Phase 6: Trigger-First Authoring Author the frontmatter `description` before expanding the body. @@ -84,7 +98,7 @@ Trigger quality is improved through: - adversarial holdout - route confusion -## Phase 6: Gate Selection +## Phase 7: Gate Selection Add gates by risk, not by habit. @@ -95,7 +109,18 @@ Add gates by risk, not by habit. See [Gate Selection](gate-selection.md). -## Phase 7: Promotion +## Phase 8: First Iteration Philosophy + +The first package is a routeable baseline, not the final answer. + +- improve trigger and exclusions before growing prose +- add one execution asset before adding many documents +- surface the three highest-value next moves so authors do not expand in every direction at once +- prefer the smallest step that increases reliability more than context cost + +See [Iteration Philosophy](iteration-philosophy.md). + +## Phase 9: Promotion A candidate route or package is promotable only when: diff --git a/scripts/init_skill.py b/scripts/init_skill.py index 15b0074..2997bd7 100644 --- a/scripts/init_skill.py +++ b/scripts/init_skill.py @@ -4,6 +4,8 @@ import json from datetime import date from pathlib import Path +from render_intent_dialogue import render_intent_dialogue +from render_iteration_directions import render_iteration_directions from render_reference_scan import render_reference_scan from render_skill_overview import render_skill_overview @@ -34,15 +36,19 @@ README_TEMPLATE = """# {title} ## How To Use 1. Load the skill through `SKILL.md`. -2. Follow the workflow steps in `SKILL.md`. -3. Check `reports/skill-overview.html` if you want a fast visual explanation of the package. +2. Start with `reports/intent-dialogue.md` to tighten the real job, outputs, and exclusions. +3. Follow the workflow steps in `SKILL.md`. +4. Check `reports/skill-overview.html` if you want a fast visual explanation of the package. +5. Review `reports/iteration-directions.md` for the three most valuable next moves. ## Package Map - `SKILL.md`: trigger and workflow entrypoint - `agents/interface.yaml`: portable interface metadata - `manifest.json`: lifecycle and packaging metadata +- `reports/intent-dialogue.md`: front-loaded discovery questions for better boundary design - `reports/skill-overview.html`: visual overview report +- `reports/iteration-directions.md`: the top three next iteration directions """ @@ -131,7 +137,9 @@ def main() -> None: encoding="utf-8", ) overview = render_skill_overview(root) + intent_dialogue = render_intent_dialogue(root) reference_scan = render_reference_scan(root, []) + iteration_directions = render_iteration_directions(root) print( json.dumps( { @@ -140,8 +148,14 @@ def main() -> None: "artifacts": { "readme": str(root / "README.md"), "manifest": str(root / "manifest.json"), - **overview["artifacts"], - **reference_scan["artifacts"], + "skill_overview_html": overview["artifacts"]["html"], + "skill_overview_json": overview["artifacts"]["json"], + "intent_dialogue_md": intent_dialogue["artifacts"]["markdown"], + "intent_dialogue_json": intent_dialogue["artifacts"]["json"], + "reference_scan_md": reference_scan["artifacts"]["markdown"], + "reference_scan_json": reference_scan["artifacts"]["json"], + "iteration_directions_md": iteration_directions["artifacts"]["markdown"], + "iteration_directions_json": iteration_directions["artifacts"]["json"], }, }, ensure_ascii=False, diff --git a/scripts/render_intent_dialogue.py b/scripts/render_intent_dialogue.py new file mode 100644 index 0000000..db4481a --- /dev/null +++ b/scripts/render_intent_dialogue.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +import argparse +import json +from pathlib import Path + +try: + import yaml +except ImportError: # pragma: no cover + yaml = None + + +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 = "\n".join(lines[1:end_index]) + body = "\n".join(lines[end_index + 1 :]).lstrip() + if yaml is not None: + payload = yaml.safe_load(frontmatter) or {} + return payload if isinstance(payload, dict) else {}, body + data = {} + for line in frontmatter.splitlines(): + if ":" not in line: + continue + key, value = line.split(":", 1) + data[key.strip()] = value.strip().strip('"') + return data, body + + +def extract_title(body: str, fallback: str) -> str: + for line in body.splitlines(): + if line.startswith("# "): + return line[2:].strip() + return fallback + + +def classify_focus(description: str) -> str: + lowered = description.lower() + if any(token in lowered for token in ["review", "audit", "incident", "risk", "govern"]): + return "quality-and-boundary" + if any(token in lowered for token in ["export", "package", "adapter", "client", "portable"]): + return "portability-and-contract" + if any(token in lowered for token in ["workflow", "coordinate", "orchestrate", "process"]): + return "execution-and-assets" + return "trigger-and-output" + + +def build_questions(focus: str) -> list[dict]: + base = [ + { + "question": "What recurring job should this skill own every time?", + "why": "This defines the core capability sentence and keeps the package from drifting.", + }, + { + "question": "What inputs will users actually hand to this skill?", + "why": "Input shape decides whether references, scripts, or templates are needed.", + }, + { + "question": "What concrete outputs must the skill produce?", + "why": "Outputs should drive the package structure before extra guidance is added.", + }, + { + "question": "Which near-neighbor requests should not trigger this skill?", + "why": "The exclusion list is the fastest route to better trigger quality.", + }, + { + "question": "What constraints matter: privacy, naming, local fit, portability, or governance?", + "why": "Constraints decide how much structure, packaging, and review the skill actually needs.", + }, + ] + if focus == "quality-and-boundary": + base.append( + { + "question": "What failure would make this skill untrustworthy in practice?", + "why": "The answer usually reveals the first evaluation gate worth adding.", + } + ) + elif focus == "portability-and-contract": + base.append( + { + "question": "Which environments or clients must be able to consume this skill?", + "why": "This sets the minimum metadata and degradation strategy.", + } + ) + else: + base.append( + { + "question": "What repeated manual step should become a deterministic asset first?", + "why": "This usually reveals whether a script or reference should be created next.", + } + ) + return base + + +def build_summary(skill_dir: Path) -> dict: + skill_text = (skill_dir / "SKILL.md").read_text(encoding="utf-8") + frontmatter, body = parse_frontmatter(skill_text) + name = frontmatter.get("name", skill_dir.name) + description = frontmatter.get("description", "No description found.") + title = extract_title(body, name.replace("-", " ").title()) + focus = classify_focus(description) + questions = build_questions(focus) + output = { + "capability_sentence": f"{title} should turn a recurring request into a reliable reusable output without widening the boundary unnecessarily.", + "required_capture": [ + "recurring job", + "real inputs", + "required outputs", + "exclusions", + "constraints", + "first evaluation target", + ], + "recommended_first_gate": "trigger and boundary" if focus != "portability-and-contract" else "portability and contract", + } + return { + "skill_name": name, + "title": title, + "description": description, + "focus": focus, + "questions": questions, + "output": output, + } + + +def render_markdown(summary: dict) -> str: + lines = [ + "# Intent Dialogue", + "", + f"Skill: `{summary['skill_name']}`", + "", + "## Why Start Here", + "", + "Use this short dialogue before deep authoring. The goal is to learn the real job, output, exclusions, and constraints so the first package is small but accurate.", + "", + "## Current Anchor", + "", + f"- Title: `{summary['title']}`", + f"- Description: {summary['description']}", + f"- Focus: `{summary['focus']}`", + "", + "## Questions To Ask", + "", + ] + for idx, item in enumerate(summary["questions"], start=1): + lines.extend( + [ + f"{idx}. {item['question']}", + f" Why: {item['why']}", + ] + ) + lines.extend( + [ + "", + "## Capture Before Drafting", + "", + f"- Capability sentence: {summary['output']['capability_sentence']}", + f"- Recommended first gate: `{summary['output']['recommended_first_gate']}`", + ] + ) + for item in summary["output"]["required_capture"]: + lines.append(f"- Capture: `{item}`") + return "\n".join(lines).strip() + "\n" + + +def render_intent_dialogue(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 / "intent-dialogue.md" + output_json = output_json or reports_dir / "intent-dialogue.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 an intent dialogue guide 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_intent_dialogue( + Path(args.skill_dir), + output_md=Path(args.output_md).resolve() if args.output_md else None, + output_json=Path(args.output_json).resolve() if args.output_json else None, + ) + print(json.dumps(result, ensure_ascii=False, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/scripts/render_iteration_directions.py b/scripts/render_iteration_directions.py new file mode 100644 index 0000000..af0eb9e --- /dev/null +++ b/scripts/render_iteration_directions.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python3 +import argparse +import json +from pathlib import Path + +try: + import yaml +except ImportError: # pragma: no cover + yaml = None + + +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 = "\n".join(lines[1:end_index]) + body = "\n".join(lines[end_index + 1 :]).lstrip() + if yaml is not None: + payload = yaml.safe_load(frontmatter) or {} + return payload if isinstance(payload, dict) else {}, body + data = {} + for line in frontmatter.splitlines(): + if ":" not in line: + continue + key, value = line.split(":", 1) + data[key.strip()] = value.strip().strip('"') + return data, body + + +def load_manifest(path: Path) -> dict: + if not path.exists(): + return {} + return json.loads(path.read_text(encoding="utf-8")) + + +def extract_sections(body: str) -> dict[str, str]: + sections: dict[str, list[str]] = {} + current = "_preamble" + sections[current] = [] + for line in body.splitlines(): + if line.startswith("## "): + current = line[3:].strip() + sections[current] = [] + continue + sections[current].append(line) + return {name: "\n".join(lines).strip() for name, lines in sections.items()} + + +def section_text(sections: dict[str, str], *names: str) -> str: + lowered = {key.lower(): value for key, value in sections.items()} + for name in names: + if name.lower() in lowered: + return lowered[name.lower()] + return "" + + +def has_any_files(path: Path) -> bool: + return path.exists() and any(path.iterdir()) + + +def direction(priority: int, title: str, why: str, actions: list[str], unlocks: str) -> dict: + return { + "priority": priority, + "title": title, + "why": why, + "actions": actions, + "unlocks": unlocks, + } + + +def build_directions(skill_dir: Path) -> tuple[dict, list[dict]]: + skill_text = (skill_dir / "SKILL.md").read_text(encoding="utf-8") + frontmatter, body = parse_frontmatter(skill_text) + sections = extract_sections(body) + manifest = load_manifest(skill_dir / "manifest.json") + description = frontmatter.get("description", "") + maturity = manifest.get("maturity_tier", "scaffold") + + candidates: list[dict] = [] + if "Do not use" not in body and "exclusion" not in body.lower(): + candidates.append( + direction( + 1, + "Tighten trigger and exclusions", + "The package needs clearer near-neighbor exclusions before it grows.", + [ + "Add 3 to 5 should-trigger and should-not-trigger examples.", + "Refine the frontmatter description to name the recurring job and non-goals.", + "Run a first trigger evaluation pass before expanding the package.", + ], + "Cleaner routing and fewer accidental activations.", + ) + ) + if not has_any_files(skill_dir / "references") or not has_any_files(skill_dir / "scripts"): + candidates.append( + direction( + 2, + "Add the first execution asset", + "The package is still mostly prose. Add one asset that removes repeated manual work.", + [ + "Move stable procedural guidance into references if users will need it repeatedly.", + "Create one deterministic helper script if a repeated step can be executed instead of described.", + "Keep the main SKILL.md compact and route-oriented.", + ], + "Stronger execution quality without bloating the entrypoint.", + ) + ) + if maturity == "scaffold": + candidates.append( + direction( + 3, + "Promote from scaffold to production-ready", + "The first version exists; the next gain usually comes from adding the smallest useful gates.", + [ + "Decide whether this skill is personal, team-reused, or library-grade.", + "Add only the gates that match that risk level.", + "Record lifecycle metadata and review cadence once reuse becomes real.", + ], + "A clearer path from exploratory package to maintained asset.", + ) + ) + workflow_section = section_text(sections, "Workflow", "Compact Workflow", "How To Use") + if workflow_section and len(workflow_section) < 120: + candidates.append( + direction( + 4, + "Deepen operator guidance", + "The workflow is still thin and may not be repeatable by another operator.", + [ + "Split the job into distinct phases or checkpoints.", + "Add success conditions and failure boundaries for each phase.", + "Link outputs to the step that produces them.", + ], + "Better repeatability and easier human review.", + ) + ) + if "portable" in description.lower() or "package" in description.lower(): + candidates.append( + direction( + 5, + "Harden portability semantics", + "The skill already signals reuse across environments, so contract clarity matters early.", + [ + "Confirm activation mode, execution context, and trust assumptions.", + "Add or review degradation strategy for non-native targets.", + "Package the skill once to verify adapter expectations.", + ], + "Safer cross-environment reuse with less target drift.", + ) + ) + + if len(candidates) < 3: + candidates.append( + direction( + len(candidates) + 10, + "Create an iteration evidence loop", + "The package should show what changed and why after the first draft.", + [ + "Generate a visual overview and keep it aligned with the package.", + "Record reference scan choices and non-goals.", + "Capture the next iteration choice explicitly before adding more files.", + ], + "A clearer path for the next author or reviewer.", + ) + ) + + chosen = sorted(candidates, key=lambda item: item["priority"])[:3] + summary = { + "skill_name": frontmatter.get("name", skill_dir.name), + "description": description, + "maturity_tier": maturity, + "selection_rule": "Pick the three smallest next steps that increase reliability more than they increase context cost.", + } + return summary, chosen + + +def render_markdown(summary: dict, directions: list[dict]) -> str: + lines = [ + "# Iteration Directions", + "", + f"Skill: `{summary['skill_name']}`", + "", + f"- Maturity tier: `{summary['maturity_tier']}`", + f"- Selection rule: {summary['selection_rule']}", + "", + "## Top 3 Next Moves", + "", + ] + for item in directions: + lines.extend( + [ + f"### {item['priority']}. {item['title']}", + "", + f"- Why now: {item['why']}", + "- Recommended actions:", + ] + ) + for action in item["actions"]: + lines.append(f" - {action}") + lines.append(f"- Unlocks: {item['unlocks']}") + lines.append("") + return "\n".join(lines).strip() + "\n" + + +def render_iteration_directions(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 / "iteration-directions.md" + output_json = output_json or reports_dir / "iteration-directions.json" + + summary, directions = build_directions(skill_dir) + payload = {"summary": summary, "directions": directions} + output_md.write_text(render_markdown(summary, directions), encoding="utf-8") + output_json.write_text(json.dumps(payload, 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), + }, + **payload, + } + + +def main() -> None: + parser = argparse.ArgumentParser(description="Render the top three next iteration directions 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_iteration_directions( + Path(args.skill_dir), + output_md=Path(args.output_md).resolve() if args.output_md else None, + output_json=Path(args.output_json).resolve() if args.output_json else None, + ) + print(json.dumps(result, ensure_ascii=False, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/scripts/yao.py b/scripts/yao.py index b84e70d..153f92f 100644 --- a/scripts/yao.py +++ b/scripts/yao.py @@ -243,6 +243,30 @@ def command_reference_scan(args: argparse.Namespace) -> int: return 0 if result["ok"] else 2 +def command_intent_dialogue(args: argparse.Namespace) -> int: + skill_dir = str(Path(args.skill_dir).resolve()) + cmd = [skill_dir] + 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_intent_dialogue.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] + 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_iteration_directions.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_review(args: argparse.Namespace) -> int: target_name = resolve_promotion_target(args.target) bundle_dir = ROOT / "reports" / "iteration_bundles" / target_name @@ -464,6 +488,24 @@ def build_parser() -> argparse.ArgumentParser: reference_scan_cmd.add_argument("--output-json") reference_scan_cmd.set_defaults(func=command_reference_scan) + intent_dialogue_cmd = subparsers.add_parser( + "intent-dialogue", + help="Render a front-loaded intent dialogue guide for a skill package.", + ) + intent_dialogue_cmd.add_argument("skill_dir", nargs="?", default=".") + intent_dialogue_cmd.add_argument("--output-md") + intent_dialogue_cmd.add_argument("--output-json") + intent_dialogue_cmd.set_defaults(func=command_intent_dialogue) + + iteration_directions_cmd = subparsers.add_parser( + "iteration-directions", + help="Render the top three next iteration directions for a skill package.", + ) + iteration_directions_cmd.add_argument("skill_dir", nargs="?", default=".") + iteration_directions_cmd.add_argument("--output-md") + iteration_directions_cmd.add_argument("--output-json") + iteration_directions_cmd.set_defaults(func=command_iteration_directions) + package_cmd = subparsers.add_parser("package", help="Export compatibility artifacts for selected targets.") package_cmd.add_argument("skill_dir", nargs="?", default=".") package_cmd.add_argument("--platform", action="append") diff --git a/tests/verify_iteration_directions.py b/tests/verify_iteration_directions.py new file mode 100644 index 0000000..2c4191b --- /dev/null +++ b/tests/verify_iteration_directions.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +import json +import subprocess +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent +CLI = ROOT / "scripts" / "yao.py" + + +def run(*args: str) -> dict: + proc = subprocess.run( + [sys.executable, str(CLI), *args], + cwd=ROOT, + capture_output=True, + text=True, + check=True, + ) + return json.loads(proc.stdout) + + +def main() -> None: + tmp_root = ROOT / "tests" / "tmp_iteration_directions" + if tmp_root.exists(): + subprocess.run(["rm", "-rf", str(tmp_root)], check=True) + tmp_root.mkdir(parents=True, exist_ok=True) + + init_payload = run( + "init", + "iteration-demo-skill", + "--description", + "Turn a repeated release checklist into a reusable reusable skill package.", + "--output-dir", + str(tmp_root), + ) + created = Path(init_payload["root"]) + report = run("iteration-directions", str(created)) + assert report["summary"]["selection_rule"], report + assert len(report["directions"]) == 3, report + titles = {item["title"] for item in report["directions"]} + assert "Tighten trigger and exclusions" in titles or "Add the first execution asset" in titles, titles + print(json.dumps({"ok": True}, ensure_ascii=False, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/tests/verify_skill_overview.py b/tests/verify_skill_overview.py index 2b66a80..78ca6e3 100644 --- a/tests/verify_skill_overview.py +++ b/tests/verify_skill_overview.py @@ -44,8 +44,12 @@ def main() -> None: created = tmp_root / "skill-overview-demo" assert (created / "README.md").exists(), created assert (created / "manifest.json").exists(), created + assert (created / "reports" / "intent-dialogue.md").exists(), created + assert (created / "reports" / "intent-dialogue.json").exists(), created assert (created / "reports" / "skill-overview.html").exists(), created assert (created / "reports" / "skill-overview.json").exists(), created + assert (created / "reports" / "iteration-directions.md").exists(), created + assert (created / "reports" / "iteration-directions.json").exists(), created rerender_result = run("skill-report", str(created)) assert rerender_result["ok"], rerender_result @@ -56,6 +60,12 @@ def main() -> None: assert "Architecture" in report_html, report_html[:400] assert "Why It Works" in report_html, report_html[:600] + intent_text = (created / "reports" / "intent-dialogue.md").read_text(encoding="utf-8") + assert "Questions To Ask" in intent_text, intent_text[:400] + + directions_text = (created / "reports" / "iteration-directions.md").read_text(encoding="utf-8") + assert "Top 3 Next Moves" in directions_text, directions_text[:400] + print(json.dumps({"ok": True}, ensure_ascii=False, indent=2)) diff --git a/tests/verify_yao_cli.py b/tests/verify_yao_cli.py index 57f405b..a12928c 100644 --- a/tests/verify_yao_cli.py +++ b/tests/verify_yao_cli.py @@ -36,8 +36,10 @@ def main() -> None: created = Path(init_result["payload"]["root"]) assert (created / "SKILL.md").exists(), created assert (created / "README.md").exists(), created + assert (created / "reports" / "intent-dialogue.md").exists(), created assert (created / "reports" / "skill-overview.html").exists(), created assert (created / "reports" / "reference-scan.md").exists(), created + assert (created / "reports" / "iteration-directions.md").exists(), created validate_result = run("validate", str(created)) assert validate_result["ok"], validate_result @@ -58,6 +60,14 @@ def main() -> None: assert reference_scan_result["ok"], reference_scan_result assert reference_scan_result["payload"]["artifacts"]["markdown"].endswith("reports/reference-scan.md"), reference_scan_result + intent_result = run("intent-dialogue", str(created)) + assert intent_result["ok"], intent_result + assert intent_result["payload"]["artifacts"]["markdown"].endswith("reports/intent-dialogue.md"), intent_result + + directions_result = run("iteration-directions", str(created)) + assert directions_result["ok"], directions_result + assert directions_result["payload"]["artifacts"]["markdown"].endswith("reports/iteration-directions.md"), directions_result + optimize_result = run("optimize-description", "--target", "root") assert optimize_result["ok"], optimize_result assert optimize_result["payload"]["winner"]["label"] == "Current", optimize_result