feat: vs mode N full passes + --competitors auto-discovery + (/Last30Days) title (#312)

* feat: vs mode runs N full passes; --competitors wraps vs with auto-discovery

Unifies vs-mode and --competitors onto one fanout architecture. A topic
containing "vs" / "versus" now runs N full pipeline.run() calls in parallel
(reverting the one-pass latency optimization that removed per-entity
depth); --competitors becomes a SKILL.md-level shortcut where the hosting
reasoning model (Claude Code, Codex, Hermes, Gemini) discovers N peers via
its own WebSearch, runs Step 0.55 per entity, and invokes the engine with
a vs-topic + --competitors-plan JSON.

Changed:
- vs-mode: N full passes in parallel via fanout (was 1 merged pass).
- --competitors: SKILL.md shortcut for vs-mode-with-discovery. Engine flag
  kept for headless/cron use. LAW 7-style stderr reframed to lead with the
  hosting-model path (use WebSearch + --competitors-plan) instead of
  BRAVE_API_KEY. Footer BRAVE/SERPER nudge suppressed when --plan or
  --competitors-plan present (hosting model already has WebSearch).

Added:
- --competitors-plan JSON flag: per-entity {x_handle, x_related, subreddits,
  github_user, github_repos, context}. Accepts inline JSON or file path.
  subrun_kwargs_for helper is the single source of truth for per-entity
  kwargs — no closure-default fallthrough from main scope.
- Per-entity save files: each entity's sub-run produces its own
  {slug}-raw.md with a single-row Resolved Entities block.
- --polymarket-keywords filter for ambiguous single-token topics.

Fixed:
- test_competitor_subrun_isolation regression suite locks in 3.0.12's
  no-leak invariant (main flags do not inherit into peer sub-runs).
- Updates test_regression.py for the new comparison-mode payload shape.

Bumps plugin.json to 3.0.13. 1,219 tests passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: comparison title attribution — (Last 30 Days) → (/Last30Days)

User feedback on 3.0.13 dogfood runs (Kanye vs Drake, Mercer Island,
Figma): the comparison-mode synthesis title should attribute to the
slash command rather than restate the date range.

Three SKILL.md occurrences updated. Pure documentation change. Bumps to
3.0.14.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Van Horn
2026-04-22 21:31:00 -07:00
committed by GitHub
parent 00d01933e0
commit 949bcf8942
18 changed files with 2022 additions and 71 deletions
+33
View File
@@ -232,6 +232,39 @@ def filter_items_against_topic(topic: str, items: List[Any]) -> List[Any]:
return filtered
def filter_items_against_keywords(items: List[Any], keywords: List[str]) -> List[Any]:
"""Keep only items whose title contains at least one keyword (case-insensitive).
Intended for disambiguating ambiguous single-token topics like 'Warriors'
via --polymarket-keywords (e.g., 'nba,gsw,golden-state') to filter out
Glasgow Warriors rugby, Honor of Kings Rogue Warriors markets that share
the 'Warriors' token but are not the target entity.
"""
if not keywords:
return items
normalized_keywords = [kw.strip().lower() for kw in keywords if kw and kw.strip()]
if not normalized_keywords:
return items
filtered = []
for item in items:
title = getattr(item, "title", None)
if title is None and isinstance(item, dict):
title = item.get("title", "")
title = (title or "").lower()
if any(kw in title for kw in normalized_keywords):
filtered.append(item)
dropped = len(items) - len(filtered)
if dropped:
_log(
f"Keyword filter dropped {dropped} Polymarket items; "
f"kept {len(filtered)} matching {normalized_keywords}"
)
return filtered
def _extract_domain_queries(topic: str, events: List[Dict]) -> List[str]:
"""Extract domain-indicator search terms from first-pass event tags.