Multi-adapter mode: read each trend-sources adapter's output → deduplicate → rough-score → write to candidates.md。
Overview
[用户:抓热点]
↓
[Phase 0: 读 .cheat-state.json 拿 enabled adapters]
↓
[Phase 1: 对每个 adapter 调 fetch]
↓
[Phase 2: normalize 到 candidate-schema]
↓
[Phase 3: 去重(vs candidates / predictions / trends-history)]
↓
[Phase 4: 对每个新 item 粗打分(调 cheat-score 内联逻辑)]
↓
[Phase 5: 排序 + 询问用户哪些加入 candidates.md]
↓
[Phase 6: 写入 + 更新 trends-history.jsonl 缓存]
Constants
- TREND_SOURCES = ["manual-paste"] — list of enabled adapters (default is manual-paste only, the most reliable)
- LOOKBACK_HOURS = 24 — fetch trends from the last N hours
- MAX_PER_SOURCE = 20 — max N items per adapter
- DEDUPE = true — deduplication toggle
- AUTO_SCORE = true — after fetching, automatically call cheat-score for rough scoring
- MIN_COMPOSITE_TO_SUGGEST = 6.0 — items below this score are not recommended for the user to add to the candidate pool (still written to trends-history to avoid re-recommending next time)
💡 Override at call time:
/cheat-trends — sources: manual-paste,aihot,weibo-hot — max-per: 10
Inputs
| Required | Source |
|---|---|
.cheat-state.json |
Default sources |
adapters/trend-sources/<name>.md |
Implementation description of each adapter |
candidates.md |
Deduplication reference |
predictions/*.md |
Deduplication reference (already published items are not recommended again) |
.cheat-cache/trends-history.jsonl |
Historical fetch deduplication cache |
Workflow
Phase 0: Read enabled adapters
# 伪代码
state = read('.cheat-state.json')
enabled_adapters = args.sources or state.get('enabled_trend_sources', ['manual-paste'])
If enabled_adapters is empty → output guidance:
你目前没有启用任何热点源。
最快配法:
- 临时跑:/cheat-trends — sources: manual-paste,aihot
- 永久启用:编辑 .cheat-state.json 的 enabled_trend_sources 数组
可用 adapter(详见 adapters/trend-sources/):
- manual-paste(默认,永远能用)
- aihot(AI 热点聚合,无需 key)
- weibo-hot(微博热搜,无需 key)
- zhihu-hot(知乎热榜,无需 key)
- trendradar-mcp(TrendRadar MCP 服务,需配置)
Phase 1-2: Call fetch + normalize for each adapter
For each adapter, read the adapters/trend-sources/<name>.md fetch interface described in (actually Bash calling underlying Python / shell / WebFetch):
| Adapter | Implementation mechanism |
|---|---|
manual-paste |
Ask the user: "Paste your candidate URL/title list for today (one per line)" → parse each line, do WebFetch on URLs to expand snippets |
aihot |
Read the fetch interface described in adapters/trend-sources/aihot.md |
weibo-hot |
Read the fetch interface described in adapters/trend-sources/weibo-hot.md |
zhihu-hot |
Read the fetch interface described in adapters/trend-sources/zhihu-hot.md |
trendradar-mcp |
Read the fetch interface described in adapters/trend-sources/trendradar-mcp.md |
Each adapter outputs items conforming to candidate-schema.md .
Graceful degradation: single adapter failure (missing API key / endpoint 503 / expired cookie) → skip that adapter,do not throw an exception, and explain in the summary:
✅ aihot: 拉到 18 条
✅ weibo-hot: 拉到 15 条
✅ zhihu-hot: 拉到 12 条
⚠️ trendradar-mcp: 跳过(MCP 服务未配置——配置见 adapters/trend-sources/trendradar-mcp.md)
Phase 3: Deduplication
According to the "deduplication protocol" in candidate-schema.md :
- Compute an id for each item (
sha256(source_type + normalized_title + url_path)[:12]) - Check whether
candidates.mdalready contains this id → skip - Check whether
predictions/*.mdalready contains this id → skip - Check whether
.cheat-cache/trends-history.jsonlalready contains this id andrejected_at != null→ skip
Write deduplication statistics to the summary report.
Phase 4: Rough scoring
AUTO_SCORE=true when enabled, for each new item:
- Use the item's
snapshot_textas input - Score the 7 dimensions according to the current rubric (do notcall the
/cheat-scoresub-skill for IO; reuse the scoring logic inline) - Compute composite
- Give a one-sentence rationale
Note: rough scoring ≠ formal prediction. Prediction must be based on the final draft (edited by the user); the scoring here is only a rough filter for "whether it's worth expanding into a piece".
AUTO_SCORE=false when enabled, when items are written to candidates.md, composite=null, and manual /cheat-score。
Phase 5: Sort + Ask
Sort by composite descending, filter out composite < MIN_COMPOSITE_TO_SUGGEST of:
🔥 抓热点完成。各源拉取统计:
- manual-paste: 5 条(用户输入)
- aihot: 18 条
- weibo-hot: 15 条
跳过 trendradar-mcp(MCP 服务未配置)
去重后剩 27 条新 item。
粗打分后 12 条 composite ≥ 6.0:
| # | 标题 | source | composite | bucket | rationale |
|---|---|---|---|---|---|
| 1 | 为什么我们都讨厌主动联系朋友 | aihot | 8.4 | 30-100w | ER+QL 双 5,AB 普适 |
| 2 | "她不一样"的一千种变体 | weibo-hot | 8.1 | 30-100w | MS 候选维度高 |
| 3 | ...... |
哪些加入 candidates.md?
- 全部加 → 回 "all"
- 选几个 → 回 "1, 3, 5"
- 都不要 → 回 "none"(这些会被记到 trends-history 避免下次重复推)
Phase 6: Write to disk
After user response:
- Selected items → by candidate-schema.md 's "Markdown representation" format append to
candidates.md - All fetched items (whether selected or not) → append to
.cheat-cache/trends-history.jsonl:{"id": "...", "title": "...", "source": "...", "snapshot_at": "...", "rejected_at": null|"<ISO>", "fetched_at": "<ISO>"}
Phase 7: Status update
{
"last_trends_run_at": "<ISO>",
"last_trends_added_count": 5
}
Key Rules
- Do not throw exceptions. Single adapter failure → skip + report. All adapters fail → error "all sources failed", with troubleshooting guidance
- manual-paste is always in. Even if all other adapters are broken, manual-paste mode must work—it is the fallback
- Deduplication is a hard constraint. Same id is not pushed repeatedly; items the user rejected are not pushed again for 6 months
- Rough scoring must be honestly labeled. In the entry in candidates.md, mark
composite (rough, snapshot-based), to avoid confusion with prediction's fine scoring - Do not go directly into predictions/. trends only produces candidates; predict is another action
Refusals
- "Directly scrape Douyin trending feed without cookies" → reject. Douyin's anti-scraping is extremely strict; without cookies it will definitely fail; guide to the douyin-session adapter configuration documentation
- "Skip deduplication and write in everything scraped" → reject. It will pollute the candidate pool, and the next recommend ranking will fail
- "Skip rough scoring and directly write raw titles" → allow (
AUTO_SCORE=false), but prompt the user that they will subsequently need/cheat-scoreto enter the recommend pool
Integration
- Upstream: user-configured
.cheat-state.json'senabled_trend_sourcesarray - Downstream:
/cheat-recommenddirectly readscandidates.mdsorting—as soon as trends finishes writing, recommend sees it immediately - And
/cheat-init: users who selected "no candidate pool" in onboarding Q4 are guided here - And
/cheat-status: the status dashboard shows "Last trend scrape: X days ago / Candidate pool pending cleanup: Y items"
Adapter implementation notes
Each adapters/trend-sources/<name>.md must document the following:
- Dependencies:API key / cookie / package
- fetch interface: invocation method (python script path / shell command / API endpoint)
- Output schema: must conform to candidate-schema.md
- Failure modes: common errors + graceful degradation behavior
- Stability level: ★ 1-5 stars
See adapters/HOWTO.md(to be implemented in batch 3).