Compare commits

...

1 Commits

Author SHA1 Message Date
Ct201314 24e2bd3b4d feat(skills): add polishpro academic research skill 2026-06-12 20:04:06 +08:00
5 changed files with 431 additions and 0 deletions

62
skills/polishpro/SKILL.md Normal file
View File

@ -0,0 +1,62 @@
---
name: polishpro
version: 1.0.0
description: "学术英文润色检查器对英文草稿做句级检查标出冗余短语in order to→to、弱动词/名词化make an investigation of→investigate、被动语态滥用、中式英文/赘词very important、more and more逐条给出更简洁地道的改法建议。当用户提到「英文润色」「检查英文表达」「中式英文」「句子啰嗦」「英文不地道」「polish」时触发。"
metadata:
requires:
optional_bins: ["python"]
---
# polishpro学术英文润色检查器
英文写得出、但总被说「表达不地道」polishpro 不改错字,而是把审稿人读着别扭的
表达——啰嗦、中式、被动滥用——逐句标出来,给更简洁地道的改法。
## 何时使用本技能
- 自己写的英文怕不地道,过一遍
- 句子啰嗦,想压简洁
- 投稿前做一次表达自查
## 与同类工具的区别
同类润色工具多直接调 LLM 重写。本技能是**纯规则引擎**:不联网、不调 LLM基于学术
写作公认的简洁性原则逐条检查,给的是**带解释的改进建议清单**而非黑盒重写。它的价值
在「指出问题 + 教你为什么」,最终改不改、怎么改由你定——既可控、可离线,也帮你
积累地道表达的直觉。
## 四类检查
| 类别 | 例子 |
|------|------|
| 冗余短语 | in order to→todue to the fact that→because |
| 弱动词/名词化 | make an investigation of→investigate |
| 被动语态 | 标出被动结构,提示非方法章节可改主动 |
| 中式英文/赘词 | very important、more and more→increasingly |
另给平均句长与过长句子(>35 词)提示,建议拆分。
## 工作流
```bash
python scripts/polish.py --input draft.txt --output report.md
python scripts/polish.py --text "In order to improve..." --format json
```
| 参数 | 说明 |
|------|------|
| `--input` | 英文草稿文件(*或用 `--text` |
| `--text` | 直接传入英文文本 |
| `--format` | `markdown`(默认)或 `json` |
| `--output` | 输出文件 |
## 注意事项
- 规则覆盖学术写作高频问题,但非穷尽;最终判断结合语境。
- 被动语态在方法章节是规范的,提示仅供参考,不必一律改主动。
- 工具只给建议,不替你重写句子——避免改变你的原意。
## References
- [rules.md](references/rules.md) — 四类检查的规则与建议对照
- [usage.md](references/usage.md) — 使用建议与配合写作流程

View File

@ -0,0 +1,56 @@
# 四类检查规则与建议对照
polishpro 基于学术写作公认的简洁性原则,逐句匹配以下规则。规则定义在 `polish.py`
的四个字典/正则中,可按需扩充。
## 冗余短语WORDY_PHRASES
| 啰嗦写法 | 建议 |
|---------|------|
| in order to | to |
| due to the fact that | because |
| in spite of the fact that | although |
| a number of | many / few |
| in the event that | if |
| it is worth noting that | notably / 直接陈述 |
| with regard to | regarding / about |
| for the purpose of | to / for |
| as a matter of fact | 删去 |
## 弱动词 / 名词化NOMINALIZATIONS
把「动词被名词化再配弱动词make/conduct/perform…」还原成强动词
| 啰嗦 | 强动词 |
|------|--------|
| make an investigation of | investigate |
| conduct an analysis of | analyze |
| perform a comparison of | compare |
| give an explanation of | explain |
| make use of | use |
## 被动语态PASSIVE
正则匹配「be 动词 + 过去分词」。被动在方法章节是规范的(强调操作而非操作者),
但在引言/讨论中过多被动会让表达迂回。工具只标出、不强制改。
## 中式英文 / 赘词CHINGLISH
| 表达 | 建议 |
|------|------|
| more better | better |
| very important | 去 very用精确词 |
| more and more | increasingly |
| as we all know | 学术写作避免 |
| big difference | large / substantial |
| get better | improve |
## 句长
平均句长与单句词数:超过 35 词的句子会被标为「过长,建议拆分」。学术英文并非越长
越严谨,长句易丢失主干。
## 设计原则
工具给「建议」不做「重写」——保留你的原意,只指出问题并解释为什么。这样你改的同时
也在积累地道表达的判断力。

View File

@ -0,0 +1,29 @@
# 使用建议与写作流程
## 怎么用
1. 把英文草稿(段落或全文)存成 .txt或用 `--text` 直接传。
2. 运行得到检查报告:分类统计 + 逐条建议 + 过长句提示。
3. 对照建议逐条改,改不改由你定——建议都附了「为什么」。
## 报告怎么读
- **分类统计**:先看哪类问题最多,集中优化。冗余短语和被动语态通常最常见。
- **逐条建议**:每条给出原片段、问题类型、建议改法、所在整句,方便定位。
- **过长句子**>35 词的句子单列,优先考虑拆分。
## 配合写作流程
- draftsmith 起草 → 自己填实内容 → **polishpro 过一遍英文** → 投稿。
- polishpro 检查的是「表达」,不检查「事实/逻辑」,后者靠 peermirror 自审。
## 注意
- 被动语态提示要结合章节:方法章节用被动是规范的,不必强行改主动。
- 规则是高频问题的覆盖,不是穷尽;它帮你抓大部分明显问题,细腻语感仍需人来把关。
- 工具不重写句子,避免改变原意——它是「教练」不是「代笔」。
## 局限
- 基于规则与正则,无法理解语义,可能漏掉「语法对但表达绕」的句子。
- 仅针对英文;中英混排文本的非英文部分会被忽略。

View File

@ -0,0 +1,209 @@
"""polishpro学术英文润色检查器。
对英文草稿做句级检查中式英文 / 啰嗦 / 不地道的地方标出来给出更简洁
更地道的改法建议它不是改错字而是把审稿人读着别扭的表达指出来并建议怎么改
四类检查纯规则不调 LLM
1. 冗余短语in order totodue to the fact thatbecausea number ofmany
学术写作里高频的啰嗦表达建议精简
2. 弱动词 / 名词化make an investigation ofinvestigateconduct an analysis
analyze动词被名词化再配弱动词的结构还原成强动词
3. 被动语态提示标出被动结构提示在方法之外的章节可考虑主动语态
4. 中式英文 / 赘词more betteraccording tovery important 等中式或口语化表达
每条命中给出原片段问题类型建议改法所在句子纯本地纯标准库离线可用
它给的是改进建议清单最终改不改怎么改由你定
用法
python polish.py --input draft.txt --output report.md
python polish.py --text "In order to improve the performance..." --format json
"""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any
if hasattr(sys.stdout, "reconfigure"):
try:
sys.stdout.reconfigure(encoding="utf-8")
except Exception:
pass
# 冗余短语:模式 -> 建议
WORDY_PHRASES = {
r"\bin order to\b": "to",
r"\bdue to the fact that\b": "because",
r"\bin spite of the fact that\b": "although",
r"\ba (large |small )?number of\b": "many / few",
r"\bin the event that\b": "if",
r"\bat this point in time\b": "now",
r"\bin the near future\b": "soon",
r"\bit is worth noting that\b": "notably / (直接陈述)",
r"\bit should be noted that\b": "note that / (直接陈述)",
r"\bwith regard to\b": "regarding / about",
r"\bin terms of\b": "(常可删去或换具体说法)",
r"\bfor the purpose of\b": "to / for",
r"\bin the case of\b": "for / in",
r"\bas a matter of fact\b": "(多余,建议删)",
}
# 名词化 + 弱动词 -> 强动词
NOMINALIZATIONS = {
r"\bmake an investigation of\b": "investigate",
r"\bconduct an analysis of\b": "analyze",
r"\bperform a comparison of\b": "compare",
r"\bgive an explanation of\b": "explain",
r"\bprovide a description of\b": "describe",
r"\bcarry out an evaluation of\b": "evaluate",
r"\bmake use of\b": "use",
r"\bmake a decision\b": "decide",
}
# 中式英文 / 赘词 / 不地道
CHINGLISH = {
r"\bmore better\b": "bettermore 多余)",
r"\bvery (important|significant|crucial)\b": "(去掉 very用更精确的词或直接陈述",
r"\bmore and more\b": "increasingly",
r"\bas we all know\b": "(学术写作避免,直接陈述或引用)",
r"\bin a word\b": "in short / in summary",
r"\bby the way\b": "(学术写作不用)",
r"\bso\b": "(句首 so 偏口语,用 therefore/thus",
r"\bbig (difference|improvement|effect)\b": "large / substantial",
r"\bget (better|worse)\b": "improve / deteriorate",
}
# 被动语态粗检be 动词 + 过去分词)
PASSIVE = re.compile(
r"\b(is|are|was|were|be|been|being)\s+(\w+ed|done|made|shown|found|used|given|taken|seen|known)\b",
re.IGNORECASE)
def split_sentences(text: str) -> list[str]:
text = text.lstrip("\ufeff")
text = re.sub(r"\s+", " ", text.strip())
# 简单断句(润色场景对断句精度要求不高)
parts = re.split(r"(?<=[.!?])\s+(?=[A-Z])", text)
return [p.strip() for p in parts if p.strip()]
def _check_dict(sentence: str, patterns: dict[str, str], category: str) -> list[dict[str, Any]]:
issues = []
for pat, suggestion in patterns.items():
for m in re.finditer(pat, sentence, re.IGNORECASE):
issues.append({
"category": category,
"fragment": m.group(0),
"suggestion": suggestion,
"sentence": sentence,
})
return issues
def check_sentence(sentence: str) -> list[dict[str, Any]]:
issues: list[dict[str, Any]] = []
issues += _check_dict(sentence, WORDY_PHRASES, "冗余短语")
issues += _check_dict(sentence, NOMINALIZATIONS, "弱动词/名词化")
issues += _check_dict(sentence, CHINGLISH, "中式英文/赘词")
# 被动语态
for m in PASSIVE.finditer(sentence):
issues.append({
"category": "被动语态",
"fragment": m.group(0),
"suggestion": "方法章节可保留;其余章节考虑改主动语态使表达更直接",
"sentence": sentence,
})
return issues
def polish(text: str) -> dict[str, Any]:
sentences = split_sentences(text)
all_issues: list[dict[str, Any]] = []
for s in sentences:
all_issues.extend(check_sentence(s))
by_cat: dict[str, int] = {}
for it in all_issues:
by_cat[it["category"]] = by_cat.get(it["category"], 0) + 1
# 简单可读性提示:平均句长
words = sum(len(re.findall(r"\b\w+\b", s)) for s in sentences)
avg_len = round(words / len(sentences), 1) if sentences else 0
long_sents = [s for s in sentences if len(re.findall(r"\b\w+\b", s)) > 35]
return {
"sentence_count": len(sentences),
"issue_count": len(all_issues),
"by_category": by_cat,
"issues": all_issues,
"avg_sentence_length": avg_len,
"long_sentences": long_sents,
}
def render_markdown(data: dict[str, Any]) -> str:
lines = [
"# 英文润色检查报告",
"",
f"{data['sentence_count']} 句,发现 {data['issue_count']} 处可改进,"
f"平均句长 {data['avg_sentence_length']} 词。",
"",
"## 分类统计",
"",
]
if data["by_category"]:
for cat, n in sorted(data["by_category"].items(), key=lambda kv: -kv[1]):
lines.append(f"- {cat}{n}")
else:
lines.append("- 未发现明显问题,表达较简洁。")
lines += ["", "## 逐条建议", ""]
if data["issues"]:
for i, it in enumerate(data["issues"], start=1):
lines.append(f"{i}. [{it['category']}] `{it['fragment']}` → 建议:{it['suggestion']}")
lines.append(f" > 所在句:{it['sentence']}")
lines.append("")
else:
lines.append("(无)")
lines.append("")
if data["long_sentences"]:
lines += ["## 过长句子(>35 词,建议拆分)", ""]
for s in data["long_sentences"]:
lines.append(f"- {s}")
lines.append("")
lines += ["---", "",
"由 polishpro 生成。基于规则的改进建议,最终改法由你定;它不替你重写句子。"]
return "\n".join(lines)
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(prog="polishpro", description="学术英文润色检查器")
p.add_argument("--input", type=Path, help="英文草稿文件")
p.add_argument("--text", help="直接传入英文文本")
p.add_argument("--format", choices=["markdown", "json"], default="markdown")
p.add_argument("--output", type=Path)
args = p.parse_args(argv)
if args.input and args.input.exists():
text = args.input.read_text(encoding="utf-8-sig", errors="replace")
elif args.text:
text = args.text
else:
print("错误:请用 --input 或 --text 提供英文文本。", file=sys.stderr)
return 2
data = polish(text)
out = (json.dumps(data, ensure_ascii=False, indent=2) if args.format == "json"
else render_markdown(data))
if args.output:
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(out, encoding="utf-8")
print(f"已写入 {args.output}")
else:
print(out)
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -0,0 +1,75 @@
"""polishpro 单元测试。"""
from __future__ import annotations
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
import pytest
from polish import split_sentences, check_sentence, polish, render_markdown
class TestSplit:
def test_basic(self):
assert len(split_sentences("First one. Second one. Third one.")) == 3
def test_bom(self):
s = split_sentences("\ufeffHello world. Bye.")
assert not s[0].startswith("\ufeff")
class TestCheckSentence:
def test_wordy(self):
issues = check_sentence("In order to test it.")
assert any(i["category"] == "冗余短语" and "in order to" in i["fragment"].lower() for i in issues)
def test_nominalization(self):
issues = check_sentence("We make an investigation of the data.")
cats = [i["category"] for i in issues]
assert "弱动词/名词化" in cats
def test_chinglish(self):
issues = check_sentence("This is a very important result.")
assert any(i["category"] == "中式英文/赘词" for i in issues)
def test_passive(self):
issues = check_sentence("The model was trained on the dataset.")
assert any(i["category"] == "被动语态" for i in issues)
def test_clean_sentence(self):
issues = check_sentence("We trained the model.")
# 主动、简洁,应无或极少问题
assert all(i["category"] != "冗余短语" for i in issues)
class TestPolish:
def test_counts(self):
data = polish("In order to win, we make use of tricks. Due to the fact that it works.")
assert data["issue_count"] >= 3
assert data["sentence_count"] == 2
def test_by_category(self):
data = polish("In order to improve, due to the fact that data is limited.")
assert "冗余短语" in data["by_category"]
def test_avg_length(self):
data = polish("Short one. Another short.")
assert data["avg_sentence_length"] > 0
def test_long_sentence_flagged(self):
long = " ".join(["word"] * 40) + "."
data = polish(long)
assert data["long_sentences"]
class TestRender:
def test_render_with_issues(self):
md = render_markdown(polish("In order to test, we make use of data."))
assert "英文润色检查报告" in md
assert "逐条建议" in md
def test_render_clean(self):
md = render_markdown(polish("We trained the model."))
assert "英文润色检查报告" in md
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-v"]))