Compare commits

...

1 Commits

Author SHA1 Message Date
Ct201314 50f1e92d0b feat(skills): add draftsmith academic research skill 2026-06-12 20:03:59 +08:00
5 changed files with 430 additions and 0 deletions

View File

@ -0,0 +1,66 @@
---
name: draftsmith
version: 1.0.0
description: "论文段落起草器:把结果要点、图表说明或中文草稿,套进 IMRaD引言/方法/结果/讨论)章节的学术句式骨架,编织成连贯的论文段落初稿,需填数字/引用处留明确占位且绝不编造数据。当用户提到「起草论文段落」「写引言/结果/讨论」「把要点写成正文」「IMRaD」「论文初稿」时触发。"
metadata:
requires:
optional_bins: ["python"]
---
# draftsmith论文段落起草器
对着一张图和几句结论憋不出英文正文draftsmith 把你的零散要点,套进学术写作的
句式骨架,搭出一段像样的初稿,给你一个起点。
## 何时使用本技能
- 有结果不知道怎么组织成段落
- 中文草稿想转写成论文段落结构
- 某一节卡住,想先搭个初稿
## 与同类工具的区别
同类写作工具多直接调 LLM 生成整段。本技能是**纯本地模板引擎**:不联网、不调 LLM
按 IMRaD 各章节的学术句式骨架,把你给的 bullet 要点编织成连贯段落,并在需要具体
数字/引用处**留明确占位**`[填统计量]`、`[引用]`)。它的原则是**绝不编造数据或
参考文献**——只搭结构,事实由你填。这既保证可控、可离线,也避免 AI 杜撰带来的学术风险。
## 三个核心能力
| 能力 | 说明 |
|------|------|
| 章节句式骨架 | 引言/方法/结果/讨论各一套学术句式(背景→空白→贡献 等) |
| 要点编织 | bullet 要点用过渡词组织成连贯段落,非简单罗列 |
| 占位提醒 | 需具体数字/引用处留 `[...]` 占位,不编造 |
## 工作流
```bash
# 起草单个章节
python scripts/draft.py --section results --points "实验组准确率更高" "差异显著"
# 多章节一次起草JSON
python scripts/draft.py --spec paper.json --output draft.md
```
`paper.json` 结构:`{"topic": "...", "sections": {"results": ["要点1", "要点2"], "discussion": [...]}}`
| 参数 | 说明 |
|------|------|
| `--section` | 单章节introduction/methods/results/discussion |
| `--points` | 要点列表(配合 --section |
| `--topic` | 研究主题(用于引言开头) |
| `--spec` | 多章节要点 JSON |
| `--format` | `markdown`(默认)或 `json` |
| `--output` | 输出文件 |
## 注意事项
- 工具只搭初稿骨架,`[...]` 占位处的数字、引用、对象需你填实,绝不编造。
- 结果段不写意义解读(留给讨论段),符合 IMRaD 规范。
- 每个章节附写作提示,引导你补全该节应有的要素。
## References
- [imrad-templates.md](references/imrad-templates.md) — 各章节句式骨架
- [usage.md](references/usage.md) — 要点写法与使用建议

View File

@ -0,0 +1,52 @@
# IMRaD 章节句式骨架
draftsmith 为四个核心章节各准备一套句式骨架,把要点填进去组织成段落。
## 引言 Introduction
结构:背景重要性 → 研究空白 → 本文工作与贡献。
- 开头:「近年来,{主题}受到广泛关注。」
- 要点编织:陈述已有进展。
- 空白:「然而,[现有方法的不足/尚未解决的问题]仍有待研究。」
- 贡献:「为此,本文[提出/研究/分析]……,主要贡献包括:」
## 方法 Methods
结构:可复现地交代数据、流程、参数、统计方法。建议用过去时、被动语态。
- 开头:「本研究采用[数据来源/实验设置]开展。」
- 过渡:具体而言 / 在此基础上 / 随后。
- 收束:「所有统计分析采用[检验方法],显著性水平设为 α = 0.05。」
## 结果 Results
结构:客观陈述发现 + 对应图表 + 统计证据。**不解读意义**(留给讨论)。
- 开头:「结果显示,[主要发现]。」
- 过渡:此外 / 进一步地 / 与之相比。
- 收束:「上述结果在[图/表 X]中汇总([填统计量、p 值、效应量])。」
## 讨论 Discussion
结构:呼应发现意义 → 与文献对比 → 承认局限 → 展望。
- 开头:「本研究表明,[核心结论]。」
- 过渡:这一发现与[文献]一致 / 与既有工作不同 / 需要指出的是。
- 局限:「本研究存在以下局限:[样本/范围/方法的限制]。」
- 展望:「未来工作可[改进方向]。」
## 过渡词
要点编织时,第二句起按章节轮换使用过渡词,使段落连贯而非罗列:
| 章节 | 过渡词 |
|------|--------|
| 引言 | 然而 / 尽管已有进展 / 值得注意的是 |
| 方法 | 具体而言 / 在此基础上 / 随后 |
| 结果 | 此外 / 进一步地 / 与之相比 |
| 讨论 | 这一发现与…一致 / 与既有工作不同 / 需要指出的是 |
## 扩充
骨架定义在 `draft.py``SECTION_TEMPLATES`,可按目标期刊的写作习惯调整句式与过渡词。

View File

@ -0,0 +1,44 @@
# 要点写法与使用建议
## 怎么写要点
每条要点写成一句话的「发现/事实」,工具会替你加过渡、组织成段。
较好的要点(结果段):
```
对比学习预训练后的模型在三个数据集上准确率均高于基线
实验组与对照组差异显著
小样本设置下提升最为明显
```
不建议的要点(太碎或太长):
```
准确率 # 太碎,不成句
我们做了很多实验然后发现了一些有趣的现象并且…… # 太长,应拆成多条
```
## 推荐流程
1. 把某节的核心发现列成 3-6 条要点。
2. 用 `--section` 起草该节,或用 `--spec` 一次起草多节。
3. 把产出初稿里的 `[...]` 占位逐个填实(数字、引用、对象)。
4. 通读、调整语序与措辞,再交给 polishpro 润色英文(若写英文)。
## 占位说明
凡是工具不该替你决定的内容,都留成占位:
- `[填统计量、p 值、效应量]`:你的真实统计结果
- `[引用]`:对应的参考文献
- `[现有方法的不足]`:你对空白的具体判断
- `[改进方向]`:你设想的未来工作
工具绝不编造这些内容——这是学术诚信的底线,也是它与「直接让 AI 写整段」的关键区别。
## 配合其它技能
- 先用 statgate 跑出统计结果,把统计量填进 draftsmith 结果段的占位。
- 先用 figforge 出图,在结果段引用「图 X」。
- 草稿成形后用 polishpro 检查英文表达。

View File

@ -0,0 +1,189 @@
"""draftsmith论文段落起草器。
对着你的结果要点图表说明甚至中文草稿起草成结构合理的 IMRaD 论文段落
Introduction / Methods / Results / Discussion它不替你编数据而是把你给的
零散要点套进学术写作的句式骨架搭出一段像样的初稿让你在有结果却憋不出
正文时有个起点
三个核心能力
1. 章节句式骨架为引言/方法/结果/讨论四个核心章节各准备一套学术句式模板
研究背景空白本文工作数据方法统计发现对比意义把要点填进去
2. 要点编织把用户提供的 bullet 要点用连接词与句式组织成连贯段落而不是
简单罗列自动补研究表明/结果显示/一致等学术过渡语
3. 占位提醒凡是需要具体数字引用对象的地方留明确占位[填具体数值]
[引用]提醒你补全绝不编造数据或参考文献
纯本地模板引擎不联网不调用 LLM仅用 Python 标准库把要点结构化为段落它给
的是初稿骨架学术判断与事实填充由你完成
用法
python draft.py --section results --points "实验组准确率更高" "差异显著" --output draft.md
python draft.py --spec paper.json --output draft.md
"""
from __future__ import annotations
import argparse
import json
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
SECTIONS = ["introduction", "methods", "results", "discussion"]
SECTION_CN = {
"introduction": "引言", "methods": "方法",
"results": "结果", "discussion": "讨论",
}
# 各章节的句式骨架:用于把要点编织成段落的开头/过渡/收束语
SECTION_TEMPLATES = {
"introduction": {
"opening": "近年来,{topic}受到广泛关注。",
"transitions": ["然而,", "尽管已有进展,", "值得注意的是,"],
"gap": "然而,[现有方法的不足/尚未解决的问题]仍有待研究。",
"contribution": "为此,本文[提出/研究/分析]……,主要贡献包括:",
"hint": "引言应:交代背景重要性 → 指出研究空白 → 引出本文工作与贡献。",
},
"methods": {
"opening": "本研究采用[数据来源/实验设置]开展。",
"transitions": ["具体而言,", "在此基础上,", "随后,"],
"gap": "",
"contribution": "所有统计分析采用[检验方法],显著性水平设为 α = 0.05。",
"hint": "方法应:可复现地交代数据、流程、参数、统计方法;用过去时、被动语态。",
},
"results": {
"opening": "结果显示,[主要发现]。",
"transitions": ["此外,", "进一步地,", "与之相比,"],
"gap": "",
"contribution": "上述结果在[图/表 X]中汇总([填统计量、p 值、效应量])。",
"hint": "结果应:客观陈述发现 + 对应图表 + 统计证据;不解读意义(留给讨论)。",
},
"discussion": {
"opening": "本研究表明,[核心结论]。",
"transitions": ["这一发现与[文献]一致,", "与既有工作不同,", "需要指出的是,"],
"gap": "本研究存在以下局限:[样本/范围/方法的限制]。",
"contribution": "未来工作可[改进方向]。",
"hint": "讨论应:呼应发现的意义 → 与文献对比 → 承认局限 → 展望。",
},
}
def weave_points(section: str, points: list[str]) -> str:
"""把要点编织成段落。"""
tpl = SECTION_TEMPLATES[section]
transitions = tpl["transitions"]
sentences: list[str] = []
for i, pt in enumerate(points):
pt = pt.strip().rstrip("。.")
if not pt:
continue
if i == 0:
sentences.append(f"{pt}")
else:
conn = transitions[(i - 1) % len(transitions)]
sentences.append(f"{conn}{pt}")
return "".join(sentences)
def draft_section(section: str, points: list[str], topic: str = "该问题") -> dict[str, Any]:
"""起草单个章节。"""
if section not in SECTION_TEMPLATES:
raise ValueError(f"未知章节:{section},应为 {SECTIONS}")
tpl = SECTION_TEMPLATES[section]
body = weave_points(section, points) if points else ""
paragraph_parts = []
if section == "introduction":
paragraph_parts.append(tpl["opening"].format(topic=topic))
if body:
paragraph_parts.append(body)
# 章节特定收束
if section == "introduction":
paragraph_parts.append(tpl["gap"])
paragraph_parts.append(tpl["contribution"])
elif section == "methods":
paragraph_parts.append(tpl["contribution"])
elif section == "results":
paragraph_parts.append(tpl["contribution"])
elif section == "discussion":
paragraph_parts.append(tpl["gap"])
paragraph_parts.append(tpl["contribution"])
return {
"section": section,
"section_cn": SECTION_CN[section],
"paragraph": "".join(paragraph_parts),
"hint": tpl["hint"],
}
def draft_paper(spec: dict[str, Any]) -> list[dict[str, Any]]:
"""按 spec 起草多个章节。spec = {topic, sections: {results: [...], ...}}"""
topic = spec.get("topic", "该问题")
out = []
for section in SECTIONS:
pts = spec.get("sections", {}).get(section)
if pts is not None:
out.append(draft_section(section, pts, topic=topic))
return out
def render_markdown(drafts: list[dict[str, Any]], topic: str = "") -> str:
lines = ["# 论文段落初稿"]
if topic:
lines.append(f"\n主题:{topic}")
lines += ["",
"> 由 draftsmith 起草。这是把要点套进学术句式的初稿骨架,"
"`[...]` 处需你填具体数值、引用与对象,工具不编造数据。", ""]
for d in drafts:
lines.append(f"## {d['section_cn']}{d['section']}")
lines.append("")
lines.append(d["paragraph"])
lines.append("")
lines.append(f"> 写作提示:{d['hint']}")
lines.append("")
lines += ["---", "", "由 draftsmith 生成。初稿仅供起步,事实与表达需你核定。"]
return "\n".join(lines)
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(prog="draftsmith", description="论文段落起草器")
p.add_argument("--section", choices=SECTIONS, help="起草单个章节")
p.add_argument("--points", nargs="+", help="要点列表(配合 --section")
p.add_argument("--topic", default="该问题", help="研究主题(用于引言开头)")
p.add_argument("--spec", type=Path, help="多章节 JSON{topic, sections:{results:[...]}}")
p.add_argument("--format", choices=["markdown", "json"], default="markdown")
p.add_argument("--output", type=Path)
args = p.parse_args(argv)
if args.spec and args.spec.exists():
spec = json.loads(args.spec.read_text(encoding="utf-8-sig"))
drafts = draft_paper(spec)
topic = spec.get("topic", "")
elif args.section:
drafts = [draft_section(args.section, args.points or [], topic=args.topic)]
topic = args.topic
else:
print("错误:请用 --section + --points或用 --spec 提供多章节要点。", file=sys.stderr)
return 2
out = (json.dumps(drafts, ensure_ascii=False, indent=2) if args.format == "json"
else render_markdown(drafts, topic))
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,79 @@
"""draftsmith 单元测试。"""
from __future__ import annotations
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
import pytest
from draft import weave_points, draft_section, draft_paper, render_markdown, SECTIONS
class TestWeavePoints:
def test_single_point(self):
s = weave_points("results", ["准确率提升"])
assert s == "准确率提升。"
def test_multi_uses_transitions(self):
s = weave_points("results", ["发现一", "发现二", "发现三"])
# 第二、三句应带过渡词
assert "发现一。" in s
assert any(conn in s for conn in ["此外,", "进一步地,", "与之相比,"])
def test_empty(self):
assert weave_points("results", []) == ""
def test_strips_trailing_period(self):
s = weave_points("methods", ["采用随机森林。"])
assert s == "采用随机森林。"
assert "。。" not in s
class TestDraftSection:
def test_introduction_has_gap_and_contribution(self):
d = draft_section("introduction", ["背景重要"], topic="图像分割")
assert "图像分割" in d["paragraph"]
assert "贡献" in d["paragraph"]
def test_results_has_figure_placeholder(self):
d = draft_section("results", ["准确率更高"])
assert "" in d["paragraph"] or "" in d["paragraph"]
def test_discussion_has_limitation(self):
d = draft_section("discussion", ["结论稳健"])
assert "局限" in d["paragraph"]
def test_unknown_section_raises(self):
with pytest.raises(ValueError):
draft_section("abstract", ["x"])
def test_hint_present(self):
d = draft_section("methods", ["用了 t 检验"])
assert d["hint"]
class TestDraftPaper:
def test_multi_section(self):
spec = {"topic": "对比学习", "sections": {
"results": ["准确率高"], "discussion": ["有价值"]}}
drafts = draft_paper(spec)
assert len(drafts) == 2
sections = [d["section"] for d in drafts]
assert "results" in sections and "discussion" in sections
def test_section_order(self):
spec = {"sections": {"discussion": ["a"], "introduction": ["b"]}}
drafts = draft_paper(spec)
# 应按 SECTIONS 固定顺序introduction 在 discussion 前
assert drafts[0]["section"] == "introduction"
class TestRender:
def test_render(self):
drafts = draft_paper({"topic": "X", "sections": {"results": ["准确率高"]}})
md = render_markdown(drafts, topic="X")
assert "论文段落初稿" in md
assert "结果" in md
assert "写作提示" in md
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-v"]))