le-wechat-article v1.3:融合 human-writing 活人感写法(材料关 / 说话位置 / 活人感审读 + check_prose.py 双自检);附 check_article front matter 剥离修复
This commit is contained in:
@@ -61,7 +61,7 @@ skills/
|
|||||||
|
|
||||||
触发方式:「写篇公众号文章」「把这份素材整理成推文」「同步到公众号草稿箱」。
|
触发方式:「写篇公众号文章」「把这份素材整理成推文」「同步到公众号草稿箱」。
|
||||||
|
|
||||||
方法论来源(只借机制,不复制表达):[mp-article-writor](https://github.com/balabalabalading/huuuuuuho-skills/tree/main/skills/mp-article-writor)(MIT,类型路由 / 去模板化 / 语义保真)· [wechat-article-skills](https://github.com/aiworkskills/wechat-article-skills)(Apache-2.0,产出配额 / AI 味指纹 / 机器自检)。
|
方法论来源(只借机制,不复制表达):[mp-article-writor](https://github.com/balabalabalading/huuuuuuho-skills/tree/main/skills/mp-article-writor)(MIT,类型路由 / 去模板化 / 语义保真)· [wechat-article-skills](https://github.com/aiworkskills/wechat-article-skills)(Apache-2.0,产出配额 / AI 味指纹 / 机器自检)· [human-writing](https://github.com/KKKKhazix/human-writing)(MIT,活人感写法 / 说话位置 / 模型腔硬禁与 check_prose 检查器)。
|
||||||
|
|
||||||
排版主题库直接引入:[gzh-design-skill](https://github.com/isjiamu/gzh-design-skill)(AGPL-3.0)的 6 套主题组件库、平台合规校验器与预览页脚本,存放于 `skills/le-wechat-article/references/themes/` 并适用其原协议(边界与修改声明见该目录 README)。
|
排版主题库直接引入:[gzh-design-skill](https://github.com/isjiamu/gzh-design-skill)(AGPL-3.0)的 6 套主题组件库、平台合规校验器与预览页脚本,存放于 `skills/le-wechat-article/references/themes/` 并适用其原协议(边界与修改声明见该目录 README)。
|
||||||
|
|
||||||
|
|||||||
@@ -30,4 +30,6 @@
|
|||||||
|
|
||||||
方法融合自两个开源项目(仅借机制与方法,未复制表达):[mp-article-writor](https://github.com/balabalabalading/huuuuuuho-skills/tree/main/skills/mp-article-writor)(MIT)的类型路由、去模板化审读与语义保真;[wechat-article-skills](https://github.com/aiworkskills/wechat-article-skills)(Apache-2.0)的产出配额、AI 味指纹阈值与机器自检思想。
|
方法融合自两个开源项目(仅借机制与方法,未复制表达):[mp-article-writor](https://github.com/balabalabalading/huuuuuuho-skills/tree/main/skills/mp-article-writor)(MIT)的类型路由、去模板化审读与语义保真;[wechat-article-skills](https://github.com/aiworkskills/wechat-article-skills)(Apache-2.0)的产出配额、AI 味指纹阈值与机器自检思想。
|
||||||
|
|
||||||
|
活人感写作法(材料关 / 说话位置 / 行文规则 / 模型腔硬禁清单)与 `check_prose.py` 检查器提炼自 [KKKKhazix/human-writing](https://github.com/KKKKhazix/human-writing)(MIT),按本工坊的文章类型体系适配,出处标注见 [references/human-voice.md](references/human-voice.md) 与审读手册致谢。
|
||||||
|
|
||||||
排版主题库(`references/themes/` 与配套校验/预览脚本)**直接引入**自 [gzh-design-skill](https://github.com/isjiamu/gzh-design-skill)(AGPL-3.0,甲木 Jiamu × 摸鱼小李)——该部分适用其原协议,本 skill 其余部分为 MIT,边界与修改声明见 [themes/README.md](references/themes/README.md)。
|
排版主题库(`references/themes/` 与配套校验/预览脚本)**直接引入**自 [gzh-design-skill](https://github.com/isjiamu/gzh-design-skill)(AGPL-3.0,甲木 Jiamu × 摸鱼小李)——该部分适用其原协议,本 skill 其余部分为 MIT,边界与修改声明见 [themes/README.md](references/themes/README.md)。
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ description: >
|
|||||||
- **真实性是底线。** 每个事实性陈述都要能追溯到用户素材、公开可验证信息或用户声明的个人经历。素材不足就明说缺什么,不为凑篇幅编造细节、数据、经历或情绪。
|
- **真实性是底线。** 每个事实性陈述都要能追溯到用户素材、公开可验证信息或用户声明的个人经历。素材不足就明说缺什么,不为凑篇幅编造细节、数据、经历或情绪。
|
||||||
- **类型决定写法。** 不同文章类型有不同的开头、节奏和审读标准(见 [references/article-types.md](references/article-types.md)),不把所有文章写成一种腔调。
|
- **类型决定写法。** 不同文章类型有不同的开头、节奏和审读标准(见 [references/article-types.md](references/article-types.md)),不把所有文章写成一种腔调。
|
||||||
- **修改不得走样。** 审读修改时保护事实、否定、范围、时间、归因和确定程度——不得把相关写成因果、可能写成确定、计划写成已完成。
|
- **修改不得走样。** 审读修改时保护事实、否定、范围、时间、归因和确定程度——不得把相关写成因果、可能写成确定、计划写成已完成。
|
||||||
- **判断放参考、数数放脚本。** 结构和审读的判断读 references/;能机器量的(标题层级、加粗密度、金句数、表格、中英文间距)交给 `check_article.py`,不靠眼睛。
|
- **判断放参考、数数放脚本。** 结构和审读的判断读 references/;能机器量的(标题层级、加粗密度、金句数、表格、中英文间距)交给 `check_article.py`,模型腔硬禁(翻案腔、破折号、冒号、黑话、路标)交给 `check_prose.py`,不靠眼睛。
|
||||||
|
|
||||||
## 工作流
|
## 工作流
|
||||||
|
|
||||||
@@ -49,10 +49,12 @@ description: >
|
|||||||
|
|
||||||
画像还是空模板、或所选账号没有分节时,用其中的「首访问题」问用户一次,把答复写回该文件(此文件随本 skill 存放,属于个人资产);每个账号首次使用各问一次,之后不再重复问。事实卡留空则跳过,用户随时可补。画像与事实卡都不能覆盖类型路由、事实核查和真实性底线。
|
画像还是空模板、或所选账号没有分节时,用其中的「首访问题」问用户一次,把答复写回该文件(此文件随本 skill 存放,属于个人资产);每个账号首次使用各问一次,之后不再重复问。事实卡留空则跳过,用户随时可补。画像与事实卡都不能覆盖类型路由、事实核查和真实性底线。
|
||||||
|
|
||||||
**校准结果是固定停点(轻量)**:展示本次将采用的约束——该账号画像要点(定位/声音/文末块/排版主题)、将取用的事实卡条目、敏感词要求——用户确认或调整后进第 3 步。画像早已填好、无新信息时也要展示:给用户当场临时调整的机会(如本文换排版主题、跳过文末块),展示可精简为几行。
|
**校准结果是固定停点(轻量)**:展示本次将采用的约束——该账号画像要点(定位/声音/文末块)、**排版主题选择**、将取用的事实卡条目、敏感词要求——用户确认或调整后进第 3 步。**排版主题以选择题呈现**:按文章类型从 [themes/theme-index.md](references/themes/theme-index.md) 给出 1 个推荐主题(附一句理由)+ 2–3 个备选(各附一句适用理由),该账号画像的默认主题标注出来;用户回复主题名或序号选定,说「按推荐」即用推荐项(可用 AskUserQuestion 出选项)。画像早已填好、无新信息时也要展示:给用户当场临时调整的机会(如换排版主题、跳过文末块),展示可精简为几行,但主题选择题不省。
|
||||||
|
|
||||||
### 第 3 步:大纲与图位
|
### 第 3 步:大纲与图位
|
||||||
|
|
||||||
|
起草大纲前先过**材料关**([human-voice.md](references/human-voice.md) 第一节):非虚构正文计划 ≥1200 字的,先列**至少 5 件可追溯的具体材料**(动作、数字、时间、原话、失败、代价、后续,注明来自用户哪句话或哪份可靠来源),并确认能组成一条实际过程;大纲每个主要小节标注依托的材料。列不出 5 件——能检索的先研究再重新计数;依赖用户经历的回第 1 步用「三问」一次问全;仍不足就**缩题或压短篇幅**,不靠推演和重复解释灌字数。
|
||||||
|
|
||||||
按所选类型的结构骨架起草大纲,同时规划图位(按 style-rules 的密度档位表选一档)。每个图位标注「信息位 / 节奏位」:
|
按所选类型的结构骨架起草大纲,同时规划图位(按 style-rules 的密度档位表选一档)。每个图位标注「信息位 / 节奏位」:
|
||||||
|
|
||||||
- **信息位**:删掉这张图读者会**看不懂**(流程、结构、数据、对比)→ 图上文字逐字取自文章
|
- **信息位**:删掉这张图读者会**看不懂**(流程、结构、数据、对比)→ 图上文字逐字取自文章
|
||||||
@@ -64,11 +66,14 @@ description: >
|
|||||||
|
|
||||||
### 第 4 步:写初稿
|
### 第 4 步:写初稿
|
||||||
|
|
||||||
遵守 style-rules 的全部硬规则(标题层级、表格限制、中英文间距、直角引号),并满足产出配额——这些规则不靠自觉,第 6 步有脚本核验:
|
动笔前读 [human-voice.md](references/human-voice.md)(活人感写法),内部回答「说话位置五问」——不把答案原样交给用户。活人感按类型分浓度(叙事/复盘高、技术/教程克制、简报不加),它来自材料与信息来路,不是口头禅与假细节。
|
||||||
|
|
||||||
|
遵守 style-rules 的全部硬规则(标题层级、表格、中英文间距、直角引号),并满足产出配额——这些规则不靠自觉,第 6 步有脚本核验:
|
||||||
|
|
||||||
- **加粗是扫读落点**:每 100 字左右一处,连续 200 字不得没有;每个小节至少一处。验收标准:把全文加粗按顺序连起来读,应该是一篇能独立看懂的缩写版
|
- **加粗是扫读落点**:每 100 字左右一处,连续 200 字不得没有;每个小节至少一处。验收标准:把全文加粗按顺序连起来读,应该是一篇能独立看懂的缩写版
|
||||||
- **摘要 ≠ 导语**:front matter 的 digest 与正文开头导语不得是同一句话
|
- **摘要 ≠ 导语**:front matter 的 digest 与正文开头导语不得是同一句话
|
||||||
- **金句卡至多一处**:`> 金句。 —— 出处`,多了就退化成装饰
|
- **金句卡至多一处**:`> 金句。(出处)`,多了就退化成装饰
|
||||||
|
- **模型腔硬禁**(human-voice.md 第七节,`check_prose.py` 核验):翻案腔零容忍、正文无破折号、冒号只引人物直接原话、黑话与洞察路标清零
|
||||||
|
|
||||||
初稿写到 `article.md`,同时复制一份完整副本到同目录 `review-baseline.md`。基线在终稿交付前不得修改——第 5 步审读要对照它检测语义漂移。
|
初稿写到 `article.md`,同时复制一份完整副本到同目录 `review-baseline.md`。基线在终稿交付前不得修改——第 5 步审读要对照它检测语义漂移。
|
||||||
|
|
||||||
@@ -76,7 +81,7 @@ description: >
|
|||||||
|
|
||||||
两项审读,各起独立 subagent(关注点不同,混在一次会顾此失彼),都给它:作者素材、`review-baseline.md`、当前 `article.md`、文章类型、[references/review-playbook.md](references/review-playbook.md)。
|
两项审读,各起独立 subagent(关注点不同,混在一次会顾此失彼),都给它:作者素材、`review-baseline.md`、当前 `article.md`、文章类型、[references/review-playbook.md](references/review-playbook.md)。
|
||||||
|
|
||||||
**5a 内容审读**——按手册输出意见清单(每条含位置、可观察的问题、对阅读的影响、必须保留的信息、建议方向):去模板化、AI 味指纹(按防误判阈值,命中本身不是修改理由)、语义保真风险(对照基线标出可能改变事实/否定/范围/条件/时间/归因/确定程度/立场的位置)。
|
**5a 内容审读**——按手册输出意见清单(每条含位置、可观察的问题、对阅读的影响、必须保留的信息、建议方向):去模板化、AI 味指纹(按防误判阈值,命中本身不是修改理由)、**活人感审读**(谁在说 / 推进 / 表演性中文 / 结尾 / 冷读)、**模型腔硬禁清单**(命中即必改,与合规同级)、语义保真风险(对照基线标出可能改变事实/否定/范围/条件/时间/归因/确定程度/立场的位置)。
|
||||||
|
|
||||||
**5b 事实核查 + 合规扫描**——数字、事件、产品名、引用是否可追溯到素材;平台合规逐类扫描(违禁内容、广告法绝对化用语、医疗金融承诺、未授权素材、画像自定义敏感词清单)——**合规命中一律必改,与 AI 味的建议级不同**。
|
**5b 事实核查 + 合规扫描**——数字、事件、产品名、引用是否可追溯到素材;平台合规逐类扫描(违禁内容、广告法绝对化用语、医疗金融承诺、未授权素材、画像自定义敏感词清单)——**合规命中一律必改,与 AI 味的建议级不同**。
|
||||||
|
|
||||||
@@ -90,10 +95,12 @@ subagent 可以给出「没有需要修改的问题」;不要为填满清单
|
|||||||
|
|
||||||
**长文终审**(正文 ≥2500 字或类型为测评/复盘/叙事/技术):修改完成后、交付前,再起一个 subagent 对照「作者素材 + 基线 + 当前稿」做终审——只查三件事:新增/遗漏/强化了主张没有、合规清零没有、HKR 一句话总评。轻稿(简报 <2500 字)跳过终审,主 agent 自查即可。
|
**长文终审**(正文 ≥2500 字或类型为测评/复盘/叙事/技术):修改完成后、交付前,再起一个 subagent 对照「作者素材 + 基线 + 当前稿」做终审——只查三件事:新增/遗漏/强化了主张没有、合规清零没有、HKR 一句话总评。轻稿(简报 <2500 字)跳过终审,主 agent 自查即可。
|
||||||
|
|
||||||
然后跑自检直到退出码为 0:
|
然后跑双脚本自检,直到两个退出码都为 0:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 scripts/check_article.py <文章目录>/article.md --type <主类型>
|
python3 scripts/check_article.py <文章目录>/article.md --type <主类型>
|
||||||
|
# 活人感硬禁:fail 项清零;「需人工判断」项按语境裁决,不改也说明理由
|
||||||
|
python3 scripts/check_prose.py <文章目录>/article.md
|
||||||
```
|
```
|
||||||
|
|
||||||
脚本输出加粗串读提要——自己读一遍那一串,像词云或像把段落重念一遍都是挑错了落点,这要人来判断,脚本量不了。
|
脚本输出加粗串读提要——自己读一遍那一串,像词云或像把段落重念一遍都是挑错了落点,这要人来判断,脚本量不了。
|
||||||
@@ -104,7 +111,7 @@ python3 scripts/check_article.py <文章目录>/article.md --type <主类型>
|
|||||||
|
|
||||||
0. **图片生产(默认执行,用户明确不要图时才跳过)**:正文图位与封面按 [visual-production.md](references/visual-production.md) 生产——交给 ckm-banner-design,或 SVG 自绘 + 浏览器截图均可,满足 brief 验收点即可。信息位图上的文字**逐字取自文章**,配色与所选排版主题一致;可编辑源文件(SVG/HTML)随 PNG 一起保留在 `imgs/src/`。生成后把正文图位占位替换为 `imgs/<文件名>.png` 真实路径——**终稿不得残留 placeholder**(此时 check_article 不再加 `--allow-placeholder`)。
|
0. **图片生产(默认执行,用户明确不要图时才跳过)**:正文图位与封面按 [visual-production.md](references/visual-production.md) 生产——交给 ckm-banner-design,或 SVG 自绘 + 浏览器截图均可,满足 brief 验收点即可。信息位图上的文字**逐字取自文章**,配色与所选排版主题一致;可编辑源文件(SVG/HTML)随 PNG 一起保留在 `imgs/src/`。生成后把正文图位占位替换为 `imgs/<文件名>.png` 真实路径——**终稿不得残留 placeholder**(此时 check_article 不再加 `--allow-placeholder`)。
|
||||||
|
|
||||||
1. 排版转换。默认走**主题引擎**——6 套实战主题组件库,融合自 gzh-design-skill(AGPL,见 [themes/README.md](references/themes/README.md))。主题以**该账号**画像的「排版偏好」为默认,按文章类型对照 [themes/theme-index.md](references/themes/theme-index.md) 的适用场景校准(教程/测评→摸鱼绿、深度观点→红白色系、设计科技→石墨极简、随笔→留白禅意、对比评测→摸鱼票据、内刊复盘→橄榄手记);选定后按 [themes/ENGINE.md](references/themes/ENGINE.md) 读该主题组件库 + 通用组件库装配 HTML,装配完**校验到全绿才算完成**:
|
1. 排版转换。默认走**主题引擎**——6 套实战主题组件库,融合自 gzh-design-skill(AGPL,见 [themes/README.md](references/themes/README.md))。主题用**第 2 步停点用户选定的**(画像默认 + 当场调整的结果);若停点被「一口气跑完」豁免跳过,按文章类型对照 [themes/theme-index.md](references/themes/theme-index.md) 的适用场景自动选(教程/测评→摸鱼绿、深度观点→红白色系、设计科技→石墨极简、随笔→留白禅意、对比评测→摸鱼票据、内刊复盘→橄榄手记),并在交付时说明选择理由。选定后按 [themes/ENGINE.md](references/themes/ENGINE.md) 读该主题组件库 + 通用组件库装配 HTML,装配完**校验到全绿才算完成**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 scripts/validate_gzh_html.py <文章目录>/article.html
|
python3 scripts/validate_gzh_html.py <文章目录>/article.html
|
||||||
@@ -148,7 +155,7 @@ python3 scripts/publish_draft.py <文章目录> --title "标题" --digest "摘
|
|||||||
|
|
||||||
## 失败模式与停手点
|
## 失败模式与停手点
|
||||||
|
|
||||||
- **素材不足以支撑所选类型** → 停下来列出缺什么,让用户补素材或换类型;不编造垫稿。
|
- **素材不足以支撑所选类型** → 停下来列出缺什么:能检索的先研究;要用户补的用 human-voice「三问」一次问全;研究后仍不足就缩题或压短篇幅,**宁可明显短于目标字数,不用推演和重复解释填满**;不编造垫稿。
|
||||||
- **审读意见互相冲突**(如保真要求与去模板化要求打架)→ 按手册判断顺序裁决:保信息 > 保类型 > 保作者声音 > 改表达;裁决不了的带两案问用户。
|
- **审读意见互相冲突**(如保真要求与去模板化要求打架)→ 按手册判断顺序裁决:保信息 > 保类型 > 保作者声音 > 改表达;裁决不了的带两案问用户。
|
||||||
- **用户要「直接发表/群发」** → 拒绝:同步止于草稿箱,发表由用户在公众号后台手动完成;凭证缺失时给配置指引并停,不代填。
|
- **用户要「直接发表/群发」** → 拒绝:同步止于草稿箱,发表由用户在公众号后台手动完成;凭证缺失时给配置指引并停,不代填。
|
||||||
- `check_article.py` 跑不过且原因不明 → 先修硬规则项;脚本本身出错时报告错误,改为人工对照 style-rules 逐项检查,不假装通过。
|
- `check_article.py` 跑不过且原因不明 → 先修硬规则项;脚本本身出错时报告错误,改为人工对照 style-rules 逐项检查,不假装通过。
|
||||||
@@ -159,7 +166,9 @@ python3 scripts/publish_draft.py <文章目录> --title "标题" --digest "摘
|
|||||||
- 不在简报类文章里强加个人故事、情绪曲线或价值升华;不为提高「共鸣」编造经历。
|
- 不在简报类文章里强加个人故事、情绪曲线或价值升华;不为提高「共鸣」编造经历。
|
||||||
- 不用「随着 AI 的发展」「综上所述」这类零信息套话开头收尾;连接词只在承担真实顺序、转折、总结功能时保留。
|
- 不用「随着 AI 的发展」「综上所述」这类零信息套话开头收尾;连接词只在承担真实顺序、转折、总结功能时保留。
|
||||||
- 不把技术术语当 AI 味删掉;技术解析保持应有正式度,不统一改成聊天语气。
|
- 不把技术术语当 AI 味删掉;技术解析保持应有正式度,不统一改成聊天语气。
|
||||||
- 不在没跑 check_article.py 的情况下宣称「自检通过」。
|
- 不在没跑 check_article.py 和 check_prose.py 的情况下宣称「自检通过」。
|
||||||
|
- 不写翻案腔——先立误解再推翻(「不是A,而是B」及一切变形)一次也不行,判断从正面下。
|
||||||
|
- 不添无来源的精确细节(时间、神态、天气、摆设、突现原话),不穿论坛服装(口头禅、粗口、错别字不是活人感)。
|
||||||
- 不把作者画像、素材中的私人内容复制进交付物或对外文件。
|
- 不把作者画像、素材中的私人内容复制进交付物或对外文件。
|
||||||
- 未获「一口气跑完」授权时,不把停点之间的多个步骤合并进同一回合执行。
|
- 未获「一口气跑完」授权时,不把停点之间的多个步骤合并进同一回合执行。
|
||||||
- 不把「在回复中展示了产物」当作「已获得确认」——确认只能来自停点之后用户的下一条消息。
|
- 不把「在回复中展示了产物」当作「已获得确认」——确认只能来自停点之后用户的下一条消息。
|
||||||
@@ -168,7 +177,8 @@ python3 scripts/publish_draft.py <文章目录> --title "标题" --digest "摘
|
|||||||
|
|
||||||
- 五类型路由与各类型结构:[article-types.md](references/article-types.md)
|
- 五类型路由与各类型结构:[article-types.md](references/article-types.md)
|
||||||
- 公众号排版硬规则、标点规范、产出配额、标题规范:[style-rules.md](references/style-rules.md)
|
- 公众号排版硬规则、标点规范、产出配额、标题规范:[style-rules.md](references/style-rules.md)
|
||||||
- 审读手册(去模板化 + AI 味指纹 + 平台合规 + 语义保真):[review-playbook.md](references/review-playbook.md)
|
- 审读手册(去模板化 + AI 味指纹 + 活人感审读 + 模型腔硬禁 + 平台合规 + 语义保真):[review-playbook.md](references/review-playbook.md)
|
||||||
|
- 活人感写法(材料关 / 说话位置 / 行文规则 / 分型浓度 / 成稿硬禁):[human-voice.md](references/human-voice.md)
|
||||||
- 排版主题库与装配规则(6 主题 + 通用组件 + 引擎规则,AGPL 来源与协议边界):[themes/README.md](references/themes/README.md) · [theme-index.md](references/themes/theme-index.md) · [ENGINE.md](references/themes/ENGINE.md)
|
- 排版主题库与装配规则(6 主题 + 通用组件 + 引擎规则,AGPL 来源与协议边界):[themes/README.md](references/themes/README.md) · [theme-index.md](references/themes/theme-index.md) · [ENGINE.md](references/themes/ENGINE.md)
|
||||||
- 作者画像(风格;个人层 + 分账号层,渐进填充):[author-profile.md](references/author-profile.md)
|
- 作者画像(风格;个人层 + 分账号层,渐进填充):[author-profile.md](references/author-profile.md)
|
||||||
- 常用事实卡(作者本人事实,渐进填充):[topic-facts.md](references/topic-facts.md)
|
- 常用事实卡(作者本人事实,渐进填充):[topic-facts.md](references/topic-facts.md)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
4. 有没有特别喜欢/不能接受的表达方式?(口语化程度、能否用梗、第一人称习惯)
|
4. 有没有特别喜欢/不能接受的表达方式?(口语化程度、能否用梗、第一人称习惯)
|
||||||
5. 文末固定要加什么?(关注引导/签名/往期推荐/什么都不加)
|
5. 文末固定要加什么?(关注引导/签名/往期推荐/什么都不加)
|
||||||
6. 排版偏好?(从 themes/theme-index.md 的 6 套主题选默认:摸鱼绿 / 红白色系 / 石墨极简 / 留白禅意 / 摸鱼票据 / 橄榄手记;简报类默认走 md2wechat 轻稿快档)
|
6. 排版偏好?(从 themes/theme-index.md 的 6 套主题选默认:摸鱼绿 / 红白色系 / 石墨极简 / 留白禅意 / 摸鱼票据 / 橄榄手记;简报类默认走 md2wechat 轻稿快档)
|
||||||
|
7. (可选)贴两三段你自己写的旧文或聊天记录作语气样本——只用于校准用词习惯与口语程度(语气层最弱的一层),不模仿口头禅,更不能替代材料与说话位置(见 [human-voice.md](human-voice.md))
|
||||||
|
|
||||||
## 个人层(全账号通用)
|
## 个人层(全账号通用)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# 活人感写法(第 4 步写初稿时加载)
|
||||||
|
|
||||||
|
> 方法论提炼自 [KKKKhazix/human-writing](https://github.com/KKKKhazix/human-writing)(MIT,活人感写作 skill),按本工坊的文章类型体系适配。核心论点:**活人感第一来自材料,第二来自说话位置,最后才是语气。** 口头禅、玩梗、错别字、粗口不是活人感;无来源的「凌晨三点的冷咖啡」是假细节,越具体 AI 味越重。
|
||||||
|
|
||||||
|
## 一、动笔前先过材料关
|
||||||
|
|
||||||
|
- 非虚构正文计划超过 1200 字时,先逐条列出**至少 5 件具体材料**(动作、数字、时间、地点、原话、失败、代价、后续结果),注明来自用户哪句话或哪份可靠来源。概括性类别(「一些案例」)不算,五条相邻的道理也不算——要能组成一条实际过程。
|
||||||
|
- 列不出五件:能检索的先研究再重新计数;依赖用户经历的,用下方「三问」一次问全;用户不让问且研究后仍不足,**缩题或写短稿**,宁可明显短于目标字数,不用重复解释和推演灌字数。
|
||||||
|
- **三问**(问用户,一次问完):你和这件事实际发生过什么关系?哪个瞬间、数字、动作或原话最让你在意?你现在最想说的判断是什么?
|
||||||
|
|
||||||
|
## 二、找到说话位置(内部回答,不交给用户)
|
||||||
|
|
||||||
|
1. 谁在说这件事——他凭什么知道,哪些只是在推测。查资料、亲历、行业熟悉、认真研究后形成判断,四种位置都行;**不能把查资料写成亲历,把猜测写成内幕**。没有亲历作者也不消失——仍然可以交代更相信哪种解释、哪条材料改变了看法。
|
||||||
|
2. 什么事让他现在想说(一条新闻、一次经历、一个想不通的小事)。
|
||||||
|
3. 手里能托住文章的东西(优先动作、数字、原话、失败、代价、后果)。
|
||||||
|
4. 他对哪一点有明确判断,依据是什么,到哪里为止。
|
||||||
|
5. 读者读完上一段,最自然会追问什么。
|
||||||
|
|
||||||
|
## 三、最有活人感的东西是信息来路
|
||||||
|
|
||||||
|
每篇**挑一两项**自然出现,全部凑齐会变成新模板:
|
||||||
|
|
||||||
|
- 哪件事让作者留意到这个问题
|
||||||
|
- 他一开始怎样理解,后来被哪条材料纠正
|
||||||
|
- 哪个细节只有做过这件事的人才会注意
|
||||||
|
- 哪两份资料打架,最后怎么取舍
|
||||||
|
- 哪个判断只是个人看法,作者愿意承担但不冒充定论(「这块我也没完全弄明白」后面必须真有信息)
|
||||||
|
|
||||||
|
## 四、行文规则
|
||||||
|
|
||||||
|
- **开头尽快碰到事情**。直答、讲刚发生的一幕、或从一个反常的小事实说起;不预告全文结构。
|
||||||
|
- **每段新增一件东西**。新事实、新动作、新例子、新区别、新后果都算;同一观点换说法不算推进。后一段接住前一段留下的问题——推进靠材料和因果,不靠「更深一层」「真正的问题」这类路标。
|
||||||
|
- **句子主干先行**。先让主语和动作出现,时间、原因、条件、例子往后接。一句话三四个「的」时,把做事的人提到前面。长定语拆成小句。
|
||||||
|
- **松紧呼吸**。句长要有高低差——十个字的句子可以挨着四十字的句子;连续短句像敲桌子,连续长句让人等不到落脚。段落不必等长,普通地方普通地结束;单句成段只留给确实需要停一下的地方。
|
||||||
|
- **连词减半**。「因为/所以/但是/同时/此外」中文小句靠语序和事理就能相接,删掉一半,读不断的不补回来。
|
||||||
|
- **动词不名词化**。「对方案进行了调整」写「把方案改了」;「实现了收入的增长」写「多挣了多少」。
|
||||||
|
- **该重复就重复**。一段里「修表」可以出现四次;为躲重复换同义词(修表→这门手艺→这项技能)文章会油。
|
||||||
|
- **感情写出来就停**。动作、物件、原话已经传出情绪,不要追在后面替读者解释。比喻落下不翻译含义。
|
||||||
|
- **允许正常的不完美**。正常的重复、补充、自我修正、短暂岔话都可以——但每处要从眼前内容长出来,不按固定间隔表演随意。
|
||||||
|
- **判断可以偏、可以有情绪**。把依据放在附近;材料支持七成就说到七成,剩下三成承认不知道,比补五个限定词更可信。幽默从事情自身的荒唐处出来(宏大说法碰到现实小麻烦的落差),不另编笑话,笑点落下继续讲事。
|
||||||
|
- **知识等走到那里再出现**。背景能解释人物为何只能这样选、改变风险、推翻直觉才放进来;只证明作者知道很多的删。绕出去要带回一件东西,否则是知识展览。
|
||||||
|
- **写完就停**。不强行升华、首尾呼应、时代意义;不在末段重新摘要全文。
|
||||||
|
- **判据句**(校准方向用):「他毕业后离开上海,去了成都。那套量化程序已经跑过一段时间,他觉得可以全职试试。收入会不会稳定,当时没人知道。」——给动作、条件与风险;「他关掉一条好走的路,把命运押上赌桌」只有姿势。
|
||||||
|
|
||||||
|
## 五、假细节与假口语红线
|
||||||
|
|
||||||
|
- **无来源的精确时间、神态、天气、房间摆设、烟酒食物、突现的原话,全部不要**——装饰性细节只负责画面显得真实,不改变理解就删。好细节会改变理解:一笔价格说明风险,一段原话说明关系,一次失败说明旧办法为何行不通。
|
||||||
|
- **不穿论坛服装**。「老铁」「兄弟们」「咱就是说」「泡杯茶慢慢说」只在说话者真会这样说时留。不为制造活人感主动加粗口、错别字、三连省略号、括号吐槽。
|
||||||
|
- 用户说「看见」,不能扩成「试过」;用户给截图,不能补注册、付款和成功运行;记不清原话就写大意或请他确认。
|
||||||
|
|
||||||
|
## 六、按文章类型分浓度
|
||||||
|
|
||||||
|
- **叙事 / 复盘类**:浓度最高。信息来路、自我修正、真实犹豫、带立场的判断都用上;素材里的失败和代价优先保留。
|
||||||
|
- **技术 / 教程 / 测评类**:克制。保持术语与正式度(不统一改聊天语气),活人感主要来自:亲历过的坑与失败处理、真实的使用代价、明确的个人取舍(「我的看法是值,但只对每周真拍两天的人值」)。
|
||||||
|
- **简报类**:基本不加。事实与条目本身就成立,强加个人故事违反反例黑名单。
|
||||||
|
|
||||||
|
## 七、成稿硬禁令(第 6 步 `check_prose.py` 机器核验,命中即 FAIL)
|
||||||
|
|
||||||
|
- **翻案腔零容忍**(一次也不行,禁的是动作不是字面):「不是A,而是B」「并非…而是…」「不在于…而在于…」「与其说…不如说…」「表面…实际…」「看似…实则…」「你以为…其实…」「回头才发现」「A不重要,重要的是B」及一切变形。判断直接从正面下,先给判断再给依据。
|
||||||
|
- **三项以上同构排比**:留两项,第三项换说法或删。
|
||||||
|
- **破折号(——、—、–)正文清零**;**冒号只许引出人物直接原话**(「一句话总结:」「核心是:」禁),网址、代码、机器字段除外。
|
||||||
|
- **洞察路标**:「更微妙的是」「还有一层」「只说对了一半」「值得注意的是」「需要指出的是」「从某种意义上说」「说白了」「说穿了」「先说结论」。
|
||||||
|
- **抽象名词配具体动词的抒情**(时间保管细节、岁月磨平、焦虑显形);**名词化句式**(进行了/实现了/完成了对/起到了…作用);**商业黑话与借喻**(赋能/抓手/闭环/底层逻辑;仓库、温度、死亡、浪潮、钥匙、底座包装抽象概念)。
|
||||||
|
- 「不只…还…」是正常递进可以用;出现在翻案位置按翻案腔处理。原话命中禁令时改转述,不靠引号保留。
|
||||||
|
|
||||||
|
完整禁令与改法见 [review-playbook.md](review-playbook.md) 的「模型腔硬禁清单」;机器核验见 `scripts/check_prose.py`(改禁词必须同步改脚本,不让脚本偷偷多一套规则)。
|
||||||
@@ -48,7 +48,7 @@ AI 味的本质是**太顺、太均匀、没有取舍**——不是用词高级
|
|||||||
| # | 指纹 | 典型样子 | 强度 |
|
| # | 指纹 | 典型样子 | 强度 |
|
||||||
|---|------|---------|------|
|
|---|------|---------|------|
|
||||||
| 1 | 万能背景开头 | 「随着 AI 技术的发展」「在当今这个时代」 | 强 |
|
| 1 | 万能背景开头 | 「随着 AI 技术的发展」「在当今这个时代」 | 强 |
|
||||||
| 2 | 「不是 X,而是 Y」滥用 | 单次是修辞,**800 字内 ≥3 次**才算命中 | 强 |
|
| 2 | 翻案腔 | 单次即命中——已升级为硬禁,见下方「模型腔硬禁清单」(check_prose.py 核验) | 强 |
|
||||||
| 3 | 面面俱到没取舍 | 利弊前提例外全讲一遍,什么都不得罪 | 强 |
|
| 3 | 面面俱到没取舍 | 利弊前提例外全讲一遍,什么都不得罪 | 强 |
|
||||||
| 4 | 空洞祝福收尾 | 「愿你…」「你值得…」谁都适用谁都记不住 | 强 |
|
| 4 | 空洞祝福收尾 | 「愿你…」「你值得…」谁都适用谁都记不住 | 强 |
|
||||||
| 5 | 工整排比成瘾 | 单处排比是好文笔,**几乎每节都排比**才算 | 中 |
|
| 5 | 工整排比成瘾 | 单处排比是好文笔,**几乎每节都排比**才算 | 中 |
|
||||||
@@ -65,6 +65,49 @@ AI 味的本质是**太顺、太均匀、没有取舍**——不是用词高级
|
|||||||
|
|
||||||
**防误判**:拿不准时宁可不报;文章主题本就关于价值观时谈意义是切题;体裁本身工整(清单文)时排比是设计。诊断目的是让稿子看清自己哪里偷懒,不是凑数找毛病。
|
**防误判**:拿不准时宁可不报;文章主题本就关于价值观时谈意义是切题;体裁本身工整(清单文)时排比是设计。诊断目的是让稿子看清自己哪里偷懒,不是凑数找毛病。
|
||||||
|
|
||||||
|
## 活人感审读(正面检查,与 AI 味指纹互补)
|
||||||
|
|
||||||
|
> 方法论改写自 [KKKKhazix/human-writing](https://github.com/KKKKhazix/human-writing)(MIT)。指纹只指出「太平滑」,这一节回答「哪里能看出是一个人在说话」。
|
||||||
|
|
||||||
|
**谁在说**——读完回答四件事:
|
||||||
|
|
||||||
|
- 作者凭什么知道这件事(亲历 / 做过调查 / 熟悉行业 / 查完资料形成判断,四种位置都行;**查资料不能写成亲历,猜测不能写成内幕**)
|
||||||
|
- 哪一处能看出他真的在意
|
||||||
|
- 哪个判断有材料托着、哪个情绪有动作或关系托着
|
||||||
|
- **哪几段换一个模型也能原样写出来**——这类段落优先处理:补信息来路、作者取舍、具体材料;没东西可补就删。不要用加「我觉得」「说真的」修复匿名感,声音来自取舍
|
||||||
|
|
||||||
|
**推进**——给每段标一个主要作用(动作/事实/解释/例子/疑问/判断/背景/情绪):连续几段做同一件事会拖;后段没接住前段留下的问题要调序;只把前段换套说法的直接合并或删。**压缩试验**:删掉三分之一后事实、动作、判断和阅读体验几乎没变,说明原稿在注水,保留删短的版本。
|
||||||
|
|
||||||
|
**表演性中文**:
|
||||||
|
|
||||||
|
- **假深刻**——单独截图好看、放回正文没增加事实/解释/情绪的句子,大部分删;连续几段用短判断收尾,留最有材料的一处
|
||||||
|
- **假具体**——精确时间、天气、神态、房间摆设、突现的原话,逐项问来自哪里、会不会改变后文;无来源也无作用就删。好细节改变理解(一笔价格说明风险,一段原话说明关系),装饰性细节只负责画面
|
||||||
|
- **假口语**——称呼语、粗口、错别字只在说话者真会这样说时留
|
||||||
|
- **抽象名词**——连续出现时问谁做了什么、影响谁、钱时间责任落在哪;一句话能原样换进十篇商业文章,说明没有当前材料
|
||||||
|
- **比喻换场**——短距离出现三套借喻(道路/战争/建筑/温度/仓储/海洋/身体)先全部还原本义,本义已清楚的一个也不放回
|
||||||
|
|
||||||
|
**结尾**——把最后两段分别删掉再读,删掉更有力就让文章提前结束;最后一段重新概括全文的先删;搜「时代、文明、未来、世界、历史、奇迹、所有人、全人类」——正文没有持续处理这个尺度,结尾回到具体事实、人物动作或当前判断。
|
||||||
|
|
||||||
|
**冷读**(暂时忘掉规则只回答):哪里让我相信作者确实知道这件事?哪里让我愿意继续读?哪一段像人在处理一个问题、哪一段像模型在完成写作任务?哪句话说得比材料大?文章在哪一句已经结束?
|
||||||
|
|
||||||
|
## 模型腔硬禁清单(命中即必改,与平台合规同级;`check_prose.py` 机器核验)
|
||||||
|
|
||||||
|
禁的是修辞动作,不是字面——换一套字做同一个动作仍算命中。改法只有一种:找到这句话原本想下的判断,从正面说出来,依据放旁边;不找另一种漂亮句式替换。
|
||||||
|
|
||||||
|
| 类别 | 内容 |
|
||||||
|
|---|---|
|
||||||
|
| 翻案腔 | 先立一个读者并没有的误解再推翻抬价,**一次也不行**。外衣:不是A而是B / 并非…而是… / 不在于…而在于… / 与其说…不如说… / 表面…实际… / 看似…实则… / 你以为…其实… / 回头才发现 / A不重要,重要的是B / 答案恰恰相反,及一切变形。文章真用材料走过从误解到修正的过程,可保留一次自我修正,但不穿任何外衣 |
|
||||||
|
| 同构排比 | 三项以上同一句型/引导词连排,留两项,第三项换说法或删 |
|
||||||
|
| 名词化 | 进行了 / 实现了 / 完成了对 / 起到了…作用 / 具有…意义,还原成直接动词 |
|
||||||
|
| 提示性冒号 | 冒号只允许引出人物直接原话;「一句话总结:」「核心是:」禁;网址、代码、机器字段除外 |
|
||||||
|
| 破折号 | ——、—、– 正文清零(金句卡出处用全角括号) |
|
||||||
|
| 洞察路标 | 说白了 / 说穿了 / 先说结论 / 更微妙的是 / 还有一层 / 只说对了一半 / 值得注意的是 / 需要指出的是 / 从某种意义上说 |
|
||||||
|
| 绝对禁词 | 赋能、抓手、商业闭环、价值闭环、能力沉淀、拉通、底层逻辑、顶层设计、认知跃迁、价值释放、能力建设、降本增效、内容矩阵、全链路、组合拳、打开想象空间、结构性机会、关键命题、深层逻辑、技术底座、公共底座、技术主权、单点风险、主脊柱、材料锚点、认知增量、迭代闭环——改成人、动作、钱、时间与后果 |
|
||||||
|
| 语境词(本义成立保留) | 沉淀、颗粒度、对齐、协同、链路、生态位、心智、范式、方法论、核心变量、打法、想象空间、闭环——给普通事情抬价时才改写 |
|
||||||
|
| 抒情借喻 | 抽象名词配具体动词(时间保管细节、岁月磨平、焦虑显形);仓库、抽屉、温度、死亡、坍塌、浪潮、钥匙、底座包装抽象概念。写具体事物不受影响。安放、抵达、微光、褶皱、丰盈、滚烫、轻盈等词给抽象概念穿衣服时删。「」自造短语一篇出现三处以上多半在造金句,拆掉 |
|
||||||
|
|
||||||
|
「不只…还…」是正常递进可以用;出现在翻案位置、用来给下文抬价时按翻案腔处理。原话命中禁令改成转述,不靠引号保留。第 6 步跑 `scripts/check_prose.py`,fail 项与本清单一致;增删禁词必须同步改脚本,不让脚本偷偷多一套规则。
|
||||||
|
|
||||||
## 其他审读线索
|
## 其他审读线索
|
||||||
|
|
||||||
- **空泛铺垫与伪深度**:只有预告作用的开场、无解释力的格言、没有真实反对者的自我辩护——删掉不损失事实/判断/逻辑的就删
|
- **空泛铺垫与伪深度**:只有预告作用的开场、无解释力的格言、没有真实反对者的自我辩护——删掉不损失事实/判断/逻辑的就删
|
||||||
@@ -93,4 +136,4 @@ AI 味的本质是**太顺、太均匀、没有取舍**——不是用词高级
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
致谢:本手册的语义保真框架与「模式不是禁词表」原则改写自 [mp-article-writor](https://github.com/balabalabalading/huuuuuuho-skills/tree/main/skills/mp-article-writor)(MIT);AI 味指纹与阈值化防误判改写自 [wechat-article-skills](https://github.com/aiworkskills/wechat-article-skills)(Apache-2.0)的 ai-flavor-check 方法论。仅借方法,未复制其表达与依赖。
|
致谢:本手册的语义保真框架与「模式不是禁词表」原则改写自 [mp-article-writor](https://github.com/balabalabalading/huuuuuuho-skills/tree/main/skills/mp-article-writor)(MIT);AI 味指纹与阈值化防误判改写自 [wechat-article-skills](https://github.com/aiworkskills/wechat-article-skills)(Apache-2.0)的 ai-flavor-check 方法论;活人感审读与模型腔硬禁清单改写自 [KKKKhazix/human-writing](https://github.com/KKKKhazix/human-writing)(MIT)。仅借方法与清单,适配与融合为本工坊改动。
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
- **专有名词**:按官方写法(macOS、iOS、GitHub、ChatGPT);有通用中文译名的优先中文,译名不通行时首次出现标注 `中文译名(Original Name)`
|
- **专有名词**:按官方写法(macOS、iOS、GitHub、ChatGPT);有通用中文译名的优先中文,译名不通行时首次出现标注 `中文译名(Original Name)`
|
||||||
- **缩略语**:首次出现括注全称(除非广为人知)
|
- **缩略语**:首次出现括注全称(除非广为人知)
|
||||||
- **省略号**:用……,不用三个句点
|
- **省略号**:用……,不用三个句点
|
||||||
|
- **破折号与冒号**:正文不用任何破折号(——、—、–);冒号只允许引出人物直接原话,网址、代码、机器字段除外(模型腔重灾区,`check_prose.py` 核验,详见 [human-voice.md](human-voice.md) 第七节)
|
||||||
|
- **硬停词与黑话**:说白了/说穿了/先说结论、赋能/抓手/底层逻辑等商业黑话零出现;详见 human-voice.md 硬禁清单
|
||||||
- **日期**:完整句中用 `2026 年 10 月 5 日` 式汉字单位表达;提国外事件标注当地时间换算
|
- **日期**:完整句中用 `2026 年 10 月 5 日` 式汉字单位表达;提国外事件标注当地时间换算
|
||||||
- **工具名精度**:素材指向具体产品时用准确名称;素材没说名称时保留「某个模型」「AI 工具」等概括表达,不猜名字装具体
|
- **工具名精度**:素材指向具体产品时用准确名称;素材没说名称时保留「某个模型」「AI 工具」等概括表达,不猜名字装具体
|
||||||
- **链接**:链接文本要能说明目标内容,不用「点击这里」「这篇」;清理短链和跟踪参数
|
- **链接**:链接文本要能说明目标内容,不用「点击这里」「这篇」;清理短链和跟踪参数
|
||||||
@@ -36,8 +38,8 @@
|
|||||||
- 每处加粗要能独立看懂:数字连着它的意思(`**省 88% Token**`),判断连着它的对象(`**打断不等于撤销**`)
|
- 每处加粗要能独立看懂:数字连着它的意思(`**省 88% Token**`),判断连着它的对象(`**打断不等于撤销**`)
|
||||||
- 不加粗整句空泛概括;不在每段首句同一位置加粗;一段最多两处
|
- 不加粗整句空泛概括;不在每段首句同一位置加粗;一段最多两处
|
||||||
- **摘要 ≠ 导语**:front matter `digest`(进公众号后台的摘要)与正文开头导语不得逐字相同——读者会同一句读两遍
|
- **摘要 ≠ 导语**:front matter `digest`(进公众号后台的摘要)与正文开头导语不得逐字相同——读者会同一句读两遍
|
||||||
- **金句卡至多一处**:全文最有转发价值的那句写成 `> 金句。 —— 出处`;两处以上卡片退化成装饰条;不带出处就是普通引用
|
- **金句卡至多一处**:全文最有转发价值的那句写成 `> 金句。(出处)`;两处以上卡片退化成装饰条;不带出处就是普通引用
|
||||||
- **枚举用列表**:三项以上并列写成 `- **标签**:说明`,不压进一个长段落
|
- **枚举用列表**:三项以上并列写成 `- **标签**,说明`(标签后用逗号,不用冒号——冒号是 check_prose 硬禁项),不压进一个长段落
|
||||||
- **`---` 分隔**:只在明显转场处用,独占一行
|
- **`---` 分隔**:只在明显转场处用,独占一行
|
||||||
- **图位密度**:大纲阶段按下表选一档(统一词汇,不各说各话),写完自查数量:
|
- **图位密度**:大纲阶段按下表选一档(统一词汇,不各说各话),写完自查数量:
|
||||||
|
|
||||||
|
|||||||
@@ -19,13 +19,13 @@
|
|||||||
|
|
||||||
## 智能处理(装配时必须做)
|
## 智能处理(装配时必须做)
|
||||||
|
|
||||||
1. **章节自动编号**:按 `##` 顺序编 01/02/03…;末章为结语/总结时用主题库指定的结语编号变体(如 `∞`)。
|
1. **章节自动编号**:按正文章节标题顺序编 01/02/03…(「融合」本工坊文章的章节是 `###`,按 `###` 计数——见 style-rules 标题层级,不用 `##`);末章为结语/总结时用主题库指定的结语编号变体(如 `∞`)。
|
||||||
2. **英文标签**:据中文章节标题生成(实测→TEST、教程→TUTORIAL、总结→SUMMARY…),主题库有槽位才用。
|
2. **英文标签**:据中文章节标题生成(实测→TEST、教程→TUTORIAL、总结→SUMMARY…),主题库有槽位才用。
|
||||||
3. **正文关键词下划线(核心特色)**:对每个正文段落主动挑 1–3 个最重要短语(4–15 字),用 theme-index「正文下划线 CSS」列的值标记——优先核心观点、结论、关键数据、专有名词。原文没有加粗也要标;与写作侧「加粗是扫读落点」互补:md 里的加粗进锚点层,下划线在装配时补标。不整段划线。
|
3. **正文关键词下划线(核心特色)**:对每个正文段落主动挑 1–3 个最重要短语(4–15 字),用 theme-index「正文下划线 CSS」列的值标记——优先核心观点、结论、关键数据、专有名词。原文没有加粗也要标;与写作侧「加粗是扫读落点」互补:md 里的加粗进锚点层,下划线在装配时补标。不整段划线。
|
||||||
4. **引言关键词高亮**:开头金句的核心词用高亮组件。
|
4. **引言关键词高亮**:开头金句的核心词用高亮组件。
|
||||||
5. **目录**:主题库有目录组件时,从全部 `##` 挑最重要的 3 个作精选导读,不是全量章节列表。
|
5. **目录**:主题库有目录组件时,从全部 `##` 挑最重要的 3 个作精选导读,不是全量章节列表。
|
||||||
6. **签名区**:「融合」只在**该账号画像配置了文末固定块**时渲染(对应 style-rules「不自动追加关注引导」红线),文案逐字取自画像;画像没配就不生成签名区。原文末尾已有的签名/引导段并入这唯一一处,不在中间或多处出现。
|
6. **签名区**:「融合」只在**该账号画像配置了文末固定块**时渲染(对应 style-rules「不自动追加关注引导」红线),文案逐字取自画像;画像没配就不生成签名区。原文末尾已有的签名/引导段并入这唯一一处,不在中间或多处出现。
|
||||||
7. **中文全角标点**:正文标点全角(,。!?:;""''()——…),生成时直接写弯引号;代码块/行内代码/URL/英文标识符内保持原样。
|
7. **中文全角标点**:正文标点全角(,。!?;()…),生成时直接写全角。「融合」引号保持 article.md 的直角引号「」,不做弯引号转换;破折号与提示性冒号在正文禁用(human-voice 硬禁,装配不得引入);代码块/行内代码/URL/英文标识符内保持原样。
|
||||||
8. **列表**:按主题库映射规则;无专属组件时转带缩进正文段。
|
8. **列表**:按主题库映射规则;无专属组件时转带缩进正文段。
|
||||||
|
|
||||||
## 视觉层级(3 层,防滥用)
|
## 视觉层级(3 层,防滥用)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ TYPE_RANGES = {
|
|||||||
"简报": (1500, 3000), "测评": (2500, 5000), "复盘": (2500, 6000),
|
"简报": (1500, 3000), "测评": (2500, 5000), "复盘": (2500, 6000),
|
||||||
"技术": (2500, 6000), "叙事": (2000, 5000),
|
"技术": (2500, 6000), "叙事": (2000, 5000),
|
||||||
}
|
}
|
||||||
FILLER_PATTERNS = ["在当今", "随着{}的发展", "综上所述", "总而言之", "值得注意的是",
|
FILLER_PATTERNS = ["在当今", "随着({})的发展", "综上所述", "总而言之", "值得注意的是",
|
||||||
"不难发现", "让我们来看看", "下面让我们", "众所周知"]
|
"不难发现", "让我们来看看", "下面让我们", "众所周知"]
|
||||||
HAN = r"\u4e00-\u9fff"
|
HAN = r"\u4e00-\u9fff"
|
||||||
|
|
||||||
@@ -38,7 +38,8 @@ def split_blocks(md):
|
|||||||
def check(path, art_type, strict, allow_placeholder=False):
|
def check(path, art_type, strict, allow_placeholder=False):
|
||||||
errors, warns, infos = [], [], []
|
errors, warns, infos = [], [], []
|
||||||
md = open(path, encoding="utf-8").read()
|
md = open(path, encoding="utf-8").read()
|
||||||
blocks = split_blocks(md)
|
fm_pre = re.match(r"^---\n.*?\n---\n", md, re.S)
|
||||||
|
blocks = split_blocks(md[fm_pre.end():] if fm_pre else md)
|
||||||
|
|
||||||
# ---- 标题层级 ----
|
# ---- 标题层级 ----
|
||||||
h1 = [ln for ln, f in blocks if not f and re.match(r"^# ", ln)]
|
h1 = [ln for ln, f in blocks if not f and re.match(r"^# ", ln)]
|
||||||
|
|||||||
+644
@@ -0,0 +1,644 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""检查中文成稿的硬禁令与常见模型化形状。只报警,不自动改文。
|
||||||
|
|
||||||
|
来源:https://github.com/KKKKhazix/human-writing (MIT,版权归 Human Writing Skill
|
||||||
|
contributors)。本工坊原样引入未改检测规则;禁令清单见 references/human-voice.md
|
||||||
|
第七节与 review-playbook.md「模型腔硬禁清单」(增删禁词须同步改本脚本)。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import collections
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
HARD_STOPS = (
|
||||||
|
"说白了",
|
||||||
|
"说穿了",
|
||||||
|
"先说结论",
|
||||||
|
)
|
||||||
|
|
||||||
|
HARD_JARGON = (
|
||||||
|
"赋能",
|
||||||
|
"抓手",
|
||||||
|
"商业闭环",
|
||||||
|
"价值闭环",
|
||||||
|
"能力沉淀",
|
||||||
|
"拉通",
|
||||||
|
"底层逻辑",
|
||||||
|
"顶层设计",
|
||||||
|
"认知跃迁",
|
||||||
|
"价值释放",
|
||||||
|
"能力建设",
|
||||||
|
"降本增效",
|
||||||
|
"内容矩阵",
|
||||||
|
"全链路",
|
||||||
|
"组合拳",
|
||||||
|
"打开想象空间",
|
||||||
|
"结构性机会",
|
||||||
|
"关键命题",
|
||||||
|
"深层逻辑",
|
||||||
|
"技术底座",
|
||||||
|
"公共底座",
|
||||||
|
"技术主权",
|
||||||
|
"单点风险",
|
||||||
|
"主脊柱",
|
||||||
|
"材料锚点",
|
||||||
|
"认知增量",
|
||||||
|
"迭代闭环",
|
||||||
|
)
|
||||||
|
|
||||||
|
CONTEXT_JARGON = (
|
||||||
|
"沉淀",
|
||||||
|
"颗粒度",
|
||||||
|
"对齐",
|
||||||
|
"协同",
|
||||||
|
"链路",
|
||||||
|
"生态位",
|
||||||
|
"心智",
|
||||||
|
"范式",
|
||||||
|
"方法论",
|
||||||
|
"核心变量",
|
||||||
|
"打法",
|
||||||
|
"想象空间",
|
||||||
|
"闭环",
|
||||||
|
"不丢",
|
||||||
|
)
|
||||||
|
|
||||||
|
LYRIC_WORDS = (
|
||||||
|
"安放",
|
||||||
|
"抵达",
|
||||||
|
"微光",
|
||||||
|
"褶皱",
|
||||||
|
"丰盈",
|
||||||
|
"滚烫",
|
||||||
|
"轻盈",
|
||||||
|
"赤裸",
|
||||||
|
"剥开",
|
||||||
|
)
|
||||||
|
|
||||||
|
ROAD_SIGNS = (
|
||||||
|
"更微妙的是",
|
||||||
|
"还有一层",
|
||||||
|
"只说对了一半",
|
||||||
|
"值得注意的是",
|
||||||
|
"需要指出的是",
|
||||||
|
"从某种意义上说",
|
||||||
|
)
|
||||||
|
|
||||||
|
ROAD_STRIP_CHARS = "。!?!? \n"
|
||||||
|
|
||||||
|
FORBIDDEN_PUNCTUATION = {
|
||||||
|
":": "中文冒号",
|
||||||
|
":": "英文冒号",
|
||||||
|
"—": "破折号",
|
||||||
|
"–": "连接号式破折号",
|
||||||
|
}
|
||||||
|
|
||||||
|
PIVOT_PATTERNS = (
|
||||||
|
re.compile(r"(?:并)?不是[^。!?\n]{0,90}而是"),
|
||||||
|
re.compile(r"并非[^。!?\n]{0,90}而是"),
|
||||||
|
re.compile(r"不在于[^。!?\n]{0,90}而在于"),
|
||||||
|
re.compile(r"与其说[^。!?\n]{0,90}(?:不如|毋宁|倒不如)"),
|
||||||
|
re.compile(r"[。!?!?]\s*而是"),
|
||||||
|
re.compile(r"表面(?:上)?[^。!?\n]{0,90}(?:其实|实际|实则)"),
|
||||||
|
re.compile(r"看似[^。!?\n]{0,90}(?:其实|实际|实则)"),
|
||||||
|
)
|
||||||
|
|
||||||
|
SEMANTIC_PIVOT_PATTERNS = (
|
||||||
|
re.compile(r"(?:总|一直|曾|都)?以为[^!?\n]{2,60}?(?:其实|才发现|才明白|才知道|后来才)"),
|
||||||
|
re.compile(r"(?:总|都|一直)以为[^!?\n]{2,60}?[。,](?:可|但|其实)"),
|
||||||
|
re.compile(r"回头(?:看|一看)?才(?:发现|明白|知道)"),
|
||||||
|
re.compile(r"(?:并)?不是[^。!?\n]{1,40},(?:更|才)?是[^,。!?\n]"),
|
||||||
|
re.compile(r"从来(?:都)?(?:不是|与[^。!?,\n]{1,12}无关)"),
|
||||||
|
re.compile(r"答案(?:是否定的|恰恰相反)|恰恰相反"),
|
||||||
|
re.compile(r"表面(?:上)?[^!?\n]{0,60}。[^!?\n]{0,12}(?:其实|实际|实则)"),
|
||||||
|
re.compile(r"看似[^!?\n]{0,60}。[^!?\n]{0,12}(?:其实|实际|实则)"),
|
||||||
|
re.compile(r"[^,。!?\n]{1,12}不重要,(?:重要|要紧)的是"),
|
||||||
|
re.compile(r"真正[^,。!?\n]{0,16}的(?:,)?是"),
|
||||||
|
re.compile(r"不只(?:是)?[^。!?\n]{0,90}(?:还|也)"),
|
||||||
|
)
|
||||||
|
|
||||||
|
NOMINALIZATION_PATTERNS = (
|
||||||
|
re.compile(r"进行(?:了|一次|一场|着)?[^。,!?\n]{0,10}(?:调整|优化|升级|分析|讨论|沟通|梳理|复盘|迭代|探索|尝试|思考|规划|布局)"),
|
||||||
|
re.compile(r"实现了?[^。,!?\n]{0,14}的?[^。,!?\n]{0,6}(?:提升|增长|突破|转变|跃升|落地)"),
|
||||||
|
re.compile(r"完成了?对[^。,!?\n]{0,16}的"),
|
||||||
|
re.compile(r"起到了?[^。,!?\n]{0,12}的?作用"),
|
||||||
|
re.compile(r"具有[^。,!?\n]{0,10}(?:意义|价值)"),
|
||||||
|
)
|
||||||
|
|
||||||
|
CONJUNCTIONS = (
|
||||||
|
"因为",
|
||||||
|
"所以",
|
||||||
|
"但是",
|
||||||
|
"然而",
|
||||||
|
"同时",
|
||||||
|
"此外",
|
||||||
|
"而且",
|
||||||
|
"并且",
|
||||||
|
"因此",
|
||||||
|
"不仅",
|
||||||
|
)
|
||||||
|
|
||||||
|
ROAD_SIGN_PATTERNS = (
|
||||||
|
re.compile(
|
||||||
|
rf"(?:^|[。!?!?]\s*){re.escape(ROAD_SIGNS[0])}[^。!?!?\n]{{0,24}}",
|
||||||
|
re.MULTILINE,
|
||||||
|
),
|
||||||
|
re.compile(
|
||||||
|
rf"(?:^|[。!?!?]\s*){re.escape(ROAD_SIGNS[1])}(?=(?:更|原因|问题|意思|考虑|变化|逻辑|价值|作用|风险|影响|值得|很少|不容易|常被|往往))[^。!?!?\n]{{0,24}}",
|
||||||
|
re.MULTILINE,
|
||||||
|
),
|
||||||
|
*(
|
||||||
|
re.compile(
|
||||||
|
rf"(?:^|[。!?!?]\s*){re.escape(phrase)}[^。!?!?\n]{{0,24}}",
|
||||||
|
re.MULTILINE,
|
||||||
|
)
|
||||||
|
for phrase in ROAD_SIGNS[2:]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
SOFT_MARKERS = (
|
||||||
|
"真正",
|
||||||
|
"本质上",
|
||||||
|
"更深层次",
|
||||||
|
"归根结底",
|
||||||
|
"换句话说",
|
||||||
|
"不可否认",
|
||||||
|
"核心是",
|
||||||
|
"关键在于",
|
||||||
|
"这意味着",
|
||||||
|
)
|
||||||
|
|
||||||
|
REPEATED_OPENERS = (
|
||||||
|
"其实",
|
||||||
|
"不过",
|
||||||
|
"当然",
|
||||||
|
"所以",
|
||||||
|
"但是",
|
||||||
|
"后来",
|
||||||
|
"当时",
|
||||||
|
"很多人",
|
||||||
|
"问题是",
|
||||||
|
"更重要的是",
|
||||||
|
"说到这里",
|
||||||
|
)
|
||||||
|
|
||||||
|
LEFT_BRANCH_PATTERNS = (
|
||||||
|
re.compile(r"(?:^|[。!?]\s*)在[^,。!?\n]{12,70}(?:以后|之后|之前|以前|过程中|情况下|背景下),"),
|
||||||
|
re.compile(r"(?:^|[。!?]\s*)那些[^,。!?\n]{10,60}的[^,。!?\n]{2,30}[,。]"),
|
||||||
|
re.compile(r"(?:^|[。!?]\s*)(?:真正|最终|最后)让[^,。!?\n]{8,70}的,是"),
|
||||||
|
)
|
||||||
|
|
||||||
|
METAPHOR_FIELDS = {
|
||||||
|
"温度": ("降温", "升温", "冷却", "余温", "温度最高"),
|
||||||
|
"生死战争": ("杀死", "死因", "枪响", "开火", "战场", "引爆", "弹药"),
|
||||||
|
"建筑灾害": ("坍塌", "崩塌", "地基", "砖头", "支柱", "废墟"),
|
||||||
|
"仓储租赁": ("仓库", "库房", "租金", "取货", "入库", "库存"),
|
||||||
|
"道路竞赛": ("赛道", "跑道", "岔路", "十字路口", "终点线", "门票"),
|
||||||
|
"机器器官": ("齿轮", "引擎", "发动机", "血管", "骨架", "肌肉"),
|
||||||
|
"海洋航行": ("蓝海", "浪潮", "潮水", "航船", "灯塔", "彼岸"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Paragraph:
|
||||||
|
position: int
|
||||||
|
text: str
|
||||||
|
han: int
|
||||||
|
sentences: int
|
||||||
|
|
||||||
|
|
||||||
|
def han_count(text: str) -> int:
|
||||||
|
return len(re.findall(r"[\u4e00-\u9fff]", text))
|
||||||
|
|
||||||
|
|
||||||
|
def line_number(text: str, position: int) -> int:
|
||||||
|
return text.count("\n", 0, position) + 1
|
||||||
|
|
||||||
|
|
||||||
|
def excerpt(value: str, width: int = 72) -> str:
|
||||||
|
value = re.sub(r"\s+", " ", value).strip()
|
||||||
|
return value if len(value) <= width else value[: width - 1] + "…"
|
||||||
|
|
||||||
|
|
||||||
|
def mask_non_prose(text: str) -> str:
|
||||||
|
"""屏蔽代码、网址和机器元数据,同时保留字符位置与换行。"""
|
||||||
|
|
||||||
|
def mask(match: re.Match[str]) -> str:
|
||||||
|
return "".join("\n" if char == "\n" else " " for char in match.group())
|
||||||
|
|
||||||
|
patterns = (
|
||||||
|
re.compile(r"\A---\s*\n.*?\n---\s*(?:\n|\Z)", re.DOTALL),
|
||||||
|
re.compile(r"```.*?```", re.DOTALL),
|
||||||
|
re.compile(r"`[^`\n]*`"),
|
||||||
|
re.compile(r"\]\([^\n)]*\)"),
|
||||||
|
re.compile(r"https?://[^\s)>]+"),
|
||||||
|
re.compile(r"<[^>\n]+>"),
|
||||||
|
)
|
||||||
|
masked = text
|
||||||
|
for pattern in patterns:
|
||||||
|
masked = pattern.sub(mask, masked)
|
||||||
|
return masked
|
||||||
|
|
||||||
|
|
||||||
|
def non_overlapping_terms(text: str, terms: tuple[str, ...]):
|
||||||
|
matches = []
|
||||||
|
occupied = []
|
||||||
|
for term in sorted(terms, key=len, reverse=True):
|
||||||
|
for match in re.finditer(re.escape(term), text):
|
||||||
|
start, end = match.span()
|
||||||
|
if any(start < old_end and end > old_start for old_start, old_end in occupied):
|
||||||
|
continue
|
||||||
|
matches.append((start, term))
|
||||||
|
occupied.append((start, end))
|
||||||
|
return sorted(matches)
|
||||||
|
|
||||||
|
|
||||||
|
def all_matches(text: str, patterns: tuple[re.Pattern[str], ...]):
|
||||||
|
matches = []
|
||||||
|
for pattern in patterns:
|
||||||
|
matches.extend(pattern.finditer(text))
|
||||||
|
return sorted(matches, key=lambda match: match.start())
|
||||||
|
|
||||||
|
|
||||||
|
def heavy_de_sentences(text: str):
|
||||||
|
"""找出主干可能被多个“的”压到后面的长句。"""
|
||||||
|
|
||||||
|
matches = []
|
||||||
|
pattern = re.compile(r"[^。!?!?\n]+(?:[。!?!?]|$)")
|
||||||
|
for match in pattern.finditer(text):
|
||||||
|
value = match.group()
|
||||||
|
if han_count(value) >= 38 and value.count("的") >= 4:
|
||||||
|
matches.append(match)
|
||||||
|
return matches
|
||||||
|
|
||||||
|
|
||||||
|
def anaphora_runs(text: str, minimum: int = 3):
|
||||||
|
"""找出同一句里三个以上小句用同一个开头的排比。"""
|
||||||
|
|
||||||
|
matches = []
|
||||||
|
for sentence in re.finditer(r"[^。!?!?\n]+(?:[。!?!?]|$)", text):
|
||||||
|
clauses = [
|
||||||
|
clause.strip()
|
||||||
|
for clause in re.split(r"[,、;,;]", sentence.group())
|
||||||
|
if han_count(clause) >= 3
|
||||||
|
]
|
||||||
|
if len(clauses) < minimum:
|
||||||
|
continue
|
||||||
|
run = 1
|
||||||
|
for previous, current in zip(clauses, clauses[1:]):
|
||||||
|
if previous[:2] == current[:2] and re.match(r"[一-鿿]{2}", current):
|
||||||
|
run += 1
|
||||||
|
if run >= minimum:
|
||||||
|
matches.append(sentence)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
run = 1
|
||||||
|
return matches
|
||||||
|
|
||||||
|
|
||||||
|
def sentence_length_cv(text: str):
|
||||||
|
"""句长变异系数。人写的长短句差距大,模型的句长彼此接近。"""
|
||||||
|
|
||||||
|
lengths = [
|
||||||
|
han_count(match.group())
|
||||||
|
for match in re.finditer(r"[^。!?!?\n]+[。!?!?]", text)
|
||||||
|
if han_count(match.group()) >= 4
|
||||||
|
]
|
||||||
|
if len(lengths) < 12:
|
||||||
|
return None
|
||||||
|
mean = sum(lengths) / len(lengths)
|
||||||
|
if mean == 0:
|
||||||
|
return None
|
||||||
|
variance = sum((value - mean) ** 2 for value in lengths) / len(lengths)
|
||||||
|
return (variance ** 0.5) / mean, len(lengths)
|
||||||
|
|
||||||
|
|
||||||
|
def bracket_highlights(text: str):
|
||||||
|
"""「」括起来的短语。太密说明在批量造金句。"""
|
||||||
|
|
||||||
|
return list(re.finditer(r"[「『][^」』\n]{1,6}[」』]", text))
|
||||||
|
|
||||||
|
|
||||||
|
def prose_paragraphs(text: str) -> list[Paragraph]:
|
||||||
|
paragraphs = []
|
||||||
|
cursor = 0
|
||||||
|
for block in re.split(r"\n\s*\n", text):
|
||||||
|
position = text.find(block, cursor)
|
||||||
|
cursor = max(position + len(block), cursor)
|
||||||
|
clean = re.sub(r"[>*_`]", "", block).strip()
|
||||||
|
if not clean or clean.startswith(("#", "http", "![", "```")):
|
||||||
|
continue
|
||||||
|
if re.match(r"^(?:[-+*]|\d+[.、])\s", clean):
|
||||||
|
continue
|
||||||
|
count = han_count(clean)
|
||||||
|
if count < 4:
|
||||||
|
continue
|
||||||
|
sentences = max(1, len(re.findall(r"[。!?!?]", clean)))
|
||||||
|
paragraphs.append(Paragraph(position, clean, count, sentences))
|
||||||
|
return paragraphs
|
||||||
|
|
||||||
|
|
||||||
|
def metaphor_cluster(text: str, distance: int = 800):
|
||||||
|
hits = []
|
||||||
|
for field, words in METAPHOR_FIELDS.items():
|
||||||
|
for word in words:
|
||||||
|
for match in re.finditer(re.escape(word), text):
|
||||||
|
hits.append((match.start(), field, word))
|
||||||
|
hits.sort()
|
||||||
|
for index, (start, _, _) in enumerate(hits):
|
||||||
|
window = [hit for hit in hits[index:] if hit[0] - start <= distance]
|
||||||
|
fields = {hit[1] for hit in window}
|
||||||
|
if len(fields) >= 3:
|
||||||
|
return window, fields
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def short_streak(paragraphs: list[Paragraph], limit: int = 4):
|
||||||
|
streak = []
|
||||||
|
for paragraph in paragraphs:
|
||||||
|
if paragraph.han <= 24 and paragraph.sentences <= 1:
|
||||||
|
streak.append(paragraph)
|
||||||
|
if len(streak) >= limit:
|
||||||
|
return streak
|
||||||
|
else:
|
||||||
|
streak = []
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def opener_counts(paragraphs: list[Paragraph]):
|
||||||
|
counts = collections.Counter()
|
||||||
|
examples = {}
|
||||||
|
for paragraph in paragraphs:
|
||||||
|
value = paragraph.text.lstrip("“‘\"((")
|
||||||
|
for opener in REPEATED_OPENERS:
|
||||||
|
if value.startswith(opener):
|
||||||
|
counts[opener] += 1
|
||||||
|
examples.setdefault(opener, paragraph.position)
|
||||||
|
break
|
||||||
|
return counts, examples
|
||||||
|
|
||||||
|
|
||||||
|
def read_text(path: str) -> str:
|
||||||
|
if path == "-":
|
||||||
|
return sys.stdin.read()
|
||||||
|
return Path(path).read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="检查中文成稿的硬禁令与模型化形状")
|
||||||
|
parser.add_argument("path", help="Markdown 或文本文件路径。使用 - 从标准输入读取")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
text = read_text(args.path)
|
||||||
|
except (OSError, UnicodeError) as error:
|
||||||
|
print(f"无法读取稿件。{error}", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
prose = mask_non_prose(text)
|
||||||
|
total_han = han_count(prose)
|
||||||
|
if total_han == 0:
|
||||||
|
print("没有检测到汉字。", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
failures = []
|
||||||
|
warnings = []
|
||||||
|
|
||||||
|
quote_colons = []
|
||||||
|
for symbol, label in FORBIDDEN_PUNCTUATION.items():
|
||||||
|
matches = list(re.finditer(re.escape(symbol), prose))
|
||||||
|
if symbol in (":", ":"):
|
||||||
|
hard = []
|
||||||
|
for match in matches:
|
||||||
|
tail = prose[match.end() : match.end() + 2].lstrip()
|
||||||
|
if tail[:1] in ("「", "『", "“", "‘", '"'):
|
||||||
|
quote_colons.append(match)
|
||||||
|
else:
|
||||||
|
hard.append(match)
|
||||||
|
matches = hard
|
||||||
|
if matches:
|
||||||
|
lines = "、".join(str(line_number(text, match.start())) for match in matches[:8])
|
||||||
|
failures.append(f"{label}共 {len(matches)} 处,出现在第 {lines} 行。")
|
||||||
|
if quote_colons:
|
||||||
|
lines = "、".join(
|
||||||
|
str(line_number(text, match.start())) for match in quote_colons[:8]
|
||||||
|
)
|
||||||
|
warnings.append(
|
||||||
|
f"引出原话的冒号 {len(quote_colons)} 处,第 {lines} 行。确认引号里确实是原话,且不是提示性用法。"
|
||||||
|
)
|
||||||
|
|
||||||
|
stop_matches = non_overlapping_terms(prose, HARD_STOPS)
|
||||||
|
for position, phrase in stop_matches:
|
||||||
|
failures.append(f"硬停词,第 {line_number(text, position)} 行,{phrase}")
|
||||||
|
|
||||||
|
jargon_matches = non_overlapping_terms(prose, HARD_JARGON)
|
||||||
|
for position, phrase in jargon_matches:
|
||||||
|
failures.append(f"黑话,第 {line_number(text, position)} 行,{phrase}")
|
||||||
|
|
||||||
|
context_jargon_matches = non_overlapping_terms(prose, CONTEXT_JARGON)
|
||||||
|
hard_spans = [
|
||||||
|
(position, position + len(phrase)) for position, phrase in jargon_matches
|
||||||
|
]
|
||||||
|
context_jargon_matches = [
|
||||||
|
(position, phrase)
|
||||||
|
for position, phrase in context_jargon_matches
|
||||||
|
if not any(
|
||||||
|
position < end and position + len(phrase) > start
|
||||||
|
for start, end in hard_spans
|
||||||
|
)
|
||||||
|
]
|
||||||
|
if context_jargon_matches:
|
||||||
|
samples = "、".join(
|
||||||
|
dict.fromkeys(phrase for _, phrase in context_jargon_matches)
|
||||||
|
)
|
||||||
|
lines = "、".join(
|
||||||
|
dict.fromkeys(
|
||||||
|
str(line_number(text, position))
|
||||||
|
for position, _ in context_jargon_matches[:8]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
warnings.append(
|
||||||
|
f"有 {len(context_jargon_matches)} 处词语需要结合语境判断。"
|
||||||
|
f"第 {lines} 行出现 {samples}。本义准确时保留,用来抬价时改写。"
|
||||||
|
)
|
||||||
|
|
||||||
|
road_signs = all_matches(prose, ROAD_SIGN_PATTERNS)
|
||||||
|
for match in road_signs:
|
||||||
|
failures.append(
|
||||||
|
f"模型路标,第 {line_number(text, match.start())} 行,"
|
||||||
|
f"“{excerpt(match.group().lstrip(ROAD_STRIP_CHARS))}”"
|
||||||
|
)
|
||||||
|
|
||||||
|
pivots = all_matches(prose, PIVOT_PATTERNS)
|
||||||
|
for match in pivots:
|
||||||
|
failures.append(
|
||||||
|
f"禁用翻案句,第 {line_number(text, match.start())} 行,"
|
||||||
|
f"“{excerpt(match.group())}”"
|
||||||
|
)
|
||||||
|
|
||||||
|
occupied_spans = [match.span() for match in pivots]
|
||||||
|
semantic_pivots = []
|
||||||
|
for match in all_matches(prose, SEMANTIC_PIVOT_PATTERNS):
|
||||||
|
if any(
|
||||||
|
match.start() < end and match.end() > start
|
||||||
|
for start, end in occupied_spans
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
semantic_pivots.append(match)
|
||||||
|
occupied_spans.append(match.span())
|
||||||
|
for match in semantic_pivots:
|
||||||
|
warnings.append(
|
||||||
|
f"疑似翻案腔变形,第 {line_number(text, match.start())} 行,"
|
||||||
|
f"“{excerpt(match.group(), 44)}”。先立误解再推翻就改成正面陈述,正常用法保留。"
|
||||||
|
)
|
||||||
|
|
||||||
|
anaphoras = anaphora_runs(prose)
|
||||||
|
for match in anaphoras[:4]:
|
||||||
|
warnings.append(
|
||||||
|
f"三连以上同构排比,第 {line_number(text, match.start())} 行,"
|
||||||
|
f"“{excerpt(match.group(), 44)}”。留两项,第三项换说法或删掉。"
|
||||||
|
)
|
||||||
|
|
||||||
|
lyric_matches = non_overlapping_terms(prose, LYRIC_WORDS)
|
||||||
|
if len(lyric_matches) >= 2:
|
||||||
|
samples = "、".join(dict.fromkeys(term for _, term in lyric_matches))
|
||||||
|
warnings.append(
|
||||||
|
f"模型偏爱的抒情词 {len(lyric_matches)} 处。{samples}。"
|
||||||
|
"写具体事物时保留,给抽象概念穿衣服时删掉。"
|
||||||
|
)
|
||||||
|
|
||||||
|
nominalizations = all_matches(prose, NOMINALIZATION_PATTERNS)
|
||||||
|
for match in nominalizations[:4]:
|
||||||
|
warnings.append(
|
||||||
|
f"名词化句式,第 {line_number(text, match.start())} 行,"
|
||||||
|
f"“{excerpt(match.group(), 36)}”。还原成直接的动词。"
|
||||||
|
)
|
||||||
|
|
||||||
|
conjunction_hits = non_overlapping_terms(prose, CONJUNCTIONS)
|
||||||
|
if total_han >= 600 and len(conjunction_hits) * 1000 / total_han > 7:
|
||||||
|
samples = "、".join(
|
||||||
|
f"{term} {count} 次"
|
||||||
|
for term, count in collections.Counter(
|
||||||
|
term for _, term in conjunction_hits
|
||||||
|
).most_common(4)
|
||||||
|
)
|
||||||
|
warnings.append(
|
||||||
|
f"连词密度偏高,每千字 {len(conjunction_hits) * 1000 // total_han} 个。{samples}。"
|
||||||
|
"中文小句靠语序和事理相接,删掉一半试试。"
|
||||||
|
)
|
||||||
|
|
||||||
|
highlights = bracket_highlights(prose)
|
||||||
|
highlight_limit = max(3, total_han // 700)
|
||||||
|
if len(highlights) > highlight_limit:
|
||||||
|
samples = "、".join(dict.fromkeys(match.group() for match in highlights[:6]))
|
||||||
|
warnings.append(
|
||||||
|
f"「」括起的短语共 {len(highlights)} 处。{samples}。太密说明在批量造金句。"
|
||||||
|
)
|
||||||
|
|
||||||
|
cv_result = sentence_length_cv(prose)
|
||||||
|
if cv_result and cv_result[0] < 0.42:
|
||||||
|
warnings.append(
|
||||||
|
f"全文 {cv_result[1]} 个句子长度过于接近(变异系数 {cv_result[0]:.2f})。"
|
||||||
|
"人写的段落里十个字的句子会挨着四十个字的句子,放开几句,压短几句。"
|
||||||
|
)
|
||||||
|
|
||||||
|
marker_matches = non_overlapping_terms(prose, SOFT_MARKERS)
|
||||||
|
marker_limit = max(2, total_han // 900)
|
||||||
|
if len(marker_matches) > marker_limit:
|
||||||
|
samples = "、".join(dict.fromkeys(term for _, term in marker_matches))
|
||||||
|
warnings.append(
|
||||||
|
f"洞察路标共 {len(marker_matches)} 处,当前提醒线为 {marker_limit} 处。"
|
||||||
|
f"重点检查 {samples}。"
|
||||||
|
)
|
||||||
|
|
||||||
|
left_branches = all_matches(prose, LEFT_BRANCH_PATTERNS)
|
||||||
|
left_limit = max(2, total_han // 1200)
|
||||||
|
if len(left_branches) > left_limit:
|
||||||
|
samples = ";".join(
|
||||||
|
f"第 {line_number(text, match.start())} 行“{excerpt(match.group(), 44)}”"
|
||||||
|
for match in left_branches[:4]
|
||||||
|
)
|
||||||
|
warnings.append(
|
||||||
|
f"长前置成分共 {len(left_branches)} 处,可能让主干来得太晚。{samples}"
|
||||||
|
)
|
||||||
|
|
||||||
|
dense_de = heavy_de_sentences(prose)
|
||||||
|
dense_de_limit = max(1, total_han // 1500)
|
||||||
|
if len(dense_de) > dense_de_limit:
|
||||||
|
samples = ";".join(
|
||||||
|
f"第 {line_number(text, match.start())} 行“{excerpt(match.group(), 44)}”"
|
||||||
|
for match in dense_de[:4]
|
||||||
|
)
|
||||||
|
warnings.append(
|
||||||
|
f"有 {len(dense_de)} 个长句包含四个以上的“的”,可能要先交代人和动作。{samples}"
|
||||||
|
)
|
||||||
|
|
||||||
|
paragraphs = prose_paragraphs(prose)
|
||||||
|
if len(paragraphs) >= 10:
|
||||||
|
one_sentence = sum(paragraph.sentences <= 1 for paragraph in paragraphs)
|
||||||
|
ratio = one_sentence / len(paragraphs)
|
||||||
|
if ratio >= 0.75:
|
||||||
|
warnings.append(
|
||||||
|
f"可识别段落中有 {ratio:.0%} 只有一句话,可能形成统一的短段鼓点。"
|
||||||
|
)
|
||||||
|
|
||||||
|
streak = short_streak(paragraphs)
|
||||||
|
if streak:
|
||||||
|
first = streak[0]
|
||||||
|
warnings.append(
|
||||||
|
f"从第 {line_number(text, first.position)} 行起连续出现 {len(streak)} 个短促单句段,"
|
||||||
|
"检查是否在排队喊结论。"
|
||||||
|
)
|
||||||
|
|
||||||
|
counts, opener_examples = opener_counts(paragraphs)
|
||||||
|
repeated = [(opener, count) for opener, count in counts.items() if count >= 4]
|
||||||
|
if repeated:
|
||||||
|
details = "、".join(f"{opener} {count} 次" for opener, count in repeated)
|
||||||
|
first_position = min(opener_examples[opener] for opener, _ in repeated)
|
||||||
|
warnings.append(
|
||||||
|
f"段落开场重复,从第 {line_number(text, first_position)} 行附近开始。{details}。"
|
||||||
|
)
|
||||||
|
|
||||||
|
metaphors = metaphor_cluster(prose)
|
||||||
|
if metaphors:
|
||||||
|
window, fields = metaphors
|
||||||
|
samples = "、".join(dict.fromkeys(hit[2] for hit in window))
|
||||||
|
warnings.append(
|
||||||
|
f"八百字内出现 {len(fields)} 套借喻。{'、'.join(sorted(fields))}。"
|
||||||
|
f"例词有 {samples}。"
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"汉字数 {total_han}")
|
||||||
|
print(
|
||||||
|
f"翻案句 {len(pivots)},翻案腔变形 {len(semantic_pivots)},"
|
||||||
|
f"同构排比 {len(anaphoras)},名词化 {len(nominalizations)},"
|
||||||
|
f"黑话 {len(jargon_matches)},硬停词 {len(stop_matches)},"
|
||||||
|
f"模型路标 {len(road_signs)},需辨语境词 {len(context_jargon_matches)},"
|
||||||
|
f"抒情词 {len(lyric_matches)},洞察路标 {len(marker_matches)},"
|
||||||
|
f"长前置成分 {len(left_branches)},重定语句 {len(dense_de)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if failures:
|
||||||
|
print("\n需要修改")
|
||||||
|
for item in failures:
|
||||||
|
print(f"- {item}")
|
||||||
|
|
||||||
|
if warnings:
|
||||||
|
print("\n需要人工判断")
|
||||||
|
for item in warnings:
|
||||||
|
print(f"- {item}")
|
||||||
|
|
||||||
|
if not failures and not warnings:
|
||||||
|
print("\n未发现这份检查器覆盖的问题。")
|
||||||
|
|
||||||
|
return 1 if failures else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user