--- name: storyline description: News-aggregator story layer workflow. Use when working on cluster.py, the prototype story engine, or the dsh backlog for it. runtime: python --- # Storyline skill — FreshRSS-adjacent story layer The repo is a **thin story layer beside a stock FreshRSS container**. Read AGENTS.md before any change; its rules are binding (keep tests green, no FreshRSS rebuild, no Kafka, no frontend framework, capped LLM, Hermes at :8080, fixed ports). ## Mental model `sources → fetch → dedup → cluster → rank → [LLM top-k] → out/index.html` - **Dedup**: pass 1 exact (normalized title / canonical URL), pass 2 MinHash-LSH → connected-component clusters (transitive merge). - **Rank**: `score = authority * (coverage_count + 1) / (age_h + 2)^1.8`. coverage_count (# sources on a story) is the "importance" signal — no votes. - **LLM**: only top-k (default 10) get summarized; input = headline + ~300 chars. - **Ports**: FreshRSS 8280, RSSHub 1200, Hermes 8080, `--serve` 8765. ## Verify every change ```bash cd prototype && python -m pytest -q ``` Acceptance is "two reworded copies → one cluster" (see `test_cluster.py`). ## Typical runs ```bash python cluster.py --snapshot fr-export.json --llm --topk 10 # from FreshRSS export python cluster.py --opml feeds.opml --llm --serve :8765 # live HTTP feeds, dev LLM_BASE_URL=http://127.0.0.1:8080/v1 LLM_MODEL=hermes python cluster.py --llm ``` Never point summaries at DeepSeek unless the operator changes `LLM_BASE_URL` on purpose.