diff --git a/README.ZH.md b/README.ZH.md index fcc8ceb..da8b12e 100644 --- a/README.ZH.md +++ b/README.ZH.md @@ -13,11 +13,27 @@ 引擎能画的所有控件——滑块、带刻度的范围滑块、分段控件、单选组、开关、 自定义取色器、条件字段——全部由 schema 提示驱动,skill 也会明确要求 agent 主动 -用起来。完整清单见 -[`schemaui` 控件画廊](https://github.com/YuniqueUnic/schemaui/blob/main/examples/controls-gallery.schema.json); +用起来。图形走同一套提示:问题和选项都可以带 **Mermaid 图、内联 SVG 或 Markdown +说明**(`x-content` / `x-options`),答案本身也可以是一张可编辑的 +实时预览图(`x-control: "mermaid"`)。完整清单见 +[`schemaui` 控件画廊](https://github.com/YuniqueUnic/schemaui/blob/main/examples/controls-gallery.schema.json) +与 +[富内容画廊](https://github.com/YuniqueUnic/schemaui/blob/main/examples/rich-content.schema.json); [`examples/web-research-brief.schema.json`](./examples/web-research-brief.schema.json) 是把这些控件全部用上的一份中文范例。 + + + + + + + + + + +
控件画廊:滑块、范围、分段、单选、取色器等全部控件
选项配图:每个部署拓扑选项自带一张 Mermaid 图
Mermaid 编辑器:左侧源码、右侧实时预览
+ ## 为什么需要它 Agent 的工具调用跑在**没有 TTY** 的子进程里——终端提示符根本渲染不出 来,agent @@ -130,20 +146,28 @@ Windows / PowerShell 7+:`pwsh scripts/ask.ps1 -Schema … -Title …`。没有 P - **给每个答案配对的控件** —— 滑块、带刻度的滑块、双柄范围、取色器、分段控件、 单选组、复选框、条件字段,外加文本、数值、单选/多选、oneOf 组合、嵌套对 象、记录列表、键值映射。 +- **能看图就别读字** —— 选项差在「形状」上时(拓扑、发布策略、数据模型), agent + 给每个选项配一张图,你认图选择;节点级 `x-content` 用图或一段富文本 + 解释字段背景;`mermaid` 控件让答案本身就是一张可编辑、实时预览的图。 +- **访谈纪律内置** —— 开放式设计需要深度访谈时,skill 优先使用你已安装的 + grilling 类 skill,都没有时回退到内置的 + [`skills/grill-with-docs`](./skills/grill-with-docs/SKILL.md):一次一问、 + 每问带推荐答案、每个问题都以项目文档为依据。 ## 示例 [`examples/`](./examples/) 里可直接运行的表单 (每个都带 `.defaults.json` 推荐答案): -| 示例 | 场景 | -| ----------------------------------------------------------------------------------- | --------------------------------------------------- | -| [`env-schema.json`](./examples/env-schema.json) | 最小 4 字段部署表单 —— 首次冒烟测试 | -| [`web-research-brief.schema.json`](./examples/web-research-brief.schema.json) | 调研:联网调研任务确认 —— 用上画廊全部控件 (中文) | -| [`feature-brief.schema.json`](./examples/feature-brief.schema.json) | 12 问需求简报,覆盖全部控件类型 (英文) | -| [`invoice-reimbursement.schema.json`](./examples/invoice-reimbursement.schema.json) | 办公:发票报销处理 (中文,每个选择带「其他」逃生口) | -| [`ecommerce-main-image.schema.json`](./examples/ecommerce-main-image.schema.json) | 设计:电商主图 —— 尺寸、字号、颜色、渐变背景 (中文) | -| [`seo-diagnosis.schema.json`](./examples/seo-diagnosis.schema.json) | SEO 排查:站点、问题、关键词、竞品 (中文) | +| 示例 | 场景 | +| --------------------------------------------------------------------------------------- | --------------------------------------------------- | +| [`env-schema.json`](./examples/env-schema.json) | 最小 4 字段部署表单 —— 首次冒烟测试 | +| [`web-research-brief.schema.json`](./examples/web-research-brief.schema.json) | 调研:联网调研任务确认 —— 用上画廊全部控件 (中文) | +| [`feature-brief.schema.json`](./examples/feature-brief.schema.json) | 12 问需求简报,覆盖全部控件类型 (英文) | +| [`invoice-reimbursement.schema.json`](./examples/invoice-reimbursement.schema.json) | 办公:发票报销处理 (中文,每个选择带「其他」逃生口) | +| [`ecommerce-main-image.schema.json`](./examples/ecommerce-main-image.schema.json) | 设计:电商主图 —— 尺寸、字号、颜色、渐变背景 (中文) | +| [`seo-diagnosis.schema.json`](./examples/seo-diagnosis.schema.json) | SEO 排查:站点、问题、关键词、竞品 (中文) | +| [`deployment-architecture.schema.json`](./examples/deployment-architecture.schema.json) | 架构选型:每个选项自带一张图 (中文,图形界面参考例) | ## 脚本契约 diff --git a/README.md b/README.md index ea973e2..c927935 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,28 @@ you and the agent can re-read later. Every control the engine can draw — sliders, range sliders with marks, segmented controls, radio groups, switches, a custom color picker, conditional fields — driven entirely by schema hints, and the skill tells the agent to reach for -them. The full set lives in -[`schemaui`'s controls gallery](https://github.com/YuniqueUnic/schemaui/blob/main/examples/controls-gallery.schema.json); +them. Figures ride the same hints: a question or an option can carry a **Mermaid +diagram, an inline SVG, or sanitised Markdown** (`x-content` / `x-options`), and +an answer can _be_ a diagram via the live-preview `x-control: "mermaid"` editor. +The full set lives in +[`schemaui`'s controls gallery](https://github.com/YuniqueUnic/schemaui/blob/main/examples/controls-gallery.schema.json) +and +[rich-content gallery](https://github.com/YuniqueUnic/schemaui/blob/main/examples/rich-content.schema.json); [`examples/web-research-brief.schema.json`](./examples/web-research-brief.schema.json) is the worked example that uses every one of them (中文). + + + + + + + + + + +
The control gallery: sliders, ranges, segmented, radio, color picker and more
Figure-bearing options: each deployment topology option ships its own Mermaid diagram
The mermaid editor: source on the left, live preview on the right
+ ## Why Agent tool calls run in subprocesses with **no TTY** — a terminal prompt renders @@ -138,20 +155,31 @@ without Python: `bash scripts/ask.sh --schema …`. ranges, colour pickers, segmented controls, radio groups, checkboxes, and conditional fields, alongside text, number, single/multi select, oneOf compositions, nested objects, record lists, and key/value maps. +- **Show, don't tell** — when options differ in _shape_ (topologies, rollout + strategies, data models), the agent attaches a diagram to each option and lets + you pick by recognising the picture; node-level `x-content` explains a field + with a figure or prose; a `mermaid` control makes the answer itself an + editable, live-previewed diagram. +- **Interview discipline built in** — for open-ended designs, the skill defers + to whatever grilling skill you already have, and falls back to the bundled + [`skills/grill-with-docs`](./skills/grill-with-docs/SKILL.md): one question at + a time, every question with a recommended answer, every question grounded in + the project's docs. ## Examples Runnable forms in [`examples/`](./examples/) (each with recommended answers in the matching `.defaults.json`): -| Example | Scenario | -| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | -| [`env-schema.json`](./examples/env-schema.json) | minimal 4-field deploy form — first smoke test | -| [`web-research-brief.schema.json`](./examples/web-research-brief.schema.json) | research: scope a web-research task — every control in the gallery (中文) | -| [`feature-brief.schema.json`](./examples/feature-brief.schema.json) | 12-question requirements brief using every control type (EN) | -| [`invoice-reimbursement.schema.json`](./examples/invoice-reimbursement.schema.json) | office: invoice & expense reimbursement (中文) | -| [`ecommerce-main-image.schema.json`](./examples/ecommerce-main-image.schema.json) | design: e-commerce hero images — sizes, fonts, colors, backgrounds (中文) | -| [`seo-diagnosis.schema.json`](./examples/seo-diagnosis.schema.json) | SEO triage: site, issues, keywords, competitors (中文) | +| Example | Scenario | +| --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| [`env-schema.json`](./examples/env-schema.json) | minimal 4-field deploy form — first smoke test | +| [`web-research-brief.schema.json`](./examples/web-research-brief.schema.json) | research: scope a web-research task — every control in the gallery (中文) | +| [`feature-brief.schema.json`](./examples/feature-brief.schema.json) | 12-question requirements brief using every control type (EN) | +| [`invoice-reimbursement.schema.json`](./examples/invoice-reimbursement.schema.json) | office: invoice & expense reimbursement (中文) | +| [`ecommerce-main-image.schema.json`](./examples/ecommerce-main-image.schema.json) | design: e-commerce hero images — sizes, fonts, colors, backgrounds (中文) | +| [`seo-diagnosis.schema.json`](./examples/seo-diagnosis.schema.json) | SEO triage: site, issues, keywords, competitors (中文) | +| [`deployment-architecture.schema.json`](./examples/deployment-architecture.schema.json) | architecture choice: every option carries a diagram (中文 — the figures reference) | ## Script contract diff --git a/SKILL.md b/SKILL.md index 6e4e1a9..72e60d2 100644 --- a/SKILL.md +++ b/SKILL.md @@ -4,10 +4,12 @@ description: > When the user must choose among options or fill in structured config, spawn a browser form (powered by schemaui Web UI, bound to 0.0.0.0), open the user's browser, and write the result to .schemaui/answers/. Never use a - terminal/TUI prompt — the agent process has no TTY attachment. Trigger - phrases: "give me a form", "ask me via form", "configure", "pick a deploy - environment", "I need to choose", "a2ui", "给我个表单", "配置一下", - "用表单问我", "问我几个问题". + terminal/TUI prompt — the agent process has no TTY attachment. Supports + rich content: figures on questions and answer options (Mermaid/SVG/Markdown + via x-content / x-options) and a live Mermaid editor (x-control: mermaid). + Trigger phrases: "give me a form", "ask me via form", "configure", "pick a + deploy environment", "I need to choose", "grill me", "a2ui", "给我个表单", + "配置一下", "用表单问我", "问我几个问题", "拷问我". --- # a2ui-ask — collect user input via a browser form (file output) @@ -109,29 +111,48 @@ channel list: see `install.md` in this repository. the field forever. The controlling field's `default` must not be the escape value, or the input would be visible from the start. +## Grilling: which interview skill to use + +Open-ended designs sometimes need a relentless interview before they become a +form. When the user asks to be grilled (or a plan needs stress-testing before +you can draft the schema), use an existing grilling skill if the user named or +installed one — check their skills directory for something like `grill-me`, +`grilling`, or a skill they explicitly referenced, and defer to it. Only when +none exists, fall back to the one bundled here: +[`skills/grill-with-docs/SKILL.md`](./skills/grill-with-docs/SKILL.md) — the +same one-question-at-a-time, recommend-then-ask discipline, plus a rule to +ground every question in the project's docs and the galleries referenced above, +and to end by turning the settled decisions into a form. The bundled skill is a +convenience so a fresh install works out of the box; it never overrides a +grilling skill the user chose themselves. + ## Question type → schema cheat sheet -| You need | Schema shape | Renders as | -| --------------------- | -------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -| short text input | `{"type": "string", "minLength": …, "pattern": …}` | inline text field | -| long text input | `{"type": "string", "x-multiline": true}` | multi-line text area | -| number | `{"type": "integer", "minimum": …, "maximum": …}` | numeric field with guards | -| number on a scale | `{"type": "…", "minimum": …, "maximum": …, "x-control": "slider"}` | slider with a live readout | -| slider with labels | slider + `"x-slider-marks": [{"value": …, "label": …}, …]` | slider with labelled stops | -| two-number interval | `{"type": "array", "items": {"type": "number"}, "minItems": 2, "maxItems": 2, "minimum": …, "maximum": …, "x-control": "range"}` | two-handle range slider | -| colour | `{"type": "string", "x-control": "color"}` | colour picker, not a text box | -| few short options | `{"type": "string", "enum": [...], "x-control": "segmented"}` | inline segmented control | -| few verbose options | `{"type": "string", "enum": [...], "x-control": "radio"}` | stacked radio group | -| yes/no | `{"type": "boolean"}` | toggle | -| yes/no, part of a set | `{"type": "boolean", "x-control": "checkbox"}` | checkbox | -| single select | `{"type": "string", "enum": [...]}` | popup selector | -| multi select | `{"type": "array", "items": {"enum": [...]}, "uniqueItems": true}` | checkbox list (one per option) | -| select + escape hatch | `enum: [..., "其他"]` plus a sibling `"_custom"` carrying `x-visible-when` | selector, then a text field once 「其他」 is picked | -| conditional field | `"x-visible-when": {"field": , "op": "equals"\|"contains", "value": …}` | hidden until the sibling matches | -| pick-one-with-config | `{"oneOf": [{"title": "A", …}, {"title": "B", …}]}` | variant chooser + subform | -| grouped fields | `{"type": "object", "properties": {…}}` | nested section | -| list of records | `{"type": "array", "items": {"type": "object", "properties": {…}}}` | list + per-entry overlay | -| free-form key/value | `{"type": "object", "additionalProperties": {"type": "string"}}` | key/value editor | +| You need | Schema shape | Renders as | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | +| short text input | `{"type": "string", "minLength": …, "pattern": …}` | inline text field | +| long text input | `{"type": "string", "x-multiline": true}` | multi-line text area | +| number | `{"type": "integer", "minimum": …, "maximum": …}` | numeric field with guards | +| number on a scale | `{"type": "…", "minimum": …, "maximum": …, "x-control": "slider"}` | slider with a live readout | +| slider with labels | slider + `"x-slider-marks": [{"value": …, "label": …}, …]` | slider with labelled stops | +| two-number interval | `{"type": "array", "items": {"type": "number"}, "minItems": 2, "maxItems": 2, "minimum": …, "maximum": …, "x-control": "range"}` | two-handle range slider | +| colour | `{"type": "string", "x-control": "color"}` | colour picker, not a text box | +| few short options | `{"type": "string", "enum": [...], "x-control": "segmented"}` | inline segmented control | +| few verbose options | `{"type": "string", "enum": [...], "x-control": "radio"}` | stacked radio group | +| yes/no | `{"type": "boolean"}` | toggle | +| yes/no, part of a set | `{"type": "boolean", "x-control": "checkbox"}` | checkbox | +| single select | `{"type": "string", "enum": [...]}` | popup selector | +| multi select | `{"type": "array", "items": {"enum": [...]}, "uniqueItems": true}` | checkbox list (one per option) | +| select + escape hatch | `enum: [..., "其他"]` plus a sibling `"_custom"` carrying `x-visible-when` | selector, then a text field once 「其他」 is picked | +| conditional field | `"x-visible-when": {"field": , "op": "equals"\|"contains", "value": …}` | hidden until the sibling matches | +| explain with a diagram | `"x-content": {"type": "mermaid", "source": "flowchart …"}` on the field | rendered figure above the control | +| explain with prose | `"x-content": {"type": "markdown", "source": "**why** this matters…"}` | sanitised rich text above the control | +| options worth seeing | `"x-options": [{"label": …, "description": …, "content": {"type": "mermaid", "source": …}}, …]` (aligned with `enum` by index) | each option shows its own figure | +| edit a diagram | `{"type": "string", "x-control": "mermaid"}` | mermaid source editor with a live preview | +| pick-one-with-config | `{"oneOf": [{"title": "A", …}, {"title": "B", …}]}` | variant chooser + subform | +| grouped fields | `{"type": "object", "properties": {…}}` | nested section | +| list of records | `{"type": "array", "items": {"type": "object", "properties": {…}}}` | list + per-entry overlay | +| free-form key/value | `{"type": "object", "additionalProperties": {"type": "string"}}` | key/value editor | `x-visible-when` is not only for escape hatches: use it whenever a question only applies conditionally (`has_changes` → `change_notes`, `cache.enabled` → @@ -162,18 +183,57 @@ removes typing or removes a wrong answer, not for decoration. `x-control` is a _request_, and bounds stay validation keywords: a slider needs `minimum`/`maximum` on the value, and a hint the engine cannot honour falls back to that shape's default control rather than failing. Slider/range/colour/ -segmented/radio hints need `schemaui` ≥ 0.14 / `schemaui-cli` ≥ 0.8; an older -engine ignores unknown `x-` keywords, so the form still runs — it just shows -plain inputs everywhere. +segmented/radio hints need `schemaui` ≥ 0.14 / `schemaui-cli` ≥ 0.8; the figure +surfaces below need `schemaui` ≥ 0.16 / `schemaui-cli` ≥ 0.9. An older engine +ignores unknown `x-` keywords, so the form still runs — it just shows plain +inputs everywhere. Read these before writing your own: `examples/web-research-brief.schema.json` (中文 — the worked reference: every control in the gallery, escape hatches, -conditional fields, `oneOf`, a record list, a key/value map), -`examples/feature-brief.schema.json` (English, every control type) and -`examples/invoice-reimbursement.schema.json` (中文, escape hatches on every -select). The engine's own gallery, -[`schemaui/examples/controls-gallery.schema.json`](https://github.com/YuniqueUnic/schemaui/blob/main/examples/controls-gallery.schema.json), -shows every hint value side by side. +conditional fields, `oneOf`, a record list, a key/value map, and figures on +nodes and options), `examples/feature-brief.schema.json` (English, every control +type) and `examples/invoice-reimbursement.schema.json` (中文, escape hatches on +every select). For what figure-bearing options look like as a complete form, +read `examples/deployment-architecture.schema.json` (中文 — every option carries +a diagram). The engine's own galleries, +[`schemaui/examples/controls-gallery.schema.json`](https://github.com/YuniqueUnic/schemaui/blob/main/examples/controls-gallery.schema.json) +and +[`schemaui/examples/rich-content.schema.json`](https://github.com/YuniqueUnic/schemaui/blob/main/examples/rich-content.schema.json), +show every hint value and every figure surface side by side. + +## Figures: when a picture answers faster than prose + +Some choices are easier to **see** than to read. The engine renders three kinds +of figure content — Mermaid diagrams, raw SVG, and sanitised Markdown — and puts +them in three places. Reach for them when the user would otherwise have to +mentally simulate your prose: + +1. **On the node** — + `"x-content": {"type": "mermaid"|"svg"|"markdown", "source": …}` renders a + figure above the control. Use it when the field needs context a sentence + can't carry: the request flow the timeout applies to, the pipeline the stage + names refer to, a legend of the states. +2. **On the options** — `"x-options"` entries (aligned with `enum` by index) may + each carry `label`, `description`, and `content` (same shape as `x-content`). + Use it when the options are _shapes of a system_ — topologies, rollouts, data + models — and the user picks by recognising the picture. A select shows each + option's figure inline; `radio` shows it under the label with the + description. +3. **As the value itself** — `"x-control": "mermaid"` makes the field's string + value a Mermaid source, edited in a live-preview editor. Only for fields + whose _answer is a diagram_ (a flow to review, a topology to correct) — never + as decoration on a config answer. + +Hard rules (the engine rejects the schema on these): every `source` must be a +non-empty string; an `x-options` array aligns with `enum` by index, so keep the +lengths aligned and give plain options `{}`/a `label` only; **`segmented` cannot +show figures** — use `radio` or `select` when options carry content; options +that mean "none/not configured" should not carry a figure. A figure is +explanatory, never the value: the answer file still receives the enum string. + +Choose figures the way you choose controls — because they remove work, not for +decoration. A three-option enum with one-word labels needs no diagrams; an +architecture choice whose options differ in shape is exactly what they're for. ## Steps @@ -283,14 +343,15 @@ something similar. Reuse a prior answer as defaults via `--config`. Six runnable forms live in `examples/` (each with a `.defaults.json` carrying the recommended answers): -| Example | Scenario | -| ----------------------------------- | ------------------------------------------------------------------------------------ | -| `env-schema.json` | minimal 4-field deploy form — first smoke test | -| `web-research-brief.schema.json` | research: scope a web-research task — **every control in the gallery** (中文) | -| `feature-brief.schema.json` | 12-question requirements brief, every control type + both hints (EN) | -| `invoice-reimbursement.schema.json` | office: invoice & expense reimbursement (中文, escape hatches) | -| `ecommerce-main-image.schema.json` | design: e-commerce hero image specs — sizes, fonts, colors, oneOf backgrounds (中文) | -| `seo-diagnosis.schema.json` | SEO triage: site, issues, keywords, competitors (中文) | +| Example | Scenario | +| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| `env-schema.json` | minimal 4-field deploy form — first smoke test | +| `web-research-brief.schema.json` | research: scope a web-research task — **every control in the gallery** + figures on nodes and a mermaid editor (中文) | +| `feature-brief.schema.json` | 12-question requirements brief, every control type + both hints (EN) | +| `invoice-reimbursement.schema.json` | office: invoice & expense reimbursement (中文, escape hatches) | +| `ecommerce-main-image.schema.json` | design: e-commerce hero image specs — sizes, fonts, colors, oneOf backgrounds (中文) | +| `seo-diagnosis.schema.json` | SEO triage: site, issues, keywords, competitors (中文) | +| `deployment-architecture.schema.json` | architecture choice: **every option carries a diagram** (中文 — the figure surfaces reference) | ```bash python3 scripts/ask.py \ diff --git a/docs/controls-gallery.png b/docs/controls-gallery.png new file mode 100644 index 0000000..05bfb86 Binary files /dev/null and b/docs/controls-gallery.png differ diff --git a/docs/mermaid-editor.png b/docs/mermaid-editor.png new file mode 100644 index 0000000..561134a Binary files /dev/null and b/docs/mermaid-editor.png differ diff --git a/docs/rich-options.png b/docs/rich-options.png new file mode 100644 index 0000000..f717498 Binary files /dev/null and b/docs/rich-options.png differ diff --git a/examples/deployment-architecture.defaults.json b/examples/deployment-architecture.defaults.json new file mode 100644 index 0000000..924b0e8 --- /dev/null +++ b/examples/deployment-architecture.defaults.json @@ -0,0 +1,8 @@ +{ + "topology": "monolith", + "rollout": "rolling", + "data_layer": "shared", + "network": { + "exposure": "公网负载均衡" + } +} diff --git a/examples/deployment-architecture.schema.json b/examples/deployment-architecture.schema.json new file mode 100644 index 0000000..eb73a28 --- /dev/null +++ b/examples/deployment-architecture.schema.json @@ -0,0 +1,188 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "部署架构选择", + "description": "示例:选项差在「形状」上时,让每个选项自带一张图,用户看图选型而不是读文字。节点上的 x-content 先给出全局结构,选项的 x-options 图回答“这个选项长什么样”。", + "type": "object", + "required": [ + "topology", + "rollout", + "data_layer" + ], + "properties": { + "topology": { + "type": "string", + "title": "服务拓扑", + "description": "radio 而不是 segmented:每个选项的图需要一整行的空间。图回答“长什么样”,description 回答“代价是什么”。", + "enum": [ + "monolith", + "microservices", + "serverless" + ], + "default": "monolith", + "x-control": "radio", + "x-options": [ + { + "label": "单体", + "description": "一个进程包办一切:部署与调试最简单,团队小时迭代最快。", + "content": { + "type": "mermaid", + "source": "flowchart TD\n ui[UI] --> app[App]\n app --> db[(DB)]" + } + }, + { + "label": "微服务", + "description": "按业务边界拆分,独立部署与扩容;换来的是网关、网络调用与运维成本。", + "content": { + "type": "mermaid", + "source": "flowchart TD\n gw[gateway] --> a & b\n a --> dba[(db)]\n b --> dbb[(db)]" + } + }, + { + "label": "Serverless", + "description": "函数 + 托管队列,按调用计费、自动扩缩;冷启动与厂商绑定是代价。", + "content": { + "type": "mermaid", + "source": "flowchart LR\n fn1(lambda) --> queue --> fn2(lambda)" + } + } + ] + }, + "rollout": { + "type": "string", + "title": "发布策略", + "description": "同一套服务,三种把新版本送到用户手里的形状。金丝雀需要流量探测与回滚自动化,团队小时不建议首选。", + "enum": [ + "rolling", + "blue-green", + "canary" + ], + "default": "rolling", + "x-control": "radio", + "x-options": [ + { + "label": "滚动发布", + "description": "实例逐批替换,简单且资源占用最低。", + "content": { + "type": "mermaid", + "source": "flowchart LR\n v1[v1 v1 v1] --> v2[v1 v2 v2] --> v3[v2 v2 v2]" + } + }, + { + "label": "蓝绿发布", + "description": "两套环境整组切换,回滚一瞬间;要付双倍资源的钱。", + "content": { + "type": "mermaid", + "source": "flowchart LR\n user(user) --> gw{switch}\n gw -->|now| blue[blue v1]\n gw -.->|after switch| green[green v2]" + } + }, + { + "label": "金丝雀发布", + "description": "一小撮真实流量先试新版本,指标正常再放量。", + "content": { + "type": "mermaid", + "source": "flowchart LR\n user(user) --> lb{按比例}\n lb -->|95%| stable[v1 稳定版]\n lb -->|5%| canary[v2 金丝雀]" + } + } + ] + }, + "data_layer": { + "type": "string", + "title": "数据层", + "description": "select 同样能带图:下拉里每个选项是一张小图,选中后大图留在控件下方。前两个选项各带图,转义选项「其他」不带——它没有形状可画。", + "enum": [ + "shared", + "dedicated", + "其他" + ], + "default": "shared", + "x-options": [ + { + "label": "共享数据库", + "description": "所有服务读写同一个库:事务简单,边界容易糊掉。", + "content": { + "type": "mermaid", + "source": "flowchart LR\n s1[服务 A] & s2[服务 B] --> db[(共享 DB)]" + } + }, + { + "label": "每服务独享", + "description": "库随服务私有,边界清晰;跨服务查询要走 API。", + "content": { + "type": "mermaid", + "source": "flowchart LR\n s1[服务 A] --> dba[(A 库)]\n s2[服务 B] --> dbb[(B 库)]" + } + }, + { + "label": "其他" + } + ] + }, + "data_layer_custom": { + "type": "string", + "title": "数据层 · 自定义说明", + "description": "选了「其他」时填写:你的数据层长什么样、为什么前两种都不合适。", + "x-multiline": true, + "x-visible-when": { + "field": "data_layer", + "op": "equals", + "value": "其他" + } + }, + "service_flow": { + "type": "string", + "title": "调用链图(可编辑)", + "description": "x-control: mermaid —— 字段的值就是图的源码,右侧实时预览。这里预填了按你拓扑推演的调用链;改图就是改答案,适合需要用户确认或修正的流程。", + "default": "flowchart TD\n client(client) --> gw[api 网关]\n gw --> svc[业务服务]\n svc --> db[(数据库)]\n svc --> cache[(缓存)]", + "x-control": "mermaid" + }, + "network": { + "type": "object", + "title": "网络边界", + "description": "对象节点也能挂 x-content:这张图解释的是下面两个字段共同描述的边界,而不是哪一个字段。", + "x-content": { + "type": "mermaid", + "source": "flowchart LR\n net[私有子网] --- svc[服务实例]\n svc --- db[(数据库)]\n lb[公网负载均衡] --- net" + }, + "properties": { + "exposure": { + "type": "string", + "title": "入口暴露", + "description": "服务如何被公网访问。", + "enum": [ + "公网负载均衡", + "仅内网 + 跳板", + "其他" + ], + "default": "公网负载均衡", + "x-options": [ + { + "label": "公网负载均衡" + }, + { + "label": "仅内网 + 跳板" + }, + { + "label": "其他" + } + ] + }, + "exposure_custom": { + "type": "string", + "title": "入口暴露 · 自定义说明", + "description": "选了「其他」时填写。", + "x-visible-when": { + "field": "exposure", + "op": "equals", + "value": "其他" + } + } + } + }, + "notes": { + "type": "string", + "title": "补充说明", + "description": "任何图都没表达出来的约束(合规、既有系统、团队技能)写在这里。", + "x-multiline": true + } + } +} diff --git a/examples/web-research-brief.schema.json b/examples/web-research-brief.schema.json index c5dad0a..14fcb49 100644 --- a/examples/web-research-brief.schema.json +++ b/examples/web-research-brief.schema.json @@ -102,7 +102,11 @@ "maximum": 4, "x-control": "slider", "x-slider-marks": [0, 1, 2, 3, 4], - "default": 2 + "default": 2, + "x-content": { + "type": "mermaid", + "source": "flowchart LR\n q[检索] --> f[筛选] --> x[提炼] --> w[成文]\n f -. 浅层到此 .-> w\n x -. 深层继续 .-> v[交叉验证] --> w" + } }, "source_authority": { "type": "integer", @@ -428,6 +432,13 @@ "description": "键值对:方便把这次调研归档和检索,例如 项目=2026 选型。", "additionalProperties": { "type": "string" }, "default": { "项目": "2026 选型", "负责人": "产品组" } + }, + "report_flow": { + "type": "string", + "title": "调研流程图(可编辑)", + "description": "x-control: mermaid —— 字段的值就是图的源码,右侧实时预览。这里预填了本次调研的执行流程;如果你对步骤或顺序有意见,直接改图,改的就是执行约定。", + "default": "flowchart TD\n q[检索 query 集] --> s[相关性打分]\n s --> dedup[去重合并]\n dedup --> read[精读 top 来源]\n read --> draft[初稿]\n draft --> cite[逐条对引用] --> final[终稿]", + "x-control": "mermaid" } } } diff --git a/prompts/ask.prompt.md b/prompts/ask.prompt.md index 36887db..2fbc643 100644 --- a/prompts/ask.prompt.md +++ b/prompts/ask.prompt.md @@ -168,3 +168,19 @@ users to tunnel first: `ssh -L 8787:localhost:8787 `. Before asking, check `.schemaui/answers/` — the user may have already answered something similar. Reuse a prior answer as defaults via `--config`. + +## Show, don't tell (figures) + +- Options that differ in _shape_ (topologies, rollouts, data models) get a + diagram each: + `"x-options": [{"label": …, "description": …, "content": + {"type": "mermaid", "source": "flowchart …"}}, …]` + — aligned with `enum` by index; use `radio`/`select` (segmented cannot show + figures). +- A question that needs a picture to make sense carries + `"x-content": {"type": "mermaid"|"svg"|"markdown", "source": …}` — rendered + above the control. +- Only when the answer _is_ a diagram: `"x-control": "mermaid"` edits the source + with a live preview. A figure explains; it is never the value. +- Reference: `examples/deployment-architecture.schema.json` (every option with a + diagram) and `schemaui ≥ 0.16` for the figure surfaces. diff --git a/skills/grill-with-docs/SKILL.md b/skills/grill-with-docs/SKILL.md new file mode 100644 index 0000000..5cfc6a3 --- /dev/null +++ b/skills/grill-with-docs/SKILL.md @@ -0,0 +1,69 @@ +--- +name: grill-with-docs +description: > + Interview the user relentlessly about a plan or design until reaching shared + understanding — one question at a time, walking each branch of the decision + tree, every question carrying a recommended answer, and every answer grounded + in the project's own docs and examples rather than guesses. Use when the user + asks to be grilled, wants a plan stress-tested, or when a design has to be + pinned down before code is written. Built into a2ui-ask so agents that need + the discipline have it even when no separate grilling skill is installed. +--- + +# grill-with-docs — relentless interviewing, grounded in reference docs + +Interview the user relentlessly about every aspect of this plan until you reach +a shared understanding. Walk down each branch of the decision tree, resolving +dependencies between decisions one by one: a choice that constrains later +questions gets asked before the questions it constrains. For every question, +provide your recommended answer — the user should be confirming or correcting, +not starting from a blank page. + +Ask the questions **one at a time**, and stop when the answers have converged: +no new branches are opening, and you could write the plan down without inventing +anything. + +If a question can be answered by exploring the codebase, git history, existing +answers, or the reference docs below — explore instead of asking. The interview +is for decisions only the user can make. + +## Ground every question in the docs + +Before the interview starts, read the reference material the task points at — a +feature brief should be shaped by how similar features are scoped in this repo, +a UI by the design system, a schema by the engine's own examples. Concretely, +for forms built with [schemaui](https://github.com/YuniqueUnic/schemaui): + +- the control cheat sheet and rich-content guide in the a2ui-ask `SKILL.md` that + ships next to this file; +- the runnable forms in a2ui-ask's `examples/` — the shortest path to "what does + a good schema look like"; +- schemaui's own + [controls gallery](https://github.com/YuniqueUnic/schemaui/blob/main/examples/controls-gallery.schema.json) + and + [rich-content gallery](https://github.com/YuniqueUnic/schemaui/blob/main/examples/rich-content.schema.json) + — every hint value and every figure surface side by side. + +Reading beats guessing: an option list drafted after looking at the gallery is +shorter and better labelled than one drafted from memory. + +## Interview discipline + +1. **One question per turn.** The user answers in one line; you follow up in the + next. A wall of ten questions gets ten lazy answers. +2. **Recommend, then ask.** "I'd go with X because Y — ok?" beats "what should + we do about Z?". +3. **Resolve dependencies in order.** Ask the question that narrows the space + before the questions it narrows. +4. **Explore instead of asking** whenever the answer is discoverable. +5. **Record what you heard.** Restate the decision in one line before moving to + the next branch, so a misunderstanding costs one message, not a whole plan. +6. **Stop when converged.** Ending the interview is part of the discipline. + +## When the interview becomes a form + +If the interviewer's questions are structured choices and config, stop asking in +chat: generate a draft-07 JSON Schema with your recommended answers as +`default`s and hand it to a2ui-ask's ask script, so the user gets a real form +(see the a2ui-ask `SKILL.md` for the control cheat sheet). Use the form for the +cluster of decisions; keep the chat interview for the open-ended ones. diff --git a/tests/test_ask.py b/tests/test_ask.py index f307272..249c4b0 100644 --- a/tests/test_ask.py +++ b/tests/test_ask.py @@ -90,6 +90,7 @@ "/urgency": "segmented", "/include_competitor_pricing": "checkbox", "/accent_color": "color", + "/report_flow": "mermaid", } # The other half of that contract: a hint is opt-in, so these fields must stay @@ -291,18 +292,47 @@ def read_until(proc: subprocess.Popen, marker: str, timeout: float = 30) -> str: raise AssertionError(f"never saw {marker!r} on stdout") +def _get_first(url: str, paths: tuple[str, ...]): + """GET the first path that answers; schemaui ≥ 0.9 serves /api/v1/*, older + engines the unversioned routes. A 404 falls through, anything else raises.""" + last: Exception | None = None + for path in paths: + try: + return urllib.request.urlopen(f"{url}{path}", timeout=5) + except urllib.error.HTTPError as error: + if error.code == 404: + last = error + continue + raise + raise last # type: ignore[misc] + + +def _post_first(url: str, paths: tuple[str, ...], body: bytes): + last: Exception | None = None + for path in paths: + req = urllib.request.Request( + f"{url}{path}", + data=body, + headers={"Content-Type": "application/json"}, + method="POST", + ) + try: + return urllib.request.urlopen(req, timeout=5) + except urllib.error.HTTPError as error: + if error.code == 404: + last = error + continue + raise + raise last # type: ignore[misc] + + def drive_session(url: str, payload: dict) -> None: """Play the role of the user: load the session, then Save & Exit.""" url = url.rstrip("/") - with urllib.request.urlopen(f"{url}/api/session", timeout=5) as resp: + with _get_first(url, ("/api/v1/session", "/api/session")) as resp: assert resp.status == 200 - req = urllib.request.Request( - f"{url}/api/exit", - data=json.dumps({"data": payload, "commit": True}).encode(), - headers={"Content-Type": "application/json"}, - method="POST", - ) - with urllib.request.urlopen(req, timeout=5) as resp: + body = json.dumps({"data": payload, "commit": True}).encode() + with _post_first(url, ("/api/v1/exit", "/api/exit"), body) as resp: assert resp.status == 200 @@ -410,7 +440,7 @@ def test_e2e_feature_brief_full_control_showcase(tmp_path: Path): ) try: url = read_until(proc, "SCHEMAUI_URL=").strip().split("=", 1)[1].rstrip("/") - with urllib.request.urlopen(f"{url}/api/session", timeout=5) as resp: + with _get_first(url, ("/api/v1/session", "/api/session")) as resp: session = json.loads(resp.read()) kinds = [ next(iter(root["kind"].values()) if isinstance(root["kind"], dict) else root["kind"]) @@ -479,7 +509,7 @@ def test_e2e_web_research_brief_covers_the_control_gallery(tmp_path: Path): ) try: url = read_until(proc, "SCHEMAUI_URL=").strip().split("=", 1)[1].rstrip("/") - with urllib.request.urlopen(f"{url}/api/session", timeout=5) as resp: + with _get_first(url, ("/api/v1/session", "/api/session")) as resp: session = json.loads(resp.read()) by_pointer = {node["pointer"]: node for node in collect_nodes(session["ui_ast"]["roots"])} @@ -497,6 +527,11 @@ def bounds(pointer: str) -> dict: # ... and `x-multiline` marks a string multi-line without naming a control assert by_pointer["/background"]["kind"]["multiline"] is True + # figures: the depth slider carries a node-level mermaid x-content, and + # the report flow is edited as a live-preview mermaid source + assert by_pointer["/depth_level"]["content"]["type"] == "mermaid" + assert by_pointer["/report_flow"]["control"] == "mermaid" + # the slider/range variants the gallery separates: a derived step # against a declared one, and bare against labelled against unit marks assert bounds("/relevance_floor")["step"] is None