@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.
@@ -1,464 +1,101 @@
1
1
  #!/usr/bin/env python3
2
- """
3
- scan_ai_flavor.py — 中文 AI 味检测脚本
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
- @dataclass
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
- def compiled(self) -> re.Pattern:
41
- return re.compile(self.regex)
15
+ 副本必然再次漂移(一边改了另一边不知道),所以这里只保留入口,实现归 canonical 一处。
42
16
 
17
+ ## canonical 查找顺序(命中即用)
43
18
 
44
- # 头部加权的模式(命中位置 ≤ 200 字 权重 ×1.5)
45
- HEAD_PATTERNS: list[Pattern] = [
46
- Pattern("A1", "开头套话", r"在当今(社会|时代)", 8, "high", "head"),
47
- Pattern("A1", "开头套话", r"随着[^,。]{2,30}的(发展|日益|不断)", 8, "high", "head"),
48
- Pattern("A1", "开头套话", r"众所周知[,,]", 7, "high", "head"),
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
- # A3 形容词/官腔
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
- # A5 排比/三段式
93
- Pattern("A5", "排比三段式", r"这不仅[^,。]{2,20}[,,][^,。]{2,15}也[^,。]{2,15}[,,更]?", 6, "high"),
94
- Pattern("A5", "排比三段式", r"从[^,。]{2,15}到[^,。]{2,15}[,,从][^,。]{2,15}到", 4, "mid"),
27
+ - 参数原样透传(位置参数 `<file>` / `--json` / `-o` / `-` 管道都可用),stdin / stdout / stderr 继承
28
+ - 退出码取 canonical 的退出码
29
+ - `--which` 只打印解析到的 canonical 路径并退出 0(转发器自有开关,canonical 无此参数)
30
+ - **找不到 canonical 时报错退出(exit 3),不静默降级** —— 宁可这一步跑不了,
31
+ 也不要给出一个没有姿态分的分数
95
32
 
96
- # A6 营销体
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
- # A7 模糊归因
113
- Pattern("A7", "模糊归因", r"专家指出", 5, "high"),
114
- Pattern("A7", "模糊归因", r"业内人士表示", 5, "high"),
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
- # A8 伪深度
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
- # A9 假大空成语
128
- Pattern("A9", "假大空成语", r"博大精深", 4, "mid"),
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
- # A10 模板口水
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
- TAIL_PATTERNS: list[Pattern] = [
144
- Pattern("A4", "结尾升华", r"让我们(共同|一起)", 8, "high", "tail"),
145
- Pattern("A4", "结尾升华", r"未来可期", 6, "high", "tail"),
146
- Pattern("A4", "结尾升华", r"任重道远", 6, "high", "tail"),
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
- ALL_PATTERNS = HEAD_PATTERNS + BODY_PATTERNS + TAIL_PATTERNS
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
- final = raw * multiplier
56
+ 本目录不再自带实现 —— 曾经那份是 E 类姿态检测之前的旧快照,会漏掉姿态维度。
57
+ 已查找:
58
+ {paths}
228
59
 
229
- hits.append(
230
- Hit(
231
- code=p.code,
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 main(argv: list[str]) -> int:
427
- parser = argparse.ArgumentParser(
428
- description="中文 AI 味检测器 — 给文章打 AI 味分数"
429
- )
430
- parser.add_argument("input", help="输入文件路径,或 '-' 表示 stdin")
431
- parser.add_argument("--json", action="store_true", help="输出 JSON 格式")
432
- parser.add_argument("-o", "--output", help="输出文件(默认 stdout)")
433
- args = parser.parse_args(argv)
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
- report = scan_text(text)
78
+ def main():
79
+ args = sys.argv[1:]
448
80
 
449
- if args.json:
450
- out = render_json(report)
451
- else:
452
- out = render_markdown(report, source)
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
- if args.output:
455
- Path(args.output).write_text(out, encoding="utf-8")
456
- print(f"✅ 已写入 {args.output}({report.score} 分 / {report.level})")
457
- else:
458
- print(out)
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
- return 0
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(sys.argv[1:]))
101
+ sys.exit(main())
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: topmind-weread
3
- version: 4.14.0
3
+ version: 4.15.1
4
4
  description: >-
5
5
  同步微信读书划线/想法/统计到专题。Use when 微信读书、划线同步、weread、读书笔记。
6
6
  Do NOT use for 普通 URL 捕获、仅整理、出稿、X.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: topmind-write
3
- version: 4.14.0
3
+ version: 4.15.1
4
4
  description: >-
5
5
  写作/润色/出稿到专题或交付层(role:delivery)。Use when 写、起草、续写、润色、出稿、交付、write、draft、deliver。
6
6
  Do NOT use for 仅捕获、仅结构整理、只写记忆、doctor/loop、社交连接器.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: topmind-x
3
- version: 4.14.0
3
+ version: 4.15.1
4
4
  description: >-
5
5
  X/Twitter:归档/搜索(Bearer)、发帖(xurl)、Agent 可用官方 MCP。Use when 发推、推特、twitter、x.com。
6
6
  Do NOT use for 微信读书、非 X 捕获、doctor、长文交付.