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
@@ -0,0 +1,203 @@
1
+ """DripStack source for last30days — premium financial newsletter search.
2
+
3
+ DripStack indexes paid Substack newsletters, analyst writeups, and financial
4
+ podcasts. The search endpoint is free and public (no API key); it returns
5
+ article metadata including title, publication, date, and a relevance-scored
6
+ snippet. Full article summaries and stock picks are behind a paid layer and
7
+ are out of scope for this source adapter.
8
+
9
+ The signal is complementary to the other financial sources: StockTwits gives
10
+ retail sentiment, Polymarket gives prediction-market odds, and DripStack gives
11
+ what professional analysts and paid newsletter authors are actually writing
12
+ about. The search results carry publication attribution (e.g. "SemiAnalysis",
13
+ "Bloomberg") which is high-credibility signal for synthesis.
14
+
15
+ GATING: DripStack search is most valuable for finance, markets, company
16
+ analysis, and industry research topics. Like arXiv (science) and Techmeme
17
+ (tech news), DripStack is relevance-gated — the search API itself filters
18
+ for topic match, so off-topic runs return thin results naturally and the
19
+ engine's thin-retry + relevance scoring handles the rest.
20
+
21
+ API: public, no auth. Search endpoint returns up to 30 items per query.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import datetime
27
+ import json
28
+ import re
29
+ import sys
30
+ import urllib.parse
31
+ from typing import Any
32
+
33
+ from . import http
34
+
35
+ _BASE_URL = "https://dripstack.xyz"
36
+ _SEARCH_URL = f"{_BASE_URL}/api/v1/search"
37
+ _UA = "Mozilla/5.0 (last30days dripstack source)"
38
+
39
+ # Depth controls how many results we request per subquery.
40
+ _DEPTH_LIMITS = {"quick": 5, "default": 10, "deep": 20}
41
+
42
+
43
+ def _log(msg: str) -> None:
44
+ try:
45
+ from . import log as _enginelog
46
+ _enginelog.source_log("DripStack", msg, tty_only=False)
47
+ except Exception:
48
+ print(f"[DripStack] {msg}", file=sys.stderr)
49
+
50
+
51
+ def _get_json(url: str, timeout: int = 20) -> dict[str, Any]:
52
+ # All engine traffic goes through the shared lib/http.py choke point so
53
+ # capture/replay, fixtures, and failure taxonomy apply to this source too.
54
+ return http.get(url, headers={"User-Agent": _UA}, timeout=timeout, retries=2)
55
+
56
+
57
+ def search_dripstack(
58
+ topic: str,
59
+ from_date: str | None = None,
60
+ to_date: str | None = None,
61
+ *,
62
+ depth: str = "default",
63
+ ) -> list[dict[str, Any]]:
64
+ """Search DripStack for articles matching the topic.
65
+
66
+ Returns a list of raw item dicts from the search API. The free endpoint
67
+ requires no authentication. Results are relevance-ranked by DripStack's
68
+ own scoring (hybrid RRF — blended semantic + keyword match).
69
+
70
+ Args:
71
+ topic: The search query (e.g. "AI capex risk", "Tesla earnings").
72
+ from_date: ISO date string for start of window (YYYY-MM-DD). Not sent
73
+ to the API (DripStack search has its own time handling), but
74
+ available for post-filtering if needed.
75
+ to_date: ISO date string for end of window (YYYY-MM-DD).
76
+ depth: One of "quick", "default", "deep" — controls result count.
77
+ """
78
+ limit = _DEPTH_LIMITS.get(depth, 10)
79
+ params = urllib.parse.urlencode({"q": topic, "limit": limit})
80
+ url = f"{_SEARCH_URL}?{params}"
81
+
82
+ try:
83
+ data = _get_json(url)
84
+ except Exception as e:
85
+ _log(f"search failed for '{topic}': {e}")
86
+ return []
87
+
88
+ items = data.get("items") or []
89
+ if from_date or to_date:
90
+ windowed = []
91
+ dropped = 0
92
+ for item in items:
93
+ published = str(item.get("publishedAt") or "")[:10]
94
+ if published and from_date and published < from_date:
95
+ dropped += 1
96
+ continue
97
+ if published and to_date and published > to_date:
98
+ dropped += 1
99
+ continue
100
+ windowed.append(item)
101
+ if dropped:
102
+ _log(f"dropped {dropped} result(s) outside the {from_date}..{to_date} window")
103
+ items = windowed
104
+ _log(f"search '{topic}': {len(items)} results (confidence: {data.get('matchConfidence', '?')})")
105
+ return items
106
+
107
+
108
+ def parse_dripstack_response(
109
+ items: list[dict[str, Any]],
110
+ query: str = "",
111
+ ) -> list[dict[str, Any]]:
112
+ """Normalize DripStack search results into engine-style item dicts.
113
+
114
+ Each item maps to the same shape as other sources (HN, Reddit, StockTwits):
115
+ id, title, url, author, date, engagement, relevance, why_relevant,
116
+ snippet, metadata.
117
+
118
+ DripStack has no engagement signal (upvotes, likes), so engagement is
119
+ empty. Ranking relies on DripStack's own relevanceScore (0-100) which we
120
+ normalize to 0-1, plus recency.
121
+ """
122
+ parsed: list[dict[str, Any]] = []
123
+ for i, item in enumerate(items):
124
+ title = (item.get("title") or "").strip()
125
+ subtitle = (item.get("subtitle") or "").strip()
126
+ snippet_text = (item.get("snippet") or "").strip()
127
+ pub_slug = (item.get("publicationSlug") or "").strip()
128
+ post_slug = (item.get("slug") or "").strip()
129
+ published_at = (item.get("publishedAt") or "")[:10] or None
130
+
131
+ # Build the article URL. For Substack-hosted publications the slug is
132
+ # the full hostname (e.g. "newsletter.doomberg.com") and the post slug
133
+ # is the path segment. For other domains the same pattern applies.
134
+ if pub_slug and post_slug:
135
+ url = f"https://{pub_slug}/{post_slug}"
136
+ else:
137
+ url = ""
138
+
139
+ # Normalize DripStack's 0-100 relevanceScore to 0-1 for the engine.
140
+ raw_score = item.get("relevanceScore", 0)
141
+ try:
142
+ relevance = round(min(1.0, max(0.0, float(raw_score) / 100.0)), 2)
143
+ except (TypeError, ValueError):
144
+ relevance = 0.5
145
+
146
+ # Build a human-readable why_relevant from the whyMatched array.
147
+ why_parts = item.get("whyMatched") or []
148
+ # Filter out internal RRF details; keep the useful match explanations.
149
+ why_clean = [
150
+ w for w in why_parts
151
+ if "RRF" not in w and "Hybrid" not in w
152
+ ]
153
+ why_relevant = "; ".join(why_clean) if why_clean else f"DripStack newsletter match for: {query}"
154
+
155
+ # The body feeds rerank and synthesis. Use subtitle (the article
156
+ # summary/lede) as the primary content, falling back to snippet.
157
+ body = subtitle or snippet_text or title
158
+
159
+ # Publication name as author — gives attribution credit to the
160
+ # newsletter/analyst who wrote it (e.g. "SemiAnalysis", "Bloomberg").
161
+ # Use the slug as a readable fallback.
162
+ author = pub_slug.replace(".substack.com", "").replace(".com", "")
163
+
164
+ parsed.append({
165
+ "id": f"DS{i + 1}",
166
+ "title": title or f"DripStack result {i + 1}",
167
+ "url": url,
168
+ "author": author or None,
169
+ "date": published_at,
170
+ "engagement": {},
171
+ "relevance": relevance,
172
+ "why_relevant": why_relevant,
173
+ "body": body,
174
+ "snippet": snippet_text[:400],
175
+ "metadata": {
176
+ "publication_slug": pub_slug,
177
+ "post_slug": post_slug,
178
+ "relevance_score": raw_score,
179
+ "match_confidence": item.get("matchConfidence"),
180
+ "topic_coverage_ratio": item.get("topicCoverageRatio"),
181
+ },
182
+ })
183
+ return parsed
184
+
185
+
186
+ # --------------------------------------------------------------------------- #
187
+ # Standalone CLI #
188
+ # python3 dripstack.py "AI capex risk" #
189
+ # --------------------------------------------------------------------------- #
190
+
191
+ if __name__ == "__main__":
192
+ topic = " ".join(sys.argv[1:]) or "AI capex"
193
+ today = datetime.date.today()
194
+ since = (today - datetime.timedelta(days=30)).isoformat()
195
+
196
+ raw = search_dripstack(topic, from_date=since, depth="default")
197
+ items = parse_dripstack_response(raw, query=topic)
198
+
199
+ print(f"Query: {topic} | {len(items)} results")
200
+ for it in items[:10]:
201
+ print(f" [{it['relevance']:.0%}] {it['title']} ({it['author']}, {it['date'] or 'no date'})")
202
+ if it["snippet"]:
203
+ print(f" {it['snippet'][:120]}")
@@ -13,6 +13,43 @@ GENERIC_HANDLES = {
13
13
  "verified", "jack", "sundarpichai",
14
14
  }
15
15
 
16
+ ENTITY_STOPWORDS = frozenset({
17
+ "the", "a", "an", "to", "for", "how", "is", "in", "of", "on", "and",
18
+ "with", "from", "by", "at", "this", "that", "it", "what", "are", "do",
19
+ "can", "his", "her", "he", "she", "its", "was", "has", "new", "just",
20
+ "says", "said", "will", "about", "after", "now", "all", "been", "here",
21
+ "not", "out", "up", "more", "also", "but", "who", "year", "first",
22
+ "make", "being", "making", "over", "into", "than", "they", "their",
23
+ "would", "could", "get", "got", "some", "like", "back", "going",
24
+ "breaking", "https", "http", "www", "com",
25
+ })
26
+
27
+
28
+ def has_anchor_signal(word: str) -> bool:
29
+ """True when a word carries an anchor signal: leading capital, all-caps,
30
+ or any digit (product/person/version anchors)."""
31
+ return word[0].isupper() or word.isupper() or any(char.isdigit() for char in word)
32
+
33
+
34
+ def extract_text_entities(text: str) -> set[str]:
35
+ """Extract significant words used by clustering and eval scoring."""
36
+ words = re.sub(r"[^\w\s]", " ", text).split()
37
+ entities = set()
38
+ for word in words:
39
+ lower = word.lower()
40
+ if lower in ENTITY_STOPWORDS or len(word) <= 2:
41
+ continue
42
+ if has_anchor_signal(word) or len(word) >= 4:
43
+ entities.add(lower)
44
+ return entities
45
+
46
+
47
+ def entity_overlap(entities_a: set[str], entities_b: set[str]) -> float:
48
+ """Return overlap coefficient for two extracted entity sets."""
49
+ if not entities_a or not entities_b:
50
+ return 0.0
51
+ return len(entities_a & entities_b) / min(len(entities_a), len(entities_b))
52
+
16
53
 
17
54
  def extract_entities(
18
55
  reddit_items: List[Dict[str, Any]],