luckiest-co 1.0.47 → 1.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/package.json +1 -1
  4. package/skills/ab-testing/CHANGELOG.md +5 -0
  5. package/skills/ab-testing/SKILL.md +12 -9
  6. package/skills/ab-testing/evals/evals.json +18 -7
  7. package/skills/ab-testing/references/sample-size-guide.md +2 -2
  8. package/skills/ads/CHANGELOG.md +5 -0
  9. package/skills/ads/SKILL.md +4 -4
  10. package/skills/ai-seo/CHANGELOG.md +7 -0
  11. package/skills/ai-seo/SKILL.md +27 -20
  12. package/skills/analytics/CHANGELOG.md +6 -0
  13. package/skills/analytics/SKILL.md +4 -3
  14. package/skills/analytics/evals/evals.json +27 -0
  15. package/skills/analytics/references/ga4-implementation.md +7 -3
  16. package/skills/analytics/references/gtm-implementation.md +8 -2
  17. package/skills/aso/CHANGELOG.md +5 -0
  18. package/skills/aso/SKILL.md +4 -4
  19. package/skills/aso/evals/evals.json +15 -2
  20. package/skills/aso/references/benchmarks.md +4 -2
  21. package/skills/aso/references/google-play-specs.md +5 -1
  22. package/skills/brainstorm/SKILL.md +59 -16
  23. package/skills/brainstorm/builder-check-prompt.md +23 -0
  24. package/skills/churn-prevention/CHANGELOG.md +5 -0
  25. package/skills/churn-prevention/SKILL.md +3 -3
  26. package/skills/churn-prevention/evals/evals.json +14 -0
  27. package/skills/churn-prevention/references/dunning-playbook.md +6 -1
  28. package/skills/coder-brainstorming/CHANGELOG.md +10 -0
  29. package/skills/coder-brainstorming/SKILL.md +62 -20
  30. package/skills/coder-brainstorming/builder-check-prompt.md +23 -0
  31. package/skills/coder-brainstorming/scripts/server.cjs +1 -1
  32. package/skills/coder-debugging/CHANGELOG.md +4 -0
  33. package/skills/coder-debugging/SKILL.md +5 -5
  34. package/skills/coder-requesting-code-review/CHANGELOG.md +4 -0
  35. package/skills/coder-requesting-code-review/SKILL.md +4 -4
  36. package/skills/coder-subagent-driven-development/CHANGELOG.md +4 -0
  37. package/skills/coder-subagent-driven-development/SKILL.md +3 -3
  38. package/skills/coder-subagent-driven-development/scripts/sdd-workspace +3 -1
  39. package/skills/cold-email/CHANGELOG.md +7 -0
  40. package/skills/cold-email/SKILL.md +27 -7
  41. package/skills/cold-email/evals/evals.json +30 -0
  42. package/skills/cold-email/references/benchmarks.md +15 -11
  43. package/skills/cold-email/references/deliverability.md +110 -0
  44. package/skills/cold-email/references/linkedin-outreach.md +91 -0
  45. package/skills/cold-email/references/multichannel-cadence.md +79 -0
  46. package/skills/cold-email/references/outbound-plays.md +88 -0
  47. package/skills/cold-email/references/personalization.md +11 -0
  48. package/skills/cold-email/references/reply-handling.md +65 -0
  49. package/skills/competitor-profiling/CHANGELOG.md +5 -0
  50. package/skills/competitor-profiling/SKILL.md +4 -4
  51. package/skills/content-strategy/CHANGELOG.md +5 -0
  52. package/skills/content-strategy/SKILL.md +5 -3
  53. package/skills/copy-editing/CHANGELOG.md +5 -0
  54. package/skills/copy-editing/SKILL.md +5 -5
  55. package/skills/copy-editing/references/content-refresh.md +33 -0
  56. package/skills/cro/CHANGELOG.md +5 -0
  57. package/skills/cro/SKILL.md +14 -3
  58. package/skills/cro/references/form.md +1 -1
  59. package/skills/directory-submissions/CHANGELOG.md +5 -0
  60. package/skills/directory-submissions/SKILL.md +13 -4
  61. package/skills/directory-submissions/evals/evals.json +15 -1
  62. package/skills/directory-submissions/references/directory-list.md +2 -4
  63. package/skills/emails/CHANGELOG.md +5 -0
  64. package/skills/emails/SKILL.md +14 -4
  65. package/skills/launch/CHANGELOG.md +7 -0
  66. package/skills/launch/SKILL.md +8 -4
  67. package/skills/launch/evals/evals.json +29 -0
  68. package/skills/launch/references/shipped-changes.md +84 -0
  69. package/skills/launch/references/site-launch-qa.md +55 -0
  70. package/skills/lead-magnets/CHANGELOG.md +5 -0
  71. package/skills/lead-magnets/SKILL.md +6 -4
  72. package/skills/lead-magnets/evals/evals.json +18 -0
  73. package/skills/lead-magnets/references/content-to-skill-corpus-design.md +89 -0
  74. package/skills/lead-magnets/references/content-to-skill-release.md +71 -0
  75. package/skills/lead-magnets/references/content-to-skill.md +108 -0
  76. package/skills/marketing-plan/CHANGELOG.md +5 -0
  77. package/skills/marketing-plan/SKILL.md +3 -3
  78. package/skills/marketing-plan/evals/evals.json +13 -0
  79. package/skills/marketing-plan/references/budget-planning.md +26 -17
  80. package/skills/model-router/CHANGELOG.md +6 -0
  81. package/skills/model-router/SKILL.md +3 -3
  82. package/skills/model-router/scripts/route.py +1 -1
  83. package/skills/model-router/scripts/test_route.py +5 -0
  84. package/skills/pricing/CHANGELOG.md +5 -0
  85. package/skills/pricing/SKILL.md +4 -4
  86. package/skills/pricing/evals/evals.json +12 -0
  87. package/skills/pricing/references/research-methods.md +8 -4
  88. package/skills/programmatic-seo/CHANGELOG.md +5 -0
  89. package/skills/programmatic-seo/SKILL.md +8 -4
  90. package/skills/programmatic-seo/evals/evals.json +13 -0
  91. package/skills/programmatic-seo/references/implementation-platforms.md +37 -0
  92. package/skills/prospecting/CHANGELOG.md +7 -0
  93. package/skills/prospecting/SKILL.md +27 -13
  94. package/skills/prospecting/evals/evals.json +41 -0
  95. package/skills/prospecting/references/account-research.md +59 -0
  96. package/skills/prospecting/references/b2b-prospecting.md +1 -1
  97. package/skills/prospecting/references/compliance.md +44 -6
  98. package/skills/prospecting/references/data-sources.md +13 -15
  99. package/skills/prospecting/references/enrichment-playbook.md +61 -0
  100. package/skills/prospecting/references/saas-prospecting.md +1 -1
  101. package/skills/prospecting/references/signal-plays.md +99 -0
  102. package/skills/prospecting/references/sourcing-and-enrichment.md +89 -0
  103. package/skills/referrals/CHANGELOG.md +5 -0
  104. package/skills/referrals/SKILL.md +10 -4
  105. package/skills/referrals/evals/evals.json +99 -0
  106. package/skills/referrals/references/affiliate-operations.md +64 -0
  107. package/skills/referrals/references/affiliate-programs.md +2 -2
  108. package/skills/referrals/references/program-examples.md +14 -6
  109. package/skills/referrals/references/review-platform-rules.md +36 -0
  110. package/skills/referrals/references/review-program-operations.md +62 -0
  111. package/skills/referrals/references/review-programs.md +96 -0
  112. package/skills/revops/CHANGELOG.md +5 -0
  113. package/skills/revops/SKILL.md +10 -6
  114. package/skills/revops/evals/evals.json +13 -0
  115. package/skills/revops/references/automation-playbooks.md +1 -1
  116. package/skills/revops/references/inbound-routing.md +43 -0
  117. package/skills/revops/references/lifecycle-definitions.md +31 -0
  118. package/skills/sales-enablement/CHANGELOG.md +5 -0
  119. package/skills/sales-enablement/SKILL.md +6 -4
  120. package/skills/sales-enablement/evals/evals.json +14 -0
  121. package/skills/sales-enablement/references/cold-call-scripts.md +68 -0
  122. package/skills/schema/CHANGELOG.md +5 -0
  123. package/skills/schema/SKILL.md +20 -8
  124. package/skills/schema/evals/evals.json +40 -0
  125. package/skills/schema/references/schema-examples.md +9 -15
  126. package/skills/seo-audit/CHANGELOG.md +5 -0
  127. package/skills/seo-audit/SKILL.md +10 -16
  128. package/skills/seo-audit/evals/evals.json +68 -0
  129. package/skills/seo-audit/references/international-seo.md +9 -11
  130. package/skills/seo-audit/references/local-seo-pages-and-measurement.md +39 -0
  131. package/skills/seo-audit/references/local-seo-profile-and-citations.md +41 -0
  132. package/skills/seo-audit/references/local-seo.md +86 -0
  133. package/skills/seo-audit/references/rankings-push.md +62 -0
  134. package/skills/seo-audit/references/title-tags.md +89 -0
  135. package/skills/signup/CHANGELOG.md +5 -0
  136. package/skills/signup/SKILL.md +7 -3
  137. package/skills/site-architecture/CHANGELOG.md +5 -0
  138. package/skills/site-architecture/SKILL.md +5 -4
  139. package/skills/site-architecture/evals/evals.json +25 -0
  140. package/skills/site-architecture/references/platforms-and-migration.md +84 -0
  141. package/skills/social/CHANGELOG.md +5 -0
  142. package/skills/social/SKILL.md +4 -4
  143. package/skills/trends/.skillignore +3 -0
  144. package/skills/trends/CHANGELOG.md +20 -0
  145. package/skills/trends/SKILL.md +12 -8
  146. package/skills/trends/references/agent-mode.md +36 -0
  147. package/skills/trends/references/category-peers.md +38 -0
  148. package/skills/trends/references/comparison.md +227 -0
  149. package/skills/trends/references/competitors.md +29 -0
  150. package/skills/trends/references/discovery.md +90 -0
  151. package/skills/trends/references/engine-contract.md +111 -1889
  152. package/skills/trends/references/followups.md +112 -0
  153. package/skills/trends/references/grok-bot-x.md +55 -0
  154. package/skills/trends/references/hiring-signals.md +19 -0
  155. package/skills/trends/references/library-queue.md +33 -0
  156. package/skills/trends/references/recommendations.md +86 -0
  157. package/skills/trends/references/research-runbook.md +781 -0
  158. package/skills/trends/references/runtime.md +165 -0
  159. package/skills/trends/references/save-html-brief.md +39 -20
  160. package/skills/trends/references/setup-wizard.md +253 -0
  161. package/skills/trends/references/synthesis.md +289 -0
  162. package/skills/trends/scripts/box_chrome_login.py +209 -0
  163. package/skills/trends/scripts/briefing.py +9 -17
  164. package/skills/trends/scripts/last30days.py +3247 -280
  165. package/skills/trends/scripts/lib/agentcookie.py +196 -0
  166. package/skills/trends/scripts/lib/amazon.py +877 -0
  167. package/skills/trends/scripts/lib/arxiv.py +42 -8
  168. package/skills/trends/scripts/lib/backends.py +769 -0
  169. package/skills/trends/scripts/lib/bird_x.py +340 -57
  170. package/skills/trends/scripts/lib/bluesky.py +84 -10
  171. package/skills/trends/scripts/lib/bounded_get.py +156 -0
  172. package/skills/trends/scripts/lib/brightdata.py +291 -0
  173. package/skills/trends/scripts/lib/categories.py +7 -1
  174. package/skills/trends/scripts/lib/chrome_cdp.py +455 -0
  175. package/skills/trends/scripts/lib/chrome_cookies.py +132 -39
  176. package/skills/trends/scripts/lib/cluster.py +31 -54
  177. package/skills/trends/scripts/lib/competitors.py +10 -1
  178. package/skills/trends/scripts/lib/cookie_extract.py +221 -44
  179. package/skills/trends/scripts/lib/cookie_paths.py +27 -0
  180. package/skills/trends/scripts/lib/corpus.py +439 -0
  181. package/skills/trends/scripts/lib/devto.py +313 -0
  182. package/skills/trends/scripts/lib/discovery_handoff.py +988 -0
  183. package/skills/trends/scripts/lib/doctor.py +2166 -0
  184. package/skills/trends/scripts/lib/dripstack.py +203 -0
  185. package/skills/trends/scripts/lib/entity_extract.py +37 -0
  186. package/skills/trends/scripts/lib/env.py +1092 -138
  187. package/skills/trends/scripts/lib/fanout.py +11 -1
  188. package/skills/trends/scripts/lib/feed.py +61 -0
  189. package/skills/trends/scripts/lib/fetch_memo.py +135 -0
  190. package/skills/trends/scripts/lib/freshness.py +566 -0
  191. package/skills/trends/scripts/lib/fusion.py +314 -28
  192. package/skills/trends/scripts/lib/github.py +438 -24
  193. package/skills/trends/scripts/lib/grok_x.py +1237 -0
  194. package/skills/trends/scripts/lib/grounding.py +129 -11
  195. package/skills/trends/scripts/lib/hackernews.py +61 -4
  196. package/skills/trends/scripts/lib/health.py +373 -0
  197. package/skills/trends/scripts/lib/hosted.py +333 -0
  198. package/skills/trends/scripts/lib/html_publish.py +34 -0
  199. package/skills/trends/scripts/lib/html_render.py +169 -3
  200. package/skills/trends/scripts/lib/http.py +1215 -70
  201. package/skills/trends/scripts/lib/instagram.py +132 -9
  202. package/skills/trends/scripts/lib/jobs.py +4 -2
  203. package/skills/trends/scripts/lib/library.py +308 -0
  204. package/skills/trends/scripts/lib/library_index.py +477 -0
  205. package/skills/trends/scripts/lib/linkedin.py +271 -24
  206. package/skills/trends/scripts/lib/log.py +5 -2
  207. package/skills/trends/scripts/lib/meta_ads.py +982 -0
  208. package/skills/trends/scripts/lib/normalize.py +382 -28
  209. package/skills/trends/scripts/lib/parallel_mcp.py +234 -0
  210. package/skills/trends/scripts/lib/permission_preflight.py +42 -3
  211. package/skills/trends/scripts/lib/perplexity.py +1080 -468
  212. package/skills/trends/scripts/lib/pipeline.py +5079 -211
  213. package/skills/trends/scripts/lib/planner.py +396 -53
  214. package/skills/trends/scripts/lib/polymarket.py +285 -23
  215. package/skills/trends/scripts/lib/prescriptions.py +432 -0
  216. package/skills/trends/scripts/lib/private_files.py +21 -0
  217. package/skills/trends/scripts/lib/providers.py +231 -10
  218. package/skills/trends/scripts/lib/quality_nudge.py +122 -37
  219. package/skills/trends/scripts/lib/reddit.py +177 -12
  220. package/skills/trends/scripts/lib/reddit_arctic.py +146 -5
  221. package/skills/trends/scripts/lib/reddit_keyless.py +121 -60
  222. package/skills/trends/scripts/lib/reddit_listing.py +215 -14
  223. package/skills/trends/scripts/lib/reddit_public.py +12 -13
  224. package/skills/trends/scripts/lib/reddit_search.py +304 -0
  225. package/skills/trends/scripts/lib/reddit_shreddit.py +51 -7
  226. package/skills/trends/scripts/lib/registers.py +134 -0
  227. package/skills/trends/scripts/lib/relevance.py +17 -0
  228. package/skills/trends/scripts/lib/render.py +2161 -271
  229. package/skills/trends/scripts/lib/rerank.py +623 -55
  230. package/skills/trends/scripts/lib/resolve.py +50 -1
  231. package/skills/trends/scripts/lib/safari_cookies.py +42 -15
  232. package/skills/trends/scripts/lib/schema.py +782 -1
  233. package/skills/trends/scripts/lib/setup_wizard.py +779 -120
  234. package/skills/trends/scripts/lib/signals.py +206 -7
  235. package/skills/trends/scripts/lib/stocktwits.py +374 -0
  236. package/skills/trends/scripts/lib/subproc.py +307 -53
  237. package/skills/trends/scripts/lib/techmeme.py +89 -64
  238. package/skills/trends/scripts/lib/telegram.py +317 -0
  239. package/skills/trends/scripts/lib/tiktok.py +13 -9
  240. package/skills/trends/scripts/lib/topic_shape.py +340 -0
  241. package/skills/trends/scripts/lib/transcribe.py +12 -3
  242. package/skills/trends/scripts/lib/trustpilot.py +256 -5
  243. package/skills/trends/scripts/lib/truthsocial.py +9 -1
  244. package/skills/trends/scripts/lib/ui.py +6 -0
  245. package/skills/trends/scripts/lib/usage.py +132 -0
  246. package/skills/trends/scripts/lib/web_search_keyless.py +63 -4
  247. package/skills/trends/scripts/lib/windows_job.py +138 -0
  248. package/skills/trends/scripts/lib/x_api.py +824 -0
  249. package/skills/trends/scripts/lib/x_envelope.py +733 -0
  250. package/skills/trends/scripts/lib/x_judge.py +292 -0
  251. package/skills/trends/scripts/lib/xai_x.py +26 -3
  252. package/skills/trends/scripts/lib/xquik.py +89 -15
  253. package/skills/trends/scripts/lib/xurl_x.py +284 -91
  254. package/skills/trends/scripts/lib/youtube_yt.py +570 -119
  255. package/skills/trends/scripts/setup-keychain.sh +12 -4
  256. package/skills/trends/scripts/setup-pass.sh +3 -0
  257. package/skills/trends/scripts/store.py +444 -50
  258. package/skills/trends/scripts/watchlist.py +38 -11
  259. package/skills/trends/scripts/x_lane_parity.py +107 -0
  260. package/skills/video-studio/CHANGELOG.md +4 -0
  261. package/skills/video-studio/SKILL.md +4 -4
  262. package/skills/video-studio/references/styles/product-flight.md +45 -0
  263. package/skills/video-studio/references/styles.md +3 -1
  264. package/skills/video-studio-motion/ATTRIBUTION.md +1 -1
  265. package/skills/video-studio-motion/CHANGELOG.md +4 -0
  266. package/skills/video-studio-motion/SKILL.md +8 -5
  267. package/skills/youtube-planner/ATTRIBUTION.md +25 -0
  268. package/skills/youtube-planner/CHANGELOG.md +12 -0
  269. package/skills/youtube-planner/SKILL.md +191 -0
  270. package/skills/youtube-planner/references/plan-template.md +57 -0
  271. package/skills/youtube-planner/references/stages.md +119 -0
  272. package/skills/youtube-planner/scripts/floor_ceiling.py +132 -0
  273. package/skills/coder-brainstorming/spec-document-reviewer-prompt.md +0 -49
  274. package/skills/trends/scripts/lib/reddit_rss.py +0 -224
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import math
6
+ from collections.abc import Iterable, Mapping
6
7
 
7
8
  from . import dates, relevance, schema
8
9
 
@@ -11,11 +12,19 @@ from . import dates, relevance, schema
11
12
  SOURCE_QUALITY = {
12
13
  "xiaohongshu": 0.7,
13
14
  "hackernews": 0.8,
15
+ "devto": 0.78,
14
16
  "youtube": 0.85,
15
17
  "digg": 0.85,
16
18
  "arxiv": 0.9,
17
19
  "techmeme": 0.85,
18
20
  "trustpilot": 0.78,
21
+ # Verified-purchase reviews on a live aggregate rating: high-quality
22
+ # buyer evidence, a notch above Trustpilot's open review model.
23
+ "amazon": 0.8,
24
+ # Paid creative is first-party marketing copy: a reliable statement of
25
+ # what the brand is pushing, but not independent evidence about it, so it
26
+ # sits below the review-backed sources and above open social.
27
+ "meta_ads": 0.72,
19
28
  "reddit": 0.6,
20
29
  "x": 0.68,
21
30
  "bluesky": 0.66,
@@ -24,6 +33,7 @@ SOURCE_QUALITY = {
24
33
  "instagram": 0.58,
25
34
  "tiktok": 0.58,
26
35
  "jobs": 0.72,
36
+ "corpus": 0.75,
27
37
  }
28
38
 
29
39
 
@@ -47,7 +57,7 @@ def local_relevance(
47
57
  # often have titles that don't keyword-match the query (e.g., "YE - FATHER
48
58
  # (feat. TRAVIS SCOTT)" doesn't match "kanye west"). The engagement signals
49
59
  # say "this is important" even when text overlap is weak.
50
- if item.source == "youtube" and item.engagement.get("views", 0) > 100_000:
60
+ if item.source == "youtube" and (item.engagement.get("views") or 0) > 100_000:
51
61
  score = max(score, 0.3)
52
62
 
53
63
  # Project-mode GitHub floor: items fetched via --github-repo are explicitly
@@ -58,6 +68,17 @@ def local_relevance(
58
68
  if "project-mode" in labels:
59
69
  score = max(score, 0.8)
60
70
 
71
+ # Grounding-exempt floor (currently Amazon): the adapter already gated
72
+ # these against the model-supplied product keyword before creating them,
73
+ # so they are relevant by construction. Their text is marketing copy plus
74
+ # buyer reviews, which rarely repeats the topic phrasing -- a "Weber
75
+ # Grills" run surfaces a product named "Spirit E-325" whose reviews talk
76
+ # about searing, not about Weber. Without the floor, correctly-retrieved
77
+ # evidence gets pruned for failing a keyword match it was never going to
78
+ # win. Mirrors the project-mode GitHub floor above.
79
+ if isinstance(item.metadata, dict) and item.metadata.get("grounding_exempt"):
80
+ score = max(score, 0.8)
81
+
61
82
  return score
62
83
 
63
84
 
@@ -68,6 +89,11 @@ def freshness(
68
89
  reference_date: str | None = None,
69
90
  max_days: int = 30,
70
91
  ) -> int:
92
+ # A live repo snapshot dated to the window end (no in-window release) has a
93
+ # read time, not an event date: rank it at the scale's midpoint instead of
94
+ # maximum freshness, or every pinned repo card sorts to #1.
95
+ if isinstance(item.metadata, dict) and item.metadata.get("date_is_snapshot"):
96
+ return 50
71
97
  score = dates.recency_score(
72
98
  item.published_at,
73
99
  max_days=max_days,
@@ -109,6 +135,7 @@ def _top_comment_score(item: schema.SourceItem) -> float:
109
135
  _VOTE_LOG_REFERENCE: dict[str, float] = {
110
136
  "reddit": 7.6, # ~log1p(2000)
111
137
  "hackernews": 6.2, # ~log1p(500)
138
+ "devto": 3.0, # ~log1p(20) direct replies; the API has no comment likes
112
139
  "youtube": 10.3, # ~log1p(30000)
113
140
  "tiktok": 10.3, # ~log1p(30000)
114
141
  "instagram": 9.2, # ~log1p(10000)
@@ -161,11 +188,18 @@ ENGAGEMENT_WEIGHTS: dict[str, list[tuple[str, float]]] = {
161
188
  "x": [("likes", 0.55), ("reposts", 0.25), ("replies", 0.15), ("quotes", 0.05)],
162
189
  "instagram": [("views", 0.50), ("likes", 0.30), ("comments", 0.20)],
163
190
  "hackernews": [("points", 0.55), ("comments", 0.45)],
191
+ "devto": [("reactions", 0.60), ("comments", 0.40)],
164
192
  "bluesky": [("likes", 0.40), ("reposts", 0.30), ("replies", 0.20), ("quotes", 0.10)],
165
193
  "truthsocial": [("likes", 0.45), ("reposts", 0.30), ("replies", 0.25)],
166
194
  "polymarket": [("volume", 0.60), ("liquidity", 0.40)],
167
195
  "digg": [("postCount", 0.40), ("uniqueAuthors", 0.30), ("rank_score", 0.30)],
168
196
  "trustpilot": [("reviews", 1.0)],
197
+ "amazon": [("ratings", 1.0)],
198
+ # Meta publishes reach and spend only for political and issue ads, so a
199
+ # commercial creative has no audience signal at all. How many variants a
200
+ # brand cut of one creative is the closest honest proxy for how hard it is
201
+ # pushing that message.
202
+ "meta_ads": [("variants", 1.0)],
169
203
  }
170
204
 
171
205
 
@@ -209,6 +243,19 @@ def _tiktok_engagement(item: schema.SourceItem) -> float | None:
209
243
  return (0.45 * views) + (0.27 * likes) + (0.18 * comments) + (0.10 * top_comment)
210
244
 
211
245
 
246
+ def _instagram_engagement(item: schema.SourceItem) -> float | None:
247
+ # Mirrors _tiktok_engagement: reels are video-shaped, and a highly-liked top
248
+ # comment carves out 10% of the signal (via comment_like_count -> score) so
249
+ # crowd-loved IG comments lift their post's ranking like YouTube/TikTok.
250
+ views = log1p_safe(item.engagement.get("views"))
251
+ likes = log1p_safe(item.engagement.get("likes"))
252
+ comments = log1p_safe(item.engagement.get("comments"))
253
+ top_comment = _top_comment_score(item)
254
+ if not any([views, likes, comments, top_comment]):
255
+ return None
256
+ return (0.45 * views) + (0.27 * likes) + (0.18 * comments) + (0.10 * top_comment)
257
+
258
+
212
259
  def _generic_engagement(item: schema.SourceItem) -> float | None:
213
260
  if not item.engagement:
214
261
  return None
@@ -225,12 +272,120 @@ def engagement_raw(item: schema.SourceItem) -> float | None:
225
272
  return _youtube_engagement(item)
226
273
  if item.source == "tiktok":
227
274
  return _tiktok_engagement(item)
275
+ if item.source == "instagram":
276
+ return _instagram_engagement(item)
228
277
  weights = ENGAGEMENT_WEIGHTS.get(item.source)
229
278
  if weights:
230
279
  return _weighted_engagement(item, weights)
231
280
  return _generic_engagement(item)
232
281
 
233
282
 
283
+ # Per-source log reference for engagement_score, in each source's own units.
284
+ # Same convention as _VOTE_LOG_REFERENCE (which it extends): the value is
285
+ # engagement_raw for a "very high" item, so that item scores 100 and, the scale
286
+ # being logarithmic, roughly its square root scores 50. Every source that can
287
+ # reach engagement_raw is listed here or in _NO_ENGAGEMENT_SCORE_SOURCES; an
288
+ # unlisted source gets no score rather than an assumed Reddit-vote scale.
289
+ _ENGAGEMENT_LOG_REFERENCE: dict[str, float] = {
290
+ **_VOTE_LOG_REFERENCE,
291
+ # Microblog likes/reposts/replies run on X's scale (~10k likes is very high).
292
+ "truthsocial": 9.2, # ~log1p(10000)
293
+ "threads": 9.2, # ~log1p(10000)
294
+ # Likes/comments/favorites on an Instagram-like feed.
295
+ "xiaohongshu": 9.2, # ~log1p(10000)
296
+ # Saves on a viral pin reach ~10k.
297
+ "pinterest": 9.2, # ~log1p(10000)
298
+ # Reactions run like Reddit upvotes: a viral post has ~2k.
299
+ "linkedin": 7.6, # ~log1p(2000)
300
+ # Per-issue/PR reactions and comments (repo snapshot cards, whose counts
301
+ # are lifetime stars, get no score: see is_repo_snapshot). A
302
+ # ~100-reaction thread lands near 50.
303
+ "github": 9.2, # ~log1p(10000)
304
+ # Dollars, not votes. polymarket.py's own saturation anchors: $9M volume
305
+ # (log1p 16) and $1.2M liquidity (log1p 14), weighted 0.6/0.4 as in
306
+ # ENGAGEMENT_WEIGHTS: 0.6*16 + 0.4*14 = 15.2.
307
+ "polymarket": 15.2,
308
+ # A rank-1 cluster (rank_score 50) with ~100 posts from ~100 authors:
309
+ # 0.4*log1p(100) + 0.3*log1p(100) + 0.3*log1p(50) = 4.4.
310
+ "digg": 4.4,
311
+ # Lifetime totals for a whole company or product, not one post: big
312
+ # consumer brands and best-sellers carry ~100k, so ~300 lands near 50.
313
+ "trustpilot": 11.5, # ~log1p(100000) reviews
314
+ "amazon": 11.5, # ~log1p(100000) ratings
315
+ }
316
+
317
+ # Sources whose engagement fields are not an audience response, so no
318
+ # engagement score is comparable with votes or likes:
319
+ # jobs - a constant open_roles=1 per posting
320
+ # meta_ads - variant count measures the advertiser's push, not reach
321
+ # grounding, perplexity - web results; counts are citations, not readers
322
+ # stocktwits - the author's follower count dominates sparse likes/reshares
323
+ # telegram - the channel's subscriber count dominates per-post views
324
+ # arxiv, techmeme, dripstack, corpus - no engagement fields at all
325
+ _NO_ENGAGEMENT_SCORE_SOURCES = frozenset({
326
+ "jobs", "meta_ads", "grounding", "perplexity", "stocktwits", "telegram",
327
+ "arxiv", "techmeme", "dripstack", "corpus",
328
+ })
329
+
330
+
331
+ # YouTube, TikTok, and Instagram weight log1p(views) at 0.45 in
332
+ # engagement_raw, and views run orders of magnitude above votes: any upload
333
+ # collects ~100 passive feed views, so a 117-view, 1-like video already had raw
334
+ # 2.4 (23 on the vote-style 10.3 reference, three times a 3-like X post).
335
+ # These sources map raw linearly from a floor (an upload with ~100 views and no
336
+ # reactions: 0.45*log1p(100) = 2.1) to a "very high" month-window post (~2-3M
337
+ # views, ~100k likes, ~3k comments: 0.45*14.9 + 0.32*11.5 + 0.13*8.0 = 11.4).
338
+ # Resulting scores: 117 views/1 like ~3, 655 views/17 likes ~17, 133k views/
339
+ # 8.4k likes ~70, 726k views/13k likes ~85, 1M+ views ~90-100.
340
+ _VIEW_ENGAGEMENT_SOURCES = frozenset({"youtube", "tiktok", "instagram"})
341
+ _VIEW_ENGAGEMENT_LOG_FLOOR = 2.1
342
+ _VIEW_ENGAGEMENT_LOG_REFERENCE = 11.5
343
+
344
+
345
+ def is_repo_snapshot(item: schema.SourceItem) -> bool:
346
+ """True for a GitHub repo card (project mode, person-mode repo items).
347
+
348
+ Their engagement is lifetime stars and the open-issue count, a snapshot
349
+ of the repo's whole history rather than an audience response inside the
350
+ window. Issues and PRs carry reactions/comments, never stars.
351
+ """
352
+ return (
353
+ item.source == "github"
354
+ and "stars" in (item.engagement or {})
355
+ and not (item.metadata or {}).get("is_pr")
356
+ )
357
+
358
+
359
+ def engagement_score(item: schema.SourceItem) -> int | None:
360
+ """Engagement on an absolute 0-100 scale, comparable across streams.
361
+
362
+ ``engagement_raw`` is a weighted mean of log1p counts, so dividing it by
363
+ the source's log reference (``_ENGAGEMENT_LOG_REFERENCE``, in that
364
+ source's own units) places a 250-upvote Reddit thread near 50 and a 3-like
365
+ X post near 8 no matter what else shares their stream. The per-stream
366
+ min-max rescale this replaces mapped every stream's best item to 100 and
367
+ a lone item to 50, so the score said nothing about actual engagement.
368
+ View-weighted sources (YouTube, TikTok, Instagram) use the view scale
369
+ (``_VIEW_ENGAGEMENT_LOG_FLOOR`` to ``_VIEW_ENGAGEMENT_LOG_REFERENCE``)
370
+ instead of their vote reference. Sources without a comparable audience
371
+ signal, and GitHub repo snapshots (lifetime stars), return None.
372
+ """
373
+ if item.source in _NO_ENGAGEMENT_SCORE_SOURCES or is_repo_snapshot(item):
374
+ return None
375
+ ref = _ENGAGEMENT_LOG_REFERENCE.get(item.source)
376
+ if ref is None:
377
+ return None
378
+ raw = engagement_raw(item)
379
+ if raw is None:
380
+ return None
381
+ if item.source in _VIEW_ENGAGEMENT_SOURCES:
382
+ floor = _VIEW_ENGAGEMENT_LOG_FLOOR
383
+ fraction = (raw - floor) / (_VIEW_ENGAGEMENT_LOG_REFERENCE - floor)
384
+ else:
385
+ fraction = raw / ref
386
+ return int(round(100.0 * max(0.0, min(1.0, fraction))))
387
+
388
+
234
389
  def normalize(values: list[float | None]) -> list[int | None]:
235
390
  valid = [value for value in values if value is not None]
236
391
  if not valid:
@@ -256,8 +411,8 @@ def annotate_stream(
256
411
  ) -> list[schema.SourceItem]:
257
412
  """Attach local scoring metadata and return items sorted by local_rank_score."""
258
413
  prepared_query = ranking_query if isinstance(ranking_query, relevance.PreparedQuery) else relevance.PreparedQuery(ranking_query)
259
- engagement_scores = normalize([engagement_raw(item) for item in items])
260
- for item, eng_score in zip(items, engagement_scores, strict=True):
414
+ for item in items:
415
+ eng_score = engagement_score(item)
261
416
  item.local_relevance = local_relevance(item, prepared_query)
262
417
  item.freshness = freshness(
263
418
  item,
@@ -295,23 +450,60 @@ def _passes_engagement_floor(item: schema.SourceItem, sole_source: bool) -> bool
295
450
  return True
296
451
  if sole_source:
297
452
  return True
298
- views = item.engagement.get("views", 0) if item.engagement else 0
453
+ views = item.engagement.get("views") or 0 if item.engagement else 0
299
454
  return views >= _VIDEO_ENGAGEMENT_FLOOR_VIEWS
300
455
 
301
456
 
302
457
  def prune_low_relevance(
303
458
  items: list[schema.SourceItem],
304
459
  minimum: float = 0.15,
460
+ first_party_handles: Iterable[str] | None = None,
461
+ first_party_by_source: Mapping[str, Iterable[str]] | None = None,
305
462
  ) -> list[schema.SourceItem]:
306
463
  """Drop weak lexical matches when stronger evidence exists.
307
464
 
308
- Social-source items with zero engagement get a stricter threshold
309
- because zero engagement on a social platform is a strong noise signal.
465
+ Social-source items with genuinely zero engagement get a stricter
466
+ threshold because zero engagement on a social platform is a strong noise
467
+ signal.
310
468
 
311
469
  TikTok and Instagram items with fewer than 1000 views are pruned
312
470
  (unless they are the only source represented in the batch).
471
+
472
+ ``first_party_handles`` names accounts this run is explicitly searching
473
+ (the subject of the topic). Their own posts are exempt from the floor: a
474
+ post almost never contains its own author's name, so lexical relevance
475
+ scores it at or near zero no matter how on-topic it is. Without the
476
+ exemption a mixed batch loses them silently, because the ``filtered or
477
+ items`` rescue below only fires when *every* item fails.
478
+
479
+ ``first_party_by_source`` scopes exemptions to one platform each (e.g.
480
+ ``--ig-creators`` names Instagram accounts). A creator flag must not
481
+ exempt a same-name account on a *different* platform: username reuse
482
+ across platforms is common, and an unrelated account would otherwise
483
+ bypass the relevance and engagement floors.
313
484
  """
314
485
  sources_present = {item.source for item in items}
486
+ first_party = {
487
+ h.strip().lstrip("@").lower()
488
+ for h in (first_party_handles or ())
489
+ if h and h.strip()
490
+ }
491
+ scoped_first_party = {
492
+ source: {
493
+ h.strip().lstrip("@").lower()
494
+ for h in handles
495
+ if h and h.strip()
496
+ }
497
+ for source, handles in (first_party_by_source or {}).items()
498
+ }
499
+
500
+ def _is_first_party(item: schema.SourceItem) -> bool:
501
+ if not item.author:
502
+ return False
503
+ author = item.author.strip().lstrip("@").lower()
504
+ if author in first_party:
505
+ return True
506
+ return author in scoped_first_party.get(item.source, frozenset())
315
507
 
316
508
  def passes(item: schema.SourceItem) -> bool:
317
509
  # YouTube items with successfully extracted transcripts should not
@@ -319,10 +511,17 @@ def prune_low_relevance(
319
511
  # already proves substantive topical coverage.
320
512
  if item.source == "youtube" and item.snippet:
321
513
  return True
514
+ # Posts by an account this run is explicitly searching are evidence by
515
+ # provenance, not by lexical overlap with the topic.
516
+ if _is_first_party(item):
517
+ return True
322
518
  rel = item.local_relevance if item.local_relevance is not None else 0.0
323
519
  if rel < minimum:
324
520
  return False
325
- if item.source in _SOCIAL_SOURCES and (item.engagement_score is None or item.engagement_score == 0):
521
+ # Key the stricter social gate on genuinely absent engagement (no
522
+ # counts at all), not on a rounded score: a handful of likes is still
523
+ # engagement even where the 0-100 scale rounds it low.
524
+ if item.source in _SOCIAL_SOURCES and not engagement_raw(item):
326
525
  if rel < minimum * 1.5:
327
526
  return False
328
527
  sole_source = sources_present == {item.source}