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,613 @@
1
+ // Nango-backed discovery connections (nango-discovery-connector-v0 T-002).
2
+ //
3
+ // The connection-identity and service-grant foundation: opaque per-user
4
+ // connection references, tenant-scoped canonical source identity, closed
5
+ // content-free target manifests, pending-request state, target references
6
+ // minted only after out-of-band human confirmation, service grants bound to
7
+ // the local approval digest and revocation epoch, and the boundary recheck
8
+ // every access must pass before any provider call.
9
+ //
10
+ // productfoo's MCP service (foo-mcp-server-v0) is unbuilt, so everything that needs a
11
+ // hosted service sits behind two explicit seams this module defines but never
12
+ // impersonates:
13
+ //
14
+ // DiscoveryConnectionDirectory — server-side resolution of a connection_ref
15
+ // to {foo_subject, provider, nango_connection_id}. Hosts never mint, hold,
16
+ // or choose Nango identifiers; they receive only the opaque ref.
17
+ //
18
+ // DiscoveryServiceEndpoint / DiscoveryConfirmationSeam — where the pending
19
+ // target request is stored and where the out-of-band human confirmation
20
+ // happens. A target_ref exists only after the seam reports a human
21
+ // confirmation; no text-only or model action can mint one, and the in-repo
22
+ // implementations are fixtures for tests, never a parallel identity, OAuth,
23
+ // or confirmation stack (D-012/RISK-011). Hosted tiers stay contract_only.
24
+ //
25
+ // Content discipline (C-004): every type and result here is content-free —
26
+ // opaque references, digests, epochs, selectors, and state. No Nango
27
+ // connection id, provider account id, provider session id, credential, or
28
+ // transcript-derived text ever appears on a surface this module produces.
29
+ import { createHash } from "node:crypto";
30
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
31
+ import path from "node:path";
32
+ export const CONNECTIONS_SCHEMA_VERSION = "0.1";
33
+ const REF_DOMAIN = "foo_discovery_connections/v1";
34
+ const CANONICAL_DOMAIN = "foo_discovery_connections/canonical/v1";
35
+ const TARGET_REF_DOMAIN = "foo_discovery_target_ref/v1";
36
+ function opaqueRef(domain, parts, length = 32) {
37
+ // Domain-separated digest of the identity inputs. Hosted services salt this
38
+ // with a server-held key so refs cannot even be confirmed offline; the V0
39
+ // fixture implementations pass no salt. A ref is non-enumerable: without
40
+ // every input (including the subject) it cannot be derived or listed.
41
+ return createHash("sha256").update([domain, ...parts].join("\n")).digest("hex").slice(0, length);
42
+ }
43
+ export function isConnectionRef(value) {
44
+ return typeof value === "string" && /^conn:[a-f0-9]{32}$/.test(value);
45
+ }
46
+ export function isTargetRef(value) {
47
+ return typeof value === "string" && /^dtarget:[a-f0-9]{32}$/.test(value);
48
+ }
49
+ // The tenant-scoped canonical source identity (R-003): {foo_subject, provider,
50
+ // canonical_connection_key}. `provider_account_identity` is whatever stable
51
+ // provider-side account identifier the delivery path carries (for Nango, its
52
+ // connection id); it is digested here and never surfaces again. One provider
53
+ // account reached through two ingress paths maps to one canonical identity
54
+ // only after the same authenticated user links them and the service verifies
55
+ // the stable identity — this function derives, it never guesses equivalence.
56
+ export function canonicalSourceIdentity(input) {
57
+ if (!input.foo_subject?.trim())
58
+ throw new Error("canonical source identity requires a productfoo subject");
59
+ if (!input.provider?.trim())
60
+ throw new Error("canonical source identity requires a provider");
61
+ if (!input.provider_account_identity?.trim())
62
+ throw new Error("canonical source identity requires a provider account identity");
63
+ return {
64
+ foo_subject: input.foo_subject,
65
+ provider: input.provider.toLowerCase(),
66
+ canonical_connection_key: `cck:${opaqueRef(CANONICAL_DOMAIN, [input.foo_subject, input.provider.toLowerCase(), input.provider_account_identity])}`,
67
+ };
68
+ }
69
+ // Mint a non-enumerable connection_ref for one verified productfoo user's connection.
70
+ // V0 supports one connection per provider per subject (R-003): the subject is
71
+ // part of the digest, so two users with the same provider alias get different
72
+ // refs and neither can derive or resolve the other's.
73
+ export function mintConnectionRef(input) {
74
+ const subject = input.foo_subject?.trim();
75
+ const provider = input.provider?.trim().toLowerCase();
76
+ const nangoId = input.nango_connection_id?.trim();
77
+ if (!subject || !provider || !nangoId)
78
+ throw new Error("connection_ref minting requires subject, provider, and nango connection id");
79
+ const canonical = canonicalSourceIdentity({ foo_subject: subject, provider, provider_account_identity: nangoId });
80
+ return {
81
+ connection_ref: `conn:${opaqueRef(REF_DOMAIN, [subject, provider, nangoId, input.server_salt ?? ""])}`,
82
+ canonical,
83
+ };
84
+ }
85
+ // In-repo fixture/V0 implementation: content-free in-memory mapping. The real
86
+ // directory is productfoo service state (foo-mcp-server-v0); this exists so the
87
+ // contract is executable and testable before that packet lands.
88
+ export function createInMemoryConnectionDirectory() {
89
+ const byRef = new Map();
90
+ return {
91
+ register(input) {
92
+ const mint = mintConnectionRef(input);
93
+ if (!byRef.has(mint.connection_ref)) {
94
+ byRef.set(mint.connection_ref, {
95
+ foo_subject: input.foo_subject,
96
+ provider: input.provider.toLowerCase(),
97
+ nango_connection_id: input.nango_connection_id,
98
+ canonical: mint.canonical,
99
+ });
100
+ }
101
+ return mint;
102
+ },
103
+ resolve(connection_ref, acting) {
104
+ const record = byRef.get(connection_ref);
105
+ if (!record)
106
+ return { resolved: false, reason: "unknown_connection_ref" };
107
+ if (record.foo_subject !== acting.foo_subject)
108
+ return { resolved: false, reason: "subject_mismatch" };
109
+ return { resolved: true, identity: record };
110
+ },
111
+ };
112
+ }
113
+ const MANIFEST_KEYS = new Set([
114
+ "schema_version",
115
+ "kind",
116
+ "packet",
117
+ "spec_id",
118
+ "workspace_ref",
119
+ "target_id",
120
+ "lane",
121
+ "connection_ref",
122
+ "approved_selectors",
123
+ "approval_digest",
124
+ "revocation_epoch",
125
+ ]);
126
+ // Same discipline as the evidence envelope: content-bearing field names are
127
+ // refused even when the values would have been empty. A manifest is identity,
128
+ // not payload.
129
+ const FORBIDDEN_MANIFEST_KEY = /transcript|content|note|summary|attendee|participant|token|secret|credential|password/i;
130
+ const STUDY_TARGET = /^study_[a-z0-9_]+$/;
131
+ const INBOX_TARGET = /^inbox:[a-z0-9_]+$/;
132
+ const SHA256_DIGEST = /^sha256:[a-f0-9]{64}$/;
133
+ export function validateDiscoveryTargetManifest(value) {
134
+ if (!value || typeof value !== "object" || Array.isArray(value))
135
+ return { ok: false, reason: "target manifest must be an object" };
136
+ const raw = value;
137
+ for (const key of Object.keys(raw)) {
138
+ if (FORBIDDEN_MANIFEST_KEY.test(key))
139
+ return { ok: false, reason: `target manifest forbids content-bearing field ${key}` };
140
+ if (!MANIFEST_KEYS.has(key))
141
+ return { ok: false, reason: `target manifest field ${key} is not part of the closed manifest` };
142
+ }
143
+ if (raw.schema_version !== CONNECTIONS_SCHEMA_VERSION)
144
+ return { ok: false, reason: "target manifest schema_version must be 0.1" };
145
+ if (raw.kind !== "discovery_target_manifest")
146
+ return { ok: false, reason: "target manifest kind must be discovery_target_manifest" };
147
+ for (const field of ["packet", "spec_id", "workspace_ref"]) {
148
+ if (typeof raw[field] !== "string" || !raw[field].trim())
149
+ return { ok: false, reason: `target manifest ${field} must be a non-empty string` };
150
+ }
151
+ const targetId = raw.target_id;
152
+ if (typeof targetId !== "string" || (!STUDY_TARGET.test(targetId) && !INBOX_TARGET.test(targetId))) {
153
+ return { ok: false, reason: "target manifest target_id must be study_<slug> or inbox:<slug>" };
154
+ }
155
+ const lane = raw.lane;
156
+ if (lane !== "study" && lane !== "inbox")
157
+ return { ok: false, reason: "target manifest lane must be study or inbox" };
158
+ if ((lane === "study" && !STUDY_TARGET.test(targetId)) || (lane === "inbox" && !INBOX_TARGET.test(targetId))) {
159
+ return { ok: false, reason: "target manifest lane does not match the target id" };
160
+ }
161
+ if (!isConnectionRef(raw.connection_ref))
162
+ return { ok: false, reason: "target manifest connection_ref must be an opaque conn: reference" };
163
+ const selectors = raw.approved_selectors;
164
+ if (!Array.isArray(selectors) || selectors.length === 0 || !selectors.every((s) => typeof s === "string" && s.trim().length > 0)) {
165
+ return { ok: false, reason: "target manifest approved_selectors must be a non-empty string array" };
166
+ }
167
+ if (typeof raw.approval_digest !== "string" || !SHA256_DIGEST.test(raw.approval_digest)) {
168
+ return { ok: false, reason: "target manifest approval_digest must be sha256:<hex>" };
169
+ }
170
+ if (typeof raw.revocation_epoch !== "number" || !Number.isInteger(raw.revocation_epoch) || raw.revocation_epoch < 0) {
171
+ return { ok: false, reason: "target manifest revocation_epoch must be a non-negative integer" };
172
+ }
173
+ return { ok: true, manifest: raw };
174
+ }
175
+ // Typed unreachability: until foo-mcp-server-v0 exists, every service call
176
+ // fails closed to this. Callers record revocation_pending; nothing pretends
177
+ // the hosted state changed.
178
+ export class DiscoveryServiceUnreachableError extends Error {
179
+ op;
180
+ constructor(op) {
181
+ super(`discovery service unreachable (${op}); hosted state is unchanged`);
182
+ this.op = op;
183
+ this.name = "DiscoveryServiceUnreachableError";
184
+ }
185
+ }
186
+ export function createUnreachableDiscoveryService() {
187
+ const refuse = async (op) => {
188
+ throw new DiscoveryServiceUnreachableError(op);
189
+ };
190
+ return {
191
+ registerPendingTarget: () => refuse("register"),
192
+ confirmTargetRegistration: () => refuse("confirm"),
193
+ suspendTarget: () => refuse("suspend"),
194
+ };
195
+ }
196
+ // The local runtime's register step: validate the manifest, hand it to the
197
+ // service as a pending request, and return needs_service_grant with the
198
+ // productfoo-owned approval handoff. Never returns a target_ref — that requires the
199
+ // out-of-band confirmation (see confirmDiscoveryTargetRegistration).
200
+ export async function registerDiscoveryTarget(input) {
201
+ const check = validateDiscoveryTargetManifest(input.manifest);
202
+ if (!check.ok)
203
+ return { status: "invalid", errors: [{ code: "E_DISCOVERY_MANIFEST_INVALID", message: check.reason }] };
204
+ if (!input.subject_ref?.trim())
205
+ return { status: "invalid", errors: [{ code: "E_DISCOVERY_SUBJECT_REQUIRED", message: "target registration requires the authenticated subject reference" }] };
206
+ try {
207
+ const stored = await input.service.registerPendingTarget({ subject_ref: input.subject_ref, manifest: check.manifest, requested_at: input.at });
208
+ if (!stored.stored)
209
+ return { status: "invalid", errors: [{ code: "E_DISCOVERY_SERVICE_REFUSED", message: stored.message }] };
210
+ return {
211
+ status: "needs_service_grant",
212
+ pending_request_id: stored.pending_request_id,
213
+ handoff: {
214
+ kind: "service_grant_approval",
215
+ requires_human: true,
216
+ },
217
+ next_actions: [
218
+ "A person must confirm this exact target manifest through productfoo's authenticated out-of-band confirmation surface before any hosted read is possible.",
219
+ ],
220
+ };
221
+ }
222
+ catch (error) {
223
+ if (error instanceof DiscoveryServiceUnreachableError) {
224
+ return { status: "invalid", errors: [{ code: "E_DISCOVERY_SERVICE_UNREACHABLE", message: error.message }] };
225
+ }
226
+ throw error;
227
+ }
228
+ }
229
+ // The confirmation flow: the seam reports the out-of-band human decision for
230
+ // the exact manifest; only a confirmed decision reaches the service, which
231
+ // mints the target_ref and activates the service grant. Anything else stays
232
+ // pending (needs_service_grant) — a deferred or refused manifest never
233
+ // becomes usable, and no local path can mint the ref itself.
234
+ export async function confirmDiscoveryTargetRegistration(input) {
235
+ const check = validateDiscoveryTargetManifest(input.manifest);
236
+ if (!check.ok)
237
+ return { status: "invalid", errors: [{ code: "E_DISCOVERY_MANIFEST_INVALID", message: check.reason }] };
238
+ let decision;
239
+ try {
240
+ decision = await input.seam.confirmTargetManifest(check.manifest);
241
+ }
242
+ catch (error) {
243
+ if (error instanceof DiscoveryServiceUnreachableError) {
244
+ return { status: "needs_service_grant", reason: "service_unreachable", handoff: { kind: "service_grant_approval", requires_human: true } };
245
+ }
246
+ throw error;
247
+ }
248
+ if (!decision.confirmed) {
249
+ return {
250
+ status: "needs_service_grant",
251
+ reason: decision.reason,
252
+ handoff: { kind: "service_grant_approval", requires_human: true },
253
+ };
254
+ }
255
+ const confirmed = await input.service.confirmTargetRegistration({ manifest: check.manifest, confirmation: decision, at: input.at });
256
+ if (!confirmed.confirmed_registration) {
257
+ return { status: "invalid", errors: [{ code: "E_DISCOVERY_CONFIRM_REFUSED", message: confirmed.message }] };
258
+ }
259
+ return {
260
+ status: "ready",
261
+ target_ref: confirmed.target_ref,
262
+ grant: confirmed.grant,
263
+ next_actions: ["Run the hosted readiness operation before any listing; local source approval is still required for repository apply."],
264
+ };
265
+ }
266
+ const GRANT_KEYS = new Set([
267
+ "schema_version",
268
+ "kind",
269
+ "subject_ref",
270
+ "workspace_ref",
271
+ "target_ref",
272
+ "target_id",
273
+ "lane",
274
+ "connection_ref",
275
+ "approved_selectors",
276
+ "approval_digest",
277
+ "revocation_epoch",
278
+ "issued_at",
279
+ "expires_at",
280
+ "budget",
281
+ ]);
282
+ export function validateDiscoveryServiceGrant(value) {
283
+ if (!value || typeof value !== "object" || Array.isArray(value))
284
+ return { ok: false, reason: "service grant must be an object" };
285
+ const raw = value;
286
+ for (const key of Object.keys(raw)) {
287
+ if (FORBIDDEN_MANIFEST_KEY.test(key))
288
+ return { ok: false, reason: `service grant forbids content-bearing field ${key}` };
289
+ if (!GRANT_KEYS.has(key))
290
+ return { ok: false, reason: `service grant field ${key} is not part of the closed grant` };
291
+ }
292
+ if (raw.schema_version !== CONNECTIONS_SCHEMA_VERSION)
293
+ return { ok: false, reason: "service grant schema_version must be 0.1" };
294
+ if (raw.kind !== "discovery_service_grant")
295
+ return { ok: false, reason: "service grant kind must be discovery_service_grant" };
296
+ if (typeof raw.subject_ref !== "string" || !raw.subject_ref.trim())
297
+ return { ok: false, reason: "service grant subject_ref must be a non-empty string" };
298
+ if (typeof raw.workspace_ref !== "string" || !raw.workspace_ref.trim())
299
+ return { ok: false, reason: "service grant workspace_ref must be a non-empty string" };
300
+ if (!isTargetRef(raw.target_ref))
301
+ return { ok: false, reason: "service grant target_ref must be an opaque dtarget: reference" };
302
+ if (typeof raw.target_id !== "string" || (!STUDY_TARGET.test(raw.target_id) && !INBOX_TARGET.test(raw.target_id))) {
303
+ return { ok: false, reason: "service grant target_id must be study_<slug> or inbox:<slug>" };
304
+ }
305
+ if (raw.lane !== "study" && raw.lane !== "inbox")
306
+ return { ok: false, reason: "service grant lane must be study or inbox" };
307
+ if (!isConnectionRef(raw.connection_ref))
308
+ return { ok: false, reason: "service grant connection_ref must be an opaque conn: reference" };
309
+ const selectors = raw.approved_selectors;
310
+ if (!Array.isArray(selectors) || selectors.length === 0 || !selectors.every((s) => typeof s === "string" && s.trim().length > 0)) {
311
+ return { ok: false, reason: "service grant approved_selectors must be a non-empty string array" };
312
+ }
313
+ if (typeof raw.approval_digest !== "string" || !SHA256_DIGEST.test(raw.approval_digest))
314
+ return { ok: false, reason: "service grant approval_digest must be sha256:<hex>" };
315
+ if (typeof raw.revocation_epoch !== "number" || !Number.isInteger(raw.revocation_epoch) || raw.revocation_epoch < 0) {
316
+ return { ok: false, reason: "service grant revocation_epoch must be a non-negative integer" };
317
+ }
318
+ for (const field of ["issued_at", "expires_at"]) {
319
+ if (typeof raw[field] !== "string" || !Number.isFinite(Date.parse(raw[field]))) {
320
+ return { ok: false, reason: `service grant ${field} must be an ISO timestamp` };
321
+ }
322
+ }
323
+ const budget = raw.budget;
324
+ if (!budget || typeof budget !== "object" || typeof budget.max_operations !== "number" || !Number.isInteger(budget.max_operations) || budget.max_operations <= 0 || budget.window !== "day") {
325
+ return { ok: false, reason: "service grant budget must be {max_operations: positive integer, window: \"day\"}" };
326
+ }
327
+ return { ok: true, grant: raw };
328
+ }
329
+ // The recheck invoked at every access boundary: subject, workspace, target,
330
+ // lane, approval digest, revocation epoch, expiry, selector scope, and budget,
331
+ // fail-closed, first mismatch wins. Pure — no I/O — so every surface can run
332
+ // it exactly where the access happens.
333
+ export function recheckDiscoveryAccessBoundary(input) {
334
+ const grantCheck = validateDiscoveryServiceGrant(input.grant);
335
+ if (!grantCheck.ok)
336
+ return { ok: false, reason: "unknown_grant", message: "no active service grant: verify the target was registered and confirmed out of band" };
337
+ const grant = grantCheck.grant;
338
+ const e = input.expected;
339
+ if (grant.subject_ref !== e.subject_ref)
340
+ return { ok: false, reason: "subject_mismatch", message: "the authenticated subject is not the grant's subject" };
341
+ if (grant.connection_ref !== e.connection_ref)
342
+ return { ok: false, reason: "connection_mismatch", message: "the connection reference does not match the grant" };
343
+ if (grant.workspace_ref !== e.workspace_ref)
344
+ return { ok: false, reason: "workspace_mismatch", message: "the workspace reference does not match the grant" };
345
+ if (grant.target_id !== e.target_id)
346
+ return { ok: false, reason: "target_mismatch", message: "the discovery target does not match the grant" };
347
+ if (grant.lane !== e.lane)
348
+ return { ok: false, reason: "lane_mismatch", message: "the discovery lane does not match the grant" };
349
+ if (grant.approval_digest !== e.approval_digest)
350
+ return { ok: false, reason: "approval_mismatch", message: "the local approval digest no longer matches the grant" };
351
+ if (grant.revocation_epoch !== e.revocation_epoch)
352
+ return { ok: false, reason: "stale_revocation_epoch", message: "the grant predates the current revocation epoch" };
353
+ const now = Date.parse(input.now);
354
+ if (!Number.isFinite(now) || now > Date.parse(grant.expires_at))
355
+ return { ok: false, reason: "grant_expired", message: "the service grant has expired" };
356
+ if (e.selector !== undefined && !grant.approved_selectors.includes(e.selector)) {
357
+ return { ok: false, reason: "selector_out_of_scope", message: "the requested selector is outside the grant's approved selectors" };
358
+ }
359
+ if (typeof input.operations_used === "number" && input.operations_used >= grant.budget.max_operations) {
360
+ return { ok: false, reason: "budget_exhausted", message: "the grant's operation budget is exhausted" };
361
+ }
362
+ return { ok: true };
363
+ }
364
+ export class DiscoveryBoundaryRefused extends Error {
365
+ check;
366
+ constructor(check) {
367
+ super(`discovery access boundary refused (${check.reason}): ${check.message}`);
368
+ this.check = check;
369
+ this.name = "DiscoveryBoundaryRefused";
370
+ }
371
+ }
372
+ // Turns a failed recheck into a typed refusal; ok passes through silently.
373
+ export function assertDiscoveryBoundary(check) {
374
+ if (!check.ok)
375
+ throw new DiscoveryBoundaryRefused(check);
376
+ }
377
+ // The structural guarantee behind C-019: the guarded access (the transcript
378
+ // fetch) CANNOT run unless the boundary recheck passes. Every access boundary
379
+ // wraps its provider call in this, so a forged digest, stale epoch,
380
+ // unconfirmed manifest, or unregistered target costs zero provider calls.
381
+ export async function guardedDiscoveryAccess(check, access) {
382
+ assertDiscoveryBoundary(check);
383
+ return access();
384
+ }
385
+ // --- In-repo service fixture (V0) --------------------------------------------
386
+ // A working service endpoint for tests and the contract's executable proof.
387
+ // It is NOT a hosted service and never leaves process memory: the real
388
+ // implementation of these ops is foo-mcp-server-v0's, behind authenticated
389
+ // identity and transport this repo does not build (hosted tiers stay
390
+ // contract_only). It demonstrates the exact semantics the packet requires:
391
+ // pending-only registration, target_ref + grant minted only on a confirmed
392
+ // out-of-band decision, privilege-reducing suspension that increments the
393
+ // revocation epoch before acknowledging, and subject checks on every call.
394
+ export function createInMemoryDiscoveryService(opts) {
395
+ const salt = opts?.server_salt ?? "";
396
+ const ttl = opts?.grant_ttl_ms ?? 24 * 60 * 60_000;
397
+ const budget = opts?.default_budget ?? { max_operations: 4, window: "day" };
398
+ const pending = new Map();
399
+ const targets = new Map();
400
+ const digestIsAuthenticated = (digest) => SHA256_DIGEST.test(digest);
401
+ return {
402
+ async registerPendingTarget({ subject_ref, manifest, requested_at }) {
403
+ if (!subject_ref.trim())
404
+ return { stored: false, reason: "invalid_subject", message: "registering a target requires the authenticated productfoo subject reference" };
405
+ const id = `dpr:${opaqueRef("foo_discovery_pending/v1", [subject_ref, manifest.target_id, manifest.connection_ref, requested_at, String(pending.size)])}`;
406
+ pending.set(id, { schema_version: CONNECTIONS_SCHEMA_VERSION, kind: "discovery_target_pending_request", subject_ref, manifest, requested_at, state: "pending" });
407
+ return { stored: true, pending_request_id: id };
408
+ },
409
+ async confirmTargetRegistration({ manifest, confirmation, at }) {
410
+ if (!confirmation.confirmed) {
411
+ return { confirmed_registration: false, reason: "not_confirmed", message: `the out-of-band confirmation did not approve this manifest (${confirmation.reason})` };
412
+ }
413
+ const request = [...pending.values()].find((req) => JSON.stringify(req.manifest) === JSON.stringify(manifest));
414
+ if (!request) {
415
+ return { confirmed_registration: false, reason: "unknown_pending_request", message: "no pending target request matches this manifest; register it first" };
416
+ }
417
+ if (!confirmation.confirmed_by.trim() || !Number.isFinite(Date.parse(confirmation.confirmed_at))) {
418
+ return { confirmed_registration: false, reason: "invalid_confirmation", message: "the confirmation decision lacks a person and a time" };
419
+ }
420
+ // The ref binds the exact confirmed manifest plus who/when confirmed it:
421
+ // the same manifest confirmed by the same person yields the same ref,
422
+ // and no ref exists without a confirmation record.
423
+ const target_ref = `dtarget:${opaqueRef(TARGET_REF_DOMAIN, [JSON.stringify(manifest), confirmation.confirmed_by, confirmation.confirmed_at, salt])}`;
424
+ const grant = {
425
+ schema_version: CONNECTIONS_SCHEMA_VERSION,
426
+ kind: "discovery_service_grant",
427
+ subject_ref: request.subject_ref,
428
+ workspace_ref: manifest.workspace_ref,
429
+ target_ref,
430
+ target_id: manifest.target_id,
431
+ lane: manifest.lane,
432
+ connection_ref: manifest.connection_ref,
433
+ approved_selectors: [...manifest.approved_selectors],
434
+ approval_digest: manifest.approval_digest,
435
+ revocation_epoch: manifest.revocation_epoch,
436
+ issued_at: new Date(at).toISOString(),
437
+ expires_at: new Date(Date.parse(at) + ttl).toISOString(),
438
+ budget: { ...budget },
439
+ };
440
+ targets.set(target_ref, { manifest, subject_ref: request.subject_ref, target_ref, grant, revocation_epoch: manifest.revocation_epoch, suspended: false, operations_used: 0 });
441
+ return { confirmed_registration: true, target_ref, grant };
442
+ },
443
+ async suspendTarget({ target_ref, acting_subject, revocation_event_digest, requested_by, at }) {
444
+ if (!digestIsAuthenticated(revocation_event_digest)) {
445
+ return { suspended: false, target_ref, reason: "invalid_request", message: "target suspension requires the authenticated local revocation event digest" };
446
+ }
447
+ const record = targets.get(target_ref);
448
+ if (!record)
449
+ return { suspended: false, target_ref, reason: "unknown_target_ref", message: "no registered target for this reference" };
450
+ if (record.subject_ref !== acting_subject)
451
+ return { suspended: false, target_ref, reason: "subject_mismatch", message: "the acting subject is not the target's registered subject" };
452
+ if (!requested_by.trim())
453
+ return { suspended: false, target_ref, reason: "invalid_request", message: "target suspension requires the requesting actor" };
454
+ record.revocation_epoch += 1;
455
+ record.suspended = true;
456
+ record.grant = { ...record.grant, revocation_epoch: record.revocation_epoch };
457
+ void at;
458
+ return { suspended: true, target_ref: record.target_ref, revocation_epoch: record.revocation_epoch };
459
+ },
460
+ currentRevocationEpoch(target_ref) {
461
+ return targets.get(target_ref)?.revocation_epoch ?? null;
462
+ },
463
+ operationsUsed(target_ref) {
464
+ return targets.get(target_ref)?.operations_used ?? null;
465
+ },
466
+ recordOperation(target_ref) {
467
+ const record = targets.get(target_ref);
468
+ if (record)
469
+ record.operations_used += 1;
470
+ },
471
+ pendingRequests() {
472
+ return [...pending.values()];
473
+ },
474
+ };
475
+ }
476
+ function safeDiscoveryTargetDir(targetId) {
477
+ if (STUDY_TARGET.test(targetId))
478
+ return targetId;
479
+ if (INBOX_TARGET.test(targetId))
480
+ return targetId.replace(":", "_");
481
+ throw new Error("invalid discovery target id");
482
+ }
483
+ function registeredTargetPath(localRoot, targetId) {
484
+ return path.join(path.resolve(localRoot), ".foo", "discovery", safeDiscoveryTargetDir(targetId), "target.json");
485
+ }
486
+ // Persists the registry record for a confirmed registration. `local` supplies
487
+ // the local approval linkage the manifest intentionally does not carry
488
+ // (the manifest references the connection only by its opaque ref).
489
+ export function recordDiscoveryTargetRegistration(localRoot, input) {
490
+ const record = {
491
+ schema_version: CONNECTIONS_SCHEMA_VERSION,
492
+ kind: "discovery_registered_target",
493
+ subject_ref: input.local.subject_ref,
494
+ target_ref: input.result.target_ref,
495
+ workspace_ref: input.manifest.workspace_ref,
496
+ target_id: input.manifest.target_id,
497
+ lane: input.manifest.lane,
498
+ packet: input.manifest.packet,
499
+ spec_id: input.manifest.spec_id,
500
+ provider: input.local.provider.toLowerCase(),
501
+ connection_id: input.local.connection_id,
502
+ connection_ref: input.manifest.connection_ref,
503
+ approved_selectors: [...input.manifest.approved_selectors],
504
+ approval_digest: input.manifest.approval_digest,
505
+ revocation_epoch: input.manifest.revocation_epoch,
506
+ registered_at: input.at,
507
+ suspension: null,
508
+ };
509
+ const file = registeredTargetPath(localRoot, record.target_id);
510
+ mkdirSync(path.dirname(file), { recursive: true });
511
+ writeFileSync(file, `${JSON.stringify(record, null, 2)}\n`, { mode: 0o600 });
512
+ return record;
513
+ }
514
+ function parseRegisteredTarget(file) {
515
+ if (!existsSync(file))
516
+ return null;
517
+ let parsed;
518
+ try {
519
+ parsed = JSON.parse(readFileSync(file, "utf8"));
520
+ }
521
+ catch {
522
+ return null; // fail-closed: an unparseable record is skipped, never trusted
523
+ }
524
+ const raw = parsed;
525
+ if (!raw || raw.schema_version !== CONNECTIONS_SCHEMA_VERSION || raw.kind !== "discovery_registered_target" || !isTargetRef(raw.target_ref))
526
+ return null;
527
+ return raw;
528
+ }
529
+ export function loadDiscoveryRegisteredTargets(localRoot) {
530
+ const root = path.join(path.resolve(localRoot), ".foo", "discovery");
531
+ if (!existsSync(root))
532
+ return [];
533
+ const records = [];
534
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
535
+ if (!entry.isDirectory())
536
+ continue;
537
+ const record = parseRegisteredTarget(path.join(root, entry.name, "target.json"));
538
+ if (record)
539
+ records.push(record);
540
+ }
541
+ return records;
542
+ }
543
+ // The approval→registered-target lookup: every hosted target registered from
544
+ // the local approval `foo source revoke` is about to tear down.
545
+ export function findDiscoveryTargetsForApproval(localRoot, approval) {
546
+ const provider = approval.provider.trim().toLowerCase();
547
+ return loadDiscoveryRegisteredTargets(localRoot).filter((record) => record.provider === provider && record.connection_id === approval.connection_id);
548
+ }
549
+ // Fans a local source revoke out to every registered hosted target as
550
+ // privilege-reducing discovery_suspend_target calls. Ordering and honesty
551
+ // rules (R-008):
552
+ //
553
+ // - the caller has ALREADY applied the immediate local revoke before this
554
+ // runs, and nothing here can weaken or undo it — a service failure never
555
+ // raises, it records revocation_pending;
556
+ // - an acknowledgement means the service incremented the target's
557
+ // revocation epoch and blocked hosted reads at the new epoch;
558
+ // - unreachability (the V0 default, since foo-mcp-server-v0 is unbuilt)
559
+ // records revocation_pending and never claims the hosted grant or
560
+ // connection was revoked.
561
+ export async function fanOutDiscoveryTargetSuspension(input) {
562
+ const records = findDiscoveryTargetsForApproval(input.localRoot, { provider: input.provider, connection_id: input.connection_id })
563
+ .filter((record) => input.target_id === undefined || record.target_id === input.target_id);
564
+ const outcomes = [];
565
+ let acknowledged = 0;
566
+ let pending = 0;
567
+ if (!SHA256_DIGEST.test(input.revocation_event_digest)) {
568
+ // Fail closed: without the authenticated revocation event digest the
569
+ // suspension request is never sent, and the honest state is pending.
570
+ for (const record of records) {
571
+ pending += 1;
572
+ outcomes.push({ target_ref: record.target_ref, target_id: record.target_id, status: "revocation_pending", reason: "invalid_request", message: "the local revocation event digest is missing or malformed; the suspension request was not sent" });
573
+ }
574
+ return { registered: records.length, acknowledged, pending, outcomes };
575
+ }
576
+ for (const record of records) {
577
+ try {
578
+ const ack = await (input.service ?? createUnreachableDiscoveryService()).suspendTarget({
579
+ target_ref: record.target_ref,
580
+ acting_subject: record.subject_ref,
581
+ revocation_event_digest: input.revocation_event_digest,
582
+ requested_by: input.requested_by,
583
+ at: input.at,
584
+ });
585
+ if (ack.suspended) {
586
+ acknowledged += 1;
587
+ record.suspension = { epoch: ack.revocation_epoch, state: "acknowledged", recorded_at: input.at };
588
+ outcomes.push({ target_ref: record.target_ref, target_id: record.target_id, status: "suspended", revocation_epoch: ack.revocation_epoch });
589
+ }
590
+ else {
591
+ pending += 1;
592
+ record.suspension = { epoch: record.revocation_epoch, state: "revocation_pending", recorded_at: input.at };
593
+ outcomes.push({ target_ref: record.target_ref, target_id: record.target_id, status: "revocation_pending", reason: ack.reason, message: ack.message });
594
+ }
595
+ }
596
+ catch (error) {
597
+ pending += 1;
598
+ record.suspension = { epoch: record.revocation_epoch, state: "revocation_pending", recorded_at: input.at };
599
+ const reason = error instanceof DiscoveryServiceUnreachableError ? "service_unreachable" : "service_error";
600
+ const message = error instanceof DiscoveryServiceUnreachableError
601
+ ? "the discovery service is unreachable; hosted suspension is not yet acknowledged and hosted reads may continue until it confirms"
602
+ : "the discovery service could not acknowledge the suspension; hosted suspension is not yet acknowledged";
603
+ outcomes.push({ target_ref: record.target_ref, target_id: record.target_id, status: "revocation_pending", reason, message });
604
+ }
605
+ // Persist the acknowledgement/pending state on the record — the fan-out
606
+ // never deletes or rewrites the rest of the registry entry.
607
+ const file = registeredTargetPath(input.localRoot, record.target_id);
608
+ if (existsSync(file)) {
609
+ writeFileSync(file, `${JSON.stringify(record, null, 2)}\n`, { mode: 0o600 });
610
+ }
611
+ }
612
+ return { registered: records.length, acknowledged, pending, outcomes };
613
+ }