novel-writer-cli 0.2.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/agents/chapter-writer.md +69 -29
- package/agents/character-weaver.md +7 -1
- package/agents/plot-architect.md +20 -7
- package/agents/quality-judge.md +239 -15
- package/agents/style-analyzer.md +14 -8
- package/agents/style-refiner.md +48 -25
- package/agents/world-builder.md +8 -1
- package/dist/__tests__/agent-prompts-anti-ai-upgrade.test.js +311 -0
- package/dist/__tests__/agent-prompts-platform-expansion.test.js +33 -0
- package/dist/__tests__/anti-ai-infrastructure.test.js +548 -0
- package/dist/__tests__/anti-ai-templates.test.js +156 -0
- package/dist/__tests__/canon-status-lifecycle.test.js +481 -0
- package/dist/__tests__/commit-gate-decision.test.js +65 -0
- package/dist/__tests__/commit-prototype-pollution.test.js +1 -1
- package/dist/__tests__/excitement-type-annotation.test.js +240 -0
- package/dist/__tests__/excitement-type.test.js +21 -0
- package/dist/__tests__/gate-decision.test.js +62 -15
- package/dist/__tests__/genre-excitement-mapping.test.js +355 -0
- package/dist/__tests__/golden-chapter-gates.test.js +79 -0
- package/dist/__tests__/golden-chapter-mini-planning.test.js +485 -0
- package/dist/__tests__/helpers/quickstart-mini-planning.js +61 -0
- package/dist/__tests__/init.test.js +57 -5
- package/dist/__tests__/instructions-platform-expansion.test.js +125 -0
- package/dist/__tests__/next-step-gate-decision-routing.test.js +98 -0
- package/dist/__tests__/orchestrator-state-write-path.test.js +1 -1
- package/dist/__tests__/platform-profile.test.js +57 -1
- package/dist/__tests__/quickstart-pipeline.test.js +73 -6
- package/dist/__tests__/scoring-weights.test.js +193 -0
- package/dist/__tests__/steps-id.test.js +2 -0
- package/dist/__tests__/validate-quickstart-prereqs.test.js +2 -0
- package/dist/advance.js +27 -2
- package/dist/anti-ai-context.js +535 -0
- package/dist/cli.js +3 -1
- package/dist/commit.js +22 -0
- package/dist/excitement-type.js +12 -0
- package/dist/gate-decision.js +98 -2
- package/dist/golden-chapter-gates.js +143 -0
- package/dist/init.js +76 -7
- package/dist/instructions.js +552 -6
- package/dist/next-step.js +124 -88
- package/dist/platform-profile.js +20 -8
- package/dist/quickstart-mini-planning.js +30 -0
- package/dist/scoring-weights.js +38 -3
- package/dist/steps.js +1 -1
- package/dist/validate.js +293 -214
- package/dist/volume-commit.js +271 -5
- package/dist/volume-planning.js +78 -3
- package/docs/user/README.md +1 -0
- package/docs/user/migration-guide.md +166 -0
- package/docs/user/novel-cli.md +4 -3
- package/docs/user/quick-start.md +354 -57
- package/package.json +1 -1
- package/schemas/platform-profile.schema.json +2 -2
- package/scripts/lint-blacklist.sh +221 -76
- package/scripts/lint-structural.sh +538 -0
- package/skills/continue/SKILL.md +6 -0
- package/skills/continue/references/context-contracts.md +71 -6
- package/skills/continue/references/periodic-maintenance.md +12 -1
- package/skills/novel-writing/references/quality-rubric.md +79 -26
- package/skills/novel-writing/references/style-guide.md +416 -28
- package/skills/start/SKILL.md +23 -3
- package/skills/start/references/vol-planning.md +12 -3
- package/templates/ai-blacklist.json +1275 -54
- package/templates/ai-sentence-patterns.json +167 -0
- package/templates/brief-template.md +5 -0
- package/templates/genre-excitement-map.json +48 -0
- package/templates/genre-golden-standards.json +80 -0
- package/templates/genre-weight-profiles.json +15 -0
- package/templates/golden-chapter-gates.json +230 -0
- package/templates/novel-ask/example.question.json +3 -2
- package/templates/platform-profile.json +141 -1
- package/templates/platforms/fanqie.md +35 -0
- package/templates/platforms/jinjiang.md +35 -0
- package/templates/platforms/qidian.md +35 -0
- package/templates/style-profile-template.json +18 -1
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
7
|
+
function repoPath(relPath) {
|
|
8
|
+
return join(repoRoot, relPath);
|
|
9
|
+
}
|
|
10
|
+
async function readText(relPath) {
|
|
11
|
+
return readFile(repoPath(relPath), "utf8");
|
|
12
|
+
}
|
|
13
|
+
test("chapter-writer prompt removes quota language and includes C16-C24 + Phase 2 checks", async () => {
|
|
14
|
+
const prompt = await readText("agents/chapter-writer.md");
|
|
15
|
+
for (const legacy of ["每角色至少 1 个口头禅", "每章至少 1 处"]) {
|
|
16
|
+
assert.equal(prompt.includes(legacy), false, `chapter-writer must remove legacy quota phrase: ${legacy}`);
|
|
17
|
+
}
|
|
18
|
+
const c11 = prompt.match(/11\.\s\*\*角色语癖(C11)\*\*:([^\n]+)/)?.[1] ?? "";
|
|
19
|
+
const c12 = prompt.match(/12\.\s\*\*反直觉细节(C12)\*\*:([^\n]+)/)?.[1] ?? "";
|
|
20
|
+
const c18 = prompt.match(/18\.\s\*\*人性化技法抽样(C18)\*\*:([^\n]+)/)?.[1] ?? "";
|
|
21
|
+
for (const [label, text] of [
|
|
22
|
+
["C11", c11],
|
|
23
|
+
["C12", c12],
|
|
24
|
+
["C18", c18]
|
|
25
|
+
]) {
|
|
26
|
+
assert.ok(text.length > 0, `${label} text must be present`);
|
|
27
|
+
assert.doesNotMatch(text, /每章.*\d|至少.*\d|≥\d|\d-\d 次/, `${label} must not reintroduce fixed quotas`);
|
|
28
|
+
}
|
|
29
|
+
for (const required of [
|
|
30
|
+
"角色语癖(C11)",
|
|
31
|
+
"反直觉细节(C12)",
|
|
32
|
+
"句长方差(C16)",
|
|
33
|
+
"叙述连接词零容忍(C17)",
|
|
34
|
+
"人性化技法抽样(C18)",
|
|
35
|
+
"对话意图约束(C19)",
|
|
36
|
+
"结构密度约束(C20)",
|
|
37
|
+
"内心活动锚点(C23)",
|
|
38
|
+
"结构呼吸感(C24,建议性约束)",
|
|
39
|
+
"6.5 **叙述连接词清扫**",
|
|
40
|
+
"6.6 **修饰词去重**",
|
|
41
|
+
"6.7 **四字词组密度检查**",
|
|
42
|
+
"6.8 **内心活动锚点检查**",
|
|
43
|
+
"6.9 **结构呼吸感检查**",
|
|
44
|
+
"按 `C24` 回看功能性停留的分布与预算",
|
|
45
|
+
"前后 2-3 句内出现至少一处合法内心活动",
|
|
46
|
+
"连续 5 句纯动作记录流",
|
|
47
|
+
"SP-07 式情绪标签句",
|
|
48
|
+
"一旦触发,就必须补任一合法锚点",
|
|
49
|
+
"角色感知或内心锚点",
|
|
50
|
+
"去掉标签后仍能大致分辨说话人",
|
|
51
|
+
"8-18 的人类常见波动控制",
|
|
52
|
+
"3 句及以上连续句长都落在 ±5 字内",
|
|
53
|
+
"中文引号内的角色对白可以按人物口吻保留",
|
|
54
|
+
"我认为",
|
|
55
|
+
"我觉得我们应该",
|
|
56
|
+
"当一段对话超过 5 个来回时,允许 1-2 句不直接服务冲突推进的“废话”",
|
|
57
|
+
"这是**上限不是目标**",
|
|
58
|
+
"短暂避锋式缓冲",
|
|
59
|
+
"功能性停留总量不超过章节正文字数的 **≤10%**",
|
|
60
|
+
"若预算与频率冲突,优先缩短或减少停留,不要突破 10% 上限",
|
|
61
|
+
"删掉后本章主线因果链仍然成立",
|
|
62
|
+
"高压段之后最好仍留 1-2 句过渡",
|
|
63
|
+
"若是连续高压章节不适合明显停留,也至少检查段尾是否留出 1-2 句过渡",
|
|
64
|
+
"详见 `style-guide §2.14`",
|
|
65
|
+
"提供“该在哪里放慢”的结构位置",
|
|
66
|
+
"功能性停留中的环境闲描仍受 `C13` 的 2 句限制",
|
|
67
|
+
"**结构呼吸感(C24,建议性约束)**",
|
|
68
|
+
"**约束优先级**"
|
|
69
|
+
]) {
|
|
70
|
+
assert.match(prompt, new RegExp(required.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
|
71
|
+
}
|
|
72
|
+
assert.equal(prompt.includes("至少 1 句"), false, "C16 should avoid quota-like phrasing such as '至少 1 句'");
|
|
73
|
+
});
|
|
74
|
+
test("style-refiner prompt follows four-step flow and brief-first genre override", async () => {
|
|
75
|
+
const prompt = await readText("agents/style-refiner.md");
|
|
76
|
+
for (const required of [
|
|
77
|
+
"Step 1:黑名单扫描",
|
|
78
|
+
"Step 2:结构规则检查",
|
|
79
|
+
"Step 3:抽象→具体转换",
|
|
80
|
+
"Step 4:节奏朗读测试",
|
|
81
|
+
"template_sentence",
|
|
82
|
+
"adjective_density",
|
|
83
|
+
"idiom_density",
|
|
84
|
+
"dialogue_intent",
|
|
85
|
+
"paragraph_structure",
|
|
86
|
+
"punctuation_rhythm",
|
|
87
|
+
"replacement_hint",
|
|
88
|
+
"paths.project_brief",
|
|
89
|
+
"类型覆写",
|
|
90
|
+
"快速检查模式",
|
|
91
|
+
"四字词组连用",
|
|
92
|
+
"情绪直述",
|
|
93
|
+
"微微系列",
|
|
94
|
+
"缓缓系列",
|
|
95
|
+
"标点过度",
|
|
96
|
+
"读取文件并建立锚点",
|
|
97
|
+
"结构规则优先",
|
|
98
|
+
"只有在入口 Skill 或 user 明确要求 quick-check / 时间受限时才启用",
|
|
99
|
+
"再回退到 brief 的题材字段",
|
|
100
|
+
"纯动作流超长检测",
|
|
101
|
+
"连续 5+ 句只有外显动作 / 对话记录",
|
|
102
|
+
"没有合法内心活动(感官侵入 / 碎片思绪 / 生理反应 / 思维中断 / 自我纠正)",
|
|
103
|
+
"插入 1-2 句最小必要的感知片段",
|
|
104
|
+
"结构呼吸感检测",
|
|
105
|
+
"高压段之间没有 1-2 句过渡",
|
|
106
|
+
"已有段落缝隙内补 1 句最小感官 / 环境过渡",
|
|
107
|
+
"若要解决问题必须新增完整功能性停留段",
|
|
108
|
+
"留给 ChapterWriter / QualityJudge 处理",
|
|
109
|
+
"结构呼吸感最小修补",
|
|
110
|
+
"停止在本层扩写",
|
|
111
|
+
"累计修改量仍需 ≤15%"
|
|
112
|
+
]) {
|
|
113
|
+
assert.match(prompt, new RegExp(required.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
|
114
|
+
}
|
|
115
|
+
assert.match(prompt, /changes\[\]\.reason.*blacklist.*structural_rule.*abstract_to_concrete.*rhythm_test.*style_match/s);
|
|
116
|
+
});
|
|
117
|
+
test("quality-judge prompt outputs new anti_ai fields and 7-indicator compatibility mode", async () => {
|
|
118
|
+
const prompt = await readText("agents/quality-judge.md");
|
|
119
|
+
for (const required of [
|
|
120
|
+
"\"indicator_mode\": \"13-indicator | 7-indicator | 4-indicator-compat\"",
|
|
121
|
+
"\"indicator_breakdown\"",
|
|
122
|
+
"\"statistical_profile\"",
|
|
123
|
+
"\"detected_humanize_techniques\"",
|
|
124
|
+
"\"structural_rule_violations\"",
|
|
125
|
+
"\"vocabulary_richness_estimate\"",
|
|
126
|
+
"blacklist_hit_rate",
|
|
127
|
+
"sentence_repetition_rate",
|
|
128
|
+
"sentence_length_std_dev",
|
|
129
|
+
"paragraph_length_cv",
|
|
130
|
+
"vocabulary_diversity_score",
|
|
131
|
+
"narration_connector_count",
|
|
132
|
+
"humanize_technique_variety",
|
|
133
|
+
"0 = green;1 个孤立命中 = yellow",
|
|
134
|
+
"≥2 个或连续多段靠连接词推进 = red",
|
|
135
|
+
"indicator_mode: \"4-indicator-compat\"",
|
|
136
|
+
"\"severity\": \"yellow\"",
|
|
137
|
+
"\"evidence\": \"原文片段\"",
|
|
138
|
+
"\"detected_humanize_techniques\": [\"thought_interrupt\", \"mundane_detail\"]",
|
|
139
|
+
"legacy / 轻量消费者读取",
|
|
140
|
+
"关键节点缺失",
|
|
141
|
+
"扣 **0.5 分/处**",
|
|
142
|
+
"纯动作流过长",
|
|
143
|
+
"额外扣 **1 分**",
|
|
144
|
+
"scores.emotional_impact.reason",
|
|
145
|
+
"不要只写抽象评价",
|
|
146
|
+
"不要另起第 14 个独立评分维度",
|
|
147
|
+
"思维中断、自我纠正都属于合法内心活动"
|
|
148
|
+
]) {
|
|
149
|
+
assert.match(prompt, new RegExp(required.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
|
150
|
+
}
|
|
151
|
+
assert.match(prompt, /manifest\.inline\.statistical_profile/);
|
|
152
|
+
assert.match(prompt, /manifest\.inline\.structural_rule_violations/);
|
|
153
|
+
assert.match(prompt, /deterministic 观测值/);
|
|
154
|
+
});
|
|
155
|
+
test("issue 177 structural breathing docs stay aligned across prompts and rubric", async () => {
|
|
156
|
+
const chapterWriter = await readText("agents/chapter-writer.md");
|
|
157
|
+
const styleRefiner = await readText("agents/style-refiner.md");
|
|
158
|
+
const qualityJudge = await readText("agents/quality-judge.md");
|
|
159
|
+
const styleGuide = await readText("skills/novel-writing/references/style-guide.md");
|
|
160
|
+
const qualityRubric = await readText("skills/novel-writing/references/quality-rubric.md");
|
|
161
|
+
const tasks = await readText("openspec/changes/m12-structural-breathing/tasks.md");
|
|
162
|
+
for (const required of [
|
|
163
|
+
"结构呼吸感(C24,建议性约束)",
|
|
164
|
+
"功能性停留",
|
|
165
|
+
"环境闲描、角色闲聊、感官片段、回忆碎片或生活细节",
|
|
166
|
+
"章节**正文字数**达到 1000 字以上时",
|
|
167
|
+
"功能性停留总量不超过章节正文字数的 **≤10%**",
|
|
168
|
+
"高压场景后是否留出 1-2 句过渡",
|
|
169
|
+
"按 `C24` 回看功能性停留的分布与预算",
|
|
170
|
+
"“任务执行”式推进",
|
|
171
|
+
"通常可按每 1000-1500 字一处的软建议安排更短、更轻的“功能性停留”",
|
|
172
|
+
"`C12` / `C18`",
|
|
173
|
+
"环境闲描仍受 `C13` 的 2 句限制",
|
|
174
|
+
"对话闲笔仍要满足 `C19` 的合法意图"
|
|
175
|
+
]) {
|
|
176
|
+
assert.ok(chapterWriter.includes(required), `chapter-writer must mention: ${required}`);
|
|
177
|
+
}
|
|
178
|
+
for (const required of [
|
|
179
|
+
"结构呼吸感检测",
|
|
180
|
+
"留给 ChapterWriter / QualityJudge 处理",
|
|
181
|
+
"已有段落缝隙内补 1 句最小感官 / 环境过渡",
|
|
182
|
+
"结构呼吸感最小修补",
|
|
183
|
+
"停止在本层扩写"
|
|
184
|
+
]) {
|
|
185
|
+
assert.ok(styleRefiner.includes(required), `style-refiner must mention: ${required}`);
|
|
186
|
+
}
|
|
187
|
+
for (const required of [
|
|
188
|
+
"结构过密,缺乏呼吸感",
|
|
189
|
+
"建议性扣 **0.5 分**",
|
|
190
|
+
"yellow / suggestion",
|
|
191
|
+
"高压场景间缺乏过渡,沉浸感断裂",
|
|
192
|
+
"不要仅凭这一项触发 `revise` / `review` / `rewrite`",
|
|
193
|
+
"哪怕只用 1-2 句呼吸段"
|
|
194
|
+
]) {
|
|
195
|
+
assert.ok(qualityJudge.includes(required), `quality-judge must mention: ${required}`);
|
|
196
|
+
}
|
|
197
|
+
for (const required of [
|
|
198
|
+
"### 2.14 结构呼吸感",
|
|
199
|
+
"信息效率过高",
|
|
200
|
+
"功能性停留 = 不直接服务于主线推进",
|
|
201
|
+
"删掉该片段后,本章主线因果链仍然成立",
|
|
202
|
+
"章节**正文字数**达到 **1000 字以上** 时",
|
|
203
|
+
"若预算与频率冲突,以 `≤10%` 上限为准",
|
|
204
|
+
"`C13` 约束,单次环境闲描 **≤2 句**",
|
|
205
|
+
"`C19` 的敷衍 / 短暂避锋式缓冲 / 转移等合法意图",
|
|
206
|
+
"StyleRefiner 阶段硬补,应回到 ChapterWriter / QualityJudge 处理",
|
|
207
|
+
"结构呼吸感最小修补",
|
|
208
|
+
"1-2 句是上限,不是目标",
|
|
209
|
+
"`C12 反直觉细节`",
|
|
210
|
+
"`C18 人性化技法`",
|
|
211
|
+
"**坏例子(信息效率过高)**",
|
|
212
|
+
"**好例子(有结构呼吸感)**"
|
|
213
|
+
]) {
|
|
214
|
+
assert.ok(styleGuide.includes(required), `style-guide must mention: ${required}`);
|
|
215
|
+
}
|
|
216
|
+
for (const required of [
|
|
217
|
+
"- [x] **T1.1**",
|
|
218
|
+
"- [x] **T1.2**",
|
|
219
|
+
"- [x] **T1.3**",
|
|
220
|
+
"- [x] **T2.1**",
|
|
221
|
+
"- [x] **T3.1**",
|
|
222
|
+
"- [x] **T3.2**",
|
|
223
|
+
"- [x] **T3.3**",
|
|
224
|
+
"- [x] **T4.1**",
|
|
225
|
+
"- [x] **T4.2**",
|
|
226
|
+
"- [x] **T4.3**",
|
|
227
|
+
"- [x] **T4.4**",
|
|
228
|
+
"每 1000-1500 字至少一处",
|
|
229
|
+
"留出足够功能性停留"
|
|
230
|
+
]) {
|
|
231
|
+
assert.ok(tasks.includes(required), `tasks must mention: ${required}`);
|
|
232
|
+
}
|
|
233
|
+
for (const required of [
|
|
234
|
+
"是否具备必要的结构呼吸感",
|
|
235
|
+
"高压段之间是否给读者保留了必要的消化空间",
|
|
236
|
+
"文笔流畅",
|
|
237
|
+
"结构过密,缺乏呼吸感",
|
|
238
|
+
"yellow / suggestion",
|
|
239
|
+
"功能性停留过多导致拖沓",
|
|
240
|
+
"高压场景间缺乏过渡,沉浸感断裂",
|
|
241
|
+
"不单独触发修订"
|
|
242
|
+
]) {
|
|
243
|
+
assert.ok(qualityRubric.includes(required), `quality-rubric must mention: ${required}`);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
test("issue 176 inner-activity docs stay aligned across style-guide and rubric", async () => {
|
|
247
|
+
const chapterWriter = await readText("agents/chapter-writer.md");
|
|
248
|
+
const qualityJudge = await readText("agents/quality-judge.md");
|
|
249
|
+
const styleGuide = await readText("skills/novel-writing/references/style-guide.md");
|
|
250
|
+
const qualityRubric = await readText("skills/novel-writing/references/quality-rubric.md");
|
|
251
|
+
for (const required of [
|
|
252
|
+
"前后 2-3 句内出现至少一处合法内心活动",
|
|
253
|
+
"第 6 句必须补一处最小必要的角色感知或内心锚点"
|
|
254
|
+
]) {
|
|
255
|
+
assert.ok(chapterWriter.includes(required), `chapter-writer must mention: ${required}`);
|
|
256
|
+
}
|
|
257
|
+
for (const required of [
|
|
258
|
+
"ChapterWriter 的生成目标是关键节点前后 2-3 句内尽早落锚",
|
|
259
|
+
"每个应触发却缺失锚点的关键节点",
|
|
260
|
+
"scores.emotional_impact.score` 最低为 **1 分**"
|
|
261
|
+
]) {
|
|
262
|
+
assert.ok(qualityJudge.includes(required), `quality-judge must mention: ${required}`);
|
|
263
|
+
}
|
|
264
|
+
for (const required of [
|
|
265
|
+
"C23 内心活动锚点",
|
|
266
|
+
"纯动作记录流",
|
|
267
|
+
"SP-07",
|
|
268
|
+
"感官侵入",
|
|
269
|
+
"碎片思绪",
|
|
270
|
+
"生理反应",
|
|
271
|
+
"思维中断",
|
|
272
|
+
"自我纠正",
|
|
273
|
+
"节点前后 **2-3 句**",
|
|
274
|
+
"QualityJudge 以“前后 3 句仍为空”作为扣分阈值",
|
|
275
|
+
"连续 **≤5 句**",
|
|
276
|
+
"合法内心活动",
|
|
277
|
+
"非法情绪标签",
|
|
278
|
+
"**边界案例**",
|
|
279
|
+
"**应触发**",
|
|
280
|
+
"**不应触发**",
|
|
281
|
+
"高速场景写法"
|
|
282
|
+
]) {
|
|
283
|
+
assert.ok(styleGuide.includes(required), `style-guide must mention: ${required}`);
|
|
284
|
+
}
|
|
285
|
+
for (const required of [
|
|
286
|
+
"内心活动锚点",
|
|
287
|
+
"关键决策 / 重大信息 / 高压节点前后 3 句无内心活动",
|
|
288
|
+
"至少扣 **0.5 分/处**",
|
|
289
|
+
"每个应触发却缺失锚点的关键节点",
|
|
290
|
+
"连续 5 句纯动作流",
|
|
291
|
+
"额外扣 **1 分**",
|
|
292
|
+
"最低只降到 **1 分**",
|
|
293
|
+
"生理反应、感官侵入、碎片思绪、思维中断、自我纠正都属于合法内心活动"
|
|
294
|
+
]) {
|
|
295
|
+
assert.ok(qualityRubric.includes(required), `quality-rubric must mention: ${required}`);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
test("issue 138 OpenSpec artifacts include style-refiner spec and no stale concept.md reference", async () => {
|
|
299
|
+
const tasks = await readText("openspec/changes/m9-anti-ai-agent-prompts/tasks.md");
|
|
300
|
+
const design = await readText("openspec/changes/m9-anti-ai-agent-prompts/design.md");
|
|
301
|
+
const styleRefinerSpec = await readText("openspec/changes/m9-anti-ai-agent-prompts/specs/style-refiner-upgrade/spec.md");
|
|
302
|
+
const qualityJudgeSpec = await readText("openspec/changes/m9-anti-ai-agent-prompts/specs/quality-judge-upgrade/spec.md");
|
|
303
|
+
const chapterWriterSpec = await readText("openspec/changes/m9-anti-ai-agent-prompts/specs/chapter-writer-upgrade/spec.md");
|
|
304
|
+
assert.equal(tasks.includes("concept.md"), false, "tasks.md must use brief-based type override wording");
|
|
305
|
+
assert.equal(design.includes("不修改 StyleRefiner"), false, "design.md must not contradict StyleRefiner scope");
|
|
306
|
+
assert.equal(qualityJudgeSpec.includes("lint values override QJ estimates"), false, "quality-judge spec must not promise unsupported statistical override inputs");
|
|
307
|
+
assert.equal(chapterWriterSpec.includes("≥ the style-profile value"), false, "chapter-writer spec must not overstate C16 as a hard lower bound");
|
|
308
|
+
assert.match(styleRefinerSpec, /Step 1 through Step 4 appear in order/);
|
|
309
|
+
assert.match(qualityJudgeSpec, /structural_rule_violations/);
|
|
310
|
+
assert.match(chapterWriterSpec, /ChapterWriter SHALL enforce dialogue-intent constraints/);
|
|
311
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
7
|
+
async function readText(relPath) {
|
|
8
|
+
return readFile(join(repoRoot, relPath), "utf8");
|
|
9
|
+
}
|
|
10
|
+
test("chapter-writer prompt accepts platform writing guide", async () => {
|
|
11
|
+
const prompt = await readText("agents/chapter-writer.md");
|
|
12
|
+
assert.match(prompt, /paths\.platform_writing_guide/);
|
|
13
|
+
assert.match(prompt, /paths\.style_guide/);
|
|
14
|
+
assert.doesNotMatch(prompt, /paths\.writing_methodology/);
|
|
15
|
+
assert.match(prompt, /平台节奏密度、对话比例、钩子、情绪回报周期与文风要求/);
|
|
16
|
+
});
|
|
17
|
+
test("quality-judge prompt defines golden chapter gates track and forced revise semantics", async () => {
|
|
18
|
+
const prompt = await readText("agents/quality-judge.md");
|
|
19
|
+
assert.match(prompt, /Track 3: Golden Chapter Gates/);
|
|
20
|
+
assert.match(prompt, /golden_chapter_gates/);
|
|
21
|
+
assert.match(prompt, /recommendation.*必须.*revise|recommendation = "revise"/s);
|
|
22
|
+
assert.match(prompt, /failed_gate_ids/);
|
|
23
|
+
});
|
|
24
|
+
test("start and continue skills document hidden tomato alias and pass-through packet fields", async () => {
|
|
25
|
+
const start = await readText("skills/start/SKILL.md");
|
|
26
|
+
const cont = await readText("skills/continue/SKILL.md");
|
|
27
|
+
assert.match(start, /fanqie \(番茄\)/);
|
|
28
|
+
assert.match(start, /jinjiang \(晋江\)/);
|
|
29
|
+
assert.equal(start.includes("\n- `tomato`\n"), false);
|
|
30
|
+
assert.match(start, /手动填 `tomato`/);
|
|
31
|
+
assert.match(cont, /platform_writing_guide/);
|
|
32
|
+
assert.match(cont, /golden_chapter_gates/);
|
|
33
|
+
});
|