← refacto-feature-scout
import json
from datetime import date
from pathlib import Path
from scout.claude_runner import ClaudeResult, extract_json
from scout.projects import Project
from scout.reddit import parse_atom, render
from scout.scan import build_prompt, is_duplicate, parse_ideas, run_scan
IDEAS = [
{"title": "Whale trade feed", "summary": "Live list of trades over $10k.", "user_value": "Traders ask for it.",
"evidence": [{"url": "https://example.com/a", "note": "thread asking for whale feed"}], "effort": "S",
"category": "feature", "spec": "Poll trades; filter notional > 10k."},
{"title": "No evidence idea", "summary": "Something", "evidence": []},
{"title": "Weird effort", "summary": "x", "evidence": ["https://example.com/b"], "effort": "huge", "category": "nope"},
]
def test_parse_ideas_filters_and_normalises():
text = "Here you go:\n```json\n" + json.dumps(IDEAS) + "\n```"
ideas = parse_ideas(text)
assert [i["title"] for i in ideas] == ["Whale trade feed", "Weird effort"]
assert ideas[1]["effort"] == "M" and ideas[1]["category"] == "feature"
assert ideas[1]["evidence"] == [{"url": "https://example.com/b", "note": ""}]
def test_extract_json_bare_and_wrapped():
assert extract_json('[{"a":1}]') == [{"a": 1}]
assert extract_json('Sure. {"status":"done"} thanks') == {"status": "done"}
def test_duplicate_detection():
assert is_duplicate("Whale trade feed", ["Live whale trade feed"])
assert not is_duplicate("Brier score tracker", ["Whale trade feed"])
def fake_project(tmp_path: Path) -> Project:
(tmp_path / "README.md").write_text("# Fake app\nHas a watchlist.")
return Project(key="terminal", name="PolyTerm", areas=["Polymarket"], folder=tmp_path, url=None,
what_it_is="fake", context_files=["README.md"],
sources={"urls": ["https://board"], "reddit": ["https://feed"]})
def test_run_scan_stores_new_ideas_and_skips_repeats(store, tmp_path):
p = fake_project(tmp_path)
calls = {}
def runner(prompt, **kw):
calls["prompt"] = prompt
return ClaudeResult(ok=True, text=json.dumps(IDEAS), cost_usd=0.5)
r = run_scan(p, store, runner=runner, fetch=lambda url: "<html><body>Board: users want alerts</body></html>",
reddit_posts=[{"title": "Give me alerts", "url": "https://reddit.com/r/x/1", "text": "please"}])
assert r["ok"] and r["added"] == 2 and r["found"] == 2
assert "Has a watchlist" in calls["prompt"] and "users want alerts" in calls["prompt"]
assert "Give me alerts" in calls["prompt"]
# second day: same ideas come back → all duplicates, nothing added
r2 = run_scan(p, store, runner=runner, fetch=lambda url: "", reddit_posts=[])
assert r2["added"] == 0 and r2["duplicates"] == 2
assert "Whale trade feed [proposed]" in build_prompt(p, "", "", "\n".join(f"- {t} [{s}]" for t, s in store.all_titles("terminal")))
def test_run_scan_reports_failure_not_silence(store, tmp_path):
p = fake_project(tmp_path)
r = run_scan(p, store, runner=lambda *a, **k: ClaudeResult(ok=False, error="boom"), fetch=lambda u: "", reddit_posts=[])
assert r["ok"] is False and r["found"] == 0 and "boom" in r["error"]
ATOM = """<?xml version="1.0"?><feed xmlns="http://www.w3.org/2005/Atom">
<entry><author><name>/u/bob</name></author><title>Need a whale tracker</title>
<link href="https://www.reddit.com/r/Polymarket/comments/abc/x/"/>
<published>2099-01-01T00:00:00+00:00</published>
<content type="html"><div>Anyone know a tool? submitted by /u/bob [link] [comments]</div></content></entry></feed>"""
def test_reddit_parse_and_render():
posts = parse_atom(ATOM)
assert posts[0]["title"] == "Need a whale tracker" and posts[0]["author"] == "/u/bob"
assert posts[0]["text"] == "Anyone know a tool?"
assert "Need a whale tracker (https://www.reddit.com/r/Polymarket/comments/abc/x/)" in render(posts)
# The old check here read the operator's own folders off this disk, so it could only pass on
# his Mac. What it was really guarding — that the live project's settings are what we think
# they are — is now in tests/test_frozen_terminal.py, which needs no files at all.
def test_reddit_collect_retries_once_on_429():
import urllib.error
from scout import reddit as rd
calls = []
def fetch(url):
calls.append(url)
if len(calls) == 1:
raise urllib.error.HTTPError(url, 429, "Too Many", {}, None)
return ATOM
posts, problems = rd.collect(["https://a"], fetch=fetch, sleep=lambda s: None)
assert len(posts) == 1 and problems == [] and calls == ["https://a", "https://a"]