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,513 @@
1
+ // `foo discovery zendesk …` — the local worker for the company Zendesk
2
+ // workspace (zendesk-discovery-connector-v0, D-010 local worker mode).
3
+ //
4
+ // The support-class runtime adapter: it lists bounded solved/closed ticket
5
+ // metadata inside an approved study or inbox scope, normalizes it through the
6
+ // Zendesk adapter (ticket-as-session, solved/closed admissibility with an
7
+ // observed solved_at), hands the closed session shape to
8
+ // `foo discovery match`, opens exactly one proof-authorized comment thread
9
+ // into process memory, synthesizes the sanitized envelope in one model
10
+ // request, and hands the envelope to `foo discovery apply`. The thread never
11
+ // reaches argv, stdin, a file, Git, a log, an error, or the host agent's
12
+ // context; every surface this module writes is content-free.
13
+ //
14
+ // What it is not: the hosted, cross-host service the Nango discovery packet
15
+ // specifies. Ownership isolation, signed service receipts, and remote hosts
16
+ // wait for that service. This worker runs where the repository working tree
17
+ // is, with the company credential brokered by Nango (or held directly by this
18
+ // runtime as an administrator-issued API token).
19
+ import { existsSync, readFileSync } from "node:fs";
20
+ import path from "node:path";
21
+ import { hasFlag } from "./cliArgs.js";
22
+ import { loadDiscoveryRun } from "./discoveryArtifacts.js";
23
+ import { discoveryTargetDir, inboxScope, isInboxTarget, localRoot, localTargetName, runDiscoveryCommand, zendeskScopeValidationError, } from "./discoveryCommand.js";
24
+ import { openAuthorizedTranscript } from "./discoveryProof.js";
25
+ import { zendeskDiscoveryAdapter } from "./discoveryProviders.js";
26
+ import { planDiscoverySync } from "./discoverySync.js";
27
+ import { anthropicSynthesisTransport, defaultSynthesisModel, synthesisCredentialPresent, synthesizeDiscoveryEnvelope } from "./discoverySynthesis.js";
28
+ import { resolveZendeskConnection, zendeskConnectionConfigured, ZendeskClient, ZendeskRuntimeError, ZENDESK_POLICY } from "./zendeskNango.js";
29
+ const MATCHABLE_STUDY_STATES = new Set(["awaiting_match", "ambiguous_match", "needs_source_approval", "needs_provider_access", "needs_provider_connector", "needs_research_proof"]);
30
+ const DEFAULT_LOOKBACK_DAYS = 14;
31
+ const SUPPORT_STATE = "contract_only";
32
+ const MODE = "local_worker";
33
+ function result(status, options = {}) {
34
+ return { schema_version: "0.1", status, writes: [], errors: [], handoff: null, next_actions: [], coverage: {}, ...options };
35
+ }
36
+ function exitCodeFor(status) {
37
+ return ["ambiguous_match", "needs_source_approval", "needs_provider_access", "needs_provider_connector", "needs_research_proof", "invalid", "budget_exhausted"].includes(status) ? 1 : 0;
38
+ }
39
+ function outcome(value, json) {
40
+ const exitCode = exitCodeFor(value.status);
41
+ if (json)
42
+ return { output: `${JSON.stringify(value, null, 2)}\n`, exitCode, result: value };
43
+ const lines = [`Discovery (zendesk): ${value.status}.`];
44
+ for (const error of value.errors)
45
+ lines.push(`- ${error.message}`);
46
+ if (value.handoff)
47
+ lines.push(`Handoff: ${value.handoff}`);
48
+ for (const action of value.next_actions)
49
+ lines.push(`Next: ${action}`);
50
+ return { output: `${lines.join("\n")}\n`, exitCode, result: value };
51
+ }
52
+ function invalid(message, json, code = "E_DISCOVERY_VALIDATION", targetId, coverage = {}) {
53
+ return outcome(result("invalid", { study_id: targetId, errors: [{ code, message }], coverage }), json);
54
+ }
55
+ // Typed provider failures map to the discovery command's closed status set.
56
+ // Messages are productfoo's own words: no provider body, id, or credential.
57
+ function fromRuntimeError(error, json, targetId, coverage) {
58
+ const base = { study_id: targetId, coverage: { ...coverage, provider_error: error.code, mode: MODE } };
59
+ switch (error.code) {
60
+ case "not_connected":
61
+ return outcome(result("needs_provider_connector", { ...base, errors: [{ code: "E_DISCOVERY_PROVIDER_CONNECTOR", message: error.message }], handoff: "Connect the company Zendesk workspace through Nango and set NANGO_SECRET_KEY and FOO_ZENDESK_NANGO_CONNECTION (plus NANGO_HOST / FOO_ZENDESK_NANGO_INTEGRATION when they differ from the defaults) and FOO_ZENDESK_SUBDOMAIN; or, for this runtime only, set FOO_ZENDESK_API_TOKEN and FOO_ZENDESK_API_TOKEN_EMAIL from an administrator-issued Zendesk API token." }), json);
62
+ case "auth_expired":
63
+ case "access_denied":
64
+ return outcome(result("needs_provider_access", { ...base, errors: [{ code: "E_DISCOVERY_PROVIDER_ACCESS", message: error.message }], handoff: "Ask the Zendesk administrator to refresh the connection or grant the read permission, then rerun." }), json);
65
+ case "budget_exhausted":
66
+ // A rate-limit stop is the quota half of R-004: the run ends, nothing
67
+ // widens, nothing retries. The next run needs a narrower window.
68
+ 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);
69
+ default:
70
+ 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);
71
+ }
72
+ }
73
+ function isoDaysAgo(now, days) {
74
+ return new Date(Date.parse(now) - days * 86_400_000).toISOString();
75
+ }
76
+ function loadTarget(root, targetId, env, now, phase = "list") {
77
+ if (isInboxTarget(targetId)) {
78
+ const scope = inboxScope(root, targetId);
79
+ if (scope.provider.toLowerCase() !== "zendesk")
80
+ throw new Error("this inbox scope is not a Zendesk scope");
81
+ if (scope.state !== "approved")
82
+ throw new Error("this inbox scope is not approved");
83
+ // R-004: validation is pre-flight — an invalid scope never reaches the
84
+ // provider. The scope's selector kinds are the Zendesk kinds; participant
85
+ // and title tokens remain Gong kinds and never stand in for one here.
86
+ const scopeError = zendeskScopeValidationError({
87
+ window: scope.time_window,
88
+ external_domains: scope.domain_allowlist,
89
+ organizations: scope.organizations,
90
+ brands: scope.brands,
91
+ include_internal_only: scope.include_internal_only,
92
+ });
93
+ if (scopeError)
94
+ throw new Error(scopeError);
95
+ const from = new Date(scope.time_window.start);
96
+ const to = new Date(scope.time_window.end);
97
+ return { target_id: targetId, lane: "unassigned_call_evidence", dir: discoveryTargetDir(root, targetId), connection_alias: scope.connection_alias, window: { from: from.toISOString(), to: to.toISOString() }, selectors: { external_domains: scope.domain_allowlist, organizations: scope.organizations, brands: scope.brands } };
98
+ }
99
+ const dir = discoveryTargetDir(root, targetId);
100
+ const run = loadDiscoveryRun(dir);
101
+ if (run.source.provider.toLowerCase() !== "zendesk")
102
+ throw new Error("this study is not a Zendesk study");
103
+ // Listing needs a matchable study; processing needs the state `match` left
104
+ // behind (ready_to_synthesize) — the proof check decides the rest.
105
+ const allowed = phase === "process" ? run.state === "ready_to_synthesize" || MATCHABLE_STUDY_STATES.has(run.state) : MATCHABLE_STUDY_STATES.has(run.state);
106
+ if (!allowed)
107
+ throw new Error(`study ${targetId} is not ready for provider reads (state ${run.state})`);
108
+ let from;
109
+ let to;
110
+ if (run.match.expected_local_date) {
111
+ const day = Date.parse(`${run.match.expected_local_date}T00:00:00Z`);
112
+ if (!Number.isFinite(day))
113
+ throw new Error("the study's expected date is invalid");
114
+ from = new Date(day - 86_400_000).toISOString();
115
+ to = new Date(day + 2 * 86_400_000).toISOString();
116
+ }
117
+ else {
118
+ const lookback = Number(env.FOO_ZENDESK_LOOKBACK_DAYS ?? DEFAULT_LOOKBACK_DAYS);
119
+ if (!Number.isFinite(lookback) || lookback <= 0 || lookback > 90)
120
+ throw new Error("FOO_ZENDESK_LOOKBACK_DAYS must be between 1 and 90");
121
+ from = isoDaysAgo(now, lookback);
122
+ to = now;
123
+ }
124
+ // R-004 applies to study scopes too: a zendesk study names its customer —
125
+ // the study's organization maps onto the ticket-organization selector kind.
126
+ // Study match constraints (participants, title tokens) are Gong selector
127
+ // kinds; the Zendesk worker narrows by them post-list instead.
128
+ const organization = run.target?.organization?.trim() || undefined;
129
+ const selectorError = zendeskScopeValidationError({
130
+ window: { start: from, end: to },
131
+ organizations: organization ? [organization] : [],
132
+ include_internal_only: false,
133
+ });
134
+ if (selectorError)
135
+ throw new Error(selectorError);
136
+ return { target_id: targetId, lane: "study_linked", dir, connection_alias: run.source.connection_alias, window: { from, to }, selectors: { organizations: organization ? [organization] : undefined } };
137
+ }
138
+ // The canonical, off-the-wire link form R-006 validates for Zendesk — built
139
+ // from the workspace subdomain, never echoed from a provider response.
140
+ function ticketUrl(subdomain, ticketId) {
141
+ return `https://${subdomain}.zendesk.com/agent/tickets/${ticketId}`;
142
+ }
143
+ function inWindow(value, window) {
144
+ if (!value)
145
+ return false;
146
+ const at = Date.parse(value);
147
+ return Number.isFinite(at) && at >= Date.parse(window.from) && at <= Date.parse(window.to);
148
+ }
149
+ // R-004: the search query reflects believed operator support; the worker
150
+ // additionally narrows after the read. A ticket whose requester sits outside
151
+ // the scope's external domains is excluded here — narrowing only, never
152
+ // widening.
153
+ function outsideDomainScope(requester, externalDomains) {
154
+ const domains = (externalDomains ?? []).map((domain) => domain.trim().toLowerCase()).filter(Boolean);
155
+ if (domains.length === 0)
156
+ return false;
157
+ const email = requester?.email?.trim().toLowerCase();
158
+ const at = email ? email.lastIndexOf("@") : -1;
159
+ if (!email || at <= 0 || at >= email.length - 1)
160
+ return true;
161
+ return !domains.includes(email.slice(at + 1));
162
+ }
163
+ async function connect(deps) {
164
+ const connection = await resolveZendeskConnection({ env: deps.env, fetchImpl: deps.fetchImpl });
165
+ const client = new ZendeskClient(connection.credential, connection.subdomain, { fetchImpl: deps.fetchImpl, sleep: deps.sleep, fetchTimeoutMs: deps.fetchTimeoutMs });
166
+ return { connection, client };
167
+ }
168
+ async function listSessions(target, connection, client) {
169
+ const listing = await client.listTickets(target.window, target.selectors);
170
+ // A ceiling stop is final for the run (R-004/R-010): no further provider
171
+ // reads happen — the user and brand metadata reads wait for a window that
172
+ // fits the agreed pages, never for a wider one.
173
+ if (listing.truncated)
174
+ return { sessions: [], listed: listing.tickets.length, pages: listing.pages, truncated: true, domain_excluded: 0, incomplete: 0 };
175
+ const requesterIds = listing.tickets.map((ticket) => ticket.requester_id).filter((id) => typeof id === "number");
176
+ const users = requesterIds.length ? await client.users(requesterIds) : new Map();
177
+ const brands = listing.tickets.some((ticket) => typeof ticket.brand_id === "number") ? await client.brands() : new Map();
178
+ const sessions = [];
179
+ let domainExcluded = 0;
180
+ let incomplete = 0;
181
+ for (const ticket of listing.tickets) {
182
+ const requester = ticket.requester_id !== undefined ? users.get(ticket.requester_id) : undefined;
183
+ if (outsideDomainScope(requester, target.selectors.external_domains)) {
184
+ domainExcluded += 1;
185
+ continue;
186
+ }
187
+ // The listing query is created-based (belief, T-001); the admissibility
188
+ // window is the solved time, narrowed here — narrowing only (R-004).
189
+ if (!inWindow(ticket.solved_at, target.window)) {
190
+ incomplete += 1;
191
+ continue;
192
+ }
193
+ const record = {
194
+ id: ticket.id,
195
+ subject: ticket.subject?.trim() || "Untitled ticket",
196
+ status: ticket.status?.trim() || "",
197
+ solved_at: ticket.solved_at,
198
+ requester: requester ? { display_name: requester.name, email: requester.email } : {},
199
+ assignee_display_name: undefined,
200
+ brand: ticket.brand_id !== undefined ? brands.get(ticket.brand_id) : undefined,
201
+ url: ticketUrl(connection.subdomain, ticket.id),
202
+ comment_access: connection.comment_scope === "missing" ? "missing" : "granted",
203
+ };
204
+ const session = zendeskDiscoveryAdapter.normalizeSession(record, target.connection_alias);
205
+ if (session.completion_state !== "completed") {
206
+ incomplete += 1;
207
+ continue;
208
+ }
209
+ sessions.push({ ...session, access: connection.comment_scope === "missing" ? "denied" : "available" });
210
+ }
211
+ return { sessions, listed: listing.tickets.length, pages: listing.pages, truncated: listing.truncated, domain_excluded: domainExcluded, incomplete };
212
+ }
213
+ async function readyCommand(root, json, deps) {
214
+ const env = deps.env;
215
+ const coverage = {
216
+ provider: "zendesk",
217
+ mode: MODE,
218
+ support_state: SUPPORT_STATE,
219
+ connection_owner: "workspace",
220
+ configured: zendeskConnectionConfigured(env),
221
+ credential_broker: env.FOO_ZENDESK_API_TOKEN?.trim() && env.FOO_ZENDESK_API_TOKEN_EMAIL?.trim() ? "direct" : env.NANGO_SECRET_KEY?.trim() && env.FOO_ZENDESK_NANGO_CONNECTION?.trim() ? "nango" : null,
222
+ synthesis: { available: synthesisCredentialPresent(env), model: defaultSynthesisModel(env) },
223
+ policy: { ...ZENDESK_POLICY },
224
+ };
225
+ try {
226
+ const { connection, client } = await connect(deps);
227
+ // The readiness probe is one metadata call inside a one-hour window: it
228
+ // proves the credential and the listing permission without reading a
229
+ // comment thread.
230
+ const now = deps.now();
231
+ await client.listTickets({ from: isoDaysAgo(now, 1 / 24), to: now });
232
+ coverage.connection = connection.connection_ref;
233
+ coverage.credential_broker = connection.broker;
234
+ coverage.credential_type = connection.credential_type;
235
+ coverage.capabilities = { list_sessions: true, open_transcript: connection.comment_scope !== "missing", stable_link: true, timestamps: connection.comment_scope !== "missing", comment_scope: connection.comment_scope };
236
+ coverage.provider_calls = client.requests;
237
+ return outcome(result("ok", { coverage, next_actions: [
238
+ ...(connection.broker === "direct" ? ["Credential broker is direct: this runtime holds an administrator-issued API token itself. Refresh and revoke happen in Zendesk, not centrally; move to Nango for the company connection."] : []),
239
+ "Approve a bounded Zendesk scope, then run foo discovery zendesk list <study-id|inbox:scope-id>.", ...(coverage.synthesis && !coverage.synthesis.available ? ["Set ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN) before processing a ticket thread."] : [])
240
+ ] }), json);
241
+ }
242
+ catch (error) {
243
+ if (error instanceof ZendeskRuntimeError)
244
+ return fromRuntimeError(error, json, undefined, coverage);
245
+ return invalid("Zendesk readiness failed closed without exposing private source content.", json, "E_DISCOVERY_VALIDATION", undefined, coverage);
246
+ }
247
+ }
248
+ async function listCommand(root, targetId, json, deps) {
249
+ if (!targetId)
250
+ return invalid("Usage: foo discovery zendesk list <study-id|inbox:scope-id>", json);
251
+ const now = deps.now();
252
+ let target;
253
+ try {
254
+ target = loadTarget(root, targetId, deps.env, now);
255
+ }
256
+ catch (error) {
257
+ return invalid(error instanceof Error ? error.message : "invalid discovery target", json, "E_DISCOVERY_VALIDATION", targetId);
258
+ }
259
+ const coverage = { mode: MODE, window: target.window, lane: target.lane, thread_opened: false };
260
+ try {
261
+ const { connection, client } = await connect(deps);
262
+ coverage.connection = connection.connection_ref;
263
+ const listed = await listSessions(target, connection, client);
264
+ Object.assign(coverage, { tickets_listed: listed.listed, pages: listed.pages, truncated: listed.truncated, domain_excluded: listed.domain_excluded, incomplete_excluded: listed.incomplete, candidates_evaluated: listed.sessions.length, provider_calls: client.requests });
265
+ if (listed.truncated) {
266
+ return outcome(result("budget_exhausted", {
267
+ study_id: targetId,
268
+ coverage: { ...coverage, provider_error: "budget_exhausted" },
269
+ errors: [{ code: "E_DISCOVERY_VALIDATION", message: `The window holds more tickets than the ${ZENDESK_POLICY.max_list_pages}-page ceiling; narrow the window or selectors instead of widening the read.` }],
270
+ handoff: "Narrow the approved window or selectors and list again; the run never widens the window and never retries.",
271
+ }), json);
272
+ }
273
+ if (listed.sessions.length === 0)
274
+ return outcome(result("awaiting_match", { study_id: targetId, coverage, next_actions: ["No solved or closed ticket with an observed solve time was listed inside the approved window."] }), json);
275
+ if (target.lane === "study_linked") {
276
+ const matched = await runDiscoveryCommand(root, ["match", targetId, "--sessions-stdin", "--json"], JSON.stringify(listed.sessions), now);
277
+ return outcome({ ...matched.result, coverage: { ...matched.result.coverage, ...coverage }, next_actions: [...matched.result.next_actions, ...(matched.result.status === "ok" ? [`Run foo discovery zendesk process ${targetId} to open the one matched comment thread and apply the sanitized evidence.`] : [])] }, json);
278
+ }
279
+ // Inbox: one ticket at a time so each source gets its own one-time proof.
280
+ let lastMiss = null;
281
+ let evaluated = 0;
282
+ for (const session of listed.sessions) {
283
+ evaluated += 1;
284
+ const matched = await runDiscoveryCommand(root, ["match", targetId, "--sessions-stdin", "--json"], JSON.stringify([session]), now);
285
+ if (matched.result.status === "awaiting_match") {
286
+ lastMiss = matched.result;
287
+ continue;
288
+ }
289
+ const remaining = listed.sessions.length - evaluated;
290
+ return outcome({
291
+ ...matched.result,
292
+ coverage: { ...matched.result.coverage, ...coverage, candidates_evaluated: evaluated, candidates_remaining: remaining },
293
+ next_actions: [
294
+ ...matched.result.next_actions,
295
+ ...(matched.result.status === "ok" ? [`Run foo discovery zendesk process ${targetId} to open this one comment thread and apply the sanitized evidence.`] : []),
296
+ ...(remaining > 0 ? [`${remaining} more listed ticket(s) were not evaluated; run list again after this one is processed.`] : []),
297
+ ],
298
+ }, json);
299
+ }
300
+ return outcome(result("awaiting_match", { study_id: targetId, coverage: { ...coverage, candidates_evaluated: evaluated }, next_actions: lastMiss?.next_actions ?? ["No listed ticket matched the approved scope; nothing was opened."] }), json);
301
+ }
302
+ catch (error) {
303
+ if (error instanceof ZendeskRuntimeError)
304
+ return fromRuntimeError(error, json, targetId, coverage);
305
+ return invalid("Zendesk listing failed closed without exposing private source content.", json, "E_DISCOVERY_VALIDATION", targetId, coverage);
306
+ }
307
+ }
308
+ function readMatch(root, targetId) {
309
+ const file = path.join(localRoot(root), localTargetName(targetId), "match.json");
310
+ if (!existsSync(file))
311
+ return null;
312
+ try {
313
+ return JSON.parse(readFileSync(file, "utf8"));
314
+ }
315
+ catch {
316
+ return null;
317
+ }
318
+ }
319
+ function studyHypotheses(dir) {
320
+ const file = path.join(dir, "study.md");
321
+ if (!existsSync(file))
322
+ return [];
323
+ const out = [];
324
+ for (const line of readFileSync(file, "utf8").split("\n")) {
325
+ const match = /^\s*(?:[-*]|\d+\.)?\s*\**(H-\d{3})\**\s*[:—–-]?\s*(.+?)\s*$/.exec(line);
326
+ if (match && !out.some((entry) => entry.id === match[1]))
327
+ out.push({ id: match[1], text: match[2] });
328
+ }
329
+ return out;
330
+ }
331
+ function mmddhhmm(iso) {
332
+ const at = iso ? new Date(iso) : undefined;
333
+ if (!at || Number.isNaN(at.getTime()))
334
+ return "--/-- --:--";
335
+ const pad = (value) => String(value).padStart(2, "0");
336
+ return `${pad(at.getUTCMonth() + 1)}/${pad(at.getUTCDate())} ${pad(at.getUTCHours())}:${pad(at.getUTCMinutes())}`;
337
+ }
338
+ // Role labels only: the speaker map is used inside this request to say who is
339
+ // the customer and who is the company. Zendesk's own user role decides — an
340
+ // end-user is the customer, agents and admins are the company — and the names
341
+ // never enter the text.
342
+ function speakerRole(user) {
343
+ if (!user)
344
+ return "Unknown speaker";
345
+ return user.role?.trim().toLowerCase() === "end-user" ? "Customer" : "Company representative";
346
+ }
347
+ function renderThread(comments, users) {
348
+ const lines = [];
349
+ for (const comment of comments) {
350
+ const role = comment.author_id !== undefined ? speakerRole(users.get(comment.author_id)) : "Unknown speaker";
351
+ const body = (comment.body ?? "").replace(/\s+/g, " ").trim();
352
+ if (!body)
353
+ continue;
354
+ lines.push(`[${mmddhhmm(comment.created_at)}] ${role}: ${body}`);
355
+ }
356
+ return lines.join("\n");
357
+ }
358
+ function containsIdentity(envelopeText, identities) {
359
+ const haystack = envelopeText.toLowerCase();
360
+ return identities.some((identity) => identity.length >= 4 && haystack.includes(identity.toLowerCase()));
361
+ }
362
+ async function processCommand(root, targetId, json, deps) {
363
+ if (!targetId)
364
+ return invalid("Usage: foo discovery zendesk process <study-id|inbox:scope-id>", json);
365
+ const now = deps.now();
366
+ let target;
367
+ try {
368
+ target = loadTarget(root, targetId, deps.env, now, "process");
369
+ }
370
+ catch (error) {
371
+ return invalid(error instanceof Error ? error.message : "invalid discovery target", json, "E_DISCOVERY_VALIDATION", targetId);
372
+ }
373
+ const match = readMatch(root, targetId);
374
+ if (!match || !Array.isArray(match.candidates) || match.candidates.length === 0)
375
+ return invalid("No matched Zendesk ticket is recorded for this target; run foo discovery zendesk list first.", json, "E_DISCOVERY_VALIDATION", targetId);
376
+ if (match.candidates.length > 1)
377
+ return outcome(result("ambiguous_match", { study_id: targetId, errors: [{ code: "E_DISCOVERY_AMBIGUOUS_MATCH", message: "Several tickets matched; a person must select one before any comment thread is opened." }], handoff: `foo discovery review ${targetId} select-source --fingerprint <sha256> --by <person>` }), json);
378
+ const candidate = match.candidates[0];
379
+ const session = candidate.session;
380
+ const ticketId = session.runtime_handle;
381
+ if (!synthesisCredentialPresent(deps.env))
382
+ 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 comment thread was not opened." }], handoff: "Set ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN) for this runtime, then rerun process." }), json);
383
+ const model = defaultSynthesisModel(deps.env);
384
+ const coverage = { mode: MODE, lane: target.lane, policy_version: ZENDESK_POLICY.policy_version, model };
385
+ const hypotheses = target.lane === "study_linked" ? studyHypotheses(target.dir) : [];
386
+ if (target.lane === "study_linked" && hypotheses.length === 0)
387
+ return invalid("The study records no H-### hypotheses in study.md; study-linked evidence needs one to test.", json, "E_DISCOVERY_VALIDATION", targetId);
388
+ let client;
389
+ try {
390
+ const connected = await connect(deps);
391
+ client = connected.client;
392
+ coverage.connection = connected.connection.connection_ref;
393
+ if (connected.connection.comment_scope === "missing") {
394
+ return outcome(result("needs_provider_access", { study_id: targetId, errors: [{ code: "E_DISCOVERY_PROVIDER_ACCESS", message: "The Zendesk credential has no ticket-read permission; the comment thread was not opened." }], handoff: session.stable_url ? `Request comment access for ${session.stable_url}` : "Request comment access from the Zendesk administrator.", coverage }), json);
395
+ }
396
+ let openError;
397
+ let opened;
398
+ try {
399
+ opened = await openAuthorizedTranscript({ study_dir: target.dir, local_root: localRoot(root), study_id: targetId, source_fingerprint: candidate.source_fingerprint, now }, async () => {
400
+ try {
401
+ const comments = await client.comments(ticketId);
402
+ const authorIds = comments.map((comment) => comment.author_id).filter((id) => typeof id === "number");
403
+ const users = authorIds.length ? await client.users(authorIds) : new Map();
404
+ return { comments, users };
405
+ }
406
+ catch (error) {
407
+ if (error instanceof ZendeskRuntimeError)
408
+ openError = error;
409
+ throw error;
410
+ }
411
+ });
412
+ }
413
+ catch (error) {
414
+ if (openError)
415
+ return fromRuntimeError(openError, json, targetId, { ...coverage, thread_body_requests: client.requests.comments });
416
+ return outcome(result("needs_research_proof", { study_id: targetId, errors: [{ code: "E_DISCOVERY_RESEARCH_PROOF", message: "The comment thread was not opened: the discovery proof is missing, expired, already used, or no longer matches the approval." }], handoff: `Run foo discovery zendesk list ${targetId} again to re-match and mint a fresh proof.`, coverage }), json);
417
+ }
418
+ coverage.thread_body_requests = client.requests.comments;
419
+ let threadText = renderThread(opened.comments, opened.users);
420
+ const estimatedTokens = Math.ceil(threadText.length / 4);
421
+ if (estimatedTokens > ZENDESK_POLICY.max_model_input_tokens) {
422
+ threadText = null;
423
+ return fromRuntimeError(new ZendeskRuntimeError("transcript_too_large", "the comment thread exceeds the model-input ceiling; nothing was synthesized"), json, targetId, coverage);
424
+ }
425
+ let synthesized;
426
+ try {
427
+ synthesized = await synthesizeDiscoveryEnvelope({
428
+ target_id: targetId,
429
+ lane: target.lane,
430
+ source: { provider: "zendesk", ...(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" },
431
+ title: session.title,
432
+ hypotheses,
433
+ transcript_text: threadText,
434
+ transcript_complete: true,
435
+ caveats: session.stable_url ? [] : ["The worker could not construct a canonical ticket link for this ticket."],
436
+ }, { model, transport: deps.transport });
437
+ }
438
+ catch {
439
+ return invalid("Synthesis did not produce a valid closed envelope; nothing was written and the comment thread was discarded.", json, "E_DISCOVERY_INVALID_ENVELOPE", targetId, coverage);
440
+ }
441
+ finally {
442
+ threadText = null;
443
+ }
444
+ coverage.synthesis_usage = synthesized.usage ?? null;
445
+ // Privacy screen beyond the envelope validator: requester and agent names
446
+ // from the provider's own metadata must not appear in derived text.
447
+ const derivedText = [
448
+ ...synthesized.envelope.findings.flatMap((finding) => [finding.headline, finding.paraphrase, finding.dissent]),
449
+ ...synthesized.envelope.coverage.caveats,
450
+ ].join("\n");
451
+ if (containsIdentity(derivedText, session.participants))
452
+ return invalid("Synthesis leaked a participant identity into derived text; nothing was written.", json, "E_DISCOVERY_INVALID_ENVELOPE", targetId, coverage);
453
+ const applied = await runDiscoveryCommand(root, ["apply", targetId, "--evidence-stdin", "--json"], JSON.stringify(synthesized.envelope), now);
454
+ return outcome({ ...applied.result, coverage: { ...applied.result.coverage, ...coverage } }, json);
455
+ }
456
+ catch (error) {
457
+ if (error instanceof ZendeskRuntimeError)
458
+ return fromRuntimeError(error, json, targetId, { ...coverage, thread_body_requests: client?.requests.comments ?? 0 });
459
+ return invalid("Zendesk processing failed closed without exposing private source content.", json, "E_DISCOVERY_VALIDATION", targetId, coverage);
460
+ }
461
+ }
462
+ async function syncCommand(root, json, deps) {
463
+ const now = deps.now();
464
+ const plan = planDiscoverySync(root, now);
465
+ const lanes = plan.lanes.filter((lane) => lane.provider.toLowerCase() === "zendesk");
466
+ if (lanes.length === 0)
467
+ return outcome(result("clean_no_op", { errors: plan.errors.map((message) => ({ code: "E_DISCOVERY_VALIDATION", message })), next_actions: ["No approved Zendesk lane is active; approve a study source or an inbox scope first."], coverage: { mode: MODE, lanes: 0, connector_reads: 0 } }), json);
468
+ const results = [];
469
+ // Metadata and match only: a comment thread is opened only by an explicit
470
+ // `process` on one target. The loop stays unarmed (R-010).
471
+ for (const lane of lanes) {
472
+ const targetId = lane.kind === "inbox" ? `inbox:${lane.scope_id}` : lane.study_id;
473
+ const listed = await listCommand(root, targetId, true, deps);
474
+ results.push({ lane_id: lane.lane_id, status: listed.result.status, handoff: listed.result.handoff });
475
+ }
476
+ const ready = results.filter((entry) => entry.status === "ok").map((entry) => entry.lane_id);
477
+ return outcome(result("ok", {
478
+ errors: plan.errors.map((message) => ({ code: "E_DISCOVERY_VALIDATION", message })),
479
+ next_actions: [
480
+ ...ready.map((laneId) => `${laneId}: one ticket matched — run foo discovery zendesk process ${laneId.replace(/^study:/, "")} to open it.`),
481
+ ...results.filter((entry) => entry.status !== "ok").map((entry) => `${entry.lane_id}: ${entry.status}${entry.handoff ? ` — ${entry.handoff}` : ""}`),
482
+ ],
483
+ coverage: { mode: MODE, lanes: results.length, threads_opened: 0, results },
484
+ }), json);
485
+ }
486
+ export async function runZendeskDiscovery(root, args, deps = {}) {
487
+ const json = hasFlag(args, "--json");
488
+ const env = deps.env ?? process.env;
489
+ const resolved = {
490
+ fetchImpl: deps.fetchImpl ?? globalThis.fetch,
491
+ env,
492
+ now: deps.now ?? (() => new Date().toISOString()),
493
+ sleep: deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
494
+ transport: deps.transport ?? anthropicSynthesisTransport({ fetchImpl: deps.fetchImpl, env }),
495
+ fetchTimeoutMs: deps.fetchTimeoutMs ?? ZENDESK_POLICY.fetch_timeout_ms,
496
+ };
497
+ const command = args[0];
498
+ const target = args[1] && !args[1].startsWith("--") ? args[1] : undefined;
499
+ try {
500
+ if (command === "ready")
501
+ return await readyCommand(root, json, resolved);
502
+ if (command === "list")
503
+ return await listCommand(root, target, json, resolved);
504
+ if (command === "process")
505
+ return await processCommand(root, target, json, resolved);
506
+ if (command === "sync")
507
+ return await syncCommand(root, json, resolved);
508
+ return invalid("Try: ready | list <study-id|inbox:scope-id> | process <study-id|inbox:scope-id> | sync", json);
509
+ }
510
+ catch {
511
+ return invalid("Zendesk discovery failed closed without exposing private source content.", json);
512
+ }
513
+ }
@@ -0,0 +1,131 @@
1
+ export type FetchLike = typeof globalThis.fetch;
2
+ export type ZendeskRuntimeErrorCode = "not_connected" | "auth_expired" | "access_denied" | "provider_failure" | "network" | "provider_timeout" | "transcript_too_large" | "budget_exhausted";
3
+ export declare class ZendeskRuntimeError extends Error {
4
+ readonly code: ZendeskRuntimeErrorCode;
5
+ constructor(code: ZendeskRuntimeErrorCode, message: string);
6
+ }
7
+ export type ZendeskCredential = {
8
+ type: "basic";
9
+ username: string;
10
+ password: string;
11
+ } | {
12
+ type: "oauth2";
13
+ access_token: string;
14
+ };
15
+ export type ZendeskConnection = {
16
+ connection_ref: string;
17
+ broker: "nango" | "direct";
18
+ credential: ZendeskCredential;
19
+ credential_type: "basic" | "oauth2";
20
+ comment_scope: "granted" | "missing" | "unknown";
21
+ subdomain: string;
22
+ };
23
+ export declare const ZENDESK_POLICY: {
24
+ readonly policy_version: "zendesk-local-worker-0.1";
25
+ readonly max_response_bytes: number;
26
+ readonly fetch_timeout_ms: 90000;
27
+ readonly max_model_input_tokens: 100000;
28
+ readonly max_list_pages: 5;
29
+ readonly min_request_interval_ms: 350;
30
+ };
31
+ export type ZendeskConnectionEnv = Record<string, string | undefined>;
32
+ export declare function zendeskConnectionConfigured(env: ZendeskConnectionEnv): boolean;
33
+ export declare function zendeskCredentialBroker(env: ZendeskConnectionEnv): "nango" | "direct" | null;
34
+ /**
35
+ * Resolve the company's Zendesk credential. Nango is the primary broker: it
36
+ * holds the credential, refreshes it, and is where an administrator revokes
37
+ * it. When Nango is not configured, an administrator-issued API token and
38
+ * agent email set directly in this runtime's environment (FOO_ZENDESK_API_TOKEN
39
+ * / FOO_ZENDESK_API_TOKEN_EMAIL) is accepted as the direct fallback — the same
40
+ * company credential, minus the central refresh and revoke story. Either way
41
+ * productfoo receives it for one command and never persists or prints it. The
42
+ * workspace subdomain (FOO_ZENDESK_SUBDOMAIN) is required for both brokers.
43
+ */
44
+ export declare function resolveZendeskConnection(input: {
45
+ env: ZendeskConnectionEnv;
46
+ fetchImpl: FetchLike;
47
+ }): Promise<ZendeskConnection>;
48
+ export type ZendeskListedTicket = {
49
+ id: string;
50
+ subject?: string;
51
+ status?: string;
52
+ solved_at?: string;
53
+ created_at?: string;
54
+ updated_at?: string;
55
+ requester_id?: number;
56
+ assignee_id?: number;
57
+ brand_id?: number;
58
+ };
59
+ export type ZendeskUser = {
60
+ id: number;
61
+ name?: string;
62
+ email?: string;
63
+ role?: string;
64
+ };
65
+ export type ZendeskBrand = {
66
+ id: number;
67
+ name?: string;
68
+ };
69
+ export type ZendeskComment = {
70
+ id?: number;
71
+ author_id?: number;
72
+ body?: string;
73
+ public?: boolean;
74
+ created_at?: string;
75
+ };
76
+ export type ZendeskScopeSelectorsRuntime = {
77
+ external_domains?: string[];
78
+ organizations?: string[];
79
+ brands?: string[];
80
+ };
81
+ type ZendeskClientOptions = {
82
+ fetchImpl: FetchLike;
83
+ baseUrl?: string;
84
+ sleep?: (ms: number) => Promise<void>;
85
+ now?: () => number;
86
+ fetchTimeoutMs?: number;
87
+ };
88
+ /**
89
+ * Minimal read-only Zendesk REST client. Only documented read operations are
90
+ * reachable; any other method or path routes through the single write-refusal
91
+ * choke point (R-009) so the refusal is testable rather than scattered.
92
+ */
93
+ export declare class ZendeskClient {
94
+ private readonly credential;
95
+ private readonly subdomain;
96
+ private readonly options;
97
+ readonly requests: {
98
+ list: number;
99
+ users: number;
100
+ brands: number;
101
+ comments: number;
102
+ };
103
+ private lastRequestAt;
104
+ private readonly baseUrl;
105
+ private readonly sleep;
106
+ private readonly now;
107
+ private readonly fetchTimeoutMs;
108
+ constructor(credential: ZendeskCredential, subdomain: string, options: ZendeskClientOptions);
109
+ private authorization;
110
+ private request;
111
+ /** Bounded metadata listing: one selector set, a page ceiling, no content. */
112
+ listTickets(window: {
113
+ from: string;
114
+ to: string;
115
+ }, selectors?: ZendeskScopeSelectorsRuntime): Promise<{
116
+ tickets: ZendeskListedTicket[];
117
+ pages: number;
118
+ truncated: boolean;
119
+ }>;
120
+ /** User metadata for the given ids (names, roles) — batched, no content fields. */
121
+ users(ids: number[]): Promise<Map<number, ZendeskUser>>;
122
+ /** Account brands, once per listing run, so brand_id maps to a name. */
123
+ brands(): Promise<Map<number, string>>;
124
+ /**
125
+ * Exactly one ticket's comment thread (R-007). This method refuses to ask
126
+ * for more than one identifier and a fixture observes that no request ever
127
+ * carries more.
128
+ */
129
+ comments(ticketId: string): Promise<ZendeskComment[]>;
130
+ }
131
+ export {};