Resources
1Install
npx skillscat add xngg1021/academic-research-kernel/retraction-watch Install via the SkillsCat registry.
SKILL.md
retraction-watch
定时撤稿监控 blueprint:每周对用户维护的 DOI 清单核查撤稿/更动信号,与历史状态快照比对,只有状态变化才报告,无变化保持静默。配套脚本 scripts/watch.py 负责网络查询与状态管理。
When to Use
- 用户维护一份参考文献/已发表论文清单,要求"有人被撤稿就告诉我"
- 用户要求在综述定稿前持续盯清单内文献的撤稿与更动信号
- 安装后 Hermes 把它登记为建议排程(suggested cron),用户接受后每周自动运行
安装与排程配置
本技能 frontmatter 带 metadata.hermes.blueprint,安装时 不会 静默创建定时任务,而是进入建议队列:
hermes skills install xngg1021/academic-research-kernel/skills/retraction-watch
# 然后在会话中:
/suggestions # 查看待处理建议
/suggestions accept N # 接受并创建 cron 任务
/suggestions dismiss N # 不再提示默认排程 "0 9 * * 1"(每周一 09:00),deliver: origin。接受建议时可用任意 cron 表达式、"every 2h" 式间隔或 ISO 时间戳覆盖 schedule。
监控清单(watchlist)配置
清单是一个 JSON 文件,默认路径 ~/.hermes/retraction-watch.json,可用 hermes config set skills.config.retraction_watch.watchlist <路径> 改。结构:
{
"dois": ["10.1038/nature12373", "10.1126/science.1234567"]
}状态快照文件默认 ~/.hermes/retraction-watch.state.json,记录每个 DOI 上次的 is_retracted 与 Crossref 更新信号集合,由脚本自动维护;删除后下一轮全部按"首次建档"报告一次。
信号来源与判定
两个独立信号,合并为状态快照(snapshot_from_signals):
- OpenAlex:
works/https://doi.org/<doi>?select=is_retracted的布尔字段; - Crossref:
works?filter=updates:<doi>反向查询,命中记录的update-to条目中指向目标 DOI 的撤稿/撤回/更正/表达关注信号(type与source双字段,与 academic-source-verification 的 check_updates 语义一致)。
变化检测(diff_snapshots):is_retracted 翻转、Crossref 更新信号新增或消失,都构成一次报告;首次建档的 DOI 也报告一次(作为基线)。完全相同则该 DOI 静默。
手动跑一次:
python ${HERMES_SKILL_DIR}/scripts/watch.py --run --watchlist ~/.hermes/retraction-watch.json数据源与限制
- OpenAlex 匿名查询有日预算与 100 req/s 上限;key 放
OPENALEX_API_KEY环境变量,只发给 api.openalex.org。429 有界退避,最多 3 次;OpenAlex 404 时继续用 Crossref 单源判定。 - Crossref 的撤稿标注依赖出版商与 Retraction Watch 数据登记;update 记录缺失不证明未撤稿,本技能只报告已登记的更新信号,不推断未登记的事实。
- 两个信号可能不一致(一方先更新):报告按快照逐字段列出,由用户判断,不做多数表决。
- DOI 大小写与前缀形式先归一化再比对,状态文件以归一化 DOI 为键。
Verification
离线自检不触网,验证信号合并与变化检测;live 查询明确 SKIP:
# smoke-test: true
import os, subprocess, sys
from pathlib import Path
base = Path(os.environ.get('SKILL_DIR', '.')).expanduser().resolve()
result = subprocess.run([sys.executable, str(base / 'scripts' / 'watch.py'), '--self-test'],
capture_output=True, text=True, timeout=60)
assert result.returncode == 0, result.stderr
assert 'SKIP live OpenAlex/Crossref checks' in result.stdout
print(result.stdout.strip().splitlines()[-1])