@cloud411716/fancy-webnovel 1.0.45 → 2.2.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/client.js +183 -0
- package/cordis.patch.yml +0 -14
- package/index.js +21 -37
- package/lib/chinese-num.js +111 -0
- package/lib/deployed-state.js +141 -0
- package/lib/error-codes.js +104 -0
- package/lib/infra.js +569 -0
- package/lib/llm-wiki.js +109 -0
- package/lib/safe-rm.js +94 -0
- package/lib/scraper-utils.js +165 -0
- package/package.json +37 -5
- package/plugins/fancy-analyze/command-input.js +65 -0
- package/plugins/fancy-analyze/index.js +973 -0
- package/plugins/fancy-analyze/schema-analyze.md +379 -0
- package/plugins/fancy-analyze/schema-write.md +178 -0
- package/plugins/fancy-analyze/scripts/split_source.js +292 -0
- package/plugins/fancy-analyze/summary-progress.js +431 -0
- package/plugins/fancy-analyze/templates.js +881 -0
- package/plugins/fancy-bootstrap/index.js +168 -0
- package/plugins/fancy-bootstrap/scripts/init.js +143 -0
- package/plugins/fancy-bootstrap/scripts/path-utils.js +21 -0
- package/plugins/fancy-bootstrap/templates.js +54 -0
- package/plugins/fancy-import/import-core.js +1118 -0
- package/plugins/fancy-import/import-driver.js +581 -0
- package/plugins/fancy-import/index.js +730 -0
- package/plugins/fancy-import/prompts/01-import-prompt.md +106 -0
- package/plugins/fancy-import/templates.js +49 -0
- package/plugins/fancy-scan/index.js +319 -0
- package/plugins/fancy-scan/scripts/run-scan.js +520 -0
- package/plugins/fancy-scan/scripts/scrapers/fanqie-rank-scraper.js +794 -0
- package/plugins/fancy-scan/scripts/scrapers/jjwxc-rank-scraper.js +339 -0
- package/plugins/fancy-scan/scripts/scrapers/qimao-rank-scraper.js +611 -0
- package/plugins/fancy-scan/templates.js +295 -0
- package/plugins/fancy-story-builder/deploy-manifest.js +239 -0
- package/plugins/fancy-story-builder/deslop-driver.js +821 -0
- package/plugins/fancy-story-builder/flesh-output.js +155 -0
- package/plugins/fancy-story-builder/index.js +3152 -0
- package/plugins/fancy-story-builder/prompts/01-skeleton-prompt.md +553 -0
- package/plugins/fancy-story-builder/prompts/02-flesh-prompt.md +540 -0
- package/plugins/fancy-story-builder/prompts/03-rhythm-prompt.md +504 -0
- package/plugins/fancy-story-builder/prompts/04-writing-prompt.md +188 -0
- package/plugins/fancy-story-builder/prompts/05-deslop-prompt.md +96 -0
- package/plugins/fancy-story-builder/purpose-template.md +107 -0
- package/plugins/fancy-story-builder/references/formulas-rhythm.json +302 -0
- package/plugins/fancy-story-builder/references/formulas-skeleton.json +229 -0
- package/plugins/fancy-story-builder/references/glossary.md +585 -0
- package/plugins/fancy-story-builder/references/index.md +490 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/00-index.md +174 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/01-scene.md +150 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/02-chapter-workflow.md +99 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/03-opening-chapter.md +104 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/04-dialogue-mastery.md +274 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/05-genre-formulas.md +432 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/06-tone-ensemble.md +259 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/07-ledger-audit.md +222 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/08-diagnosis-revision.md +205 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/09-reading-power.md +135 -0
- package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/10-deslop.md +129 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/00-index.md +98 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/01-upgrade-rhythm.md +300 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/02-arc-rhythm.md +168 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/03-highpoint-hooks.md +189 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/04-satisfaction.md +226 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/05-foreshadow-density.md +195 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/06-chapter-outline.md +226 -0
- package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/07-audit.md +163 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/00-index.md +100 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/01-character.md +239 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/02-worldbuilding.md +245 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/03-relations.md +181 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/04-emotion-methods.md +286 -0
- package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/05-audit.md +177 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/00-index.md +91 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/01-positioning.md +208 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/02-packaging.md +135 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/03-entities.md +305 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/04-conflict.md +328 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/05-quota.md +187 -0
- package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/06-audit.md +142 -0
- package/plugins/fancy-story-builder/rhythm-output.js +140 -0
- package/plugins/fancy-story-builder/schema-setting.md +324 -0
- package/plugins/fancy-story-builder/scripts/ai-patterns.js +619 -0
- package/plugins/fancy-story-builder/scripts/check-guide-compliance.js +289 -0
- package/plugins/fancy-story-builder/scripts/content-quality.js +413 -0
- package/plugins/fancy-story-builder/scripts/deslop-validate.js +143 -0
- package/plugins/fancy-story-builder/scripts/error-classes.js +112 -0
- package/plugins/fancy-story-builder/scripts/flesh-validate.js +1189 -0
- package/plugins/fancy-story-builder/scripts/formula-engine.js +112 -0
- package/plugins/fancy-story-builder/scripts/graph-utils.js +1385 -0
- package/plugins/fancy-story-builder/scripts/label-coverage.js +171 -0
- package/plugins/fancy-story-builder/scripts/prompt-drift-check.js +441 -0
- package/plugins/fancy-story-builder/scripts/reading-power.js +298 -0
- package/plugins/fancy-story-builder/scripts/rhythm-validate.js +1216 -0
- package/plugins/fancy-story-builder/scripts/semantic-lint.js +377 -0
- package/plugins/fancy-story-builder/scripts/skeleton-quota.js +145 -0
- package/plugins/fancy-story-builder/scripts/skeleton-validate.js +1832 -0
- package/plugins/fancy-story-builder/scripts/stage-batches.js +1371 -0
- package/plugins/fancy-story-builder/scripts/stage-contract.js +417 -0
- package/plugins/fancy-story-builder/scripts/upstream-query.js +198 -0
- package/plugins/fancy-story-builder/scripts/writing-rules.js +167 -0
- package/plugins/fancy-story-builder/scripts/writing-validate.js +682 -0
- package/plugins/fancy-story-builder/skeleton-output.js +199 -0
- package/plugins/fancy-story-builder/stage-driver.js +1049 -0
- package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//346/227/266/345/272/217/346/230/240/345/260/204/346/250/241/346/235/277.json +204 -0
- package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//347/253/240/350/212/202/347/273/206/347/272/262/346/250/241/346/235/277.json +34 -0
- package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//347/253/240/350/212/202/350/247/204/345/210/222/346/250/241/346/235/277.json +24 -0
- package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//350/212/202/345/245/217/345/217/202/346/225/260/346/250/241/346/235/277.json +27 -0
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//344/270/226/347/225/214/350/247/202/350/247/204/345/210/231/346/250/241/346/235/277.json +66 -0
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/205/263/351/224/256/351/201/223/345/205/267/346/250/241/346/235/277.json +76 -0
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/206/262/347/252/201/346/250/241/346/235/277.json +72 -0
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/211/247/346/203/205/346/250/241/346/235/277.json +37 -0
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/212/277/345/212/233/346/250/241/346/235/277.json +53 -0
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/234/260/347/202/271/346/250/241/346/235/277.json +40 -0
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//347/273/223/345/261/200/346/250/241/346/235/277.json +79 -0
- package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//350/247/222/350/211/262/346/250/241/346/235/277.json +132 -0
- package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/205/263/347/263/273/346/250/241/346/235/277.json +133 -0
- package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/256/236/344/275/223/346/250/241/346/235/277.json +160 -0
- package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/261/202/347/272/247/346/250/241/346/235/277.json +65 -0
- package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//346/241/206/346/236/266/346/250/241/346/235/277.json +134 -0
- package/plugins/fancy-story-builder/templates.js +1248 -0
- package/plugins/fancy-story-builder/wiki-references.js +204 -0
- package/plugins/fancy-story-builder/writing-driver.js +1061 -0
- package/plugins/fancy-topic/index.js +704 -0
- package/plugins/fancy-topic/scripts/select-topic.js +274 -0
- package/plugins/fancy-topic/templates.js +373 -0
- package/commands/bootstrap/index.js +0 -160
- package/commands/bootstrap/init-project.js +0 -121
- package/commands/scan/index.js +0 -275
- package/commands/scan/platforms/fanqie.js +0 -146
- package/commands/scan/platforms/jinjiang.js +0 -143
- package/commands/scan/platforms/qidian.js +0 -102
- package/commands/scan/platforms/qimao.js +0 -127
- package/commands/scan/scraper.js +0 -195
- package/infra.js +0 -160
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ai-patterns.js —— 24 类 AI 写作模式的唯一真源
|
|
3
|
+
*
|
|
4
|
+
* 这套规则原先散在三处,而且**互相漂移**:
|
|
5
|
+
* 旧 fancy-deslop/references/ai-patterns-24.md —— 声称 24 类,列了 24 条(4 组 × 6)
|
|
6
|
+
* 旧 fancy-review/references/report-metadata-and-fallback.md —— 标题写"19 类",实际列了 20 条
|
|
7
|
+
* 旧 fancy-write/references/check-ai-patterns.py —— 声称 24 类,实际只实现了 21 类
|
|
8
|
+
* (缺 11 同义词循环 / 15 内联标题垂直列表 / 16 标题大写)
|
|
9
|
+
*
|
|
10
|
+
* 同一个"去 AI 味"在三个文件里是三套类目,模型根本不知道以哪套为准。
|
|
11
|
+
* 现在收敛成**一份**:类目编号沿用 ai-patterns-24.md(4 组 × 6),
|
|
12
|
+
* 正则沿用 check-ai-patterns.py(它写得更细),并补齐它缺的 3 类。
|
|
13
|
+
*
|
|
14
|
+
* 分级沿用旧定义:
|
|
15
|
+
* blocking —— 命中就要改(11 类)
|
|
16
|
+
* advisory —— 只提示(13 类)
|
|
17
|
+
*
|
|
18
|
+
* 每一类都带 rewrite(改写指引)。改写本身由模型按提示词做,
|
|
19
|
+
* 脚本只负责"检测 + 判级 + 算改动比例 + 回滚",不做机械替换 ——
|
|
20
|
+
* 旧实现的 9 条正则替换会把句子改坏("此外," 直接删掉,句子就散了)。
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// ============================================================
|
|
24
|
+
// 24 类模式
|
|
25
|
+
// ============================================================
|
|
26
|
+
|
|
27
|
+
export const AI_PATTERN_GROUPS = ['内容模式', '语言和语法模式', '风格模式', '交流模式'];
|
|
28
|
+
|
|
29
|
+
export const AI_PATTERNS = [
|
|
30
|
+
// ---------------- 内容模式(6 类) ----------------
|
|
31
|
+
{
|
|
32
|
+
id: 'overstate_meaning',
|
|
33
|
+
no: 1,
|
|
34
|
+
group: '内容模式',
|
|
35
|
+
name: '过度强调意义',
|
|
36
|
+
severity: 'blocking',
|
|
37
|
+
patterns: [
|
|
38
|
+
/作为.{0,20}的体现/,
|
|
39
|
+
/标志着.{0,30}的到来/,
|
|
40
|
+
/象征着.{0,20}的/,
|
|
41
|
+
/是关键.{0,10}的.{0,10}时刻/
|
|
42
|
+
],
|
|
43
|
+
rewrite: '把"作为……的体现""标志着……的到来"这类拔高句改成具体动作或具体后果。写"他推开门",不写"他推开了门,这一刻标志着某种东西的到来"。'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'media_fame',
|
|
47
|
+
no: 2,
|
|
48
|
+
group: '内容模式',
|
|
49
|
+
name: '过度强调知名度',
|
|
50
|
+
severity: 'advisory',
|
|
51
|
+
patterns: [/独立报道/, /知名专家(?:撰写|表示)/],
|
|
52
|
+
rewrite: '故事里不需要"独立报道""知名专家表示"这种外部背书。要权威感就让具体的人(有身份、有利害关系)来给结论。'
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'ing_ending',
|
|
56
|
+
no: 3,
|
|
57
|
+
group: '内容模式',
|
|
58
|
+
name: '-ing 结尾',
|
|
59
|
+
severity: 'blocking',
|
|
60
|
+
patterns: [
|
|
61
|
+
/(?:突出|强调|彰显|确保|反映|象征|展示)[^。!?]{0,30}的[^\s]{0,15}/
|
|
62
|
+
],
|
|
63
|
+
rewrite: '中文不是英语,不存在 -ing 尾。"强调了……的""彰显了……的"这类结构改成动词收尾:"他强调了三件事""这一战彰显了他的决心"。'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'promo_words',
|
|
67
|
+
no: 4,
|
|
68
|
+
group: '内容模式',
|
|
69
|
+
name: '宣传广告词',
|
|
70
|
+
severity: 'blocking',
|
|
71
|
+
patterns: [/充满活力的.{0,15}/, /令人叹为观止/, /必游之地/, /坐落于/],
|
|
72
|
+
rewrite: '把宣传语换成具体画面或具体感受。"令人叹为观止的景色"改成"风从谷口灌进来,刮得人睁不开眼"。'
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'vague_attribution',
|
|
76
|
+
no: 5,
|
|
77
|
+
group: '内容模式',
|
|
78
|
+
name: '模糊归因',
|
|
79
|
+
severity: 'advisory',
|
|
80
|
+
patterns: [/行业报告显示/, /专家认为/, /一些批评者认为/],
|
|
81
|
+
rewrite: '把"行业报告显示""专家认为"换成具体的来源:具体的人、具体的报告名、具体的数字。没有来源就删掉。'
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'challenge_outlook',
|
|
85
|
+
no: 6,
|
|
86
|
+
group: '内容模式',
|
|
87
|
+
name: '挑战展望',
|
|
88
|
+
severity: 'advisory',
|
|
89
|
+
patterns: [/尽管其.{0,30}面临若干挑战/, /未来展望/, /挑战与遗产/],
|
|
90
|
+
rewrite: '"尽管……面临若干挑战,未来展望……"是公文腔。改成角色当下的处境和打算,不替他们做总结。'
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
// ---------------- 语言和语法模式(6 类) ----------------
|
|
94
|
+
{
|
|
95
|
+
id: 'ai_frequent_words',
|
|
96
|
+
no: 7,
|
|
97
|
+
group: '语言和语法模式',
|
|
98
|
+
name: 'AI 高频词',
|
|
99
|
+
severity: 'blocking',
|
|
100
|
+
patterns: [
|
|
101
|
+
/此外[,,]/,
|
|
102
|
+
/至关重要/,
|
|
103
|
+
/深入探讨/,
|
|
104
|
+
/持久的/,
|
|
105
|
+
// "复杂""格局"在中文里是普通词("复杂的笑容""格局不大"),
|
|
106
|
+
// 原来的 /复杂(?:性|的)/、/格局(?:的)?/ 会把正常句子全判成 AI 腔。
|
|
107
|
+
// 只留真正带 AI 腔的固定搭配:"复杂性""复杂而…""复杂地…"、宏大/开阔的"格局"。
|
|
108
|
+
/复杂性/,
|
|
109
|
+
/复杂(?:而|地)/,
|
|
110
|
+
/格局(?:宏大|开阔|很大|深远)/,
|
|
111
|
+
/格局的(?:意义|层面|格局)/,
|
|
112
|
+
/织锦/,
|
|
113
|
+
/宝贵的/,
|
|
114
|
+
/充满活力的/
|
|
115
|
+
],
|
|
116
|
+
rewrite: '把这些词换成具体的动词或名词:"至关重要"→"少了他就塌了";"深入探讨"→"掰开来看";"格局"→具体指什么就写什么。'
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: 'copula_avoidance',
|
|
120
|
+
no: 8,
|
|
121
|
+
group: '语言和语法模式',
|
|
122
|
+
name: '系动词回避',
|
|
123
|
+
severity: 'advisory',
|
|
124
|
+
patterns: [/作为[一个]?/, /代表着/, /标志着[一个]?/],
|
|
125
|
+
rewrite: '"作为……"开头是 AI 腔的重灾区。改成主谓结构直接说。'
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: 'negation_flip',
|
|
129
|
+
no: 9,
|
|
130
|
+
group: '语言和语法模式',
|
|
131
|
+
name: '否定排比',
|
|
132
|
+
severity: 'blocking',
|
|
133
|
+
patterns: [/不仅仅?是.{1,30}而是/, /这不仅仅是.{0,30}而是/],
|
|
134
|
+
rewrite: '"不是……而是……"用一次是有力,连用就是套路。留最需要对比的那一处,其余直接说。'
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: 'rule_of_three',
|
|
138
|
+
no: 10,
|
|
139
|
+
group: '语言和语法模式',
|
|
140
|
+
name: '三段式法则',
|
|
141
|
+
// 2026-10-27 由 blocking 降为 advisory:
|
|
142
|
+
// "张三、李四、王五""少年、少女、老人,"都是正常中文,
|
|
143
|
+
// 原来的正则把它们全判成 AI 腔 —— 于是正常小说几乎不可能零命中,
|
|
144
|
+
// 每章都要反复改写,改写幅度一大又触发 35% 回滚。
|
|
145
|
+
// 现在仍然提示,但不拦(模型可以顺手改,也可以不动)。
|
|
146
|
+
severity: 'advisory',
|
|
147
|
+
patterns: [
|
|
148
|
+
/[一-鿿]{2,3}[、,,][一-鿿]{2,3}[、,,][一-鿿]{2,3}[^的]/
|
|
149
|
+
],
|
|
150
|
+
rewrite: '"甲、乙、丙"是 AI 最爱的排比。留两个,或者把第三个拆成独立的句子。'
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 'synonym_loop',
|
|
154
|
+
no: 11,
|
|
155
|
+
group: '语言和语法模式',
|
|
156
|
+
name: '同义词循环',
|
|
157
|
+
severity: 'advisory',
|
|
158
|
+
patterns: [
|
|
159
|
+
/(?:推进|促进|提升|加强|改善|加快|提高|降低|减少|增加|加大|深化)[了]?(?:进展|提升|水平|管理|分析|工作|建设|发展|速度|效率|质量|能力|意识|力度|力度|程度|氛围|效果|效率|规模|力度)/
|
|
160
|
+
],
|
|
161
|
+
rewrite: '"推进了进展""提升了水平""加强了管理"——谓语和宾语说的是同一件事。直接说动词:"推进了工期""水平提上来了""把他骂了一顿"。'
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: 'false_range',
|
|
165
|
+
no: 12,
|
|
166
|
+
group: '语言和语法模式',
|
|
167
|
+
name: '虚假范围',
|
|
168
|
+
severity: 'advisory',
|
|
169
|
+
patterns: [/从[一-鿿]{0,30}到[一-鿿]{0,30}的.{0,20}/],
|
|
170
|
+
rewrite: '"从……到……"两端不在同一个尺度上(从感情到生死、从鸡毛蒜皮到天崩地裂)就是虚假范围。要么统一尺度,要么拆开写。'
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
// ---------------- 风格模式(6 类) ----------------
|
|
174
|
+
{
|
|
175
|
+
id: 'em_dash_overuse',
|
|
176
|
+
no: 13,
|
|
177
|
+
group: '风格模式',
|
|
178
|
+
name: '破折号',
|
|
179
|
+
// 2026-10-27 由 blocking 降为 advisory,并加密度阈值:
|
|
180
|
+
// 破折号是中文的正常标点(一章里出现一两次完全正常),
|
|
181
|
+
// "出现即 blocking" 会让每章都命中,等于逼着模型把正常标点改掉。
|
|
182
|
+
// 真正的 AI 味是"到处都在用",所以按密度判:每 1000 字超过 2 处才算命中。
|
|
183
|
+
severity: 'advisory',
|
|
184
|
+
minDensity: 2,
|
|
185
|
+
patterns: [/——/],
|
|
186
|
+
rewrite: '"——"用得太密是 AI 味。一章里一两处破折号很正常,密集出现才需要改:换成逗号、句号,或者把后半句提起来单独成句。'
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: 'bold_overuse',
|
|
190
|
+
no: 14,
|
|
191
|
+
group: '风格模式',
|
|
192
|
+
name: '粗体过度',
|
|
193
|
+
severity: 'advisory',
|
|
194
|
+
patterns: [/\*\*[一-鿿]{2,8}\*\*/],
|
|
195
|
+
rewrite: '正文里几乎不该出现加粗。要强调就用动作或者语气,不用格式。'
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: 'inline_bullet_title',
|
|
199
|
+
no: 15,
|
|
200
|
+
group: '风格模式',
|
|
201
|
+
name: '内联标题垂直列表',
|
|
202
|
+
severity: 'advisory',
|
|
203
|
+
patterns: [/^[ \t]*[-*][ \t]*\*\*[^*]+\*\*[::]/m],
|
|
204
|
+
rewrite: '"- **标题**:说明"这种列表是文档体,不是小说体。要点散进句子里,或者直接删掉这一层包装。'
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: 'title_case_heading',
|
|
208
|
+
no: 16,
|
|
209
|
+
group: '风格模式',
|
|
210
|
+
name: '标题大写',
|
|
211
|
+
severity: 'advisory',
|
|
212
|
+
patterns: [/^[A-Z][A-Z0-9 \t]{4,}$/m],
|
|
213
|
+
rewrite: '整行全大写的标题是英文写作习惯,中文正文里很突兀。改成正常大小写。(中文不适用此条)'
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
id: 'emoji',
|
|
217
|
+
no: 17,
|
|
218
|
+
group: '风格模式',
|
|
219
|
+
name: '表情符号',
|
|
220
|
+
severity: 'advisory',
|
|
221
|
+
// 2026-10-27 修:原来的 /\u{1F300}-\u{1F6FF}/u 里的 "-" 是**字面连字符**,
|
|
222
|
+
// 整个正则匹配的是"😀-😁"这个三字符序列 —— 单独一个 emoji 从来没被命中过。
|
|
223
|
+
// 字符类里的范围写法才对:/[\u{1F300}-\u{1F6FF}]/u
|
|
224
|
+
patterns: [/[\u{1F300}-\u{1F6FF}]/u],
|
|
225
|
+
rewrite: '表情符号不出现在小说正文里。'
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: 'curly_quotes',
|
|
229
|
+
no: 18,
|
|
230
|
+
group: '风格模式',
|
|
231
|
+
name: '弯引号',
|
|
232
|
+
// 2026-10-27 由 blocking 降为 advisory:
|
|
233
|
+
// 中文对话的标准引号就是“”,写作提示词从没要求用「」,
|
|
234
|
+
// 按 blocking 拦等于逼模型把正常对白引号全部改掉。
|
|
235
|
+
severity: 'advisory',
|
|
236
|
+
patterns: [/[“”]/],
|
|
237
|
+
rewrite: '弯引号(“”)本身不是错。如果这本书统一用直角引号「」,可以统一改;不统一就不用动。'
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
// ---------------- 交流模式(6 类) ----------------
|
|
241
|
+
{
|
|
242
|
+
id: 'assistant_chatter',
|
|
243
|
+
no: 19,
|
|
244
|
+
group: '交流模式',
|
|
245
|
+
name: '协作交流痕迹',
|
|
246
|
+
severity: 'advisory',
|
|
247
|
+
patterns: [/希望这对您有帮助/, /当然!/, /您说得完全正确/, /请告诉我/],
|
|
248
|
+
rewrite: '"希望这对您有帮助""当然!"是助手对助手说的话,不该出现在正文。'
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
id: 'knowledge_cutoff',
|
|
252
|
+
no: 20,
|
|
253
|
+
group: '交流模式',
|
|
254
|
+
name: '知识截止日期',
|
|
255
|
+
severity: 'advisory',
|
|
256
|
+
patterns: [/截至\s*\d{4}/, /根据我最后的训练/],
|
|
257
|
+
rewrite: '"截至 2024 年""根据我最后的训练"是模型的自我暴露,正文里绝对不能有。'
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
id: 'sycophancy',
|
|
261
|
+
no: 21,
|
|
262
|
+
group: '交流模式',
|
|
263
|
+
name: '谄媚语气',
|
|
264
|
+
severity: 'advisory',
|
|
265
|
+
patterns: [/非常好的问题/, /您说得完全正确/, /这是一个复杂的话题/],
|
|
266
|
+
rewrite: '"这是一个复杂的话题"是助手腔。改成角色自己的判断,哪怕粗糙。'
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
id: 'filler_phrase',
|
|
270
|
+
no: 22,
|
|
271
|
+
group: '交流模式',
|
|
272
|
+
name: '填充短语',
|
|
273
|
+
severity: 'blocking',
|
|
274
|
+
patterns: [/为了实现这一目标/, /由于.{1,10}的事实/, /在这个时间点/],
|
|
275
|
+
rewrite: '"为了实现这一目标""在这个时间点"是过渡填充。删掉,句子照样成立。'
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
id: 'over_hedging',
|
|
279
|
+
no: 23,
|
|
280
|
+
group: '交流模式',
|
|
281
|
+
name: '过度限定',
|
|
282
|
+
severity: 'blocking',
|
|
283
|
+
patterns: [/(?:可能|或许|也许|似乎)\s*(?:可能|或许|也许|似乎){1,3}/],
|
|
284
|
+
rewrite: '连着好几个"可能""也许"是 AI 在回避表态。挑一个,或者干脆给个准话。'
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
id: 'generic_positive',
|
|
288
|
+
no: 24,
|
|
289
|
+
group: '交流模式',
|
|
290
|
+
name: '通用积极结论',
|
|
291
|
+
severity: 'blocking',
|
|
292
|
+
patterns: [/未来看起来光明/, /激动人心的时代/, /踏上新的旅程/, /新的篇章(?:开始|即将)/],
|
|
293
|
+
rewrite: '"未来一片光明""激动人心的时代""新的篇章开始了"是收尾套话。结尾要落在具体的事上。'
|
|
294
|
+
}
|
|
295
|
+
];
|
|
296
|
+
|
|
297
|
+
// ============================================================
|
|
298
|
+
// 索引
|
|
299
|
+
// ============================================================
|
|
300
|
+
|
|
301
|
+
const BY_ID = new Map(AI_PATTERNS.map((p) => [p.id, p]));
|
|
302
|
+
const BY_NO = new Map(AI_PATTERNS.map((p) => [p.no, p]));
|
|
303
|
+
|
|
304
|
+
/** 按 id 查一个类目 */
|
|
305
|
+
export function patternById(id) {
|
|
306
|
+
return BY_ID.get(id) || null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** 按编号(1-24)查一个类目 */
|
|
310
|
+
export function patternByNo(no) {
|
|
311
|
+
return BY_NO.get(Number(no)) || null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** blocking 类目的 id 集合 */
|
|
315
|
+
export function blockingPatternIds() {
|
|
316
|
+
return AI_PATTERNS.filter((p) => p.severity === 'blocking').map((p) => p.id);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** advisory 类目的 id 集合 */
|
|
320
|
+
export function advisoryPatternIds() {
|
|
321
|
+
return AI_PATTERNS.filter((p) => p.severity === 'advisory').map((p) => p.id);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* 检测一个类目在文本里的命中。
|
|
326
|
+
* @returns {Array<{index:number, line:number, match:string}>}
|
|
327
|
+
*/
|
|
328
|
+
export function matchPattern(pattern, text) {
|
|
329
|
+
const out = [];
|
|
330
|
+
if (!pattern || !pattern.patterns) return out;
|
|
331
|
+
for (const re of pattern.patterns) {
|
|
332
|
+
const rx = new RegExp(re.source, re.flags.includes('g') ? re.flags : re.flags + 'g');
|
|
333
|
+
let m;
|
|
334
|
+
while ((m = rx.exec(text)) !== null) {
|
|
335
|
+
out.push({
|
|
336
|
+
index: m.index,
|
|
337
|
+
line: countLine(text, m.index),
|
|
338
|
+
match: m[0]
|
|
339
|
+
});
|
|
340
|
+
if (m[0].length === 0) rx.lastIndex++;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return out.sort((a, b) => a.index - b.index);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* 全文检测 24 类 AI 模式。
|
|
348
|
+
*
|
|
349
|
+
* @param {string} text 正文
|
|
350
|
+
* @returns {{findings:Array, byCategory:Object, blockingCount:number, advisoryCount:number}}
|
|
351
|
+
* findings —— 每条 {categoryId, category, severity, line, match}
|
|
352
|
+
*/
|
|
353
|
+
export function detectAiPatterns(text) {
|
|
354
|
+
const findings = [];
|
|
355
|
+
const byCategory = {};
|
|
356
|
+
if (typeof text !== 'string' || !text) {
|
|
357
|
+
return { findings, byCategory, blockingCount: 0, advisoryCount: 0 };
|
|
358
|
+
}
|
|
359
|
+
for (const p of AI_PATTERNS) {
|
|
360
|
+
const hits = matchPattern(p, text);
|
|
361
|
+
if (!hits.length) continue;
|
|
362
|
+
// 密度阈值:某些模式(破折号)本身是正常标点,
|
|
363
|
+
// "出现即命中"会误伤整章。按每千字允许若干处来判,
|
|
364
|
+
// 超过这个量才算真命中(minDensity = 每 1000 字允许的处数)。
|
|
365
|
+
if (p.minDensity) {
|
|
366
|
+
const allowed = Math.max(1, Math.floor((text.length / 1000) * p.minDensity));
|
|
367
|
+
if (hits.length <= allowed) continue;
|
|
368
|
+
}
|
|
369
|
+
byCategory[p.id] = {
|
|
370
|
+
id: p.id,
|
|
371
|
+
no: p.no,
|
|
372
|
+
name: p.name,
|
|
373
|
+
group: p.group,
|
|
374
|
+
severity: p.severity,
|
|
375
|
+
count: hits.length,
|
|
376
|
+
matches: hits.slice(0, 10)
|
|
377
|
+
};
|
|
378
|
+
for (const h of hits) {
|
|
379
|
+
findings.push({
|
|
380
|
+
categoryId: p.id,
|
|
381
|
+
category: p.name,
|
|
382
|
+
no: p.no,
|
|
383
|
+
severity: p.severity,
|
|
384
|
+
line: h.line,
|
|
385
|
+
match: h.match.slice(0, 60),
|
|
386
|
+
index: h.index
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
findings,
|
|
392
|
+
byCategory,
|
|
393
|
+
blockingCount: findings.filter((f) => f.severity === 'blocking').length,
|
|
394
|
+
advisoryCount: findings.filter((f) => f.severity === 'advisory').length
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** 统计某个下标所在行号(1 起) */
|
|
399
|
+
function countLine(text, index) {
|
|
400
|
+
let line = 1;
|
|
401
|
+
for (let i = 0; i < index && i < text.length; i++) {
|
|
402
|
+
if (text.charCodeAt(i) === 10) line++;
|
|
403
|
+
}
|
|
404
|
+
return line;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* 改动比例:改动的字符占原文字数的比例(0-1)。
|
|
409
|
+
*
|
|
410
|
+
* 旧实现用的是 (原长 - 新长) / 原长,
|
|
411
|
+
* 但删字不是唯一改法 —— 换词也会改长度但长度可能不变。
|
|
412
|
+
* 这里用**归一化编辑距离**(LCS 近似):
|
|
413
|
+
* ratio = (n + m - 2 * lcs) / (n + m)
|
|
414
|
+
*
|
|
415
|
+
* 语义:等长文本时它就等于"被改掉的字符占比"
|
|
416
|
+
* (改掉一半 → 0.5)。之前写成 (n-lcs + (m-lcs)) / max(n,m)
|
|
417
|
+
* 会把"替换"算成一次删除加一次插入 —— 改掉一半的字算成 100%,
|
|
418
|
+
* 阈值 35% 就会误伤正常改写。
|
|
419
|
+
*
|
|
420
|
+
* 注意:纯增删时它偏低(凭空多写 20% 的字只算 9%),
|
|
421
|
+
* 所以"只加不减"的改写要靠**字数容差**那条闸门兜住(见 deslop-driver.js)。
|
|
422
|
+
*
|
|
423
|
+
* @param {string} before 改前
|
|
424
|
+
* @param {string} after 改后
|
|
425
|
+
* @returns {number} 0-1 之间
|
|
426
|
+
*/
|
|
427
|
+
export function changeRatio(before, after) {
|
|
428
|
+
const a = typeof before === 'string' ? before : '';
|
|
429
|
+
const b = typeof after === 'string' ? after : '';
|
|
430
|
+
if (!a.length && !b.length) return 0;
|
|
431
|
+
if (!a.length) return 1;
|
|
432
|
+
if (!b.length) return 1;
|
|
433
|
+
// 最长公共子序列(O(n*m) 空间换时间,长文本要防爆)
|
|
434
|
+
const n = a.length;
|
|
435
|
+
const m = b.length;
|
|
436
|
+
if (n * m > 4 * 1000 * 1000) {
|
|
437
|
+
// 太大时改用**分块指纹**算相似度(见 blockSimilarity)。
|
|
438
|
+
//
|
|
439
|
+
// 为什么不能退化成长度差:等长整章重写的长度差是 0 —— 于是
|
|
440
|
+
// "改动超 35% 自动回滚"这条保护在 2000 字以上的章节(= 正常章节长度)
|
|
441
|
+
// **完全失效**:模型可以把整章换掉而回滚不触发(2026-10-27 实测:
|
|
442
|
+
// 2000 字等长重写 → 1,2001 字等长重写 → 0)。
|
|
443
|
+
return Math.min(1, 1 - blockSimilarity(a, b));
|
|
444
|
+
}
|
|
445
|
+
let prev = new Uint32Array(m + 1);
|
|
446
|
+
let cur = new Uint32Array(m + 1);
|
|
447
|
+
for (let i = 1; i <= n; i++) {
|
|
448
|
+
cur[0] = 0;
|
|
449
|
+
for (let j = 1; j <= m; j++) {
|
|
450
|
+
if (a.charCodeAt(i - 1) === b.charCodeAt(j - 1)) cur[j] = prev[j - 1] + 1;
|
|
451
|
+
else cur[j] = prev[j] > cur[j - 1] ? prev[j] : cur[j - 1];
|
|
452
|
+
}
|
|
453
|
+
const tmp = prev;
|
|
454
|
+
prev = cur;
|
|
455
|
+
cur = tmp;
|
|
456
|
+
}
|
|
457
|
+
const lcs = prev[m];
|
|
458
|
+
return Math.min(1, (n + m - 2 * lcs) / (n + m));
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* 分块指纹相似度:把两段文本切成同样大小的块,逐块算指纹,
|
|
463
|
+
* 用"指纹多重集交集 ÷ 块数"当相似度。
|
|
464
|
+
*
|
|
465
|
+
* 为什么用块而不是字符:字符级比对在长文本上是 O(n*m),2000 字就已经
|
|
466
|
+
* 400 万个格子;块级是 O(n),任意长度都不爆。
|
|
467
|
+
*
|
|
468
|
+
* 行为:
|
|
469
|
+
* 整章重写(内容全换、字数不变) → 所有指纹都不同 → 相似度 0 → 比例 1
|
|
470
|
+
* 改一个字 → 只影响一个块 → 比例很小
|
|
471
|
+
* 增删 10% 字数 → 块数变了 → 比例同时反映长度变化
|
|
472
|
+
*
|
|
473
|
+
* @param {string} a 改前
|
|
474
|
+
* @param {string} b 改后
|
|
475
|
+
* @param {number} blockSize 块大小(字符)
|
|
476
|
+
* @returns {number} 0-1,1 = 完全不同
|
|
477
|
+
*/
|
|
478
|
+
function blockSimilarity(a, b, blockSize = 100) {
|
|
479
|
+
const ha = blockHashes(a, blockSize);
|
|
480
|
+
const hb = blockHashes(b, blockSize);
|
|
481
|
+
if (!ha.length && !hb.length) return 1;
|
|
482
|
+
if (!ha.length || !hb.length) return 0;
|
|
483
|
+
const pool = new Map();
|
|
484
|
+
for (const h of ha) pool.set(h, (pool.get(h) || 0) + 1);
|
|
485
|
+
let same = 0;
|
|
486
|
+
for (const h of hb) {
|
|
487
|
+
const c = pool.get(h) || 0;
|
|
488
|
+
if (c > 0) {
|
|
489
|
+
same++;
|
|
490
|
+
pool.set(h, c - 1);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return same / Math.max(ha.length, hb.length);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* 逐块指纹(FNV-1a 32 位 + 块长,避免不同长度的尾块撞指纹)。
|
|
498
|
+
* @param {string} text
|
|
499
|
+
* @param {number} size
|
|
500
|
+
* @returns {number[]}
|
|
501
|
+
*/
|
|
502
|
+
function blockHashes(text, size) {
|
|
503
|
+
const out = [];
|
|
504
|
+
for (let i = 0; i < text.length; i += size) {
|
|
505
|
+
const chunk = text.slice(i, i + size);
|
|
506
|
+
let h = 2166136261;
|
|
507
|
+
for (let k = 0; k < chunk.length; k++) {
|
|
508
|
+
h ^= chunk.charCodeAt(k);
|
|
509
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
510
|
+
}
|
|
511
|
+
out.push((h >>> 0) + ':' + chunk.length);
|
|
512
|
+
}
|
|
513
|
+
return out;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* 去味等级:按改动比例分档。
|
|
518
|
+
* 旧实现的阈值是 轻度<15%、中度<25%、重度≥25%,且**没有上限**
|
|
519
|
+
* —— 文档承诺"超过 35% 回滚",代码里根本没有。这里把上限补上。
|
|
520
|
+
*/
|
|
521
|
+
export const DESLOP_LEVELS = [
|
|
522
|
+
{ id: 'light', name: '轻度', max: 0.15 },
|
|
523
|
+
{ id: 'medium', name: '中度', max: 0.25 },
|
|
524
|
+
{ id: 'heavy', name: '重度', max: 0.35 }
|
|
525
|
+
];
|
|
526
|
+
|
|
527
|
+
/** 超过这个比例就要回滚(改得太多说明改坏了,不是去味) */
|
|
528
|
+
export const DESLOP_ROLLBACK_RATIO = 0.35;
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* 按改动比例判定等级。
|
|
532
|
+
*
|
|
533
|
+
* 档位与超限在同一个出口算:
|
|
534
|
+
* - 档位:<15% 轻度 / <25% 中度 / <35% 重度 / ≥35% 也是重度(但超限)
|
|
535
|
+
* - 超限:>35% 就该回滚
|
|
536
|
+
* 之前把 exceeded 写在循环里,等于把一个恒为 false 的值算两遍 ——
|
|
537
|
+
* 循环分支的条件是 r < 档位上限(最大 0.35),r > 0.35 在那里永远不成立。
|
|
538
|
+
*
|
|
539
|
+
* @param {number} ratio 改动比例(0-1)
|
|
540
|
+
* @returns {{id:string,name:string,exceeded:boolean}}
|
|
541
|
+
* exceeded = 超过回滚上限
|
|
542
|
+
*/
|
|
543
|
+
export function deslopLevel(ratio) {
|
|
544
|
+
const r = typeof ratio === 'number' && isFinite(ratio) ? ratio : 0;
|
|
545
|
+
let level = DESLOP_LEVELS[DESLOP_LEVELS.length - 1];
|
|
546
|
+
for (const lv of DESLOP_LEVELS) {
|
|
547
|
+
if (r < lv.max) {
|
|
548
|
+
level = lv;
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return { id: level.id, name: level.name, exceeded: r > DESLOP_ROLLBACK_RATIO };
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ============================================================
|
|
556
|
+
// 派生清单(提示词 / 文档的唯一出处)
|
|
557
|
+
// ============================================================
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* 历史 id → 当前 id 的兼容映射。
|
|
561
|
+
*
|
|
562
|
+
* 为什么需要:第 19 类原来的 id 是 'assistant_ chatter'(**带空格**)。
|
|
563
|
+
* id 会进日志、报告、URL 和脚本参数,带空格就到处要转义。
|
|
564
|
+
* 改名之后,外部按旧 id 查类目会查不到,所以留这张表做换算。
|
|
565
|
+
*/
|
|
566
|
+
export const LEGACY_PATTERN_IDS = {
|
|
567
|
+
'assistant_ chatter': 'assistant_chatter'
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* 24 类的结构化行,供文档守卫逐条比对。
|
|
572
|
+
* @returns {Array<{no:number,id:string,group:string,name:string,severity:string,legacyId?:string}>}
|
|
573
|
+
*/
|
|
574
|
+
export function deslopPatternRows() {
|
|
575
|
+
return AI_PATTERNS.map((p) => {
|
|
576
|
+
const row = {
|
|
577
|
+
no: p.no,
|
|
578
|
+
id: p.id,
|
|
579
|
+
group: p.group,
|
|
580
|
+
name: p.name,
|
|
581
|
+
severity: p.severity
|
|
582
|
+
};
|
|
583
|
+
for (const [oldId, newId] of Object.entries(LEGACY_PATTERN_IDS)) {
|
|
584
|
+
if (newId === p.id) row.legacyId = oldId;
|
|
585
|
+
}
|
|
586
|
+
return row;
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* 生成"AI 模式清单",直接拼进去味提示词。
|
|
592
|
+
*
|
|
593
|
+
* 为什么动态生成而不是把清单写死在 markdown 里:
|
|
594
|
+
* 旧 skill 正是这么坏的 —— deslop 声称 24 类、review 声称 19 类(实际 20 条)、
|
|
595
|
+
* 代码里只实现 21 类,三份互不一致,模型根本不知道以哪套为准。
|
|
596
|
+
* 清单只有这一份,提示词、校验器、写作指导都从它派生。
|
|
597
|
+
*
|
|
598
|
+
* 提示词里必须带 rewrite 指引:只告诉模型"第 12 类命中了"没用,
|
|
599
|
+
* 得告诉它这类句式该改成什么样。
|
|
600
|
+
*
|
|
601
|
+
* @returns {string} 可直接拼进提示词的 markdown 段
|
|
602
|
+
*/
|
|
603
|
+
export function deslopLegend() {
|
|
604
|
+
const lines = [];
|
|
605
|
+
lines.push('---');
|
|
606
|
+
lines.push('');
|
|
607
|
+
lines.push('## 附:AI 模式清单(24 类,由校验器判定)');
|
|
608
|
+
lines.push('');
|
|
609
|
+
lines.push('校验器按下面这张表判定。`blocking` = 命中就要改;`advisory` = 只提示,不拦。');
|
|
610
|
+
lines.push('每类后面的「改法」就是改写指引,按它改,不要自己另发明替换规则。');
|
|
611
|
+
lines.push('');
|
|
612
|
+
lines.push('| # | 分组 | 名称 | 级别 | 改法 |');
|
|
613
|
+
lines.push('|---|---|---|---|---|');
|
|
614
|
+
for (const p of AI_PATTERNS) {
|
|
615
|
+
lines.push('| ' + p.no + ' | ' + p.group + ' | ' + p.name + ' | ' + p.severity + ' | ' + p.rewrite + ' |');
|
|
616
|
+
}
|
|
617
|
+
lines.push('');
|
|
618
|
+
return lines.join('\n');
|
|
619
|
+
}
|