Repository navigation
Releases: cabbage2000-lab/paper-tutor-skills
Release list
v0.1.8
本版让检索结果第一眼就看得出哪篇能合法拿到全文。 每条文献多一列「全文获取」——开放
获取(OA)状态 + 版本 + 直达链接(一条真实医学检索的 30 条结果里,15 条拿到了合法开放
版本),此前每条只有一个 DOI 链接、点进去一半是付费墙,用户得逐篇自己试。同时撤稿检测
接入 PubMed 这个第三源:医学是撤稿重灾区,而此前两源一个只给布尔标记、一个不给声明刊期,
现在能拿到撤稿声明的期刊卷期页、DOI 与 PMID;「期刊已出具关注声明(Expression of
Concern)」这个中间态也第一次陈列得出来——且不会被误升成「已撤稿」。
新增(Added)
-
检索结果现在看得出哪篇能合法拿到全文——
/paper-search的笔记表多一列「全文获取」,
按状态 · 版本 · 链接陈列开放获取(OA)可得性。此前每条只有一个 DOI 链接,点进去
一半是付费墙,用户得逐篇自己试。三个源的 OA 字段全部来自已经拿回来、但此前被丢弃的字段,零额外 HTTP 请求:
OpenAlex 的open_access+best_oa_location、PubMed 的 PMC 全文(articleids里的
pmc)、arXiv 的 PDF 链接(Atom 里 arXiv 自己给的<link title="pdf">)。实测一条
医学检索(30 条结果)里 15 条拿到了合法开放版本直链。四个实测得来的设计点,改的时候别推翻:
- 链接必须两级降级
pdf_url → landing_page_url。实测pdf_url经常是 null 而
landing_page_url有值(10.1056/NEJMoa2034577即是),只取 pdf_url 会让大量真有
开放版本的文献显示成「无链接」。 - 版本必须与状态一起陈列。同一条实测的开放版是
submittedVersion(投稿版,
不是最终发表版)——只报「这篇是 OA」会让用户拿投稿版当发表版引,页码与结论表述
都可能不同。所以有version非 publishedVersion 的一律标「可能非最终版」。 - 源没给 OA 字段 → 「未知(各源未给出)」,绝不写成
closed。「没查到开放版本」
与「确认没有开放版本」是两件事,混同会让用户放弃一篇本来能从别处拿到的文献
(同cited_by_count不补 0 的既有立场)。实测 Crossref 压根不给 OA 字段、OpenAlex
会明确回closed,两者在产物里必须长得不一样。 - 跨源合并按信息完整度、不按源权威序(
search.py·_merge_oa):有链接 > 无链接,
publishedVersion> accepted > submitted,平局才用源权威序兜底。理由是 OA 的价值在
「能不能点开、点开是不是最终版」,与元数据权威性无关——而权威序最高的 Crossref 恰恰
不给这个字段,按权威序取整列会消失(同author_details取「认得出的人最多」的源)。
OA 是陈列列,不是排序键、也不是阅读优先级——与被引数同性质(红线 1):可得性与
该不该读是两件事,产物里不会出现「这篇容易拿到,建议先读」。 - 链接必须两级降级
-
撤稿检测接入第三源 PubMed(惠及
/paper-verify与/paper-search两处)。医学是撤稿
重灾区,而此前只有 Crossref(Retraction Watch)与 OpenAlex(布尔is_retracted)两源。
PubMed 的信息量更大:给出撤稿声明所在的期刊、卷期页、DOI 与 PMID,用户可以直接点开
那份声明。两路都是零额外请求——pubtype快路径在 esummary 响应里,CommentsCorrections
详情搭摘要那趟 efetch 的车。实测样本 PMID42371203(真撤稿文献)两路均命中。两处方向与归属的坑,测试已守住:
- 只认
RetractionIn(本文被撤稿),不认RetractionOf(本文就是那份撤稿声明)。
读反则撤稿态永不触发,而撤稿声明本身进检索结果时会被误标成「已撤稿」——与
Crossref 的update-to/updated-by是同一个坑。 - 关注声明(Expression of Concern)不进
retraction、不新增第七态。它是撤稿的
中间态:期刊对该文存疑、尚未定论。而SourceHit.retraction一旦非空,
paper-verify就判 RETRACTED——把「存疑」说成「已撤稿」是替期刊下结论。故它落在
metadata.expression_of_concern单独陈列,六态判定一字未改。
- 只认
变更(Changed)
-
边界拒绝清单新增 A 类条目
no-paywall-circumvention(不提供绕过付费墙的获取路径),
A 类由 8 条增至 9 条,README 不做表与 PRD 边界清单同步。这条边界先于上面的链接功能
合入:功能一上线,第一个用户请求大概率就是「能不能加个 Sci-Hub 链接」,而原有 8 条
没有一条覆盖付费墙。立场与既有的「不程序化抓取知网」同构——不把站点 ToS 与账号风险
默认转嫁给每个安装者。出口指引是机构图书馆入口 / 馆际互借(ILL)/ 向通讯作者索取 /
找预印本版本。/paper-search相应多两行越界转化(Sci-Hub 类请求、「帮我把这 50 篇下
下来」),本批只增强检索、不做任何下载与代抓。 -
PubMed 元数据留下完整的
pubtype数组(新增metadata.pubtypes)。此前只取首项当
type,实测10.1056/NEJMoa2034577的首项是 "Clinical Trial, Phase II",把
"Randomized Controlled Trial" 这个最有价值的证据类型标签丢了。type的既有行为不变。
v0.1.7
**本版让中文文献这条路真的走得通了。**带中文 DOI 的文献现在能自动取到完整题录(标题 / 作者 /
刊名 / 卷期页 / 摘要)——此前这类文献一律落「待人工核对」,依据是一句「中文注册机构没有免费
元数据 API」的错误断言;知网 / 万方的官方导出引文能一键解析入表,20 条文献不必再逐条手抄;
检索不到时给出的也不再是一句话术,而是可照着做的操作卡。同时 /paper-search 补上了滚雪球
补召回(关键词检索最容易漏的正是经典文献)、按作者检索与 ORCID / 机构陈列(同名作者的甄别
交给客观标识)、被引数与摘要采集、以及回填题录与源元数据的交叉核验。
新增(Added)
-
中文 DOI 的题录现在能自动取到了——新增
doi_meta源(DOI 内容协商)。此前
routing.py断言「ISTIC(中文 DOI)注册机构
未提供免费元数据 API」,把中文 DOI 整源跳过(RA_ROUTES["ISTIC"] = [])、径直落待人工核对。
这个断言经实测为假:DOI 全球解析系统本身提供内容协商——向https://doi.org/{doi}带
Accept: application/vnd.citationstyles.csl+json请求,注册机构支持的话直接回 CSL-JSON。实测
10.11821/dlxb202001001(《地理学报》2020 年第 1 期,宋长青)拿到中文标题、作者、刊名
《地理学报》、卷 75 期 1 页 3,以及 381 字的完整中文摘要——字段齐到足以完成核验与笔记表填充。于是带 ISTIC DOI 的中文文献从「待人工核对」变成可 VERIFIED,撤稿检查与逐字段比对也一并
适用。走的是 DOI 基础设施而不是站点接口,因此不碰知网 / 万方的 robots.txt 与账号风险,与
「不把站点 ToS 转嫁给每个安装者」那条既有取舍不冲突。CNKI补进注册机构路由表:知网自己就是一家 DOI 注册机构(实测
10.16511/j.cnki.qhdxxb.2020.22.001的 agency 为CNKI)。此前它落unknown走保守路由,去查
crossref / openalex——它们按定义不收录别家注册的 DOI,两次必然 miss,还把「英文库没有」混进
中文条目的证据链里,让报告读起来像查无此文。它的内容协商实测回多重解析 HTML 选择页、不回
CSL-JSON,故doi_meta对它多为 miss;非 JSON 响应一律按 miss(该注册机构不支持内容协商)
处理而不按 error——两者对判定的含义差着「查了没有」与「查不成」,混同会让知网条目在报告里
显示成网络故障。措辞精度上有一处刻意的克制:两条
route_note与判定摘要都只说「DOI 前缀已注册」,
不说「这条 DOI 合法存在」。RA 判别是前缀级的,实测编造后缀的10.11821/dlxb209999999
前缀照样报 ISTIC——说成「合法存在」等于替一个可能编造的引用作存在性担保,这比不说更糟。
完整 DOI 级的存在性判定要另走 handle API(hdl.handle.net/api/handles/{doi},rc=1 存在 /
rc=100 不存在),本次不引入。守卫见
test_routing.py与test_judge.py
里的措辞断言(防漂回去)。顺带补上 CSL-JSON 的类型方言:
article-journal与 Crossref 的journal-article词序恰好相反,
此前不在归一表里,中文 DOI 条目的 type 会归一成None,于是--type journal-article筛选时
它成了唯一不受类型约束的一档——命中集与声明的筛选条件不符。 -
/paper-search的中文轨从「逐条手抄」升级为「官方导出 + 自动解析」——新增
parse_export.py:用户在知网 / 万方站内检索后,用站点自己的
「导出引文」功能导出 BibTex / EndNote(RIS) 文件,交脚本解析成与search.py同形的results,直接并入
同一张文献笔记表。20 条文献从「逐条抄标题/作者/年份/刊名」变成一次导出,且题录比手抄更全(带卷期页码、
部分带 DOI,可续跑--lookup-doi补全)。注册表相应给cnki/wanfang两个 guided 源加上
import_export能力。覆盖方式如实分档为「用户回填(官方导出)」与「用户回填(手工)」,都不是「自动检索」——中文库的检索
仍由用户在站内执行,说成自动检索会违反覆盖声明的如实要求。同一篇同时来自英文 API 与中文库时,sources
累积并列(中英双库均收录是有价值的事实,不丢任一侧)。为什么是解析导出、而不是程序化检索知网:实测知网海外版
robots.txt为User-agent: * / Disallow: /,明示
禁止自动化访问(国内主站对非浏览器 UA 返回 HTTP 418,robots 内容读不到);而本产品分发给他人使用,不把站点
ToS 与账号风险默认转嫁给每个不知情的安装者。这是合规与分发层面的取舍,不是技术做不到——浏览器 MCP 驱动
站内官方导出 API 的路径已评估、题录质量与手动导出相同,未采用的原因就是上面这两条,留档见
检索方案模板。站内题名确实注入了防爬水印(单条标题散布 22-27 个不可见字符,U+200B-200F / U+2060-206F / U+FEFF,外加
<span class="hrc">包裹的「知网」「版权」字样),但那是解析器要处理的事实、不是拒绝抓取的理由:
不可见字符按 UnicodeCf/Cc类无损清除(且必须先删再折叠空白——反过来会把 U+FEFF 变成真空格,在词内
留下content→c ontent这种不可逆的假词边界);可见水印词只告警、不删(「版权」可能是真实题名的一
部分,如《数字出版版权保护研究》,判断归用户)。跳过的条目、疑似水印题名、无 DOI 条目数三类一律进
warnings,宿主须原文呈现、不许吞掉。 -
/paper-search新增滚雪球补召回(--snowball)——关键词检索的召回有天花板,经典文献尤其漏:它们
年份早,在默认的year_desc下排在最末,paper-anchor早就记录过这个病(拿SCL-90 symptom checklist validation检索,1970 年代的量表原始文献与 1989 年的 TAM 都被排到末尾,配--limit 10一条都看不到)。
现在可以从一篇公认的核心文献出发往两边捞:--direction backward取它的参考文献(补经典),forward取
引用它的文献(补最新跟进),both(默认)两向都取。数据源层相应新增
references/cited_by两项能力与门面入口related():Semantic Scholar 走
/paper/{id}/references|citations一次调用直接回题录;OpenAlex 是两跳(referenced_works只给 ID 列表,
题录要再按filter=openalex_id:批量取,每批 ≤50)。Crossref 有意不给这两项能力——它的reference
数组投递率不稳定且没有前向。输出与--query完全同形,所以滚雪球结果能直接并进同一张笔记表;
coverage里逐源逐向各占一行,后向失败不影响前向(合成一行会让「前向查到了、后向没查到」看着像整源
故障)。中文库不支持(引文图谱里没有它们),如实占位「未覆盖(无 API,滚雪球不适用)」。 -
文献笔记表新增被引数列与摘要采集——各源的被引数(OpenAlex
cited_by_count/ Crossref
is-referenced-by-count/ S2citationCount)跨源取最大值并括注来源库:各库口径本就不同(Crossref 只数
注册了 DOI 的引用、S2 收更多灰色文献),取最大是「至少有这么多」的下界陈述,比静默取主源值诚实——静默取值
会让同一篇在换个主源后数字突变、且看不出为什么。缺失严格区别于 0:源不给这个数就是空白,绝不落成
「零被引」。被引数只是陈列列,不是排序键——脚本有意不提供
--sort cited_by。被引数虽是客观计数,实际是质量代理,
按它排序就是替用户回答「哪篇更重要」,正是红线 1 要挡的;高引常常只意味着发表得早。用户自己看着这一列排
优先级是他的判断,做成默认顺序就成了 AI 的判断。摘要此前完全没采集,这是「三格起草档」的硬前置:S2 与 arXiv 直接给纯文本(arXiv 的 Atom
summary带
硬换行,折成单行再存,否则进 markdown 表格会把表打散);OpenAlex 只给abstract_inverted_index,新增倒排
还原(位置有洞时跳过、不填占位符——宁可句子短一截,不编内容);PubMed 的esummary不返回摘要,
必须另走一次efetchXML,失败则吞掉返回空(题录此时已拿到,为摘要把整源判成「未覆盖」是把次要字段的失败
升级成主能力的失败);Crossref 的abstract是 JATS XML 片段,清洗见下一条。给不出摘要的条目仍由 dedup
从其他源补(摘要按可得性取第一个非空值,不像题录那样按权威序——摘要不存在「谁的更权威」)。 -
Crossref 的 JATS 摘要清洗——补上摘要覆盖的最后一块:同一查询的摘要覆盖率由 6/20 升到 13/20(新增的 7 条
全部来自 Crossref)。与 S2 不同不必动缓存键:Crossref 的 URL 没有select参数、返回的是全字段,
abstract本就躺在旧缓存的响应里——实测cache_hit_rate: 1.0(全走旧缓存)时照样拿到摘要。240 条真实响应的清洗结果:239 条干净、0 条残渣、1 条如实弃用。四个真实数据缺陷是照着样本一个个修出来的,
不是照标准猜的:- 前缀不止
jats:。同一字段实测出现过jats:p、无前缀的p、以及ns3:p三种写法,且没有一处
声明 xmlns——直接喂ET.fromstring必报 unbound prefix。所以统一剥前缀,而不是去声明某个特定命名
空间(前缀名是出版商任意取的)。 - 出版商普遍双重转义,三种形态都实测存在:
&amp;经 XML 解析只解到&,用户会在笔记表里
看到字面&;&lt;.001解到<,本该显示成P < .001;<p>反转义后变成字面
<p>,于是又要再剥一遍标签。因此实体反转义与标签剥离必须交替做到稳定,只做一遍或只做其中一件,
这三条都会带着可见的转义残渣进笔记表(字面标签更会在 HTML 视图里真的插进 DOM)。 - 降级路径不许静默吞正文。XML 解析失败(数据本身不良构)时改用正则,但不能用贪婪的
<[^>]*>:
它会从摘要里合法的数学比较符<一路吃到下一个>,把中间的正文整段吞掉(实测
p < 0.05 with unclosed <jats:bold>会丢掉 "0.05 with unclosed")。只剥形态完整的标签(字母开头 +
闭合>),剥不掉的孤立<保留——三种坏结果里,静默残缺最坏,整条丢弃次之,留个孤立尖括号最轻。
同理,早先「含任何<就判脏」的守卫会把带比较符的摘要整条丢掉,已改为只认标签形态。 - 有出版商投递
<jats:p>.</jats:p>(一个句点,10.18502/kss.v3i6.2443)。把它当摘要交给三格起草档,
等于请 AI 对着一个句点编方法学,所以加一道「有没有词」的守卫——这是客观事实判据,不是主观长度阈值,
对汉字同样成立。
结构化摘要(
<sec><title>Methods</title>…)拼成Methods: …,与 PubMed efetch 同一口径,两边产出形状
一致;顶层的<title>Abstract</title>丢掉(那是标签不是内容,实测 240 条里 61 条带它)。 - 前缀不止
-
五字段新增「三格起草档」——条目一多,五字段全人工填就是 50 条 × 5 格的负荷,多数人填几条就放弃了。现在
「研究问题 / 方法 / 数据」三格可以按真实摘要整批起草,让用户从「填空」变成「核对」。四条约束一条不松:
只据真实摘要转述(无摘要就写「无摘要,未起草」,绝不凭标题猜方法学——猜出来的比空白有害得多);每格
必带「(AI 摘要转述稿,待你核对)」;「结论 / 不足」永不起草(结论边界与批判性判断是用户在练的能力,
代填等于把训练脚手架拆了);先问再做。 -
检索结果新增分布 advisory——
advisories字段与warnings并列而非合并:warnings 说「这次检索有
问题」,advisories 说「这批结果长这样」,混在一起宿主会把分布特征当异常念给用户听。四个维度(年份 5 年一档 /
期刊来源 / 文献类型 / 主命中源)中任一维度的单一取值占到 70% 就出一条,措辞固定带「这是分布信号,不是缺陷」。三处刻意的设计:分母是该维度有值的条目数,不是总条数(venue 缺一半时拿总数当分母,真实的集中会被稀释到
阈值以下,信号就没了);算在截断前的全量上,不是results(算在截断后的子集上,--limit 10配
year_desc会必然报出「年份高度集中」,那是 limit 造出来的假信号);方法与研究场景两个维度不算——题录
元数据里推不出来,给出「方法类型:问卷 = 70%」是把不确定的归类包装成确定的统计,比不给更糟,那两项只按摘要
定性描述。年份档 advisory 触发时(七成挤在同一 5 年档)正是缺经典的信号,此时主动提议滚后向。clean-room 声明:借鉴了 academic-research-skills
(CC BY-NC 4.0)DISTRIBUTIONAL_SKEW_ADVISORY的公开设计理念——阈值化的覆盖分布信号、以「该维度有值的条目数」
为分母、措辞上把它定性为信号而非缺陷。本项目独立实现为:只在题录元数据可算的四个维度上统计(年份档 /
期刊来源 / 文献类型 / 主命中源),明确拒绝对研究方法与地理场景给百分比(该来源对这两个维度给比例,本项目
判定元数据推不出来、给了就是编),阈值与分母的取值依据本项目自身的 Research Gap 五类与真实检索实测确定,
算在截断前的ranked集合上。参数推导过程留档见本地docs/specs/(内部文档)。 -
回填补全新增题录交叉核验(
--lookup-doi --title)——此前--lookup-doi只回答「这个 DOI 查不查得到」,
查得到就把源元数据填进笔记表。但「DOI 解析得开」不等于「就是你手里这一篇」:DOI 抄错一位、或题录与 DOI
张冠李戴,都会得到一个found=true却指向另一篇论文的结果,光看这个字段发现不了。现在把手里那条题录的标题
(与可选的--ref-year)一并交上来,就会与源元数据做一次交叉核验。输出的
metadata_consistent是三态:true= 比对一致,可入表;false= 对不上,连同field_notes
(哪个字段、差多少)原文转述请用户核对 DOI 来源,先别入表;null= 未比对(没传标题,或压根没查到)。
null 与 false 严格区分——把「没核对过」说成「核对一致」是这次要堵的另一个洞,所以没传标题时绝不拿源自己的
标题去自比出一个 True。仍然不判态:本命令只陈列比对结果并指路
/paper-verify,不判 NOT_FOUND、不判「这是编造的」、不新增任何
态码——存在性判定归 verify,加了比对也不归。反过来也不许替用户改题录去迁就源元数据。比对内核(标题重叠系数 0.8 阈值、年份差 2、第一作者姓候选集合)没有新写一套,而是把
paper-verify里
已经跑了三个版本的那套提到_shared/paper_shared/matching.py
两边共用。同一条题录在两个命令下必须得到同一个比对结论,各存一份必然漂移。取重叠系数而非编辑距离是有意的:
引用标题常是源标题的子集(省略副标题),重叠系数对这种合法省略更宽容,误报更低。有意不提供--authors——
中文题录的作者是汉字、英文源多给拼音,姓氏候选集合必然无交集 → 系统性误报;DOI + 标题 + 年份三项已足够判
「是不是同一篇」。 -
/paper-search支持按作者检索——--find-author "姓名"列同名候选(ORCID / 历年机构 / 研究领域 /
论文数),用户选定后--author-works "orcid:0000-…"取该作者的论文,输出与--query同形、可并进同一张
笔记表。数据源层新增search_author能力位与find_authors()/works_by_author()两个门面入口。必须两步、中间停下让用户选,这是本功能的核心约束而非交互偏好:同名作者是普遍现象,实测
"Shenghua Zhou" 在 OpenAlex 有 33 个作者实体、"Wei Wang" 有 9757 个。跳过选择直接取第一个人的论文,
会把别人的成果当成他的呈给用户。哪怕只返回一个候选也要确认——源没收录别的同名者不等于这位就是要找的人。归并只认 ORCID:源自己会把同一个人拆成多个实体(实测
0000-0003-3871-9099→ 99 篇 + 6 篇),按
ORCID 合并是客观事实的还原;而同名 + 同机构 + 同领域一律不合(实测 4 个不同 ORCID 的 "Shenghua Zhou"
同在中南大学)。取论文时 ORCID 键优先——它在 works 层过滤,能穿透实体拆分拿到完整列表(实测 105 = 99 + 6);
只能按实体 ID 查时,脚本自报「可能不是该作者的全部论文」。三处如实声明堵的都是真实误判:候选截断(同名上千时想找的人可能不在这批里)、模糊匹配(实测搜「周生华」
会返回「周华生」,标出来但不替用户滤掉——英文库里中文名的姓序本就混乱)、以及本轨仅 OpenAlex 单源、
完全不覆盖知网 / 万方——中文发表为主的学者在这里天然稀疏,必须主动说明这是覆盖问题、不是学术产出问题。辨认线索用历年 affiliations(带年份)而非
last_known_institutions:后者实测常年失准(同一位作者的
last_known 是 Jiangsu University of Technology,历年记录却是 Shanghai / Central South)。 -
/paper-search的检索结果带上作者的 ORCID 与机构——文献笔记表的作者列现在把带 ORCID 的作者写成
姓名 [0000-0002-9322-3515],用来解决中文姓名重名(同一批结果里三个 "Wei Wang" 到底是不是同一个人)。
数据从 OpenAlex 的authorships与 Crossref 的author里提取,零额外请求——两个源的响应本来就带着
这些字段,此前只取了 display_name。既有的authors(字符串列表)形状不变,标识走并行的新字段
author_details(每条自带name/orcid/affiliations/orcid_verified)。只陈列、不做同名归并:ORCID 是作者本人注册的唯一键,属客观事实可以照搬;而「无 ORCID 时这两个同名
作者是不是同一个人」是概率推断,判错会把别人的成果算到某人头上,因此归用户判断(红线 1 / 红线 3)。
这不是保守——实测 "Shenghua Zhou" 在 OpenAlex 有 33 个作者实体、其中 4 个同在中南大学,而同一个 ORCID
0000-0003-3871-9099被 OpenAlex 自己拆成了两个实体(99 篇 + 6 篇)。连专业消歧系统对中文名都是碎的。同理,机构字段只作线索、不可当权威:实测
10.1038/nature14539,OpenAlex 给 Yann LeCun 的机构是
"Sanford Broadway Medical Center"、给 Geoffrey Hinton 的是 "U...
v0.1.6
本版让 /paper-search 的检索结果不再静默缺斤少两:结果被 --limit 截断时脚本会自报截掉了
几条、该调到多少(此前只能靠自己比对 stats.after_dedup 与 shown,照抄示例参数做综述检索会
整年整年地漏文献),--limit 0 可一次取回全部去重结果;DOI 回填补全也不再把前缀根本没注册的
(疑似编造的)DOI 和真实中文文献说成同一件事。
修复(Fixed)
-
/paper-search的示例参数会让综述检索静默漏掉整整几年的文献——SKILL.md 第 2 步
「英文 / 自动轨」的命令行示例写着--per-source 20 --limit 30,宿主照抄就跑。实测一条
真实 RQ 下:74 条去重结果只呈现 30 条,2024 与 2023 两整年、36 篇一条未进。根因是
--limit是截断不是分页(build_payload()里ranked[:limit],没有 offset),默认 30
配默认排序year_desc恰好等于「只给最新的 30 条」——年份越早越容易被整年切掉。判据其实
一直在输出里(stats.after_dedup=74对shown=30),只是没有一处告诉宿主要看这两个数。
换--sort source_count不解决(实测两种排序前 30 条只差 3 条)。现在 SKILL.md 补一条与
「不加日级时间窗」并列的子项:先跑一次看stats.after_dedup是多少、按那个数调--limit,
shown明显小于after_dedup就必须调大重跑或向用户说明。提示层只能管到「宿主读到了就会调参」,所以代码层同时补两处(都是纯增量,默认 30 不动
——/paper-daily的新发轨共用这个脚本,改默认值会波及按时间监测):- 截断时脚本自报——
shown < after_dedup时往warnings数组塞一条,说清截掉了几条、
按当前排序被截的是哪一头、该把--limit调到多少。搭的是warnings已有的现成契约
(SKILL.md 早写着「非空时原文呈现给用户,不要吞掉」),宿主漏读文档也会在输出里撞见。 --limit 0= 不截断,全量呈现去重结果,省掉「先跑一次看after_dedup再重跑」这轮
往返。负数按手滑挡掉(退出码 2),同--days的既有口径。
实测复现与验证(2026-07-29,crossref + openalex,
flipped classroom nursing education
限 2020-2024):--limit 5时去重 35 条只呈现 5 条、全是 2024 年,2020-2023 整四年
一条未进,warning 如实报出「去重后 35 条、本次只展示 5 条……请用--limit 35或
--limit 0重跑」;换--limit 0后 35 条全出,年份分布 2024:6 / 2023:5 / 2022:6 /
2021:12 / 2020:6,warnings空。 - 截断时脚本自报——
-
回填补全把编造 DOI 与真实中文文献说成同一件事——
/paper-search第 4 步的
scripts/search.py --lookup-doi对着一个前缀根本没注册的 DOI(10.9999/fake这类编造
强信号),给出的note与真实中文文献未被收录时一字不差:「各开放源未命中:可能是
中文库文献或元数据未收录,请人工核对,勿据此判定编造」——方向正好说反了。根因是
build_lookup_payload()只分了 ISTIC 与「其余查不到」两档,而路由给not_registered的
sources是空列表(前缀未注册就不浪费重试预算,见_shared/paper_shared/datasources/routing.py),
于是hits恒空、径直落进兜底档。区分信息只剩route_note字段,而note才是给宿主读的
断言句。更要紧的是同一个not_registered在/paper-verify的judge.py里正是判
NOT_FOUND / fabricated 的最强信号——同一个信号在两个 skill 里被读成相反的意思。现在
not_registered单列一档:如实说「DOI 前缀未在任一注册机构注册(因此未查任何元数据源):
这是 DOI 不存在的强信号,请人工核对来源;存在性判定请走/paper-verify」。两处措辞是有意的
——不写「元数据未命中」(一个源都没查过,说未命中不如实),仍保留「人工核对」且found
照旧为false,存在性判定不在 paper-search 做这条红线不动。SKILL.md 同步补两处行为
指令:第 4 步回填闭环明写route_note与note两个字段都要读、都要原文转述,三档
证据强度不许混为一谈,并加「DOI 照记」的唯一例外(not_registered一档先别照记,回读
两个字段请用户确认 DOI 来源——「不判 NOT_FOUND」不等于把疑似编造的 DOI 静默收进笔记表),
边界与异常对照表在 ISTIC 那行下新增对应一行。route_note这处此前只有/paper-verify在
展示(report.py/report_html.py),paper-search 的指引漏了它,分档信息等于白传。单测
钉住两侧:既不许退回 NOT_FOUND,也不许与「各源未命中」共用同一句。 -
开发骨架能一路留到发版、把占位符发进 GitHub Release——0.1.5 发版时实况:CHANGELOG
的 0.1.5 段落到打 tag 前仍顶着「⚠️ 开发中骨架,发版前必须改写」的警告块、摘要句是
「(待填:…)」,而extract_changelog_notes.py当时只拦「段落找不到」与「段落为空」
两种情形,占位符两道都过;759 个测试也全绿,因为没有一条盯这件事。差一步就把骨架
发到 Release 页面上,靠人肉复核才拦住。脚本的 docstring 其实早写了「宁可不发,也不发
空白或残缺的」——只是「残缺」那一半从未实现。现在补第三道检查
find_blockers():段落里留着RELEASE-BLOCKER哨兵、或残留任何
HTML 注释,一律以非 0 退出、发布流程直接失败,并把该改什么一次说清(免得删了哨兵
重打 tag 又栽在注释上)。两处设计取舍:- 检查放在脚本里,不做成 pytest。 骨架在开发期是合法状态——开完骨架要一路
累加条目,中间每次 push 都跑tests.yml。做成断言会让整个开发周期 CI 长红,而
长红会让人对红灯脱敏,比没有守卫更危险。这个脚本只被release.yml调用,正好卡在
骨架「从合法变成错误」的那一刻。 - 用哨兵,不用字面量黑名单。「待填」「开发中骨架」这类词表一改措辞就静默失效,
等于又造一份会漂移的真相(硬规则 1 反对的正是这个)。哨兵是机器可读契约,骨架
提示语随便改;删掉哨兵是一个有意识的「我确认这段收口了」的动作。HTML 注释那条
是兜底——万一开骨架时忘了写哨兵,脚手架注释还在就仍拦得住。
- 检查放在脚本里,不做成 pytest。 骨架在开发期是合法状态——开完骨架要一路
-
extract_changelog_notes.py此前零单测——发版链路上唯一的关键脚本,tests/下四个
测试文件一个都不碰它。新增tests/test_release_notes.py:
切段边界(不吃进上一版内容、段末分隔线不进正文、末版取到文件尾)+ 两条阻塞判据 +
「换一套骨架措辞后哨兵仍生效」。另有一条与release.yml互补的事后守卫——已发布的
历史段落必须全部干净(release.yml 事前拦,这条在下个版本开发时立刻红,若某次真把
骨架发出去了会指名道姓)。有意不断言最新段落可发布,理由同上第 1 条。 -
哨兵检查会把「讲哨兵机制的段落」误判成开发骨架——本版发版当场踩到:上面那条新增的
哨兵检查是整段搜字面量,而本段落正文正在讲这套机制、如实写出了哨兵名,于是 0.1.6
被自己的检查拦住、发布流程直接失败。改 CHANGELOG 措辞躲开不算修——CLAUDE.md 的发版
流程和后续每次讨论这个机制都会写出这个名字,躲一次下次照样栽。现在按哨兵的本来语义
收窄:只在 HTML 注释块内才算哨兵(骨架的哨兵本来就写在注释里),未闭合的注释按吃到
段末处理、骨架写坏了也不漏拦。代价是哨兵检查不再是独立防线(注释内的哨兵必然也被
「残留 HTML 注释」那条兜住),它退化为更精准的诊断——报「这仍是开发骨架、该删哨兵块」
比报「有脚手架注释」更能让人一眼知道该改什么。单测钉两头:正文提哨兵名不拦、注释里的
哨兵(含未闭合)仍拦。
变更(Changed)
- CLAUDE.md 的发版流程补「收口」这一步——这是上述漏洞的另一半根因:原流程第 1 步写
「在 CHANGELOG 写## [x.y.z] — 日期段落」,读起来像发版时才写;而实际做法是开骨架时
写框架、一路累加、发版时才需要收口,「收口」这个动作在文档里根本不存在。现在拆成
「开版本骨架」与「发版」两段,前者明确骨架必须含RELEASE-BLOCKER哨兵,后者第 1 步
列全收口的四件事及其失败后果。守卫测试表由四个更新为五个。
v0.1.5
/paper-daily 的「今日 / 最近 N 天」从写在文档里变成真能执行。 一个叫「每日学术雷达」
的命令,此前实际时间粒度是年:日级日期在归一化那步被丢掉,SKILL.md 里写的「按天筛选」
宿主无从下手。这次四层一起补齐(arXiv 客户端带出 date、检索脚本输出契约加 date、
后置过滤支持日期闭区间、CLI 新增 --days N 与 --date-from / --date-to),并把 arXiv 的
原生日期区间与按提交时间倒序下推到 API——少了后者,「拉回前 N 篇」按的是相关度而不是最新,
--per-source 调多大都换不来时效性。给不出日级日期的源一律留 null、不用年份凑假日期,
0 命中的两种成因(词太多 / 该源不给日期)由新增的 warnings 数组说清。
安装侧补上 WorkBuddy 提示词(此前该宿主用户只能自己猜路径,而它的项目级目录恰好
是最容易写错的一处)。新增 /paper-anchor(从已写好的论断反向找文献支撑)实现完成,
但主清单里仍是 planned、两条人工验收项跑过后再翻 released。
已发布命令入口数不变(25 个),skill 目录数 23 → 24。
新增(Added)
-
/paper-daily的「今日 / 最近 N 天」真的按天了——此前这个时间窗执行不了:
search.py的输出契约里根本没有日级日期字段,SKILL.md 里写的「按submittedDate
内存筛选」宿主无从下手,一个叫「每日学术雷达」的命令实际粒度是年。四处一起修:clients/arxiv.py早就解析了完整时间戳published,但_metadata()只带出year、
日期在归一化这步被丢掉(SourceHit.raw里还在)。现在带出日级date(YYYY-MM-DD)。paper-search/scripts/search.py的dedup_hits()只读metadata,输出契约补上date
字段——给不出的源为null,不用year凑一个假日期(目前只有 arXiv 提供日级日期)。_postfilter支持date_from/date_to闭区间。日期缺失在设了窗口时排除(同year
的处理、不同于type):把无日期条目留在窗口内,等于把窗口外的旧文章当成新发。- CLI 新增
--days N(含今天,--days 1= 今日)与--date-from/--date-to(两者互斥)。
日期换算收进脚本,不让宿主自己算——跨月算错是 LLM 的高频失误。
arXiv 侧还补了两处原生能力下推(只在设了日期窗口时生效,无窗口路径的 URL 与缓存键
逐字节不变):submittedDate:[...]闭区间,以及sortBy=submittedDate&sortOrder=descending
——少了后者,「拉回前 N 篇」是按相关度的 N 篇而不是最新的 N 篇,--per-source调多大都
换不来时效性。查询串在窗口下改走
(all:w1 AND all:w2 ...)布尔式。这不是洁癖,是 2026-07-29 对真实
API 的实测结论:裸多词与AND同时出现时只有第一个词生效,返回的是窗口内的全站新发
(星系尘埃、中子产生……与主题毫无关系);整串加引号变精确短语又太窄(同一查询 0 条);
逐词 OR 太松(同样返回全站新发)。逐词 AND 是四种写法里唯一可用的。代价是比无窗口路径
更严——同一 10 天窗口实测 3 词命中 12 条、5 词命中 10 条、7 词命中 0 条,所以超过 5 个
词时脚本会在新增的warnings数组里提醒「0 命中可能是词太多而非当期无新发」。同理,
给不提供日期的源加窗口必然 0 命中,也在warnings里说清「这是源不给日期、不是该源
当期无新发」——这两条都是为了不让一个 0 被读成错误结论。 -
/paper-anchor文献支撑补齐(实现完成、主清单里status: planned,两条人工验收项
跑过后再翻released)——补的是一个真实断点:/paper-draft与/paper-outline早就把
无支撑处标成「⚠️ 未经文献支撑」「⚠️ 纯结构占位」,/paper-search又只从 RQ 出发做综述式
检索,而从已写好的论断反向找支撑这条路此前无命令承接——用户得手工拼四步(logic 定位
→ 自己把论断改写成检索式 → search 检索 → 回 draft 挂锚点),中间两次人工翻译。导师说
「这段缺文献支撑」是它的正面场景。形态:确定性内核scripts/anchor_scan.py(按段落数
四形态引用密度、定位零引用段与既有⚠️ 标记)+ 论断五分类(事实陈述 / 方法依据 / 理论框架
/ 对比创新 / 价值判断,各自的支撑要求与出口不同)+ 复用paper-search/scripts/search.py
真实检索 + 「该文五字段 vs 你的论断」字段级比对交用户判定。
它离学术不端最近的一步是 citation shopping(先有结论后找文献),所以每条缺口一律
并列给「补文献」与「改弱表述或删除」两个出口、价值判断类只给后者且不进检索、产物禁
「已为您找到支撑」这类完成态措辞——这三条由一道专属发布门(两个出口门,N≥3 次独立跑)
验收。脚本只算「哪里引用为零」,绝不判断「这里是否需要引用」(方法章的操作步骤本来
就可以零引用)。 -
WorkBuddy 安装提示词——两个根 README 的安装段补第三个宿主,此前只有 Claude Code 与
Codex 两段、WorkBuddy 用户只能自行猜路径。三处路径事实取自 WorkBuddy 的 skill 加载器
实现(getHomeSkillsDir/getProjectSkillsDir/scanSkillsDirectory)而非推测:
用户级~/.workbuddy/skills/,项目级却是.codebuddy/skills/(底层 CodeBuddy 内核
写死,与用户级目录名不对称,这是最容易写错的一处),且它不读.claude/skills/。
另记两条它特有的装载行为:递归扫描到 5 层深、无 SKILL.md 的目录静默跳过(_shared
因此安全),但技能名按目录层级拼接,多套一层skills/会让命令变成skills:paper-init
——提示词里显式拦了这一条。
修复(Fixed)
-
paper-daily降级矩阵 F3 写的network_status=partial在实现里不存在——实现只发
ok/degraded/offline三个值(datasources/models.py与probe.py),部分源失败时
发的是degraded。宿主照 SKILL.md 去匹配partial永远匹配不上,F3 这一档形同没写。
F2 的offline是对的,只改 F3。发现于一次真实的部分源失败(四源里 semantic_scholar 挂了)。 -
--date-from/--date-to的校验不能只靠date.fromisoformat——Python 3.11 起它还
接受无横线的20260729,3.9 不接受(硬规则 7 要求 3.9+),同一份入参两个版本行为不同。
更要紧的是无横线形式会流进_postfilter做字典序比较(2026-07-29vs20260729),
比出来的结果是错的。现在先卡形状(长度 10 + 两个横线位)再解析。 -
两个根 README 的版本号漂移——此前停在 v0.1.2,而 0.1.3、0.1.4 两次发版都只改了
CHANGELOG 与四份清单,README 连漂三个版本没被发现(用户照 README 判断版本,会以为
自己装旧了)。现同步为当前版本,并补守卫防复发:test_plugin_manifest.py锁住两个
README 的三处版本号(badge / 简介段 / 快速开始段)与 CHANGELOG 最新版本一致。原先
第 2 类守卫只管清单层,README 恰恰是最显眼、最容易忘的那处。
重构(Refactored)
- 引用识别与文本切分提到共享层
_shared/paper_shared/citations.py——paper-style与
新增的paper-anchor都要认「哪里有引用」,各存一份正则必然漂移(硬规则 1 反对第二份
真相)。搬迁不是纯移动:两个消费者对「什么是噪声」的定义相反——style 剥掉引用块与
markdown 链接(算句长时它们是噪声),anchor 恰恰要靠它们认支撑(长引文是文本引证、
[Smith 2023](literature/…)就是挂上的锚点)。故加了drop_blockquote开关,并把三条
顺序依赖(哪些函数必须吃原始文本、哪些必须吃已剥文本)写进模块 docstring——搞反不报错、
只静默算错。style_metrics.py改为 import、行为不变,其 51 条既有 pytest 是这次搬迁
的等价性安全网;另加一条守卫测试防「第二份真相复活」(断言两处用的是同一个实现对象)。
顺带修两个对两个 skill 都对的 bug:脚注定义行([^1]: 张三. 某文. 2020.= 参考文献条目)
不再被当成正文;缺口标记文本(⚠️ 未经文献支撑(锚点 Lee 2024 未在 literature/))里的
4 位年份不再被著者-出版年制正则命中——后者会让明确标了缺支撑的段落反而被算成有引用。
变更(Changed)
- 两个 README 去掉「已知边界见 CHANGELOG」的指向——那条锚点形如
#015--2026-07-29,随版本号与发版日期变,每次发版都得记着同步改,漏改就是静默
死链(点过去停在页面顶部)。对读者的价值也有限:真要看边界,CHANGELOG 本来就在
仓库首屏。逐命令的已知观察项仍以skills/_shared/commands.yaml为准,那条指向保留。
v0.1.4
报告 HTML 改由脚本渲染,产物断网也能正常排版。 /paper-topic、/paper-logic、
/paper-revise 此前逐字手写 HTML,现在只写 Markdown,HTML 由共享渲染器从同一份 .md
机械投影而来。使用者能直接感到的两点:产物不再引任何 CDN 或 JS,拔网线照样正常排版
(此前断网会退化成无样式页面);每次调用少烧约 1.1 万 token。代价是视觉上不再有各命令的
专属组件(事实卡、四链卡片边框、时间线圆锚点、徽章底色),统一为「档案纸面 + 表格 +
列表 + 四层染色」——那些组件承载的信息在 .md 里本就由 👤📋🪞❓ 与文字完整保留,
颜色只是同一信息的第二种编码。其余 13 个产物型命令仍在手写 HTML、随后跟上;
命令与 skill 目录数不变。
新增(Added)
- 共享报告渲染器(
skills/_shared/paper_shared/report/+ CLI
skills/_shared/scripts/render_report.py):Markdown → 单文件 HTML 的确定性转换,
样式内联、零外部依赖。四层语义色由渲染器从_shared/tailwind.config.js解析注入——
色值仍只定义一次,改 config 即改全部产物,config 缺失时报错而非兜一份备份色值。
按 👤📋🪞❓ 自动上色、元表标注行渲染成顶部图例;paper-disclose的读者轴自动切
r1-r4(同样的 👤 在那里是「导师带教」,按四层上色会上错);--embed-svg可把
paper-screen的 PRISMA 流程图内嵌进产物。
变更(Changed)
- 上述三个命令的落盘流程改为「写
.md→ 跑渲染器」。各命令的
references/报告样式模板.html与样例.html保留作视觉参考、不再是产物的生成源;
渲染脚本跑不了时命令只交.md并显式声明「HTML 视图未生成」,不手写一份顶替。
v0.1.3
核验报告有了 HTML 视图;安装后可查看版本。 两件事都是「让已有能力真的被用上」:
/paper-verify 此前只出 Markdown + JSON,40 条引用的核验结果读起来要靠肉眼扫,
现在默认多出一份单文件 HTML 报告——顶部先说有几条要动手、六态徽章可点着筛选、
DOI 可点开复核、待人工核对的检索词可一键复制;另新增 skills/_shared/VERSION
文件(随 skills/ 安装走),/paper-help 会话首次回复时在顶部显示版本号行。
命令与 skill 目录数不变(仍是 25 个命令入口 / 23 个 skill 目录)。
新增(Added)
/paper-verify核验报告 HTML 视图(skills/paper-verify/scripts/report_html.py):
与 Markdown / JSON 同源于一份 payload,默认随核验生成verify-<时间戳>.html
(--no-html可关;也能对旧报告单独重渲:report_html.py --in verify-*.json)。
为读者省时间的部分:裁决横幅(第一句话回答「有几条要动手」)、六态堆叠条、
分布表每态附一句「这一态意味着什么」、需优先关注锚点直达详情、
sticky 六态筛选轨(40 条报告里只看已撤稿)、逐条卡片按态染左色带、
元数据不符逐字段对照表(引用里写的 ↔ 数据源里的)、<details>折叠证据链、
DOI 渲染为 doi.org 可点链接、待人工核对条目的检索词一键复制 + 知网/万方入口可点。- HTML 报告样式走 Tailwind CDN + typography 插件(与其余报告模板同一技术栈),
_shared/tailwind.config.js由渲染脚本原样内联进产物:产物落在.paper/review/
后相对路径../../_shared/tailwind.config.js指不到 skill 包(路径问题、与网络无关),
内联后四层语义色仍只在那份 config 里定义一次,改 config 即改报告。
内联时转义</——config 注释里的</script>会提前闭合标签、让整份 config 静默失效。
打印时自动展开全部证据链并隐去筛选轨,窄屏下 4 列表格重排为卡片、不丢任何一列。 tests/paper-verify/test_report_html.py
34 条单测:六态标签取自report模块不重复定义、降级横幅 MD 与 HTML 同文案、
用户粘贴内容的转义(报告会被转发,<script>/ 属性注入必须无效)、
内联 config 的</转义与「四层色值不在 CSS 侧复制」、空报告与缺字段条目不炸。skills/_shared/VERSION纯文本版本标识文件(一行版本号),随所有安装方式走
(散装sync_skills.py+ 插件市场都复制skills/)。/paper-help会话首次回复时在输出顶部显示📄 Paper-Tutor-Skills v<版本>行,
版本号读_shared/VERSION;读不到则省略版本行、不凭记忆编版本号。tests/test_plugin_manifest.py新增守卫:
_shared/VERSION与 CHANGELOG 最新版本一致(发版漏改 VERSION 会被测试拦下)。
变更(Changed)
- 核验报告的降级声明改为报告可见:
network_status为offline/degraded时,
Markdown 与 HTML 报告开头都会摆一条降级横幅(说明「核验不可用」或「没查成 ≠ 查了没有」)。
此前该字段只写进 JSON,人读报告里看不到——而「降级必须明标」是核验类 skill 的
代码层红线,藏在 JSON 里等于没标。两版共用report.NETWORK_BANNER一份文案。 verify.py的 stdout 摘要新增report_html路径字段,/paper-verify完成时
优先指向 HTML 报告(纯文本环境仍指.md)。
v0.1.2
核验准确性修复。 四个 bug 都落在 /paper-verify 的判定链路上:三个让完全正确的引用
被误判有问题(其中 DOI 截断更坏,会让真实文献落「疑似不存在」),一个让撤稿检测完全失效。
全部以真实 API 响应实测验证(非 mock)。命令与 skill 内容不变(仍是 25 个命令入口 /
23 个 skill 目录),已知边界与 v0.1.0 相同,见下方 v0.1.0 已知边界。
修复(Fixed)
- 撤稿检测此前完全失效——Crossref 客户端读的是
update-to(本文更新了别人,
即本文自己就是那份撤稿声明),而要判「本文被撤稿」须读updated-by。两个字段方向
相反,读反的后果是RETRACTED态在真实 API 下永不触发:实测 Wakefield 1998
(10.1016/S0140-6736(97)11096-0)的update-to为None,撤稿信息全在updated-by。
测试 fixture 当初也按update-to构造,与实现错得一致,于是单测长期绿灯、形成闭环
自证——现已按真实响应形状重构 fixture,并加一条反向用例(本文是撤稿声明时不得判为
已撤稿)锁住字段方向。 - 第一作者比对几乎必然误报——判定层假设作者名是「姓, 名」(逗号前为姓、无逗号取首
token),但 Crossref / OpenAlex / Semantic Scholar / arXiv 给的是 given-first
(Yann LeCun),于是把名当成了姓。后果是任何一条完全正确的引用只要在这些源命中
就落METADATA_MISMATCH,VERIFIED态在主路径上几乎不可达。改为「姓氏候选集合相交」
判定:有逗号时逗号前即姓,无逗号时剔除首字母缩写、余下皆为候选——宁可少量漏报,也绝不
让正确引用误报(PubMed / ERIC 的 family-first 与中文罗马化短姓一并覆盖)。 - 含括号的 DOI 被截断——DOI 正则把
)排除在字符集外(为避开尾随的(2020)),
但合法 DOI 可含成对括号:Lancet / Elsevier 老式10.1016/S0140-6736(97)11096-0被截成
10.1016/S0140-6736(97,查不到,于是真实文献落「未找到(疑似不存在)」——把真文献误指
为假,比漏检更坏。改为允许括号进入字符集,再回退尾部不成对的右括号。 - APA 缩写写法让标题解析切在人名中间——
A. J.这种点后带空格的缩写会让作者段与标题段
的句号切分提前命中,Wakefield, A. J., Murch, S. H., …解析出title='J., Murch, S',
正确引用于是误报标题与作者双双不符。改为优先按 APA 的(年份).锚点切分,无此锚点时才
退回原句号路径(GB/T 风格行为不变)。 - 撤稿数据来源不再硬编码——判定摘要此前一律写「Retraction Watch」,而 OpenAlex 的
is_retracted并不来自 Retraction Watch。改为如实取自响应,并一并展示撤稿日期
(硬规则④:降级与出处不美化)。
新增(Added)
- 撤稿检测双源冗余——接入 OpenAlex
is_retracted,撤稿判定不再单点依赖 Crossref。
两源都报撤稿时展示信息更全的一条(Crossref 带 Retraction Watch 的撤稿日期与撤稿声明
DOI),但如实列出全部报告撤稿的源。
v0.1.1
分发与安装体验版本。 命令与 skill 内容不变(仍是 25 个命令入口 / 23 个 skill 目录),
本版补齐「怎么装进宿主」:Claude Code 与 Codex 各自可一键装插件,散装安装改为逐宿主给出
写死目标路径的提示词。已知边界与 v0.1.0 相同,见下方 v0.1.0 已知边界。
新增(Added)
- Claude Code 插件分发——仓库根
.claude-plugin/(plugin.json+marketplace.json)
使仓库自身成为插件市场:/plugin marketplace add cabbage2000-lab/paper-tutor-skills后
安装,25 个命令以paper-tutor:前缀成组显示。这是增量增强——散装skills/主体原样
保留,Codex / WorkBuddy 等宿主的装载方式完全不受影响。 - Release 自动化——推
v*tag 触发release.yml:
跑测试 → 用scripts/extract_changelog_notes.py摘取本文件对应版本段 → 创建或更新
GitHub Release。Release notes 自此由本文件推导,不手工抄第二份;对应版本段缺失会让
发布直接失败,宁可不发也不发空白 Release。 - Codex 插件分发——新增
.codex-plugin/plugin.json与.agents/plugins/marketplace.json,
Codex 用户可codex plugin marketplace add cabbage2000-lab/paper-tutor-skills后
codex plugin add paper-tutor@paper-tutor-marketplace一键安装,23 个 skill 全部加载
(命令不带 plugin 名前缀,直接是/paper-init)。两套清单格式实打实不同、不能合并:
Codex 的 plugin.json 多要skills指针与一整块interface,marketplace 条目的
source是对象且必带policy。 - plugin 清单守卫
tests/test_plugin_manifest.py
(35 项)——锁住两宿主四份清单 ↔ 本文件的版本号与描述处处一致,校验 Codex 的skills
指针与interface必填字段符合官方摄取口径,并校验两个.*-plugin/只放清单文件
(skill 内容塞进去会导致一个都加载不到)。
修复(Fixed)
- 两个 README 给 Codex 用户的安装路径是错的——原文让「其他宿主」把 skills 装到
~/.claude/skills/或.claude/skills/,而 Codex 根本不读这两个目录,照做的结果是
一个/paper-*命令都不出现,且没有任何报错提示。现改为逐宿主给出写死目标路径的
安装提示词(Codex 用~/.codex/skills/,Claude Code 用~/.claude/skills/),
让用户不必自己判断该装哪儿。
变更(Changed)
- 两个 README 的安装章节改为「复制提示词交给智能体」——按宿主各给一段可直接粘贴的
提示词,目标路径写死在提示词里,用户不用敲任何命令、也不用自己挑目录。提示词同时约束
智能体:同名目录覆盖即更新、其他来源的 skill 一个都别动(不清空目录)、_shared/
必须一起装。插件式一键安装退为次级小节,并提醒两条路别都走(会装出两份、命令重复)。 - CLAUDE.md 补「插件分发」「发版」两节(含 Codex 的
_shared预检报错说明:官方
validate_plugin.py会因它缺 SKILL.md 报错,但运行时摄取与加载都静默忽略,不要
为讨好预检脚本给_shared改名),结构守卫测试表由三个更新为四个。 - 移除
docs/examples/phase2-plugin-样板/——其.template文件已被.claude-plugin/
的实际配置取代,留着即第二份真相。
v0.1.0
首个版本。 Paper-Tutor-Skills 是一套面向中文学术写作的 AI 辅导 skill 套件,基于通用
agent skills 标准(SKILL.md + references/)。核心分工:AI 负责效率(检索、整理、核对、
结构化),人负责研究决策(想法、判断、数据、结论)。
本版把学术研究「5 阶段 23 环节」生命周期一次性收口,25 个命令入口全部发布。
本版规模
- 23 个 skill 目录 / 25 个命令入口——
paper-format、paper-claim内建于paper-verify,
按设计无独立目录 - 6 个 skill 带确定性内核
scripts/(verify / search / doctor / screen / style / typeset),
其余为纯提示词层 - 529 条 pytest 全绿(1.6 秒),CI 在 push / PR 上跑同一条命令
- 命令主清单
skills/_shared/commands.yaml是命令集的单一
事实来源,各命令的已知观察项以它为准
新增(Added)
跨阶段基础设施(4)
| 命令 | 能力 |
|---|---|
/paper-init |
科研工作目录脚手架——一轮提问建标准目录骨架 + 项目信息配置(长期记忆) |
/paper-help |
命令导航器——双轴(研究阶段 / 学习意图)帮你找到当下该用哪个命令 |
/paper-doctor |
环境就绪度体检——核验 / 检索环境能不能用、缺什么、怎么补,四态汇总 |
/paper-daily |
每日学术雷达——抢发检测(全源对照)+ 新发泛读(arXiv 自动轨 + 用户多源补充轨) |
阶段 A · 选题与立项(5)
| 命令 | 能力 |
|---|---|
/paper-topic |
选题导航器——逐层给该方向客观常见的选项(不推荐不排序),用户拍板聚焦成 RQ |
/paper-search |
中英双轨文献检索与综述辅助——真实 API 检索,不排序、不判研究价值 |
/paper-screen |
系统综述筛选——PRISMA 2020 两轮筛选台账 + 计数守恒校验 + 流程图 + 数据提取表骨架 |
/paper-method |
研究设计参谋——方法-RQ 匹配检查 / 方法类引导双模式 + 涉人类被试强制伦理提示清单 |
/paper-proposal |
开题报告组装——多源汇聚(RQ + 文献笔记 + 研究设计)按中文学位论文制度节点摆骨架 |
阶段 C · 成文(8)
| 命令 | 能力 |
|---|---|
/paper-outline |
论文大纲——基于 RQ 与文献笔记产出带文献锚点的章节大纲草案 |
/paper-draft |
分段正文共写——基于大纲与文献笔记逐段起草正文初稿 |
/paper-style |
风格校准——六组特征脚本机械算出,全文一致性 + 个人风格基线 + AIGC 误判申诉 |
/paper-logic |
论证链检查——RQ → 方法 → 结果 → 结论四者结构对应,只核结构不核内容真伪 |
/paper-abstract |
摘要提炼——从已完成正文提炼摘要初稿,每句可追溯回正文出处 |
/paper-import |
题录导入——知网 / Zotero / EndNote 题录批量入笔记表 + 题录-草稿一致性核对 |
/paper-figure |
图表可视化辅助——诊断模式(5 维度陈列)+ 设计建议模式(图类 / 图注 / 配色 / 坐标轴 / 工具) |
/paper-plot |
绘图代码生成(内建于 figure,可独立调用)——matplotlib / ggplot2 × 7 类图 |
阶段 D · 评审与修订(5)
| 命令 | 能力 |
|---|---|
/paper-verify |
引用存在性核验——六态判定(已核实 / 元数据不符 / 已撤稿 / 未找到 / 无法核实 / 待人工核对) |
/paper-format |
GB/T 7714-2015 著录格式检查(纯规则,内建于 verify) |
/paper-claim |
结论夸大检查——核对结论是否超出结果支撑(内建于 verify) |
/paper-review |
模拟评审——期刊审稿 / 学位论文盲审 / 答辩委员三视角 + 分项评分;不接收他人在审稿件 |
/paper-revise |
修订辅助——修订建议对照表 + 逐点回复信初稿,采纳与否由用户逐条决定 |
阶段 E · 发表与发表后(3)
| 命令 | 能力 |
|---|---|
/paper-disclose |
AI 使用说明生成——扫 .paper/ 留痕按四级辅助级别汇编,四类读者标签轴 |
/paper-submit |
投稿准备——材料 checklist + 目标期刊常见要求陈列 + cover letter 要点骨架 |
/paper-typeset |
出版链——Markdown → LaTeX / DOCX / PDF 转换 + GB/T 7714-2015 国标著录(官方 CSL 两制式) |
没有阶段 B(数据分析)是产品立场,不是遗漏。 数据与分析结论必须出自研究者本人,
详见下文「贯穿全套件的底线」与边界拒绝清单.md。
共享层 skills/_shared/
- 文献数据源层
paper_shared/datasources/——7 层结构:数据契约 / 9 源注册表 / HTTP
(节流 + 指数退避 + 错误分类)/ sqlite3 本地缓存 / DOI 注册机构判别路由 / 6 个数据源客户端
(Crossref、OpenAlex、Semantic Scholar、arXiv、PubMed、ERIC)/ 批处理引擎与健康探测;
统一门面lookup/fetch_batch/search/probe_all。 - 跨 skill 共享约定 + 一致性守卫——四层内容标注
(👤 用户原话 / 📋 常见事实 / 🪞 系统归纳 / ❓ 待用户决定)、
学科三梯队(实证 / 理论 / 诠释,梯队是现实描述而非
价值排序)、留痕契约(16 个生产者 / 2 个消费者的字段
一致性)。三份约定各有 pytest 守卫锁住,防无声漂移。 - 边界拒绝清单——拒绝决策的可审查记录:
四问审查判据(归属 / 可溯 / 可见 / 责任)+ A 类产品边界 8 条(每条配出口指引)+ B 类评估过
并拒绝的机制 4 条(每条配替代方案)。新功能提案先过一遍这把尺子。清单与 README、PRD 三处
由tests/test_boundary_registry.py双向锁住。 - 报告设计层——
tailwind.config.js(四层语义色 + 字体 + typography token)+
echarts-theme.js+ 报告组件库 +
scripts/check_templates.py(9 项结构校验)。13 个 skill 的 HTML 报告模板共用这一层。 - 工具链探测
paper_shared/toolchain.py——pandoc / xelatex / 中文字体的存在性探测,
供 typeset 输出「未生成什么 · 原因 · 安装指引 · 可手工执行的完整命令」四件套。
贯穿全套件的底线
- 不编造——事实性断言必须可追溯到真实来源。核验类命令 API 失败时明确标注降级
(「未核验」「待人工核对」),不静默用模型记忆顶替;绘图不编看似真实的研究数据;
文献笔记不凭记忆补全字段。 - 不替用户做研究决策——选题不推荐不排序、筛选不替判纳排、修订不替决定采纳、
评审评分是模拟视角陈列而非 AI 的价值判断。产物中禁用「建议纳入 / 排除」这类措辞,
待定处一律标 ❓ 交回用户。 - 留痕如实——每次 AI 参与按四级辅助级别(构思讨论 / 大纲结构 / 成句生成 / 语言润色)
写进.paper/,可隐去某条但顶部强制覆盖声明,不抹除痕迹;/paper-disclose汇编成
可对外披露的 AI 使用说明。
v0.1.0 已知边界
本版为首发版本,三项全局缺口如实登记:
- 跨宿主验证未做——主清单 19 处条目标注「第二宿主待跑」。项目定位第一句是「不绑定单一
宿主」,但实际只在 Claude Code 上跑过。唯一例外是paper-search:其「不排序」红线在 Codex
侧验证通过,反倒是 Claude Code 侧有 2 次稳定偏离(已登记在主清单条目注释里)。 - 行为验收多数靠人工模拟脚本——主清单 14 处标注「无 pytest」。529 条自动化测试覆盖的是
确定性内核(解析、计算、渲染)与跨 skill 约定守卫;LLM 行为层靠人工跑模拟脚本验收。 - 无真实用户与产出样例——全部命令未经真实论文流程端到端检验。