@yottameta/yotta-humanize 0.1.3 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## v0.2.1 (2026-09-19)
2
+
3
+ - 修复:`score` / `analyze` / `report` / `suggest` / `rewrite` 以位置参数直接传文本时报 `expected string or bytes-like object, got 'list'`(`_read_text` 把文本列表原样返回,未拼接);`-f` / `--stdin` 路径不受影响。
4
+ - 补回归测试:位置参数单文本 / 多词拼接共 3 项(修复前复现红,修复后全量 158/158 通过)。
5
+
6
+ ## v0.2.0 (2026-09-09)
7
+
8
+ - 评测完善批 2:FAQ 补速查索引、触发说明与安装排障;新增 references/walkthroughs.md(公众号/正式纪要/批量门禁三类复杂走查)。
9
+ - 安装器错误处理:用法/目标/安装错误统一退出码与修复建议;新增 test/install.test.js。
10
+ - package.json 补 npm test 脚本;版本对齐 0.2.0(package / SKILL / CHANGELOG / CLI)。
11
+
1
12
  ## v0.1.3 (2026-09-01)
2
13
 
3
14
  评测反馈优化(文档 + 错误提示,功能不变)。
package/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: yotta-humanize
3
- version: 0.1.3
3
+ version: 0.2.1
4
4
  description: 元真 —— 去 AI 味的中文写作编辑技能:检测器引擎(24 类规则 + 词表 + 统计突发性)识别并改写 AI 腔文本,让中文写作更自然、更像人写的。触发:编辑 / 润色文本、去 AI 味、让文章 / 文案 / 回复更像人写、发现文本充斥着 AI 常用词与句式(赋能 / 闭环 / 值得注意的是 / 综上所述 / 希望对你有所帮助 等)、给 AI 生成的中文稿件做检测与改写。边界:只处理文本,不生成新内容;不改写事实 / 数据 / 专有名词;不破坏作者原意;改写为确定性规则,不依赖模型。
5
5
  license: MIT
6
6
  ---
@@ -73,6 +73,12 @@ python3 scripts/yotta_humanize.py score -f article.md --gate --threshold 45
73
73
  - references/patterns.md — 24 类规则目录与命中示例
74
74
  - references/scoring.md — 评分公式与统计量说明
75
75
  - references/rewriting.md — 确定性改写规则与 CLI 协议
76
+ - references/faq.md — 常见问题速查(输入 / 结果 / 触发 / 安装)
77
+ - references/walkthroughs.md — 公众号 / 正式纪要 / 批量门禁三类复杂走查
78
+
79
+ ## 常见问题(速查)
80
+
81
+ 文本传不进去、改写变化不大、触发不灵、安装不上时,先看 references/faq.md。
76
82
 
77
83
  ## 责任声明
78
84
 
package/bin/install.js CHANGED
@@ -1,51 +1,97 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * yotta-humanize 跨平台安装器(YottaSkills)
4
- * 用法:
5
- * npx -y @yottameta/yotta-humanize --agent <name> # 按智能体默认用户级目录安装(推荐)
6
- * npx -y @yottameta/yotta-humanize --dir PATH # 装到指定目录(用户改了目录的智能体)
7
- * npx -y @yottameta/yotta-humanize -g # 安装到全部已知智能体用户级目录
8
- * npx -y @yottameta/yotta-humanize # 安装到检测到的项目级目录
9
- * npx -y @yottameta/yotta-humanize --list # 列出智能体 -> 默认目录
3
+ * @yottameta/yotta-humanize cross-platform installer.
4
+ *
5
+ * Usage:
6
+ * npx -y @yottameta/yotta-humanize --agent <name>
7
+ * npx -y @yottameta/yotta-humanize --dir <path>
8
+ * npx -y @yottameta/yotta-humanize -g
9
+ * npx -y @yottameta/yotta-humanize
10
+ * npx -y @yottameta/yotta-humanize --list
10
11
  */
11
12
  'use strict';
13
+
12
14
  const fs = require('fs');
13
15
  const path = require('path');
14
16
  const os = require('os');
15
17
 
16
18
  const SKILL_NAME = 'yotta-humanize';
17
19
  const PKG_ROOT = path.join(__dirname, '..');
20
+ const COPY_SKIP = new Set(['package.json', 'bin', 'node_modules', '.git', '.github', 'test']);
18
21
 
19
- // 智能体 -> 用户级默认技能目录(dirs 按优先级排列;--agent 装到第一个)
20
- // 依据官方文档:.agents/skills 并非通用目录,被 OpenCode / Cursor / Cline / Amp /
21
- // Kimi / Gemini CLI / GitHub Copilot 等读取;Claude Code 与 Codex 默认不读 .agents。
22
22
  const AGENT_DIRS = {
23
23
  claude: { label: 'Claude Code', dirs: ['.claude/skills'] },
24
24
  cursor: { label: 'Cursor', dirs: ['.cursor/skills', '.agents/skills'] },
25
- codex: { label: 'Codex', dirs: ['.codex/skills'] }, // 特判:$CODEX_HOME/skills
25
+ codex: { label: 'Codex', dirs: ['.codex/skills'] },
26
26
  gemini: { label: 'Gemini CLI', dirs: ['.gemini/skills', '.agents/skills'] },
27
27
  goose: { label: 'Goose', dirs: ['.config/goose/skills', '.agents/skills'] },
28
28
  amp: { label: 'Amp', dirs: ['.config/agents/skills', '.agents/skills'] },
29
- opencode: { label: 'OpenCode', dirs: ['.config/opencode/skills'] }, // 特判:$XDG_CONFIG_HOME
29
+ opencode: { label: 'OpenCode', dirs: ['.config/opencode/skills'] },
30
30
  windsurf: { label: 'Windsurf', dirs: ['.codeium/windsurf/skills'] },
31
31
  workbuddy: { label: 'WorkBuddy', dirs: ['.workbuddy/skills'] },
32
32
  kiro: { label: 'Kiro', dirs: ['.kiro/skills'] },
33
33
  trae: { label: 'Trae Code CLI', dirs: ['.traecli/skills'] },
34
- 'trae-cn': { label: 'Trae IDE(国内)', dirs: ['.trae-cn/skills'] },
34
+ 'trae-cn': { label: 'Trae IDE', dirs: ['.trae-cn/skills'] },
35
35
  qwen: { label: 'Qwen Code', dirs: ['.qwen/skills'] },
36
- comate: { label: 'Comate 文心快码', dirs: ['.comate/skills'] },
36
+ comate: { label: 'Comate', dirs: ['.comate/skills'] },
37
37
  codebuddy: { label: 'CodeBuddy Code', dirs: ['.codebuddy/skills'] },
38
38
  kimi: { label: 'Kimi Code CLI', dirs: ['.kimi/skills'] },
39
- agents: { label: '通用 AGENTS.md', dirs: ['.agents/skills'] },
39
+ agents: { label: 'Generic AGENTS.md', dirs: ['.agents/skills'] },
40
40
  };
41
41
 
42
- // Codex 用户级目录特判:优先 $CODEX_HOME/skills,否则 ~/.codex/skills
42
+ class UsageError extends Error {}
43
+ class TargetError extends Error {}
44
+ class InstallError extends Error {}
45
+
46
+ function usage() {
47
+ console.log('yotta-humanize installer');
48
+ console.log('');
49
+ console.log('Usage:');
50
+ console.log(' npx -y @yottameta/yotta-humanize --agent <name> Install to an agent default directory');
51
+ console.log(' npx -y @yottameta/yotta-humanize --dir <path> Install to a custom directory');
52
+ console.log(' npx -y @yottameta/yotta-humanize -g Install to all known agent directories');
53
+ console.log(' npx -y @yottameta/yotta-humanize Auto-detect project-level directories');
54
+ console.log(' npx -y @yottameta/yotta-humanize --list List supported agent directories');
55
+ console.log('');
56
+ console.log('Options:');
57
+ console.log(' --agent <name> Agent key, see --list');
58
+ console.log(' --dir <path> Custom skills directory');
59
+ console.log(' -g, --global Install to all known user-level directories');
60
+ console.log(' --list, -l List supported agents');
61
+ console.log(' -h, --help Show this help');
62
+ }
63
+
64
+ function parseArgs(argv) {
65
+ const opts = { help: false, list: false, global: false, dir: null, agent: null };
66
+ for (let i = 0; i < argv.length; i++) {
67
+ const arg = argv[i];
68
+ if (arg === '--help' || arg === '-h') opts.help = true;
69
+ else if (arg === '--list' || arg === '-l') opts.list = true;
70
+ else if (arg === '--global' || arg === '-g') opts.global = true;
71
+ else if (arg === '--dir') {
72
+ const value = argv[++i];
73
+ if (!value) throw new UsageError('--dir requires a non-empty path');
74
+ opts.dir = value;
75
+ } else if (arg === '--agent') {
76
+ const value = argv[++i];
77
+ if (!value) throw new UsageError('--agent requires a non-empty name');
78
+ opts.agent = value.toLowerCase();
79
+ } else {
80
+ throw new UsageError('Unknown argument: ' + arg);
81
+ }
82
+ }
83
+ if (!opts.help) {
84
+ const selected = [opts.dir, opts.agent, opts.global].filter(Boolean).length;
85
+ if (selected > 1) throw new UsageError('Use only one of --dir, --agent, or -g');
86
+ }
87
+ return opts;
88
+ }
89
+
43
90
  function codexUserDir() {
44
91
  const base = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
45
92
  return path.join(base, 'skills');
46
93
  }
47
94
 
48
- // OpenCode 用户级目录特判:优先 $XDG_CONFIG_HOME/opencode/skills,否则 ~/.config/opencode/skills
49
95
  function opencodeUserDir() {
50
96
  const base = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
51
97
  return path.join(base, 'opencode', 'skills');
@@ -57,82 +103,65 @@ function resolveUserDir(rel) {
57
103
  return path.join(os.homedir(), rel);
58
104
  }
59
105
 
60
- function installTo(dest) {
61
- const target = path.join(dest, SKILL_NAME);
62
- fs.mkdirSync(target, { recursive: true });
63
- copyDir(PKG_ROOT, target, new Set(['package.json', 'bin', 'node_modules', '.git']));
64
- console.log('installed -> ' + target);
65
- }
66
-
67
- function copyDir(src, dst, skip) {
68
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
69
- if (skip.has(entry.name)) continue;
70
- const s = path.join(src, entry.name);
71
- const d = path.join(dst, entry.name);
72
- if (entry.isDirectory()) {
73
- fs.mkdirSync(d, { recursive: true });
74
- copyDir(s, d, skip);
75
- } else if (entry.isFile()) {
76
- fs.copyFileSync(s, d);
77
- }
78
- }
79
- }
80
-
81
106
  function displayDir(rel) {
82
107
  if (process.platform === 'win32') return '%USERPROFILE%\\' + rel.replace(/\//g, '\\');
83
108
  return '~/' + rel;
84
109
  }
85
110
 
86
- function main() {
87
- const args = process.argv.slice(2);
88
- const isGlobal = args.includes('-g') || args.includes('--global');
89
- const list = args.includes('--list') || args.includes('-l');
90
- let explicitDir = null;
91
- const di = args.indexOf('--dir');
92
- if (di !== -1 && args[di + 1]) explicitDir = args[di + 1];
93
- let agent = null;
94
- const ai = args.indexOf('--agent');
95
- if (ai !== -1 && args[ai + 1]) agent = String(args[ai + 1]).toLowerCase();
96
-
97
- if (list) {
98
- console.log('智能体 -> 默认技能目录(--agent <name> 装到第一个,用户级):');
99
- for (const [key, v] of Object.entries(AGENT_DIRS)) {
100
- const resolved = v.dirs.map(displayDir);
101
- console.log(' ' + key.padEnd(10) + v.label.padEnd(18) + resolved.join('、'));
102
- }
103
- console.log('\n说明:Windows 用 %USERPROFILE%,Linux/macOS 用 ~;仅收录有官方默认目录的智能体。');
104
- console.log('改了目录的请用 --dir <路径>,不要依赖默认位置;若设置了 CODEX_HOME / XDG_CONFIG_HOME,安装自动以该变量为准。');
105
- return;
111
+ function printList() {
112
+ console.log('Agent -> default skill directory:');
113
+ for (const [key, value] of Object.entries(AGENT_DIRS)) {
114
+ console.log(' ' + key.padEnd(12) + value.label.padEnd(20) + value.dirs.map(displayDir).join(', '));
106
115
  }
116
+ console.log('');
117
+ console.log('Use --dir <path> for agents not listed. CODEX_HOME and XDG_CONFIG_HOME are respected.');
118
+ }
107
119
 
108
- if (explicitDir) { installTo(explicitDir); return; }
120
+ function assertSafeTarget(target) {
121
+ const rel = path.relative(PKG_ROOT, target);
122
+ if (rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))) {
123
+ throw new UsageError('Target directory must be outside the skill source directory');
124
+ }
125
+ }
109
126
 
110
- if (agent) {
111
- const info = AGENT_DIRS[agent];
112
- if (!info) {
113
- console.log('未收录智能体: ' + agent + '。请用 --dir <路径> 指定技能目录。');
114
- console.log('可用: ' + Object.keys(AGENT_DIRS).join(', '));
115
- return;
127
+ function copyDir(src, dst, skip) {
128
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
129
+ if (skip.has(entry.name)) continue;
130
+ const from = path.join(src, entry.name);
131
+ const to = path.join(dst, entry.name);
132
+ try {
133
+ if (entry.isDirectory()) {
134
+ fs.mkdirSync(to, { recursive: true });
135
+ copyDir(from, to, skip);
136
+ } else if (entry.isFile()) {
137
+ fs.copyFileSync(from, to);
138
+ }
139
+ } catch (err) {
140
+ throw new InstallError('Failed to copy ' + from + ' -> ' + to + ': ' + err.message);
116
141
  }
117
- installTo(resolveUserDir(info.dirs[0]));
118
- console.log('完成。');
119
- return;
120
142
  }
143
+ }
121
144
 
122
- if (isGlobal) {
123
- const seen = new Set();
124
- for (const v of Object.values(AGENT_DIRS)) {
125
- for (const d of v.dirs) {
126
- if (seen.has(d)) continue;
127
- seen.add(d);
128
- installTo(resolveUserDir(d));
129
- }
145
+ function installTo(dest) {
146
+ if (!dest || typeof dest !== 'string') throw new UsageError('Destination directory is required');
147
+ const target = path.resolve(dest, SKILL_NAME);
148
+ assertSafeTarget(target);
149
+ try {
150
+ fs.mkdirSync(target, { recursive: true });
151
+ copyDir(PKG_ROOT, target, COPY_SKIP);
152
+ if (!fs.existsSync(path.join(target, 'SKILL.md'))) {
153
+ throw new InstallError('Installed directory is missing SKILL.md');
130
154
  }
131
- console.log('完成。');
132
- return;
155
+ } catch (err) {
156
+ if (err instanceof UsageError || err instanceof InstallError) throw err;
157
+ throw new InstallError('Cannot install to ' + target + ': ' + err.message);
133
158
  }
159
+ console.log('installed -> ' + target);
160
+ return target;
161
+ }
134
162
 
135
- const PROJECT_DIRS = [
163
+ function projectDirs() {
164
+ return [
136
165
  '.claude/skills',
137
166
  '.cursor/skills',
138
167
  '.codex/skills',
@@ -150,14 +179,67 @@ function main() {
150
179
  '.codebuddy/skills',
151
180
  '.kimi/skills',
152
181
  '.agents/skills',
153
- ];
154
- let installedAny = false;
155
- for (const d of PROJECT_DIRS) {
156
- if (fs.existsSync(d)) { installTo(d); installedAny = true; }
182
+ ].filter((dir) => fs.existsSync(dir));
183
+ }
184
+
185
+ function run() {
186
+ const opts = parseArgs(process.argv.slice(2));
187
+ if (opts.help) { usage(); return; }
188
+ if (opts.list) { printList(); return; }
189
+
190
+ if (opts.dir) { installTo(opts.dir); return; }
191
+
192
+ if (opts.agent) {
193
+ const info = AGENT_DIRS[opts.agent];
194
+ if (!info) {
195
+ throw new UsageError('Unknown agent: ' + opts.agent + '. Available: ' + Object.keys(AGENT_DIRS).join(', ') + '. Use --dir for a custom directory.');
196
+ }
197
+ installTo(resolveUserDir(info.dirs[0]));
198
+ return;
157
199
  }
158
- if (!installedAny) {
159
- console.log('未检测到项目级智能体目录。可手动复制,或用 --agent <name> / -g 装到用户级。');
200
+
201
+ if (opts.global) {
202
+ const seen = new Set();
203
+ let count = 0;
204
+ for (const value of Object.values(AGENT_DIRS)) {
205
+ for (const dir of value.dirs) {
206
+ if (seen.has(dir)) continue;
207
+ seen.add(dir);
208
+ installTo(resolveUserDir(dir));
209
+ count++;
210
+ }
211
+ }
212
+ console.log('Installed to ' + count + ' directories.');
213
+ return;
214
+ }
215
+
216
+ const dirs = projectDirs();
217
+ if (!dirs.length) {
218
+ throw new TargetError('No project-level agent directory detected. Use --agent <name> or --dir <path>.');
219
+ }
220
+ for (const dir of dirs) installTo(dir);
221
+ }
222
+
223
+ function main() {
224
+ try {
225
+ run();
226
+ } catch (err) {
227
+ if (err instanceof UsageError) {
228
+ console.error('Usage error: ' + err.message);
229
+ usage();
230
+ process.exitCode = 2;
231
+ } else if (err instanceof TargetError) {
232
+ console.error('Target error: ' + err.message);
233
+ process.exitCode = 4;
234
+ } else if (err instanceof InstallError) {
235
+ console.error('Install failed: ' + err.message);
236
+ console.error('Fix: check directory permissions and free space, then retry. Use --dir to choose another directory.');
237
+ process.exitCode = 1;
238
+ } else {
239
+ console.error('Unexpected error: ' + (err && err.message ? err.message : String(err)));
240
+ process.exitCode = 1;
241
+ }
160
242
  }
161
243
  }
162
244
 
163
- main();
245
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yottameta/yotta-humanize",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "Yuanzhen (元真) — AI-flavor remover for Chinese writing: a detector engine (24 rule classes + wordlists + statistical burstiness) that identifies and rewrites AI-typical Chinese text to make it read more naturally. Triggers when editing/polishing text, removing AI flavor, or making copy read more human. Boundaries: text-only, deterministic rewrites, no model dependency.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -18,7 +18,10 @@
18
18
  "bin",
19
19
  "NOTICE",
20
20
  "CHANGELOG.md",
21
- "README.zh-CN.md"
21
+ "README.zh-CN.md",
22
+ "!scripts/__pycache__",
23
+ "!**/__pycache__",
24
+ "!**/*.pyc"
22
25
  ],
23
26
  "repository": {
24
27
  "type": "git",
@@ -29,5 +32,8 @@
29
32
  },
30
33
  "bin": {
31
34
  "yotta-humanize": "bin/install.js"
35
+ },
36
+ "scripts": {
37
+ "test": "node --test \"test/*.test.js\" && python scripts/test_yotta_humanize.py"
32
38
  }
33
39
  }
package/references/faq.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  > 常见问题速查:文本传不进去、结果不像预期、文件报错时先看这里。
4
4
 
5
+ ## 速查索引
6
+
7
+ - **输入**:怎么传文本 · UTF-8 报错 · 长文怎么处理
8
+ - **结果**:改写变化不大 · rewrite/suggest 区别 · 会不会改坏
9
+ - **规则**:检测原理 · CI 拦截 · 退出码
10
+ - **使用**:什么时候自动触发 · 安装/智能体目录
11
+
12
+ ## 1. 怎么把文本传进去?
13
+
5
14
  ## 1. 怎么把文本传进去?
6
15
  三种方式任选:`-f 文件.txt`(读 UTF-8 文件)、`--stdin`(管道输入)、直接传文本参数(`yotta-humanize score "这段文字"`)。注意顺序:`score / analyze / report / suggest / rewrite` 子命令 + 输入方式。
7
16
 
@@ -32,3 +41,11 @@
32
41
 
33
42
  ## 10. 边界
34
43
  只处理文本,不生成新内容;不改写事实 / 数据 / 专有名词;不破坏作者原意。AI 腔极重或嵌套复杂的文本,建议配合人工润色。
44
+
45
+ ## 11. 什么时候会触发?
46
+
47
+ 当你编辑/润色文本、给 AI 生成内容做检测、或发现文本里满是“赋能 / 闭环 / 综上所述 / 希望对你有所帮助”这类 AI 常用词时触发;用户也可以直接说“元真 / 去 AI 味 / 帮我改得像人写的”。
48
+
49
+ ## 12. 安装不上 / 智能体没识别怎么办?
50
+
51
+ 用 `npx -y @yottameta/yotta-humanize --agent <名称>` 装到已知智能体,或 `--dir <技能目录>` 装到自定义路径;未收录智能体用 `--dir`。走国内 npm 镜像遇到传播延迟时临时加 `--registry=https://registry.npmjs.org/`。装完后新会话自动加载才会生效。
@@ -0,0 +1,28 @@
1
+ # 元真复杂场景走查
2
+
3
+ ## 走查 1:公众号推文去 AI 味
4
+
5
+ 输入:一篇约 2000 字的“行业观察”,开头“在当今数字化浪潮的背景下……赋能……闭环……总而言之”,结尾“希望对你有所帮助”。
6
+
7
+ 1. 先 `score -f article.md` 拿基线分;预期偏高。
8
+ 2. `analyze` 看命中规则,优先处理套话开头/结尾、赋能/闭环等词表替换。
9
+ 3. `rewrite` 做机械改写,核对修复清单;保留事实数据与专有名词。
10
+ 4. 对判断性改写(排比拆句、模糊归因)用 `suggest` 输出人工建议,不在 rewrite 里强改。
11
+ 5. 二次 `score` 对比,确认评分下降且原意未变。
12
+
13
+ ## 走查 2:会议纪要不破坏正式语气
14
+
15
+ 输入:一段会议纪要,AI 痕迹主要是“值得注意的是”“综上所述”“我们”滥用,但语气偏正式。
16
+
17
+ 1. 先跑 `analyze` 分五组看命中,避免只按词表删。
18
+ 2. 正式文档优先 `suggest` 而不是 `rewrite`,让作者选择保留正式语气的改写。
19
+ 3. 检查“我们”是否真的冗余;数据、结论、待办一字不改。
20
+ 4. 输出改写建议清单供人工确认,而不是直接交付改写稿。
21
+
22
+ ## 走查 3:CSV/接口文案批量门禁
23
+
24
+ 输入:一批由 AI 生成的中文短文案,要统一过 CI 防 AI 味。
25
+
26
+ 1. 用 `score --stdin --gate --threshold 45` 接入发布前检查。
27
+ 2. 超阈值的条目单独 `suggest`/`rewrite`,保留作者原意。
28
+ 3. 门禁只看确定性评分,不替代人工编辑;评分低于阈值只代表未命中规则,不代表“一定不像 AI 写的”。
@@ -178,6 +178,22 @@ def test_cli():
178
178
  check("score 文本输出为数字", r.returncode == 0 and r.stdout.strip().isdigit(),
179
179
  "rc=%d out=%r" % (r.returncode, r.stdout))
180
180
 
181
+ # 位置参数直接传文本(v0.2.1 回归:_read_text 曾把 args.text 列表原样返回)
182
+ r = _run(["score", ai])
183
+ check("score 位置参数直接传文本",
184
+ r.returncode == 0 and r.stdout.strip().isdigit(),
185
+ "rc=%d out=%r err=%r" % (r.returncode, r.stdout[:60], r.stderr[:80]))
186
+
187
+ r = _run(["rewrite", ai])
188
+ check("rewrite 位置参数直接传文本",
189
+ r.returncode == 0 and "改写后评分" in r.stdout,
190
+ "rc=%d err=%r" % (r.returncode, r.stderr[:80]))
191
+
192
+ r = _run(["score", "众所周知,", "未来可期。"])
193
+ check("score 多词位置参数按空格拼接",
194
+ r.returncode == 0 and r.stdout.strip().isdigit(),
195
+ "rc=%d out=%r err=%r" % (r.returncode, r.stdout[:60], r.stderr[:80]))
196
+
181
197
  r = _run(["score", "--gate", "--threshold", "1"], inp=ai)
182
198
  check("score --gate 高分退出码 1", r.returncode == 1, "rc=%d" % r.returncode)
183
199
 
@@ -47,7 +47,7 @@ _HERE = Path(__file__).resolve().parent
47
47
  sys.path.insert(0, str(_HERE))
48
48
  import humanize_rules as HR # noqa: E402
49
49
 
50
- VERSION = "0.1.3"
50
+ VERSION = "0.2.1"
51
51
  TOOL_NAME = "yotta-humanize"
52
52
  TOOL_CN = "元真"
53
53
  DEFAULT_THRESHOLD = 45
@@ -525,7 +525,7 @@ def _read_text(args):
525
525
  if getattr(args, "stdin", False):
526
526
  return _read_stdin()
527
527
  if getattr(args, "text", None):
528
- return args.text
528
+ return " ".join(args.text)
529
529
  if not sys.stdin.isatty():
530
530
  return _read_stdin()
531
531
  raise SystemExit("缺少输入:用 -f 指定文件、--stdin 读管道,或直接传文本参数。")