Compare commits
1 Commits
master
...
skill/draf
| Author | SHA1 | Date |
|---|---|---|
|
|
50f1e92d0b |
|
|
@ -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) — 要点写法与使用建议
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# IMRaD 章节句式骨架
|
||||
|
||||
draftsmith 为四个核心章节各准备一套句式骨架,把要点填进去组织成段落。
|
||||
|
||||
## 引言 Introduction
|
||||
|
||||
结构:背景重要性 → 研究空白 → 本文工作与贡献。
|
||||
|
||||
- 开头:「近年来,{主题}受到广泛关注。」
|
||||
- 要点编织:陈述已有进展。
|
||||
- 空白:「然而,[现有方法的不足/尚未解决的问题]仍有待研究。」
|
||||
- 贡献:「为此,本文[提出/研究/分析]……,主要贡献包括:」
|
||||
|
||||
## 方法 Methods
|
||||
|
||||
结构:可复现地交代数据、流程、参数、统计方法。建议用过去时、被动语态。
|
||||
|
||||
- 开头:「本研究采用[数据来源/实验设置]开展。」
|
||||
- 过渡:具体而言 / 在此基础上 / 随后。
|
||||
- 收束:「所有统计分析采用[检验方法],显著性水平设为 α = 0.05。」
|
||||
|
||||
## 结果 Results
|
||||
|
||||
结构:客观陈述发现 + 对应图表 + 统计证据。**不解读意义**(留给讨论)。
|
||||
|
||||
- 开头:「结果显示,[主要发现]。」
|
||||
- 过渡:此外 / 进一步地 / 与之相比。
|
||||
- 收束:「上述结果在[图/表 X]中汇总([填统计量、p 值、效应量])。」
|
||||
|
||||
## 讨论 Discussion
|
||||
|
||||
结构:呼应发现意义 → 与文献对比 → 承认局限 → 展望。
|
||||
|
||||
- 开头:「本研究表明,[核心结论]。」
|
||||
- 过渡:这一发现与[文献]一致 / 与既有工作不同 / 需要指出的是。
|
||||
- 局限:「本研究存在以下局限:[样本/范围/方法的限制]。」
|
||||
- 展望:「未来工作可[改进方向]。」
|
||||
|
||||
## 过渡词
|
||||
|
||||
要点编织时,第二句起按章节轮换使用过渡词,使段落连贯而非罗列:
|
||||
|
||||
| 章节 | 过渡词 |
|
||||
|------|--------|
|
||||
| 引言 | 然而 / 尽管已有进展 / 值得注意的是 |
|
||||
| 方法 | 具体而言 / 在此基础上 / 随后 |
|
||||
| 结果 | 此外 / 进一步地 / 与之相比 |
|
||||
| 讨论 | 这一发现与…一致 / 与既有工作不同 / 需要指出的是 |
|
||||
|
||||
## 扩充
|
||||
|
||||
骨架定义在 `draft.py` 的 `SECTION_TEMPLATES`,可按目标期刊的写作习惯调整句式与过渡词。
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
# 要点写法与使用建议
|
||||
|
||||
## 怎么写要点
|
||||
|
||||
每条要点写成一句话的「发现/事实」,工具会替你加过渡、组织成段。
|
||||
|
||||
较好的要点(结果段):
|
||||
|
||||
```
|
||||
对比学习预训练后的模型在三个数据集上准确率均高于基线
|
||||
实验组与对照组差异显著
|
||||
小样本设置下提升最为明显
|
||||
```
|
||||
|
||||
不建议的要点(太碎或太长):
|
||||
|
||||
```
|
||||
准确率 # 太碎,不成句
|
||||
我们做了很多实验然后发现了一些有趣的现象并且…… # 太长,应拆成多条
|
||||
```
|
||||
|
||||
## 推荐流程
|
||||
|
||||
1. 把某节的核心发现列成 3-6 条要点。
|
||||
2. 用 `--section` 起草该节,或用 `--spec` 一次起草多节。
|
||||
3. 把产出初稿里的 `[...]` 占位逐个填实(数字、引用、对象)。
|
||||
4. 通读、调整语序与措辞,再交给 polishpro 润色英文(若写英文)。
|
||||
|
||||
## 占位说明
|
||||
|
||||
凡是工具不该替你决定的内容,都留成占位:
|
||||
|
||||
- `[填统计量、p 值、效应量]`:你的真实统计结果
|
||||
- `[引用]`:对应的参考文献
|
||||
- `[现有方法的不足]`:你对空白的具体判断
|
||||
- `[改进方向]`:你设想的未来工作
|
||||
|
||||
工具绝不编造这些内容——这是学术诚信的底线,也是它与「直接让 AI 写整段」的关键区别。
|
||||
|
||||
## 配合其它技能
|
||||
|
||||
- 先用 statgate 跑出统计结果,把统计量填进 draftsmith 结果段的占位。
|
||||
- 先用 figforge 出图,在结果段引用「图 X」。
|
||||
- 草稿成形后用 polishpro 检查英文表达。
|
||||
|
|
@ -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())
|
||||
|
|
@ -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"]))
|
||||
Loading…
Reference in New Issue