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,756 @@
1
+ import { companyFromEmailDomain, contactHintsFromDisplayLabel, dedupeContactHints, isValidLinkedinUrl, sanitizeDiscoveryStableUrl, } from "./discoverySources.js";
2
+ const GRANOLA_MEETING_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
3
+ function granolaStableUrl(raw) {
4
+ if (raw.url)
5
+ return sanitizeDiscoveryStableUrl("granola", raw.url);
6
+ if (!GRANOLA_MEETING_UUID.test(raw.id))
7
+ return undefined;
8
+ return sanitizeDiscoveryStableUrl("granola", `https://notes.granola.ai/d/${raw.id}`);
9
+ }
10
+ // Contact capture at ingest (discovery-contact-capture-v0 R-003/R-004): the
11
+ // provider's own attendee metadata seeds contact-card candidates instead of
12
+ // being discarded. Display strings yield name (+company); the email DOMAIN
13
+ // yields the company and the raw address is discarded right here — it never
14
+ // enters the normalized session; title and LinkedIn ride only when the payload
15
+ // actually carries them.
16
+ function granolaContactHints(raw) {
17
+ const hints = [];
18
+ for (const attendee of raw.attendees ?? []) {
19
+ const name = attendee.name?.trim();
20
+ if (name)
21
+ hints.push(...contactHintsFromDisplayLabel(name, "granola:attendee"));
22
+ const email = attendee.email?.trim();
23
+ if (email) {
24
+ const company = companyFromEmailDomain(email);
25
+ if (company)
26
+ hints.push({ field: "company", value: company, source: "granola:attendee_email_domain" });
27
+ }
28
+ const company = attendee.company?.trim();
29
+ if (company)
30
+ hints.push({ field: "company", value: company, source: "granola:attendee" });
31
+ const title = attendee.title?.trim();
32
+ if (title)
33
+ hints.push({ field: "title", value: title, source: "granola:attendee" });
34
+ const linkedin = attendee.linkedin?.trim();
35
+ if (linkedin && isValidLinkedinUrl(linkedin))
36
+ hints.push({ field: "linkedin", value: linkedin, source: "granola:attendee" });
37
+ }
38
+ const deduped = dedupeContactHints(hints);
39
+ return deduped.length ? deduped : undefined;
40
+ }
41
+ export const granolaDiscoveryAdapter = {
42
+ provider: "granola",
43
+ normalizeSession(raw, connectionAlias) {
44
+ const contactHints = granolaContactHints(raw);
45
+ const stableUrl = granolaStableUrl(raw);
46
+ return {
47
+ schema_version: "0.1",
48
+ provider: "granola",
49
+ connection_alias: connectionAlias,
50
+ runtime_handle: raw.id,
51
+ completed_at: new Date(raw.datetime).toISOString(),
52
+ completion_state: raw.status === "completed" ? "completed" : "in_progress",
53
+ title: raw.title.trim(),
54
+ participants: (raw.attendees ?? []).map((attendee) => attendee.name?.trim() ?? "").filter(Boolean),
55
+ stable_url: stableUrl,
56
+ // open_transcript is observed, not assumed (R-004): a note without an
57
+ // observed transcript grant normalizes with open_transcript: false, and
58
+ // the readiness operation is where the needs_provider_access stop lives.
59
+ capabilities: { list_sessions: true, open_transcript: raw.transcript_access === "granted", stable_link: Boolean(stableUrl), timestamps: true },
60
+ access: "unknown",
61
+ ...(contactHints ? { contact_hints: contactHints } : {}),
62
+ };
63
+ },
64
+ };
65
+ const GONG_CALL_ID = /^[A-Za-z0-9_-]{4,64}$/;
66
+ function gongStableUrl(raw) {
67
+ if (raw.url)
68
+ return sanitizeDiscoveryStableUrl("gong", raw.url);
69
+ if (!GONG_CALL_ID.test(raw.id))
70
+ return undefined;
71
+ return sanitizeDiscoveryStableUrl("gong", `https://app.gong.io/call?id=${raw.id}`);
72
+ }
73
+ function gongCompletedAt(raw) {
74
+ if (raw.ended)
75
+ return new Date(raw.ended).toISOString();
76
+ const started = new Date(raw.started).getTime();
77
+ const duration = Number.isFinite(raw.duration_seconds) ? Number(raw.duration_seconds) * 1000 : 0;
78
+ return new Date(started + duration).toISOString();
79
+ }
80
+ // Contact hints follow the Granola discipline (discovery-contact-capture-v0
81
+ // R-003/R-004) with one Gong-specific rule: a party the provider marks as
82
+ // internal is the company's own rep, not a customer contact candidate, so it
83
+ // contributes no hint. Everyone still appears in `participants` for matching.
84
+ function gongContactHints(raw) {
85
+ const hints = [];
86
+ for (const party of raw.parties ?? []) {
87
+ if (party.affiliation?.trim().toLowerCase() === "internal")
88
+ continue;
89
+ const name = party.name?.trim();
90
+ if (name)
91
+ hints.push(...contactHintsFromDisplayLabel(name, "gong:party"));
92
+ const email = party.email?.trim();
93
+ if (email) {
94
+ const company = companyFromEmailDomain(email);
95
+ if (company)
96
+ hints.push({ field: "company", value: company, source: "gong:party_email_domain" });
97
+ }
98
+ const title = party.title?.trim();
99
+ if (title)
100
+ hints.push({ field: "title", value: title, source: "gong:party" });
101
+ }
102
+ const deduped = dedupeContactHints(hints);
103
+ return deduped.length ? deduped : undefined;
104
+ }
105
+ // Participant labels are party display names plus, for each external party
106
+ // with an address, a domain-only label ("@exampletonbros.example"). The domain label
107
+ // is what an inbox scope's domain_allowlist selects on; the local part of the
108
+ // address is discarded here and never leaves the adapter (R-004/R-005).
109
+ function gongParticipantLabels(raw) {
110
+ const labels = [];
111
+ for (const party of raw.parties ?? []) {
112
+ const name = party.name?.trim();
113
+ if (name)
114
+ labels.push(name);
115
+ if (party.affiliation?.trim().toLowerCase() === "internal")
116
+ continue;
117
+ const email = party.email?.trim().toLowerCase();
118
+ const at = email ? email.lastIndexOf("@") : -1;
119
+ if (email && at > 0 && at < email.length - 1)
120
+ labels.push(`@${email.slice(at + 1)}`);
121
+ }
122
+ return [...new Set(labels)];
123
+ }
124
+ export const gongDiscoveryAdapter = {
125
+ provider: "gong",
126
+ normalizeSession(raw, connectionAlias) {
127
+ const contactHints = gongContactHints(raw);
128
+ const stableUrl = gongStableUrl(raw);
129
+ return {
130
+ schema_version: "0.1",
131
+ provider: "gong",
132
+ connection_alias: connectionAlias,
133
+ runtime_handle: raw.id,
134
+ completed_at: gongCompletedAt(raw),
135
+ completion_state: raw.state === "completed" ? "completed" : "in_progress",
136
+ title: raw.title.trim(),
137
+ participants: gongParticipantLabels(raw),
138
+ stable_url: stableUrl,
139
+ // timestamps: Gong transcript sentences carry millisecond offsets, so
140
+ // envelope locators can cite them. open_transcript is observed, not
141
+ // assumed (R-005).
142
+ capabilities: { list_sessions: true, open_transcript: raw.transcript_access === "granted", stable_link: Boolean(stableUrl), timestamps: true },
143
+ access: "unknown",
144
+ ...(contactHints ? { contact_hints: contactHints } : {}),
145
+ };
146
+ },
147
+ };
148
+ const FIREFLIES_TRANSCRIPT_ID = /^[A-Za-z0-9_-]{8,64}$/;
149
+ function firefliesStableUrl(raw) {
150
+ if (raw.url)
151
+ return sanitizeDiscoveryStableUrl("fireflies", raw.url);
152
+ if (!FIREFLIES_TRANSCRIPT_ID.test(raw.id))
153
+ return undefined;
154
+ // Belief, owned by the packet's feasibility task (T-001): Fireflies links a
155
+ // transcript as a path under app.fireflies.ai.
156
+ return sanitizeDiscoveryStableUrl("fireflies", `https://app.fireflies.ai/transcript/${raw.id}`);
157
+ }
158
+ // The distinct email domains the transcript's own participant metadata
159
+ // carries. OQ-002: Fireflies' role attribution (internal/external) is
160
+ // unverified, so externality derives from email domains only.
161
+ function firefliesParticipantDomains(raw) {
162
+ const domains = new Set();
163
+ for (const participant of raw.participants ?? []) {
164
+ const email = participant.email?.trim().toLowerCase();
165
+ const at = email ? email.lastIndexOf("@") : -1;
166
+ if (email && at > 0 && at < email.length - 1)
167
+ domains.add(email.slice(at + 1));
168
+ }
169
+ return [...domains];
170
+ }
171
+ // A transcript with every identified participant on ONE shared domain shows no
172
+ // external party by domain — the only internal signal the email-domain rule
173
+ // (OQ-002) can support — and is excluded by default. A transcript whose
174
+ // participants carry no emails, or whose emails span domains, is not excluded
175
+ // here; the scope's selectors still have to admit it. Domain-only labels and
176
+ // the internal-only rule are re-derived by the worker from this helper.
177
+ export function firefliesInternalOnly(raw) {
178
+ const domains = firefliesParticipantDomains(raw);
179
+ return domains.length === 1;
180
+ }
181
+ // Contact hints follow the Granola discipline (discovery-contact-capture-v0
182
+ // R-003/R-004). Without provider-declared affiliation (OQ-002) a mixed-domain
183
+ // transcript cannot attribute internality per participant, so every emailed
184
+ // participant contributes a DOMAIN-ONLY company hint and every well-formed
185
+ // display name a full_name hint — candidates for a person to confirm, never
186
+ // confirmed identity. An internal-only transcript (everyone on one domain)
187
+ // yields no hints: its only domain is the company's own.
188
+ function firefliesContactHints(raw) {
189
+ if (firefliesInternalOnly(raw))
190
+ return undefined;
191
+ const hints = [];
192
+ for (const participant of raw.participants ?? []) {
193
+ const name = participant.display_name?.trim();
194
+ if (name)
195
+ hints.push(...contactHintsFromDisplayLabel(name, "fireflies:participant"));
196
+ const email = participant.email?.trim();
197
+ if (email) {
198
+ const company = companyFromEmailDomain(email);
199
+ if (company)
200
+ hints.push({ field: "company", value: company, source: "fireflies:participant_email_domain" });
201
+ }
202
+ }
203
+ const deduped = dedupeContactHints(hints);
204
+ return deduped.length ? deduped : undefined;
205
+ }
206
+ // Participant labels are speaker display names plus, for each participant with
207
+ // an address, a domain-only label ("@exampletonbros.example") — the label an inbox
208
+ // scope's domain_allowlist selects on. OQ-002: with no affiliation field, no
209
+ // label is filtered; the raw email dies here and never leaves the adapter.
210
+ function firefliesParticipantLabels(raw) {
211
+ const labels = [];
212
+ for (const participant of raw.participants ?? []) {
213
+ const name = participant.display_name?.trim();
214
+ if (name)
215
+ labels.push(name);
216
+ const email = participant.email?.trim().toLowerCase();
217
+ const at = email ? email.lastIndexOf("@") : -1;
218
+ if (email && at > 0 && at < email.length - 1)
219
+ labels.push(`@${email.slice(at + 1)}`);
220
+ }
221
+ return [...new Set(labels)];
222
+ }
223
+ export const firefliesDiscoveryAdapter = {
224
+ provider: "fireflies",
225
+ normalizeSession(raw, connectionAlias) {
226
+ const contactHints = firefliesContactHints(raw);
227
+ const stableUrl = firefliesStableUrl(raw);
228
+ return {
229
+ schema_version: "0.1",
230
+ provider: "fireflies",
231
+ connection_alias: connectionAlias,
232
+ runtime_handle: raw.id,
233
+ // The meeting's end time; the start is the honest fallback when the
234
+ // provider reports no end (R-005).
235
+ completed_at: new Date(raw.ended ?? raw.started_at).toISOString(),
236
+ // Belief (T-001): Fireflies marks a finished transcript "processed";
237
+ // "completed" is accepted as the generic terminal spelling. Anything
238
+ // else (including "processing") is honestly in_progress.
239
+ completion_state: (raw.status?.trim().toLowerCase() === "completed" || raw.status?.trim().toLowerCase() === "processed") ? "completed" : "in_progress",
240
+ title: raw.title.trim(),
241
+ participants: firefliesParticipantLabels(raw),
242
+ stable_url: stableUrl,
243
+ // timestamps: Fireflies transcript sentences carry millisecond offsets
244
+ // (belief, T-001), so envelope locators can cite them.
245
+ // open_transcript is observed, not assumed (R-005).
246
+ capabilities: { list_sessions: true, open_transcript: raw.transcript_access === "granted", stable_link: Boolean(stableUrl), timestamps: true },
247
+ access: "unknown",
248
+ ...(contactHints ? { contact_hints: contactHints } : {}),
249
+ };
250
+ },
251
+ };
252
+ // Zoom marks exactly one participant as the account's own: the recording host.
253
+ // The host is the company's rep — no domain label, no contact hint — and every
254
+ // other named participant is treated as external (Gong's internal-party
255
+ // discipline with the only internal marker the recording contract provides).
256
+ // A host name matching nobody leaves every participant external: nothing else
257
+ // is assumed, because Zoom participant records carry no affiliation field.
258
+ function zoomIsHost(raw, displayName) {
259
+ const host = raw.host_display_name?.trim().toLowerCase();
260
+ return Boolean(host) && displayName?.trim().toLowerCase() === host;
261
+ }
262
+ function zoomContactHints(raw) {
263
+ const hints = [];
264
+ for (const participant of raw.participants ?? []) {
265
+ const name = participant.display_name?.trim();
266
+ if (!name || zoomIsHost(raw, name))
267
+ continue;
268
+ hints.push(...contactHintsFromDisplayLabel(name, "zoom:participant"));
269
+ const email = participant.email?.trim();
270
+ if (email) {
271
+ const company = companyFromEmailDomain(email);
272
+ if (company)
273
+ hints.push({ field: "company", value: company, source: "zoom:participant_email_domain" });
274
+ }
275
+ }
276
+ const deduped = dedupeContactHints(hints);
277
+ return deduped.length ? deduped : undefined;
278
+ }
279
+ // Participant labels are display names plus, for each non-host participant
280
+ // with an address, a domain-only label ("@exampletonbros.example") — what an inbox
281
+ // scope's domain_allowlist selects on. The local part of the address is
282
+ // discarded here and never leaves the adapter (R-004/R-005).
283
+ function zoomParticipantLabels(raw) {
284
+ const labels = [];
285
+ for (const participant of raw.participants ?? []) {
286
+ const name = participant.display_name?.trim();
287
+ if (name)
288
+ labels.push(name);
289
+ const email = participant.email?.trim().toLowerCase();
290
+ if (email && name && !zoomIsHost(raw, name)) {
291
+ const at = email.lastIndexOf("@");
292
+ if (at > 0 && at < email.length - 1)
293
+ labels.push(`@${email.slice(at + 1)}`);
294
+ }
295
+ }
296
+ return [...new Set(labels)];
297
+ }
298
+ // Honest completion: the recording's end when observed, otherwise the meeting
299
+ // start — never a fabricated end (R-005).
300
+ function zoomCompletedAt(raw) {
301
+ return new Date(raw.end_time ?? raw.start_time).toISOString();
302
+ }
303
+ export const zoomDiscoveryAdapter = {
304
+ provider: "zoom",
305
+ normalizeSession(raw, connectionAlias) {
306
+ const contactHints = zoomContactHints(raw);
307
+ // A tokenized or credential-bearing link throws here: refused, never
308
+ // cleaned (R-006). No canonical URL is built from the bare UUID — the
309
+ // account subdomain is unknown to the adapter.
310
+ const stableUrl = raw.url ? sanitizeDiscoveryStableUrl("zoom", raw.url) : undefined;
311
+ return {
312
+ schema_version: "0.1",
313
+ provider: "zoom",
314
+ connection_alias: connectionAlias,
315
+ runtime_handle: raw.meeting_uuid,
316
+ completed_at: zoomCompletedAt(raw),
317
+ completion_state: raw.state === "completed" ? "completed" : "in_progress",
318
+ title: raw.topic.trim(),
319
+ participants: zoomParticipantLabels(raw),
320
+ stable_url: stableUrl,
321
+ // timestamps: VTT cue timing is a belief owned by the feasibility task
322
+ // (T-001); until a real transcript is observed it is not claimed.
323
+ capabilities: { list_sessions: true, open_transcript: raw.transcript_available === true, stable_link: Boolean(stableUrl), timestamps: false },
324
+ access: "unknown",
325
+ ...(contactHints ? { contact_hints: contactHints } : {}),
326
+ };
327
+ },
328
+ };
329
+ function teamsCompletedAt(raw) {
330
+ // Honest completion: an explicit provider end wins; without one the start
331
+ // stands in and the record is not yet completed (the state check below).
332
+ const end = raw.end_time?.trim() || raw.start_time;
333
+ if (!Number.isFinite(Date.parse(end)))
334
+ throw new Error("a Teams meeting needs a parseable start and end time");
335
+ return new Date(end).toISOString();
336
+ }
337
+ // Participants are attendee display names only (R-005): no addresses, no AAD
338
+ // ids, no domain labels — the meeting identifier plus these names are all the
339
+ // shared model carries.
340
+ function teamsParticipants(raw) {
341
+ const names = (raw.attendees ?? []).map((attendee) => attendee.display_name?.trim() ?? "").filter(Boolean);
342
+ return [...new Set(names)];
343
+ }
344
+ // Contact hints are external-only (R-005): the tenant-owned meeting is
345
+ // organized by the company's own staff, so the attendee whose display name
346
+ // matches the organizer is the company's representative and contributes no
347
+ // hint. Every other attendee yields a name hint plus, when the record carries
348
+ // an address, a DOMAIN-only company hint — the address itself dies here
349
+ // (R-004), and free-mail domains yield nothing.
350
+ function teamsContactHints(raw) {
351
+ const organizer = raw.organizer_display_name?.trim().toLowerCase();
352
+ const hints = [];
353
+ for (const attendee of raw.attendees ?? []) {
354
+ const name = attendee.display_name?.trim();
355
+ if (!name)
356
+ continue;
357
+ if (organizer && name.toLowerCase() === organizer)
358
+ continue;
359
+ hints.push(...contactHintsFromDisplayLabel(name, "microsoft-teams:attendee"));
360
+ const email = attendee.email?.trim();
361
+ if (email) {
362
+ const company = companyFromEmailDomain(email);
363
+ if (company)
364
+ hints.push({ field: "company", value: company, source: "microsoft-teams:attendee_email_domain" });
365
+ }
366
+ }
367
+ const deduped = dedupeContactHints(hints);
368
+ return deduped.length ? deduped : undefined;
369
+ }
370
+ export const teamsDiscoveryAdapter = {
371
+ provider: "microsoft-teams",
372
+ normalizeSession(raw, connectionAlias) {
373
+ const contactHints = teamsContactHints(raw);
374
+ // A carried URL is sanitized or the record is refused loudly: with no
375
+ // declared Teams form, any URL (tokenized join links included) throws
376
+ // here — never partially cleaned (R-006).
377
+ const stableUrl = raw.url ? sanitizeDiscoveryStableUrl("microsoft-teams", raw.url) : undefined;
378
+ return {
379
+ schema_version: "0.1",
380
+ provider: "microsoft-teams",
381
+ connection_alias: connectionAlias,
382
+ runtime_handle: raw.meeting_id,
383
+ completed_at: teamsCompletedAt(raw),
384
+ completion_state: raw.state === "completed" ? "completed" : "in_progress",
385
+ title: raw.subject.trim(),
386
+ participants: teamsParticipants(raw),
387
+ stable_url: stableUrl,
388
+ // timestamps: the Graph transcript's locator capability is unverified
389
+ // (T-001 owns it), so the capability fails closed until observed.
390
+ // open_transcript is observed, never assumed (R-005).
391
+ capabilities: { list_sessions: true, open_transcript: raw.transcript_available === true, stable_link: Boolean(stableUrl), timestamps: false },
392
+ access: "unknown",
393
+ ...(contactHints ? { contact_hints: contactHints } : {}),
394
+ };
395
+ },
396
+ };
397
+ export const fakeDiscoveryAdapter = {
398
+ provider: "fakecalls",
399
+ normalizeSession(raw, connectionAlias) {
400
+ return {
401
+ schema_version: "0.1",
402
+ provider: "fakecalls",
403
+ connection_alias: connectionAlias,
404
+ runtime_handle: raw.key,
405
+ completed_at: new Date(raw.ended).toISOString(),
406
+ completion_state: raw.done ? "completed" : "in_progress",
407
+ title: raw.subject.trim(),
408
+ participants: raw.people.labels.map((label) => label.trim()).filter(Boolean),
409
+ stable_url: raw.review_link ? sanitizeDiscoveryStableUrl("fakecalls", raw.review_link) : undefined,
410
+ capabilities: { list_sessions: true, open_transcript: raw.transcript_available === true, stable_link: Boolean(raw.review_link), timestamps: false },
411
+ access: "unknown",
412
+ };
413
+ },
414
+ };
415
+ const ZENDESK_COMPLETED_STATUSES = new Set(["solved", "closed"]);
416
+ // Participant labels are display names plus, for the external requester with
417
+ // an address, a domain-only label ("@exampletonbros.example") — the label an inbox
418
+ // scope's domain_allowlist selects on. The assignee is the company's own
419
+ // agent and contributes no domain label. The local part of the requester
420
+ // address is discarded here and never leaves the adapter (R-004/R-005).
421
+ function zendeskParticipantLabels(raw) {
422
+ const labels = [];
423
+ const requesterName = raw.requester?.display_name?.trim();
424
+ if (requesterName)
425
+ labels.push(requesterName);
426
+ const email = raw.requester?.email?.trim().toLowerCase();
427
+ const at = email ? email.lastIndexOf("@") : -1;
428
+ if (email && at > 0 && at < email.length - 1)
429
+ labels.push(`@${email.slice(at + 1)}`);
430
+ const assigneeName = raw.assignee_display_name?.trim();
431
+ if (assigneeName)
432
+ labels.push(assigneeName);
433
+ return [...new Set(labels)];
434
+ }
435
+ // Contact hints follow the Granola discipline (discovery-contact-capture-v0
436
+ // R-003/R-004) with the support-class rule: only the requester is a customer
437
+ // contact candidate — the assignee is company staff, and the raw address dies
438
+ // here, replaced by its domain.
439
+ function zendeskContactHints(raw) {
440
+ const hints = [];
441
+ const name = raw.requester?.display_name?.trim();
442
+ if (name)
443
+ hints.push(...contactHintsFromDisplayLabel(name, "zendesk:requester"));
444
+ const email = raw.requester?.email?.trim();
445
+ if (email) {
446
+ const company = companyFromEmailDomain(email);
447
+ if (company)
448
+ hints.push({ field: "company", value: company, source: "zendesk:requester_email_domain" });
449
+ }
450
+ const deduped = dedupeContactHints(hints);
451
+ return deduped.length ? deduped : undefined;
452
+ }
453
+ // D-001's explicit done-label override (T-005): a thread carrying any of these
454
+ // labels is completed evidence regardless of recency. Kept beside the email
455
+ // class rather than the transport policy — it is class semantics every Gmail
456
+ // runtime shares, versioned with the class, not a request ceiling.
457
+ export const GMAIL_DONE_LABELS = ["done"];
458
+ // D-001 (gmail-discovery-connector-v0 T-005, pinned per OQ-001's
459
+ // recommendation): a thread is "completed" evidence when its last message
460
+ // predates the approved scope's quiet-period boundary, or when it carries an
461
+ // explicit done label. Without a stamped boundary and without a done label the
462
+ // thread is not settled — in_progress, never guessed.
463
+ export function gmailThreadAdmissibility(raw) {
464
+ const labels = new Set((raw.labels ?? []).map((label) => label.trim().toLowerCase()).filter(Boolean));
465
+ if (GMAIL_DONE_LABELS.some((label) => labels.has(label)))
466
+ return "completed";
467
+ const boundary = Date.parse(raw.quiet_period_ends_at ?? "");
468
+ const last = Date.parse(raw.last_message_at);
469
+ if (Number.isFinite(boundary) && Number.isFinite(last) && last < boundary)
470
+ return "completed";
471
+ return "in_progress";
472
+ }
473
+ function gmailDomainLabel(email) {
474
+ // Domain labels exist so an inbox scope's domain_allowlist can select on
475
+ // them (the raw address never leaves the adapter, R-005). Free-mail domains
476
+ // yield no label: "@gmail.com" is noise for a customer selector, and the
477
+ // shared rule already refuses to derive an employer from them.
478
+ if (!companyFromEmailDomain(email))
479
+ return undefined;
480
+ return `@${email.slice(email.lastIndexOf("@") + 1).trim().toLowerCase()}`;
481
+ }
482
+ function internalDomain(raw) {
483
+ const owner = raw.mailbox_owner_email?.trim().toLowerCase();
484
+ const at = owner ? owner.lastIndexOf("@") : -1;
485
+ return owner && at > 0 && at < owner.length - 1 ? owner.slice(at + 1) : undefined;
486
+ }
487
+ // R-004: a thread with no participant outside the mailbox owner's own domain
488
+ // is the company talking to itself in one person's mailbox and is excluded by
489
+ // default. A thread whose participants carry no addresses stays in — the
490
+ // scope's own selectors still have to admit it.
491
+ export function gmailInternalOnly(raw) {
492
+ const own = internalDomain(raw);
493
+ if (!own)
494
+ return false;
495
+ const addressed = (raw.participants ?? []).filter((participant) => participant.email?.trim());
496
+ if (addressed.length === 0)
497
+ return false;
498
+ return addressed.every((participant) => participant.email.trim().toLowerCase().endsWith(`@${own}`));
499
+ }
500
+ // Contact hints follow the shared discipline: display names become full_name
501
+ // candidates, a non-free email DOMAIN becomes the company, and the address
502
+ // itself dies here. The mailbox owner is the company's own rep — their
503
+ // identity contributes no hint and no domain label (per-user ownership,
504
+ // D-002).
505
+ function gmailContactHints(raw) {
506
+ const own = internalDomain(raw);
507
+ const hints = [];
508
+ for (const participant of raw.participants ?? []) {
509
+ const name = participant.display_name?.trim();
510
+ const email = participant.email?.trim();
511
+ if (own && email?.toLowerCase().endsWith(`@${own}`))
512
+ continue;
513
+ if (name)
514
+ hints.push(...contactHintsFromDisplayLabel(name, "gmail:participant"));
515
+ if (email) {
516
+ const company = companyFromEmailDomain(email);
517
+ if (company)
518
+ hints.push({ field: "company", value: company, source: "gmail:participant_email_domain" });
519
+ }
520
+ }
521
+ const deduped = dedupeContactHints(hints);
522
+ return deduped.length ? deduped : undefined;
523
+ }
524
+ export const zendeskDiscoveryAdapter = {
525
+ provider: "zendesk",
526
+ normalizeSession(raw, connectionAlias) {
527
+ // A solved-class ticket with no observed solved time stays in_progress
528
+ // rather than inventing a completion instant (D-001).
529
+ const solvedAt = raw.solved_at?.trim();
530
+ const stableUrl = raw.url ? sanitizeDiscoveryStableUrl("zendesk", raw.url) : undefined;
531
+ const contactHints = zendeskContactHints(raw);
532
+ return {
533
+ schema_version: "0.1",
534
+ provider: "zendesk",
535
+ connection_alias: connectionAlias,
536
+ runtime_handle: raw.id,
537
+ completed_at: solvedAt ? new Date(solvedAt).toISOString() : "",
538
+ completion_state: ZENDESK_COMPLETED_STATUSES.has(raw.status.toLowerCase()) && solvedAt ? "completed" : "in_progress",
539
+ title: raw.subject.trim(),
540
+ participants: zendeskParticipantLabels(raw),
541
+ stable_url: stableUrl,
542
+ // Comment threads carry a created_at per comment, so the timestamps
543
+ // capability rides the same observed grant as open_transcript (R-005):
544
+ // neither is claimed without the connection-level observation.
545
+ capabilities: { list_sessions: true, open_transcript: raw.comment_access === "granted", stable_link: Boolean(stableUrl), timestamps: raw.comment_access === "granted" },
546
+ access: "unknown",
547
+ ...(contactHints ? { contact_hints: contactHints } : {}),
548
+ };
549
+ },
550
+ };
551
+ // Participant labels are display names plus, for each non-free-mail address,
552
+ // a domain-only label ("@exampletonbros.example"). The local part of every address
553
+ // is discarded here and never leaves the adapter (R-005).
554
+ function gmailParticipantLabels(raw) {
555
+ const own = internalDomain(raw);
556
+ const labels = [];
557
+ for (const participant of raw.participants ?? []) {
558
+ const name = participant.display_name?.trim();
559
+ if (name)
560
+ labels.push(name);
561
+ const email = participant.email?.trim().toLowerCase();
562
+ if (!email || (own && email.endsWith(`@${own}`)))
563
+ continue;
564
+ const domain = gmailDomainLabel(email);
565
+ if (domain)
566
+ labels.push(domain);
567
+ }
568
+ return [...new Set(labels)];
569
+ }
570
+ export const gmailDiscoveryAdapter = {
571
+ provider: "gmail",
572
+ normalizeSession(raw, connectionAlias) {
573
+ const contactHints = gmailContactHints(raw);
574
+ // R-006: Gmail declares no ProviderLinkForm in V0 — the believed web
575
+ // thread URL carries the identifier in a fragment (or query) form that the
576
+ // global stable-link rule refuses, and the feasibility task owns verifying
577
+ // the real forms. Links stay unset with a coverage caveat; a tokenized or
578
+ // authenticated URL is never stored. The raw url field dies here.
579
+ void raw.url;
580
+ return {
581
+ schema_version: "0.1",
582
+ provider: "gmail",
583
+ connection_alias: connectionAlias,
584
+ runtime_handle: raw.thread_id,
585
+ completed_at: new Date(raw.last_message_at).toISOString(),
586
+ completion_state: gmailThreadAdmissibility(raw),
587
+ title: raw.snippet_subject.trim() || "(no subject)",
588
+ participants: gmailParticipantLabels(raw),
589
+ // No declared link form → no stable link, and sanitizeDiscoveryStableUrl
590
+ // keeps refusing every Gmail URL shape (query or fragment) unchanged.
591
+ stable_url: undefined,
592
+ // timestamps: email messages carry no per-locus timestamps an envelope
593
+ // locator could cite (the locator format is mm:ss of a recording), so
594
+ // this stays honestly false. open_transcript (body access) is observed,
595
+ // not assumed (R-005).
596
+ capabilities: { list_sessions: true, open_transcript: raw.body_access === "granted", stable_link: false, timestamps: false },
597
+ access: "unknown",
598
+ ...(contactHints ? { contact_hints: contactHints } : {}),
599
+ };
600
+ },
601
+ };
602
+ export const slackDiscoveryAdapter = {
603
+ provider: "slack",
604
+ normalizeSession(raw, connectionAlias) {
605
+ const stableUrl = raw.permalink ? sanitizeDiscoveryStableUrl("slack", raw.permalink) : undefined;
606
+ const lastMessageSeconds = raw.last_message_at ? Number(raw.last_message_at.split(".")[0]) : undefined;
607
+ const fallbackSeconds = Number(raw.thread_ts.split(".")[0]);
608
+ return {
609
+ schema_version: "0.1",
610
+ provider: "slack",
611
+ connection_alias: connectionAlias,
612
+ runtime_handle: `${raw.channel_id}:${raw.thread_ts}`,
613
+ // A thread is complete by selection: processing reads exactly the thread
614
+ // the scope named. Truncation inside a read is recorded on the envelope
615
+ // (transcript_complete), not by pretending the conversation is unfinished.
616
+ completed_at: raw.last_message_at && lastMessageSeconds
617
+ ? new Date(lastMessageSeconds * 1000).toISOString()
618
+ : new Date(fallbackSeconds * 1000).toISOString(),
619
+ completion_state: "completed",
620
+ // Channel-level label (D-001): the disclosure level for chat evidence is
621
+ // the channel, never a speaker, so participants stay empty and the title
622
+ // names the channel or the selector — not people.
623
+ title: raw.channel_name?.trim() ? `Slack thread in #${raw.channel_name.trim()}` : `Slack thread ${raw.channel_id} ${raw.thread_ts}`,
624
+ participants: [],
625
+ stable_url: stableUrl,
626
+ capabilities: { list_sessions: true, open_transcript: true, stable_link: Boolean(stableUrl), timestamps: true },
627
+ access: "unknown",
628
+ };
629
+ },
630
+ };
631
+ // The email class's explicit done override (T-005), adapted to conversation
632
+ // groups: Outlook has no label store, so the override maps Gmail's done label
633
+ // onto Graph message categories — the closest per-message marker a mailbox
634
+ // owner sets by hand. Same value list as GMAIL_DONE_LABELS; same class
635
+ // semantics (a category marker is intent, not transport policy).
636
+ export const OUTLOOK_DONE_CATEGORIES = ["done"];
637
+ // The email class's admissibility rule (outlook-discovery-connector-v0 T-005,
638
+ // pinned as the Gmail packet's D-001 rule applied to conversation groups): a
639
+ // conversation is "completed" evidence when its last message predates the
640
+ // approved scope's quiet-period boundary, or when a message in the group
641
+ // carries an explicit done category. Without a stamped boundary and without a
642
+ // done category the conversation is not settled — in_progress, never guessed.
643
+ export function outlookConversationAdmissibility(raw) {
644
+ const categories = new Set((raw.categories ?? []).map((category) => category.trim().toLowerCase()).filter(Boolean));
645
+ if (OUTLOOK_DONE_CATEGORIES.some((category) => categories.has(category)))
646
+ return "completed";
647
+ const boundary = Date.parse(raw.quiet_period_ends_at ?? "");
648
+ const last = Date.parse(raw.last_message_at);
649
+ if (Number.isFinite(boundary) && Number.isFinite(last) && last < boundary)
650
+ return "completed";
651
+ return "in_progress";
652
+ }
653
+ function outlookDomainLabel(smtp) {
654
+ // Same discipline as Gmail's: domain labels exist so an inbox scope's
655
+ // domain_allowlist can select on them (the raw address never leaves the
656
+ // adapter, R-005), and free-mail domains yield no label — "@outlook.com" is
657
+ // noise for a customer selector, and the shared rule already refuses to
658
+ // derive an employer from free-mail domains.
659
+ if (!companyFromEmailDomain(smtp))
660
+ return undefined;
661
+ return `@${smtp.slice(smtp.lastIndexOf("@") + 1).trim().toLowerCase()}`;
662
+ }
663
+ function outlookInternalDomain(raw) {
664
+ const owner = raw.mailbox_owner_email?.trim().toLowerCase();
665
+ const at = owner ? owner.lastIndexOf("@") : -1;
666
+ return owner && at > 0 && at < owner.length - 1 ? owner.slice(at + 1) : undefined;
667
+ }
668
+ // R-004: a conversation with no participant outside the mailbox owner's own
669
+ // domain is the company talking to itself in one person's mailbox and is
670
+ // excluded by default. A conversation whose participants carry no smtp
671
+ // addresses stays in — the scope's own selectors still have to admit it.
672
+ export function outlookInternalOnly(raw) {
673
+ const own = outlookInternalDomain(raw);
674
+ if (!own)
675
+ return false;
676
+ const addressed = (raw.participants ?? []).filter((participant) => participant.smtp?.trim());
677
+ if (addressed.length === 0)
678
+ return false;
679
+ return addressed.every((participant) => participant.smtp.trim().toLowerCase().endsWith(`@${own}`));
680
+ }
681
+ // Contact hints follow the shared discipline: display names become full_name
682
+ // candidates, a non-free smtp DOMAIN becomes the company, and the address
683
+ // itself dies here. The mailbox owner is the company's own rep — their
684
+ // identity contributes no hint and no domain label (per-user ownership,
685
+ // D-002).
686
+ function outlookContactHints(raw) {
687
+ const own = outlookInternalDomain(raw);
688
+ const hints = [];
689
+ for (const participant of raw.participants ?? []) {
690
+ const name = participant.display_name?.trim();
691
+ const smtp = participant.smtp?.trim();
692
+ if (own && smtp?.toLowerCase().endsWith(`@${own}`))
693
+ continue;
694
+ if (name)
695
+ hints.push(...contactHintsFromDisplayLabel(name, "outlook:participant"));
696
+ if (smtp) {
697
+ const company = companyFromEmailDomain(smtp);
698
+ if (company)
699
+ hints.push({ field: "company", value: company, source: "outlook:participant_smtp_domain" });
700
+ }
701
+ }
702
+ const deduped = dedupeContactHints(hints);
703
+ return deduped.length ? deduped : undefined;
704
+ }
705
+ // Participant labels are display names plus, for each non-free-mail address,
706
+ // a domain-only label ("@exampletonbros.example"). The local part of every address
707
+ // is discarded here and never leaves the adapter (R-005).
708
+ function outlookParticipantLabels(raw) {
709
+ const own = outlookInternalDomain(raw);
710
+ const labels = [];
711
+ for (const participant of raw.participants ?? []) {
712
+ const name = participant.display_name?.trim();
713
+ if (name)
714
+ labels.push(name);
715
+ const smtp = participant.smtp?.trim().toLowerCase();
716
+ if (!smtp || (own && smtp.endsWith(`@${own}`)))
717
+ continue;
718
+ const domain = outlookDomainLabel(smtp);
719
+ if (domain)
720
+ labels.push(domain);
721
+ }
722
+ return [...new Set(labels)];
723
+ }
724
+ export const outlookDiscoveryAdapter = {
725
+ provider: "outlook",
726
+ normalizeSession(raw, connectionAlias) {
727
+ const contactHints = outlookContactHints(raw);
728
+ // R-006: Outlook declares no ProviderLinkForm in V0 — Graph web links
729
+ // embed the item id as an opaque token in the path (or carry it in a
730
+ // query), a form the global stable-link rule refuses, and the feasibility
731
+ // task owns verifying the real forms. Links stay unset; a tokenized or
732
+ // authenticated URL is never stored. The raw url field dies here.
733
+ void raw.url;
734
+ return {
735
+ schema_version: "0.1",
736
+ provider: "outlook",
737
+ connection_alias: connectionAlias,
738
+ runtime_handle: raw.conversation_id,
739
+ completed_at: new Date(raw.last_message_at).toISOString(),
740
+ completion_state: outlookConversationAdmissibility(raw),
741
+ title: raw.subject.trim() || "(no subject)",
742
+ participants: outlookParticipantLabels(raw),
743
+ // No declared link form → no stable link, and
744
+ // sanitizeDiscoveryStableUrl keeps refusing every Outlook web-link
745
+ // shape (tokenized path or query) unchanged.
746
+ stable_url: undefined,
747
+ // timestamps: email messages carry no per-locus timestamps an envelope
748
+ // locator could cite (the locator format is mm:ss of a recording), so
749
+ // this stays honestly false. open_transcript (body access) is observed,
750
+ // not assumed (R-005).
751
+ capabilities: { list_sessions: true, open_transcript: raw.body_access === "granted", stable_link: false, timestamps: false },
752
+ access: "unknown",
753
+ ...(contactHints ? { contact_hints: contactHints } : {}),
754
+ };
755
+ },
756
+ };