opencode-skills-collection 3.0.51 → 3.1.1

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 (287) hide show
  1. package/README.md +44 -12
  2. package/bundled-skills/.antigravity-install-manifest.json +84 -1
  3. package/bundled-skills/android-ui-journey-testing/SKILL.md +191 -0
  4. package/bundled-skills/ask-matt/SKILL.md +92 -0
  5. package/bundled-skills/bugs-are-annoying/SKILL.md +137 -0
  6. package/bundled-skills/codebase-design/DEEPENING.md +37 -0
  7. package/bundled-skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
  8. package/bundled-skills/codebase-design/SKILL.md +145 -0
  9. package/bundled-skills/competitor-analysis/LICENSE.txt +21 -0
  10. package/bundled-skills/competitor-analysis/SKILL.md +434 -0
  11. package/bundled-skills/competitor-analysis/references/battle-card-subagent.md +127 -0
  12. package/bundled-skills/competitor-analysis/references/battle-card.md +91 -0
  13. package/bundled-skills/competitor-analysis/references/example-research.md +130 -0
  14. package/bundled-skills/competitor-analysis/references/report-template.html +127 -0
  15. package/bundled-skills/competitor-analysis/references/research-patterns.md +217 -0
  16. package/bundled-skills/competitor-analysis/references/workflow.md +434 -0
  17. package/bundled-skills/competitor-analysis/scripts/capture_screenshots.mjs +142 -0
  18. package/bundled-skills/competitor-analysis/scripts/compile_report.mjs +929 -0
  19. package/bundled-skills/competitor-analysis/scripts/extract_vs_names.mjs +140 -0
  20. package/bundled-skills/competitor-analysis/scripts/gate_candidates.mjs +194 -0
  21. package/bundled-skills/competitor-analysis/scripts/list_urls.mjs +90 -0
  22. package/bundled-skills/competitor-analysis/scripts/md_utils.mjs +50 -0
  23. package/bundled-skills/competitor-analysis/scripts/merge_partials.mjs +291 -0
  24. package/bundled-skills/competitor-analysis/scripts/package.json +6 -0
  25. package/bundled-skills/design-it/3d-ui/SKILL.md +259 -0
  26. package/bundled-skills/design-it/SKILL.md +170 -0
  27. package/bundled-skills/design-it/ai-native-ui/SKILL.md +295 -0
  28. package/bundled-skills/design-it/aurora-ui/SKILL.md +307 -0
  29. package/bundled-skills/design-it/bento-ui/SKILL.md +314 -0
  30. package/bundled-skills/design-it/brutalism/SKILL.md +270 -0
  31. package/bundled-skills/design-it/brutalist-typography/SKILL.md +287 -0
  32. package/bundled-skills/design-it/card-based-design/SKILL.md +262 -0
  33. package/bundled-skills/design-it/claymorphism/SKILL.md +287 -0
  34. package/bundled-skills/design-it/color-blocking/SKILL.md +278 -0
  35. package/bundled-skills/design-it/command-center-ui/SKILL.md +345 -0
  36. package/bundled-skills/design-it/cyber-y2k/SKILL.md +312 -0
  37. package/bundled-skills/design-it/cyberpunk-ui/SKILL.md +262 -0
  38. package/bundled-skills/design-it/dark-mode/SKILL.md +289 -0
  39. package/bundled-skills/design-it/dashboard-design/SKILL.md +331 -0
  40. package/bundled-skills/design-it/data-dense-design/SKILL.md +322 -0
  41. package/bundled-skills/design-it/duotone-design/SKILL.md +248 -0
  42. package/bundled-skills/design-it/editorial-design/SKILL.md +328 -0
  43. package/bundled-skills/design-it/flat-design/SKILL.md +221 -0
  44. package/bundled-skills/design-it/flat-design-2/SKILL.md +240 -0
  45. package/bundled-skills/design-it/floating-ui/SKILL.md +299 -0
  46. package/bundled-skills/design-it/frutiger-aero/SKILL.md +274 -0
  47. package/bundled-skills/design-it/glassmorphism/SKILL.md +272 -0
  48. package/bundled-skills/design-it/gradient-design/SKILL.md +309 -0
  49. package/bundled-skills/design-it/high-contrast/SKILL.md +288 -0
  50. package/bundled-skills/design-it/holographic-ui/SKILL.md +310 -0
  51. package/bundled-skills/design-it/isometric-design/SKILL.md +228 -0
  52. package/bundled-skills/design-it/layered-design/SKILL.md +247 -0
  53. package/bundled-skills/design-it/material-design/SKILL.md +275 -0
  54. package/bundled-skills/design-it/maximalism/SKILL.md +297 -0
  55. package/bundled-skills/design-it/minimalism/SKILL.md +267 -0
  56. package/bundled-skills/design-it/monochromatic-ui/SKILL.md +296 -0
  57. package/bundled-skills/design-it/neo-brutalism/SKILL.md +270 -0
  58. package/bundled-skills/design-it/neumorphism/SKILL.md +248 -0
  59. package/bundled-skills/design-it/retro-design/SKILL.md +283 -0
  60. package/bundled-skills/design-it/retro-futurism/SKILL.md +259 -0
  61. package/bundled-skills/design-it/sci-fi-interface/SKILL.md +309 -0
  62. package/bundled-skills/design-it/skeuomorphism/SKILL.md +280 -0
  63. package/bundled-skills/design-it/soft-pastel/SKILL.md +307 -0
  64. package/bundled-skills/design-it/spatial-computing-ui/SKILL.md +300 -0
  65. package/bundled-skills/design-it/spatial-design/SKILL.md +268 -0
  66. package/bundled-skills/design-it/swiss-design/SKILL.md +293 -0
  67. package/bundled-skills/design-it/synthwave/SKILL.md +257 -0
  68. package/bundled-skills/design-it/tile-design/SKILL.md +297 -0
  69. package/bundled-skills/design-it/typography-first/SKILL.md +247 -0
  70. package/bundled-skills/design-it/vaporwave/SKILL.md +331 -0
  71. package/bundled-skills/design-it/vibrant-maximalism/SKILL.md +291 -0
  72. package/bundled-skills/design-it/widget-based-design/SKILL.md +274 -0
  73. package/bundled-skills/design-it/y2k-design/SKILL.md +268 -0
  74. package/bundled-skills/diagnosing-bugs/SKILL.md +165 -0
  75. package/bundled-skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
  76. package/bundled-skills/docs/contributors/skill-scoring.md +235 -0
  77. package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
  78. package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
  79. package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
  80. package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
  81. package/bundled-skills/docs/users/bundles.md +1 -1
  82. package/bundled-skills/docs/users/claude-code-skills.md +1 -1
  83. package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
  84. package/bundled-skills/docs/users/getting-started.md +1 -1
  85. package/bundled-skills/docs/users/kiro-integration.md +1 -1
  86. package/bundled-skills/docs/users/usage.md +4 -4
  87. package/bundled-skills/docs/users/visual-guide.md +4 -4
  88. package/bundled-skills/domain-modeling/ADR-FORMAT.md +47 -0
  89. package/bundled-skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  90. package/bundled-skills/domain-modeling/SKILL.md +105 -0
  91. package/bundled-skills/grill-me/SKILL.md +36 -0
  92. package/bundled-skills/grill-with-docs/SKILL.md +36 -0
  93. package/bundled-skills/grilling/SKILL.md +39 -0
  94. package/bundled-skills/handoff/SKILL.md +45 -0
  95. package/bundled-skills/image-generator/.env.example +7 -0
  96. package/bundled-skills/image-generator/SKILL.md +509 -0
  97. package/bundled-skills/improve-codebase-architecture/HTML-REPORT.md +123 -0
  98. package/bundled-skills/improve-codebase-architecture/SKILL.md +97 -0
  99. package/bundled-skills/learn/SKILL.md +156 -0
  100. package/bundled-skills/lesson-generator/SKILL.md +90 -0
  101. package/bundled-skills/llm-council/.env.example +7 -0
  102. package/bundled-skills/llm-council/SKILL.md +602 -0
  103. package/bundled-skills/loop-library/SKILL.md +205 -0
  104. package/bundled-skills/loop-library/agents/openai.yaml +4 -0
  105. package/bundled-skills/loop-library/references/catalog.md +270 -0
  106. package/bundled-skills/mailtrap-managing-contacts/SKILL.md +112 -0
  107. package/bundled-skills/mailtrap-sending-emails/SKILL.md +167 -0
  108. package/bundled-skills/mailtrap-setting-up-sending-domain/SKILL.md +77 -0
  109. package/bundled-skills/mailtrap-testing-with-sandbox/SKILL.md +110 -0
  110. package/bundled-skills/prototype/LOGIC.md +79 -0
  111. package/bundled-skills/prototype/SKILL.md +62 -0
  112. package/bundled-skills/prototype/UI.md +112 -0
  113. package/bundled-skills/setup-matt-pocock-skills/SKILL.md +158 -0
  114. package/bundled-skills/setup-matt-pocock-skills/domain.md +51 -0
  115. package/bundled-skills/setup-matt-pocock-skills/issue-tracker-github.md +34 -0
  116. package/bundled-skills/setup-matt-pocock-skills/issue-tracker-gitlab.md +35 -0
  117. package/bundled-skills/setup-matt-pocock-skills/issue-tracker-local.md +19 -0
  118. package/bundled-skills/setup-matt-pocock-skills/triage-labels.md +15 -0
  119. package/bundled-skills/survey-generator/LICENSE +21 -0
  120. package/bundled-skills/survey-generator/SKILL.md +143 -0
  121. package/bundled-skills/survey-generator/build_artifact.py +208 -0
  122. package/bundled-skills/survey-generator/examples/agentic-engineering/research_bundle.json +1196 -0
  123. package/bundled-skills/survey-generator/examples/agentic-engineering/survey.html +706 -0
  124. package/bundled-skills/survey-generator/style_spec.json +85 -0
  125. package/bundled-skills/survey-generator/templates/research_bundle_template.json +69 -0
  126. package/bundled-skills/tdd/SKILL.md +139 -0
  127. package/bundled-skills/tdd/mocking.md +59 -0
  128. package/bundled-skills/tdd/refactoring.md +10 -0
  129. package/bundled-skills/tdd/tests.md +61 -0
  130. package/bundled-skills/teach/GLOSSARY-FORMAT.md +35 -0
  131. package/bundled-skills/teach/LEARNING-RECORD-FORMAT.md +46 -0
  132. package/bundled-skills/teach/MISSION-FORMAT.md +31 -0
  133. package/bundled-skills/teach/RESOURCES-FORMAT.md +32 -0
  134. package/bundled-skills/teach/SKILL.md +169 -0
  135. package/bundled-skills/to-issues/SKILL.md +115 -0
  136. package/bundled-skills/to-prd/SKILL.md +104 -0
  137. package/bundled-skills/tools-page-seo-optimizer/SKILL.md +616 -0
  138. package/bundled-skills/triage/AGENT-BRIEF.md +207 -0
  139. package/bundled-skills/triage/OUT-OF-SCOPE.md +105 -0
  140. package/bundled-skills/triage/SKILL.md +143 -0
  141. package/bundled-skills/vibecode-production-qa-validator/SKILL.md +371 -141
  142. package/bundled-skills/wiki-builder/SKILL.md +157 -0
  143. package/bundled-skills/wiki-builder/agents/openai.yaml +5 -0
  144. package/bundled-skills/wiki-builder/references/wiki-flavors.md +98 -0
  145. package/bundled-skills/wiki-builder/scripts/init_wiki.sh +105 -0
  146. package/bundled-skills/wiki-builder/templates/index.md +20 -0
  147. package/bundled-skills/wiki-builder/templates/maintenance-log.md +7 -0
  148. package/bundled-skills/wiki-builder/templates/prompts/compile-concept-page.md +12 -0
  149. package/bundled-skills/wiki-builder/templates/prompts/compile-index.md +11 -0
  150. package/bundled-skills/wiki-builder/templates/prompts/compile-source-page.md +12 -0
  151. package/bundled-skills/wiki-builder/templates/prompts/lint-wiki.md +10 -0
  152. package/bundled-skills/wiki-builder/templates/prompts/query-and-file.md +11 -0
  153. package/bundled-skills/wiki-builder/templates/sources.md +9 -0
  154. package/bundled-skills/wiki-builder/templates/wiki.config.md +53 -0
  155. package/bundled-skills/writing-great-skills/GLOSSARY.md +181 -0
  156. package/bundled-skills/writing-great-skills/SKILL.md +111 -0
  157. package/bundled-skills/yao-meta-skill/SKILL.md +86 -0
  158. package/bundled-skills/yao-meta-skill/agents/interface.yaml +26 -0
  159. package/bundled-skills/yao-meta-skill/manifest.json +24 -0
  160. package/bundled-skills/yao-meta-skill/references/artifact-design-doctrine.md +49 -0
  161. package/bundled-skills/yao-meta-skill/references/authoring-discipline.md +78 -0
  162. package/bundled-skills/yao-meta-skill/references/autonomous-adaptation.md +65 -0
  163. package/bundled-skills/yao-meta-skill/references/distribution-registry-method.md +60 -0
  164. package/bundled-skills/yao-meta-skill/references/eval-playbook.md +69 -0
  165. package/bundled-skills/yao-meta-skill/references/gate-selection.md +68 -0
  166. package/bundled-skills/yao-meta-skill/references/governance.md +134 -0
  167. package/bundled-skills/yao-meta-skill/references/human-review-template.md +54 -0
  168. package/bundled-skills/yao-meta-skill/references/intent-dialogue.md +138 -0
  169. package/bundled-skills/yao-meta-skill/references/iteration-philosophy.md +30 -0
  170. package/bundled-skills/yao-meta-skill/references/non-skill-decision-tree.md +39 -0
  171. package/bundled-skills/yao-meta-skill/references/operating-modes.md +107 -0
  172. package/bundled-skills/yao-meta-skill/references/output-eval-method.md +113 -0
  173. package/bundled-skills/yao-meta-skill/references/output-quality-risk.md +41 -0
  174. package/bundled-skills/yao-meta-skill/references/output-visual-quality.md +53 -0
  175. package/bundled-skills/yao-meta-skill/references/packaging-contracts.md +70 -0
  176. package/bundled-skills/yao-meta-skill/references/pattern-extraction-doctrine.md +76 -0
  177. package/bundled-skills/yao-meta-skill/references/platform-capability-matrix.md +49 -0
  178. package/bundled-skills/yao-meta-skill/references/prompt-engineering-doctrine.md +76 -0
  179. package/bundled-skills/yao-meta-skill/references/qa-ladder.md +57 -0
  180. package/bundled-skills/yao-meta-skill/references/reference-scan.md +126 -0
  181. package/bundled-skills/yao-meta-skill/references/regression-cause-taxonomy.md +80 -0
  182. package/bundled-skills/yao-meta-skill/references/resource-boundaries.md +120 -0
  183. package/bundled-skills/yao-meta-skill/references/review-studio-method.md +87 -0
  184. package/bundled-skills/yao-meta-skill/references/review-waiver-method.md +76 -0
  185. package/bundled-skills/yao-meta-skill/references/runtime-conformance-method.md +21 -0
  186. package/bundled-skills/yao-meta-skill/references/skill-archetypes.md +86 -0
  187. package/bundled-skills/yao-meta-skill/references/skill-atlas-method.md +35 -0
  188. package/bundled-skills/yao-meta-skill/references/skill-engineering-method.md +210 -0
  189. package/bundled-skills/yao-meta-skill/references/skill-ir-method.md +41 -0
  190. package/bundled-skills/yao-meta-skill/references/skillops-decision-policy.md +53 -0
  191. package/bundled-skills/yao-meta-skill/references/systems-thinking-doctrine.md +75 -0
  192. package/bundled-skills/yao-meta-skill/references/telemetry-drift-method.md +182 -0
  193. package/bundled-skills/yao-meta-skill/references/trust-security-method.md +79 -0
  194. package/bundled-skills/yao-meta-skill/references/user-memory-policy.md +35 -0
  195. package/bundled-skills/youtube-notetaker/SKILL.md +209 -0
  196. package/bundled-skills/youtube-notetaker/reference/artifact.html +269 -0
  197. package/bundled-skills/youtube-notetaker/scripts/contact_sheet.py +53 -0
  198. package/bundled-skills/youtube-notetaker/scripts/detect_slides.sh +19 -0
  199. package/bundled-skills/youtube-notetaker/scripts/download.sh +24 -0
  200. package/bundled-skills/youtube-notetaker/scripts/extract_slides.py +43 -0
  201. package/bundled-skills/youtube-notetaker/scripts/serve.py +173 -0
  202. package/bundled-skills/youtube-notetaker/scripts/setup.sh +27 -0
  203. package/bundled-skills/youtube-notetaker/scripts/verify.sh +31 -0
  204. package/bundled-skills/youtube-notetaker/scripts/vtt_to_transcript.py +59 -0
  205. package/bundled-skills/youtube-notetaker/scripts/write_library_item.py +69 -0
  206. package/dist/skill-pointer/config-loader.d.ts +14 -0
  207. package/dist/skill-pointer/config-loader.js +30 -3
  208. package/dist/skill-pointer/content-scanner.d.ts +38 -0
  209. package/dist/skill-pointer/content-scanner.js +118 -0
  210. package/dist/skill-pointer/index.d.ts +7 -2
  211. package/dist/skill-pointer/index.js +14 -4
  212. package/dist/skill-pointer/pointer-generator.js +2 -0
  213. package/dist/skill-pointer/skill-patcher.d.ts +13 -0
  214. package/dist/skill-pointer/skill-patcher.js +99 -0
  215. package/package.json +1 -1
  216. package/skills_index.json +1956 -286
  217. package/bundled-skills/ai-md/SKILL.md +0 -523
  218. package/bundled-skills/atlas-contract/SKILL.md +0 -650
  219. package/bundled-skills/busybox-on-windows/SKILL.md +0 -40
  220. package/bundled-skills/monte-carlo-prevent/SKILL.md +0 -257
  221. package/bundled-skills/monte-carlo-prevent/references/TROUBLESHOOTING.md +0 -23
  222. package/bundled-skills/monte-carlo-prevent/references/parameters.md +0 -32
  223. package/bundled-skills/monte-carlo-prevent/references/workflows.md +0 -478
  224. package/bundled-skills/monte-carlo-push-ingestion/SKILL.md +0 -372
  225. package/bundled-skills/monte-carlo-push-ingestion/references/anomaly-detection.md +0 -87
  226. package/bundled-skills/monte-carlo-push-ingestion/references/custom-lineage.md +0 -203
  227. package/bundled-skills/monte-carlo-push-ingestion/references/direct-http-api.md +0 -207
  228. package/bundled-skills/monte-carlo-push-ingestion/references/prerequisites.md +0 -150
  229. package/bundled-skills/monte-carlo-push-ingestion/references/push-lineage.md +0 -160
  230. package/bundled-skills/monte-carlo-push-ingestion/references/push-metadata.md +0 -158
  231. package/bundled-skills/monte-carlo-push-ingestion/references/push-query-logs.md +0 -219
  232. package/bundled-skills/monte-carlo-push-ingestion/references/validation.md +0 -257
  233. package/bundled-skills/monte-carlo-push-ingestion/scripts/sample_verify.py +0 -357
  234. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_and_push_lineage.py +0 -70
  235. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_and_push_metadata.py +0 -65
  236. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_and_push_query_logs.py +0 -70
  237. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_lineage.py +0 -214
  238. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_metadata.py +0 -160
  239. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/collect_query_logs.py +0 -164
  240. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/push_lineage.py +0 -198
  241. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/push_metadata.py +0 -193
  242. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery/push_query_logs.py +0 -207
  243. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/collect_and_push_metadata.py +0 -71
  244. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/collect_and_push_query_logs.py +0 -64
  245. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/collect_metadata.py +0 -253
  246. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/collect_query_logs.py +0 -149
  247. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/push_metadata.py +0 -190
  248. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/bigquery-iceberg/push_query_logs.py +0 -208
  249. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_and_push_lineage.py +0 -83
  250. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_and_push_metadata.py +0 -77
  251. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_and_push_query_logs.py +0 -83
  252. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_lineage.py +0 -240
  253. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_metadata.py +0 -212
  254. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/collect_query_logs.py +0 -204
  255. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/push_lineage.py +0 -192
  256. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/push_metadata.py +0 -178
  257. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/databricks/push_query_logs.py +0 -200
  258. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_and_push_lineage.py +0 -119
  259. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_and_push_metadata.py +0 -119
  260. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_and_push_query_logs.py +0 -117
  261. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_lineage.py +0 -265
  262. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_metadata.py +0 -313
  263. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/collect_query_logs.py +0 -284
  264. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/push_lineage.py +0 -309
  265. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/push_metadata.py +0 -245
  266. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/hive/push_query_logs.py +0 -255
  267. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_and_push_lineage.py +0 -78
  268. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_and_push_metadata.py +0 -80
  269. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_and_push_query_logs.py +0 -88
  270. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_lineage.py +0 -235
  271. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_metadata.py +0 -219
  272. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/collect_query_logs.py +0 -239
  273. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/push_lineage.py +0 -178
  274. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/push_metadata.py +0 -178
  275. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/redshift/push_query_logs.py +0 -196
  276. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_and_push_lineage.py +0 -154
  277. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_and_push_metadata.py +0 -137
  278. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_and_push_query_logs.py +0 -137
  279. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_lineage.py +0 -349
  280. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_metadata.py +0 -329
  281. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/collect_query_logs.py +0 -254
  282. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/push_lineage.py +0 -307
  283. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/push_metadata.py +0 -228
  284. package/bundled-skills/monte-carlo-push-ingestion/scripts/templates/snowflake/push_query_logs.py +0 -248
  285. package/bundled-skills/monte-carlo-push-ingestion/scripts/test_template_sdk_usage.py +0 -340
  286. package/bundled-skills/skill-optimizer/SKILL.md +0 -271
  287. package/bundled-skills/using-superpowers/SKILL.md +0 -98
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Parses "X vs Y" patterns from `browse cloud search` result titles across discovery batch files.
4
+ // Produces a ranked list of candidate competitor names, with an example title each,
5
+ // and attempts to resolve each name to a domain from the result URL pool.
6
+ //
7
+ // Usage: node extract_vs_names.mjs <directory> [--prefix competitor] [--seed "Exa,Tavily,SerpAPI"]
8
+ //
9
+ // Output: newline-delimited JSON to stdout, one object per candidate:
10
+ // { "name": "serper", "hits": 3, "domain": "serper.dev", "example": "Tavily vs Serper..." }
11
+
12
+ import { readdirSync, readFileSync } from 'fs';
13
+ import { join } from 'path';
14
+
15
+ const args = process.argv.slice(2);
16
+
17
+ if (args.includes('--help') || args.includes('-h') || args.length === 0) {
18
+ console.error(`Usage: node extract_vs_names.mjs <directory> [--prefix <prefix>] [--seed "<csv>"]
19
+
20
+ Reads all <prefix>_discovery_batch_*.json files, parses "X vs Y" patterns from result
21
+ titles, and outputs a ranked list of candidate competitor names as newline-delimited JSON.
22
+
23
+ Options:
24
+ --prefix <prefix> Batch file prefix (default: "competitor")
25
+ --seed "<csv>" Comma-separated list of seed names to exclude from output
26
+ (you already know these; want the OTHER side of the comparison)
27
+ --help, -h Show this help message`);
28
+ process.exit(args.includes('--help') || args.includes('-h') ? 0 : 1);
29
+ }
30
+
31
+ const dir = args[0];
32
+ const prefixIdx = args.indexOf('--prefix');
33
+ const prefix = prefixIdx !== -1 && args[prefixIdx + 1] ? args[prefixIdx + 1] : 'competitor';
34
+ const seedIdx = args.indexOf('--seed');
35
+ const seeds = seedIdx !== -1 && args[seedIdx + 1]
36
+ ? args[seedIdx + 1].split(',').map(s => s.trim().toLowerCase()).filter(Boolean)
37
+ : [];
38
+ const seedSet = new Set(seeds);
39
+
40
+ // Escape regex metacharacters in the user-supplied prefix so a value like
41
+ // "comp.+" matches the literal filename, not as a regex pattern.
42
+ const escapedPrefix = prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
43
+ const pattern = new RegExp(`^${escapedPrefix}_discovery_batch_.*\\.json$`);
44
+
45
+ let files;
46
+ try {
47
+ files = readdirSync(dir).filter(f => pattern.test(f)).sort();
48
+ } catch (err) {
49
+ console.error(`Error reading directory ${dir}: ${err.message}`);
50
+ process.exit(1);
51
+ }
52
+
53
+ if (files.length === 0) {
54
+ console.error(`No ${prefix}_discovery_batch_*.json files found in ${dir}`);
55
+ process.exit(1);
56
+ }
57
+
58
+ const allResults = [];
59
+ for (const f of files) {
60
+ try {
61
+ const d = JSON.parse(readFileSync(join(dir, f), 'utf-8'));
62
+ const rs = Array.isArray(d) ? d : d.results || [];
63
+ allResults.push(...rs);
64
+ } catch {}
65
+ }
66
+
67
+ // Build a lookup of hostname -> candidate root domain from all result URLs.
68
+ // Used later to try to resolve "serper" -> "serper.dev".
69
+ // Exclude any host whose root-base equals a seed name — otherwise a short extracted token
70
+ // like "exa" can match the user's own domain (exa.ai).
71
+ const hostMap = new Map();
72
+ for (const r of allResults) {
73
+ if (!r.url) continue;
74
+ try {
75
+ const h = new URL(r.url).hostname.replace(/^www\./, '');
76
+ const root = h.split('.').slice(-2).join('.');
77
+ const rootBase = root.split('.')[0];
78
+ if (seedSet.has(rootBase)) continue;
79
+ if (!hostMap.has(root)) hostMap.set(root, h);
80
+ } catch {}
81
+ }
82
+
83
+ // Extract names from "X vs Y" patterns.
84
+ const counts = new Map();
85
+ for (const r of allResults) {
86
+ const title = (r.title || '').toLowerCase();
87
+ const ms = [...title.matchAll(/\b([a-z][\w.\-]{2,})\s+(?:vs\.?|versus)\s+([a-z][\w.\-]{2,})/g)];
88
+ for (const m of ms) {
89
+ for (const raw of [m[1], m[2]]) {
90
+ const name = raw.replace(/[^a-z0-9.\-]/g, '').trim();
91
+ if (!name || name.length < 3) continue;
92
+ if (seedSet.has(name)) continue;
93
+ // Reject obvious non-product tokens
94
+ if (['the', 'and', 'for', 'with', 'best', 'top', 'better', 'using', 'choosing'].includes(name)) continue;
95
+ if (!counts.has(name)) counts.set(name, { name, hits: 0, example: r.title });
96
+ counts.get(name).hits += 1;
97
+ }
98
+ }
99
+ }
100
+
101
+ // Try to resolve each name to a domain.
102
+ // Strategy:
103
+ // 1. Exact match on rootBase wins outright.
104
+ // 2. Otherwise allow rootBase.startsWith(needle) ONLY when the suffix is a known
105
+ // branding token (e.g. "serp" → "serpapi.com"). Bidirectional startsWith
106
+ // was too loose: "serp" matched serpstack.com, "exa" matched example.com.
107
+ // 3. Among multiple suffix matches, prefer the shortest suffix (most specific —
108
+ // "serp" should match "serpapi" before "serpapilabs"). Deterministic.
109
+ const BRAND_SUFFIXES = ['api','search','app','ai','io','hq','co','dev','tech','cloud','agent','agents','labs','lab'];
110
+
111
+ function resolveDomain(name) {
112
+ const needle = name.replace(/\./g, '');
113
+ let exact = null;
114
+ let bestSuffix = null; // { host, suffixLen }
115
+ for (const [root, host] of hostMap.entries()) {
116
+ const rootBase = root.split('.')[0];
117
+ if (rootBase === needle) { exact = host; break; }
118
+ if (rootBase.length > needle.length && rootBase.startsWith(needle)) {
119
+ const suffix = rootBase.slice(needle.length).replace(/^[\-_]/, '');
120
+ if (BRAND_SUFFIXES.includes(suffix)) {
121
+ if (!bestSuffix || suffix.length < bestSuffix.suffixLen) {
122
+ bestSuffix = { host, suffixLen: suffix.length };
123
+ }
124
+ }
125
+ }
126
+ }
127
+ if (exact) return exact;
128
+ if (bestSuffix) return bestSuffix.host;
129
+ return null;
130
+ }
131
+
132
+ const ranked = [...counts.values()]
133
+ .map(c => ({ ...c, domain: resolveDomain(c.name) }))
134
+ .sort((a, b) => b.hits - a.hits);
135
+
136
+ for (const c of ranked) {
137
+ console.log(JSON.stringify(c));
138
+ }
139
+
140
+ console.error(`Extracted ${ranked.length} candidate names from ${files.length} batch files`);
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Category-fit gate. For each candidate URL, fetch the homepage hero via `browse cloud fetch`,
4
+ // extract visible text, and decide whether the candidate is in the same category as
5
+ // the user's company based on include/exclude keyword rules.
6
+ //
7
+ // Usage:
8
+ // cat urls.txt | node gate_candidates.mjs \
9
+ // --include "web search api,neural search,retrieval api,semantic search,search for agents" \
10
+ // --exclude "vector database,observability,analytics,enterprise search appliance,site search widget" \
11
+ // --concurrency 6
12
+ //
13
+ // Output: newline-delimited JSON to stdout with one object per URL:
14
+ // { "url": "https://foo.com", "status": "PASS" | "REJECT" | "UNKNOWN",
15
+ // "matched_includes": [...], "matched_excludes": [...], "title": "...", "hero": "..." }
16
+
17
+ import { execFile } from 'child_process';
18
+ import { promisify } from 'util';
19
+ import { readFileSync } from 'fs';
20
+
21
+ // Async execFile so the worker pool actually parallelizes. spawnSync blocks the entire
22
+ // event loop, which silently turns --concurrency N into N=1 — every URL fetched serially
23
+ // regardless of the flag. With promisified execFile, N workers can wait on N pending
24
+ // `browse cloud fetch` processes concurrently.
25
+ const execFileAsync = promisify(execFile);
26
+
27
+ const args = process.argv.slice(2);
28
+
29
+ if (args.includes('--help') || args.includes('-h')) {
30
+ console.error(`Usage: cat urls.txt | node gate_candidates.mjs [options]
31
+
32
+ Reads URLs from stdin (one per line) OR from --input <file>. For each URL, fetches
33
+ the homepage via \`browse cloud fetch --allow-redirects\`, extracts the first N chars of visible
34
+ text (the hero / tagline area), and classifies against include/exclude keyword rules.
35
+
36
+ Options:
37
+ --include "<csv>" Required. Comma-separated keywords; candidate PASSES if any match.
38
+ --exclude "<csv>" Comma-separated keywords; candidate REJECTS if any match.
39
+ --input <file> Read URLs from file instead of stdin.
40
+ --concurrency <n> Max parallel fetches (default: 6).
41
+ --hero-chars <n> Chars of visible text to examine (default: 800).
42
+ --help, -h Show this help message.`);
43
+ process.exit(args.includes('--help') || args.includes('-h') ? 0 : 1);
44
+ }
45
+
46
+ function flag(name) {
47
+ const i = args.indexOf(name);
48
+ return i !== -1 ? args[i + 1] : null;
49
+ }
50
+
51
+ const includes = (flag('--include') || '').split(',').map(s => s.trim().toLowerCase()).filter(Boolean);
52
+ const excludes = (flag('--exclude') || '').split(',').map(s => s.trim().toLowerCase()).filter(Boolean);
53
+ // Floor at 1: `--concurrency 0` or a non-numeric value makes parseInt yield 0/NaN, which would
54
+ // spawn zero workers — the script would exit "successfully" having gated nothing, making
55
+ // discovery look empty with no error. Always run at least one worker.
56
+ const concurrency = Math.max(1, parseInt(flag('--concurrency') || '6', 10) || 0);
57
+ const heroChars = parseInt(flag('--hero-chars') || '800', 10);
58
+ const inputFile = flag('--input');
59
+
60
+ if (includes.length === 0) {
61
+ console.error('Error: --include is required');
62
+ process.exit(1);
63
+ }
64
+
65
+ let urls;
66
+ if (inputFile) {
67
+ urls = readFileSync(inputFile, 'utf-8').split('\n').map(l => l.trim()).filter(Boolean);
68
+ } else {
69
+ const stdin = readFileSync(0, 'utf-8');
70
+ urls = stdin.split('\n').map(l => l.trim()).filter(Boolean);
71
+ }
72
+
73
+ if (urls.length === 0) {
74
+ console.error('Error: no URLs provided (pipe via stdin or use --input)');
75
+ process.exit(1);
76
+ }
77
+
78
+ function stripHtml(html) {
79
+ return html
80
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, ' ')
81
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, ' ')
82
+ .replace(/<[^>]*>/g, ' ')
83
+ .replace(/&amp;/g, '&')
84
+ .replace(/&lt;/g, '<')
85
+ .replace(/&gt;/g, '>')
86
+ .replace(/&quot;/g, '"')
87
+ .replace(/&#x27;/g, "'")
88
+ .replace(/&nbsp;/g, ' ')
89
+ .replace(/\s+/g, ' ')
90
+ .trim();
91
+ }
92
+
93
+ // Position-aware classification:
94
+ // 1. Exclude term in <title> → REJECT (their primary identity is the excluded category)
95
+ // 2. Include term in <title> → PASS (their primary identity matches)
96
+ // 3. Include in early hero (200ch) → PASS iff no exclude in early hero
97
+ // 4. Otherwise → REJECT (default conservative)
98
+ // Rationale: <title> is the single strongest signal of what a company sells.
99
+ // Mid/late hero mentions (e.g. "we also support web scraping use cases") shouldn't
100
+ // disqualify a real competitor that self-identifies in its title as a cloud browser.
101
+ function classify(title, heroFull, includes, excludes) {
102
+ const titleLower = (title || '').toLowerCase();
103
+ const heroLower = heroFull.toLowerCase();
104
+ const heroEarly = heroLower.slice(0, 200);
105
+
106
+ const incTitle = includes.filter(k => titleLower.includes(k));
107
+ const excTitle = excludes.filter(k => titleLower.includes(k));
108
+ const incEarly = includes.filter(k => heroEarly.includes(k));
109
+ const excEarly = excludes.filter(k => heroEarly.includes(k));
110
+ const incHero = includes.filter(k => heroLower.includes(k));
111
+ const excHero = excludes.filter(k => heroLower.includes(k));
112
+
113
+ let status, reason;
114
+ if (incTitle.length > 0 && excTitle.length > 0) {
115
+ // Hybrid-identity title (e.g. "Browser Automation & Web Scraping API").
116
+ // Break the tie by the early hero — whichever category has more mentions wins.
117
+ if (incEarly.length > excEarly.length) { status = 'PASS'; reason = `title-hybrid→hero200 leans include(${incEarly[0] || incTitle[0]})`; }
118
+ else if (excEarly.length > incEarly.length) { status = 'REJECT'; reason = `title-hybrid→hero200 leans exclude(${excEarly[0] || excTitle[0]})`; }
119
+ else { status = 'PASS'; reason = `title-hybrid→tie, defaulting include(${incTitle[0]})`; }
120
+ }
121
+ else if (excTitle.length > 0) { status = 'REJECT'; reason = `title→exclude(${excTitle[0]})`; }
122
+ else if (incTitle.length > 0) { status = 'PASS'; reason = `title→include(${incTitle[0]})`; }
123
+ else if (incEarly.length > 0 && excEarly.length === 0) { status = 'PASS'; reason = `hero200→include(${incEarly[0]})`; }
124
+ else if (excEarly.length > 0) { status = 'REJECT'; reason = `hero200→exclude(${excEarly[0]})`; }
125
+ else if (incHero.length > 0 && excHero.length === 0) { status = 'PASS'; reason = `hero→include(${incHero[0]})`; }
126
+ // Late-hero conflict: both include AND exclude appear in chars 200–800 (nothing in
127
+ // title or early hero). This is genuine ambiguous signal, not absence — return UNKNOWN
128
+ // so the candidate surfaces in the user-confirmation bucket at Step 4.5 instead of
129
+ // being silently dropped as REJECT.
130
+ else if (incHero.length > 0 && excHero.length > 0) { status = 'UNKNOWN'; reason = `hero→conflict(include:${incHero[0]}, exclude:${excHero[0]})`; }
131
+ else { status = 'REJECT'; reason = 'no category signal'; }
132
+
133
+ return {
134
+ status, reason,
135
+ matched_includes: [...new Set([...incTitle, ...incEarly, ...incHero])],
136
+ matched_excludes: [...new Set([...excTitle, ...excEarly, ...excHero])],
137
+ };
138
+ }
139
+
140
+ async function gateOne(url) {
141
+ let stdout;
142
+ try {
143
+ // --format raw returns the JSON envelope with raw HTML in `.content` (the default
144
+ // is markdown, which has no <title> tag for the position-aware classifier to read).
145
+ const r = await execFileAsync('browse', ['cloud', 'fetch', '--allow-redirects', '--format', 'raw', url], {
146
+ maxBuffer: 4 * 1024 * 1024,
147
+ timeout: 20000,
148
+ });
149
+ stdout = r.stdout;
150
+ } catch (err) {
151
+ // Non-zero exit, timeout, or spawn failure all surface here.
152
+ return { url, status: 'UNKNOWN', reason: `browse cloud fetch failed: ${err.message}`, matched_includes: [], matched_excludes: [], title: '', hero: '' };
153
+ }
154
+ let resp;
155
+ try { resp = JSON.parse(stdout); } catch {
156
+ return { url, status: 'UNKNOWN', reason: 'non-JSON response', matched_includes: [], matched_excludes: [], title: '', hero: '' };
157
+ }
158
+ const html = resp.content || '';
159
+ const titleM = html.match(/<title[^>]*>([^<]*)<\/title>/i);
160
+ const title = titleM ? titleM[1].trim() : '';
161
+ const heroFull = stripHtml(html).slice(0, heroChars);
162
+ const c = classify(title, heroFull, includes, excludes);
163
+ return {
164
+ url,
165
+ status: c.status,
166
+ reason: c.reason,
167
+ matched_includes: c.matched_includes,
168
+ matched_excludes: c.matched_excludes,
169
+ title,
170
+ hero: heroFull.slice(0, 240),
171
+ };
172
+ }
173
+
174
+ // Run with bounded concurrency
175
+ const results = [];
176
+ async function runAll() {
177
+ const queue = [...urls];
178
+ const workers = Array(Math.min(concurrency, queue.length)).fill(0).map(async () => {
179
+ while (queue.length > 0) {
180
+ const u = queue.shift();
181
+ const r = await gateOne(u);
182
+ results.push(r);
183
+ console.log(JSON.stringify(r));
184
+ }
185
+ });
186
+ await Promise.all(workers);
187
+ }
188
+
189
+ await runAll();
190
+
191
+ const pass = results.filter(r => r.status === 'PASS').length;
192
+ const reject = results.filter(r => r.status === 'REJECT').length;
193
+ const unknown = results.filter(r => r.status === 'UNKNOWN').length;
194
+ console.error(`\nGate: ${pass} PASS / ${reject} REJECT / ${unknown} UNKNOWN (of ${results.length})`);
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Deduplicates discovery URLs from `browse cloud search` JSON output files.
4
+ // Usage: node list_urls.mjs /tmp [--prefix competitor]
5
+ // Reads all {prefix}_discovery_batch_*.json files, deduplicates by domain,
6
+ // outputs one URL per line to stdout, stats to stderr.
7
+
8
+ import { readdirSync, readFileSync } from 'fs';
9
+ import { join } from 'path';
10
+
11
+ const args = process.argv.slice(2);
12
+
13
+ if (args.includes('--help') || args.includes('-h') || args.length === 0) {
14
+ console.error(`Usage: node list_urls.mjs <directory> [--prefix <prefix>]
15
+
16
+ Reads all <prefix>_discovery_batch_*.json files from <directory>,
17
+ deduplicates URLs by domain, and outputs one URL per line to stdout.
18
+
19
+ Options:
20
+ --prefix <prefix> Batch file prefix (default: "competitor")
21
+ --help, -h Show this help message
22
+
23
+ Examples:
24
+ node list_urls.mjs /tmp
25
+ node list_urls.mjs /tmp --prefix competitor`);
26
+ process.exit(args.includes('--help') || args.includes('-h') ? 0 : 1);
27
+ }
28
+
29
+ const dir = args[0];
30
+ const prefixIdx = args.indexOf('--prefix');
31
+ const prefix = prefixIdx !== -1 && args[prefixIdx + 1] ? args[prefixIdx + 1] : 'competitor';
32
+
33
+ // Escape regex metacharacters in the user-supplied prefix so a value like
34
+ // "comp.+" matches the literal filename, not as a regex pattern.
35
+ const escapedPrefix = prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
36
+ const pattern = new RegExp(`^${escapedPrefix}_discovery_batch_.*\\.json$`);
37
+
38
+ let files;
39
+ try {
40
+ files = readdirSync(dir)
41
+ .filter(f => pattern.test(f))
42
+ .sort();
43
+ } catch (err) {
44
+ console.error(`Error reading directory ${dir}: ${err.message}`);
45
+ process.exit(1);
46
+ }
47
+
48
+ if (files.length === 0) {
49
+ console.error(`No ${prefix}_discovery_batch_*.json files found in ${dir}`);
50
+ process.exit(1);
51
+ }
52
+
53
+ // Dedup by hostname, but prefer the site root over a deep link. The first search hit for a
54
+ // domain is often a blog/doc/comparison path; gating + enrichment want the homepage, so when
55
+ // multiple URLs share a host we keep the shallowest path (fewest segments). First-seen host
56
+ // order is preserved (Map.set on an existing key keeps its position).
57
+ const byDomain = new Map(); // hostname -> { url, depth }
58
+ let totalResults = 0;
59
+
60
+ for (const file of files) {
61
+ try {
62
+ const data = JSON.parse(readFileSync(join(dir, file), 'utf-8'));
63
+ const results = Array.isArray(data) ? data : (data.results || []);
64
+ totalResults += results.length;
65
+
66
+ for (const result of results) {
67
+ const url = result.url;
68
+ if (!url) continue;
69
+
70
+ try {
71
+ const u = new URL(url);
72
+ const hostname = u.hostname.replace(/^www\./, '');
73
+ const depth = u.pathname.replace(/\/+$/, '').split('/').filter(Boolean).length;
74
+ const existing = byDomain.get(hostname);
75
+ if (!existing || depth < existing.depth) byDomain.set(hostname, { url, depth });
76
+ } catch {
77
+ // Skip invalid URLs
78
+ }
79
+ }
80
+ } catch (err) {
81
+ console.error(`Warning: Failed to parse ${file}: ${err.message}`);
82
+ }
83
+ }
84
+
85
+ const urls = [...byDomain.values()].map(v => v.url);
86
+ for (const url of urls) {
87
+ console.log(url);
88
+ }
89
+
90
+ console.error(`\n${files.length} files, ${totalResults} total results, ${urls.length} unique domains`);
@@ -0,0 +1,50 @@
1
+ // Shared markdown parsing helpers for competitor-analysis scripts.
2
+ // Used by compile_report.mjs, merge_partials.mjs, and capture_screenshots.mjs.
3
+
4
+ // Parses YAML-ish frontmatter delimited by `---` lines.
5
+ // Returns an object of fields, or null if no frontmatter delimiter is found.
6
+ export function parseFrontmatter(content) {
7
+ content = content.replace(/\r\n/g, '\n'); // tolerate CRLF — anchors below assume LF
8
+ const m = content.match(/^---\n([\s\S]*?)\n---/);
9
+ if (!m) return null;
10
+ const fields = {};
11
+ for (const line of m[1].split('\n')) {
12
+ const idx = line.indexOf(':');
13
+ if (idx > 0) {
14
+ const k = line.slice(0, idx).trim();
15
+ const v = line.slice(idx + 1).trim().replace(/^["']|["']$/g, '');
16
+ if (k && v) fields[k] = v;
17
+ }
18
+ }
19
+ return fields;
20
+ }
21
+
22
+ // Returns the body text after the closing `---` of the frontmatter, trimmed.
23
+ // If no frontmatter is present, returns the full content trimmed — so callers
24
+ // that don't gate on parseFrontmatter still get usable text.
25
+ export function parseBody(content) {
26
+ content = content.replace(/\r\n/g, '\n'); // tolerate CRLF — anchors below assume LF
27
+ const m = content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)/);
28
+ return m ? m[1].trim() : content.trim();
29
+ }
30
+
31
+ // Splits a markdown body into sections keyed by `## Heading` line.
32
+ // Content before the first `## ` is dropped (matches existing behavior).
33
+ export function parseSections(body) {
34
+ const sections = {};
35
+ const lines = body.replace(/\r\n/g, '\n').split('\n');
36
+ let currentKey = null;
37
+ let buffer = [];
38
+ for (const line of lines) {
39
+ const m = line.match(/^## (.+)$/);
40
+ if (m) {
41
+ if (currentKey !== null) sections[currentKey] = buffer.join('\n').trim();
42
+ currentKey = m[1].trim();
43
+ buffer = [];
44
+ } else if (currentKey !== null) {
45
+ buffer.push(line);
46
+ }
47
+ }
48
+ if (currentKey !== null) sections[currentKey] = buffer.join('\n').trim();
49
+ return sections;
50
+ }