Files
last30days-skill/tests/test_category_integration.py
T
Matt Van Horn 4e91f4e754 fix: Step 0.55 category-peer subreddit expansion (#305)
* feat(resolve): category-peer subreddit map for Step 0.55

Introduces scripts/lib/categories.py with a curated category->peer-subs
map and wires scripts/lib/resolve.py auto_resolve() to merge peers into
the WebSearch-extracted subreddit list. Named 2026-04-22 failure mode:
a "Prompting GPT Image 2" run resolved only r/OpenAI + r/ChatGPT and
missed r/StableDiffusion, r/midjourney, r/dalle2, r/aiArt where
prompting techniques actually live.

Map is static, curated, ~11 categories (ai_image_generation,
ai_video_generation, ai_music_generation, ai_coding_agent,
ai_agent_framework, ai_chat_model, saas_screen_recording,
saas_productivity, prediction_markets, crypto_defi, dev_tool_cli).
First-match-wins ordering from most-specific to least-specific.
Compound-term patterns only (no bare common nouns like "image", "ai").

auto_resolve now:
- calls detect_category(topic) after _extract_subreddits
- merges peer_subs case-insensitively, caps at MAX_SUBS (10)
- preserves every WebSearch-returned sub (freshest signal)
- emits [Resolve] Matched category=<id>, adding peers: <list> on stderr
  only when peers were actually added
- returns new "category" key in the result dict for observability
- wraps classifier in try/except so failures degrade to unwidened list

Includes drive-by: test_full_resolve / test_partial_failure
searches_run expectations bumped from 3->4 / 2->3 to match the current
queries dict (subreddit + news + x_handle + github).

* feat(skill): Step 0.55 category-peer expansion and self-check

Adds Section 2a (category-peer expansion, MANDATORY for product topics)
and the Step 0.55 self-check checkpoint that fires immediately before
the Resolved block displays. Structural mirror of the engine-side
categories.py map: same categories, same peer subs, same priority
order.

The model-side path now:
- Applies category-peer expansion to the WebSearch-resolved subs on
  every product-in-a-known-category run.
- Emits the (+ <category_id> peers) annotation on the Reddit line of
  the Resolved block as the observable contract. Absence on a
  product-in-a-known-category topic is a Step 0.55 regression.
- Runs a self-check before emitting Resolved: "does the resolved list
  include at least 2 peer subs for the matched category? if not,
  widen NOW and do not run the engine yet."

Mirror of the Python map lives inside Step 0.55 as a table for the
model to pattern-match against; extrapolation to unlisted categories
is explicitly allowed. Worked example (the exact failing query)
appears below the table so reviewers can see before/after at a glance.

Both changes land inside the existing Step 0.55 block. No new
top-level section, no new LAW. LAWs 1-6 wording unchanged.

* test: end-to-end regression for GPT Image 2 failure mode

Stubs grounding.web_search to return the OpenAI-only subs that caused
the 2026-04-22 failure, then asserts that auto_resolve widens to
include the image-gen peers and emits the [Resolve] Matched
category=ai_image_generation stderr line. Covers the cap boundary
and the uncategorized-topic no-op path.

Fixture tests/fixtures/prompting-gpt-image-2-resolved-block.md is
documentation-grade (not parsed by tests) and shows the pre-fix vs
post-fix Resolved block shape so reviewers can evaluate future
categories.py edits against the original bug.

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-04-22 14:31:39 -07:00

146 lines
4.9 KiB
Python

"""End-to-end regression test for the 2026-04-22 `Prompting GPT Image 2` bug.
Guards the failing run's Resolved-block shape end-to-end: stubs
`grounding.web_search` to return the OpenAI-only subs that caused the
original failure, then asserts that `auto_resolve` now returns the widened
list and emits the expected stderr trace.
If this test starts failing after a `scripts/lib/categories.py` edit, either
the fix regressed or the map intentionally dropped the `ai_image_generation`
category — update the test deliberately.
Fixture reference: `tests/fixtures/prompting-gpt-image-2-resolved-block.md`.
"""
import io
import sys
import unittest
from contextlib import redirect_stderr
from pathlib import Path
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
from lib import resolve
OPENAI_BRAND_SUBREDDIT_RESULTS = [
{
"title": "r/OpenAI community hub",
"snippet": "Discussion at r/ChatGPT and r/singularity about GPT Image 2.",
"url": "https://reddit.com/r/OpenAI/",
},
{
"title": "r/ChatGPTpromptengineering prompt collection",
"snippet": "Also see r/artificial for broader AI chatter.",
"url": "",
},
]
EMPTY_RESULTS: list[dict] = []
def _fake_websearch(label_to_items: dict[str, list[dict]]):
def _search(query, date_range, config):
if "subreddit" in query:
return label_to_items.get("subreddit", EMPTY_RESULTS), {}
if "news" in query:
return label_to_items.get("news", EMPTY_RESULTS), {}
if "handle" in query:
return label_to_items.get("x_handle", EMPTY_RESULTS), {}
if "github" in query:
return label_to_items.get("github", EMPTY_RESULTS), {}
return EMPTY_RESULTS, {}
return _search
class PromptingGptImage2RegressionGuard(unittest.TestCase):
"""The named 2026-04-22 failure mode. Resolved block must include peers."""
@patch("lib.resolve.grounding.web_search")
def test_auto_resolve_widens_to_image_gen_peers(self, mock_search):
mock_search.side_effect = _fake_websearch({
"subreddit": OPENAI_BRAND_SUBREDDIT_RESULTS,
})
result = resolve.auto_resolve(
"Prompting GPT Image 2",
{"BRAVE_API_KEY": "fake"},
)
subs_lower = [s.lower() for s in result["subreddits"]]
# Original WebSearch-returned brand subs preserved
self.assertIn("openai", subs_lower)
self.assertIn("chatgpt", subs_lower)
self.assertIn("singularity", subs_lower)
# At least three of the image-gen peers were added
expected_peers = {"stablediffusion", "midjourney", "dalle2", "aiart", "promptengineering"}
found_peers = expected_peers.intersection(subs_lower)
self.assertGreaterEqual(
len(found_peers),
3,
f"Expected at least 3 image-gen peer subs, found: {found_peers}. "
f"Actual subs: {result['subreddits']}",
)
self.assertEqual(result["category"], "ai_image_generation")
@patch("lib.resolve.grounding.web_search")
def test_stderr_contains_category_match_log_line(self, mock_search):
mock_search.side_effect = _fake_websearch({
"subreddit": OPENAI_BRAND_SUBREDDIT_RESULTS,
})
buf = io.StringIO()
with redirect_stderr(buf):
resolve.auto_resolve(
"Prompting GPT Image 2",
{"BRAVE_API_KEY": "fake"},
)
self.assertIn("Matched category=ai_image_generation", buf.getvalue())
@patch("lib.resolve.grounding.web_search")
def test_cap_enforced_end_to_end(self, mock_search):
# Synthesize a subreddit response with 9 brand subs
many_subs_items = [
{"title": f"r/Brand{i}", "snippet": "", "url": ""}
for i in range(9)
]
mock_search.side_effect = _fake_websearch({
"subreddit": many_subs_items,
})
result = resolve.auto_resolve(
"Prompting GPT Image 2",
{"BRAVE_API_KEY": "fake"},
)
self.assertLessEqual(len(result["subreddits"]), resolve.MAX_SUBS)
# The first WebSearch sub is still present (brand subs never evicted)
self.assertIn("Brand0", result["subreddits"])
@patch("lib.resolve.grounding.web_search")
def test_uncategorized_topic_does_not_inject_peers(self, mock_search):
mock_search.side_effect = _fake_websearch({
"subreddit": [{"title": "r/Kanye is wild", "snippet": "", "url": ""}],
})
buf = io.StringIO()
with redirect_stderr(buf):
result = resolve.auto_resolve(
"Kanye West latest album",
{"BRAVE_API_KEY": "fake"},
)
self.assertEqual(result["subreddits"], ["Kanye"])
self.assertIsNone(result["category"])
self.assertNotIn("Matched category=", buf.getvalue())
if __name__ == "__main__":
unittest.main()