让创作节点声明并注入绑定库,并新增用户需求步承接偏好。
装载时同时注入正文安全子集;编排与对话落盘都能看见可用库条目。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+3
-2
@@ -135,8 +135,8 @@
|
||||
| `design-flow` | **创作 · 流程编排** | 以已选【配方】为起点,编排/增量修订工作流计划(可变 DAG) |
|
||||
| `design-step` | **创作 · 执行步骤** | 按工作流计划执行当前【技能】 |
|
||||
| `opening-generator` | **开局 · 开场白** | 无 `opening-setup` 时的兜底落库;有则选定开场白节点即收口 |
|
||||
| `opening-setup`(技能) | **开场白与开场变量** | 每条开场 = meta(用户角色+初值)+ 正文(present.v1,壳跟正文组成)。游玩辅助 Tab「开场」点选;没有或不采用则用当前用户角色卡 |
|
||||
| `protagonist`(技能) | **主角设定** | 旧路径兜底;新开场把用户角色写在开场白 meta 里 |
|
||||
| `opening-setup`(技能) | **开场白与开场变量** | 工序路径:1~多条候选 swipe。对话落盘用 `设计.开场白[#短码]` 增殖;游玩辅助 Tab「开场」点选 |
|
||||
| `protagonist`(技能) | **主角设定** | `设计.主角设定[#短码]`;与开场白同短码即绑定该开局。挂在当前选用的用户角色卡下 |
|
||||
| `worker-spec`(技能) | **游玩拓扑** | 勾选固定槽;旧称「Worker 规格」,勿再当自由发明演员 |
|
||||
| `design-core` 等 | (已废弃) | 旧分步 skill;勿再调度 |
|
||||
|
||||
@@ -207,6 +207,7 @@
|
||||
| `narrative_guide` | 叙事指南与故事推进 |
|
||||
| `narrative-guide` | 叙事指南(对话落盘) |
|
||||
| `story-progression` | 故事推进(对话落盘) |
|
||||
| `user-requirements` | 用户需求(偏好库选用) |
|
||||
| `input_protocol` | 输入协议 |
|
||||
| `core_premise` | 核心前提 |
|
||||
| `aesthetics` | 美学纲领(旧;已并入美学纲领与交互范式) |
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
# auto = true 时为程序步:不抛默认问题、不经「同意并开始」,提案后直接执行(产物仍验收)
|
||||
# kind = 节点特性。缺省=普通执行步。prior-artifact=先验产物(须先定写什么;params 是规划产物,不拦执行)
|
||||
# intake = 可选;recipe=仅工序编排 DAG;dictate=仅对话落盘何时落盘。缺省=两族都可见
|
||||
# libraries = 可选;本步绑定的全局库 id 列表(如 style-packs);design-step 注入,库空跳过
|
||||
# params = 可选;普通节点=编排进 DAG 时 steps[].params 的声明(required 须在执行前钉齐)
|
||||
# 〔先验产物〕=规划产物字段(可提前填,空则步内钉)
|
||||
# opening = 可选;覆盖 prompt.md 里 ```opening 默认问题(一般只写在 prompt.md)
|
||||
@@ -38,11 +39,20 @@ modules:
|
||||
|
||||
- id: protagonist
|
||||
name: 主角设定
|
||||
repeatable: true
|
||||
declaration: >-
|
||||
钉清 @玩家 扮演的那个人:名字、背景、特殊设定(身份/金手指等)。
|
||||
仅当开场白 meta 不够用时才排;用户角色优先写在开场白里。
|
||||
代入有名片时排本步;可 设计.主角设定#短码 与开场白同槽绑定;禁止把名字/简介写进开场白正文。
|
||||
artifact: 设计.主角设定
|
||||
|
||||
- id: user-requirements
|
||||
name: 用户需求
|
||||
libraries: [preferences]
|
||||
declaration: >-
|
||||
把跨局偏好库里对本局生效的硬约束(禁区/纠偏/默认取向)落成可执行条目;
|
||||
不重做文风问卷,也不钉体验站位
|
||||
artifact: 设计.用户需求
|
||||
|
||||
# —— 世界与内容 ——
|
||||
- id: world-blueprint
|
||||
name: 舞台骨架
|
||||
@@ -91,6 +101,7 @@ modules:
|
||||
- id: narrative
|
||||
name: 叙事指南与故事推进
|
||||
intake: recipe
|
||||
libraries: [style-packs]
|
||||
declaration: >-
|
||||
美学已有方向时:把感觉落成可执行的遣词、笔墨落点、禁忌与推进方式;
|
||||
不重做体验问卷。工序编排用本步;对话落盘拆成「叙事指南」「故事推进」
|
||||
@@ -99,6 +110,7 @@ modules:
|
||||
- id: narrative-guide
|
||||
name: 叙事指南
|
||||
intake: dictate
|
||||
libraries: [style-packs]
|
||||
declaration: >-
|
||||
美学已有方向时:把感觉落成可执行的遣词、笔墨落点、禁忌与写法档;
|
||||
不重做体验问卷,也不钉故事怎么往前
|
||||
|
||||
@@ -46,11 +46,12 @@ feeds: narrator,gm
|
||||
把美学里的感觉落成可执行的写法。作用对象是本局游玩正文(转述与主世界层都读这份)。
|
||||
|
||||
执行顺序:
|
||||
1. 继承美学体验与禁忌,改写成做法向句子。
|
||||
2. 写纲领、遣词(含示范)、笔墨焦点、禁忌/不偏好。
|
||||
1. 若提示中有【文风库 · 可选用】且用户点名/对上某包:优先按该包落成「怎么写」,再按本局美学微调;不要重做文风问卷。
|
||||
2. 否则继承美学体验与禁忌,改写成做法向句子。
|
||||
3. 写纲领、遣词(含示范)、笔墨焦点、禁忌/不偏好。
|
||||
遣词用户未提段落形态时,默认写入:完整长自然段叙述,不要单句成段(篇幅数字交给「回复呈现」)。
|
||||
3. 情境备用 = 写法档(体裁/调性容器),不是具体桥段。有切换则每条写:名字=档名(如「官能小说」「网文爽文」「展示恐怖」「展示情色」),用法=该档怎么遣词、笔墨落哪、何时切进来。无切换则 `[]`。用户若报出「当众折辱」「独处一室」这类场合,升一档写进对应写法档,场合本身不单列。
|
||||
4. 自评 + 追问。故事怎么往前、用户输入用法交给「故事推进」,本步不写。
|
||||
4. 情境备用 = 写法档(体裁/调性容器),不是具体桥段。有切换则每条写:名字=档名(如「官能小说」「网文爽文」「展示恐怖」「展示情色」),用法=该档怎么遣词、笔墨落哪、何时切进来。无切换则 `[]`。用户若报出「当众折辱」「独处一室」这类场合,升一档写进对应写法档,场合本身不单列。
|
||||
5. 自评 + 追问。故事怎么往前、用户输入用法交给「故事推进」,本步不写。
|
||||
|
||||
summary:`叙事指南 · …`
|
||||
```
|
||||
|
||||
@@ -50,16 +50,17 @@ feeds: narrator,gm
|
||||
mount:`narrator` 与 `world-simulator` 都挂同一份全文——不要为省一点点 token 拆两套裁剪方案。
|
||||
|
||||
执行顺序:
|
||||
1. 继承美学体验与禁忌,改写成做法向句子。
|
||||
2. 写纲领、遣词(含示范)、笔墨焦点、禁忌/不偏好。
|
||||
1. 若提示中有【文风库 · 可选用】且用户点名/对上某包:优先按该包落成「怎么写」部分,再按本局美学微调;不要重做文风问卷。推进仍按本步规则钉。
|
||||
2. 否则继承美学体验与禁忌,改写成做法向句子。
|
||||
3. 写纲领、遣词(含示范)、笔墨焦点、禁忌/不偏好。
|
||||
遣词用户未提段落形态时,默认写入:完整长自然段叙述,不要单句成段(篇幅数字交给「回复呈现」)。
|
||||
3. 情境备用 = 写法档(体裁/调性容器),不是具体桥段。有切换则每条写:名字=档名(如「官能小说」「网文爽文」「展示恐怖」「展示情色」),用法=该档怎么遣词、笔墨落哪、何时切进来。无切换则 `[]`。用户若报出「当众折辱」「独处一室」这类场合,升一档写进对应写法档,场合本身不单列。
|
||||
4. 写推进与决策(可写「无专门推进」);变量只写钩子。
|
||||
4. 情境备用 = 写法档(体裁/调性容器),不是具体桥段。有切换则每条写:名字=档名(如「官能小说」「网文爽文」「展示恐怖」「展示情色」),用法=该档怎么遣词、笔墨落哪、何时切进来。无切换则 `[]`。用户若报出「当众折辱」「独处一室」这类场合,升一档写进对应写法档,场合本身不单列。
|
||||
5. 写推进与决策(可写「无专门推进」);变量只写钩子。
|
||||
「用户输入用法」未提及时:按美学/交互范式选一种写入产物——
|
||||
- 写手/大纲成稿 → **大纲扩写**(输入=大纲→细化成正文;不越界、完整演绎);
|
||||
- 代入扮演、轮转等用户决策 → **扮演停笔**(输入的行为与对话作正文开头并扩写填满;不写用户角色未给出的行为/对话;不替用户角色做决定;停在其他角色等回应之刻)。
|
||||
两种都像则写进追问让用户点一次;不要混成半套规则。
|
||||
5. 自评 + 追问。
|
||||
6. 自评 + 追问。
|
||||
|
||||
summary:`叙事指南与故事推进 · …`
|
||||
```
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
# 用户需求
|
||||
|
||||
> 对话落盘与工序编排共用。程序只切割下方 **fence 块**;`##` 标题仅供人读。
|
||||
> 产物外壳:`docs/context-fragment-design.md`。
|
||||
> 从全局【偏好库】选用硬约束写入本局;不管文风遣词(那是叙事指南)。
|
||||
|
||||
## meta
|
||||
|
||||
```meta
|
||||
name: 用户需求
|
||||
id: user-requirements
|
||||
artifact: 设计.用户需求
|
||||
declaration: >
|
||||
把跨局偏好库里对本局生效的硬约束(禁区/纠偏/默认取向)落成可执行条目;
|
||||
不重做文风问卷,也不钉体验站位
|
||||
when: |
|
||||
提示中有【偏好库 · 可选用】且库非空,或用户主动谈跨局禁区、绝对不要写什么、默认取向要进本局。
|
||||
开局常见:库里已有起步条目且本局尚无「设计.用户需求」。
|
||||
when_not: |
|
||||
本局已有等价「设计.用户需求」且用户未要求改偏好 → 不重复排。
|
||||
只谈文风遣词/示范 → 「叙事指南」(工序编排:「叙事指南与故事推进」)。
|
||||
只谈体验站位/核心感觉 → 「美学纲领与交互范式」。
|
||||
库空且用户未提硬约束 → 不排。
|
||||
boundary: |
|
||||
本技能:一份 context-fragment,写清本局必须遵守的硬约束列表。
|
||||
文风:叙事指南。体验:美学。推进:故事推进。
|
||||
feeds: narrator,gm
|
||||
```
|
||||
|
||||
## opening
|
||||
|
||||
```opening
|
||||
偏好库里已有跨局硬约束时,请确认本局要带哪些(想到什么写什么):
|
||||
|
||||
1. 哪些条目本局生效?有没有要改写、收紧或放宽的?
|
||||
2. 有没有库外、但本局一定要遵守的禁区/纠偏?(一句话即可)
|
||||
```
|
||||
|
||||
## task
|
||||
|
||||
```task
|
||||
你正在执行「用户需求」。产物必须是 **context-fragment.v1** JSON,写入「设计.用户需求」。
|
||||
|
||||
作用对象是本局游玩固定上下文(主世界层与转述都读)。只写硬约束,不写遣词示范。
|
||||
|
||||
执行顺序:
|
||||
1. 若提示中有【偏好库 · 可选用】:默认把 active 条目纳入,再按用户点名增删/改写;勿整库无脑粘贴原文若用户只要子集。
|
||||
2. 用户本局补充的禁区/纠偏:写成可执行短句,与库条并列。
|
||||
3. 自评 + 追问。文风交给叙事指南;体验站位交给美学。
|
||||
|
||||
summary:`用户需求 · …`
|
||||
```
|
||||
|
||||
## principles
|
||||
|
||||
```principles
|
||||
1. 硬约束 ≠ 文风:禁区/取向/纠偏归本步;遣词与示范归叙事指南。
|
||||
2. 条目要短、可执行;写「不要 X,改做 Y」优于空泛「注意分寸」。
|
||||
3. 库空且用户无补充 → 可不排本步;有库则优先落盘,勿装作没有。
|
||||
4. 与本局世界观许可冲突时,以本步产物为准(游玩侧已约定)。
|
||||
```
|
||||
|
||||
## probe
|
||||
|
||||
```probe
|
||||
本步只钉:本局生效的硬约束列表是否够用、有无漏掉用户点名的禁区。
|
||||
|
||||
库条目用户未否决的默认纳入;用户只要子集则只写子集。
|
||||
缺关键禁区且用户话里有信号:在「追问」写 1 点。够用:`追问.题目` 为 `[]`。
|
||||
|
||||
用户谈到文风遣词:写入「开放问题」,交给叙事指南。
|
||||
用户谈到站位/核心感觉:写入「开放问题」,交给美学。
|
||||
```
|
||||
|
||||
## output
|
||||
|
||||
```output
|
||||
{
|
||||
"schema": "context-fragment.v1",
|
||||
"技能": "用户需求",
|
||||
"brief": "一句话:本局硬约束重心",
|
||||
"mount": ["world-simulator", "narrator"],
|
||||
"稳变": "stable",
|
||||
"正文": {
|
||||
"来源说明": "偏好库选用 + 本局补充",
|
||||
"约束": [
|
||||
"短、可执行的一条",
|
||||
"…"
|
||||
]
|
||||
},
|
||||
"自评": {
|
||||
"维度": [
|
||||
{ "名": "可执行", "分数": 8, "说明": "…" },
|
||||
{ "名": "覆盖用户点名", "分数": 8, "说明": "…" }
|
||||
],
|
||||
"薄弱点": ""
|
||||
},
|
||||
"追问": { "导语": "", "题目": [] },
|
||||
"开放问题": []
|
||||
}
|
||||
```
|
||||
|
||||
硬规则:合法 JSON;`约束` 至少 1 条(若用户明确「本局不要任何跨局偏好」则可写一条「本局不套用偏好库」);禁止 order。
|
||||
|
||||
## checklist
|
||||
|
||||
```checklist
|
||||
- [ ] 约束短且可执行?
|
||||
- [ ] 未把文风遣词/体验站位写进本步?
|
||||
- [ ] 用户点名的禁区已覆盖或已追问?
|
||||
```
|
||||
|
||||
## examples
|
||||
|
||||
```examples
|
||||
好:纳入偏好库 NTR/取向两条,并加一条「不要反复报精确数字」。
|
||||
坏:写成叙事遣词示范;或整库粘贴却无视用户「只要取向那条」。
|
||||
```
|
||||
@@ -10,6 +10,10 @@ import { readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
import {
|
||||
collectLibraryIdsFromModules,
|
||||
formatBoundLibrariesForPrompt,
|
||||
} from "../libraries/index.js";
|
||||
import { tryParseJsonDoc } from "../parse/json-doc.js";
|
||||
|
||||
export const CREATION_FLOW_TAG = "设计.创作流程";
|
||||
@@ -211,6 +215,11 @@ export type ModuleCatalogEntry = {
|
||||
* recipe = 只进工序编排 DAG;dictate = 只进对话落盘何时落盘。
|
||||
*/
|
||||
intake?: RecipeIntakeFamily;
|
||||
/**
|
||||
* 可选:本步绑定的全局库 id(如 style-packs)。
|
||||
* design-step 执行时按注册表注入对应块;库空则跳过。
|
||||
*/
|
||||
libraries?: string[];
|
||||
/** 来自 prompt.md ```meta:何时该选用(编排选型) */
|
||||
when?: string;
|
||||
/** 来自 prompt.md ```meta:何时不该选用 */
|
||||
@@ -951,6 +960,7 @@ export function parseModuleCatalog(raw: string): ModuleCatalog | null {
|
||||
const kind = parseModuleNodeKind(m.kind);
|
||||
const params = parseModuleParamSpecs(m.params);
|
||||
const intake = parseModuleIntake(m.intake);
|
||||
const libraries = parseModuleLibraries(m.libraries);
|
||||
modules.push({
|
||||
id,
|
||||
name,
|
||||
@@ -963,6 +973,7 @@ export function parseModuleCatalog(raw: string): ModuleCatalog | null {
|
||||
...(opening ? { opening } : {}),
|
||||
...(params ? { params } : {}),
|
||||
...(intake ? { intake } : {}),
|
||||
...(libraries ? { libraries } : {}),
|
||||
});
|
||||
}
|
||||
if (modules.length === 0) return null;
|
||||
@@ -992,6 +1003,19 @@ function parseModuleParamSpecs(raw: unknown): ModuleParamSpec[] | undefined {
|
||||
return out.length > 0 ? out : undefined;
|
||||
}
|
||||
|
||||
function parseModuleLibraries(raw: unknown): string[] | undefined {
|
||||
if (!Array.isArray(raw) || raw.length === 0) return undefined;
|
||||
const ids: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const item of raw) {
|
||||
const id = typeof item === "string" ? item.trim() : "";
|
||||
if (!id || seen.has(id)) continue;
|
||||
seen.add(id);
|
||||
ids.push(id);
|
||||
}
|
||||
return ids.length ? ids : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 能力 prompt.md 可切割块(fence 语言标签 = 块 id)。
|
||||
* 标准块见 MODULE_SECTION_IDS;程序只认 ```id … ```,不认散文标题 alone。
|
||||
@@ -1373,7 +1397,8 @@ const DICTATE_PROGRAM_SIDE_MODULE_IDS = new Set([
|
||||
* 不写 DAG / design-step 话术;不塞执行全文。
|
||||
*/
|
||||
export function formatModuleCatalogForDictate(catalog: ModuleCatalog): string {
|
||||
const lines = catalogModulesForIntake(catalog, "dictate").map((m) => {
|
||||
const dictateModules = catalogModulesForIntake(catalog, "dictate");
|
||||
const lines = dictateModules.map((m) => {
|
||||
const programSide = DICTATE_PROGRAM_SIDE_MODULE_IDS.has(m.id) || m.auto;
|
||||
const flags = [
|
||||
m.repeatable ? "〔可反复〕" : "",
|
||||
@@ -1393,7 +1418,7 @@ export function formatModuleCatalogForDictate(catalog: ModuleCatalog): string {
|
||||
if (m.boundary) parts.push(` 边界:${indentMultiline(m.boundary, " ")}`);
|
||||
return parts.join("\n");
|
||||
});
|
||||
return [
|
||||
const head = [
|
||||
"【能力 · 何时落盘】",
|
||||
"对照下方「何时用 / 何时不用 / 边界」与已有产物判定:条件成立才 insert(或 declare_*);含糊或未到时机 → 本轮不落该能力,改为短确认或问挡住的 1 点。",
|
||||
"同轮可多次落盘:凡本轮材料已够且「何时用」成立的能力,都可 insert / declare_*(可并行);每个 insert 各回 reply_module,可见回复分段附带。",
|
||||
@@ -1404,6 +1429,10 @@ export function formatModuleCatalogForDictate(catalog: ModuleCatalog): string {
|
||||
"能力执行全文不进聊天;规格只走 toolcall。insert 成功后按返回的 reply_module 写可见回复(见系统提示「对用户可见回复」)。",
|
||||
lines.join("\n"),
|
||||
].join("\n");
|
||||
|
||||
const libIds = collectLibraryIdsFromModules(dictateModules);
|
||||
const libBlock = formatBoundLibrariesForPrompt(libIds).trim();
|
||||
return libBlock ? `${head}\n\n${libBlock}` : head;
|
||||
}
|
||||
|
||||
function dictateLandHint(m: ModuleCatalogEntry): string {
|
||||
|
||||
+27
-2
@@ -559,11 +559,36 @@ export async function loadWorkerSkillWithContext(
|
||||
skill.skillPackRoot,
|
||||
skillsRoot,
|
||||
);
|
||||
const parts: string[] = [];
|
||||
if (shells.length) {
|
||||
rosterBlock = `${formatPresentShellsForPrompt(shells)}\n\n`;
|
||||
parts.push(formatPresentShellsForPrompt(shells));
|
||||
}
|
||||
const {
|
||||
loadMarkdownSafeSubsetCatalog,
|
||||
formatMarkdownSafeSubsetForPrompt,
|
||||
} = await import("./markdown-safe-subset.js");
|
||||
const safeSubset = await loadMarkdownSafeSubsetCatalog(
|
||||
skill.skillPackRoot,
|
||||
skillsRoot,
|
||||
);
|
||||
if (safeSubset) {
|
||||
parts.push(formatMarkdownSafeSubsetForPrompt(safeSubset));
|
||||
}
|
||||
if (parts.length) {
|
||||
rosterBlock = `${parts.join("\n\n")}\n\n`;
|
||||
}
|
||||
}
|
||||
modulePromptBlock = `${rosterBlock}${paramsBlock}\n\n## 【本步方法 · ${binding.module.name}】\n\n${binding.modulePrompt.trim()}${openingNote}${inheritNote}`;
|
||||
let libraryBlock = "";
|
||||
if (binding.module.libraries?.length) {
|
||||
const { formatBoundLibrariesForPrompt } = await import(
|
||||
"../libraries/index.js"
|
||||
);
|
||||
const libText = formatBoundLibrariesForPrompt(
|
||||
binding.module.libraries,
|
||||
).trim();
|
||||
if (libText) libraryBlock = `${libText}\n\n`;
|
||||
}
|
||||
modulePromptBlock = `${rosterBlock}${libraryBlock}${paramsBlock}\n\n## 【本步方法 · ${binding.module.name}】\n\n${binding.modulePrompt.trim()}${openingNote}${inheritNote}`;
|
||||
const depTags = inheritTag
|
||||
? binding.depTags.filter((tag) => tag !== inheritTag)
|
||||
: binding.depTags;
|
||||
|
||||
@@ -499,6 +499,16 @@ describe("creation-flow", () => {
|
||||
expect(catalog?.modules.find((m) => m.name === "故事推进")?.intake).toBe(
|
||||
"dictate",
|
||||
);
|
||||
expect(catalog?.modules.find((m) => m.name === "用户需求")?.libraries).toEqual([
|
||||
"preferences",
|
||||
]);
|
||||
expect(
|
||||
catalog?.modules.find((m) => m.name === "叙事指南")?.libraries,
|
||||
).toEqual(["style-packs"]);
|
||||
expect(
|
||||
catalog?.modules.find((m) => m.name === "叙事指南与故事推进")?.libraries,
|
||||
).toEqual(["style-packs"]);
|
||||
expect(dictateBlock).toContain("【偏好库 · 可选用】");
|
||||
expect(
|
||||
catalogModulesForIntake(catalog!, "recipe").some(
|
||||
(m) => m.name === "叙事指南",
|
||||
|
||||
Reference in New Issue
Block a user