- 新增常驻调度器 daily/scheduler.py + run-scheduler.ps1(定时生成/推送) - 新增 daily/bridge_manager.py:Windows 兼容的 Cursor SDK 桥接 - 新增 skills/daily-featured-pick 首推 Skill 与叙事轴/去重逻辑 - 新闻抓取窗口、GitHub 搜索、企微 delta 模式等多项改进 - 补充设计文档与 superpowers 计划/规范 - 新增对应测试(scheduler、featured_pick、github_search、news_fetch_window 等) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
274 lines
10 KiB
Markdown
274 lines
10 KiB
Markdown
# Design: 企微早报 Delta 模式
|
||
|
||
Generated: 2026-07-09
|
||
Repo: daily-robots
|
||
Status: DRAFT
|
||
Mode: Builder
|
||
|
||
## Problem Statement
|
||
|
||
企微早报每天推送五榜 Top 10 + 18 条新闻,内容与前几日高度重复(`find-skills`、`openclaw`、飞书集群等长期霸榜)。读者真实需求是「今天有什么新变化」,而非「再读一遍黄页」。
|
||
|
||
根因:
|
||
|
||
1. `daily-agent/SKILL.md` 要求即使较昨日无新增,仍须完整列出 Top 榜。
|
||
2. `movement` 仅用于 opening / signals,列表区块仍全量渲染。
|
||
3. Trending 与 Hot 独立展示,同一 skill 描述写两遍。
|
||
4. 新闻 `DAILY_AI_NEWS_HOURS=72`,无已推送 link 去重,旧闻可连续出现。
|
||
|
||
## What Makes This Cool
|
||
|
||
把早报从「日报复印机」变成「变化通知」:只有新入榜、新新闻、编辑推荐时才占版面;榜全稳且无新新闻时静默不推。读者打开企微即知「今天值得扫一眼的是什么」。
|
||
|
||
## Explicit Non-Goals(已否决方案)
|
||
|
||
以下方案**不在本设计范围内**:
|
||
|
||
| 方案 | 状态 |
|
||
|------|------|
|
||
| 静态页 / 外链档案库 | ❌ 不做 |
|
||
| 今日一装(每天一个 `npx skills add`) | ❌ 不做 |
|
||
| 按星期轮换版面 | ❌ 不做 |
|
||
| 榜首锚点(稳定日仍展示 #1) | ❌ 不做 |
|
||
|
||
## Premises
|
||
|
||
1. 重复感主要来自**列表区块全量复印**,而非 opening 里引用榜首数字。
|
||
2. `output/*.data.json` 与 `daily/delta.py` 已具备新入榜对比能力,应上升为**列表渲染主数据源**。
|
||
3. 企微消息仍在应用内读完,不依赖外部页面。
|
||
4. 叙事层(opening、信号、首推、新闻)保持充实;缩短的是**榜单列表**,不是整报。
|
||
|
||
## Recommended Approach: Delta 模式
|
||
|
||
### 环境变量
|
||
|
||
```env
|
||
# full = 现有行为(全量 Top 榜列表)
|
||
# delta = 本设计(默认推荐)
|
||
DAILY_WECOM_MODE=delta
|
||
|
||
# 无对比基准时(首日或缺历史 data.json)是否自动 full 一次
|
||
DAILY_DELTA_BASELINE_FALLBACK=full # full | empty
|
||
|
||
# 推送闸门全不满足时是否跳过 webhook(仍写 output 文件)
|
||
DAILY_SKIP_PUSH_WHEN_SILENT=1
|
||
|
||
# 强制推送(忽略静默)
|
||
# DAILY_FORCE_PUSH=1
|
||
|
||
# 新闻:缩短窗口 + 去重天数
|
||
DAILY_AI_NEWS_HOURS=24
|
||
DAILY_NEWS_DEDUP_DAYS=7
|
||
```
|
||
|
||
### 推送闸门(Push Gate)
|
||
|
||
满足**任一**条件则生成并推送企微早报:
|
||
|
||
| 条件 | 数据源 |
|
||
|------|--------|
|
||
| 任榜单有新入条目 | `movement.*_moves` 非空 |
|
||
| 去重后仍有新新闻 | 国际或国内 AI 时讯 |
|
||
| 存在 `featured_pick` | Step 0 编辑推荐 |
|
||
| `DAILY_FORCE_PUSH=1` | 环境变量 |
|
||
|
||
**静默日**:以上皆不满足 → 不调用 webhook(`DAILY_SKIP_PUSH_WHEN_SILENT=1` 时)。
|
||
仍执行 `daily generate`,写入 `output/{date}.md`、`output/{date}.wecom.md`、`output/{date}.data.json` 留档。
|
||
|
||
**注意**:仅新闻有新、榜单全稳时**仍推送**,但 Skills/GitHub 列表区块整块省略(不是全天静默)。
|
||
|
||
### 列表渲染(Delta 列表)
|
||
|
||
`DAILY_WECOM_MODE=delta` 时:
|
||
|
||
#### Skills
|
||
|
||
- **仅展示** `movement.skills_trending_moves` / `movement.skills_hot_moves` 中的新入榜条目。
|
||
- **跨榜去重**:按 `skill_id`(`id` 或 `source/title`)合并;同一 skill 只出现一次,标注来源榜(如 `Trending #4 · Hot #2`)。
|
||
- **无新入**:该榜区块**整块不出现**(不写多行「较昨日无新增」)。
|
||
|
||
#### GitHub
|
||
|
||
- 仅展示 `movement.github_trending_moves`、`github_emerging_moves`、`github_topic_moves`。
|
||
- 无新入则区块省略。
|
||
|
||
#### 不包含
|
||
|
||
- 全量 Top N 列表
|
||
- 榜首锚点
|
||
- `(新入 … #n)` 括号标注(与现 `agent_workflow._strip_new_entry_notes` 一致,列表标题用 `[新入 #n]` 前缀即可)
|
||
|
||
### 固定骨架(不因 Delta 缩短)
|
||
|
||
Agent 模式(`DAILY_REPORT_MODE=agent`)下,以下区块**保持**:
|
||
|
||
- opening(2–3 句,首句含具体证据)
|
||
- headline / 今日主题
|
||
- 今日信号(3–5 条)
|
||
- 今日首推
|
||
- 国际 AI / 国内 AI 精选(条数仍由 `DAILY_WECOM_AI_NEWS` 等控制)
|
||
|
||
榜单变短;叙事与新闻不主动砍到 0。
|
||
|
||
### Full 模式逃生口
|
||
|
||
`DAILY_WECOM_MODE=full` 时行为与**当前生产一致**(`format_wecom.build_wecom_report` / `replace_wecom_skill_sections` 全量 Top N)。用于手动切回或对比测试。
|
||
|
||
### 首日 / 无历史基准
|
||
|
||
`find_previous_data(date)` 返回 `None` 时:
|
||
|
||
| `DAILY_DELTA_BASELINE_FALLBACK` | 行为 |
|
||
|----------------------------------|------|
|
||
| `full`(推荐) | 当日按 full 模式渲染列表一次;次日起 delta |
|
||
| `empty` | 当日列表区块为空;opening 须说明「首日报,暂无对比基准」 |
|
||
|
||
实现时在 `generate_report` 或 `build_llm_input` 传入 `baseline_date` 供 Agent 引用。
|
||
|
||
## News Dedup
|
||
|
||
### P0(本阶段)
|
||
|
||
- 维护 `cache/pushed-news-links.json`(或写入 `output/` 旁 cache):最近 `DAILY_NEWS_DEDUP_DAYS` 天已出现在企微早报中的 `link` 集合。
|
||
- `prepare_wecom_news_items` / `prepare_wecom_cn_news_items` 输出前过滤已见 link。
|
||
- `DAILY_AI_NEWS_HOURS` 默认改为 `24`(`.env.example` 同步)。
|
||
|
||
### P1(可选后续)
|
||
|
||
- 标题归一化去重(同一事件多源报道)
|
||
- 每 `source_name` 每日上限 N 条
|
||
|
||
## Agent Skill 变更
|
||
|
||
文件:`skills/daily-agent/SKILL.md`
|
||
|
||
### 删除 / 修改
|
||
|
||
- 删除规则:「即使某榜较昨日无新增,仍须完整列出 Top 榜条目」。
|
||
- 删除:「禁止改用 movement 作为列表来源」(在 delta 模式下反转)。
|
||
|
||
### 新增
|
||
|
||
当 `DAILY_WECOM_MODE=delta`(或 llm_input 含 `wecom_mode: delta`):
|
||
|
||
1. Agent **不写** Skills Trending / Hot / GitHub 列表(仍由 Python 插入,与现流程一致)。
|
||
2. opening / signals **可引用**榜首与 movement 摘要;禁止在 signals 重复列表已展示的同一事实。
|
||
3. 榜全稳时,signals 聚焦新闻与首推,不必编造榜单变化。
|
||
|
||
当 `wecom_mode: full` 时保持现有 SKILL 规则。
|
||
|
||
## Python 模块变更
|
||
|
||
| 模块 | 变更 |
|
||
|------|------|
|
||
| `daily/config.py` | `wecom_mode()`, `news_dedup_days()`, `skip_push_when_silent()`, `delta_baseline_fallback()` |
|
||
| `daily/delta.py` | 可选:`merge_skill_moves_for_wecom(trending_moves, hot_moves)` 跨榜去重 |
|
||
| `daily/format_wecom.py` | `build_skills_delta_section()`, `build_github_delta_section()`;`replace_wecom_skill_sections` 支持 delta |
|
||
| `daily/news/fetch.py` | `filter_pushed_news()` + cache 读写 |
|
||
| `daily/generate.py` | 推送闸门;baseline fallback;静默 skip push |
|
||
| `daily/report_data.py` | `llm_input` 增加 `wecom_mode`, `push_gate` 摘要 |
|
||
| `daily/agent_workflow.py` | 无逻辑变更;依赖 Python 插入 delta 列表 |
|
||
| `.env.example` | 新 env 文档 |
|
||
|
||
## 企微消息示例
|
||
|
||
### 有变化日
|
||
|
||
```markdown
|
||
📰 **早报 · 2026-07-10**
|
||
|
||
[opening:今天最大变化,含数字/条目名]
|
||
|
||
🎯 **{headline}**
|
||
|
||
💡 **今日信号**
|
||
> ...
|
||
|
||
📦 **今日首推**
|
||
[...]
|
||
|
||
🌍 **国际 AI · 精选 N**
|
||
...
|
||
|
||
📈 **Skills Trending 变化**
|
||
1. [新入 #4] [**xxx**](...) · ...
|
||
描述一行
|
||
|
||
🔥 **Skills Hot 变化**
|
||
1. [新入 #2] [**yyy**](...) · ...
|
||
|
||
🐙 **GitHub Trending 变化**
|
||
1. [新入 #4] [owner/repo](...) · ...
|
||
```
|
||
|
||
### 仅新闻有新(榜稳)
|
||
|
||
- 无 📈/🔥/🐙 区块
|
||
- opening 可一句:「榜单较昨日 Top15 无新入;以下为今日 AI 时讯。」
|
||
|
||
### 静默日
|
||
|
||
- 不推送企微
|
||
- `output/` 仍落盘;日志:`[silent] no push gate matched for 2026-07-10`
|
||
|
||
## Approaches Considered
|
||
|
||
### Approach A: 配置瘦身(缩 Top N、24h 新闻)
|
||
|
||
- Effort: S | Risk: Low
|
||
- 只减篇幅,榜头仍天天重复;未解决根因。
|
||
|
||
### Approach B: Delta 列表 + 推送闸门 + 新闻去重(本设计)
|
||
|
||
- Effort: M | Risk: Med
|
||
- 复用 `delta.py`;改 format + skill + push 逻辑。
|
||
|
||
### Approach C: 仅改 Agent 文案
|
||
|
||
- Effort: S | Risk: High
|
||
- Python 仍插入全量列表,规则冲突,不可持续。
|
||
|
||
**Recommendation: B** — 数据层与展示层一致,静默日与跨榜去重可测。
|
||
|
||
## Success Criteria
|
||
|
||
1. 连续 3 天对比 `output/*.data.json`:企微列表区块**重复 skill_id 占比**显著下降。
|
||
2. 榜全稳且无新新闻日:`DAILY_SKIP_PUSH_WHEN_SILENT=1` 时不发 webhook。
|
||
3. Trending/Hot 同一 skill 在列表中**最多出现 1 次**。
|
||
4. `DAILY_WECOM_MODE=full` 与现网行为一致(回归用)。
|
||
5. 首日 `DAILY_DELTA_BASELINE_FALLBACK=full` 不产生空列表投诉。
|
||
|
||
## Open Questions
|
||
|
||
1. 静默日是否需要在企微发一行「今日无更新」?当前设计:**不发**。
|
||
2. 新闻去重 cache 是否纳入 git?建议:**否**,放 `cache/`(已在 `.gitignore`)。
|
||
3. Classic 模式(非 agent)是否同步 delta?建议:**是**,同一 `format_wecom` 路径。
|
||
|
||
## Implementation Tasks
|
||
|
||
| ID | Priority | Task | Files |
|
||
|----|----------|------|-------|
|
||
| T1 | P1 | 新增 config helpers + `.env.example` | `daily/config.py`, `.env.example` |
|
||
| T2 | P1 | 新闻 link 去重 cache | `daily/news/fetch.py`, `daily/config.py` |
|
||
| T3 | P1 | Delta 列表渲染 + 跨榜去重 | `daily/format_wecom.py`, `daily/delta.py` |
|
||
| T4 | P1 | 推送闸门 + 静默 skip push | `daily/generate.py`, `daily/webhook.py` |
|
||
| T5 | P1 | baseline fallback full 一次 | `daily/generate.py` |
|
||
| T6 | P1 | 更新 `daily-agent/SKILL.md` | `skills/daily-agent/SKILL.md` |
|
||
| T7 | P2 | `llm_input` 传 `wecom_mode` / push 摘要 | `daily/report_data.py` |
|
||
| T8 | P2 | 单元测试:跨榜去重、推送闸门、新闻去重 | `tests/test_wecom_delta.py` |
|
||
|
||
## Test Plan
|
||
|
||
- [ ] 有 `2026-07-09.data.json` 时生成 `2026-07-10`:列表仅含新入项
|
||
- [ ] 人造「全稳 + 无新新闻」:不 push
|
||
- [ ] 人造「全稳 + 有新新闻」:push,无 Skills/GitHub 块
|
||
- [ ] `DAILY_WECOM_MODE=full` 输出与改前 `2026-07-09.wecom.md` 结构一致
|
||
- [ ] 无 baseline + `DAILY_DELTA_BASELINE_FALLBACK=full`:首日全量列表
|
||
- [ ] 同一 skill 在 Trending/Hot moves 均出现:列表只 1 条
|
||
|
||
## What I Noticed
|
||
|
||
- 重复感是**产品形态**问题,不是 Agent 文笔问题。
|
||
- 明确否决静态页、今日一装、轮换、锚点后,方案边界清晰,实现可分期。
|
||
- 推送闸门必须**把新闻算进去**,否则静默日会被新闻绕过。
|