fix: per-entity Step 0.55, LAW 7 sub-run quiet, default 2, canonical SKILL.md (#311)

Four fixes based on 2026-04-22 test-window feedback on v3.0.11 --competitors:

- Each competitor sub-run now runs Step 0.55 (X handle / subreddits /
  GitHub) via resolve.auto_resolve inside the fanout closure. Deep-copied
  config per entity prevents _auto_resolve_context leak across sub-runs.
  Resolved data stored on report.artifacts["resolved"] for the renderer.
- New internal_subrun keyword on planner.plan_query and pipeline.run
  suppresses the LAW 7 "No --plan passed" stderr for engine-internal
  fan-out only. Default path unchanged.
- Default --competitors count is now 2 (3-way total). --competitors=N
  still customizes; range 1..6.
- SKILL.md STEP 0 canonical-path self-check forces readers who loaded
  from marketplaces/ (auto-restored to origin/main, stale) to re-read
  from plugins/cache/last30days-skill/last30days/{VERSION}/SKILL.md.
  Two of three 2026-04-22 test windows hit this stale-path trap.
- New ## Resolved Entities block in render_comparison_multi shows
  per-entity handles/subs/github for debug visibility.

Bumps plugin.json to 3.0.12. 12 new tests; 1,175 total passing.

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:30:08 -07:00
committed by GitHub
parent 5f054380c5
commit 00d01933e0
13 changed files with 1019 additions and 23 deletions
+104
View File
@@ -159,6 +159,110 @@ class RenderComparisonMultiTests(unittest.TestCase):
self.assertIn("GPT-5 drop", out)
class ResolvedEntitiesBlockTests(unittest.TestCase):
def _build_with_resolved(self, label, topic, resolved):
r = _build_report(topic, ["Cluster A"])
if resolved is not None:
r.artifacts["resolved"] = resolved
return (label, r)
def test_block_emitted_when_any_entity_has_resolved(self):
reports = [
self._build_with_resolved("OpenAI", "OpenAI", {
"entity": "OpenAI",
"x_handle": "OpenAI",
"subreddits": ["OpenAI", "MachineLearning"],
"github_user": "openai",
"github_repos": ["openai/gpt"],
"context": "GPT-5 release signals are strong",
}),
self._build_with_resolved("Anthropic", "Anthropic", {
"entity": "Anthropic",
"x_handle": "AnthropicAI",
"subreddits": ["ClaudeAI"],
"github_user": "anthropics",
"github_repos": [],
"context": "",
}),
]
rendered = render.render_comparison_multi(reports)
self.assertIn("## Resolved Entities", rendered)
self.assertIn("**OpenAI**: X @OpenAI", rendered)
self.assertIn("r/OpenAI, r/MachineLearning", rendered)
self.assertIn("@openai (openai/gpt)", rendered)
self.assertIn("**Anthropic**: X @AnthropicAI", rendered)
# Missing context renders as "-"
self.assertIn("Context: -", rendered)
def test_block_omitted_when_no_resolved_artifacts(self):
reports = [
self._build_with_resolved("A", "A", None),
self._build_with_resolved("B", "B", None),
]
rendered = render.render_comparison_multi(reports)
self.assertNotIn("## Resolved Entities", rendered)
def test_missing_fields_render_as_dash(self):
reports = [
self._build_with_resolved("OpenAI", "OpenAI", {
"entity": "OpenAI",
"x_handle": "",
"subreddits": [],
"github_user": "",
"github_repos": [],
"context": "",
}),
]
rendered = render.render_comparison_multi(reports)
self.assertIn("**OpenAI**: X - | Subs - | GitHub - | Context: -", rendered)
def test_long_context_truncated(self):
long = "a" * 200
reports = [
self._build_with_resolved("X", "X", {
"entity": "X",
"x_handle": "",
"subreddits": [],
"github_user": "",
"github_repos": [],
"context": long,
}),
]
rendered = render.render_comparison_multi(reports)
# The truncate helper adds an ellipsis; context line should not show
# the full 200-char string.
self.assertNotIn("a" * 200, rendered)
def test_context_emit_includes_resolved_block(self):
reports = [
self._build_with_resolved("OpenAI", "OpenAI", {
"entity": "OpenAI",
"x_handle": "OpenAI",
"subreddits": ["OpenAI"],
"github_user": "",
"github_repos": [],
"context": "",
}),
]
out = render.render_comparison_multi_context(reports)
self.assertIn("## Resolved Entities", out)
self.assertIn("**OpenAI**: X @OpenAI", out)
def test_subreddit_overflow_truncated(self):
reports = [
self._build_with_resolved("X", "X", {
"entity": "X",
"x_handle": "",
"subreddits": ["a", "b", "c", "d", "e", "f", "g"],
"github_user": "",
"github_repos": [],
"context": "",
}),
]
rendered = render.render_comparison_multi(reports)
self.assertIn("r/a, r/b, r/c, r/d, r/e (+2)", rendered)
class EmitComparisonOutputTests(unittest.TestCase):
def test_json_emit_nests_per_entity(self):
reports = [