productfoo 0.0.0-stage → 1.0.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 (498) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +338 -2
  3. package/bin/foo +3 -0
  4. package/bin/foo.mjs +42 -0
  5. package/commands/bug.md +31 -0
  6. package/commands/discovery.md +28 -0
  7. package/commands/ideate.md +27 -0
  8. package/commands/init.md +28 -0
  9. package/commands/pitch.md +27 -0
  10. package/commands/preview.md +25 -0
  11. package/commands/research.md +31 -0
  12. package/commands/roast.md +27 -0
  13. package/commands/spec.md +30 -0
  14. package/config/pricing.yaml +198 -0
  15. package/config/routing-policy.yaml +171 -0
  16. package/dist-public/src/activeModel.d.ts +83 -0
  17. package/dist-public/src/activeModel.js +331 -0
  18. package/dist-public/src/agentAdapters.d.ts +122 -0
  19. package/dist-public/src/agentAdapters.js +178 -0
  20. package/dist-public/src/agentState.d.ts +43 -0
  21. package/dist-public/src/agentState.js +177 -0
  22. package/dist-public/src/bugCapsule.d.ts +94 -0
  23. package/dist-public/src/bugCapsule.js +227 -0
  24. package/dist-public/src/bugCapture.d.ts +26 -0
  25. package/dist-public/src/bugCapture.js +251 -0
  26. package/dist-public/src/bugOwnerLog.d.ts +93 -0
  27. package/dist-public/src/bugOwnerLog.js +304 -0
  28. package/dist-public/src/bugReportStore.d.ts +48 -0
  29. package/dist-public/src/bugReportStore.js +860 -0
  30. package/dist-public/src/bugSpecContext.d.ts +7 -0
  31. package/dist-public/src/bugSpecContext.js +129 -0
  32. package/dist-public/src/channelFreshness.d.ts +25 -0
  33. package/dist-public/src/channelFreshness.js +194 -0
  34. package/dist-public/src/cli.d.ts +30 -0
  35. package/dist-public/src/cli.js +1667 -0
  36. package/dist-public/src/cliArgs.d.ts +7 -0
  37. package/dist-public/src/cliArgs.js +44 -0
  38. package/dist-public/src/codeCatalog.d.ts +41 -0
  39. package/dist-public/src/codeCatalog.js +197 -0
  40. package/dist-public/src/commandContract.d.ts +84 -0
  41. package/dist-public/src/commandContract.js +390 -0
  42. package/dist-public/src/commandUsage.d.ts +3 -0
  43. package/dist-public/src/commandUsage.js +30 -0
  44. package/dist-public/src/commands/agent.d.ts +60 -0
  45. package/dist-public/src/commands/agent.js +1353 -0
  46. package/dist-public/src/commands/asp.d.ts +8 -0
  47. package/dist-public/src/commands/asp.js +669 -0
  48. package/dist-public/src/commands/evalCmd.d.ts +4 -0
  49. package/dist-public/src/commands/evalCmd.js +405 -0
  50. package/dist-public/src/commands/login.d.ts +25 -0
  51. package/dist-public/src/commands/login.js +94 -0
  52. package/dist-public/src/commands/loop.d.ts +16 -0
  53. package/dist-public/src/commands/loop.js +619 -0
  54. package/dist-public/src/commands/reconcile.d.ts +41 -0
  55. package/dist-public/src/commands/reconcile.js +292 -0
  56. package/dist-public/src/commands/runtime.d.ts +1 -0
  57. package/dist-public/src/commands/runtime.js +36 -0
  58. package/dist-public/src/commands/scanRedact.d.ts +8 -0
  59. package/dist-public/src/commands/scanRedact.js +266 -0
  60. package/dist-public/src/commands/setup.d.ts +28 -0
  61. package/dist-public/src/commands/setup.js +398 -0
  62. package/dist-public/src/commands/shared.d.ts +17 -0
  63. package/dist-public/src/commands/shared.js +147 -0
  64. package/dist-public/src/commands/source.d.ts +1 -0
  65. package/dist-public/src/commands/source.js +134 -0
  66. package/dist-public/src/commands/specCmd.d.ts +24 -0
  67. package/dist-public/src/commands/specCmd.js +794 -0
  68. package/dist-public/src/commands/update.d.ts +61 -0
  69. package/dist-public/src/commands/update.js +244 -0
  70. package/dist-public/src/comprehensionSignal.d.ts +20 -0
  71. package/dist-public/src/comprehensionSignal.js +166 -0
  72. package/dist-public/src/conductor/diversity.d.ts +48 -0
  73. package/dist-public/src/conductor/diversity.js +423 -0
  74. package/dist-public/src/conductor/engine.d.ts +7 -0
  75. package/dist-public/src/conductor/engine.js +295 -0
  76. package/dist-public/src/conductor/families/ideate.d.ts +2 -0
  77. package/dist-public/src/conductor/families/ideate.js +255 -0
  78. package/dist-public/src/conductor/families/interview.d.ts +2 -0
  79. package/dist-public/src/conductor/families/interview.js +39 -0
  80. package/dist-public/src/conductor/families/specSwarm.d.ts +2 -0
  81. package/dist-public/src/conductor/families/specSwarm.js +146 -0
  82. package/dist-public/src/conductor/familyDelivery.d.ts +6 -0
  83. package/dist-public/src/conductor/familyDelivery.js +37 -0
  84. package/dist-public/src/conductor/familyPackArtifact.d.ts +4 -0
  85. package/dist-public/src/conductor/familyPackArtifact.js +260 -0
  86. package/dist-public/src/conductor/ideateDecision.d.ts +6 -0
  87. package/dist-public/src/conductor/ideateDecision.js +237 -0
  88. package/dist-public/src/conductor/pack.d.ts +26 -0
  89. package/dist-public/src/conductor/pack.js +75 -0
  90. package/dist-public/src/conductor/types.d.ts +111 -0
  91. package/dist-public/src/conductor/types.js +7 -0
  92. package/dist-public/src/configPath.d.ts +2 -0
  93. package/dist-public/src/configPath.js +7 -0
  94. package/dist-public/src/discoveryArtifacts.d.ts +54 -0
  95. package/dist-public/src/discoveryArtifacts.js +467 -0
  96. package/dist-public/src/discoveryCommand.d.ts +127 -0
  97. package/dist-public/src/discoveryCommand.js +2148 -0
  98. package/dist-public/src/discoveryConnections.d.ts +339 -0
  99. package/dist-public/src/discoveryConnections.js +613 -0
  100. package/dist-public/src/discoveryDocs.d.ts +129 -0
  101. package/dist-public/src/discoveryDocs.js +577 -0
  102. package/dist-public/src/discoveryEvidence.d.ts +59 -0
  103. package/dist-public/src/discoveryEvidence.js +198 -0
  104. package/dist-public/src/discoveryFireflies.d.ts +25 -0
  105. package/dist-public/src/discoveryFireflies.js +551 -0
  106. package/dist-public/src/discoveryGmail.d.ts +12 -0
  107. package/dist-public/src/discoveryGmail.js +466 -0
  108. package/dist-public/src/discoveryGong.d.ts +12 -0
  109. package/dist-public/src/discoveryGong.js +491 -0
  110. package/dist-public/src/discoveryLearningRouting.d.ts +90 -0
  111. package/dist-public/src/discoveryLearningRouting.js +405 -0
  112. package/dist-public/src/discoveryMatching.d.ts +26 -0
  113. package/dist-public/src/discoveryMatching.js +73 -0
  114. package/dist-public/src/discoveryOutlook.d.ts +12 -0
  115. package/dist-public/src/discoveryOutlook.js +492 -0
  116. package/dist-public/src/discoveryProcess.d.ts +221 -0
  117. package/dist-public/src/discoveryProcess.js +703 -0
  118. package/dist-public/src/discoveryProof.d.ts +143 -0
  119. package/dist-public/src/discoveryProof.js +529 -0
  120. package/dist-public/src/discoveryProviders.d.ts +152 -0
  121. package/dist-public/src/discoveryProviders.js +756 -0
  122. package/dist-public/src/discoveryReadiness.d.ts +219 -0
  123. package/dist-public/src/discoveryReadiness.js +672 -0
  124. package/dist-public/src/discoveryReconcile.d.ts +57 -0
  125. package/dist-public/src/discoveryReconcile.js +424 -0
  126. package/dist-public/src/discoverySlack.d.ts +12 -0
  127. package/dist-public/src/discoverySlack.js +452 -0
  128. package/dist-public/src/discoverySources.d.ts +56 -0
  129. package/dist-public/src/discoverySources.js +322 -0
  130. package/dist-public/src/discoverySync.d.ts +52 -0
  131. package/dist-public/src/discoverySync.js +151 -0
  132. package/dist-public/src/discoverySynthesis.d.ts +57 -0
  133. package/dist-public/src/discoverySynthesis.js +215 -0
  134. package/dist-public/src/discoveryTeams.d.ts +12 -0
  135. package/dist-public/src/discoveryTeams.js +516 -0
  136. package/dist-public/src/discoveryZoom.d.ts +12 -0
  137. package/dist-public/src/discoveryZoom.js +523 -0
  138. package/dist-public/src/elicitationSurface.d.ts +13 -0
  139. package/dist-public/src/elicitationSurface.js +77 -0
  140. package/dist-public/src/errorReport.d.ts +99 -0
  141. package/dist-public/src/errorReport.js +443 -0
  142. package/dist-public/src/errorTaxonomy.d.ts +84 -0
  143. package/dist-public/src/errorTaxonomy.js +189 -0
  144. package/dist-public/src/evalCalibration.d.ts +82 -0
  145. package/dist-public/src/evalCalibration.js +169 -0
  146. package/dist-public/src/evalChecks.d.ts +21 -0
  147. package/dist-public/src/evalChecks.js +295 -0
  148. package/dist-public/src/evalCompile.d.ts +56 -0
  149. package/dist-public/src/evalCompile.js +58 -0
  150. package/dist-public/src/evalImprove.d.ts +60 -0
  151. package/dist-public/src/evalImprove.js +175 -0
  152. package/dist-public/src/evalJudge.d.ts +47 -0
  153. package/dist-public/src/evalJudge.js +143 -0
  154. package/dist-public/src/evalLabels.d.ts +100 -0
  155. package/dist-public/src/evalLabels.js +287 -0
  156. package/dist-public/src/evalRecord.d.ts +68 -0
  157. package/dist-public/src/evalRecord.js +165 -0
  158. package/dist-public/src/evalRoast.d.ts +88 -0
  159. package/dist-public/src/evalRoast.js +249 -0
  160. package/dist-public/src/evidenceIntake.d.ts +66 -0
  161. package/dist-public/src/evidenceIntake.js +356 -0
  162. package/dist-public/src/feedback.d.ts +78 -0
  163. package/dist-public/src/feedback.js +294 -0
  164. package/dist-public/src/firefliesClient.d.ts +106 -0
  165. package/dist-public/src/firefliesClient.js +258 -0
  166. package/dist-public/src/firstMoveTensions.d.ts +10 -0
  167. package/dist-public/src/firstMoveTensions.js +93 -0
  168. package/dist-public/src/gitRun.d.ts +21 -0
  169. package/dist-public/src/gitRun.js +50 -0
  170. package/dist-public/src/githubReconcile.d.ts +94 -0
  171. package/dist-public/src/githubReconcile.js +205 -0
  172. package/dist-public/src/gmailNango.d.ts +131 -0
  173. package/dist-public/src/gmailNango.js +329 -0
  174. package/dist-public/src/gongNango.d.ts +118 -0
  175. package/dist-public/src/gongNango.js +308 -0
  176. package/dist-public/src/grammarGate.d.ts +13 -0
  177. package/dist-public/src/grammarGate.js +72 -0
  178. package/dist-public/src/harnessGates.d.ts +65 -0
  179. package/dist-public/src/harnessGates.js +168 -0
  180. package/dist-public/src/humanSurfaceEval.d.ts +19 -0
  181. package/dist-public/src/humanSurfaceEval.js +156 -0
  182. package/dist-public/src/ideate.d.ts +43 -0
  183. package/dist-public/src/ideate.js +255 -0
  184. package/dist-public/src/ideateContext.d.ts +11 -0
  185. package/dist-public/src/ideateContext.js +83 -0
  186. package/dist-public/src/index.d.ts +2 -0
  187. package/dist-public/src/index.js +4 -0
  188. package/dist-public/src/initCommand.d.ts +25 -0
  189. package/dist-public/src/initCommand.js +558 -0
  190. package/dist-public/src/interviewGate.d.ts +25 -0
  191. package/dist-public/src/interviewGate.js +126 -0
  192. package/dist-public/src/jsonFile.d.ts +2 -0
  193. package/dist-public/src/jsonFile.js +22 -0
  194. package/dist-public/src/loopBudget.d.ts +182 -0
  195. package/dist-public/src/loopBudget.js +494 -0
  196. package/dist-public/src/loopContracts.d.ts +35 -0
  197. package/dist-public/src/loopContracts.js +364 -0
  198. package/dist-public/src/machineState.d.ts +1 -0
  199. package/dist-public/src/machineState.js +9 -0
  200. package/dist-public/src/notionNango.d.ts +93 -0
  201. package/dist-public/src/notionNango.js +279 -0
  202. package/dist-public/src/orchestrationResult.d.ts +11 -0
  203. package/dist-public/src/orchestrationResult.js +73 -0
  204. package/dist-public/src/outlookNango.d.ts +124 -0
  205. package/dist-public/src/outlookNango.js +327 -0
  206. package/dist-public/src/packetCorrections.d.ts +40 -0
  207. package/dist-public/src/packetCorrections.js +172 -0
  208. package/dist-public/src/packetGlossary.d.ts +6 -0
  209. package/dist-public/src/packetGlossary.js +42 -0
  210. package/dist-public/src/packetIndex.d.ts +54 -0
  211. package/dist-public/src/packetIndex.js +744 -0
  212. package/dist-public/src/packetIndexEntry.d.ts +3 -0
  213. package/dist-public/src/packetIndexEntry.js +128 -0
  214. package/dist-public/src/packetIndexServe.d.ts +13 -0
  215. package/dist-public/src/packetIndexServe.js +113 -0
  216. package/dist-public/src/packetIndexWorker.d.ts +7 -0
  217. package/dist-public/src/packetIndexWorker.js +45 -0
  218. package/dist-public/src/packetLifecycle.d.ts +30 -0
  219. package/dist-public/src/packetLifecycle.js +127 -0
  220. package/dist-public/src/packetReconcile.d.ts +73 -0
  221. package/dist-public/src/packetReconcile.js +386 -0
  222. package/dist-public/src/packetStatus.d.ts +125 -0
  223. package/dist-public/src/packetStatus.js +770 -0
  224. package/dist-public/src/packetValidation.d.ts +17 -0
  225. package/dist-public/src/packetValidation.js +333 -0
  226. package/dist-public/src/packetVerify.d.ts +73 -0
  227. package/dist-public/src/packetVerify.js +303 -0
  228. package/dist-public/src/packetWrite.d.ts +52 -0
  229. package/dist-public/src/packetWrite.js +448 -0
  230. package/dist-public/src/pitch.d.ts +38 -0
  231. package/dist-public/src/pitch.js +154 -0
  232. package/dist-public/src/processMemo.d.ts +8 -0
  233. package/dist-public/src/processMemo.js +23 -0
  234. package/dist-public/src/productDetect.d.ts +30 -0
  235. package/dist-public/src/productDetect.js +376 -0
  236. package/dist-public/src/profile.d.ts +62 -0
  237. package/dist-public/src/profile.js +334 -0
  238. package/dist-public/src/questionModel.d.ts +46 -0
  239. package/dist-public/src/questionModel.js +74 -0
  240. package/dist-public/src/questionText.d.ts +7 -0
  241. package/dist-public/src/questionText.js +38 -0
  242. package/dist-public/src/redactConfig.d.ts +2 -0
  243. package/dist-public/src/redactConfig.js +11 -0
  244. package/dist-public/src/redactEngine.d.ts +59 -0
  245. package/dist-public/src/redactEngine.js +509 -0
  246. package/dist-public/src/redactGate.d.ts +63 -0
  247. package/dist-public/src/redactGate.js +209 -0
  248. package/dist-public/src/repoRead.d.ts +29 -0
  249. package/dist-public/src/repoRead.js +160 -0
  250. package/dist-public/src/research.d.ts +242 -0
  251. package/dist-public/src/research.js +1314 -0
  252. package/dist-public/src/researchCompletion.d.ts +34 -0
  253. package/dist-public/src/researchCompletion.js +123 -0
  254. package/dist-public/src/researchExecution.d.ts +47 -0
  255. package/dist-public/src/researchExecution.js +309 -0
  256. package/dist-public/src/researchFormat.d.ts +21 -0
  257. package/dist-public/src/researchFormat.js +77 -0
  258. package/dist-public/src/researchGate.d.ts +86 -0
  259. package/dist-public/src/researchGate.js +1036 -0
  260. package/dist-public/src/researchReceipt.d.ts +76 -0
  261. package/dist-public/src/researchReceipt.js +148 -0
  262. package/dist-public/src/researchRender.d.ts +2 -0
  263. package/dist-public/src/researchRender.js +267 -0
  264. package/dist-public/src/researchSigningKey.d.ts +19 -0
  265. package/dist-public/src/researchSigningKey.js +95 -0
  266. package/dist-public/src/riskClassify.d.ts +42 -0
  267. package/dist-public/src/riskClassify.js +149 -0
  268. package/dist-public/src/routines.d.ts +42 -0
  269. package/dist-public/src/routines.js +269 -0
  270. package/dist-public/src/routingPolicy.d.ts +172 -0
  271. package/dist-public/src/routingPolicy.js +646 -0
  272. package/dist-public/src/secretInput.d.ts +8 -0
  273. package/dist-public/src/secretInput.js +89 -0
  274. package/dist-public/src/selfCheck.d.ts +7 -0
  275. package/dist-public/src/selfCheck.js +473 -0
  276. package/dist-public/src/selfCheckCache.d.ts +29 -0
  277. package/dist-public/src/selfCheckCache.js +147 -0
  278. package/dist-public/src/severityClass.d.ts +15 -0
  279. package/dist-public/src/severityClass.js +22 -0
  280. package/dist-public/src/skillClientPolicy.d.ts +8 -0
  281. package/dist-public/src/skillClientPolicy.js +16 -0
  282. package/dist-public/src/skillLocal.d.ts +98 -0
  283. package/dist-public/src/skillLocal.js +463 -0
  284. package/dist-public/src/skillTreeDigest.d.ts +9 -0
  285. package/dist-public/src/skillTreeDigest.js +64 -0
  286. package/dist-public/src/slackNango.d.ts +62 -0
  287. package/dist-public/src/slackNango.js +208 -0
  288. package/dist-public/src/social/arxiv.d.ts +19 -0
  289. package/dist-public/src/social/arxiv.js +96 -0
  290. package/dist-public/src/social/github.d.ts +2 -0
  291. package/dist-public/src/social/github.js +78 -0
  292. package/dist-public/src/social/hackernews.d.ts +2 -0
  293. package/dist-public/src/social/hackernews.js +67 -0
  294. package/dist-public/src/social/index.d.ts +10 -0
  295. package/dist-public/src/social/index.js +79 -0
  296. package/dist-public/src/social/reddit.d.ts +2 -0
  297. package/dist-public/src/social/reddit.js +125 -0
  298. package/dist-public/src/social/types.d.ts +73 -0
  299. package/dist-public/src/social/types.js +61 -0
  300. package/dist-public/src/social/xml.d.ts +10 -0
  301. package/dist-public/src/social/xml.js +49 -0
  302. package/dist-public/src/social/youtube.d.ts +7 -0
  303. package/dist-public/src/social/youtube.js +217 -0
  304. package/dist-public/src/sourceIngress.d.ts +488 -0
  305. package/dist-public/src/sourceIngress.js +1651 -0
  306. package/dist-public/src/specAfter.d.ts +187 -0
  307. package/dist-public/src/specAfter.js +814 -0
  308. package/dist-public/src/specBudget.d.ts +124 -0
  309. package/dist-public/src/specBudget.js +424 -0
  310. package/dist-public/src/specDecisionSignals.d.ts +38 -0
  311. package/dist-public/src/specDecisionSignals.js +121 -0
  312. package/dist-public/src/specLinkedCommits.d.ts +7 -0
  313. package/dist-public/src/specLinkedCommits.js +107 -0
  314. package/dist-public/src/specSections.d.ts +105 -0
  315. package/dist-public/src/specSections.js +718 -0
  316. package/dist-public/src/statusGlyphs.d.ts +18 -0
  317. package/dist-public/src/statusGlyphs.js +75 -0
  318. package/dist-public/src/swarmCommand.d.ts +10 -0
  319. package/dist-public/src/swarmCommand.js +96 -0
  320. package/dist-public/src/teamsNango.d.ts +114 -0
  321. package/dist-public/src/teamsNango.js +301 -0
  322. package/dist-public/src/tokenStore.d.ts +10 -0
  323. package/dist-public/src/tokenStore.js +77 -0
  324. package/dist-public/src/trustEnvelope.d.ts +78 -0
  325. package/dist-public/src/trustEnvelope.js +392 -0
  326. package/dist-public/src/untrustedText.d.ts +6 -0
  327. package/dist-public/src/untrustedText.js +20 -0
  328. package/dist-public/src/updateNotice.d.ts +41 -0
  329. package/dist-public/src/updateNotice.js +290 -0
  330. package/dist-public/src/vendorYaml.d.ts +1 -0
  331. package/dist-public/src/vendorYaml.js +7374 -0
  332. package/dist-public/src/workflowRunActuals.d.ts +67 -0
  333. package/dist-public/src/workflowRunActuals.js +184 -0
  334. package/dist-public/src/zendeskDiscovery.d.ts +12 -0
  335. package/dist-public/src/zendeskDiscovery.js +513 -0
  336. package/dist-public/src/zendeskNango.d.ts +131 -0
  337. package/dist-public/src/zendeskNango.js +344 -0
  338. package/dist-public/src/zoomNango.d.ts +106 -0
  339. package/dist-public/src/zoomNango.js +351 -0
  340. package/docs/CHANGELOG.md +32 -0
  341. package/docs/THIRD_PARTY_NOTICES.md +36 -0
  342. package/package.json +71 -4
  343. package/schemas/bug-capsule-receipt.schema.json +54 -0
  344. package/schemas/bug-capsule.schema.json +189 -0
  345. package/schemas/discovery-evidence-envelope.schema.json +79 -0
  346. package/schemas/discovery-processing-receipt.schema.json +71 -0
  347. package/schemas/discovery-service-contract.schema.json +189 -0
  348. package/skills/AGENTS.md +32 -0
  349. package/skills/README.md +287 -0
  350. package/skills/bug/SKILL.md +49 -0
  351. package/skills/build-preview-spec/SKILL.md +103 -0
  352. package/skills/capture-external-artifact/SKILL.md +23 -0
  353. package/skills/capture-external-artifact/agents/openai.yaml +4 -0
  354. package/skills/capture-external-artifact/references/extractor-libraries.md +27 -0
  355. package/skills/check-spec-health/SKILL.md +45 -0
  356. package/skills/check-spec-health/agents/openai.yaml +4 -0
  357. package/skills/competitive-positioning/SKILL.md +86 -0
  358. package/skills/competitive-positioning/agents/openai.yaml +4 -0
  359. package/skills/cpo-lens/SKILL.md +398 -0
  360. package/skills/cpo-lens/agents/openai.yaml +4 -0
  361. package/skills/create-prd/SKILL.md +26 -0
  362. package/skills/create-prd/agents/openai.yaml +4 -0
  363. package/skills/customer-discovery/SKILL.md +192 -0
  364. package/skills/design-system/SKILL.md +49 -0
  365. package/skills/design-system/agents/openai.yaml +4 -0
  366. package/skills/discussion-guide/SKILL.md +75 -0
  367. package/skills/discussion-guide/agents/openai.yaml +4 -0
  368. package/skills/firecrawl-research-papers/SKILL.md +96 -0
  369. package/skills/firecrawl-research-papers/agents/openai.yaml +4 -0
  370. package/skills/foo-roast/SKILL.md +325 -0
  371. package/skills/generate-gtm-brief/SKILL.md +41 -0
  372. package/skills/generate-gtm-brief/agents/openai.yaml +4 -0
  373. package/skills/generate-gtm-brief/references/gtm-brief-template.md +56 -0
  374. package/skills/gong-quotes/SKILL.md +43 -0
  375. package/skills/gong-quotes/agents/openai.yaml +4 -0
  376. package/skills/grill-me/SKILL.md +27 -0
  377. package/skills/grill-me/agents/openai.yaml +4 -0
  378. package/skills/ideate/SKILL.md +180 -0
  379. package/skills/ideate/references/family-packs.md +5 -0
  380. package/skills/initiative-tracker/SKILL.md +23 -0
  381. package/skills/initiative-tracker/agents/openai.yaml +4 -0
  382. package/skills/interop-review/SKILL.md +25 -0
  383. package/skills/interop-review/agents/openai.yaml +4 -0
  384. package/skills/interview-me/SKILL.md +404 -0
  385. package/skills/interview-me/agents/openai.yaml +4 -0
  386. package/skills/loop-library-audit/SKILL.md +123 -0
  387. package/skills/pitch/SKILL.md +110 -0
  388. package/skills/pitch/references/opportunity-assessment.md +136 -0
  389. package/skills/pitch/references/prfaq-template.md +54 -0
  390. package/skills/pmm-release-briefing/SKILL.md +27 -0
  391. package/skills/pmm-release-briefing/agents/openai.yaml +4 -0
  392. package/skills/pmm-release-briefing/references/variant-generalized.md +16 -0
  393. package/skills/positioning-messaging/SKILL.md +70 -0
  394. package/skills/positioning-messaging/agents/openai.yaml +4 -0
  395. package/skills/positioning-messaging/references/guest-insights.md +1400 -0
  396. package/skills/preview-swarm/SKILL.md +418 -0
  397. package/skills/preview-swarm/agents/openai.yaml +4 -0
  398. package/skills/preview-swarm/references/html-effectiveness-build-previews.md +168 -0
  399. package/skills/product-copywriting/SKILL.md +127 -0
  400. package/skills/product-copywriting/agents/openai.yaml +4 -0
  401. package/skills/product-copywriting/references/foo-voice.md +26 -0
  402. package/skills/product-shape-interview/SKILL.md +219 -0
  403. package/skills/product-shape-interview/agents/openai.yaml +4 -0
  404. package/skills/ready-for-release/SKILL.md +39 -0
  405. package/skills/ready-for-release/agents/openai.yaml +4 -0
  406. package/skills/redact-transcripts/SKILL.md +23 -0
  407. package/skills/redact-transcripts/agents/openai.yaml +4 -0
  408. package/skills/research-brief/SKILL.md +48 -0
  409. package/skills/research-brief/agents/openai.yaml +4 -0
  410. package/skills/research-refresh/SKILL.md +63 -0
  411. package/skills/research-refresh/references/worked-cases.md +44 -0
  412. package/skills/review-loop/SKILL.md +200 -0
  413. package/skills/review-spec/SKILL.md +32 -0
  414. package/skills/review-spec/agents/openai.yaml +4 -0
  415. package/skills/rfc-from-research/SKILL.md +42 -0
  416. package/skills/rfc-from-research/agents/openai.yaml +4 -0
  417. package/skills/rfc-from-research/references/rfc-pattern.md +69 -0
  418. package/skills/skill-registry.yaml +810 -0
  419. package/skills/spec-cleanup/SKILL.md +103 -0
  420. package/skills/spec-cleanup/agents/openai.yaml +4 -0
  421. package/skills/spec-swarm/SKILL.md +87 -0
  422. package/skills/spec-swarm/agents/openai.yaml +4 -0
  423. package/skills/spec-swarm/template/SKILL.template.md +94 -0
  424. package/skills/spec-swarm/template/personas/accessibility-inclusion.md +40 -0
  425. package/skills/spec-swarm/template/personas/ai-technical-pm.md +69 -0
  426. package/skills/spec-swarm/template/personas/analytics-engineering.md +39 -0
  427. package/skills/spec-swarm/template/personas/applied-ai-eval.md +64 -0
  428. package/skills/spec-swarm/template/personas/business-gm-pm.md +67 -0
  429. package/skills/spec-swarm/template/personas/consumer-craft-pm.md +68 -0
  430. package/skills/spec-swarm/template/personas/content-design.md +40 -0
  431. package/skills/spec-swarm/template/personas/cpo.md +42 -0
  432. package/skills/spec-swarm/template/personas/data-science.md +42 -0
  433. package/skills/spec-swarm/template/personas/developer-experience.md +65 -0
  434. package/skills/spec-swarm/template/personas/dx-product.md +70 -0
  435. package/skills/spec-swarm/template/personas/field.md +43 -0
  436. package/skills/spec-swarm/template/personas/finance-cfo.md +40 -0
  437. package/skills/spec-swarm/template/personas/growth-pm.md +67 -0
  438. package/skills/spec-swarm/template/personas/platform-pm.md +67 -0
  439. package/skills/spec-swarm/template/personas/pmm.md +43 -0
  440. package/skills/spec-swarm/template/personas/principal-product-designer.md +42 -0
  441. package/skills/spec-swarm/template/personas/product-ops-program.md +41 -0
  442. package/skills/spec-swarm/template/personas/qa-release.md +41 -0
  443. package/skills/spec-swarm/template/personas/responsible-ai-safety.md +64 -0
  444. package/skills/spec-swarm/template/personas/security-privacy-legal.md +40 -0
  445. package/skills/spec-swarm/template/personas/strategy-competitive.md +41 -0
  446. package/skills/spec-swarm/template/personas/technical-architect.md +42 -0
  447. package/skills/spec-swarm/template/personas/technical-lead.md +43 -0
  448. package/skills/spec-swarm/template/personas/user-research.md +41 -0
  449. package/skills/spec-swarm-accessibility-inclusion/SKILL.md +95 -0
  450. package/skills/spec-swarm-accessibility-inclusion/agents/openai.yaml +4 -0
  451. package/skills/spec-swarm-ai-technical-pm/SKILL.md +124 -0
  452. package/skills/spec-swarm-analytics-engineering/SKILL.md +94 -0
  453. package/skills/spec-swarm-analytics-engineering/agents/openai.yaml +4 -0
  454. package/skills/spec-swarm-applied-ai-eval/SKILL.md +120 -0
  455. package/skills/spec-swarm-business-gm-pm/SKILL.md +122 -0
  456. package/skills/spec-swarm-consumer-craft-pm/SKILL.md +122 -0
  457. package/skills/spec-swarm-content-design/SKILL.md +95 -0
  458. package/skills/spec-swarm-content-design/agents/openai.yaml +4 -0
  459. package/skills/spec-swarm-cpo/SKILL.md +94 -0
  460. package/skills/spec-swarm-cpo/agents/openai.yaml +4 -0
  461. package/skills/spec-swarm-data-science/SKILL.md +95 -0
  462. package/skills/spec-swarm-data-science/agents/openai.yaml +4 -0
  463. package/skills/spec-swarm-developer-experience/SKILL.md +121 -0
  464. package/skills/spec-swarm-dx-product/SKILL.md +126 -0
  465. package/skills/spec-swarm-field/SKILL.md +96 -0
  466. package/skills/spec-swarm-field/agents/openai.yaml +4 -0
  467. package/skills/spec-swarm-finance-cfo/SKILL.md +95 -0
  468. package/skills/spec-swarm-finance-cfo/agents/openai.yaml +4 -0
  469. package/skills/spec-swarm-growth-pm/SKILL.md +122 -0
  470. package/skills/spec-swarm-platform-pm/SKILL.md +122 -0
  471. package/skills/spec-swarm-pmm/SKILL.md +96 -0
  472. package/skills/spec-swarm-pmm/agents/openai.yaml +4 -0
  473. package/skills/spec-swarm-principal-product-designer/SKILL.md +95 -0
  474. package/skills/spec-swarm-principal-product-designer/agents/openai.yaml +4 -0
  475. package/skills/spec-swarm-product-ops-program/SKILL.md +96 -0
  476. package/skills/spec-swarm-product-ops-program/agents/openai.yaml +4 -0
  477. package/skills/spec-swarm-qa-release/SKILL.md +96 -0
  478. package/skills/spec-swarm-qa-release/agents/openai.yaml +4 -0
  479. package/skills/spec-swarm-responsible-ai-safety/SKILL.md +120 -0
  480. package/skills/spec-swarm-security-privacy-legal/SKILL.md +95 -0
  481. package/skills/spec-swarm-security-privacy-legal/agents/openai.yaml +4 -0
  482. package/skills/spec-swarm-strategy-competitive/SKILL.md +96 -0
  483. package/skills/spec-swarm-strategy-competitive/agents/openai.yaml +4 -0
  484. package/skills/spec-swarm-technical-architect/SKILL.md +95 -0
  485. package/skills/spec-swarm-technical-architect/agents/openai.yaml +4 -0
  486. package/skills/spec-swarm-technical-lead/SKILL.md +96 -0
  487. package/skills/spec-swarm-technical-lead/agents/openai.yaml +4 -0
  488. package/skills/spec-swarm-user-research/SKILL.md +95 -0
  489. package/skills/spec-swarm-user-research/agents/openai.yaml +4 -0
  490. package/skills/strategy-check/SKILL.md +170 -0
  491. package/skills/strategy-check/agents/openai.yaml +4 -0
  492. package/skills/study-digest/SKILL.md +41 -0
  493. package/skills/synthetic-customer/SKILL.md +68 -0
  494. package/skills/update-cli-spec/SKILL.md +69 -0
  495. package/skills/update-cli-spec/agents/openai.yaml +4 -0
  496. package/skills/voc-researcher/SKILL.md +45 -0
  497. package/skills/voc-researcher/agents/openai.yaml +4 -0
  498. package/skills-lock.json +35 -0
@@ -0,0 +1,125 @@
1
+ // Reddit adapter — the one non-trivial port. Bare reddit.com/*.json rate-limits
2
+ // hard (429/403), which is why last30days ships several strategies.
3
+ //
4
+ // HONEST LIMITATION (found by the dependency watcher, 2026-07-12): Arctic Shift
5
+ // CANNOT serve a global keyword search — its API answers
6
+ // "'query' query parameter requires one of: author, subreddit"
7
+ // so an arctic call with only a query returns HTTP 400, always. It was in this
8
+ // chain and had never once worked; the fallback masked it. It is removed rather
9
+ // than left as a strategy that silently fails. (It does work when scoped to a
10
+ // subreddit — see OQ: use it for subreddit-scoped search, which would restore an
11
+ // off-reddit.com fallback.)
12
+ //
13
+ // So the chain is Reddit Atom (.rss) → Reddit public JSON. Both live on
14
+ // reddit.com and therefore throttle TOGETHER: there is currently no off-domain
15
+ // fallback for global keyword search. Resilience here is weaker than a
16
+ // multi-host chain, and the spec says so rather than implying otherwise. A
17
+ // strategy that throws or 429s drops to the next; the source is only `excluded`
18
+ // if all fail (R-001 done_when).
19
+ //
20
+ // Reddit asks clients to send a descriptive User-Agent; we do (it is correct
21
+ // practice, though it does not by itself defeat rate limiting).
22
+ import { resolveOptions, settleStatus, withinWindow } from "./types.js";
23
+ import { parseAtomEntries } from "./xml.js";
24
+ function postToItem(p) {
25
+ if (!p.id || !p.title)
26
+ return null;
27
+ const createdSec = typeof p.created_utc === "number" ? p.created_utc : 0;
28
+ return {
29
+ source: "reddit",
30
+ id: `reddit:${p.id}`,
31
+ title: p.title,
32
+ url: p.permalink ? `https://www.reddit.com${p.permalink}` : p.url || "",
33
+ author: p.author ?? null,
34
+ createdAt: new Date(createdSec * 1000).toISOString(),
35
+ score: typeof p.score === "number" ? p.score : null,
36
+ snippet: p.selftext ? p.selftext.slice(0, 280) : null,
37
+ };
38
+ }
39
+ function filterAndCap(items, o) {
40
+ return items
41
+ .filter((it) => it !== null && Boolean(it.url))
42
+ .filter((it) => withinWindow(it.createdAt, o.now, o.days))
43
+ .slice(0, o.limit);
44
+ }
45
+ // Reddit asks for a unique, descriptive User-Agent on API-ish requests.
46
+ const REDDIT_UA = "foo-research/0.1 (+https://github.com/rwo572/productfoo)";
47
+ async function redditRss(query, o) {
48
+ const url = `https://www.reddit.com/search.rss?q=${encodeURIComponent(query)}&sort=relevance&t=month`;
49
+ const res = await o.fetchImpl(url, {
50
+ headers: { Accept: "application/atom+xml", "User-Agent": REDDIT_UA },
51
+ signal: o.signal,
52
+ });
53
+ if (!res.ok)
54
+ throw new Error(`HTTP ${res.status}`);
55
+ const xml = await res.text();
56
+ const items = parseAtomEntries(xml).map((e) => {
57
+ if (!e.title || !e.link)
58
+ return null;
59
+ // Reddit Atom ids look like "t3_abcdef"; fall back to the link.
60
+ const id = e.id ? e.id.replace(/^.*?(t3_[a-z0-9]+).*$/i, "$1") : e.link;
61
+ return {
62
+ source: "reddit",
63
+ id: `reddit:${id}`,
64
+ title: e.title,
65
+ url: e.link,
66
+ author: e.author,
67
+ createdAt: e.published ?? new Date(0).toISOString(),
68
+ score: null, // the Atom feed carries no engagement score
69
+ snippet: e.summary ? e.summary.slice(0, 280) : null,
70
+ };
71
+ });
72
+ return filterAndCap(items, o);
73
+ }
74
+ async function redditPublicJson(query, o) {
75
+ const url = `https://www.reddit.com/search.json?q=${encodeURIComponent(query)}` +
76
+ `&sort=relevance&t=month&limit=${o.limit}`;
77
+ const res = await o.fetchImpl(url, {
78
+ headers: { Accept: "application/json", "User-Agent": REDDIT_UA },
79
+ signal: o.signal,
80
+ });
81
+ if (!res.ok)
82
+ throw new Error(`HTTP ${res.status}`);
83
+ const body = (await res.json());
84
+ const children = body.data?.children ?? [];
85
+ return filterAndCap(children.map((c) => (c.data ? postToItem(c.data) : null)), o);
86
+ }
87
+ // arctic is deliberately absent: its API cannot do a global keyword search, so
88
+ // it returned HTTP 400 on every call (see the header note). Both remaining
89
+ // strategies are on reddit.com and throttle together — a known, recorded
90
+ // resilience gap, not a silent one.
91
+ const STRATEGIES = [
92
+ { name: "rss", run: redditRss },
93
+ { name: "public", run: redditPublicJson },
94
+ ];
95
+ export async function fetchReddit(query, opts = {}) {
96
+ const o = resolveOptions(opts);
97
+ const tried = [];
98
+ for (const strategy of STRATEGIES) {
99
+ try {
100
+ const items = await strategy.run(query, o);
101
+ if (items.length > 0) {
102
+ const prior = tried.length ? ` (after ${tried.join(", ")})` : "";
103
+ return {
104
+ source: "reddit",
105
+ status: settleStatus("available", items.length),
106
+ reason: `Reddit via ${strategy.name}: ${items.length} post(s) within ${o.days}d${prior}.`,
107
+ items,
108
+ };
109
+ }
110
+ tried.push(`${strategy.name}: empty`);
111
+ }
112
+ catch (err) {
113
+ tried.push(`${strategy.name}: ${err.message}`);
114
+ }
115
+ }
116
+ // Every strategy either failed or returned nothing. If any strategy actually
117
+ // ran without error we degraded (no signal); if all threw we exclude.
118
+ const allErrored = tried.every((t) => !t.endsWith("empty"));
119
+ return {
120
+ source: "reddit",
121
+ status: allErrored ? "excluded" : "degraded",
122
+ reason: `Reddit returned no usable posts — ${tried.join("; ")}.`,
123
+ items: [],
124
+ };
125
+ }
@@ -0,0 +1,73 @@
1
+ export type SocialSource = "reddit" | "hackernews" | "arxiv" | "github" | "youtube";
2
+ export interface YtVideo {
3
+ id: string;
4
+ title: string;
5
+ url: string;
6
+ uploader: string | null;
7
+ uploadDate: string;
8
+ viewCount: number | null;
9
+ }
10
+ export interface YtDlp {
11
+ search(query: string, n: number): Promise<YtVideo[]>;
12
+ transcript?(videoId: string): Promise<string | null>;
13
+ }
14
+ export type AdapterStatus = "available" | "empty" | "degraded" | "excluded";
15
+ export interface SocialItem {
16
+ source: SocialSource;
17
+ id: string;
18
+ title: string;
19
+ url: string;
20
+ author: string | null;
21
+ createdAt: string;
22
+ score: number | null;
23
+ snippet: string | null;
24
+ }
25
+ export interface AdapterResult {
26
+ source: SocialSource;
27
+ status: AdapterStatus;
28
+ reason: string;
29
+ items: SocialItem[];
30
+ }
31
+ export interface HttpResponse {
32
+ ok: boolean;
33
+ status: number;
34
+ json(): Promise<unknown>;
35
+ text(): Promise<string>;
36
+ }
37
+ export type FetchLike = (url: string, init?: {
38
+ headers?: Record<string, string>;
39
+ signal?: AbortSignal;
40
+ }) => Promise<HttpResponse>;
41
+ export interface AdapterOptions {
42
+ days?: number;
43
+ limit?: number;
44
+ now?: Date;
45
+ fetchImpl?: FetchLike;
46
+ githubToken?: string;
47
+ ytdlp?: YtDlp;
48
+ timeoutMs?: number;
49
+ }
50
+ export declare const DEFAULT_DAYS = 30;
51
+ export declare const DEFAULT_LIMIT = 25;
52
+ export declare const DEFAULT_FETCH_TIMEOUT_MS = 30000;
53
+ export interface ResolvedOptions {
54
+ days: number;
55
+ limit: number;
56
+ now: Date;
57
+ fetchImpl: FetchLike;
58
+ githubToken?: string;
59
+ signal: AbortSignal;
60
+ }
61
+ export declare function resolveOptions(opts?: AdapterOptions): ResolvedOptions;
62
+ /**
63
+ * The honest-zero rule, in one place: a source that returned no items did not
64
+ * contribute evidence, so it cannot report `available` — whatever it thinks.
65
+ *
66
+ * Applied at BOTH layers on purpose. Each adapter settles its own status through
67
+ * this, so an adapter is honest when called directly; and `gatherSocial` re-applies
68
+ * it, so a future adapter that forgets cannot reintroduce the lie. `degraded` and
69
+ * `excluded` already carry their own meaning and pass through untouched.
70
+ */
71
+ export declare function settleStatus(status: AdapterStatus, itemCount: number): AdapterStatus;
72
+ export declare function windowStartMs(now: Date, days: number): number;
73
+ export declare function withinWindow(iso: string, now: Date, days: number): boolean;
@@ -0,0 +1,61 @@
1
+ // foo social adapters — the keyless free-tier port of last30days into productfoo's
2
+ // `social` SourceClass (specs/research-social-last30d-v0, R-001). This module is
3
+ // the *fetch* layer: it is deliberately separate from research.ts (the
4
+ // deterministic, fetch-free spine) so the spine keeps its "never fetches"
5
+ // invariant. The agent invokes these adapters at gather time; foo synthesizes.
6
+ //
7
+ // Every adapter is offline-testable: network access is a single injected
8
+ // `FetchLike` (default = global fetch), so unit tests pass fixtures and no live
9
+ // I/O runs in the test suite — the same posture as src/sourceIngress.ts.
10
+ //
11
+ // Every HTTP fetch is also time-bounded: `resolveOptions` hands each adapter one
12
+ // abort signal (default ceiling `DEFAULT_FETCH_TIMEOUT_MS`, overridable with
13
+ // `timeoutMs` for tests and slow-host tuning) that the adapter passes on every
14
+ // call, so one hung endpoint can no longer stall the whole gather.
15
+ export const DEFAULT_DAYS = 30;
16
+ export const DEFAULT_LIMIT = 25;
17
+ // The HTTP fetch ceiling. Before this existed a hung endpoint stalled the whole
18
+ // gather for minutes, bounded only by undici's defaults (#774).
19
+ export const DEFAULT_FETCH_TIMEOUT_MS = 30_000;
20
+ const globalFetch = (url, init) =>
21
+ // eslint-disable-next-line no-undef
22
+ fetch(url, {
23
+ ...init,
24
+ // Default ceiling for direct callers; the adapters pass the resolved signal
25
+ // explicitly so the `timeoutMs` override reaches every call.
26
+ signal: init?.signal ?? AbortSignal.timeout(DEFAULT_FETCH_TIMEOUT_MS),
27
+ });
28
+ export function resolveOptions(opts = {}) {
29
+ return {
30
+ days: opts.days ?? DEFAULT_DAYS,
31
+ limit: opts.limit ?? DEFAULT_LIMIT,
32
+ now: opts.now ?? new Date(),
33
+ fetchImpl: opts.fetchImpl ?? globalFetch,
34
+ githubToken: opts.githubToken,
35
+ signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS),
36
+ };
37
+ }
38
+ /**
39
+ * The honest-zero rule, in one place: a source that returned no items did not
40
+ * contribute evidence, so it cannot report `available` — whatever it thinks.
41
+ *
42
+ * Applied at BOTH layers on purpose. Each adapter settles its own status through
43
+ * this, so an adapter is honest when called directly; and `gatherSocial` re-applies
44
+ * it, so a future adapter that forgets cannot reintroduce the lie. `degraded` and
45
+ * `excluded` already carry their own meaning and pass through untouched.
46
+ */
47
+ export function settleStatus(status, itemCount) {
48
+ return status === "available" && itemCount === 0 ? "empty" : status;
49
+ }
50
+ // The oldest timestamp (ms) an item may carry to fall inside the recency window.
51
+ export function windowStartMs(now, days) {
52
+ return now.getTime() - days * 24 * 60 * 60 * 1000;
53
+ }
54
+ // True when `iso` parses and is at or after the window start. Unparseable dates
55
+ // are treated as out-of-window (defensive: never let a garbage date leak in).
56
+ export function withinWindow(iso, now, days) {
57
+ const t = Date.parse(iso);
58
+ if (Number.isNaN(t))
59
+ return false;
60
+ return t >= windowStartMs(now, days) && t <= now.getTime() + 60_000;
61
+ }
@@ -0,0 +1,10 @@
1
+ export interface AtomEntry {
2
+ id: string | null;
3
+ title: string | null;
4
+ link: string | null;
5
+ published: string | null;
6
+ author: string | null;
7
+ summary: string | null;
8
+ }
9
+ export declare function unescapeXml(value: string | null): string | null;
10
+ export declare function parseAtomEntries(xml: string): AtomEntry[];
@@ -0,0 +1,49 @@
1
+ // A minimal, dependency-free Atom reader — just enough to parse the two Atom
2
+ // feeds this module consumes (Reddit search.rss and the arXiv query API). It is
3
+ // deliberately lenient (regex over well-formed, regular feeds) rather than a
4
+ // general XML parser; fixtures in the test suite pin the shapes it must handle.
5
+ function firstTag(block, name) {
6
+ const m = block.match(new RegExp(`<${name}\\b[^>]*>([\\s\\S]*?)</${name}>`, "i"));
7
+ return m ? m[1].trim() : null;
8
+ }
9
+ function linkHref(block) {
10
+ // Prefer an alternate/self <link href="…">; fall back to a text <link>…</link>.
11
+ const href = block.match(/<link\b[^>]*\bhref="([^"]+)"/i);
12
+ if (href)
13
+ return href[1];
14
+ return firstTag(block, "link");
15
+ }
16
+ function authorName(block) {
17
+ const author = block.match(/<author\b[^>]*>([\s\S]*?)<\/author>/i);
18
+ if (!author)
19
+ return null;
20
+ return firstTag(author[1], "name");
21
+ }
22
+ export function unescapeXml(value) {
23
+ if (value == null)
24
+ return null;
25
+ return value
26
+ .replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, "$1")
27
+ .replace(/&lt;/g, "<")
28
+ .replace(/&gt;/g, ">")
29
+ .replace(/&quot;/g, '"')
30
+ .replace(/&#39;/g, "'")
31
+ .replace(/&apos;/g, "'")
32
+ .replace(/&amp;/g, "&")
33
+ .replace(/<[^>]+>/g, "") // strip any inline markup left in titles/summaries
34
+ .trim();
35
+ }
36
+ export function parseAtomEntries(xml) {
37
+ const blocks = xml.split(/<entry\b/i).slice(1);
38
+ return blocks.map((raw) => {
39
+ const block = raw.split(/<\/entry>/i)[0];
40
+ return {
41
+ id: firstTag(block, "id"),
42
+ title: unescapeXml(firstTag(block, "title")),
43
+ link: linkHref(block),
44
+ published: firstTag(block, "published") ?? firstTag(block, "updated"),
45
+ author: authorName(block),
46
+ summary: unescapeXml(firstTag(block, "summary")),
47
+ };
48
+ });
49
+ }
@@ -0,0 +1,7 @@
1
+ import type { AdapterOptions, AdapterResult, YtDlp } from "./types.js";
2
+ export declare class MissingBinaryError extends Error {
3
+ constructor(message?: string);
4
+ }
5
+ export declare const defaultYtDlp: YtDlp;
6
+ export declare function parseVtt(vtt: string): string;
7
+ export declare function fetchYouTube(query: string, opts?: AdapterOptions): Promise<AdapterResult>;
@@ -0,0 +1,217 @@
1
+ // YouTube adapter — keyless, but via the yt-dlp binary rather than an HTTP API
2
+ // (upstream last30days does the same: "no API keys needed — just have yt-dlp
3
+ // installed"). yt-dlp is a *dependency*, not a secret, so YouTube stays a
4
+ // free-tier source (D-004). When yt-dlp is absent the source degrades honestly
5
+ // and the run continues — it never crashes (R-003 done_when).
6
+ //
7
+ // V0 covers recency-scoped search with view-count engagement. Transcript
8
+ // extraction (a bounded per-video enrichment) is a follow-on — see OQ-5 — kept
9
+ // out of V0 because it needs on-disk subtitle handling that would ship untested.
10
+ //
11
+ // The yt-dlp boundary is injected (types.ts YtDlp) so the adapter's recency and
12
+ // status logic is unit-tested with a fake and no binary runs in the suite.
13
+ import { execFile } from "node:child_process";
14
+ import { mkdtemp, readdir, readFile, rm } from "node:fs/promises";
15
+ import { tmpdir } from "node:os";
16
+ import path from "node:path";
17
+ import { promisify } from "node:util";
18
+ import { resolveOptions, settleStatus, withinWindow } from "./types.js";
19
+ const execFileAsync = promisify(execFile);
20
+ // How many top videos to enrich with a transcript per run — bounded so a run
21
+ // never fans out an unbounded number of subtitle fetches.
22
+ const TRANSCRIPT_LIMIT = 3;
23
+ const TRANSCRIPT_MAX_CHARS = 500;
24
+ // Hard ceiling on each yt-dlp process. A shell-out has no fetch signal to
25
+ // respect, so without this a hung yt-dlp stalled the whole gather for minutes
26
+ // (#774). Generous — a ytsearch fans out several upstream requests — but a
27
+ // bound: the child is killed and the adapter reports `excluded` like any other
28
+ // failure.
29
+ const YTDLP_TIMEOUT_MS = 120_000;
30
+ // Thrown by a YtDlp implementation when the binary is not installed, so the
31
+ // adapter can distinguish "not set up" (degrade + how-to) from "ran and failed".
32
+ export class MissingBinaryError extends Error {
33
+ constructor(message = "yt-dlp is not installed") {
34
+ super(message);
35
+ this.name = "MissingBinaryError";
36
+ }
37
+ }
38
+ // Default implementation: shells out to yt-dlp and streams one JSON object per
39
+ // result. `ytsearchN:` is yt-dlp's search selector; --dump-json emits full
40
+ // metadata (upload_date, view_count) per video.
41
+ export const defaultYtDlp = {
42
+ async search(query, n) {
43
+ try {
44
+ const { stdout } = await execFileAsync("yt-dlp", ["--dump-json", "--no-warnings", `ytsearch${n}:${query}`], { maxBuffer: 32 * 1024 * 1024, timeout: YTDLP_TIMEOUT_MS });
45
+ return stdout
46
+ .split("\n")
47
+ .map((line) => line.trim())
48
+ .filter(Boolean)
49
+ .map((line) => {
50
+ try {
51
+ const v = JSON.parse(line);
52
+ if (!v.id || !v.title)
53
+ return null;
54
+ return {
55
+ id: v.id,
56
+ title: v.title,
57
+ url: v.webpage_url || v.url || `https://www.youtube.com/watch?v=${v.id}`,
58
+ uploader: v.uploader ?? v.channel ?? null,
59
+ uploadDate: v.upload_date ?? "",
60
+ viewCount: typeof v.view_count === "number" ? v.view_count : null,
61
+ };
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ })
67
+ .filter((v) => v !== null);
68
+ }
69
+ catch (err) {
70
+ const code = err.code;
71
+ if (code === "ENOENT")
72
+ throw new MissingBinaryError();
73
+ if (err.killed) {
74
+ throw new Error(`yt-dlp did not finish within ${YTDLP_TIMEOUT_MS / 1000}s (process killed)`);
75
+ }
76
+ throw err;
77
+ }
78
+ },
79
+ // Best-effort auto-subtitle fetch. yt-dlp writes subtitles to disk, so we use
80
+ // an isolated temp dir, read the VTT back, and always clean up. Any failure
81
+ // (no captions, network, missing binary) yields null — a transcript is
82
+ // enrichment, never a reason to fail the item.
83
+ async transcript(videoId) {
84
+ let dir;
85
+ try {
86
+ dir = await mkdtemp(path.join(tmpdir(), "foo-yt-"));
87
+ const url = `https://www.youtube.com/watch?v=${videoId}`;
88
+ await execFileAsync("yt-dlp", [
89
+ "--skip-download",
90
+ "--write-auto-subs",
91
+ "--sub-lang",
92
+ "en",
93
+ "--sub-format",
94
+ "vtt",
95
+ "--no-warnings",
96
+ "-o",
97
+ path.join(dir, "%(id)s.%(ext)s"),
98
+ url,
99
+ ], { maxBuffer: 32 * 1024 * 1024, timeout: YTDLP_TIMEOUT_MS });
100
+ const files = await readdir(dir);
101
+ const vttFile = files.find((f) => f.endsWith(".vtt"));
102
+ if (!vttFile)
103
+ return null;
104
+ const text = parseVtt(await readFile(path.join(dir, vttFile), "utf8"));
105
+ return text || null;
106
+ }
107
+ catch {
108
+ return null;
109
+ }
110
+ finally {
111
+ if (dir)
112
+ await rm(dir, { recursive: true, force: true }).catch(() => { });
113
+ }
114
+ },
115
+ };
116
+ // Reduce a WebVTT caption file to plain running text: drop the header, cue
117
+ // indices, timestamp lines, and inline tags, and collapse the duplicate lines
118
+ // that auto-generated rolling captions produce.
119
+ export function parseVtt(vtt) {
120
+ const out = [];
121
+ for (const raw of vtt.split(/\r?\n/)) {
122
+ const line = raw.trim();
123
+ if (!line || line === "WEBVTT")
124
+ continue;
125
+ if (line.startsWith("Kind:") || line.startsWith("Language:"))
126
+ continue;
127
+ if (line.includes("-->"))
128
+ continue; // timestamp cue
129
+ if (/^\d+$/.test(line))
130
+ continue; // cue index
131
+ const text = line
132
+ .replace(/<[^>]+>/g, "")
133
+ .replace(/&nbsp;/g, " ")
134
+ .trim();
135
+ if (!text || out[out.length - 1] === text)
136
+ continue; // dedupe rolling captions
137
+ out.push(text);
138
+ }
139
+ return out.join(" ");
140
+ }
141
+ // yt-dlp's upload_date is "YYYYMMDD"; normalize to an ISO instant so the shared
142
+ // recency filter can parse it. Pass through anything already ISO-ish.
143
+ function normalizeDate(raw) {
144
+ const m = /^(\d{4})(\d{2})(\d{2})$/.exec(raw);
145
+ if (m)
146
+ return `${m[1]}-${m[2]}-${m[3]}T00:00:00Z`;
147
+ return raw;
148
+ }
149
+ function toItem(v) {
150
+ if (!v.uploadDate)
151
+ return null; // no date → cannot place in the window
152
+ return {
153
+ source: "youtube",
154
+ id: `youtube:${v.id}`,
155
+ title: v.title,
156
+ url: v.url,
157
+ author: v.uploader,
158
+ createdAt: normalizeDate(v.uploadDate),
159
+ score: v.viewCount,
160
+ snippet: null,
161
+ };
162
+ }
163
+ export async function fetchYouTube(query, opts = {}) {
164
+ const { days, limit, now } = resolveOptions(opts);
165
+ const ytdlp = opts.ytdlp ?? defaultYtDlp;
166
+ try {
167
+ const videos = await ytdlp.search(query, limit);
168
+ const items = videos
169
+ .map(toItem)
170
+ .filter((it) => it !== null)
171
+ .filter((it) => withinWindow(it.createdAt, now, days))
172
+ .slice(0, limit);
173
+ if (items.length === 0) {
174
+ return {
175
+ source: "youtube",
176
+ status: "degraded",
177
+ reason: `yt-dlp ran but found no videos within ${days}d.`,
178
+ items,
179
+ };
180
+ }
181
+ // Best-effort transcript enrichment for the top few videos (bounded).
182
+ let transcribed = 0;
183
+ if (ytdlp.transcript) {
184
+ const fetchTranscript = ytdlp.transcript.bind(ytdlp);
185
+ await Promise.all(items.slice(0, TRANSCRIPT_LIMIT).map(async (it) => {
186
+ const text = await fetchTranscript(it.id.replace(/^youtube:/, ""));
187
+ if (text) {
188
+ it.snippet = text.slice(0, TRANSCRIPT_MAX_CHARS);
189
+ transcribed += 1;
190
+ }
191
+ }));
192
+ }
193
+ return {
194
+ source: "youtube",
195
+ status: settleStatus("available", items.length),
196
+ reason: `yt-dlp returned ${items.length} video(s) within ${days}d` +
197
+ (transcribed > 0 ? `, ${transcribed} with transcript.` : "."),
198
+ items,
199
+ };
200
+ }
201
+ catch (err) {
202
+ if (err instanceof MissingBinaryError) {
203
+ return {
204
+ source: "youtube",
205
+ status: "excluded",
206
+ reason: "yt-dlp is not installed; install it (e.g. brew install yt-dlp) to enable YouTube.",
207
+ items: [],
208
+ };
209
+ }
210
+ return {
211
+ source: "youtube",
212
+ status: "excluded",
213
+ reason: `yt-dlp failed: ${err.message}`,
214
+ items: [],
215
+ };
216
+ }
217
+ }