feat: v3.0.0 - intelligent search, GitHub person/project mode, ELI5, 13+ sources

v3 rewrites the search engine from the ground up:

- Intelligent pre-research: resolves X handles, GitHub repos, subreddits,
  TikTok hashtags, and YouTube channels before searching
- GitHub person-mode: PR velocity, top repos by stars, release notes
- GitHub project-mode: live star counts, README, releases, top issues
- ELI5 mode: plain language synthesis, no jargon
- 13+ sources: Reddit, X, YouTube, TikTok, Instagram, HN, Polymarket,
  GitHub, Threads, Pinterest, Perplexity, Bluesky, Web
- Free Reddit comments via public JSON (no API key needed)
- Fun judge v2: humor scoring baked into narrative
- Cookie consent before browser scanning
- 10,000 free ScrapeCreators calls
- 1,012 tests

Thank you to the community contributors whose issues and PRs shaped v3:
@uppinote20 (#143), @zerone0x (#134, #136), @thinkun (#116),
@thomasmktong (#124), @fanispoulinakisai-boop (#100), @pejmanjohn (#78),
@zl190 (#115), @hnshah (#84, #85, #86)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Van Horn
2026-04-08 10:52:23 -07:00
parent 61904b31e3
commit 0a9ff16dfc
397 changed files with 21427 additions and 53106 deletions
+114 -8
View File
@@ -12,8 +12,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import Any, Dict, List, Optional
from urllib.parse import quote_plus, urlencode
from . import http
from .query_type import detect_query_type
from . import http, log
from .relevance import LOW_SIGNAL_QUERY_TOKENS, token_overlap_relevance
GAMMA_SEARCH_URL = "https://gamma-api.polymarket.com/public-search"
@@ -34,10 +33,7 @@ RESULT_CAP = {
def _log(msg: str):
"""Log to stderr (only in TTY mode to avoid cluttering Claude Code output)."""
if sys.stderr.isatty():
sys.stderr.write(f"[PM] {msg}\n")
sys.stderr.flush()
log.source_log("PM", msg)
def _extract_core_subject(topic: str) -> str:
@@ -75,7 +71,7 @@ def _expand_queries(topic: str) -> List[str]:
words = core.split()
if len(words) >= 2:
for word in words:
if len(word) > 1 and word.lower() not in LOW_SIGNAL_QUERY_TOKENS:
if len(word) > 1 and word.lower() not in LOW_SIGNAL_QUERY_TOKENS and word.lower() not in _NOISE_WORDS:
queries.append(word)
# Add the full topic if different from core
@@ -95,6 +91,79 @@ def _expand_queries(topic: str) -> List[str]:
_GENERIC_TAGS = frozenset({"sports", "politics", "crypto", "science", "culture", "pop culture"})
# Words that are too generic to serve as the sole topic-match signal.
# If ALL core words from the topic are in this set, we skip filtering (can't meaningfully filter).
# But if some words are informative and some are generic, we require at least one informative word.
_NOISE_WORDS = frozenset({
# Articles, prepositions, conjunctions
"the", "a", "an", "in", "on", "at", "of", "for", "and", "or", "to", "is", "are",
"was", "were", "will", "be", "by", "with", "from", "as", "it", "its", "not", "no",
"but", "if", "so", "do", "has", "had", "have", "this", "that", "what", "who",
# Directional / geographic terms that cause false matches
"west", "east", "north", "south", "central", "southern", "northern", "eastern", "western",
# Common sports / category terms
"champion", "championship", "league", "division", "conference", "cup", "series",
"team", "game", "match", "season", "win", "winner", "finals",
# Common geographic / place nouns that cause false matches
# "club" -> Athletic Club, Racing Club; "island" -> Epstein's Island, Rhode Island
"club", "island", "city", "park", "hill", "lake", "bay", "beach", "valley",
"river", "mountain", "county", "state", "village", "town", "point", "creek",
"springs", "heights", "ridge", "bridge", "harbor", "port", "station", "center",
"square", "field", "forest", "garden", "tower", "school", "church", "camp",
"ranch", "crossing", "shore", "rock", "summit", "falls", "grove", "haven",
# Generic tech terms that match too broadly on Polymarket
# "cli" -> any CLI tool market; "mcp" -> protocol markets; "ai" -> every AI market
"cli", "mcp", "protocol", "tool", "app", "code", "model", "ai", "api",
"software", "plugin", "skill", "agent", "bot", "search", "research",
# Generic prediction market terms
"market", "odds", "prediction", "forecast", "chance", "probability",
})
def _passes_topic_filter(topic: str, event_title: str) -> bool:
"""Check if event title contains enough informative words from the topic.
Prevents noise like "Meek Mill" matching "Mill.com food recycler" by requiring
proportional word overlap. For topics with 3+ informative words, at least 2 must
match. For shorter topics, 1 match suffices (existing behavior).
Returns True if the event should be kept, False if it should be filtered out.
"""
core = _extract_core_subject(topic).lower()
core_words = [w for w in re.sub(r"[^\w\s]", " ", core).split() if len(w) > 1]
if not core_words:
return True # No words to check against
# Split into informative vs generic
informative = [w for w in core_words if w not in _NOISE_WORDS]
# If ALL words are generic, we can't meaningfully filter — keep everything
if not informative:
return True
# Normalize the title for matching
title_lower = " ".join(re.sub(r"[^\w\s]", " ", event_title.lower()).split())
title_words = set(title_lower.split())
# Count how many informative words appear in the title
match_count = 0
for word in informative:
# Check as whole word in the title word set
if word in title_words:
match_count += 1
continue
# Also check as substring for compound words (e.g., "kanye" in "kanyewest")
if len(word) >= 4 and word in title_lower:
match_count += 1
# For topics with 3+ informative words, require at least 2 matches.
# This prevents single-word false positives like "mill" in "Meek Mill"
# when the topic is "Mill.com food recycler" (3 informative words).
min_matches = 2 if len(informative) >= 3 else 1
return match_count >= min_matches
def _extract_domain_queries(topic: str, events: List[Dict]) -> List[str]:
"""Extract domain-indicator search terms from first-pass event tags.
@@ -130,6 +199,14 @@ def _extract_domain_queries(topic: str, events: List[Dict]) -> List[str]:
return domain_queries
def _infer_query_intent(topic: str) -> str:
"""Tiny local fallback for Polymarket search tuning only."""
text = topic.lower().strip()
if re.search(r"\b(predict|prediction|odds|forecast|chance|probability|will .* win)\b", text):
return "prediction"
return "breaking_news"
def _search_single_query(query: str, page: int = 1) -> Dict[str, Any]:
"""Run a single search query against Gamma API."""
params = {
@@ -328,7 +405,7 @@ def _compute_text_similarity(topic: str, title: str, outcomes: List[str] = None)
if core in title_lower:
return 1.0
query_type = detect_query_type(topic)
query_type = _infer_query_intent(topic)
title_score = token_overlap_relevance(core, title)
best_score = title_score
@@ -392,6 +469,7 @@ def parse_polymarket_response(response: Dict[str, Any], topic: str = "") -> List
events = response.get("events", [])
items = []
filtered_count = 0
for i, event in enumerate(events):
event_id = event.get("id", "")
title = event.get("title", "")
@@ -403,6 +481,12 @@ def parse_polymarket_response(response: Dict[str, Any], topic: str = "") -> List
if not event.get("active", True):
continue
# Filter: skip events that don't match the topic's core subject
# This prevents "NFC West" from matching a "Kanye West" search
if topic and not _passes_topic_filter(topic, title):
filtered_count += 1
continue
# Get markets for this event
markets = event.get("markets", [])
if not markets:
@@ -574,7 +658,29 @@ def parse_polymarket_response(response: Dict[str, Any], topic: str = "") -> List
"why_relevant": f"Prediction market: {title[:60]}",
})
if filtered_count:
_log(f"Filtered {filtered_count} noise events (topic: '{topic}')")
# Sort by relevance (quality-signal ranked) and apply cap
items.sort(key=lambda x: x["relevance"], reverse=True)
# Drop ALL results if nothing is genuinely on-topic.
# If the best item's relevance is below the threshold, the Gamma API
# returned only tangential matches (e.g., "Anthropic best AI model"
# for a "CLI vs MCP" query). Better to show 0 than noise.
_MIN_RELEVANCE = 0.15
if items and items[0]["relevance"] < _MIN_RELEVANCE:
_log(f"All {len(items)} Polymarket results below relevance threshold "
f"({items[0]['relevance']:.2f} < {_MIN_RELEVANCE}), dropping all")
return []
# Per-item floor: drop individual noise items even if the best item passed
_ITEM_MIN_RELEVANCE = 0.10
before_count = len(items)
items = [i for i in items if i["relevance"] >= _ITEM_MIN_RELEVANCE]
dropped = before_count - len(items)
if dropped:
_log(f"Dropped {dropped} Polymarket items below per-item relevance floor ({_ITEM_MIN_RELEVANCE})")
cap = response.get("_cap", len(items))
return items[:cap]