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,117 @@
"""AI 时讯 deep-research 解析与企微格式。"""
from __future__ import annotations
import unittest
from daily.format_wecom import _ai_news_lines, replace_wecom_news_sections
from daily.news.research import parse_research_response
class TestAiNewsResearchParse(unittest.TestCase):
def test_parse_items(self):
raw = """
{
"items": [
{
"title": "Apple sues OpenAI",
"link": "https://techcrunch.com/2026/07/10/apple/",
"source_name": "TechCrunch",
"desc_short": "苹果起诉 OpenAI 涉嫌窃取商业机密",
"published_fmt": "07-11 05:00"
}
]
}
"""
items, tech = parse_research_response(raw, limit=10)
self.assertEqual(len(items), 1)
self.assertEqual(items[0]["source_name"], "TechCrunch")
self.assertIn("苹果", items[0]["desc_short"])
self.assertEqual(tech, [])
def test_dedupe_links(self):
raw = """{"items": [
{"title": "A", "link": "https://example.com/a", "source_name": "Ex", "desc_short": ""},
{"title": "B", "link": "https://example.com/a", "source_name": "Ex", "desc_short": ""}
]}"""
items, _ = parse_research_response(raw, limit=10)
self.assertEqual(len(items), 1)
def test_parse_tech_items(self):
raw = """{"items": [
{"title": "A", "link": "https://example.com/a", "source_name": "Ex", "desc_short": ""}
], "tech_items": [
{"title": "B", "link": "https://example.com/b", "source_name": "Ex", "desc_short": ""}
]}"""
items, tech = parse_research_response(raw, limit=10, tech_limit=5)
self.assertEqual(len(items), 1)
self.assertEqual(len(tech), 1)
self.assertEqual(tech[0]["title"], "B")
class TestMergedWecomNews(unittest.TestCase):
def test_merged_block_format(self):
items = [
{
"title": "Apple sues OpenAI",
"link": "https://techcrunch.com/x",
"source_name": "TechCrunch",
"desc_short": "苹果起诉 OpenAI",
"published_fmt": "07-11 05:00",
}
]
lines = _ai_news_lines(items, merged=True)
self.assertIn("TechCrunch - Apple sues OpenAI", lines[0])
self.assertIn("— 苹果起诉 OpenAI", lines[0])
self.assertNotIn("07-11", lines[0])
def test_merged_with_tech_block(self):
md = """📰 **早报**
📈 **Skills Trending Top 1**
1. skill
"""
main = [
{"title": "Main", "link": "https://example.com/m", "source_name": "Src", "desc_short": "主条", "published_fmt": "07-11"}
]
tech = [
{"title": "Tech", "link": "https://example.com/t", "source_name": "Src2", "desc_short": "技术条", "published_fmt": "07-12"}
]
out = replace_wecom_news_sections(md, ai_news=main, tech_ai_news=tech, merged=True)
self.assertIn("📰 **AI 时讯精选 Top 2**", out)
self.assertNotIn("技术类时讯", out)
self.assertNotIn("🔧", out)
self.assertNotIn("07-11", out)
self.assertNotIn("07-12", out)
self.assertIn("2. [Src2 - Tech]", out)
def test_replace_merged_removes_split_blocks(self):
md = """📰 **早报**
🌍 **国际 AI 时讯 Top 1**
1. [old](https://example.com/old) · `X`
🇨🇳 **国内 AI 时讯 Top 1**
1. [old2](https://example.com/old2) · `Y`
📈 **Skills Trending Top 1**
1. skill
"""
items = [
{
"title": "New story",
"link": "https://example.com/new",
"source_name": "Fortune",
"desc_short": "新故事",
"published_fmt": "",
}
]
out = replace_wecom_news_sections(md, ai_news=items, merged=True)
self.assertIn("📰 **AI 时讯精选 Top 1**", out)
self.assertNotIn("国际 AI 时讯", out)
self.assertNotIn("国内 AI 时讯", out)
self.assertIn("📈 **Skills Trending Top 1**", out)
if __name__ == "__main__":
unittest.main()