Skip to content

Releases: cabbage2000-lab/paper-tutor-skills

v0.1.8

Choose a tag to compare

@github-actions github-actions released this 04 Aug 03:17

本版让检索结果第一眼就看得出哪篇能合法拿到全文。 每条文献多一列「全文获取」——开放
获取(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 条拿到了合法开放版本直链。

    四个实测得来的设计点,改的时候别推翻:

    1. 链接必须两级降级 pdf_url → landing_page_url。实测 pdf_url 经常是 null 而
      landing_page_url 有值(10.1056/NEJMoa2034577 即是),只取 pdf_url 会让大量真有
      开放版本的文献显示成「无链接」。
    2. 版本必须与状态一起陈列。同一条实测的开放版是 submittedVersion(投稿版,
      不是最终发表版
      )——只报「这篇是 OA」会让用户拿投稿版当发表版引,页码与结论表述
      都可能不同。所以有 version 非 publishedVersion 的一律标「可能非最终版」。
    3. 源没给 OA 字段 → 「未知(各源未给出)」,绝不写成 closed。「没查到开放版本」
      与「确认没有开放版本」是两件事,混同会让用户放弃一篇本来能从别处拿到的文献
      (同 cited_by_count 不补 0 的既有立场)。实测 Crossref 压根不给 OA 字段、OpenAlex
      会明确回 closed,两者在产物里必须长得不一样。
    4. 跨源合并按信息完整度、不按源权威序(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 的车。实测样本 PMID 42371203(真撤稿文献)两路均命中。

    两处方向与归属的坑,测试已守住:

    • 只认 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

Choose a tag to compare

@github-actions github-actions released this 31 Jul 10:37

**本版让中文文献这条路真的走得通了。**带中文 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"> 包裹的「知网」「版权」字样),但那是解析器要处理的事实、不是拒绝抓取的理由:
    不可见字符按 Unicode Cf/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 / S2 citationCount)跨源取最大值并括注来源库:各库口径本就不同(Crossref 只数
    注册了 DOI 的引用、S2 收更多灰色文献),取最大是「至少有这么多」的下界陈述,比静默取主源值诚实——静默取值
    会让同一篇在换个主源后数字突变、且看不出为什么。缺失严格区别于 0:源不给这个数就是空白,绝不落成
    「零被引」。

    被引数只是陈列列,不是排序键——脚本有意不提供 --sort cited_by。被引数虽是客观计数,实际是质量代理,
    按它排序就是替用户回答「哪篇更重要」,正是红线 1 要挡的;高引常常只意味着发表得早。用户自己看着这一列排
    优先级是他的判断,做成默认顺序就成了 AI 的判断。

    摘要此前完全没采集,这是「三格起草档」的硬前置:S2 与 arXiv 直接给纯文本(arXiv 的 Atom summary 带
    硬换行,折成单行再存,否则进 markdown 表格会把表打散);OpenAlex 只给 abstract_inverted_index,新增倒排
    还原(位置有洞时跳过、不填占位符——宁可句子短一截,不编内容);PubMed 的 esummary 不返回摘要,
    必须另走一次 efetch XML,失败则吞掉返回空(题录此时已拿到,为摘要把整源判成「未覆盖」是把次要字段的失败
    升级成主能力的失败);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 条如实弃用。四个真实数据缺陷是照着样本一个个修出来的,
    不是照标准猜的:

    1. 前缀不止 jats:。同一字段实测出现过 jats:p、无前缀的 p、以及 ns3:p 三种写法,且没有一处
      声明 xmlns
      ——直接喂 ET.fromstring 必报 unbound prefix。所以统一剥前缀,而不是去声明某个特定命名
      空间(前缀名是出版商任意取的)。
    2. 出版商普遍双重转义,三种形态都实测存在:&amp;amp; 经 XML 解析只解到 &amp;,用户会在笔记表里
      看到字面 &amp;;&amp;lt;.001 解到 &lt;,本该显示成 P < .001;&lt;p&gt; 反转义后变成字面
      <p>,于是又要再剥一遍标签。因此实体反转义与标签剥离必须交替做到稳定,只做一遍或只做其中一件,
      这三条都会带着可见的转义残渣进笔记表(字面标签更会在 HTML 视图里真的插进 DOM)。
    3. 降级路径不许静默吞正文。XML 解析失败(数据本身不良构)时改用正则,但不能用贪婪的 <[^>]*>:
      它会从摘要里合法的数学比较符 < 一路吃到下一个 >,把中间的正文整段吞掉(实测
      p < 0.05 with unclosed <jats:bold> 会丢掉 "0.05 with unclosed")。只剥形态完整的标签(字母开头 +
      闭合 >),剥不掉的孤立 < 保留——三种坏结果里,静默残缺最坏,整条丢弃次之,留个孤立尖括号最轻。
      同理,早先「含任何 < 就判脏」的守卫会把带比较符的摘要整条丢掉,已改为只认标签形态。
    4. 有出版商投递 <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...

Read more

v0.1.6

Choose a tag to compare

@github-actions github-actions released this 30 Jul 02:19

本版让 /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 的新发轨共用这个脚本,改默认值会波及按时间监测):

    1. 截断时脚本自报——shown < after_dedup 时往 warnings 数组塞一条,说清截掉了几条、
      按当前排序被截的是哪一头、该把 --limit 调到多少。搭的是 warnings 已有的现成契约
      (SKILL.md 早写着「非空时原文呈现给用户,不要吞掉」),宿主漏读文档也会在输出里撞见。
    2. --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 又栽在注释上)。两处设计取舍:

    1. 检查放在脚本里,不做成 pytest。 骨架在开发期是合法状态——开完骨架要一路
      累加条目,中间每次 push 都跑 tests.yml。做成断言会让整个开发周期 CI 长红,而
      长红会让人对红灯脱敏,比没有守卫更危险。这个脚本只被 release.yml 调用,正好卡在
      骨架「从合法变成错误」的那一刻。
    2. 用哨兵,不用字面量黑名单。「待填」「开发中骨架」这类词表一改措辞就静默失效,
      等于又造一份会漂移的真相(硬规则 1 反对的正是这个)。哨兵是机器可读契约,骨架
      提示语随便改;删掉哨兵是一个有意识的「我确认这段收口了」的动作。HTML 注释那条
      是兜底——万一开骨架时忘了写哨兵,脚手架注释还在就仍拦得住。
  • 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

Choose a tag to compare

@github-actions github-actions released this 29 Jul 09:14

/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
    内存筛选」宿主无从下手,一个叫「每日学术雷达」的命令实际粒度是年。四处一起修:

    1. clients/arxiv.py 早就解析了完整时间戳 published,但 _metadata() 只带出 year、
      日期在归一化这步被丢掉(SourceHit.raw 里还在)。现在带出日级 date(YYYY-MM-DD)。
    2. paper-search/scripts/search.py 的 dedup_hits() 只读 metadata,输出契约补上 date
      字段——给不出的源为 null,不用 year 凑一个假日期(目前只有 arXiv 提供日级日期)。
    3. _postfilter 支持 date_from / date_to 闭区间。日期缺失在设了窗口时排除(同 year
      的处理、不同于 type):把无日期条目留在窗口内,等于把窗口外的旧文章当成新发。
    4. 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-29 vs 20260729),
    比出来的结果是错的。现在先卡形状(长度 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

Choose a tag to compare

@github-actions github-actions released this 29 Jul 05:01

报告 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

Choose a tag to compare

@github-actions github-actions released this 28 Jul 11:01

核验报告有了 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

Choose a tag to compare

@github-actions github-actions released this 28 Jul 08:25

核验准确性修复。 四个 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

Choose a tag to compare

@github-actions github-actions released this 28 Jul 05:42

分发与安装体验版本。 命令与 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

Choose a tag to compare

@github-actions github-actions released this 27 Jul 11:21

首个版本。 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 行为层靠人工跑模拟脚本验收。
  • 无真实用户与产出样例——全部命令未经真实论文流程端到端检验。