| name | ai-news-keji |
| description | 生成 AI/科技新闻日报:从已配置的 Newsletter、RSS、可选外部来源和网站来源抓取、去重、评分并生成 Markdown。触发:用户说"生成日报 / AI日报 / 今日新闻 / 刷新日报 / fetch news / /ai-news-keji",或要求重新配置本 skill("改输出目录 / 重新配置 Newsletter / 重新选外部集成 / 更新个人偏好")。不触发:单纯"看看 BestBlogs 推荐"、"我关注的 AI builder"等请求,应交给对应的独立 skill。 |
AI 科技新闻日报
从用户配置的信息源生成适合 Obsidian 使用的 AI/科技新闻原始稿和摘要稿。
交互语言
所有面向用户的说明、进度更新、错误解释和最终回复都必须使用中文。命令、文件名、配置键、环境变量、URL、产品名和 Newsletter 名称保持原样。
启动工作流时不要用英文开场,例如不要说 “I'll start...”。应使用类似“我先检查 ai-news-keji 的初始化状态。”这样的中文说明。
运行约束(重要)
- 所有 Python 命令默认使用 repo-local
.venv/bin/python,优先从实际开发仓库根目录运行,而不是盲目假设 ~/.hermes/skills/... 就是可执行 repo。先用 scripts/doctor.py 确认当前环境指向的真实仓库路径;如果 doctor 输出了开发仓库(例如 ~/Code/ai-news-keji),后续 init.py / check-run-state.py / build-summary-context.py / fetch 脚本都从那个 repo root 执行,确保使用同一份 .venv、prompts/summary-template.md 和最新脚本。
- 凭据只走环境变量:IMAP 账号/密码/授权码绝不写入
config.yaml,只通过 email.imap.username_env / password_env 指定的环境变量提供。
- 不要把整份 raw JSON / normalized JSON / 原始稿 / transcript 直接喂给模型。heavy source 必须先 deterministic normalization,再构建 compact context,最后才进入 LLM 总结步骤。
- cron / 无人值守场景下的 partial-run 默认策略:如果
check-run-state.py 显示 has_existing=true,且现有产物只有缓存、原始稿/摘要稿缺失,这类情况默认视为“补充抓取 / 补全产物”,优先复用已有缓存、补抓当前可用来源、重建 summary-context.md,然后补写原始稿和摘要稿;不要把这种 cache-only partial run 直接当成需要覆盖重跑的信号。
- 网站来源与 compact context 的一致性要显式验证:抓到
websites.json 之后,必须确认当前 build-summary-context.py 是否真的把网站条目纳入 summary-context.md。如果没有纳入,就要么先补齐脚本/流程再依赖这些网站条目,要么明确把网站来源降级为“已缓存但未进入本轮 compact context”,不要声称它已经参与了原始稿/摘要稿生成。
- email.mode = mcp 且目标时区为亚洲早晨时,Newsletter 搜索默认用近 2–3 天窗口再过滤目标日期:不要只按“目标日期当天”搜索 Gmail,因为美区 newsletter 经常在上海上午之前仍落在前一自然日或跨日边界。抓取后只保留命中白名单来源、且实际属于本轮日报的结构化条目。
- 缓存结构化邮件或网页摘要前,先做不可见字符清洗:去掉零宽字符、方向控制符等隐形字符,再写
email-raw.json / 其他缓存,避免安全扫描把结构化缓存误判为可疑内容。
路径
所有文件都相对于 skill 目录解析,也就是包含 SKILL.md 的目录。
- 本地配置:
config.yaml
- 公开配置模板:
config.example.yaml
- 本地来源:
sources.yaml
- 公开来源模板:
sources.example.yaml
- 初始化入口:
scripts/init.py
- 初始化向导:
scripts/init_wizard.py
- 运行状态检查:
scripts/check-run-state.py
- 摘要模板:
prompts/summary-template.md
- 健康检查:
scripts/doctor.py
- AI HOT 抓取脚本:
scripts/fetch-aihot.py
- RSS 抓取脚本:
scripts/fetch-rss.py
- IMAP 邮件抓取脚本:
scripts/fetch-email-imap.py
- 外部重型来源 normalization:
scripts/normalize-external-source.py
运行工作流前:
- 在 skill 目录运行
.venv/bin/python scripts/init.py --check。
- 如果检查因为缺少 PyYAML 失败,运行
.venv/bin/python -m pip install -r requirements.txt,然后重试一次检查。
- 如果检查提示首次启动、缺少
config.yaml、缺少 sources.yaml、setup.initialized is not true,或“尚未完成初始化向导”,进入 Agent 分步初始化流程。优先使用 init.py --check 输出里的“建议开场”和“第 1 步问题”作为下一条用户消息;如果检查输出里没有这两段,运行 .venv/bin/python scripts/init.py 获取同样的分步提示。
- 初始化首轮消息固定只包含开场和第 1 步外部集成问题。第 1 步完成前,当前轮不读取
config.example.yaml 或 sources.example.yaml,也不展示 Newsletter、IMAP、输出目录或个人偏好问题。
- 分步初始化每轮只处理当前步骤;每一步都先用一句话说明这一步的作用,然后必须调用
AskUserQuestion 让用户用选项卡形式作答,不要让用户在普通对话里手敲文字回答。每步的选项设计、字段、文案细节见 references/init-flow.md;以 init.py / init_wizard.py 输出的"建议开场"和分步问题为准。用户提交答案后再推进下一步。
- 收集完全部答案后,把答案写入临时 JSON 文件,然后运行
.venv/bin/python scripts/init.py --answers-file <answers.json>。JSON 字段:external_skills、newsletter.choice、newsletter.host、newsletter.folder、newsletter.username_env、newsletter.password_env、output_dir、preferences。
- 写入配置后再次运行
.venv/bin/python scripts/init.py --check。只有检查通过后,才继续读取 config.yaml、sources.yaml 并抓取新闻。
- 可选外部 skills 或 CLI 只根据用户在初始化回答里的选择安装。
8.1 用户事后想改某一步配置("重新配置 Newsletter / 改输出目录 / 改个人偏好 / 重新选外部集成"),走
--reconfigure:先运行 .venv/bin/python scripts/init.py --reconfigure {external_skills|newsletter|output_dir|preferences} 获取该步的 AskUserQuestion 指令,收集答案后运行 .venv/bin/python scripts/init.py --answers-file <answers.json> --reconfigure <section> 写回 config.yaml,其他步骤保持原值不动。区分:仅追加安装一个外部 skill(不改任何已有答案)才用 --skills <name>;任何涉及"重新选 / 改回答"的场景都走 --reconfigure。
- 解析路径时展开
~ 和环境变量。
- 不要把私有输出、原始邮件缓存、token 或用户专属配置写进准备发布的文件。
配置
使用 config.yaml 控制行为:
paths.output_dir:原始稿和摘要 Markdown 的写入目录
paths.filter_rules:评分规则和兴趣画像;未设置或文件缺失时使用内置示例
paths.cache_dir:原始来源数据和滚动去重状态的缓存目录
settings.default_date:yesterday 或 today
settings.retention_days:缓存清理窗口
settings.dedup_window_days:跨天去重窗口
settings.timezone:日期过滤时区
pipeline.enabled_sources:要尝试的来源组,例如 aihot、rss、email、external_skills、websites
pipeline.skip_unavailable_sources:缺少工具或凭据时跳过对应来源,而不是直接失败
email.mode:none、imap 或 mcp
email.imap.*:IMAP host、folder 和凭据环境变量名
external_skills.*:可选命令;只运行 enabled: true 的条目
notification.method:none、macos 或其他用户配置方式
配置不确定时,运行健康检查:
.venv/bin/python scripts/doctor.py
缺少本地配置时,在 Agent 对话中收集初始化答案,然后写入临时 JSON 并运行:
.venv/bin/python scripts/init.py --answers-file <answers.json>
抓取来源前,工作流必须通过这个检查:
.venv/bin/python scripts/init.py --check
初始化检查通过后、抓取任何来源前,必须检查目标日期是否已经有运行产物:
.venv/bin/python scripts/check-run-state.py --date YYYY-MM-DD --config config.yaml
如果输出里的 has_existing 为 true,除非用户在原始请求中明确说“强制刷新 / 覆盖重跑 / 忽略已有文件”,否则必须先停下来询问用户如何处理,不要直接抓取。除了 existing_kinds,还要检查:
is_partial_run
partial_reasons
source_states
用单选提问,先说明检测到哪些已有产物、哪些来源只跑了一半,再提供这些选项:
使用已有结果:不抓取新数据。若摘要稿已存在,直接报告原始稿和摘要稿路径;若只有原始稿存在,则基于已有原始稿生成摘要。
补充抓取:保留已有原始稿和缓存,只抓取当前可用来源的新内容;如果某个 heavy external source 只有 raw cache、还没完成 normalization,这个选项也负责先补完 normalization,再追加新条目并重写摘要。
重新抓取并覆盖:清空目标日期缓存并覆盖当天原始稿和摘要稿;这个选项会覆盖已有结果,必须在说明里明确提醒。
频率规则
sources.yaml 里的每个来源都可以包含 frequency:
| frequency | rule |
|---|
daily | 每次运行都检查。 |
weekday | 目标日期是周六或周日时跳过。 |
3x_week | 每次运行都检查;没有新内容是正常情况。 |
weekly | 如果缓存显示最近 7 天已成功抓取,则跳过。 |
irregular | 每次运行都检查;经常没有新内容是正常情况。 |
被跳过的来源不是失败,也不应该生成空章节。
工作流
默认使用 settings.default_date 选择的日期。如果用户指定了日期,使用用户指定的日期。
1. 检查已有产物
先运行:
.venv/bin/python scripts/check-run-state.py --date YYYY-MM-DD --config config.yaml
如果目标日期已经存在原始稿、摘要稿或缓存:
- 用中文告诉用户检测到的已有产物,例如“检测到 2026-04-25 已经有原始稿和 RSS 缓存”。
- 询问用户选择
使用已有结果、补充抓取 或 重新抓取并覆盖。
- 用户未选择前,不抓取 RSS、Email、外部 skills 或网站来源。
- 选择
使用已有结果 时,不改写已有原始稿;如摘要稿存在,直接结束并报告路径;如摘要稿不存在,只执行“生成摘要”步骤。
- 选择
补充抓取 时,继续后续抓取流程;若 partial_reasons 显示某个来源只有 raw cache,则先基于现有 raw cache 补跑 normalization,再决定是否需要重抓上游。写入原始稿时只追加新增内容,不删除已有内容。
- 选择
重新抓取并覆盖 时,先说明会覆盖目标日期文件;只有用户明确选择后,才清空 {paths.cache_dir}/YYYY-MM-DD/ 并覆盖 {paths.output_dir}/YYYY-MM-DD.md 与摘要稿。
如果没有已有产物,继续正常抓取。
2. 抓取来源
工具可用时,并行抓取已启用的来源组。
AI HOT
如果 pipeline.enabled_sources 启用了 aihot,优先把 AI HOT 作为无需 token 的原生 API 来源抓取。不要安装或调用 AI HOT 的独立 Agent Skill;当前 skill 已经通过稳定 REST API 接入。
.venv/bin/python scripts/fetch-aihot.py --date YYYY-MM-DD --output {cache_dir}/YYYY-MM-DD/aihot.json
.venv/bin/python scripts/normalize-external-source.py --source aihot --input {cache_dir}/YYYY-MM-DD/aihot.json --output {cache_dir}/YYYY-MM-DD/aihot-normalized.json
默认抓 items?mode=selected,脚本会自动带 AI HOT API 要求的浏览器式 User-Agent,并按目标日期过滤 publishedAt。不要手写 curl 省略 UA,也不要把整份 API raw JSON 直接喂给 LLM;后续只让 build-summary-context.py 读取 aihot-normalized.json。
RSS
在 skill 目录运行:
.venv/bin/python scripts/fetch-rss.py --date YYYY-MM-DD --config sources.yaml
如果缺少 sources.yaml,脚本会回退到 sources.example.yaml。
Email Newsletter
只有当 pipeline.enabled_sources 启用了 email 时,才使用已配置的 email 来源白名单。
支持的模式:
none:跳过邮件来源。
imap:运行内置 IMAP 抓取脚本。凭据必须来自 email.imap.username_env 和 email.imap.password_env 指定的环境变量。
mcp:当当前 Agent 运行环境提供 email/Gmail MCP 工具时使用该工具。
使用 IMAP 时,在 skill 目录运行:
.venv/bin/python scripts/fetch-email-imap.py --date YYYY-MM-DD --config config.yaml --sources sources.yaml
IMAP 抓取脚本会搜索目标日期,用 BODY.PEEK[] 读取邮件以避免标记为已读,根据配置的发件人和可选 subject_contains 过滤邮件,尽可能提取纯文本,并把 JSON 输出到 stdout。
使用 MCP 时,搜索目标日期的邮件,根据配置的发件人和可选 subject 规则过滤,读取匹配邮件,并跳过欢迎邮件、订阅确认、纯赞助邮件、广告和招聘内容。
如果 IMAP 凭据或 MCP 工具不可用,且 pipeline.skip_unavailable_sources 为 true,则跳过邮件来源,并记录该来源组不可用。
外部 skills 和 CLI
对每个 enabled: true 的 external_skills.* 条目,运行其配置的命令。命令缺失、目录缺失或非零退出都视为来源失败;如果 pipeline.skip_unavailable_sources 为 true,则跳过并记录原因。
当前柯基的运行配置里,follow-builders 已从 ai-news-keji 主日报来源中移出,改由 Hermes cron follow-builders-standalone-daily 每天 07:00 单独抓取、推送,并写入 每日新闻/Follow Builders/YYYY-MM-DD.md;08:15 的 sync-follow-builders-to-ai-news-summary 会把该 section 同步进当天 YYYY-MM-DD 摘要.md。因此主日报流程不要因为 follow-builders 已安装就主动运行它;只有当 external_skills.follow-builders.enabled: true 时才按下面 heavy-source 流程处理。
对外部 skills 的结果要做可落库性检查。对 aihot、follow-builders(仅当配置显式启用)、bestblogs、ak-rss-digest 这类 heavy external source,必须先缓存 raw,再运行 deterministic normalization,最后才把 normalized items 带进写稿与摘要流程:
.venv/bin/python scripts/normalize-external-source.py --source aihot --input {cache_dir}/YYYY-MM-DD/aihot.json --output {cache_dir}/YYYY-MM-DD/aihot-normalized.json
.venv/bin/python scripts/normalize-external-source.py --source follow-builders --input {cache_dir}/YYYY-MM-DD/follow-builders.json --output {cache_dir}/YYYY-MM-DD/follow-builders-normalized.json
.venv/bin/python scripts/normalize-external-source.py --source bestblogs --input {cache_dir}/YYYY-MM-DD/bestblogs.json --output {cache_dir}/YYYY-MM-DD/bestblogs-normalized.json --deep-read-bestblogs
.venv/bin/python scripts/normalize-external-source.py --source ak-rss-digest --input {cache_dir}/YYYY-MM-DD/ak-rss.json --output {cache_dir}/YYYY-MM-DD/ak-rss-digest-normalized.json
然后再继续下面这些规则:
- 默认优先保留带直达原文链接的候选;有链接的条目在排序、归并和摘要时优先级更高。
- 如果外部输出是超长转录、整段 Prompt、整份 digest 或明显未结构化的大块文本,先做源适配和事实抽取;如果当前轮做不到稳定抽取,就整源跳过并记录原因,不要把长噪音直接带进日报。
follow-builders 一类含长 podcast transcript / prompt 模板 / 汇总摘要的来源,默认不能整段直写,必须先拆成独立事件或明确跳过。
bestblogs 的 discover ... --json 结果里,readUrl 可能为 null。不要因为首层 JSON 没给链接就判定“无原文链接”。如果条目带有 resourceId,必须先尝试运行 bestblogs read deep <resourceId> 拉取详情;这个详情通常会返回可落库的 canonical URL 和正文 Markdown。
- 如果通过
bestblogs read deep <resourceId> 成功拿到 URL,就按正常有链接条目处理,并优先使用 deep read 返回的 URL 作为原始稿和摘要稿链接。
- 如果
bestblogs read deep <resourceId> 命中 RATE_LIMITED / 429,不要误记为“BestBlogs 无链接”。应把该来源标记为“本轮 deep read 受限流影响,待下轮补抓”,并保留 discover 层候选信息,方便下一轮或次日重试。
- 解析
bestblogs read deep <resourceId> --json 时,先看 data.meta 是否已经给出 canonical URL / readUrl,再做 RATE_LIMITED / 429 字符串兜底判断;正文或支持链接里可能天然带有 429 数字,不能因此把成功的 deep-read 误判成限流。
- 只有在
readUrl 为空、resourceId 不可用,或 bestblogs read deep <resourceId> 明确失败且不是限流时,才把该条目当作 BestBlogs 推荐摘要写入;此时必须明确标注它来自 BestBlogs 推荐、属于二手摘要、缺少直达原文链接,不要把它伪装成已核验的一手来源。
- 对仍然没有原文链接的外部候选,摘要时应降低置信度和优先级;优先保留那些标题具体、摘要信息量足够、并且和用户关注方向强相关的条目。
- 把
aihot、follow-builders、bestblogs、ak-rss-digest 视为 heavy external source:先缓存 raw,再做 deterministic normalization,再把 normalized items 交给去重/评分/写稿流程;不要把整段 transcript、tweet dump、prompt 或聚合摘要直接喂进日报。
check-run-state.py 返回 is_partial_run / partial_reasons / source_states 时,优先把它解释为“该日期可能只跑了一半”。如果用户没明确要求覆盖,先选择补完 normalization 或补生成摘要,而不是直接重抓覆盖。
- 对这类改造或流程重构,默认由代理自己完成验证:至少跑 byte-compile、
doctor.py、AI HOT 实网抓取 smoke test、heavy-source normalization 样本,以及 check-run-state.py 的 partial/complete 两种 fixture。
- 如果修的是 BestBlogs deep-read / normalization 逻辑,额外保留一个最小回归:构造“stdout 是成功 JSON、
data.meta.url 存在,但 markdown/帮助链接里带 429 数字”的样本;该样本必须仍被判定为成功,不能回退成 rate_limited。
- 不要让“同厂商的次级生态消息”替代“当天真正的主发布”。如果同一天既出现某家前沿模型公司的旗舰发布(如新模型/新产品线/重大版本)又出现该公司的生态接入、SDK、平台集成、Framework 适配等消息,写稿前必须显式核对:日报是否已经覆盖了那条主发布。如果已经写了 Claude / OpenAI / Gemini / Grok / DeepSeek 的生态接入条目,但没写同日更大的旗舰发布,这应视为选题漏检,而不是合理取舍。
- 对 Anthropic、OpenAI、Google、xAI、Meta、DeepSeek 等头部厂商,最终落稿前要做一次“厂商主发布 sanity check”:如果当天素材里出现该厂商名称,至少确认一次是否存在更高优先级的发布新闻被相邻条目(如 Apple framework 接入、SDK 发布、X 讨论、Benchmark 帖子)遮蔽。必要时保留两条,但不能只保留次级条目而漏掉主发布。
用户明确选择外部 skills 后,可通过初始化答案写入配置;如果只是追加安装某个外部 skill,可运行:
.venv/bin/python scripts/init.py --skills follow-builders,bestblogs,ak-rss-digest
初始化脚本会把原始外部仓库安装到 external_skills.install_dir 下,并把选择的 skills 软链到 external_skills.link_targets。
它可以安装并启用:
follow-builders:把 zarazhangrui/follow-builders clone 到受管理的外部 skill 目录,在其 scripts/ 目录运行 npm install,并软链到已配置的 agent skill 目录。
bestblogs:安装 @bestblogs/cli,并提示用户运行 bestblogs auth login;不安装 BestBlogs 对话式 agent skills。
ak-rss-digest:clone rookie-ricardo/erduo-skills,把其中的 skills/ak-rss-digest 子 skill 链接到受管理的外部 skill 目录,并软链到已配置的 agent skill 目录。
网站来源
对 Readwise Weekly 等已配置网站来源,仅在浏览器或 web-fetch 能力可用时抓取。使用缓存避免重复抓取同一期周报。
3. 缓存原始数据
把原始来源数据写入 {paths.cache_dir}/YYYY-MM-DD/。
建议缓存文件:
email-raw.json
rss-raw.json
external-skills.json
websites.json
aihot.json
aihot-normalized.json
follow-builders.json
follow-builders-normalized.json
bestblogs.json
bestblogs-normalized.json
ak-rss.json
ak-rss-digest-normalized.json
run-manifest.json
原始邮件缓存可能包含私人内容。请把 paths.cache_dir 放在公开 skill 目录之外,且不要提交缓存文件。
如果本轮存在 weekly / frequency skip 的来源,或某些来源以“已检查但无新增”结束,建议额外写一个 run-manifest.json,至少包含:
- 顶层
status: complete
- 各来源的
status / completed / notes
- 对按频率跳过的来源显式写
status: skipped 且 completed: true
这样 check-run-state.py 才能把“已按规则跳过”与“真的没跑完”区分开,避免完整运行在 cron 场景里被误判成 partial run。
对 heavy external source,后续去重、评分和写稿应优先读取 *-normalized.json,而不是直接消费 raw 输出。
在进入原始稿/摘要稿写作前,先构建 compact context:
.venv/bin/python scripts/build-summary-context.py --date YYYY-MM-DD --config config.yaml --output {cache_dir}/YYYY-MM-DD/summary-context.md
然后必须做一次新鲜度检查:
.venv/bin/python scripts/analyze-source-freshness.py --date YYYY-MM-DD --config config.yaml
这一步不是可选项。它的目标是区分:
- 今天确实有新的硬新闻(rss / email / aihot)
- 硬新闻偏少,但有较新的深读 / 观点 / 播客(如 BestBlogs)
- 各来源整体都偏旧,这时应少写并转为分析,而不是假装今天有很多新主线
新鲜度判断的实现细节也要固定下来,避免误判:
email 的时间字段优先看原始邮件日期(常见为 RFC2822 / UTC 字符串),不要因为格式不同就把整批 newsletter 误判为 undated。
bestblogs 的发布时间很多时候不在顶层,而是在 normalized 条目的 deep_read.publish_datetime;做 freshness 统计时必须把它提升/回填到 published_at 参与判断。
summary-context.md 里必须同时输出两层信息:一层是来源级统计(dated / fresh_48h / recent_7d),另一层是候选条目上的 published=... 与 freshness=... 标记,方便写稿时区分“今天新内容”和“近几天延续内容”。
- 如果 freshness 检查显示“硬新闻源偏旧,但深读源较新”,最终成稿必须明确承认这一点:要么上浮高价值新深读,要么直说“今天主线新闻偏少,本期以深读/分析为主”。不要把旧 newsletter 主线硬包装成今天新闻。
后续 LLM 步骤只读取 summary-context.md、prompts/summary-template.md 和评分规则。不要再把整份 raw JSON、normalized JSON、完整原始稿或长 transcript 拉进上下文。需要核验时,只回到单条来源链接或单个缓存文件,不要整源重读。
如果 cron / agent 环境与交互式 shell 使用了不同 Python,优先保证所有 repo 命令从仓库根目录用 .venv/bin/python 执行。对可能被外部调度器从错误解释器拉起的脚本,可以加一个很薄的 runtime guard:先检查必需模块,再自动 re-exec 到 repo-local .venv/bin/python,最后才继续导入 feedparser、yaml 等依赖。
如果本轮改动了交付文件(尤其 email-raw.json、原始稿、摘要稿、summary-context.md、run-manifest.json)且仓库没有 canonical test/lint/build 命令,结束前必须做一次 ad-hoc verification:
- 在系统临时目录里用
tempfile 创建带 hermes-verify- 前缀的临时 Python 脚本;不要把验证脚本长期留在 repo 或缓存目录。
- 脚本至少要断言:上述关键文件存在且可读;
email-raw.json 结构正常;原始稿和摘要稿没有 {{...}} 模板残留;关键章节标题存在。
- 脚本内再跑一次
.venv/bin/python scripts/check-run-state.py --date YYYY-MM-DD --config config.yaml,并断言 is_partial_run=false。
- 运行后尽量删除临时脚本,并在最终回报里明确写成 ad-hoc verification,不要包装成“测试套件全绿”。
- 如果在一次 ad-hoc verification 之后又改了这些交付文件,之前的验证证据立即作废,必须重新生成一个新的
hermes-verify-*.py 临时脚本再跑一遍。
清理早于 settings.retention_days 的日期缓存目录。
4. 维护滚动去重状态
维护 {paths.cache_dir}/recent-events.json,保存最近 settings.dedup_window_days 天的事件。
对每个抓取项:
- 先把不同来源但核心事实相同的条目归并成事件单元,不要把每个来源条目都当成新事件。
- 提取事件核心:主体(公司/产品/人物/机构)+ 动作(发布/融资/并购/监管/上线/合作)+ 关键事实。
- 与
recent-events.json 比较,判断是否属于旧事件延续报道。
- 在写摘要前,再额外回看最近 3 天 的摘要主线;优先检查“今日行业大事”是否已经写过同一主线。若当天文件已归档,也要把归档目录中的最近 3 天摘要视为同一检查范围。
- 只有当延续报道提供实质新信息时才保留,例如新增数字、日期、发布范围、功能细节、监管结果、当事人原话或后续动作。
- 仅仅是不同媒体改写、Newsletter 复述、观点扩写,而没有新增事实时,视为重复转述,直接删除。
- 将最终保留的新事件或 continuation 事件写入最近事件窗口,并清理超出
settings.dedup_window_days 的旧记录。
保留延续报道时,标记为 continuation,并在评分时应用 settings.continuation_penalty。如果同一事件已经在最近 3 天进入过“今日行业大事”,默认不要连续两天再次放入“今日行业大事”;只有确认存在实质新信息时才允许再次进入,并在文案里明确写出新增了什么。
5. 写入原始稿
写入或追加 {paths.output_dir}/YYYY-MM-DD.md。
原始稿要求:
- YAML frontmatter 包含
created、updated、type 和 sources
- 每个来源一个章节
- 每个条目包含加粗标题、2-5 句有用摘要和原始链接
- 不包含赞助、广告、招聘或订阅确认内容
- 长 Newsletter 内容应压缩摘要,但保留具体事实、数字、产品名、日期和链接
如果文件已存在,追加新抓取的来源章节,不删除已有内容。
6. 生成摘要
如果 {paths.filter_rules} 存在则读取它,否则使用 references/filter-rules.example.md。
优先读取 {paths.cache_dir}/YYYY-MM-DD/summary-context.md;只有在核验单条事实或修正链接时才回看原始稿/单个缓存文件。不要把完整原始稿重新喂给模型。
应用三层处理:
- 去重并移除噪音。
- 分别按行业雷达和个人价值两条轨道为每个事件评分。
- 填充
prompts/summary-template.md,写入 {paths.output_dir}/YYYY-MM-DD 摘要.md,覆盖该日期的旧摘要。模板里所有 {{...}} 占位符必须替换为实际值,不能保留花括号、也不能整段省略。
规则:
- 不要在章节标题后立刻添加解释性文字。
- 完整条目之间用
--- 分隔。
- 数量是参考而不是硬性配额:2-4 条行业大事、3-5 条对我有用、10-15 条值得关注、2-3 条关键信号。
- 每个事件放在最适合的章节;避免同一事件在主要章节里重复出现。
- 生成摘要前必须做一次“最近 3 天主线回查”:优先核对最近 3 天摘要中的“今日行业大事”,不要把同一主线仅因标题改写或来源变化再次写成当天主线。
- 如果某事件最近 3 天已经进入过“今日行业大事”,且今天没有实质新增事实,应降级到“值得关注”或直接删除,而不是重复占据主栏目。
- 如果
analyze-source-freshness.py 显示硬新闻来源没有 48 小时内的新内容,不要把最近几天的 newsletter / AI HOT 条目伪装成“今天主线”;必须在文案里明确说明它们是近几天的延续。
- 如果硬新闻来源整体偏旧,但 BestBlogs 等深读来源更新更近,可以让高价值新深读上浮;或者直接写成“今天主线新闻偏少,本期以新深读 / 结构性分析为主”。
- 如果硬新闻来源与深读来源都缺少最近 7 天新内容,宁可少写,也要明确说明“今天没有明显新信息”,并给出原因分析;不要为凑数重复旧主线。
- 行业重要性不必与用户个人兴趣一致。
- 个人有用可以包括小但可执行的文章。
7. 通知
如果 notification.method 为 macos,只在 macOS 上使用 osascript。如果通知不可用或设为 none,静默结束,并用中文向用户报告生成的文件路径。
失败处理
- 配置允许跳过时,跳过不可用的可选来源。
- 在原始稿末尾记录失败来源。
- 某个 feed 格式错误或某个可选命令失败时,继续处理其他来源。
- 不要根据社交元数据猜测人物角色;有明确 profile 或 bio 字段时才使用。
- 外部评分(例如博客排名分)只作为提示,最终始终应用配置的评分规则。