# Interactive Questionnaire

> When you (the agent) would ask the user anything at all — one quick question or a full intake of preferences, requirements, or structured input — use this skill instead of asking loosely in chat. It routes by complexity: simple asks become a numbered plain-text list (题号/选项号; bold title, italic description, options as markdown list items so they stay one-per-line in chat); complex asks become an interactive HTML questionnaire assembled from a template plus component snippets, which the user fills in and copies back as JSON. Dependent questions must be serial rounds (never "if Q1 then Q3" in the same form); independent questions may be asked together. Internally keep a question domain and do not stop until it is complete; expand it when answers create underivable new questions. Once invoked in a conversation it stays in effect for the whole conversation: every later question, even a single simple one, must go through the questionnaire or the text-version — never a casual free-form ask. Enforced by a mandatory s

- Skill: `duskykitecn/interactive-questionnaire` (Agent Skill, multi-file: 16 files)
- Install (CLI): `npx skillmds@latest add duskykitecn/interactive-questionnaire`
- Raw SKILL.md: https://api.skillmd.com/api/skills/duskykitecn/interactive-questionnaire/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: duskykitecn (https://skillmd.com/u/duskykitecn)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/duskykitecn/interactive-questionnaire

---


# Interactive Questionnaire

当你要向用户提问时（哪怕只有一个问题），用本技能组织提问，而不是在聊天里零散追问。**一经在对话中启用，就对整个对话持续生效**（见下方「生效范围」）。

## 何时用

- 你需要向用户提问、或收集偏好/需求/结构化输入时——**哪怕只有一个问题**。
- 需要把用户回答变成**可解析的结构化结果**，而非一段散文。

## 生效范围：一经调用，全会话生效

本技能一旦在某次对话中被调用，就对该对话的**所有后续回合持续生效**，直到用户明确要求停止。**不要**问完一份问卷，就在下一回合把它忘掉、退回随口提问或散在聊天里问——那会导致「上一份问完、下一回合就不遵守、还退回没有约定的散问」，正是要杜绝的。

**任何时候你要向用户提问——无论问题多少、无论多简单——只能走以下两条之一，不存在第三条「随口简单问」的路线：**

- 复杂 → HTML 问卷；
- 简单 → 文字版（题号/选项号约定 + 每题描述），**即使只有一个问题也照此办理**。

即：普通自由散问被禁用；每一次提问，要么是问卷，要么是符合约定的文字版。

## 问题域与串并行（内部编排，强制）

开始提问前，先在内部圈定**问题域**：为完成当前任务，有哪些决策必须由用户拍板。问题域只作你自己的对账，**不要**写进回复、问卷标题或副标题，也不要请用户确认这份清单。

收到答案后更新问题域：已拍板的划掉；若答案引出了新的疑问，且该疑问**无法**从已有答案准确、唯一地推导出来，就把新疑问并入问题域。停止提问的唯一条件是：当前问题域内每一项都已得到用户确定的回复与决策，且没有未消化的新疑问。用户答完后若问题域已扩展，结束与否取决于**新的**问题域是否已无疑问——不是「这份问卷交回来了」就结束。

### 一题必须可单独决断

问卷里每一题，用户都要能在不知道其他题答案的情况下做完。禁止「若第 1 题选 x，则回答本题」这类把依赖写进题面的问法。

### 串行 / 并行

- **有前后依赖**（本题的选项、是否该问、问什么，取决于另一题的答案）→ **串行多轮**：本轮只问前面那题，以及所有与之互不依赖、现在就能答的题；等用户把这一份答完回传后，再根据答案设计下一轮。不要把后置题提前塞进同一份。
- **没有明显前后/逻辑依赖** → **并行**：可以放在同一份问卷里一起问。不设题量上限，也不要只因为「主题不同」就拆成多轮。

同一份里若有多个主题：用**题目顺序**表达——问完一个主题再问下一个。需要时题干写成 `「主题」问题正文`；并非每份问卷、每一题都要加主题标识。各题本身已经很好区分时（例如十题十个主题），不要再加主题前缀。

### 一题的粒度

尺度只有一条：用户是否容易理解、容易单独作答。

- 同一个问题点，一题就能问完的（含题内短附属，如 toggle-reveal 的「是/否 + 开启后填一句」），不要拆成多轮。
- 不要为了少拆一轮，把多个决策点挤进同一题的题干、描述或选项里。

## 每轮结尾自检（强制）

本技能生效期间，**每一轮回复的结尾**都必须执行一次自检。修辞性反问、不期待用户回答的语句不在核对范围内。核对：

1. 面向用户、期待其回答的提问，是否全部走了问卷或文字版（无散问）。
2. 本轮问卷里的每一题是否可单独决断：无「若第 n 题选 x 则回答本题」；有依赖的后置题是否已留到下一轮，而不是堆进同一份。
3. 是否把多个决策点挤进了同一题；同一问题点能一题问完的，是否被无故拆轮。
4. 文字版是否按「文字版约定」写成会话 markdown 块：题干加粗、说明斜体且无抢眼前缀、选项为 `- a. ` 列表（裸 `a.` 换行在会话里会收成一行）。
5. 若本轮是在收答案而非提问：内部问题域是否仍有缺口或不可推导的新疑问——有则必须在本轮输出末尾给出下一轮问卷/文字版，不得当作已经问完。

- **静默执行**：自检通过时不输出任何标注，不打扰用户。
- **发现违规 → 当场补救**：在本轮结尾简要标注自检结果，并立即按规则重出——散问改为问卷/文字版；同卷依赖改为只保留本轮可独立作答的题；挤成一题的拆开；文字版版式不对的按约定重排。是补救，不是只报告违规。
- 本自检是「生效范围」与「问题域/串并行」的强制执行机制：防止问完一份就退回散问，也防止一股脑把有依赖的题堆在同一份里。

## 用法：先正常输出，末尾再把本轮问题总结成问卷/文字版

**本技能不改变你自身的输出行为。** 先按你的惯例正常展开分析、给出思路与建议——该怎么写就怎么写。等这一轮正常输出结束后，再做一次「总结性提问」：只把**本轮可独立作答**的问题，用最小心智负担、精确而完整地概括成「标题 + 描述 + 选项（如有）」，据此生成问卷（复杂）或文字版（简单）。有依赖、现在还不能答的题不要写进这一份；等答案回传后再设计下一轮。

每个问题都带一段**描述**（见下）：讲清这个问题**为什么会出现**、**要解决什么**、**推荐哪种做法、为什么**。用意是——用户若能仅凭问卷/文字版就做决定，就直接决定；否则可对照你上面的原始输出理解、或就原始输出向你追问，从而以最快、最省心的方式与你的思路对齐。

## 路由：按复杂度二选一

技能触发后**不默认走问卷**，先判复杂度：

- **简单**（问题少、每题选项少、无排序/区间/多层嵌套）→ **文字版**：编号列出问题，用下面的题号/选项号约定。
- **复杂**（问题多，或含多选长列表、排序、数值区间等）→ **HTML 问卷**：装配交给用户填。
- 用户可显式指定走哪一种，以用户要求为准。

「本题要等另一题的答案才能问」不是改走 HTML 的理由，而是改走**下一轮**的理由。

## 文字版约定（题号 / 选项号）

只用于文字版；HTML 问卷不带题号/选项号（可视化已区分各题）。文字版写在智能体会话里，会话按 markdown 渲染。版式必须迁就这件事，否则「源码里已经分行」在屏幕上仍会糊成一块。

**为什么选项会挤成一行**：CommonMark 里只有 `1.` `2.` 这种数字会开有序列表；`a.` `b.` `c.` 只是普通文本。相邻的普通文本行会收成**同一段落**，换行变成空格。行首裸写 `1. 题干` 还会开启有序列表，把后面的说明和选项吞进同一项。这是 markdown 规则，不是 Cursor 单独的渲染 bug。

**题号（强制）**：题干整行加粗，形如 `**1. 出行人数？**`。每份从 `1.` 重新编号；第 10 题及以后用 `**10. 题干**`。题与题之间空一行。加粗有两件事：题号是视觉焦点；行首不是裸的 `1. `，不会触发有序列表。

**问题描述（强制版式）**：题干之下空一行，说明整段斜体，形如 `*……*`。讲清为什么问、要解决什么、推荐怎么选——与 HTML 问卷里的描述区同义；描述**不带题号/选项号**。不要加 `※`、不要加 `>`，也不要加任何比题号更抢眼的前缀——说明是旁注，不能喧宾夺主。禁止把说明写成普通正文，更禁止和选项挤在同一段。

**选项号**：
- **选择类问题** → 每个选项写成一条 markdown 无序列表：`- a. 美食`。前面的 `- ` 是为了让会话把它当成独立块，才会一行一条；用户仍只靠字母 `a` `b` 作答。禁止只写裸的 `a.` 再换行（会被收成一段）。禁止把多个选项写在同一行。
- **自由回答类问题** → **不加**选项号；斜体说明写完即可。
- 选项超过 26 个（超出 `z`）时，把**最后一个选项固定为「其他」**，请用户用自己的话补充。

**用户回复与解析**：选择题按字母作答（多选给多个字母，如 `1: a,c`）、自由题写文字，按题号定位。解析这些回复填回你的流程；不要另造固定回传语法，自然作答即可。

示例（智能体输出时按下面的 markdown 写，不要包进代码块）：

```
**1. 出行人数？**

*同行人数决定房型与整体节奏，填你确定的人数即可；带老人小孩可在备注说明。*

- a. 1 人
- b. 2 人
- c. 3–4 人
- d. 5 人以上

**2. 兴趣（可多选）？**

*决定我优先推荐哪类目的地与活动，选你真正想要的即可。*

- a. 美食
- b. 自然风光
- c. 历史人文

**3. 怎么称呼你？（直接写）**

*只是方便称呼你，随意填，不影响推荐。*
```

## HTML 问卷：装配流程

引擎已封装为 `assets/template.html`（外壳）+ `references/snippets/`（每种组件一个片段）。装配步骤（详见 `references/components.md`）：

1. 复制 `assets/template.html`。
2. 替换顶部 `<QUESTIONNAIRE_TITLE>`（标题）与 `<QUESTIONNAIRE_SUBTITLE>`（一句副标题，**不写填写说明**）。
3. 每个问题选一种组件 → 从 `snippets/` 取片段 → 替换其中 `<大写下划线>` 占位（片段末尾有注释指引）→ 删掉注释行。
4. 所有填好的片段按顺序拼接，整体替换模板里那行 `<!-- QUESTION_FIELDS -->`。
5. 把完成的 HTML 交给用户打开填写。用户填完点「复制结果」得到 JSON，回贴给你解析。

要点：`data-field` 全问卷唯一、snake_case（= 结果 JSON 的 key）；每题预选最可能的默认项。每题标题下、预设区之上有一段**描述区**（填 `<QUESTION_DESC>`），承载该问题的「为什么 / 解决什么 / 推荐什么」——它只帮助用户理解，**不出现在结果 JSON 里**。完整 13 组件范例见 `assets/demo.html`。同卷内按主题顺序排列题目（问完一个主题再问下一个）；编排规则与文字版相同，见「问题域与串并行」。

## 组件选择

| 需求 | 用 | data-type |
| --- | --- | --- |
| 单选 · 选项少且短 | segmented | `segmented` |
| 单选 · 选项多 | select | `select` |
| 单选 · 每项需一句说明 | radio-cards | `radiocards` |
| 多选 · 选项少且短 | chips | `chips` |
| 多选 · 选项多或较长 | multi-select | `multiselect` |
| 是/否（可开启后追问） | toggle-reveal | `toggle` |
| 有序档位（悠闲/适中/紧凑） | slider（label 模式） | `slider` |
| 单个数值（预算等） | slider（number 模式） | `slider` |
| 数值区间（上下限） | range | `range` |
| 小整数计数（住几晚） | stepper | `stepper` |
| 单行短文本 | text | `text` |
| 多行长文本 | textarea | `textarea` |
| 优先级排序 | rank | `rank` |

toggle-reveal 只用于**同一决策点**的是/否及其附属短信息。若开启之后要问的是另一个必须单独拍板的决策，拆成下一轮，不要靠隐藏字段把两题焊在一份问卷里。

**三条硬规则**：
1. 每个可选组件都带「＋ 改用文字填写」入口（用户随时可改为自由文字，值可能是字符串）——`text`/`textarea` 本身即文字，无此入口。
2. 必**预选最可能项**：单选选一项、多选选一或多个、数值/区间/计数给默认值、排序给合理初始次序。
3. 能用直观控件就不用下拉：选项少优先 segmented 而非 select；是/否用 toggle 而非两个选项。

## 结果 JSON 契约

用户点「复制结果」得到（与 `assets/demo.html` 引擎一致，**只含可见字段**——隐藏的条件子字段不出现）：

```json
{
  "title": "问卷标题",
  "responses": {
    "party_size": { "type": "answer",    "answer":    { "value": "2 人", "dirty": false } },
    "budget":     { "type": "custom",    "custom":    { "value": "看行程再定" } },
    "need_hotel": { "type": "objection", "objection": { "text": "这题不适用" } }
  },
  "system": {
    "theme": null,
    "display_mode": "preview",
    "fold_mode": "collapsed"
  }
}
```

- `responses` 的 key = 组件 `data-field`。每题一个条目，形如 `{ "type": ..., "<type>": {...} }`，只带当前 `type` 对应的那个对象：
  - `type: "answer"` → `answer: { value, dirty }`：用户按控件作答。`dirty=false` 表示仍是预选默认值（未改动），`true` 表示用户改过。
  - `type: "custom"` → `custom: { value }`：用户点了「改用文字填写」，`value` 是其自由文字。
  - `type: "objection"` → `objection: { text }`：用户对该题有异议/认为不适用，`text` 是其说明（可空）。
- `answer.value` 的形态随组件：单选/档位为字符串，多选为字符串数组，区间为 `[下限, 上限]`，排序为有序数组，计数/数值为数字。以引擎实际输出为准。
- `system`：`theme`、`display_mode`（`preview`/`raw`）、`fold_mode`（`collapsed`/`expanded`）。`theme` 取值 `null` | `"light"` | `"dark"` 且**恒出现**：首次问卷为 `null`（页面据环境 `prefers-color-scheme` 探测——探到则回填 `light`/`dark`，探不到保持 `null`；`null` 按 light 渲染但值不写成 light），用户手动切换后落为 `"light"`/`"dark"`；一旦非 `null` 便不再被探测覆盖。若要在同一会话的后续问卷里保持用户上次的选择，把新问卷 `template.html` 中的 `var themeState={value:null}` 改为上一份结果的 `system.theme`。解析结果时通常只关心 `responses`。

## 完备性自检

**三份清单必须行数相等**：`references/components.md` 登记表的行数 == `references/snippets/` 的文件数 == `assets/demo.html` 里 `COMPONENTS` 的条目数（当前均为 **12**）。

**加一种组件**只需三处小改：`assets/demo.html`（及 `template.html`）里 `COMPONENTS` 加一条 `{def, wire, collect, ...}` 条目 + `components.md` 登记表加一行 + `snippets/` 加一个片段文件。共享逻辑（折叠、异议、自定义、dirty、JSON 组装）已在引擎核心，无需触碰。

