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,523 @@
1
+ // `foo discovery zoom …` — the local worker for a company Zoom connection
2
+ // (zoom-discovery-connector-v0, gong playbook verbatim: D-010 local worker).
3
+ //
4
+ // Bounded cloud-recording listing inside an approved study or inbox scope,
5
+ // normalization through the Zoom adapter, exactly one proof-authorized
6
+ // transcript read into process memory, sanitized synthesis in one model
7
+ // request, and `foo discovery apply`. The transcript never reaches argv,
8
+ // stdin, a file, Git, a log, an error, or the host agent's context; every
9
+ // surface this module writes is content-free.
10
+ //
11
+ // What it is not: the hosted, cross-host service the Nango discovery packet
12
+ // specifies, and not a live-verified integration — support stays contract_only
13
+ // until the feasibility (T-001) and dogfood (T-010) evidence exists.
14
+ import { existsSync, readFileSync } from "node:fs";
15
+ import path from "node:path";
16
+ import { hasFlag } from "./cliArgs.js";
17
+ import { loadDiscoveryRun } from "./discoveryArtifacts.js";
18
+ import { discoveryTargetDir, inboxScope, isInboxTarget, localRoot, localTargetName, runDiscoveryCommand, zoomScopeValidationError, } from "./discoveryCommand.js";
19
+ import { openAuthorizedTranscript } from "./discoveryProof.js";
20
+ import { zoomDiscoveryAdapter } from "./discoveryProviders.js";
21
+ import { planDiscoverySync } from "./discoverySync.js";
22
+ import { anthropicSynthesisTransport, defaultSynthesisModel, synthesisCredentialPresent, synthesizeDiscoveryEnvelope } from "./discoverySynthesis.js";
23
+ import { ZOOM_POLICY, resolveZoomConnection, ZoomClient, ZoomRuntimeError, zoomConnectionConfigured, zoomCredentialBroker } from "./zoomNango.js";
24
+ const MATCHABLE_STUDY_STATES = new Set(["awaiting_match", "ambiguous_match", "needs_source_approval", "needs_provider_access", "needs_provider_connector", "needs_research_proof"]);
25
+ const DEFAULT_LOOKBACK_DAYS = 14;
26
+ const SUPPORT_STATE = "contract_only";
27
+ const MODE = "local_worker";
28
+ function result(status, options = {}) {
29
+ return { schema_version: "0.1", status, writes: [], errors: [], handoff: null, next_actions: [], coverage: {}, ...options };
30
+ }
31
+ function exitCodeFor(status) {
32
+ return ["ambiguous_match", "needs_source_approval", "needs_provider_access", "needs_provider_connector", "needs_research_proof", "invalid", "budget_exhausted"].includes(status) ? 1 : 0;
33
+ }
34
+ function outcome(value, json) {
35
+ const exitCode = exitCodeFor(value.status);
36
+ if (json)
37
+ return { output: `${JSON.stringify(value, null, 2)}\n`, exitCode, result: value };
38
+ const lines = [`Discovery (zoom): ${value.status}.`];
39
+ for (const error of value.errors)
40
+ lines.push(`- ${error.message}`);
41
+ if (value.handoff)
42
+ lines.push(`Handoff: ${value.handoff}`);
43
+ for (const action of value.next_actions)
44
+ lines.push(`Next: ${action}`);
45
+ return { output: `${lines.join("\n")}\n`, exitCode, result: value };
46
+ }
47
+ function invalid(message, json, code = "E_DISCOVERY_VALIDATION", targetId, coverage = {}) {
48
+ return outcome(result("invalid", { study_id: targetId, errors: [{ code, message }], coverage }), json);
49
+ }
50
+ // Typed provider failures map to the discovery command's closed status set.
51
+ // Messages are productfoo's own words: no provider body, id, or credential.
52
+ function fromRuntimeError(error, json, targetId, coverage) {
53
+ const base = { study_id: targetId, coverage: { ...coverage, provider_error: error.code, mode: MODE } };
54
+ switch (error.code) {
55
+ case "not_connected":
56
+ return outcome(result("needs_provider_connector", { ...base, errors: [{ code: "E_DISCOVERY_PROVIDER_CONNECTOR", message: error.message }], handoff: "Connect the company Zoom account through Nango and set NANGO_SECRET_KEY and FOO_ZOOM_NANGO_CONNECTION (plus NANGO_HOST / FOO_ZOOM_NANGO_INTEGRATION when they differ from the defaults); or, for this runtime only, set FOO_ZOOM_ACCOUNT_ID, FOO_ZOOM_CLIENT_ID, and FOO_ZOOM_CLIENT_SECRET from an administrator-issued server-to-server app." }), json);
57
+ case "auth_expired":
58
+ case "access_denied":
59
+ return outcome(result("needs_provider_access", { ...base, errors: [{ code: "E_DISCOVERY_PROVIDER_ACCESS", message: error.message }], handoff: "Ask the Zoom administrator to refresh the connection or grant the recording-read permission, then rerun." }), json);
60
+ case "budget_exhausted":
61
+ // A rate-limit stop is the quota half of R-004: the run ends, nothing
62
+ // widens, nothing retries. The next run needs a narrower window.
63
+ return outcome(result("budget_exhausted", { ...base, errors: [{ code: "E_DISCOVERY_VALIDATION", message: error.message }], handoff: "The agreed page or quota ceiling stopped this run; narrow the window or selectors and rerun — productfoo never widens the window or auto-retries." }), json);
64
+ default:
65
+ return outcome(result("invalid", { ...base, errors: [{ code: "E_DISCOVERY_VALIDATION", message: error.message }], handoff: error.code === "transcript_too_large" || error.code === "provider_timeout" ? "Nothing was written and no retry was attempted; a new attempt needs a fresh match." : null }), json);
66
+ }
67
+ }
68
+ function isoDaysAgo(now, days) {
69
+ return new Date(Date.parse(now) - days * 86_400_000).toISOString();
70
+ }
71
+ function loadTarget(root, targetId, env, now, phase = "list") {
72
+ if (isInboxTarget(targetId)) {
73
+ const scope = inboxScope(root, targetId);
74
+ if (scope.provider.toLowerCase() !== "zoom")
75
+ throw new Error("this inbox scope is not a Zoom scope");
76
+ if (scope.state !== "approved")
77
+ throw new Error("this inbox scope is not approved");
78
+ // R-004: validation is pre-flight — an invalid scope never reaches the
79
+ // provider. Zoom selectors use the shared inbox vocabulary: named external
80
+ // participants, external companies or email domains, and title tokens.
81
+ const scopeError = zoomScopeValidationError({
82
+ window: scope.time_window,
83
+ external_names: scope.participant_allowlist,
84
+ external_domains: scope.domain_allowlist,
85
+ title_tokens: scope.title_tokens,
86
+ });
87
+ if (scopeError)
88
+ throw new Error(scopeError);
89
+ const from = new Date(scope.time_window.start);
90
+ const to = new Date(scope.time_window.end);
91
+ return { target_id: targetId, lane: "unassigned_call_evidence", dir: discoveryTargetDir(root, targetId), connection_alias: scope.connection_alias, window: { from: from.toISOString(), to: to.toISOString() } };
92
+ }
93
+ const dir = discoveryTargetDir(root, targetId);
94
+ const run = loadDiscoveryRun(dir);
95
+ if (run.source.provider.toLowerCase() !== "zoom")
96
+ throw new Error("this study is not a Zoom study");
97
+ // Listing needs a matchable study; processing needs the state `match` left
98
+ // behind (ready_to_synthesize) — the proof check decides the rest.
99
+ const allowed = phase === "process" ? run.state === "ready_to_synthesize" || MATCHABLE_STUDY_STATES.has(run.state) : MATCHABLE_STUDY_STATES.has(run.state);
100
+ if (!allowed)
101
+ throw new Error(`study ${targetId} is not ready for provider reads (state ${run.state})`);
102
+ let from;
103
+ let to;
104
+ if (run.match.expected_local_date) {
105
+ const day = Date.parse(`${run.match.expected_local_date}T00:00:00Z`);
106
+ if (!Number.isFinite(day))
107
+ throw new Error("the study's expected date is invalid");
108
+ from = new Date(day - 86_400_000).toISOString();
109
+ to = new Date(day + 2 * 86_400_000).toISOString();
110
+ }
111
+ else {
112
+ const lookback = Number(env.FOO_ZOOM_LOOKBACK_DAYS ?? DEFAULT_LOOKBACK_DAYS);
113
+ if (!Number.isFinite(lookback) || lookback <= 0 || lookback > 90)
114
+ throw new Error("FOO_ZOOM_LOOKBACK_DAYS must be between 1 and 90");
115
+ from = isoDaysAgo(now, lookback);
116
+ to = now;
117
+ }
118
+ // R-004 applies to study scopes too: a zoom study names its customer — the
119
+ // study target is the named external participant or company, and the match
120
+ // constraints narrow further. A study with neither is invalid.
121
+ const selectorError = zoomScopeValidationError({
122
+ window: { start: from, end: to },
123
+ external_names: [...(run.match?.required_participants ?? []), ...(run.target?.name?.trim() ? [run.target.name.trim()] : [])],
124
+ external_domains: run.target?.organization?.trim() ? [run.target.organization.trim()] : [],
125
+ title_tokens: run.match?.required_title_tokens ?? [],
126
+ });
127
+ if (selectorError)
128
+ throw new Error(selectorError);
129
+ return { target_id: targetId, lane: "study_linked", dir, connection_alias: run.source.connection_alias, window: { from, to } };
130
+ }
131
+ async function connect(deps) {
132
+ const connection = await resolveZoomConnection({ env: deps.env, fetchImpl: deps.fetchImpl });
133
+ const client = new ZoomClient(connection.credential, { fetchImpl: deps.fetchImpl, sleep: deps.sleep, fetchTimeoutMs: deps.fetchTimeoutMs });
134
+ return { connection, client };
135
+ }
136
+ // R-004: a recording whose only named participants are the account's own host
137
+ // is the company talking to itself and is excluded by default. A recording
138
+ // with no participant data stays in — the scope's own selectors still have to
139
+ // admit it (absence is not proof of internality).
140
+ function internalOnly(raw) {
141
+ const named = (raw.participants ?? []).filter((participant) => participant.display_name?.trim());
142
+ if (named.length === 0)
143
+ return false;
144
+ const host = raw.host_display_name?.trim().toLowerCase();
145
+ return Boolean(host) && named.every((participant) => participant.display_name?.trim().toLowerCase() === host);
146
+ }
147
+ async function listSessions(target, connection, client) {
148
+ const listing = await client.listSessions("me", target.window);
149
+ // A ceiling stop is final for the run (R-004/R-010): no further provider
150
+ // reads happen — nothing waits for a wider window because productfoo never widens.
151
+ if (listing.truncated)
152
+ return { sessions: [], listed: listing.recordings.length, pages: listing.pages, truncated: true, internal_only_excluded: 0, incomplete: 0 };
153
+ const sessions = [];
154
+ let internalExcluded = 0;
155
+ let incomplete = 0;
156
+ for (const raw of listing.recordings) {
157
+ if (internalOnly(raw)) {
158
+ internalExcluded += 1;
159
+ continue;
160
+ }
161
+ const session = zoomDiscoveryAdapter.normalizeSession(raw, target.connection_alias);
162
+ if (session.completion_state !== "completed") {
163
+ incomplete += 1;
164
+ continue;
165
+ }
166
+ sessions.push({ ...session, access: connection.transcript_scope === "missing" ? "denied" : "available" });
167
+ }
168
+ return { sessions, listed: listing.recordings.length, pages: listing.pages, truncated: listing.truncated, internal_only_excluded: internalExcluded, incomplete };
169
+ }
170
+ async function readyCommand(json, deps) {
171
+ const env = deps.env;
172
+ const coverage = {
173
+ provider: "zoom",
174
+ mode: MODE,
175
+ support_state: SUPPORT_STATE,
176
+ connection_owner: "workspace",
177
+ configured: zoomConnectionConfigured(env),
178
+ credential_broker: zoomCredentialBroker(env),
179
+ synthesis: { available: synthesisCredentialPresent(env), model: defaultSynthesisModel(env) },
180
+ policy: { ...ZOOM_POLICY },
181
+ };
182
+ try {
183
+ const { connection, client } = await connect(deps);
184
+ // The readiness probe is one metadata call inside a one-hour window: it
185
+ // proves the credential and the listing permission without reading
186
+ // recordings or transcripts.
187
+ const now = deps.now();
188
+ await client.listSessions("me", { from: isoDaysAgo(now, 1 / 24), to: now });
189
+ coverage.connection = connection.connection_ref;
190
+ coverage.credential_broker = connection.broker;
191
+ coverage.credential_type = connection.credential_type;
192
+ coverage.capabilities = { list_sessions: true, open_transcript: connection.transcript_scope !== "missing", stable_link: true, timestamps: false, transcript_scope: connection.transcript_scope };
193
+ coverage.provider_calls = client.requests;
194
+ return outcome(result("ok", { coverage, next_actions: [
195
+ ...(connection.broker === "direct" ? ["Credential broker is direct: this runtime holds a server-to-server app credential itself. Refresh and revoke happen in Zoom, not centrally; move to Nango for the company connection."] : []),
196
+ "Approve a bounded Zoom scope, then run foo discovery zoom list <study-id|inbox:scope-id>.", ...(coverage.synthesis && !coverage.synthesis.available ? ["Set ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN) before processing a transcript."] : [])
197
+ ] }), json);
198
+ }
199
+ catch (error) {
200
+ if (error instanceof ZoomRuntimeError)
201
+ return fromRuntimeError(error, json, undefined, coverage);
202
+ return invalid("Zoom readiness failed closed without exposing private source content.", json, "E_DISCOVERY_VALIDATION", undefined, coverage);
203
+ }
204
+ }
205
+ async function listCommand(root, targetId, json, deps) {
206
+ if (!targetId)
207
+ return invalid("Usage: foo discovery zoom list <study-id|inbox:scope-id>", json);
208
+ const now = deps.now();
209
+ let target;
210
+ try {
211
+ target = loadTarget(root, targetId, deps.env, now);
212
+ }
213
+ catch (error) {
214
+ return invalid(error instanceof Error ? error.message : "invalid discovery target", json, "E_DISCOVERY_VALIDATION", targetId);
215
+ }
216
+ const coverage = { mode: MODE, window: target.window, lane: target.lane, transcript_opened: false };
217
+ try {
218
+ const { connection, client } = await connect(deps);
219
+ coverage.connection = connection.connection_ref;
220
+ const listed = await listSessions(target, connection, client);
221
+ Object.assign(coverage, { recordings_listed: listed.listed, pages: listed.pages, truncated: listed.truncated, internal_only_excluded: listed.internal_only_excluded, incomplete_excluded: listed.incomplete, candidates_evaluated: listed.sessions.length, provider_calls: client.requests });
222
+ if (listed.truncated) {
223
+ return outcome(result("budget_exhausted", {
224
+ study_id: targetId,
225
+ coverage: { ...coverage, provider_error: "budget_exhausted" },
226
+ errors: [{ code: "E_DISCOVERY_VALIDATION", message: `The window holds more recordings than the ${ZOOM_POLICY.max_list_pages}-page ceiling; narrow the window or selectors instead of widening the read.` }],
227
+ handoff: "Narrow the approved window or selectors and list again; the run never widens the window and never retries.",
228
+ }), json);
229
+ }
230
+ if (listed.sessions.length === 0)
231
+ return outcome(result("awaiting_match", { study_id: targetId, coverage, next_actions: ["No completed recording with a non-host participant was listed inside the approved window."] }), json);
232
+ if (target.lane === "study_linked") {
233
+ const matched = await runDiscoveryCommand(root, ["match", targetId, "--sessions-stdin", "--json"], JSON.stringify(listed.sessions), now);
234
+ return outcome({ ...matched.result, coverage: { ...matched.result.coverage, ...coverage }, next_actions: [...matched.result.next_actions, ...(matched.result.status === "ok" ? [`Run foo discovery zoom process ${targetId} to open the one matched transcript and apply the sanitized evidence.`] : [])] }, json);
235
+ }
236
+ // Inbox: one session at a time so each source gets its own one-time proof.
237
+ let lastMiss = null;
238
+ let evaluated = 0;
239
+ for (const session of listed.sessions) {
240
+ evaluated += 1;
241
+ const matched = await runDiscoveryCommand(root, ["match", targetId, "--sessions-stdin", "--json"], JSON.stringify([session]), now);
242
+ if (matched.result.status === "awaiting_match") {
243
+ lastMiss = matched.result;
244
+ continue;
245
+ }
246
+ const remaining = listed.sessions.length - evaluated;
247
+ return outcome({
248
+ ...matched.result,
249
+ coverage: { ...matched.result.coverage, ...coverage, candidates_evaluated: evaluated, candidates_remaining: remaining },
250
+ next_actions: [
251
+ ...matched.result.next_actions,
252
+ ...(matched.result.status === "ok" ? [`Run foo discovery zoom process ${targetId} to open this one transcript and apply the sanitized evidence.`] : []),
253
+ ...(remaining > 0 ? [`${remaining} more listed recording(s) were not evaluated; run list again after this one is processed.`] : []),
254
+ ],
255
+ }, json);
256
+ }
257
+ return outcome(result("awaiting_match", { study_id: targetId, coverage: { ...coverage, candidates_evaluated: evaluated }, next_actions: lastMiss?.next_actions ?? ["No listed recording matched the approved scope; nothing was opened."] }), json);
258
+ }
259
+ catch (error) {
260
+ if (error instanceof ZoomRuntimeError)
261
+ return fromRuntimeError(error, json, targetId, coverage);
262
+ return invalid("Zoom listing failed closed without exposing private source content.", json, "E_DISCOVERY_VALIDATION", targetId, coverage);
263
+ }
264
+ }
265
+ function readMatch(root, targetId) {
266
+ const file = path.join(localRoot(root), localTargetName(targetId), "match.json");
267
+ if (!existsSync(file))
268
+ return null;
269
+ try {
270
+ return JSON.parse(readFileSync(file, "utf8"));
271
+ }
272
+ catch {
273
+ return null;
274
+ }
275
+ }
276
+ function studyHypotheses(dir) {
277
+ const file = path.join(dir, "study.md");
278
+ if (!existsSync(file))
279
+ return [];
280
+ const out = [];
281
+ for (const line of readFileSync(file, "utf8").split("\n")) {
282
+ const match = /^\s*(?:[-*]|\d+\.)?\s*\**(H-\d{3})\**\s*[:—–-]?\s*(.+?)\s*$/.exec(line);
283
+ if (match && !out.some((entry) => entry.id === match[1]))
284
+ out.push({ id: match[1], text: match[2] });
285
+ }
286
+ return out;
287
+ }
288
+ // WebVTT → plain dialogue, one line per cue, timestamps dropped (the session's
289
+ // declared capability is none until cue timing is verified — T-001). A cue's
290
+ // speaker label becomes a role when it names an observed participant, and any
291
+ // observed name or address inside the cue text is replaced the same way: the
292
+ // rendered text carries roles, never identities.
293
+ function renderZoomVtt(vtt, roles) {
294
+ const lines = [];
295
+ for (const rawLine of vtt.split(/\r?\n/)) {
296
+ const line = rawLine.trim();
297
+ if (!line || line === "WEBVTT" || /^(Kind|Language):/.test(line) || /^\d+$/.test(line) || line.includes("-->"))
298
+ continue;
299
+ let text = line;
300
+ const speakerMatch = /^([^:]{1,80}):\s+(.*)$/.exec(line);
301
+ if (speakerMatch) {
302
+ const role = roles.get(speakerMatch[1].trim().toLowerCase());
303
+ if (role)
304
+ text = `${role}: ${speakerMatch[2]}`;
305
+ }
306
+ for (const [name, role] of roles) {
307
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
308
+ text = text.replace(new RegExp(escaped, "gi"), role);
309
+ }
310
+ lines.push(text);
311
+ }
312
+ return lines.join("\n");
313
+ }
314
+ function containsIdentity(envelopeText, identities) {
315
+ const haystack = envelopeText.toLowerCase();
316
+ return identities.some((identity) => identity.length >= 4 && haystack.includes(identity.toLowerCase()));
317
+ }
318
+ // The identity screen set: every display name and address the listing showed
319
+ // for the matched recording. The render map sends them to roles; the post-
320
+ // synthesis screen refuses any that reappear in derived text.
321
+ function identityScreen(raw) {
322
+ const roles = new Map();
323
+ const identities = [];
324
+ const observe = (label, role) => {
325
+ const value = label?.trim().toLowerCase();
326
+ if (!value)
327
+ return;
328
+ if (!roles.has(value))
329
+ roles.set(value, role);
330
+ identities.push(label.trim());
331
+ };
332
+ for (const participant of raw.participants ?? []) {
333
+ const isHost = participant.display_name?.trim().toLowerCase() === raw.host_display_name?.trim().toLowerCase();
334
+ const role = isHost ? "Company representative" : "Customer";
335
+ observe(participant.display_name, role);
336
+ observe(participant.email, role);
337
+ }
338
+ observe(raw.host_display_name, "Company representative");
339
+ return { roles, identities };
340
+ }
341
+ // The matched recording is re-listed inside a tight window around its own
342
+ // completion time; its manifest — never a second source — names the one
343
+ // transcript file this run may download. No request ever carries more than one
344
+ // recording identifier: the listing is a per-user time window, the download is
345
+ // one URL (T-007).
346
+ async function findMatchedRecording(client, session) {
347
+ const completed = Date.parse(session.completed_at);
348
+ if (!Number.isFinite(completed))
349
+ return undefined;
350
+ const listing = await client.listSessions("me", { from: new Date(completed - 86_400_000).toISOString(), to: new Date(completed + 2 * 86_400_000).toISOString() });
351
+ if (listing.truncated)
352
+ throw new ZoomRuntimeError("budget_exhausted", "the re-list window hit the page ceiling; nothing was opened");
353
+ const recordings = listing.recordings.filter((raw) => raw.meeting_uuid === session.runtime_handle);
354
+ return recordings.length === 1 ? recordings[0] : undefined;
355
+ }
356
+ async function processCommand(root, targetId, json, deps) {
357
+ if (!targetId)
358
+ return invalid("Usage: foo discovery zoom process <study-id|inbox:scope-id>", json);
359
+ const now = deps.now();
360
+ let target;
361
+ try {
362
+ target = loadTarget(root, targetId, deps.env, now, "process");
363
+ }
364
+ catch (error) {
365
+ return invalid(error instanceof Error ? error.message : "invalid discovery target", json, "E_DISCOVERY_VALIDATION", targetId);
366
+ }
367
+ const match = readMatch(root, targetId);
368
+ if (!match || !Array.isArray(match.candidates) || match.candidates.length === 0)
369
+ return invalid("No matched Zoom recording is recorded for this target; run foo discovery zoom list first.", json, "E_DISCOVERY_VALIDATION", targetId);
370
+ if (match.candidates.length > 1)
371
+ return outcome(result("ambiguous_match", { study_id: targetId, errors: [{ code: "E_DISCOVERY_AMBIGUOUS_MATCH", message: "Several recordings matched; a person must select one before any transcript is opened." }], handoff: `foo discovery review ${targetId} select-source --fingerprint <sha256> --by <person>` }), json);
372
+ const candidate = match.candidates[0];
373
+ const session = candidate.session;
374
+ if (!synthesisCredentialPresent(deps.env))
375
+ return outcome(result("needs_research_proof", { study_id: targetId, errors: [{ code: "E_DISCOVERY_RESEARCH_PROOF", message: "No model credential is available for server-side synthesis, so the transcript was not opened." }], handoff: "Set ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN) for this runtime, then rerun process." }), json);
376
+ const model = defaultSynthesisModel(deps.env);
377
+ const coverage = { mode: MODE, lane: target.lane, policy_version: ZOOM_POLICY.policy_version, model };
378
+ const hypotheses = target.lane === "study_linked" ? studyHypotheses(target.dir) : [];
379
+ if (target.lane === "study_linked" && hypotheses.length === 0)
380
+ return invalid("The study records no H-### hypotheses in study.md; study-linked evidence needs one to test.", json, "E_DISCOVERY_VALIDATION", targetId);
381
+ let client;
382
+ try {
383
+ const connected = await connect(deps);
384
+ client = connected.client;
385
+ coverage.connection = connected.connection.connection_ref;
386
+ if (connected.connection.transcript_scope === "missing") {
387
+ return outcome(result("needs_provider_access", { study_id: targetId, errors: [{ code: "E_DISCOVERY_PROVIDER_ACCESS", message: "The Zoom credential has no recording-download permission; the transcript was not opened." }], handoff: session.stable_url ? `Request transcript access for ${session.stable_url}` : "Request transcript access from the Zoom administrator.", coverage }), json);
388
+ }
389
+ // Re-list to find the one matched recording and its own transcript file.
390
+ const raw = await findMatchedRecording(client, session);
391
+ if (!raw) {
392
+ return outcome(result("needs_provider_access", { study_id: targetId, errors: [{ code: "E_DISCOVERY_PROVIDER_ACCESS", message: "The matched recording no longer appears inside its bounded window, or its identity is ambiguous; nothing was opened." }], handoff: `Run foo discovery zoom list ${targetId} again to re-match.`, coverage }), json);
393
+ }
394
+ const transcriptFile = (raw.recording_files ?? []).find((file) => file.file_type?.toUpperCase() === "TRANSCRIPTION" && file.download_url);
395
+ if (!transcriptFile?.download_url) {
396
+ return outcome(result("needs_provider_access", { study_id: targetId, errors: [{ code: "E_DISCOVERY_PROVIDER_ACCESS", message: "The matched recording shows no downloadable transcript file; nothing was opened." }], handoff: `Run foo discovery zoom list ${targetId} again after Zoom finishes processing the recording.`, coverage }), json);
397
+ }
398
+ const { roles, identities } = identityScreen(raw);
399
+ let openError;
400
+ let transcriptText;
401
+ try {
402
+ const vtt = await openAuthorizedTranscript({ study_dir: target.dir, local_root: localRoot(root), study_id: targetId, source_fingerprint: candidate.source_fingerprint, now }, async () => {
403
+ try {
404
+ return (await client.transcript(transcriptFile.download_url)).vtt;
405
+ }
406
+ catch (error) {
407
+ if (error instanceof ZoomRuntimeError)
408
+ openError = error;
409
+ throw error;
410
+ }
411
+ });
412
+ transcriptText = renderZoomVtt(vtt, roles);
413
+ }
414
+ catch (error) {
415
+ if (openError)
416
+ return fromRuntimeError(openError, json, targetId, { ...coverage, transcript_body_requests: client.requests.transcript });
417
+ return outcome(result("needs_research_proof", { study_id: targetId, errors: [{ code: "E_DISCOVERY_RESEARCH_PROOF", message: "The transcript was not opened: the discovery proof is missing, expired, already used, or no longer matches the approval." }], handoff: `Run foo discovery zoom list ${targetId} again to re-match and mint a fresh proof.`, coverage }), json);
418
+ }
419
+ coverage.transcript_body_requests = client.requests.transcript;
420
+ // Budget accounting for the shared account (R-010): the model-input
421
+ // estimate is checked against the policy ceiling before any request.
422
+ const estimatedTokens = Math.ceil(transcriptText.length / 4);
423
+ coverage.budget = { estimated_model_input_tokens: estimatedTokens, max_model_input_tokens: ZOOM_POLICY.max_model_input_tokens, provider_calls: { ...client.requests } };
424
+ if (estimatedTokens > ZOOM_POLICY.max_model_input_tokens) {
425
+ transcriptText = null;
426
+ return fromRuntimeError(new ZoomRuntimeError("transcript_too_large", "the transcript exceeds the model-input ceiling; nothing was synthesized"), json, targetId, coverage);
427
+ }
428
+ let synthesized;
429
+ try {
430
+ synthesized = await synthesizeDiscoveryEnvelope({
431
+ target_id: targetId,
432
+ lane: target.lane,
433
+ source: { provider: "zoom", ...(session.stable_url ? { stable_url: session.stable_url } : {}), source_fingerprint: candidate.source_fingerprint, completed_at: session.completed_at, locator_capability: session.capabilities.timestamps ? "timestamp" : "none" },
434
+ title: session.title,
435
+ hypotheses,
436
+ transcript_text: transcriptText,
437
+ transcript_complete: true,
438
+ // The validator requires an explicit caveat whenever a capability is
439
+ // missing: no canonical link, and — zoom-specific — unverified cue
440
+ // timing means the evidence carries no locators.
441
+ caveats: [
442
+ ...(session.stable_url ? [] : ["The provider returned no canonical recording link for this recording."]),
443
+ ...(session.capabilities.timestamps ? [] : ["The provider transcript's cue timing is unverified; this evidence carries no locators."]),
444
+ ],
445
+ }, { model, transport: deps.transport });
446
+ }
447
+ catch {
448
+ return invalid("Synthesis did not produce a valid closed envelope; nothing was written and the transcript was discarded.", json, "E_DISCOVERY_INVALID_ENVELOPE", targetId, coverage);
449
+ }
450
+ finally {
451
+ transcriptText = null;
452
+ }
453
+ coverage.synthesis_usage = synthesized.usage ?? null;
454
+ // Privacy screen beyond the envelope validator: participant names and
455
+ // addresses from the provider's own metadata must not appear in derived
456
+ // text.
457
+ const derivedText = [
458
+ ...synthesized.envelope.findings.flatMap((finding) => [finding.headline, finding.paraphrase, finding.dissent]),
459
+ ...synthesized.envelope.coverage.caveats,
460
+ ].join("\n");
461
+ if (containsIdentity(derivedText, identities))
462
+ return invalid("Synthesis leaked a participant identity into derived text; nothing was written.", json, "E_DISCOVERY_INVALID_ENVELOPE", targetId, coverage);
463
+ const applied = await runDiscoveryCommand(root, ["apply", targetId, "--evidence-stdin", "--json"], JSON.stringify(synthesized.envelope), now);
464
+ return outcome({ ...applied.result, coverage: { ...applied.result.coverage, ...coverage } }, json);
465
+ }
466
+ catch (error) {
467
+ if (error instanceof ZoomRuntimeError)
468
+ return fromRuntimeError(error, json, targetId, { ...coverage, transcript_body_requests: client?.requests.transcript ?? 0 });
469
+ return invalid("Zoom processing failed closed without exposing private source content.", json, "E_DISCOVERY_VALIDATION", targetId, coverage);
470
+ }
471
+ }
472
+ async function syncCommand(root, json, deps) {
473
+ const now = deps.now();
474
+ const plan = planDiscoverySync(root, now);
475
+ const lanes = plan.lanes.filter((lane) => lane.provider.toLowerCase() === "zoom");
476
+ if (lanes.length === 0)
477
+ return outcome(result("clean_no_op", { errors: plan.errors.map((message) => ({ code: "E_DISCOVERY_VALIDATION", message })), next_actions: ["No approved Zoom lane is active; approve a study source or an inbox scope first."], coverage: { mode: MODE, lanes: 0, connector_reads: 0 } }), json);
478
+ const results = [];
479
+ // Metadata and match only: a transcript is opened only by an explicit
480
+ // `process` on one target. The loop stays unarmed (R-010).
481
+ for (const lane of lanes) {
482
+ const targetId = lane.kind === "inbox" ? `inbox:${lane.scope_id}` : lane.study_id;
483
+ const listed = await listCommand(root, targetId, true, deps);
484
+ results.push({ lane_id: lane.lane_id, status: listed.result.status, handoff: listed.result.handoff });
485
+ }
486
+ const ready = results.filter((entry) => entry.status === "ok").map((entry) => entry.lane_id);
487
+ return outcome(result("ok", {
488
+ errors: plan.errors.map((message) => ({ code: "E_DISCOVERY_VALIDATION", message })),
489
+ next_actions: [
490
+ ...ready.map((laneId) => `${laneId}: one recording matched — run foo discovery zoom process ${laneId.replace(/^study:/, "")} to open it.`),
491
+ ...results.filter((entry) => entry.status !== "ok").map((entry) => `${entry.lane_id}: ${entry.status}${entry.handoff ? ` — ${entry.handoff}` : ""}`),
492
+ ],
493
+ coverage: { mode: MODE, lanes: results.length, transcripts_opened: 0, results },
494
+ }), json);
495
+ }
496
+ export async function runZoomDiscovery(root, args, deps = {}) {
497
+ const json = hasFlag(args, "--json");
498
+ const env = deps.env ?? process.env;
499
+ const resolved = {
500
+ fetchImpl: deps.fetchImpl ?? globalThis.fetch,
501
+ env,
502
+ now: deps.now ?? (() => new Date().toISOString()),
503
+ sleep: deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
504
+ transport: deps.transport ?? anthropicSynthesisTransport({ fetchImpl: deps.fetchImpl, env }),
505
+ fetchTimeoutMs: deps.fetchTimeoutMs ?? ZOOM_POLICY.fetch_timeout_ms,
506
+ };
507
+ const command = args[0];
508
+ const target = args[1] && !args[1].startsWith("--") ? args[1] : undefined;
509
+ try {
510
+ if (command === "ready")
511
+ return await readyCommand(json, resolved);
512
+ if (command === "list")
513
+ return await listCommand(root, target, json, resolved);
514
+ if (command === "process")
515
+ return await processCommand(root, target, json, resolved);
516
+ if (command === "sync")
517
+ return await syncCommand(root, json, resolved);
518
+ return invalid("Try: ready | list <study-id|inbox:scope-id> | process <study-id|inbox:scope-id> | sync", json);
519
+ }
520
+ catch {
521
+ return invalid("Zoom discovery failed closed without exposing private source content.", json);
522
+ }
523
+ }
@@ -0,0 +1,13 @@
1
+ export type ElicitationSurface = "widget" | "text";
2
+ export type SurfaceDecision = {
3
+ surface: ElicitationSurface;
4
+ runtime: string;
5
+ reason: string;
6
+ };
7
+ export type SurfaceContext = {
8
+ env?: NodeJS.ProcessEnv;
9
+ isTTY?: boolean;
10
+ widgetCapableRuntimes?: Iterable<string>;
11
+ };
12
+ export declare function detectRuntimeId(env?: NodeJS.ProcessEnv): string;
13
+ export declare function detectElicitationSurface(context?: SurfaceContext): SurfaceDecision;
@@ -0,0 +1,77 @@
1
+ // Elicitation surface selection for productfoo's branded question moment.
2
+ //
3
+ // Packet: specs/foo-branded-elicitation-v0 — T-001 (render-surface decision +
4
+ // capability detection). This module is the spike's durable artifact: it records the
5
+ // decision in code and resolves, for any runtime and output context, whether productfoo should
6
+ // ask a question as a branded widget or as numbered text.
7
+ //
8
+ // DECISION (R-001 / D-002): productfoo's branded question surface renders through an inline
9
+ // widget MCP (HTML + a `sendPrompt` round-trip), NOT the native Claude Code
10
+ // `AskUserQuestion` dialog and NOT a plain MCP tool. The `AskUserQuestion` chrome is
11
+ // unbrandable — only a <=12-char `header` chip plus the question/option text is
12
+ // controllable, and a `PreToolUse` hook can rewrite that input but never the chrome.
13
+ // A plain MCP tool renders no UI at all. An inline widget MCP (a `show_widget`-style
14
+ // elicitation surface with a `sendPrompt` callback) is the only mechanism that yields a
15
+ // fully productfoo-branded interactive question surface.
16
+ //
17
+ // CAPABILITY RULE (R-003 / R-004): the widget is opt-in and "full tier". Numbered text
18
+ // is the DEFAULT — every runtime can render it and a coding agent can read it. The
19
+ // widget is selected only when the caller has not forced text, output is an interactive
20
+ // TTY, and the active runtime advertises a branded-widget renderer. Precedence is
21
+ // safety-first:
22
+ // 1. FOO_ELICITATION_SURFACE=text -> text (forcing text is always safe)
23
+ // 2. non-interactive stdout -> text (never render a widget into a pipe or
24
+ // agent reader — the sharpest constraint)
25
+ // 3. FOO_ELICITATION_SURFACE=widget -> widget (explicit opt-in; TTY already confirmed)
26
+ // 4. runtime advertises a renderer -> widget
27
+ // 5. otherwise -> text (the default)
28
+ // Runtimes that ship a productfoo-branded widget renderer today. Claude Code can render an
29
+ // inline widget MCP (show_widget elicitation + sendPrompt); Codex and unknown runtimes
30
+ // have no branded-widget renderer yet, so they take the text path. Extend the set with
31
+ // FOO_WIDGET_RUNTIMES (comma-separated) as new runtimes gain a renderer.
32
+ const DEFAULT_WIDGET_CAPABLE_RUNTIMES = ["claude-code"];
33
+ // Detect the active coding-agent runtime from the environment, mirroring the runtime
34
+ // detection productfoo already uses for adapter and branch-prefix decisions. FOO_RUNTIME forces
35
+ // a value; Claude Code sets CLAUDECODE / CLAUDE_CODE_*; Codex identifies through its own
36
+ // adapter env. Anything else is "unknown" (and therefore takes the text path).
37
+ export function detectRuntimeId(env = process.env) {
38
+ const forced = env.FOO_RUNTIME?.trim();
39
+ if (forced)
40
+ return forced;
41
+ if (env.CLAUDECODE || env.CLAUDE_CODE_ENTRYPOINT || env.CLAUDE_CODE === "1")
42
+ return "claude-code";
43
+ if (env.CODEX || env.CODEX_SANDBOX || env.CODEX_HOME)
44
+ return "codex";
45
+ return "unknown";
46
+ }
47
+ function widgetCapableRuntimes(context, env) {
48
+ if (context.widgetCapableRuntimes)
49
+ return new Set(context.widgetCapableRuntimes);
50
+ const fromEnv = env.FOO_WIDGET_RUNTIMES?.split(",").map((entry) => entry.trim()).filter(Boolean);
51
+ return new Set(fromEnv && fromEnv.length > 0 ? fromEnv : DEFAULT_WIDGET_CAPABLE_RUNTIMES);
52
+ }
53
+ // Resolve which surface productfoo should use to ask a question in the given context. The
54
+ // default is "text"; "widget" is the earned, positively-detected exception.
55
+ export function detectElicitationSurface(context = {}) {
56
+ const env = context.env ?? process.env;
57
+ const runtime = detectRuntimeId(env);
58
+ const override = env.FOO_ELICITATION_SURFACE?.trim().toLowerCase();
59
+ if (override === "text") {
60
+ return { surface: "text", runtime, reason: "forced by FOO_ELICITATION_SURFACE=text" };
61
+ }
62
+ const isTTY = context.isTTY ?? Boolean(process.stdout.isTTY);
63
+ if (!isTTY) {
64
+ return {
65
+ surface: "text",
66
+ runtime,
67
+ reason: "non-interactive output (piped or agent reader); numbered text is the readable fallback",
68
+ };
69
+ }
70
+ if (override === "widget") {
71
+ return { surface: "widget", runtime, reason: "forced by FOO_ELICITATION_SURFACE=widget" };
72
+ }
73
+ if (widgetCapableRuntimes(context, env).has(runtime)) {
74
+ return { surface: "widget", runtime, reason: `runtime "${runtime}" advertises a branded-widget renderer` };
75
+ }
76
+ return { surface: "text", runtime, reason: `runtime "${runtime}" has no branded-widget renderer; text is the default` };
77
+ }