feat: 早报系统重构与功能增强

- 新增常驻调度器 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>
This commit is contained in:
2026-07-17 18:12:00 +08:00
parent 6192dd4e2a
commit 6ea2a4e4c6
35 changed files with 4389 additions and 359 deletions

View File

@@ -0,0 +1,273 @@
# 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`)下,以下区块**保持**
- opening23 句,首句含具体证据)
- headline / 今日主题
- 今日信号35 条)
- 今日首推
- 国际 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 文笔问题。
- 明确否决静态页、今日一装、轮换、锚点后,方案边界清晰,实现可分期。
- 推送闸门必须**把新闻算进去**,否则静默日会被新闻绕过。