fix: clean up press context for reader-facing output (#135)

Three bugs fixed in press context rendering: 1. Correlation list truncated to top 10 in reader mode (was showing all 117) 2. AI instruction blocks stripped from reader-facing output 3. Divergence lists capped at 10 items with '…and N more' trailer Also fixes Copilot CLI invocation: uses file-read approach instead of --attachment (which only supports images) or $(cat) (argument too long). Verified locally: - All 498 tests pass - Reader-mode output is ~6KB (was 22KB+) - Copilot CLI successfully reads 411KB prompt file and produces high-quality analysis with proper Industry & Press section Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Juan Manuel Servera committed May 19, 2026 at 21:10 UTC 9d5e5ebbcc860140fdea446280557c08614d8800
5 files changed +391 -21
.github/workflows/crawl-and-publish.yml
+1 -2
@@ -300,8 +300,7 @@ jobs:
300 fi
301
302 if command -v copilot >/dev/null 2>&1 && copilot \
303 - -p "Analyze the attached weekly GitHub data and produce a markdown summary following the instructions in the attached file." \
304 - --attachment "$PROMPT_FILE" \
303 + -p "Read the file at ${PROMPT_FILE} — it contains your full analysis instructions and weekly GitHub data. Follow those instructions exactly and output ONLY the final markdown analysis (no commentary)." \
304 -s \
305 --no-ask-user \
306 --model claude-sonnet-4 \
.squad/agents/farnsworth/history.md
+1 -1
@@ -26,4 +26,4 @@
26 - **2026-05-19T15:08:00Z:** Leela milestone decomposition complete. Issues assigned to v0.5–v0.9 milestones. Scribe logged orchestration and merged decision. Your assigned v0.5 analysis and synthesis issues are ready. See `.squad/orchestration-log/2026-05-19T15-08-leela.md` for full decomposition outcome.
27 - **2026-05-19T15:22:00+02:00:** Topic-aware prompt template implemented (Issue #63). Key architecture decisions: (1) Used `{{#IF_TOPIC}}`/`{{#IF_NO_TOPIC}}` conditional blocks rather than Jinja2 to keep the template readable as standalone markdown and avoid adding template engine dependencies. (2) Wisdom injection is two-tier — global wisdom from `.squad/identity/wisdom.md` (existing) plus per-topic wisdom from `topics/{id}/wisdom.md` (new). (3) Render script (`scripts/render_topic_prompt.py`) is zero-dependency (stdlib only, with optional PyYAML), so it works in any CI environment without pip install. (4) Backward compatibility guaranteed: when no `squadscope.topic.yml` exists, the template collapses cleanly to general-mode analysis identical to the existing `analyze-weekly.md` behavior.
28 - **2026-05-19T20:07:19+02:00:** Fixed correlator "0 repos" bug (PR #130). Root cause: `correlate.py` loaded repos via `raw_data.get("repos")` but `crawl.py` writes them under `new_repos` and `trending_repos`. Key paths: `scripts/correlate.py:320`, `scripts/crawl.py:857-858`. Lesson: when integrating scripts in a pipeline, always verify the producer's *actual output schema* against the consumer's expected input schema — don't assume key names match. The CI skill pattern ("test the wire") would have caught this if applied at integration time.
29 -- **2026-05-19T20:13:00+02:00:** Divergence analysis implemented (PR #131). Architecture: `detect_divergences()` in `correlate.py` takes the correlation results and inverts them — unmatched articles become "uncovered tech trends", unmatched repos become "unpublicized dev activity". Articles grouped by first category/entity, repos by first topic. Renderer (`render_press_context.py`) appends divergences as a labeled section after the template. Key insight: divergence detection is cheap once correlations are computed — it's just set-difference on matched URLs and repo names. The editorial value is in surfacing *what's missing*, not just what aligns.
29 +- **2026-05-19T20:50:22+02:00:** Press context dual-mode rendering implemented. Three reader-facing bugs fixed: (1) correlation list truncated to top-10 in reader mode (sorted by confidence desc, hype_risk severity); (2) `### Instructions` block stripped from reader output — it is AI prompt input only; (3) `#### Divergence Instructions` replaced with a plain narrative sentence for reader display. Architecture: `render_press_context.py` gained `reader_mode` kwarg propagated to `format_correlations_list(top_n=)` and `format_divergences(reader_mode=)`. `analyze_fallback._render_press_section_no_ai` now calls `_strip_ai_instructions()` which post-processes the pre-rendered file via regex — chosen because the fallback reads a file path, not raw JSON, so re-rendering from scratch would require threading data paths through. Key paths: `scripts/render_press_context.py`, `scripts/analyze_fallback.py`. 16 new tests added; 498 total pass.
scripts/analyze_fallback.py
+87 -3
@@ -258,8 +258,93 @@ def call_github_models(prompt: str) -> str:
258 raise RuntimeError("GitHub Models API request failed after retries") from last_exc
259
260
261 +def _strip_ai_instructions(content: str) -> str:
262 + """Remove AI-facing instruction blocks from a rendered press context string.
263 +
264 + Strips:
265 + - The "### Instructions" section (from that heading to the next "###" or EOF)
266 + - The "#### Divergence Instructions" block (heading + bullet items)
267 + - Truncates the "### Correlation Summary" list to the first 10 entries,
268 + appending a "…and N more" summary line when truncation occurs.
269 + """
270 + import re # noqa: PLC0415
271 +
272 + # Strip ### Instructions section (to next ### heading or EOF)
273 + content = re.sub(
274 + r"\n### Instructions\n.*?(?=\n###|\Z)",
275 + "",
276 + content,
277 + flags=re.DOTALL,
278 + )
279 +
280 + # Strip #### Divergence Instructions block (to next #### / ### heading or EOF)
281 + content = re.sub(
282 + r"\n#### Divergence Instructions\n.*?(?=\n####|\n###|\Z)",
283 + "",
284 + content,
285 + flags=re.DOTALL,
286 + )
287 +
288 + # Truncate correlations list to top 10
289 + corr_match = re.search(
290 + r"(### Correlation Summary\n[^\n]*\n)((?:- [^\n]*\n?)+)",
291 + content,
292 + )
293 + if corr_match:
294 + header = corr_match.group(1)
295 + list_block = corr_match.group(2)
296 + list_lines = [ln for ln in list_block.splitlines() if ln.startswith("- ")]
297 + total = len(list_lines)
298 + if total > 10:
299 + omitted = total - 10
300 + truncated = "\n".join(list_lines[:10])
301 + truncated += f"\n…and {omitted} more repos with press correlation\n"
302 + content = (
303 + content[: corr_match.start()]
304 + + header
305 + + truncated
306 + + content[corr_match.end() :]
307 + )
308 +
309 + # Truncate divergence lists to top 10 items each
310 + for section_header in (
311 + r"#### 🔍 Tech Trends Without Dev Activity",
312 + r"#### 🚀 Dev Activity Without Press Coverage",
313 + ):
314 + div_match = re.search(
315 + rf"({re.escape(section_header)}\n[^\n]*\n\n?)((?:- [^\n]*\n?)+)",
316 + content,
317 + )
318 + if div_match:
319 + header = div_match.group(1)
320 + list_block = div_match.group(2)
321 + list_lines = [ln for ln in list_block.splitlines() if ln.startswith("- ")]
322 + total = len(list_lines)
323 + if total > 10:
324 + omitted = total - 10
325 + truncated = "\n".join(list_lines[:10])
326 + truncated += f"\n- …and {omitted} more topics\n"
327 + content = (
328 + content[: div_match.start()]
329 + + header
330 + + truncated
331 + + content[div_match.end() :]
332 + )
333 +
334 + # Add reader-friendly conclusion if divergences exist but instructions were stripped
335 + if "### Divergence Analysis" in content and "Divergence Instructions" not in content:
336 + if "These divergences highlight" not in content:
337 + content = content.rstrip()
338 + content += (
339 + "\n\nThese divergences highlight gaps between what the tech industry "
340 + "is reporting and what developers are actually building.\n"
341 + )
342 +
343 + return content.strip()
344 +
345 +
346 def _render_press_section_no_ai(press_context_path: Path | None) -> str:
262 - """Render press context data for the no-AI summary."""
347 + """Render press context data for the no-AI summary (reader-facing)."""
348 if not press_context_path or not press_context_path.exists() or press_context_path.stat().st_size == 0:
349 return (
350 "No industry press data was available for this week's analysis. "
@@ -268,8 +353,7 @@ def _render_press_section_no_ai(press_context_path: Path | None) -> str:
353 "highlighting press-driven hype versus organic growth patterns."
354 )
355 content = press_context_path.read_text(encoding="utf-8").strip()
271 - # Include the press context as-is (it's already formatted markdown)
272 - return content
356 + return _strip_ai_instructions(content)
357
358
359 def generate_no_ai_summary(raw_json_path: Path, current_datetime: str, press_context_path: Path | None = None) -> str:
scripts/render_press_context.py
+91 -15
@@ -53,12 +53,36 @@ def format_articles_list(articles: list[dict]) -> str:
53 return "\n".join(lines)
54
55
56 -def format_correlations_list(correlations: list[dict]) -> str:
57 - """Format correlations into a markdown list."""
56 +_HYPE_RISK_SEVERITY: dict[str, int] = {"high": 3, "medium": 2, "low": 1, "none": 0}
57 +
58 +
59 +def format_correlations_list(correlations: list[dict], *, top_n: int | None = None) -> str:
60 + """Format correlations into a markdown list.
61 +
62 + Args:
63 + correlations: List of correlation dicts.
64 + top_n: When set, show only the top N entries (sorted by confidence desc,
65 + then hype_risk severity desc) and append a "…and N more" summary line.
66 + """
67 if not correlations:
68 return "- (none)"
69 +
70 + if top_n is not None:
71 + sorted_corrs = sorted(
72 + correlations,
73 + key=lambda c: (
74 + -c.get("correlation_confidence", 0.0),
75 + -_HYPE_RISK_SEVERITY.get(c.get("hype_risk", "none"), 0),
76 + ),
77 + )
78 + omitted = max(0, len(sorted_corrs) - top_n)
79 + display = sorted_corrs[:top_n]
80 + else:
81 + display = correlations
82 + omitted = 0
83 +
84 lines = []
61 - for corr in correlations:
85 + for corr in display:
86 repo = corr.get("repo", "unknown")
87 match_type = corr.get("match_type", "unknown")
88 confidence = corr.get("correlation_confidence", 0.0)
@@ -67,11 +91,21 @@ def format_correlations_list(correlations: list[dict]) -> str:
91 f"- {repo} — match: {match_type}, "
92 f"confidence: {confidence:.1f}, hype_risk: {hype_risk}"
93 )
94 +
95 + if omitted > 0:
96 + lines.append(f"…and {omitted} more repos with press correlation")
97 +
98 return "\n".join(lines)
99
100
73 -def format_divergences(divergences: dict) -> str:
74 - """Format divergences section into markdown."""
101 +def format_divergences(divergences: dict, *, reader_mode: bool = False) -> str:
102 + """Format divergences section into markdown.
103 +
104 + Args:
105 + divergences: Divergence data dict.
106 + reader_mode: When True, replaces the AI instruction block with a
107 + reader-friendly conclusion sentence.
108 + """
109 if not divergences:
110 return ""
111
@@ -83,10 +117,14 @@ def format_divergences(divergences: dict) -> str:
117
118 lines = ["\n### Divergence Analysis\n"]
119
120 + # In reader mode, cap divergence lists to keep output concise
121 + max_items = 10 if reader_mode else None
122 +
123 if uncovered:
124 lines.append("#### 🔍 Tech Trends Without Dev Activity")
125 lines.append("Topics heavily covered by TechCrunch with no matching GitHub repos:\n")
89 - for item in uncovered:
126 + display_uncovered = uncovered[:max_items] if max_items else uncovered
127 + for item in display_uncovered:
128 topic = item.get("topic", "unknown")
129 articles = item.get("techcrunch_articles", [])
130 article_refs = ", ".join(
@@ -94,12 +132,15 @@ def format_divergences(divergences: dict) -> str:
132 for a in articles[:3]
133 )
134 lines.append(f"- **{topic}**: {article_refs}")
135 + if max_items and len(uncovered) > max_items:
136 + lines.append(f"- …and {len(uncovered) - max_items} more tech trends without dev activity")
137 lines.append("")
138
139 if unpublicized:
140 lines.append("#### 🚀 Dev Activity Without Press Coverage")
141 lines.append("GitHub repos/trends with no matching TechCrunch coverage:\n")
102 - for item in unpublicized:
142 + display_unpub = unpublicized[:max_items] if max_items else unpublicized
143 + for item in display_unpub:
144 topic = item.get("topic", "unknown")
145 repos = item.get("github_repos", [])
146 repo_refs = ", ".join(
@@ -107,19 +148,31 @@ def format_divergences(divergences: dict) -> str:
148 for r in repos[:3]
149 )
150 lines.append(f"- **{topic}**: {repo_refs}")
151 + if max_items and len(unpublicized) > max_items:
152 + lines.append(f"- …and {len(unpublicized) - max_items} more dev topics without press coverage")
153 lines.append("")
154
112 - lines.append("#### Divergence Instructions")
113 - lines.append("Use divergences to identify:")
114 - lines.append("- 🔮 Where industry is moving but devs haven't caught up")
115 - lines.append("- 💡 Where devs are innovating ahead of media attention")
116 - lines.append("- 📊 Opportunity gaps between narrative and reality")
155 + if reader_mode:
156 + lines.append(
157 + "These divergences highlight gaps between what the tech industry is reporting "
158 + "and what developers are actually building."
159 + )
160 + else:
161 + lines.append("#### Divergence Instructions")
162 + lines.append("Use divergences to identify:")
163 + lines.append("- 🔮 Where industry is moving but devs haven't caught up")
164 + lines.append("- 💡 Where devs are innovating ahead of media attention")
165 + lines.append("- 📊 Opportunity gaps between narrative and reality")
166
167 return "\n".join(lines)
168
169
170 def render_press_context(
122 - techcrunch_data: dict | None, correlation_data: dict | None, week: str
171 + techcrunch_data: dict | None,
172 + correlation_data: dict | None,
173 + week: str,
174 + *,
175 + reader_mode: bool = False,
176 ) -> str:
177 """Render the press context prompt section.
178
@@ -127,6 +180,9 @@ def render_press_context(
180 techcrunch_data: Parsed TechCrunch crawl JSON or None.
181 correlation_data: Parsed correlation JSON or None.
182 week: The week string (YYYY-WNN).
183 + reader_mode: When True, produces reader-facing output: top-10 correlations
184 + only, no AI instruction blocks, and a narrative divergence
185 + conclusion instead of model directives.
186
187 Returns:
188 Rendered markdown prompt section.
@@ -161,15 +217,35 @@ def render_press_context(
217 if correlation_data:
218 divergences = correlation_data.get("divergences", {})
219
220 + # In reader mode, sort correlations by confidence desc then hype_risk severity
221 + if reader_mode and correlations:
222 + correlations = sorted(
223 + correlations,
224 + key=lambda c: (
225 + -c.get("correlation_confidence", 0.0),
226 + -_HYPE_RISK_SEVERITY.get(c.get("hype_risk", "none"), 0),
227 + ),
228 + )
229 +
230 + top_n = 10 if reader_mode else None
231 +
232 # Render template
233 rendered = template.replace("{date}", week)
234 rendered = rendered.replace("{article_count}", str(article_count))
235 rendered = rendered.replace("{articles_list}", format_articles_list(articles))
236 rendered = rendered.replace("{correlation_count}", str(correlation_count))
169 - rendered = rendered.replace("{correlations_list}", format_correlations_list(correlations))
237 + rendered = rendered.replace(
238 + "{correlations_list}", format_correlations_list(correlations, top_n=top_n)
239 + )
240 +
241 + # Strip the AI-only ### Instructions block in reader mode
242 + if reader_mode:
243 + instructions_marker = "\n### Instructions\n"
244 + if instructions_marker in rendered:
245 + rendered = rendered[: rendered.index(instructions_marker)]
246
247 # Append divergences section
172 - divergence_section = format_divergences(divergences)
248 + divergence_section = format_divergences(divergences, reader_mode=reader_mode)
249 if divergence_section:
250 rendered += "\n" + divergence_section
251
tests/test_render_press_context.py
+211
@@ -9,6 +9,7 @@ sys.path.insert(0, str(_REPO_ROOT / "scripts"))
9 from render_press_context import (
10 format_articles_list,
11 format_correlations_list,
12 + format_divergences,
13 render_press_context,
14 resolve_paths,
15 )
@@ -193,3 +194,213 @@ class TestResolvePaths:
194 tc, corr = resolve_paths(None, "2026-W21")
195 assert "2026-W21-techcrunch.json" in str(tc)
196 assert "2026-W21-correlations.json" in str(corr)
197 +
198 +
199 +class TestFormatCorrelationsListTopN:
200 + def _make_corrs(self, n: int) -> list[dict]:
201 + """Return n correlations with varying confidence/hype_risk."""
202 + risks = ["none", "low", "medium", "high"]
203 + return [
204 + {
205 + "repo": f"org/repo-{i}",
206 + "match_type": "keyword",
207 + "correlation_confidence": round(0.1 + 0.8 * i / max(n - 1, 1), 2),
208 + "hype_risk": risks[i % 4],
209 + }
210 + for i in range(n)
211 + ]
212 +
213 + def test_no_truncation_when_under_limit(self):
214 + corrs = self._make_corrs(5)
215 + result = format_correlations_list(corrs, top_n=10)
216 + assert "more repos with press correlation" not in result
217 + assert result.count("- org/repo") == 5
218 +
219 + def test_truncates_to_top_n(self):
220 + corrs = self._make_corrs(20)
221 + result = format_correlations_list(corrs, top_n=10)
222 + assert "…and 10 more repos with press correlation" in result
223 + assert result.count("- org/repo") == 10
224 +
225 + def test_sorted_by_confidence_desc(self):
226 + corrs = [
227 + {"repo": "low/conf", "match_type": "k", "correlation_confidence": 0.2, "hype_risk": "none"},
228 + {"repo": "high/conf", "match_type": "k", "correlation_confidence": 0.9, "hype_risk": "none"},
229 + {"repo": "mid/conf", "match_type": "k", "correlation_confidence": 0.5, "hype_risk": "none"},
230 + ]
231 + result = format_correlations_list(corrs, top_n=2)
232 + lines = [l for l in result.splitlines() if l.startswith("- ")]
233 + assert lines[0].startswith("- high/conf")
234 + assert lines[1].startswith("- mid/conf")
235 + assert "…and 1 more repos with press correlation" in result
236 +
237 + def test_no_top_n_returns_all(self):
238 + corrs = self._make_corrs(20)
239 + result = format_correlations_list(corrs)
240 + assert result.count("- org/repo") == 20
241 + assert "more repos" not in result
242 +
243 +
244 +class TestFormatDivergencesReaderMode:
245 + def _divergences(self):
246 + return {
247 + "uncovered_tech_trends": [
248 + {
249 + "topic": "quantum-computing",
250 + "techcrunch_articles": [{"title": "Quantum Leap", "url": "https://tc.com/q"}],
251 + }
252 + ],
253 + "unpublicized_dev_activity": [
254 + {
255 + "topic": "wasm-tooling",
256 + "github_repos": [{"full_name": "org/wasm-lib", "stars": 500}],
257 + }
258 + ],
259 + }
260 +
261 + def test_ai_mode_has_instructions(self):
262 + result = format_divergences(self._divergences(), reader_mode=False)
263 + assert "#### Divergence Instructions" in result
264 + assert "Use divergences to identify" in result
265 +
266 + def test_reader_mode_no_instructions(self):
267 + result = format_divergences(self._divergences(), reader_mode=True)
268 + assert "#### Divergence Instructions" not in result
269 + assert "Use divergences to identify" not in result
270 +
271 + def test_reader_mode_has_narrative(self):
272 + result = format_divergences(self._divergences(), reader_mode=True)
273 + assert "gaps between what the tech industry is reporting" in result
274 + assert "developers are actually building" in result
275 +
276 + def test_reader_mode_still_shows_data(self):
277 + result = format_divergences(self._divergences(), reader_mode=True)
278 + assert "quantum-computing" in result
279 + assert "wasm-tooling" in result
280 +
281 +
282 +class TestRenderPressContextReaderMode:
283 + def test_reader_mode_removes_instructions_block(self):
284 + result = render_press_context(
285 + _techcrunch_data(), _correlation_data(), "2026-W21", reader_mode=True
286 + )
287 + assert "### Instructions" not in result
288 + assert "Press-correlated" not in result
289 + assert "Press vs Reality" not in result
290 +
291 + def test_ai_mode_keeps_instructions_block(self):
292 + result = render_press_context(
293 + _techcrunch_data(), _correlation_data(), "2026-W21", reader_mode=False
294 + )
295 + assert "### Instructions" in result
296 + assert "Press-correlated" in result
297 +
298 + def test_reader_mode_truncates_large_correlations(self):
299 + many = [
300 + {
301 + "repo": f"org/repo-{i}",
302 + "match_type": "keyword",
303 + "correlation_confidence": 0.5,
304 + "hype_risk": "low",
305 + }
306 + for i in range(20)
307 + ]
308 + result = render_press_context(
309 + _techcrunch_data(),
310 + _correlation_data(many),
311 + "2026-W21",
312 + reader_mode=True,
313 + )
314 + repo_lines = [ln for ln in result.splitlines() if ln.startswith("- org/repo")]
315 + assert len(repo_lines) == 10
316 + assert "…and 10 more repos with press correlation" in result
317 +
318 + def test_reader_mode_no_truncation_when_under_limit(self):
319 + few = [
320 + {
321 + "repo": f"org/repo-{i}",
322 + "match_type": "keyword",
323 + "correlation_confidence": 0.5,
324 + "hype_risk": "low",
325 + }
326 + for i in range(5)
327 + ]
328 + result = render_press_context(
329 + _techcrunch_data(),
330 + _correlation_data(few),
331 + "2026-W21",
332 + reader_mode=True,
333 + )
334 + assert "more repos with press correlation" not in result
335 +
336 +
337 +class TestStripAiInstructions:
338 + """Tests for analyze_fallback._strip_ai_instructions."""
339 +
340 + def setup_method(self):
341 + import sys
342 + sys.path.insert(0, str(_REPO_ROOT))
343 + import scripts.analyze_fallback as af
344 + self.af = af
345 +
346 + def _full_press_context(self) -> str:
347 + """Simulate a fully rendered AI-mode press context."""
348 + return (
349 + "## Press Context (TechCrunch, week of 2026-W21)\n"
350 + "3 articles published relevant to tech/open-source.\n\n"
351 + "Notable coverage:\n"
352 + "- [Article One](https://tc.com/1) [AI]\n\n"
353 + "### Correlation Summary\n"
354 + "15 repos have press correlation:\n"
355 + + "\n".join(
356 + f"- org/repo-{i} — match: keyword, confidence: 0.5, hype_risk: low"
357 + for i in range(15)
358 + )
359 + + "\n\n"
360 + "### Instructions\n"
361 + "For each trending repo, note if press coverage preceded the star surge.\n"
362 + "Label repos as:\n"
363 + "- '📰 Press-correlated' — stars gained after/during press coverage\n"
364 + "- '🌱 Organic growth' — stars gained without press coverage\n"
365 + )
366 +
367 + def test_removes_instructions_section(self):
368 + content = self._full_press_context()
369 + result = self.af._strip_ai_instructions(content)
370 + assert "### Instructions" not in result
371 + assert "Press-correlated" not in result
372 +
373 + def test_removes_divergence_instructions(self):
374 + content = (
375 + "### Divergence Analysis\n\n"
376 + "#### 🚀 Dev Activity Without Press Coverage\n"
377 + "repos...\n\n"
378 + "#### Divergence Instructions\n"
379 + "Use divergences to identify:\n"
380 + "- 🔮 Where industry is moving\n"
381 + "- 💡 Where devs are innovating\n"
382 + )
383 + result = self.af._strip_ai_instructions(content)
384 + assert "#### Divergence Instructions" not in result
385 + assert "Use divergences to identify" not in result
386 +
387 + def test_truncates_correlation_list_to_10(self):
388 + content = self._full_press_context()
389 + result = self.af._strip_ai_instructions(content)
390 + repo_lines = [ln for ln in result.splitlines() if ln.startswith("- org/repo")]
391 + assert len(repo_lines) == 10
392 + assert "…and 5 more repos with press correlation" in result
393 +
394 + def test_no_truncation_when_under_limit(self):
395 + content = (
396 + "### Correlation Summary\n"
397 + "5 repos have press correlation:\n"
398 + + "\n".join(
399 + f"- org/repo-{i} — match: keyword, confidence: 0.5, hype_risk: low"
400 + for i in range(5)
401 + )
402 + + "\n"
403 + )
404 + result = self.af._strip_ai_instructions(content)
405 + assert "more repos with press correlation" not in result
406 + assert result.count("- org/repo") == 5