@topmindspace/topmind-skills 4.14.0 → 4.15.1
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/evals/evals.json +24 -1
- package/package.json +1 -1
- package/shared/auto-suggest.md +3 -1
- package/shared/long-url-capture.md +3 -0
- package/shared/trigger-disambiguation.md +10 -1
- package/topmind/SKILL.md +7 -5
- package/topmind/references/multi-intent.md +4 -2
- package/topmind-capture/SKILL.md +5 -3
- package/topmind-ledger/SKILL.md +1 -1
- package/topmind-loop/SKILL.md +1 -1
- package/topmind-maintain/SKILL.md +1 -1
- package/topmind-memory/SKILL.md +1 -1
- package/topmind-organize/SKILL.md +6 -3
- package/topmind-pack.json +12 -6
- package/topmind-wechat/SKILL.md +15 -4
- package/topmind-wechat/references/workflow.md +13 -2
- package/topmind-wechat/references/writing-quality.md +11 -3
- package/topmind-wechat/scripts/audit-provenance.py +366 -0
- package/topmind-wechat/scripts/scan_ai_flavor.py +71 -434
- package/topmind-weread/SKILL.md +1 -1
- package/topmind-write/SKILL.md +1 -1
- package/topmind-x/SKILL.md +1 -1
|
@@ -1,464 +1,101 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
scan_ai_flavor.py —
|
|
4
|
-
|
|
5
|
-
用法:
|
|
6
|
-
python3 scan_ai_flavor.py <file> # 输出 Markdown 报告
|
|
7
|
-
python3 scan_ai_flavor.py --json <file> # 输出 JSON
|
|
8
|
-
python3 scan_ai_flavor.py -o report.md <file>
|
|
9
|
-
cat article.md | python3 scan_ai_flavor.py - # 管道
|
|
10
|
-
|
|
11
|
-
设计目标:纯 stdlib,无第三方依赖,能在普通 Mac / Linux 上直接跑。
|
|
12
|
-
"""
|
|
13
|
-
from __future__ import annotations
|
|
14
|
-
|
|
15
|
-
import argparse
|
|
16
|
-
import json
|
|
17
|
-
import re
|
|
18
|
-
import sys
|
|
19
|
-
from collections import Counter
|
|
20
|
-
from dataclasses import dataclass, field, asdict
|
|
21
|
-
from pathlib import Path
|
|
22
|
-
from typing import Iterable
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""scan_ai_flavor.py — 转发器:调用 qu-aiwei-zh 的 canonical 检测脚本。
|
|
23
4
|
|
|
5
|
+
**本文件不含检测实现。** 原因见下。
|
|
24
6
|
|
|
25
|
-
|
|
26
|
-
# 模式定义:每条 (类别代码, 类别名, 正则, 单次扣分, 严重度, 开头末尾加权, 备注)
|
|
27
|
-
# --------------------------------------------------------------------------- #
|
|
7
|
+
## 为什么是转发器而不是自带副本
|
|
28
8
|
|
|
9
|
+
topmind-wechat 曾自带一份 463 行的实现副本,它是 `qu-aiwei-zh` 在加入「E 类 · 作者姿态层」
|
|
10
|
+
**之前**的旧快照 —— 不输出姿态参考分,也识别不了姿态类 AI 痕迹。照 SKILL.md 跑它,会拿到一个
|
|
11
|
+
**没有姿态维度的「100 分」**:看着干净,其实少了一整个维度。
|
|
29
12
|
|
|
30
|
-
|
|
31
|
-
class Pattern:
|
|
32
|
-
code: str # e.g. "A1"
|
|
33
|
-
name: str # e.g. "开头套话"
|
|
34
|
-
regex: str # 正则字符串
|
|
35
|
-
penalty: int # 单次扣分(绝对值)
|
|
36
|
-
severity: str # high / mid / low
|
|
37
|
-
weighted: str = "" # "head" | "tail" | ""(加权区段)
|
|
38
|
-
note: str = ""
|
|
13
|
+
两版实测差异:旧副本 0 处「姿态」相关代码,canonical 20 处。
|
|
39
14
|
|
|
40
|
-
|
|
41
|
-
return re.compile(self.regex)
|
|
15
|
+
副本必然再次漂移(一边改了另一边不知道),所以这里只保留入口,实现归 canonical 一处。
|
|
42
16
|
|
|
17
|
+
## canonical 查找顺序(命中即用)
|
|
43
18
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Pattern("A1", "开头套话", r"毋庸置疑[,,]|不可否认[,,]", 7, "high", "head"),
|
|
50
|
-
Pattern("A1", "开头套话", r"近年来[^,。]{0,15}日益", 6, "high", "head"),
|
|
51
|
-
Pattern("A1", "开头套话", r"伴随着[^,。]{2,30}的(快速|迅猛)", 8, "high", "head"),
|
|
52
|
-
Pattern("A1", "开头套话", r"在[^,。]{2,20}的背景下", 5, "mid", "head"),
|
|
53
|
-
Pattern("A1", "开头套话", r"一直以来[^,。]{2,20}都", 4, "low", "head"),
|
|
54
|
-
Pattern("A10", "模板口水", r"大家好[,,]", 6, "high", "head"),
|
|
55
|
-
Pattern("A10", "模板口水", r"本文将从[^,。]{0,30}个方面", 7, "high", "head"),
|
|
56
|
-
Pattern("A10", "模板口水", r"希望本文能够", 5, "mid", "head"),
|
|
57
|
-
Pattern("A10", "模板口水", r"如(有不当|有疏漏|有错误)[,,]", 4, "low", "head"),
|
|
58
|
-
]
|
|
59
|
-
|
|
60
|
-
# 全文模式
|
|
61
|
-
BODY_PATTERNS: list[Pattern] = [
|
|
62
|
-
# A2 转折连接
|
|
63
|
-
Pattern("A2", "转折连接", r"此外[,,]", 2, "low"),
|
|
64
|
-
Pattern("A2", "转折连接", r"与此同时[,,]", 2, "low"),
|
|
65
|
-
Pattern("A2", "转折连接", r"不仅如此[,,]", 2, "low"),
|
|
66
|
-
Pattern("A2", "转折连接", r"综上所述[,,]", 4, "mid"),
|
|
67
|
-
Pattern("A2", "转折连接", r"总而言之[,,]", 4, "mid"),
|
|
68
|
-
Pattern("A2", "转折连接", r"一方面[^,。]{1,15}[,,另一方面]", 3, "low"),
|
|
69
|
-
Pattern("A2", "转折连接", r"既[^,。]{1,12}又[^,。]{1,12}还", 3, "low"),
|
|
70
|
-
Pattern("A2", "转折连接", r"无可厚非", 4, "mid"),
|
|
71
|
-
Pattern("A2", "转折连接", r"瑕不掩瑜", 4, "mid"),
|
|
19
|
+
1. `$QU_AIWEI_SCAN` 显式指定(测试与非标准安装用)
|
|
20
|
+
2. `~/.workbuddy/skills/qu-aiwei-zh/scripts/scan_ai_flavor.py`
|
|
21
|
+
3. `~/.agents/skills/qu-aiwei-zh/scripts/scan_ai_flavor.py`
|
|
22
|
+
4. `~/.claude/skills/qu-aiwei-zh/scripts/scan_ai_flavor.py`
|
|
23
|
+
5. `~/.codex/skills/qu-aiwei-zh/scripts/scan_ai_flavor.py`
|
|
72
24
|
|
|
73
|
-
|
|
74
|
-
Pattern("A3", "官腔形容词", r"至关重要", 5, "high"),
|
|
75
|
-
Pattern("A3", "官腔形容词", r"举足轻重", 5, "high"),
|
|
76
|
-
Pattern("A3", "官腔形容词", r"不可或缺", 4, "mid"),
|
|
77
|
-
Pattern("A3", "官腔形容词", r"深入(探讨|分析|研究)", 5, "high"),
|
|
78
|
-
Pattern("A3", "官腔形容词", r"高度重视", 5, "high"),
|
|
79
|
-
Pattern("A3", "官腔形容词", r"深入推进", 5, "high"),
|
|
80
|
-
Pattern("A3", "官腔形容词", r"扎实开展", 5, "high"),
|
|
81
|
-
Pattern("A3", "官腔形容词", r"积极探索", 4, "mid"),
|
|
82
|
-
Pattern("A3", "官腔形容词", r"有效落实", 4, "mid"),
|
|
83
|
-
Pattern("A3", "官腔形容词", r"持续优化", 4, "mid"),
|
|
84
|
-
Pattern("A3", "官腔形容词", r"凝聚共识", 5, "high"),
|
|
85
|
-
Pattern("A3", "官腔形容词", r"汇聚合力", 5, "high"),
|
|
86
|
-
Pattern("A3", "官腔形容词", r"协同推进", 4, "mid"),
|
|
87
|
-
Pattern("A3", "官腔形容词", r"全方位(提升|推进|优化)", 4, "mid"),
|
|
88
|
-
Pattern("A3", "官腔形容词", r"多角度(分析|审视)", 4, "mid"),
|
|
89
|
-
Pattern("A3", "官腔形容词", r"立体化", 3, "low"),
|
|
90
|
-
# 注:数字化/智能化 是领域术语,不作为 AI 味标志,跳过
|
|
25
|
+
## 行为
|
|
91
26
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
27
|
+
- 参数原样透传(位置参数 `<file>` / `--json` / `-o` / `-` 管道都可用),stdin / stdout / stderr 继承
|
|
28
|
+
- 退出码取 canonical 的退出码
|
|
29
|
+
- `--which` 只打印解析到的 canonical 路径并退出 0(转发器自有开关,canonical 无此参数)
|
|
30
|
+
- **找不到 canonical 时报错退出(exit 3),不静默降级** —— 宁可这一步跑不了,
|
|
31
|
+
也不要给出一个没有姿态分的分数
|
|
95
32
|
|
|
96
|
-
|
|
97
|
-
Pattern("A6", "营销黑话", r"赋能", 5, "high"),
|
|
98
|
-
Pattern("A6", "营销黑话", r"加持", 5, "high"),
|
|
99
|
-
Pattern("A6", "营销黑话", r"引爆", 5, "high"),
|
|
100
|
-
Pattern("A6", "营销黑话", r"破圈", 5, "high"),
|
|
101
|
-
Pattern("A6", "营销黑话", r"一站式", 4, "mid"),
|
|
102
|
-
Pattern("A6", "营销黑话", r"全链路", 4, "mid"),
|
|
103
|
-
Pattern("A6", "营销黑话", r"闭环", 4, "mid"),
|
|
104
|
-
Pattern("A6", "营销黑话", r"抓手", 5, "high"),
|
|
105
|
-
Pattern("A6", "营销黑话", r"底层逻辑", 5, "high"),
|
|
106
|
-
Pattern("A6", "营销黑话", r"顶层设计", 5, "high"),
|
|
107
|
-
Pattern("A6", "营销黑话", r"(打造|构建|重塑)[^,。]{2,15}(方案|生态|体系|平台)", 3, "low"),
|
|
108
|
-
Pattern("A6", "营销黑话", r"拉通|对齐|复盘", 3, "low"),
|
|
109
|
-
# 工程圈行话:用户明确点过——「踩坑」这类词读起来像圈内口令,不像人在说话
|
|
110
|
-
Pattern("A6", "工程圈行话", r"踩坑|避坑|干货|踩出来的坑|踩过的坑", 3, "low"),
|
|
33
|
+
## 用法
|
|
111
34
|
|
|
112
|
-
#
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
Pattern("A7", "模糊归因", r"据相关(研究|调查|数据)表明", 5, "high"),
|
|
116
|
-
Pattern("A7", "模糊归因", r"大量数据表明", 5, "high"),
|
|
117
|
-
Pattern("A7", "模糊归因", r"有学者认为", 4, "mid"),
|
|
118
|
-
Pattern("A7", "模糊归因", r"专家(普遍|一致)认为", 4, "mid"),
|
|
119
|
-
Pattern("A7", "模糊归因", r"大家普遍认为", 3, "low"),
|
|
35
|
+
python3 scripts/scan_ai_flavor.py <包>/公众号稿.md # 目标 ≥85
|
|
36
|
+
python3 scripts/scan_ai_flavor.py --which
|
|
37
|
+
"""
|
|
120
38
|
|
|
121
|
-
|
|
122
|
-
Pattern("A8", "伪深度", r"(是|为)[^,。]{2,15}的(体现|证明|象征)", 5, "high"),
|
|
123
|
-
Pattern("A8", "伪深度", r"彰显了[^,。]{2,20}(意义|价值|精神)", 5, "high"),
|
|
124
|
-
Pattern("A8", "伪深度", r"凸显了[^,。]{2,20}(意义|价值)", 5, "high"),
|
|
125
|
-
Pattern("A8", "伪深度", r"反映了更深(层次)的", 4, "mid"),
|
|
39
|
+
from __future__ import annotations
|
|
126
40
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
Pattern("A9", "假大空成语", r"源远流长", 4, "mid"),
|
|
130
|
-
Pattern("A9", "假大空成语", r"历久弥新", 4, "mid"),
|
|
131
|
-
Pattern("A9", "假大空成语", r"与时俱进", 3, "low"),
|
|
132
|
-
Pattern("A9", "假大空成语", r"开拓创新", 3, "low"),
|
|
133
|
-
Pattern("A9", "假大空成语", r"锐意进取", 3, "low"),
|
|
134
|
-
Pattern("A9", "假大空成语", r"举世(瞩目|无双)", 4, "mid"),
|
|
41
|
+
import os
|
|
42
|
+
import sys
|
|
135
43
|
|
|
136
|
-
|
|
137
|
-
Pattern("A10", "模板口水", r"感谢(您的)?(观看|阅读|支持)", 5, "high"),
|
|
138
|
-
Pattern("A10", "模板口水", r"以上就是[^,。]{2,30}(全部)?内容", 5, "high"),
|
|
139
|
-
Pattern("A10", "模板口水", r"欢迎(指正|交流|留言)", 4, "mid"),
|
|
140
|
-
]
|
|
44
|
+
REL = os.path.join("qu-aiwei-zh", "scripts", "scan_ai_flavor.py")
|
|
141
45
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
Pattern("A4", "结尾升华", r"砥砺前行|扬帆远航", 5, "high", "tail"),
|
|
148
|
-
Pattern("A4", "结尾升华", r"在[^,。]{2,20}的道路上(不断)?前行", 6, "high", "tail"),
|
|
149
|
-
Pattern("A4", "结尾升华", r"为[^,。]{2,15}贡献(自己)?的?力量", 6, "high", "tail"),
|
|
150
|
-
Pattern("A4", "结尾升华", r"谱写[^,。]{2,15}新篇章", 6, "high", "tail"),
|
|
151
|
-
Pattern("A4", "结尾升华", r"展望未来", 4, "mid", "tail"),
|
|
46
|
+
CANDIDATES = [
|
|
47
|
+
os.path.join("~", ".workbuddy", "skills", REL),
|
|
48
|
+
os.path.join("~", ".agents", "skills", REL),
|
|
49
|
+
os.path.join("~", ".claude", "skills", REL),
|
|
50
|
+
os.path.join("~", ".codex", "skills", REL),
|
|
152
51
|
]
|
|
153
52
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
# --------------------------------------------------------------------------- #
|
|
158
|
-
# 数据结构
|
|
159
|
-
# --------------------------------------------------------------------------- #
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
@dataclass
|
|
163
|
-
class Hit:
|
|
164
|
-
code: str
|
|
165
|
-
name: str
|
|
166
|
-
pattern: str
|
|
167
|
-
text: str
|
|
168
|
-
line: int
|
|
169
|
-
column: int
|
|
170
|
-
severity: str
|
|
171
|
-
penalty: int
|
|
172
|
-
raw_penalty: int # 加权前
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
@dataclass
|
|
176
|
-
class Report:
|
|
177
|
-
score: int
|
|
178
|
-
level: str
|
|
179
|
-
level_emoji: str
|
|
180
|
-
hits: list[Hit] = field(default_factory=list)
|
|
181
|
-
lexical_deduction: int = 0
|
|
182
|
-
structural_deduction: int = 0
|
|
183
|
-
stats: dict = field(default_factory=dict)
|
|
184
|
-
suggestions: list[str] = field(default_factory=list)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
# --------------------------------------------------------------------------- #
|
|
188
|
-
# 扫描逻辑
|
|
189
|
-
# --------------------------------------------------------------------------- #
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def scan_text(text: str) -> Report:
|
|
193
|
-
lines = text.splitlines()
|
|
194
|
-
total_chars = len(text)
|
|
195
|
-
total_lines = len(lines)
|
|
196
|
-
head_chars = "".join(lines[: max(1, total_lines // 5)]) # 前 20%
|
|
197
|
-
tail_chars = "".join(lines[-max(1, total_lines // 5):]) # 后 20%
|
|
198
|
-
|
|
199
|
-
hits: list[Hit] = []
|
|
200
|
-
seen_in_text: dict[tuple[str, str], int] = {} # (pattern, exact_text) → 第 N 次出现
|
|
201
|
-
|
|
202
|
-
for p in ALL_PATTERNS:
|
|
203
|
-
regex = p.compiled()
|
|
204
|
-
for m in regex.finditer(text):
|
|
205
|
-
# 计算行号/列号
|
|
206
|
-
start = m.start()
|
|
207
|
-
line_no = text.count("\n", 0, start) + 1
|
|
208
|
-
col_no = start - (text.rfind("\n", 0, start) + 1) + 1
|
|
209
|
-
|
|
210
|
-
# 计算加权:位置决定
|
|
211
|
-
region = "body"
|
|
212
|
-
if p.weighted == "head" and start < len(head_chars):
|
|
213
|
-
region = "head"
|
|
214
|
-
elif p.weighted == "tail" and start >= len(text) - len(tail_chars):
|
|
215
|
-
region = "tail"
|
|
216
|
-
|
|
217
|
-
raw = p.penalty
|
|
218
|
-
if region != "body":
|
|
219
|
-
raw = int(raw * 1.5)
|
|
220
|
-
|
|
221
|
-
# 重复衰减:同一模式第 N 次出现
|
|
222
|
-
key = (p.regex, m.group(0))
|
|
223
|
-
n = seen_in_text.get(key, 0) + 1
|
|
224
|
-
seen_in_text[key] = n
|
|
225
|
-
multiplier = min(n, 3) # 最多 3 次
|
|
53
|
+
MISSING = """\
|
|
54
|
+
✗ 找不到 qu-aiwei-zh 的 canonical 检测脚本,无法执行「文字关」。
|
|
226
55
|
|
|
227
|
-
|
|
56
|
+
本目录不再自带实现 —— 曾经那份是 E 类姿态检测之前的旧快照,会漏掉姿态维度。
|
|
57
|
+
已查找:
|
|
58
|
+
{paths}
|
|
228
59
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
name=p.name,
|
|
233
|
-
pattern=p.regex,
|
|
234
|
-
text=m.group(0),
|
|
235
|
-
line=line_no,
|
|
236
|
-
column=col_no,
|
|
237
|
-
severity=p.severity,
|
|
238
|
-
penalty=final,
|
|
239
|
-
raw_penalty=raw,
|
|
240
|
-
)
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
# 结构扣分
|
|
244
|
-
structural = 0
|
|
245
|
-
# 重复的段首加粗标签:**结论**:/ **踩坑提醒**:/ **注意**: …
|
|
246
|
-
# 这是模板化写作最典型的痕迹,词面词表抓不到,只能按"同一标签重复出现"判定。
|
|
247
|
-
# 判据取 ≥3 次:用两次属于正常强调,用三次以上就是在套模板。
|
|
248
|
-
lead_labels = re.findall(r"^\s*\*\*([^*\n]{1,10})\*\*\s*[::]", text, re.M)
|
|
249
|
-
repeated_labels: list[str] = []
|
|
250
|
-
if lead_labels:
|
|
251
|
-
counts = Counter(lead_labels)
|
|
252
|
-
repeated_labels = [k for k, v in counts.items() if v >= 3]
|
|
253
|
-
if total_lines >= 5:
|
|
254
|
-
# 破折号过多
|
|
255
|
-
dash_count = text.count("——")
|
|
256
|
-
if dash_count >= max(3, total_chars // 200):
|
|
257
|
-
structural -= 5
|
|
258
|
-
# 感叹号过多
|
|
259
|
-
if text.count("!!!") >= 1 or text.count("!!") >= 3:
|
|
260
|
-
structural -= 3
|
|
261
|
-
# 缺乏具体细节(无任何数字)
|
|
262
|
-
if not re.search(r"\d", text):
|
|
263
|
-
structural -= 12
|
|
264
|
-
# 缺乏个性化声音(无第一人称)
|
|
265
|
-
if not re.search(r"[我我们]", text):
|
|
266
|
-
structural -= 8
|
|
267
|
-
# 标题 emoji 滥用
|
|
268
|
-
emoji_count = len(re.findall(r"[\U0001F300-\U0001FAFF\U00002600-\U000027BF]", text))
|
|
269
|
-
if emoji_count >= 3:
|
|
270
|
-
structural -= min(9, (emoji_count - 2) * 3)
|
|
271
|
-
# 段首加粗标签模板化
|
|
272
|
-
if repeated_labels:
|
|
273
|
-
structural -= min(12, sum(lead_labels.count(k) for k in repeated_labels) * 2)
|
|
274
|
-
|
|
275
|
-
# 词面扣分汇总
|
|
276
|
-
# 注意:structural 累加时是负数(structural -= n),必须取绝对值再相加。
|
|
277
|
-
# 若直接相加,lexical + structural 会变成"加分",结构扣分整条维度静默失效。
|
|
278
|
-
lexical = sum(h.penalty for h in hits)
|
|
279
|
-
structural = abs(structural)
|
|
280
|
-
total_deduction = lexical + structural
|
|
281
|
-
score = max(0, min(100, 100 - total_deduction))
|
|
282
|
-
|
|
283
|
-
level, level_emoji = grade(score)
|
|
284
|
-
|
|
285
|
-
# 生成建议(Top 3)
|
|
286
|
-
suggestions = build_suggestions(hits, score)
|
|
287
|
-
if repeated_labels:
|
|
288
|
-
tips = "、".join(f"**{k}**:×{lead_labels.count(k)}" for k in repeated_labels)
|
|
289
|
-
suggestions.insert(0, f"段首加粗标签模板化 — {tips},同一标签重复 3 次以上就是在套模板,改成直陈句")
|
|
290
|
-
|
|
291
|
-
return Report(
|
|
292
|
-
score=score,
|
|
293
|
-
level=level,
|
|
294
|
-
level_emoji=level_emoji,
|
|
295
|
-
hits=hits,
|
|
296
|
-
lexical_deduction=lexical,
|
|
297
|
-
structural_deduction=structural,
|
|
298
|
-
stats={
|
|
299
|
-
"total_chars": total_chars,
|
|
300
|
-
"total_lines": total_lines,
|
|
301
|
-
"ai_pattern_count": len(hits),
|
|
302
|
-
"repeated_lead_labels": repeated_labels,
|
|
303
|
-
},
|
|
304
|
-
suggestions=suggestions,
|
|
305
|
-
)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
def grade(score: int) -> tuple[str, str]:
|
|
309
|
-
if score >= 85:
|
|
310
|
-
return "人话", "🟢"
|
|
311
|
-
if score >= 70:
|
|
312
|
-
return "微 AI 味", "🟡"
|
|
313
|
-
if score >= 50:
|
|
314
|
-
return "中度 AI 味", "🟠"
|
|
315
|
-
if score >= 30:
|
|
316
|
-
return "重度 AI 味", "🔴"
|
|
317
|
-
return "完全 AI 体", "⚫"
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
def build_suggestions(hits: list[Hit], score: int) -> list[str]:
|
|
321
|
-
"""按类别聚类,给出 3 条优先建议。"""
|
|
322
|
-
by_code: dict[str, list[Hit]] = {}
|
|
323
|
-
for h in hits:
|
|
324
|
-
by_code.setdefault(h.code, []).append(h)
|
|
325
|
-
|
|
326
|
-
# 优先级:A4>A3>A6>A7>A1>A8>A10>A5>A9
|
|
327
|
-
priority = ["A4", "A3", "A6", "A7", "A1", "A8", "A10", "A5", "A9"]
|
|
328
|
-
tips_map = {
|
|
329
|
-
"A1": "开头套话 — 删掉'在当今社会'等模板开头,直接说事实",
|
|
330
|
-
"A2": "转折词 — 用句号断开,靠上下文自然过渡",
|
|
331
|
-
"A3": "官腔形容词 — 换成大白话或具体数字",
|
|
332
|
-
"A4": "结尾升华 — 删掉'让我们共同'等空话,给具体计划",
|
|
333
|
-
"A5": "排比三段式 — 砍到两项或四项,别硬凑三段",
|
|
334
|
-
"A6": "营销黑话 — 改为具体动词,如'做了一个工具'",
|
|
335
|
-
"A7": "模糊归因 — 给具体名字/出处/数字",
|
|
336
|
-
"A8": "伪深度 — 删除'是 XX 的体现/证明'句式",
|
|
337
|
-
"A9": "假大空成语 — 用白话翻译一遍",
|
|
338
|
-
"A10": "模板口水 — 删掉口水句,直接进入正文",
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
suggestions = []
|
|
342
|
-
for code in priority:
|
|
343
|
-
if code in by_code:
|
|
344
|
-
n = len(by_code[code])
|
|
345
|
-
suggestions.append(f"{tips_map[code]}(命中 {n} 处)")
|
|
346
|
-
if len(suggestions) >= 3:
|
|
347
|
-
break
|
|
348
|
-
|
|
349
|
-
if not suggestions:
|
|
350
|
-
suggestions.append("未发现明显 AI 模式,但建议人工通读检查结构/个性化声音")
|
|
351
|
-
|
|
352
|
-
return suggestions
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
# --------------------------------------------------------------------------- #
|
|
356
|
-
# 输出格式
|
|
357
|
-
# --------------------------------------------------------------------------- #
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
def render_markdown(report: Report, source: str) -> str:
|
|
361
|
-
md: list[str] = []
|
|
362
|
-
md.append(f"## AI 味检测报告\n")
|
|
363
|
-
md.append(f"**文件:** `{source}` ")
|
|
364
|
-
md.append(f"**总分:** **{report.score}** / 100 {report.level_emoji} {report.level}\n")
|
|
365
|
-
|
|
366
|
-
md.append("### 分项评分")
|
|
367
|
-
md.append(f"- 词面命中扣分:**-{report.lexical_deduction}**")
|
|
368
|
-
md.append(f"- 结构问题扣分:**{report.structural_deduction}**")
|
|
369
|
-
md.append(f"- 总计:**-{report.lexical_deduction + abs(report.structural_deduction)}** → {report.score} 分\n")
|
|
370
|
-
|
|
371
|
-
md.append("### 基础统计")
|
|
372
|
-
md.append(f"- 字符数:{report.stats.get('total_chars', 0)}")
|
|
373
|
-
md.append(f"- 行数:{report.stats.get('total_lines', 0)}")
|
|
374
|
-
md.append(f"- AI 模式命中数:**{report.stats.get('ai_pattern_count', 0)}**")
|
|
375
|
-
reps = report.stats.get("repeated_lead_labels") or []
|
|
376
|
-
if reps:
|
|
377
|
-
md.append(f"- 重复段首加粗标签:**{'、'.join(reps)}**")
|
|
378
|
-
md.append("")
|
|
379
|
-
|
|
380
|
-
if report.hits:
|
|
381
|
-
md.append("### 命中位置(按严重度排序)")
|
|
382
|
-
md.append("| # | 类别 | 命中内容 | 位置 | 严重度 | 扣分 |")
|
|
383
|
-
md.append("|---|---|---|---|---|---|")
|
|
384
|
-
sorted_hits = sorted(report.hits, key=lambda h: -h.penalty)
|
|
385
|
-
for i, h in enumerate(sorted_hits[:30], 1):
|
|
386
|
-
preview = h.text if len(h.text) <= 25 else h.text[:25] + "…"
|
|
387
|
-
md.append(f"| {i} | {h.code} {h.name} | `{preview}` | 第 {h.line} 行 | {h.severity} | -{h.penalty} |")
|
|
388
|
-
if len(report.hits) > 30:
|
|
389
|
-
md.append(f"\n*(还有 {len(report.hits) - 30} 处命中未显示)*\n")
|
|
390
|
-
else:
|
|
391
|
-
md.append("### 命中位置")
|
|
392
|
-
md.append("无命中 🎉\n")
|
|
393
|
-
|
|
394
|
-
md.append("### Top 3 优先改写建议")
|
|
395
|
-
for s in report.suggestions:
|
|
396
|
-
md.append(f"1. {s}")
|
|
397
|
-
md.append("")
|
|
398
|
-
|
|
399
|
-
return "\n".join(md)
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
def render_json(report: Report) -> str:
|
|
403
|
-
return json.dumps(
|
|
404
|
-
{
|
|
405
|
-
"score": report.score,
|
|
406
|
-
"level": report.level,
|
|
407
|
-
"level_emoji": report.level_emoji,
|
|
408
|
-
"deductions": {
|
|
409
|
-
"lexical": report.lexical_deduction,
|
|
410
|
-
"structural": report.structural_deduction,
|
|
411
|
-
},
|
|
412
|
-
"stats": report.stats,
|
|
413
|
-
"hits": [asdict(h) for h in report.hits],
|
|
414
|
-
"suggestions": report.suggestions,
|
|
415
|
-
},
|
|
416
|
-
ensure_ascii=False,
|
|
417
|
-
indent=2,
|
|
418
|
-
)
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
# --------------------------------------------------------------------------- #
|
|
422
|
-
# CLI
|
|
423
|
-
# --------------------------------------------------------------------------- #
|
|
60
|
+
解决:安装 qu-aiwei-zh 技能,或用环境变量显式指定:
|
|
61
|
+
export QU_AIWEI_SCAN=/path/to/qu-aiwei-zh/scripts/scan_ai_flavor.py
|
|
62
|
+
"""
|
|
424
63
|
|
|
425
64
|
|
|
426
|
-
def
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
65
|
+
def resolve():
|
|
66
|
+
"""返回 canonical 脚本的绝对路径;找不到返回 None。"""
|
|
67
|
+
env = os.environ.get("QU_AIWEI_SCAN")
|
|
68
|
+
if env:
|
|
69
|
+
p = os.path.abspath(os.path.expanduser(env))
|
|
70
|
+
return p if os.path.isfile(p) else None
|
|
71
|
+
for c in CANDIDATES:
|
|
72
|
+
p = os.path.abspath(os.path.expanduser(c))
|
|
73
|
+
if os.path.isfile(p):
|
|
74
|
+
return p
|
|
75
|
+
return None
|
|
434
76
|
|
|
435
|
-
# 读取输入
|
|
436
|
-
if args.input == "-":
|
|
437
|
-
text = sys.stdin.read()
|
|
438
|
-
source = "<stdin>"
|
|
439
|
-
else:
|
|
440
|
-
p = Path(args.input)
|
|
441
|
-
if not p.exists():
|
|
442
|
-
print(f"❌ 文件不存在: {args.input}", file=sys.stderr)
|
|
443
|
-
return 1
|
|
444
|
-
text = p.read_text(encoding="utf-8")
|
|
445
|
-
source = str(p)
|
|
446
77
|
|
|
447
|
-
|
|
78
|
+
def main():
|
|
79
|
+
args = sys.argv[1:]
|
|
448
80
|
|
|
449
|
-
if args
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
81
|
+
if args and args[0] == "--which":
|
|
82
|
+
target = resolve()
|
|
83
|
+
if target:
|
|
84
|
+
print(target)
|
|
85
|
+
return 0
|
|
86
|
+
print("✗ 未解析到 canonical 脚本;用 QU_AIWEI_SCAN 指定或安装 qu-aiwei-zh",
|
|
87
|
+
file=sys.stderr)
|
|
88
|
+
return 3
|
|
453
89
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
90
|
+
target = resolve()
|
|
91
|
+
if not target:
|
|
92
|
+
shown = [os.path.abspath(os.path.expanduser(c)) for c in CANDIDATES]
|
|
93
|
+
sys.stderr.write(MISSING.format(paths="\n".join(" " + p for p in shown)))
|
|
94
|
+
return 3
|
|
459
95
|
|
|
460
|
-
|
|
96
|
+
os.execv(sys.executable, [sys.executable, target] + args)
|
|
97
|
+
return 0 # 不会到达
|
|
461
98
|
|
|
462
99
|
|
|
463
100
|
if __name__ == "__main__":
|
|
464
|
-
sys.exit(main(
|
|
101
|
+
sys.exit(main())
|
package/topmind-weread/SKILL.md
CHANGED
package/topmind-write/SKILL.md
CHANGED