@dxos/plugin-github 0.7.4 → 0.13.0

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 (628) hide show
  1. package/LICENSE +102 -5
  2. package/PLUGIN.mdl +784 -0
  3. package/dist/lib/GitHubCapabilities.mjs +10 -0
  4. package/dist/lib/GitHubCapabilities.mjs.map +1 -0
  5. package/dist/lib/GitHubEvents.mjs +15 -0
  6. package/dist/lib/GitHubEvents.mjs.map +1 -0
  7. package/dist/lib/GitHubOperation.mjs +358 -0
  8. package/dist/lib/GitHubOperation.mjs.map +1 -0
  9. package/dist/lib/GitHubPlugin.mjs +16 -0
  10. package/dist/lib/GitHubPlugin.mjs.map +1 -0
  11. package/dist/lib/Walkthrough.mjs +59 -0
  12. package/dist/lib/Walkthrough.mjs.map +1 -0
  13. package/dist/lib/capabilities.browser.mjs +54 -0
  14. package/dist/lib/capabilities.browser.mjs.map +1 -0
  15. package/dist/lib/capabilities.mjs +54 -0
  16. package/dist/lib/capabilities.mjs.map +1 -0
  17. package/dist/lib/capabilities.node.mjs +30 -0
  18. package/dist/lib/capabilities.node.mjs.map +1 -0
  19. package/dist/lib/capabilities.tauri.mjs +54 -0
  20. package/dist/lib/capabilities.tauri.mjs.map +1 -0
  21. package/dist/lib/capabilities.workerd.mjs +30 -0
  22. package/dist/lib/capabilities.workerd.mjs.map +1 -0
  23. package/dist/lib/chunk-ImportPullRequestDialog.mjs +116 -0
  24. package/dist/lib/chunk-ImportPullRequestDialog.mjs.map +1 -0
  25. package/dist/lib/chunk-PLUGIN.mjs +6 -0
  26. package/dist/lib/chunk-PLUGIN.mjs.map +1 -0
  27. package/dist/lib/chunk-PullRequestArticle.mjs +1486 -0
  28. package/dist/lib/chunk-PullRequestArticle.mjs.map +1 -0
  29. package/dist/lib/chunk-PullRequestArticle2.mjs +2 -0
  30. package/dist/lib/chunk-PullRequestCardMenu.mjs +123 -0
  31. package/dist/lib/chunk-PullRequestCardMenu.mjs.map +1 -0
  32. package/dist/lib/chunk-PullRequestsArticle.mjs +594 -0
  33. package/dist/lib/chunk-PullRequestsArticle.mjs.map +1 -0
  34. package/dist/lib/chunk-WalkthroughArticle.mjs +26 -0
  35. package/dist/lib/chunk-WalkthroughArticle.mjs.map +1 -0
  36. package/dist/lib/chunk-add-pull-request-comment.mjs +19 -0
  37. package/dist/lib/chunk-add-pull-request-comment.mjs.map +1 -0
  38. package/dist/lib/chunk-add-pull-request-review-comment.mjs +25 -0
  39. package/dist/lib/chunk-add-pull-request-review-comment.mjs.map +1 -0
  40. package/dist/lib/chunk-app-graph-builder.mjs +39 -0
  41. package/dist/lib/chunk-app-graph-builder.mjs.map +1 -0
  42. package/dist/lib/chunk-connector.mjs +70 -0
  43. package/dist/lib/chunk-connector.mjs.map +1 -0
  44. package/dist/lib/chunk-constants.mjs +11 -0
  45. package/dist/lib/chunk-constants.mjs.map +1 -0
  46. package/dist/lib/chunk-errors.mjs +29 -0
  47. package/dist/lib/chunk-errors.mjs.map +1 -0
  48. package/dist/lib/chunk-generate-walkthrough.mjs +86 -0
  49. package/dist/lib/chunk-generate-walkthrough.mjs.map +1 -0
  50. package/dist/lib/chunk-generate.mjs +719 -0
  51. package/dist/lib/chunk-generate.mjs.map +1 -0
  52. package/dist/lib/chunk-get-pull-request-diff.mjs +25 -0
  53. package/dist/lib/chunk-get-pull-request-diff.mjs.map +1 -0
  54. package/dist/lib/chunk-get-pull-request-status.mjs +29 -0
  55. package/dist/lib/chunk-get-pull-request-status.mjs.map +1 -0
  56. package/dist/lib/chunk-get-repositories.mjs +32 -0
  57. package/dist/lib/chunk-get-repositories.mjs.map +1 -0
  58. package/dist/lib/chunk-github-api.mjs +375 -0
  59. package/dist/lib/chunk-github-api.mjs.map +1 -0
  60. package/dist/lib/chunk-github-link.mjs +59 -0
  61. package/dist/lib/chunk-github-link.mjs.map +1 -0
  62. package/dist/lib/chunk-import-pull-request-from-snapshot.mjs +25 -0
  63. package/dist/lib/chunk-import-pull-request-from-snapshot.mjs.map +1 -0
  64. package/dist/lib/chunk-import-pull-request.mjs +96 -0
  65. package/dist/lib/chunk-import-pull-request.mjs.map +1 -0
  66. package/dist/lib/chunk-link-resolver.mjs +109 -0
  67. package/dist/lib/chunk-link-resolver.mjs.map +1 -0
  68. package/dist/lib/chunk-markdown-extension.mjs +92 -0
  69. package/dist/lib/chunk-markdown-extension.mjs.map +1 -0
  70. package/dist/lib/chunk-materialize-target.mjs +36 -0
  71. package/dist/lib/chunk-materialize-target.mjs.map +1 -0
  72. package/dist/lib/chunk-operation-handler.mjs +12 -0
  73. package/dist/lib/chunk-operation-handler.mjs.map +1 -0
  74. package/dist/lib/chunk-page-action.mjs +22 -0
  75. package/dist/lib/chunk-page-action.mjs.map +1 -0
  76. package/dist/lib/chunk-pull-request.mjs +58 -0
  77. package/dist/lib/chunk-pull-request.mjs.map +1 -0
  78. package/dist/lib/chunk-pull-request2.mjs +110 -0
  79. package/dist/lib/chunk-pull-request2.mjs.map +1 -0
  80. package/dist/lib/chunk-react-surface.mjs +233 -0
  81. package/dist/lib/chunk-react-surface.mjs.map +1 -0
  82. package/dist/lib/chunk-rolldown-runtime.mjs +13 -0
  83. package/dist/lib/chunk-schema.mjs +15 -0
  84. package/dist/lib/chunk-schema.mjs.map +1 -0
  85. package/dist/lib/chunk-skill-definition.mjs +10 -0
  86. package/dist/lib/chunk-skill-definition.mjs.map +1 -0
  87. package/dist/lib/chunk-submit-pull-request-approval.mjs +56 -0
  88. package/dist/lib/chunk-submit-pull-request-approval.mjs.map +1 -0
  89. package/dist/lib/chunk-sync-pull-request.mjs +24 -0
  90. package/dist/lib/chunk-sync-pull-request.mjs.map +1 -0
  91. package/dist/lib/chunk-sync.mjs +481 -0
  92. package/dist/lib/chunk-sync.mjs.map +1 -0
  93. package/dist/lib/chunk-useOpenObject.mjs +43 -0
  94. package/dist/lib/chunk-useOpenObject.mjs.map +1 -0
  95. package/dist/lib/extensions.mjs +132 -0
  96. package/dist/lib/extensions.mjs.map +1 -0
  97. package/dist/lib/index.mjs +4 -0
  98. package/dist/lib/meta.mjs +47 -0
  99. package/dist/lib/meta.mjs.map +1 -0
  100. package/dist/lib/ns/Extensions.mjs +18 -0
  101. package/dist/lib/ns/Extensions.mjs.map +1 -0
  102. package/dist/lib/operations.mjs +22 -0
  103. package/dist/lib/operations.mjs.map +1 -0
  104. package/dist/lib/plugin.mjs +9 -0
  105. package/dist/lib/plugin.mjs.map +1 -0
  106. package/dist/lib/skills.mjs +44 -0
  107. package/dist/lib/skills.mjs.map +1 -0
  108. package/dist/lib/testing.mjs +189 -0
  109. package/dist/lib/testing.mjs.map +1 -0
  110. package/dist/lib/translations.mjs +136 -0
  111. package/dist/lib/translations.mjs.map +1 -0
  112. package/dist/lib/types.mjs +5 -0
  113. package/dist/types/dx.config.d.ts +28 -0
  114. package/dist/types/dx.config.d.ts.map +1 -0
  115. package/dist/types/src/Extensions.d.ts +2 -0
  116. package/dist/types/src/Extensions.d.ts.map +1 -0
  117. package/dist/types/src/GitHubPlugin.d.ts +37 -0
  118. package/dist/types/src/GitHubPlugin.d.ts.map +1 -0
  119. package/dist/types/src/GitHubPlugin.workerd.test.d.ts +2 -0
  120. package/dist/types/src/GitHubPlugin.workerd.test.d.ts.map +1 -0
  121. package/dist/types/src/approval.d.ts +21 -0
  122. package/dist/types/src/approval.d.ts.map +1 -0
  123. package/dist/types/src/approval.test.d.ts +2 -0
  124. package/dist/types/src/approval.test.d.ts.map +1 -0
  125. package/dist/types/src/capabilities/app-graph-builder.d.ts +9 -0
  126. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  127. package/dist/types/src/capabilities/connector.d.ts +17 -0
  128. package/dist/types/src/capabilities/connector.d.ts.map +1 -0
  129. package/dist/types/src/capabilities/gen/browser.d.ts +13 -0
  130. package/dist/types/src/capabilities/gen/browser.d.ts.map +1 -0
  131. package/dist/types/src/capabilities/gen/node.d.ts +13 -0
  132. package/dist/types/src/capabilities/gen/node.d.ts.map +1 -0
  133. package/dist/types/src/capabilities/gen/tauri.d.ts +13 -0
  134. package/dist/types/src/capabilities/gen/tauri.d.ts.map +1 -0
  135. package/dist/types/src/capabilities/gen/workerd.d.ts +13 -0
  136. package/dist/types/src/capabilities/gen/workerd.d.ts.map +1 -0
  137. package/dist/types/src/capabilities/index.d.ts +13 -0
  138. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  139. package/dist/types/src/capabilities/link-resolver.d.ts +10 -0
  140. package/dist/types/src/capabilities/link-resolver.d.ts.map +1 -0
  141. package/dist/types/src/capabilities/markdown-extension.d.ts +14 -0
  142. package/dist/types/src/capabilities/markdown-extension.d.ts.map +1 -0
  143. package/dist/types/src/capabilities/operation-handler.d.ts +5 -0
  144. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  145. package/dist/types/src/capabilities/page-action.d.ts +5 -0
  146. package/dist/types/src/capabilities/page-action.d.ts.map +1 -0
  147. package/dist/types/src/capabilities/react-surface.d.ts +5 -0
  148. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  149. package/dist/types/src/capabilities/schema.d.ts +12 -0
  150. package/dist/types/src/capabilities/schema.d.ts.map +1 -0
  151. package/dist/types/src/capabilities/skill-definition.d.ts +5 -0
  152. package/dist/types/src/capabilities/skill-definition.d.ts.map +1 -0
  153. package/dist/types/src/cards/GitHubCard.d.ts +11 -0
  154. package/dist/types/src/cards/GitHubCard.d.ts.map +1 -0
  155. package/dist/types/src/cards/GitHubCard.stories.d.ts +45 -0
  156. package/dist/types/src/cards/GitHubCard.stories.d.ts.map +1 -0
  157. package/dist/types/src/cards/PullRequestCard.d.ts +9 -0
  158. package/dist/types/src/cards/PullRequestCard.d.ts.map +1 -0
  159. package/dist/types/src/cards/index.d.ts +3 -0
  160. package/dist/types/src/cards/index.d.ts.map +1 -0
  161. package/dist/types/src/components/CommentComposer/CommentComposer.d.ts +30 -0
  162. package/dist/types/src/components/CommentComposer/CommentComposer.d.ts.map +1 -0
  163. package/dist/types/src/components/CommentComposer/index.d.ts +2 -0
  164. package/dist/types/src/components/CommentComposer/index.d.ts.map +1 -0
  165. package/dist/types/src/components/PullRequestFiles/DiffStat.d.ts +8 -0
  166. package/dist/types/src/components/PullRequestFiles/DiffStat.d.ts.map +1 -0
  167. package/dist/types/src/components/PullRequestFiles/FileTree.d.ts +17 -0
  168. package/dist/types/src/components/PullRequestFiles/FileTree.d.ts.map +1 -0
  169. package/dist/types/src/components/PullRequestFiles/PullRequestFiles.d.ts +23 -0
  170. package/dist/types/src/components/PullRequestFiles/PullRequestFiles.d.ts.map +1 -0
  171. package/dist/types/src/components/PullRequestFiles/files.d.ts +39 -0
  172. package/dist/types/src/components/PullRequestFiles/files.d.ts.map +1 -0
  173. package/dist/types/src/components/PullRequestFiles/files.test.d.ts +2 -0
  174. package/dist/types/src/components/PullRequestFiles/files.test.d.ts.map +1 -0
  175. package/dist/types/src/components/PullRequestFiles/index.d.ts +5 -0
  176. package/dist/types/src/components/PullRequestFiles/index.d.ts.map +1 -0
  177. package/dist/types/src/components/PullRequestOverview/ArtifactPill.d.ts +21 -0
  178. package/dist/types/src/components/PullRequestOverview/ArtifactPill.d.ts.map +1 -0
  179. package/dist/types/src/components/PullRequestOverview/CheckRunList.d.ts +15 -0
  180. package/dist/types/src/components/PullRequestOverview/CheckRunList.d.ts.map +1 -0
  181. package/dist/types/src/components/PullRequestOverview/PullRequestDetails.d.ts +10 -0
  182. package/dist/types/src/components/PullRequestOverview/PullRequestDetails.d.ts.map +1 -0
  183. package/dist/types/src/components/PullRequestOverview/PullRequestOverview.d.ts +16 -0
  184. package/dist/types/src/components/PullRequestOverview/PullRequestOverview.d.ts.map +1 -0
  185. package/dist/types/src/components/PullRequestOverview/RelatedCards.d.ts +28 -0
  186. package/dist/types/src/components/PullRequestOverview/RelatedCards.d.ts.map +1 -0
  187. package/dist/types/src/components/PullRequestOverview/index.d.ts +6 -0
  188. package/dist/types/src/components/PullRequestOverview/index.d.ts.map +1 -0
  189. package/dist/types/src/components/PullRequestStatus/PullRequestStatus.d.ts +20 -0
  190. package/dist/types/src/components/PullRequestStatus/PullRequestStatus.d.ts.map +1 -0
  191. package/dist/types/src/components/PullRequestStatus/index.d.ts +2 -0
  192. package/dist/types/src/components/PullRequestStatus/index.d.ts.map +1 -0
  193. package/dist/types/src/components/WalkthroughView/WalkthroughView.d.ts +18 -0
  194. package/dist/types/src/components/WalkthroughView/WalkthroughView.d.ts.map +1 -0
  195. package/dist/types/src/components/WalkthroughView/extensions.d.ts +16 -0
  196. package/dist/types/src/components/WalkthroughView/extensions.d.ts.map +1 -0
  197. package/dist/types/src/components/WalkthroughView/index.d.ts +3 -0
  198. package/dist/types/src/components/WalkthroughView/index.d.ts.map +1 -0
  199. package/dist/types/src/components/index.d.ts +5 -8
  200. package/dist/types/src/components/index.d.ts.map +1 -1
  201. package/dist/types/src/constants.d.ts +7 -0
  202. package/dist/types/src/constants.d.ts.map +1 -0
  203. package/dist/types/src/containers/ImportPullRequestDialog/ImportPullRequestDialog.d.ts +6 -0
  204. package/dist/types/src/containers/ImportPullRequestDialog/ImportPullRequestDialog.d.ts.map +1 -0
  205. package/dist/types/src/containers/ImportPullRequestDialog/index.d.ts +3 -0
  206. package/dist/types/src/containers/ImportPullRequestDialog/index.d.ts.map +1 -0
  207. package/dist/types/src/containers/PullRequestArticle/PullRequestArticle.d.ts +15 -0
  208. package/dist/types/src/containers/PullRequestArticle/PullRequestArticle.d.ts.map +1 -0
  209. package/dist/types/src/containers/PullRequestArticle/failure.d.ts +10 -0
  210. package/dist/types/src/containers/PullRequestArticle/failure.d.ts.map +1 -0
  211. package/dist/types/src/containers/PullRequestArticle/failure.test.d.ts +2 -0
  212. package/dist/types/src/containers/PullRequestArticle/failure.test.d.ts.map +1 -0
  213. package/dist/types/src/containers/PullRequestArticle/index.d.ts +2 -0
  214. package/dist/types/src/containers/PullRequestArticle/index.d.ts.map +1 -0
  215. package/dist/types/src/containers/PullRequestCardMenu/PullRequestCardMenu.d.ts +14 -0
  216. package/dist/types/src/containers/PullRequestCardMenu/PullRequestCardMenu.d.ts.map +1 -0
  217. package/dist/types/src/containers/PullRequestCardMenu/index.d.ts +2 -0
  218. package/dist/types/src/containers/PullRequestCardMenu/index.d.ts.map +1 -0
  219. package/dist/types/src/containers/PullRequestsArticle/PullRequestsArticle.d.ts +13 -0
  220. package/dist/types/src/containers/PullRequestsArticle/PullRequestsArticle.d.ts.map +1 -0
  221. package/dist/types/src/containers/PullRequestsArticle/arrange.d.ts +44 -0
  222. package/dist/types/src/containers/PullRequestsArticle/arrange.d.ts.map +1 -0
  223. package/dist/types/src/containers/PullRequestsArticle/arrange.test.d.ts +2 -0
  224. package/dist/types/src/containers/PullRequestsArticle/arrange.test.d.ts.map +1 -0
  225. package/dist/types/src/containers/PullRequestsArticle/format.d.ts +3 -0
  226. package/dist/types/src/containers/PullRequestsArticle/format.d.ts.map +1 -0
  227. package/dist/types/src/containers/PullRequestsArticle/index.d.ts +2 -0
  228. package/dist/types/src/containers/PullRequestsArticle/index.d.ts.map +1 -0
  229. package/dist/types/src/containers/WalkthroughArticle/WalkthroughArticle.d.ts +13 -0
  230. package/dist/types/src/containers/WalkthroughArticle/WalkthroughArticle.d.ts.map +1 -0
  231. package/dist/types/src/containers/WalkthroughArticle/index.d.ts +2 -0
  232. package/dist/types/src/containers/WalkthroughArticle/index.d.ts.map +1 -0
  233. package/dist/types/src/containers/index.d.ts +7 -0
  234. package/dist/types/src/containers/index.d.ts.map +1 -0
  235. package/dist/types/src/errors.d.ts +157 -0
  236. package/dist/types/src/errors.d.ts.map +1 -0
  237. package/dist/types/src/extensions/index.d.ts +3 -0
  238. package/dist/types/src/extensions/index.d.ts.map +1 -0
  239. package/dist/types/src/extensions/links.d.ts +21 -0
  240. package/dist/types/src/extensions/links.d.ts.map +1 -0
  241. package/dist/types/src/extensions/links.test.d.ts +2 -0
  242. package/dist/types/src/extensions/links.test.d.ts.map +1 -0
  243. package/dist/types/src/extensions/references.d.ts +23 -0
  244. package/dist/types/src/extensions/references.d.ts.map +1 -0
  245. package/dist/types/src/extensions/references.test.d.ts +2 -0
  246. package/dist/types/src/extensions/references.test.d.ts.map +1 -0
  247. package/dist/types/src/github-link.d.ts +29 -0
  248. package/dist/types/src/github-link.d.ts.map +1 -0
  249. package/dist/types/src/hooks/index.d.ts +5 -2
  250. package/dist/types/src/hooks/index.d.ts.map +1 -1
  251. package/dist/types/src/hooks/useOpenObject.d.ts +9 -0
  252. package/dist/types/src/hooks/useOpenObject.d.ts.map +1 -0
  253. package/dist/types/src/hooks/usePullRequestChecks.d.ts +10 -0
  254. package/dist/types/src/hooks/usePullRequestChecks.d.ts.map +1 -0
  255. package/dist/types/src/hooks/usePullRequestDiff.d.ts +12 -0
  256. package/dist/types/src/hooks/usePullRequestDiff.d.ts.map +1 -0
  257. package/dist/types/src/hooks/usePullRequestFiles.d.ts +22 -0
  258. package/dist/types/src/hooks/usePullRequestFiles.d.ts.map +1 -0
  259. package/dist/types/src/hooks/useSyncPullRequest.d.ts +10 -0
  260. package/dist/types/src/hooks/useSyncPullRequest.d.ts.map +1 -0
  261. package/dist/types/src/index.d.ts +3 -3
  262. package/dist/types/src/index.d.ts.map +1 -1
  263. package/dist/types/src/meta.d.ts +34 -11
  264. package/dist/types/src/meta.d.ts.map +1 -1
  265. package/dist/types/src/operations/add-pull-request-comment.d.ts +5 -0
  266. package/dist/types/src/operations/add-pull-request-comment.d.ts.map +1 -0
  267. package/dist/types/src/operations/add-pull-request-review-comment.d.ts +5 -0
  268. package/dist/types/src/operations/add-pull-request-review-comment.d.ts.map +1 -0
  269. package/dist/types/src/operations/generate-walkthrough.d.ts +11 -0
  270. package/dist/types/src/operations/generate-walkthrough.d.ts.map +1 -0
  271. package/dist/types/src/operations/get-pull-request-diff.d.ts +5 -0
  272. package/dist/types/src/operations/get-pull-request-diff.d.ts.map +1 -0
  273. package/dist/types/src/operations/get-pull-request-status.d.ts +5 -0
  274. package/dist/types/src/operations/get-pull-request-status.d.ts.map +1 -0
  275. package/dist/types/src/operations/get-repositories.d.ts +13 -0
  276. package/dist/types/src/operations/get-repositories.d.ts.map +1 -0
  277. package/dist/types/src/operations/import-pull-request-from-snapshot.d.ts +12 -0
  278. package/dist/types/src/operations/import-pull-request-from-snapshot.d.ts.map +1 -0
  279. package/dist/types/src/operations/import-pull-request.d.ts +63 -0
  280. package/dist/types/src/operations/import-pull-request.d.ts.map +1 -0
  281. package/dist/types/src/operations/import-pull-request.test.d.ts +2 -0
  282. package/dist/types/src/operations/import-pull-request.test.d.ts.map +1 -0
  283. package/dist/types/src/operations/index.d.ts +3 -0
  284. package/dist/types/src/operations/index.d.ts.map +1 -0
  285. package/dist/types/src/operations/materialize-target.d.ts +12 -0
  286. package/dist/types/src/operations/materialize-target.d.ts.map +1 -0
  287. package/dist/types/src/operations/pull-request.d.ts +48 -0
  288. package/dist/types/src/operations/pull-request.d.ts.map +1 -0
  289. package/dist/types/src/operations/pull-request.test.d.ts +2 -0
  290. package/dist/types/src/operations/pull-request.test.d.ts.map +1 -0
  291. package/dist/types/src/operations/submit-pull-request-approval.d.ts +5 -0
  292. package/dist/types/src/operations/submit-pull-request-approval.d.ts.map +1 -0
  293. package/dist/types/src/operations/sync-pull-request.d.ts +5 -0
  294. package/dist/types/src/operations/sync-pull-request.d.ts.map +1 -0
  295. package/dist/types/src/operations/sync.d.ts +123 -0
  296. package/dist/types/src/operations/sync.d.ts.map +1 -0
  297. package/dist/types/src/operations/sync.test.d.ts +2 -0
  298. package/dist/types/src/operations/sync.test.d.ts.map +1 -0
  299. package/dist/types/src/plugin.d.ts +4 -0
  300. package/dist/types/src/plugin.d.ts.map +1 -0
  301. package/dist/types/src/plugin.test.d.ts +2 -0
  302. package/dist/types/src/plugin.test.d.ts.map +1 -0
  303. package/dist/types/src/pull-request-body.d.ts +37 -0
  304. package/dist/types/src/pull-request-body.d.ts.map +1 -0
  305. package/dist/types/src/pull-request-body.test.d.ts +2 -0
  306. package/dist/types/src/pull-request-body.test.d.ts.map +1 -0
  307. package/dist/types/src/pull-request.d.ts +28 -0
  308. package/dist/types/src/pull-request.d.ts.map +1 -0
  309. package/dist/types/src/pull-request.test.d.ts +2 -0
  310. package/dist/types/src/pull-request.test.d.ts.map +1 -0
  311. package/dist/types/src/services/github-api.d.ts +332 -0
  312. package/dist/types/src/services/github-api.d.ts.map +1 -0
  313. package/dist/types/src/services/index.d.ts +2 -0
  314. package/dist/types/src/services/index.d.ts.map +1 -0
  315. package/dist/types/src/skills/GitHubSkill.d.ts +12 -0
  316. package/dist/types/src/skills/GitHubSkill.d.ts.map +1 -0
  317. package/dist/types/src/skills/index.d.ts +2 -0
  318. package/dist/types/src/skills/index.d.ts.map +1 -0
  319. package/dist/types/src/stories/Generate.stories.d.ts +27 -0
  320. package/dist/types/src/stories/Generate.stories.d.ts.map +1 -0
  321. package/dist/types/src/stories/Links.stories.d.ts +23 -0
  322. package/dist/types/src/stories/Links.stories.d.ts.map +1 -0
  323. package/dist/types/src/stories/PullRequestArticle.stories.d.ts +359 -0
  324. package/dist/types/src/stories/PullRequestArticle.stories.d.ts.map +1 -0
  325. package/dist/types/src/stories/PullRequestFiles.stories.d.ts +365 -0
  326. package/dist/types/src/stories/PullRequestFiles.stories.d.ts.map +1 -0
  327. package/dist/types/src/stories/PullRequestOverview.stories.d.ts +367 -0
  328. package/dist/types/src/stories/PullRequestOverview.stories.d.ts.map +1 -0
  329. package/dist/types/src/stories/PullRequestsArticle.stories.d.ts +357 -0
  330. package/dist/types/src/stories/PullRequestsArticle.stories.d.ts.map +1 -0
  331. package/dist/types/src/stories/Walkthrough.stories.d.ts +380 -0
  332. package/dist/types/src/stories/Walkthrough.stories.d.ts.map +1 -0
  333. package/dist/types/src/stories/github.d.ts +23 -0
  334. package/dist/types/src/stories/github.d.ts.map +1 -0
  335. package/dist/types/src/stories/github.test.d.ts +2 -0
  336. package/dist/types/src/stories/github.test.d.ts.map +1 -0
  337. package/dist/types/src/stories/walkthrough-fixture.d.ts +8 -0
  338. package/dist/types/src/stories/walkthrough-fixture.d.ts.map +1 -0
  339. package/dist/types/src/testing/fixtures.d.ts +14 -0
  340. package/dist/types/src/testing/fixtures.d.ts.map +1 -0
  341. package/dist/types/src/testing/index.d.ts +4 -0
  342. package/dist/types/src/testing/index.d.ts.map +1 -0
  343. package/dist/types/src/testing/plugin.d.ts +4 -0
  344. package/dist/types/src/testing/plugin.d.ts.map +1 -0
  345. package/dist/types/src/testing/pull-request-13348.d.ts +6 -0
  346. package/dist/types/src/testing/pull-request-13348.d.ts.map +1 -0
  347. package/dist/types/src/translations.d.ts +339 -53
  348. package/dist/types/src/translations.d.ts.map +1 -1
  349. package/dist/types/src/types/GitHubCapabilities.d.ts +13 -0
  350. package/dist/types/src/types/GitHubCapabilities.d.ts.map +1 -0
  351. package/dist/types/src/types/GitHubEvents.d.ts +8 -0
  352. package/dist/types/src/types/GitHubEvents.d.ts.map +1 -0
  353. package/dist/types/src/types/GitHubOperation.d.ts +270 -0
  354. package/dist/types/src/types/GitHubOperation.d.ts.map +1 -0
  355. package/dist/types/src/types/PullRequestsView.d.ts +35 -0
  356. package/dist/types/src/types/PullRequestsView.d.ts.map +1 -0
  357. package/dist/types/src/types/Walkthrough.d.ts +31 -0
  358. package/dist/types/src/types/Walkthrough.d.ts.map +1 -0
  359. package/dist/types/src/types/Walkthrough.test.d.ts +2 -0
  360. package/dist/types/src/types/Walkthrough.test.d.ts.map +1 -0
  361. package/dist/types/src/types/index.d.ts +5 -0
  362. package/dist/types/src/types/index.d.ts.map +1 -0
  363. package/dist/types/src/walkthrough/fill.d.ts +28 -0
  364. package/dist/types/src/walkthrough/fill.d.ts.map +1 -0
  365. package/dist/types/src/walkthrough/fill.test.d.ts +2 -0
  366. package/dist/types/src/walkthrough/fill.test.d.ts.map +1 -0
  367. package/dist/types/src/walkthrough/generate.d.ts +67 -0
  368. package/dist/types/src/walkthrough/generate.d.ts.map +1 -0
  369. package/dist/types/src/walkthrough/generate.test.d.ts +2 -0
  370. package/dist/types/src/walkthrough/generate.test.d.ts.map +1 -0
  371. package/dist/types/src/walkthrough/generated.d.ts +20 -0
  372. package/dist/types/src/walkthrough/generated.d.ts.map +1 -0
  373. package/dist/types/src/walkthrough/generated.test.d.ts +2 -0
  374. package/dist/types/src/walkthrough/generated.test.d.ts.map +1 -0
  375. package/dist/types/src/walkthrough/headings.d.ts +8 -0
  376. package/dist/types/src/walkthrough/headings.d.ts.map +1 -0
  377. package/dist/types/src/walkthrough/headings.test.d.ts +2 -0
  378. package/dist/types/src/walkthrough/headings.test.d.ts.map +1 -0
  379. package/dist/types/src/walkthrough/index.d.ts +9 -0
  380. package/dist/types/src/walkthrough/index.d.ts.map +1 -0
  381. package/dist/types/src/walkthrough/patch.d.ts +48 -0
  382. package/dist/types/src/walkthrough/patch.d.ts.map +1 -0
  383. package/dist/types/src/walkthrough/plan.d.ts +55 -0
  384. package/dist/types/src/walkthrough/plan.d.ts.map +1 -0
  385. package/dist/types/src/walkthrough/plan.test.d.ts +2 -0
  386. package/dist/types/src/walkthrough/plan.test.d.ts.map +1 -0
  387. package/dist/types/src/walkthrough/prompt.d.ts +33 -0
  388. package/dist/types/src/walkthrough/prompt.d.ts.map +1 -0
  389. package/dist/types/src/walkthrough/prompt.test.d.ts +2 -0
  390. package/dist/types/src/walkthrough/prompt.test.d.ts.map +1 -0
  391. package/dist/types/src/walkthrough/score.d.ts +43 -0
  392. package/dist/types/src/walkthrough/score.d.ts.map +1 -0
  393. package/dist/types/src/walkthrough/score.test.d.ts +2 -0
  394. package/dist/types/src/walkthrough/score.test.d.ts.map +1 -0
  395. package/dist/types/tsconfig.tsbuildinfo +1 -0
  396. package/dx.config.ts +39 -0
  397. package/package.json +167 -48
  398. package/src/Extensions.ts +7 -0
  399. package/src/GitHubPlugin.ts +15 -0
  400. package/src/GitHubPlugin.workerd.test.ts +23 -0
  401. package/src/approval.test.ts +40 -0
  402. package/src/approval.ts +39 -0
  403. package/src/capabilities/app-graph-builder.ts +47 -0
  404. package/src/capabilities/connector.ts +95 -0
  405. package/src/capabilities/gen/browser.ts +82 -0
  406. package/src/capabilities/gen/node.ts +43 -0
  407. package/src/capabilities/gen/tauri.ts +82 -0
  408. package/src/capabilities/gen/workerd.ts +43 -0
  409. package/src/capabilities/index.ts +71 -0
  410. package/src/capabilities/link-resolver.ts +137 -0
  411. package/src/capabilities/markdown-extension.ts +146 -0
  412. package/src/capabilities/operation-handler.ts +16 -0
  413. package/src/capabilities/page-action.ts +32 -0
  414. package/src/capabilities/react-surface.ts +98 -0
  415. package/src/capabilities/schema.ts +16 -0
  416. package/src/capabilities/skill-definition.ts +14 -0
  417. package/src/cards/GitHubCard.stories.tsx +165 -0
  418. package/src/cards/GitHubCard.tsx +93 -0
  419. package/src/cards/PullRequestCard.tsx +23 -0
  420. package/src/cards/index.ts +6 -0
  421. package/src/components/CommentComposer/CommentComposer.tsx +109 -0
  422. package/src/components/CommentComposer/index.ts +5 -0
  423. package/src/components/PullRequestFiles/DiffStat.tsx +15 -0
  424. package/src/components/PullRequestFiles/FileTree.tsx +102 -0
  425. package/src/components/PullRequestFiles/PullRequestFiles.tsx +87 -0
  426. package/src/components/PullRequestFiles/files.test.ts +59 -0
  427. package/src/components/PullRequestFiles/files.ts +115 -0
  428. package/src/components/PullRequestFiles/index.ts +8 -0
  429. package/src/components/PullRequestOverview/ArtifactPill.tsx +82 -0
  430. package/src/components/PullRequestOverview/CheckRunList.tsx +120 -0
  431. package/src/components/PullRequestOverview/PullRequestDetails.tsx +15 -0
  432. package/src/components/PullRequestOverview/PullRequestOverview.tsx +110 -0
  433. package/src/components/PullRequestOverview/RelatedCards.tsx +147 -0
  434. package/src/components/PullRequestOverview/index.ts +9 -0
  435. package/src/components/PullRequestStatus/PullRequestStatus.tsx +81 -0
  436. package/src/components/PullRequestStatus/index.ts +5 -0
  437. package/src/components/WalkthroughView/WalkthroughView.tsx +62 -0
  438. package/src/components/WalkthroughView/extensions.ts +53 -0
  439. package/src/components/WalkthroughView/index.ts +6 -0
  440. package/src/components/index.ts +6 -9
  441. package/src/constants.ts +12 -0
  442. package/src/containers/ImportPullRequestDialog/ImportPullRequestDialog.tsx +160 -0
  443. package/src/containers/ImportPullRequestDialog/index.ts +7 -0
  444. package/src/containers/PullRequestArticle/PullRequestArticle.tsx +573 -0
  445. package/src/containers/PullRequestArticle/failure.test.ts +42 -0
  446. package/src/containers/PullRequestArticle/failure.ts +16 -0
  447. package/src/containers/PullRequestArticle/index.ts +5 -0
  448. package/src/containers/PullRequestCardMenu/PullRequestCardMenu.tsx +134 -0
  449. package/src/containers/PullRequestCardMenu/index.ts +5 -0
  450. package/src/containers/PullRequestsArticle/PullRequestsArticle.tsx +361 -0
  451. package/src/containers/PullRequestsArticle/arrange.test.ts +122 -0
  452. package/src/containers/PullRequestsArticle/arrange.ts +245 -0
  453. package/src/containers/PullRequestsArticle/format.ts +31 -0
  454. package/src/containers/PullRequestsArticle/index.ts +5 -0
  455. package/src/containers/WalkthroughArticle/WalkthroughArticle.tsx +31 -0
  456. package/src/containers/WalkthroughArticle/index.ts +5 -0
  457. package/src/containers/index.ts +11 -0
  458. package/src/errors.ts +61 -0
  459. package/src/extensions/index.ts +6 -0
  460. package/src/extensions/links.test.ts +133 -0
  461. package/src/extensions/links.ts +91 -0
  462. package/src/extensions/references.test.ts +83 -0
  463. package/src/extensions/references.ts +100 -0
  464. package/src/github-link.ts +70 -0
  465. package/src/hooks/index.ts +6 -3
  466. package/src/hooks/useOpenObject.ts +48 -0
  467. package/src/hooks/usePullRequestChecks.ts +81 -0
  468. package/src/hooks/usePullRequestDiff.ts +57 -0
  469. package/src/hooks/usePullRequestFiles.ts +98 -0
  470. package/src/hooks/useSyncPullRequest.ts +51 -0
  471. package/src/index.ts +4 -6
  472. package/src/meta.ts +8 -13
  473. package/src/operations/add-pull-request-comment.ts +31 -0
  474. package/src/operations/add-pull-request-review-comment.ts +32 -0
  475. package/src/operations/generate-walkthrough.ts +124 -0
  476. package/src/operations/get-pull-request-diff.ts +37 -0
  477. package/src/operations/get-pull-request-status.ts +44 -0
  478. package/src/operations/get-repositories.ts +53 -0
  479. package/src/operations/import-pull-request-from-snapshot.ts +48 -0
  480. package/src/operations/import-pull-request.test.ts +151 -0
  481. package/src/operations/import-pull-request.ts +141 -0
  482. package/src/operations/index.ts +29 -0
  483. package/src/operations/materialize-target.ts +60 -0
  484. package/src/operations/pull-request.test.ts +120 -0
  485. package/src/operations/pull-request.ts +141 -0
  486. package/src/operations/submit-pull-request-approval.ts +41 -0
  487. package/src/operations/sync-pull-request.ts +37 -0
  488. package/src/operations/sync.test.ts +453 -0
  489. package/src/operations/sync.ts +807 -0
  490. package/src/plugin.test.ts +40 -0
  491. package/src/plugin.ts +37 -0
  492. package/src/pull-request-body.test.ts +61 -0
  493. package/src/pull-request-body.ts +144 -0
  494. package/src/pull-request.test.ts +67 -0
  495. package/src/pull-request.ts +80 -0
  496. package/src/services/github-api.ts +735 -0
  497. package/src/services/index.ts +5 -0
  498. package/src/skills/GitHubSkill.ts +46 -0
  499. package/src/skills/index.ts +5 -0
  500. package/src/stories/Generate.stories.tsx +152 -0
  501. package/src/stories/Links.stories.tsx +153 -0
  502. package/src/stories/PullRequestArticle.stories.tsx +107 -0
  503. package/src/stories/PullRequestFiles.stories.tsx +69 -0
  504. package/src/stories/PullRequestOverview.stories.tsx +113 -0
  505. package/src/stories/PullRequestsArticle.stories.tsx +170 -0
  506. package/src/stories/Walkthrough.stories.tsx +110 -0
  507. package/src/stories/github.test.ts +65 -0
  508. package/src/stories/github.ts +70 -0
  509. package/src/stories/walkthrough-fixture.ts +115 -0
  510. package/src/testing/fixtures.ts +99 -0
  511. package/src/testing/index.ts +7 -0
  512. package/src/testing/plugin.ts +25 -0
  513. package/src/testing/pull-request-13348.ts +104 -0
  514. package/src/testing/pull-request-13363.patch +2981 -0
  515. package/src/translations.ts +135 -63
  516. package/src/types/GitHubCapabilities.ts +27 -0
  517. package/src/types/GitHubEvents.ts +16 -0
  518. package/src/types/GitHubOperation.ts +371 -0
  519. package/src/types/PullRequestsView.ts +45 -0
  520. package/src/types/Walkthrough.test.ts +75 -0
  521. package/src/types/Walkthrough.ts +66 -0
  522. package/src/types/index.ts +8 -0
  523. package/src/vite-env.d.ts +15 -0
  524. package/src/walkthrough/fill.test.ts +337 -0
  525. package/src/walkthrough/fill.ts +246 -0
  526. package/src/walkthrough/generate.test.ts +219 -0
  527. package/src/walkthrough/generate.ts +248 -0
  528. package/src/walkthrough/generated.test.ts +70 -0
  529. package/src/walkthrough/generated.ts +70 -0
  530. package/src/walkthrough/headings.test.ts +40 -0
  531. package/src/walkthrough/headings.ts +39 -0
  532. package/src/walkthrough/index.ts +12 -0
  533. package/src/walkthrough/patch.ts +154 -0
  534. package/src/walkthrough/plan.test.ts +178 -0
  535. package/src/walkthrough/plan.ts +251 -0
  536. package/src/walkthrough/prompt.test.ts +100 -0
  537. package/src/walkthrough/prompt.ts +147 -0
  538. package/src/walkthrough/score.test.ts +272 -0
  539. package/src/walkthrough/score.ts +430 -0
  540. package/README.md +0 -15
  541. package/dist/lib/browser/chunk-S4MPSBA5.mjs +0 -21
  542. package/dist/lib/browser/chunk-S4MPSBA5.mjs.map +0 -7
  543. package/dist/lib/browser/index.css +0 -7
  544. package/dist/lib/browser/index.css.map +0 -7
  545. package/dist/lib/browser/index.mjs +0 -1535
  546. package/dist/lib/browser/index.mjs.map +0 -7
  547. package/dist/lib/browser/meta.json +0 -1
  548. package/dist/lib/browser/meta.mjs +0 -11
  549. package/dist/lib/browser/meta.mjs.map +0 -7
  550. package/dist/types/src/GithubPlugin.d.ts +0 -4
  551. package/dist/types/src/GithubPlugin.d.ts.map +0 -1
  552. package/dist/types/src/components/EmbeddedMain/EmbeddedMain.d.ts +0 -4
  553. package/dist/types/src/components/EmbeddedMain/EmbeddedMain.d.ts.map +0 -1
  554. package/dist/types/src/components/EmbeddedMain/GfmPreview.d.ts +0 -9
  555. package/dist/types/src/components/EmbeddedMain/GfmPreview.d.ts.map +0 -1
  556. package/dist/types/src/components/EmbeddedMain/index.d.ts +0 -2
  557. package/dist/types/src/components/EmbeddedMain/index.d.ts.map +0 -1
  558. package/dist/types/src/components/ExportDialog.d.ts +0 -8
  559. package/dist/types/src/components/ExportDialog.d.ts.map +0 -1
  560. package/dist/types/src/components/GithubApiProviders/GitHubSettings.d.ts +0 -3
  561. package/dist/types/src/components/GithubApiProviders/GitHubSettings.d.ts.map +0 -1
  562. package/dist/types/src/components/GithubApiProviders/OctokitProvider.d.ts +0 -15
  563. package/dist/types/src/components/GithubApiProviders/OctokitProvider.d.ts.map +0 -1
  564. package/dist/types/src/components/GithubApiProviders/index.d.ts +0 -3
  565. package/dist/types/src/components/GithubApiProviders/index.d.ts.map +0 -1
  566. package/dist/types/src/components/GithubEchoResolverProviders/DocumentTreeItem.d.ts +0 -6
  567. package/dist/types/src/components/GithubEchoResolverProviders/DocumentTreeItem.d.ts.map +0 -1
  568. package/dist/types/src/components/GithubEchoResolverProviders/ResolverContext.d.ts +0 -7
  569. package/dist/types/src/components/GithubEchoResolverProviders/ResolverContext.d.ts.map +0 -1
  570. package/dist/types/src/components/GithubEchoResolverProviders/ResolverDialog.d.ts +0 -6
  571. package/dist/types/src/components/GithubEchoResolverProviders/ResolverDialog.d.ts.map +0 -1
  572. package/dist/types/src/components/GithubEchoResolverProviders/ResolverProps.d.ts +0 -16
  573. package/dist/types/src/components/GithubEchoResolverProviders/ResolverProps.d.ts.map +0 -1
  574. package/dist/types/src/components/GithubEchoResolverProviders/ResolverTree.d.ts +0 -3
  575. package/dist/types/src/components/GithubEchoResolverProviders/ResolverTree.d.ts.map +0 -1
  576. package/dist/types/src/components/GithubEchoResolverProviders/SpacePickerTreeItem.d.ts +0 -11
  577. package/dist/types/src/components/GithubEchoResolverProviders/SpacePickerTreeItem.d.ts.map +0 -1
  578. package/dist/types/src/components/GithubEchoResolverProviders/index.d.ts +0 -3
  579. package/dist/types/src/components/GithubEchoResolverProviders/index.d.ts.map +0 -1
  580. package/dist/types/src/components/GithubEchoResolverProviders/spaceResolvers.d.ts +0 -6
  581. package/dist/types/src/components/GithubEchoResolverProviders/spaceResolvers.d.ts.map +0 -1
  582. package/dist/types/src/components/ImportDialog.d.ts +0 -7
  583. package/dist/types/src/components/ImportDialog.d.ts.map +0 -1
  584. package/dist/types/src/components/Issue.d.ts +0 -5
  585. package/dist/types/src/components/Issue.d.ts.map +0 -1
  586. package/dist/types/src/components/Loading/Loading.d.ts +0 -15
  587. package/dist/types/src/components/Loading/Loading.d.ts.map +0 -1
  588. package/dist/types/src/components/Loading/Loading.stories.d.ts +0 -40
  589. package/dist/types/src/components/Loading/Loading.stories.d.ts.map +0 -1
  590. package/dist/types/src/components/Loading/index.d.ts +0 -2
  591. package/dist/types/src/components/Loading/index.d.ts.map +0 -1
  592. package/dist/types/src/components/MarkdownActions.d.ts +0 -7
  593. package/dist/types/src/components/MarkdownActions.d.ts.map +0 -1
  594. package/dist/types/src/components/UrlDialog.d.ts +0 -6
  595. package/dist/types/src/components/UrlDialog.d.ts.map +0 -1
  596. package/dist/types/src/hooks/useDocGhId.d.ts +0 -6
  597. package/dist/types/src/hooks/useDocGhId.d.ts.map +0 -1
  598. package/dist/types/src/hooks/useGhIdFromUrl.d.ts +0 -2
  599. package/dist/types/src/hooks/useGhIdFromUrl.d.ts.map +0 -1
  600. package/dist/types/src/types.d.ts +0 -30
  601. package/dist/types/src/types.d.ts.map +0 -1
  602. package/src/GithubPlugin.tsx +0 -127
  603. package/src/components/EmbeddedMain/EmbeddedMain.tsx +0 -294
  604. package/src/components/EmbeddedMain/GfmPreview.tsx +0 -110
  605. package/src/components/EmbeddedMain/embedded.css +0 -3
  606. package/src/components/EmbeddedMain/index.ts +0 -5
  607. package/src/components/ExportDialog.tsx +0 -157
  608. package/src/components/GithubApiProviders/GitHubSettings.tsx +0 -72
  609. package/src/components/GithubApiProviders/OctokitProvider.tsx +0 -60
  610. package/src/components/GithubApiProviders/index.ts +0 -6
  611. package/src/components/GithubEchoResolverProviders/DocumentTreeItem.tsx +0 -26
  612. package/src/components/GithubEchoResolverProviders/ResolverContext.tsx +0 -137
  613. package/src/components/GithubEchoResolverProviders/ResolverDialog.tsx +0 -34
  614. package/src/components/GithubEchoResolverProviders/ResolverProps.ts +0 -23
  615. package/src/components/GithubEchoResolverProviders/ResolverTree.tsx +0 -86
  616. package/src/components/GithubEchoResolverProviders/SpacePickerTreeItem.tsx +0 -105
  617. package/src/components/GithubEchoResolverProviders/index.ts +0 -6
  618. package/src/components/GithubEchoResolverProviders/spaceResolvers.ts +0 -86
  619. package/src/components/ImportDialog.tsx +0 -72
  620. package/src/components/Issue.tsx +0 -62
  621. package/src/components/Loading/Loading.stories.tsx +0 -32
  622. package/src/components/Loading/Loading.tsx +0 -70
  623. package/src/components/Loading/index.ts +0 -5
  624. package/src/components/MarkdownActions.tsx +0 -136
  625. package/src/components/UrlDialog.tsx +0 -52
  626. package/src/hooks/useDocGhId.ts +0 -38
  627. package/src/hooks/useGhIdFromUrl.ts +0 -26
  628. package/src/types.ts +0 -51
@@ -0,0 +1,807 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+ import * as FetchHttpClient from 'effect/http/FetchHttpClient';
7
+ import * as Layer from 'effect/Layer';
8
+ import * as Semaphore from 'effect/Semaphore';
9
+
10
+ import * as ConnectorSync from '@dxos/app-toolkit/ConnectorSync';
11
+ import * as LayoutOperation from '@dxos/app-toolkit/LayoutOperation';
12
+ import * as Operation from '@dxos/compute/Operation';
13
+ import { Database, Filter, Obj, Query, Ref, Type } from '@dxos/echo';
14
+ import { Cursor } from '@dxos/link';
15
+ import { log } from '@dxos/log';
16
+ import * as Binding from '@dxos/plugin-connector/Binding';
17
+ import { Milestone, Organization, Person, Task, TaskSet } from '@dxos/types';
18
+
19
+ import { meta } from '#meta';
20
+ import { GitHubOperation } from '#types';
21
+
22
+ import { GITHUB_SOURCE } from '../constants.ts';
23
+ import {
24
+ GitHubProjectMissingError,
25
+ GitHubRepoInaccessibleError,
26
+ GitHubRepoUnresolvedError,
27
+ formatGitHubSyncFailure,
28
+ } from '../errors.ts';
29
+ import { GitHubApi } from '../services/index.ts';
30
+
31
+ const { mergeField, snapshotField } = ConnectorSync;
32
+
33
+ //
34
+ // Direction: bidirectional (pull-then-push) for projects and tasks.
35
+ //
36
+ // Each sync pass syncs one binding (one repo). It first pulls every reachable
37
+ // Organization, Person, Project, and Task from GitHub into ECHO, then walks the
38
+ // local mirrors and pushes any field that diverged from the snapshot taken at
39
+ // the end of the previous pull. Snapshots live on the `Cursor` object's
40
+ // `spec.snapshots[<github id>]` (where ids are the GitHub-issued numeric ids,
41
+ // stringified for stable foreign-key matching).
42
+ //
43
+ // Mapped fields per object:
44
+ // Project (repo): description (description-only — see updateRepo for why
45
+ // `name` is intentionally not pushed)
46
+ // Task (issue): title, description, status (open/closed)
47
+ //
48
+ // Conflict policy is remote-wins (mirrors Trello/Linear). Comments are NOT
49
+ // synced (the chunked-yield rewrite is still pending; see TODO below).
50
+ //
51
+ // Attribution caveat: pushed edits are attributed to the GitHub App / OAuth
52
+ // user that owns the connection token, not to the local user who made the
53
+ // edit. This is unavoidable with the current token model — a write from
54
+ // "Person A in space" appears on GitHub as a write from the App. Document
55
+ // before enabling for shared spaces.
56
+ //
57
+ // New local objects (no GitHub foreign key) are NOT pushed in this pass —
58
+ // creating a new GitHub issue requires (owner, repo) which we currently
59
+ // don't surface from local Tasks unambiguously.
60
+ //
61
+ // Sync target shape: each binding's target is the local root Project for one
62
+ // REPOSITORY. The repo's owning org (and org members) are auto-pulled.
63
+ //
64
+
65
+ //
66
+ // Tunables
67
+ //
68
+
69
+ /** Per-repo issue parallelism. */
70
+ const ISSUE_CONCURRENCY = 4;
71
+
72
+ //
73
+ // Snapshot field shapes (stored on Cursor's `spec.snapshots`)
74
+ //
75
+
76
+ /**
77
+ * Snapshot shapes. Fields are required (not `?`) because every snapshot is
78
+ * written in one shot via {@link Cursor.writeSnapshot} after a pull; absence-of-
79
+ * snapshot is modeled by `Cursor.readSnapshot` returning `undefined`. `name` on a
80
+ * Project is recorded but not pushed — see {@link GitHubApi.updateRepo}.
81
+ */
82
+ type ProjectSnapshot = {
83
+ name: string;
84
+ description: string;
85
+ };
86
+
87
+ type TaskSnapshot = {
88
+ title: string;
89
+ description: string;
90
+ status: 'todo' | 'done';
91
+ };
92
+
93
+ //
94
+ // Foreign-key + lookup helpers
95
+ //
96
+
97
+ const fkFor = (id: string | number) => ({ source: GITHUB_SOURCE, id: String(id) });
98
+
99
+ /**
100
+ * Generic foreign-key lookup. Type is forwarded to `Filter.foreignKeys`
101
+ * untyped — the caller supplies the result type via the explicit `T` parameter
102
+ * (mirrors how plugin-trello casts after `Database.query(...).run`).
103
+ */
104
+ const findByForeignId = <T>(type: Type.AnyEntity, id: string | number) =>
105
+ Effect.gen(function* () {
106
+ const results = yield* Database.query(Query.select(Filter.foreignKeys(type as never, [fkFor(id)]))).run;
107
+ return results.length > 0 ? (results[0] as T) : undefined;
108
+ });
109
+
110
+ //
111
+ // Field mappers (GitHub ↔ DXOS)
112
+ //
113
+
114
+ /**
115
+ * Maps GitHub issue state to a Task status. GitHub issues only have `open` /
116
+ * `closed` — there's no "in progress" concept, so we collapse open→`todo` and
117
+ * closed→`done`. PRs use the same mapping; merged PRs are reported as `closed`
118
+ * by GitHub plus a non-null `pull_request.merged_at`.
119
+ */
120
+ const issueStateToTaskStatus = (state: string): 'todo' | 'done' => (state === 'closed' ? 'done' : 'todo');
121
+
122
+ /**
123
+ * Reverse of {@link issueStateToTaskStatus}. `'started'` is collapsed to
124
+ * `open` since GitHub has no equivalent state. Returns undefined when the
125
+ * status is unrecognised so the caller can skip the push instead of failing.
126
+ */
127
+ const taskStatusToIssueState = (status: string | undefined): 'open' | 'closed' | undefined => {
128
+ switch (status) {
129
+ case 'done':
130
+ return 'closed';
131
+ case 'todo':
132
+ case 'started':
133
+ return 'open';
134
+ default:
135
+ return undefined;
136
+ }
137
+ };
138
+
139
+ const sinceFromOptions = (options: GitHubOperation.SyncOptions | undefined): string | undefined => {
140
+ const days = options?.maxDaysBack;
141
+ if (typeof days !== 'number' || days <= 0) {
142
+ return undefined;
143
+ }
144
+ return new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
145
+ };
146
+
147
+ /** GitHub reports `due_on` as an ISO datetime; `Milestone.targetDate` is date-only. */
148
+ const dueOnToTargetDate = (dueOn: string | null | undefined): string | undefined => dueOn?.slice(0, 10) ?? undefined;
149
+
150
+ /**
151
+ * Move a task into `container`. Idempotent — sync runs repeatedly and must never append a second
152
+ * ref for a task already in the set.
153
+ */
154
+ export const setTaskContainer = Effect.fn('setTaskContainer')(function* (task: Task.Task, container: TaskSet.TaskSet) {
155
+ const listed = container.tasks.some(Ref.hasEntityId(task.id));
156
+ // A stale entry for a task now parented elsewhere is not membership; it still needs re-rooting.
157
+ if (listed && Obj.getParent(task)?.id === container.id) {
158
+ return;
159
+ }
160
+ // The reverse-ref index, not `Obj.getParent`: legacy sets may hold refs to tasks whose parent
161
+ // edge has not yet been healed to the set, and every holder must drop its stale entry.
162
+ const previous = yield* Database.query(
163
+ Query.select(Filter.id(task.id)).referencedBy(TaskSet.TaskSet, 'tasks'),
164
+ ).run.pipe(Effect.orElseSucceed(() => []));
165
+ for (const set of previous) {
166
+ if (set.id === container.id) {
167
+ continue;
168
+ }
169
+ Obj.update(set, (set) => {
170
+ TaskSet.removeRefsInPlace(set.tasks, new Set([task.id]));
171
+ });
172
+ }
173
+ // A task filed as a sub-task is listed by its parent rather than a set; it leaves that list too.
174
+ const parent = Task.getParentTask(task);
175
+ if (parent) {
176
+ Obj.update(parent, (parent) => {
177
+ TaskSet.removeRefsInPlace(parent.subtasks ?? [], new Set([task.id]));
178
+ });
179
+ }
180
+ if (!listed) {
181
+ Obj.update(container, (container) => {
182
+ container.tasks.push(Ref.make(task));
183
+ });
184
+ }
185
+ // `tasks` claims only an unparented task, so a move between sets moves the edge itself.
186
+ Obj.setParent(task, container);
187
+ });
188
+
189
+ //
190
+ // Pull (snapshot-driven three-way merge for Project + Task; simple upsert for Org/Person)
191
+ //
192
+
193
+ const upsertOrganization = Effect.fn('upsertOrganization')(function* (org: GitHubApi.GitHubOrg) {
194
+ const existing = yield* findByForeignId<Organization.Organization>(Organization.Organization, org.id);
195
+ if (existing) {
196
+ Obj.update(existing, (existing) => {
197
+ existing.name = org.name ?? org.login;
198
+ if (org.description != null) {
199
+ existing.description = org.description;
200
+ }
201
+ if (org.blog) {
202
+ existing.website = org.blog;
203
+ }
204
+ if (org.avatar_url) {
205
+ existing.image = org.avatar_url;
206
+ }
207
+ });
208
+ return existing;
209
+ }
210
+ const created = Obj.make(Organization.Organization, {
211
+ [Obj.Meta]: { keys: [fkFor(org.id)] },
212
+ name: org.name ?? org.login,
213
+ description: org.description ?? undefined,
214
+ website: org.blog ?? undefined,
215
+ image: org.avatar_url ?? undefined,
216
+ });
217
+ return yield* Database.add(created);
218
+ });
219
+
220
+ const upsertPerson = Effect.fn('upsertPerson')(function* (
221
+ user: GitHubApi.GitHubUser,
222
+ organization: Organization.Organization | undefined,
223
+ ) {
224
+ const existing = yield* findByForeignId<Person.Person>(Person.Person, user.id);
225
+ if (existing) {
226
+ Obj.update(existing, (existing) => {
227
+ existing.fullName = user.name ?? user.login;
228
+ if (user.avatar_url) {
229
+ existing.image = user.avatar_url;
230
+ }
231
+ if (organization && !existing.organization) {
232
+ existing.organization = Ref.make(organization);
233
+ }
234
+ // Add the GitHub login identity if not already present.
235
+ const ids = existing.identities ?? [];
236
+ if (!ids.some((entry: { value: string }) => entry.value === user.login)) {
237
+ (existing.identities ??= []).push({ label: 'github', value: user.login });
238
+ }
239
+ });
240
+ return existing;
241
+ }
242
+ const created = Person.make({
243
+ [Obj.Meta]: { keys: [fkFor(user.id)] },
244
+ fullName: user.name ?? user.login,
245
+ image: user.avatar_url ?? undefined,
246
+ organization: organization ? Ref.make(organization) : undefined,
247
+ identities: [{ label: 'github', value: user.login }],
248
+ });
249
+ return yield* Database.add(created);
250
+ });
251
+
252
+ /**
253
+ * Pull a GitHub repo into ECHO as a Project. Mapped fields go through
254
+ * three-way merge against `binding.snapshots[<repo id>]`; the snapshot
255
+ * is then refreshed to remote-current so the next push pass sees only
256
+ * fields the user has edited locally. `name` is recorded in the snapshot
257
+ * (so we can detect divergence) but is not pushed back — see
258
+ * {@link GitHubApi.updateRepo}.
259
+ */
260
+ const upsertProject = Effect.fn('upsertProject')(function* (
261
+ binding: Cursor.ExternalCursor,
262
+ repo: GitHubApi.GitHubRepo,
263
+ ) {
264
+ const remoteFields: Required<ProjectSnapshot> = {
265
+ name: repo.full_name,
266
+ description: repo.description ?? '',
267
+ };
268
+ const fid = String(repo.id);
269
+ const existing = yield* findByForeignId<TaskSet.TaskSet>(TaskSet.TaskSet, repo.id);
270
+
271
+ if (existing) {
272
+ const snapshot = Cursor.readSnapshot<ProjectSnapshot>(binding, fid);
273
+ const nameResult = mergeField<string | undefined>(
274
+ existing.name,
275
+ remoteFields.name,
276
+ snapshotField(snapshot, 'name'),
277
+ );
278
+ const descriptionResult = mergeField<string | undefined>(
279
+ existing.description,
280
+ remoteFields.description,
281
+ snapshotField(snapshot, 'description'),
282
+ );
283
+ const writeName = nameResult.source === 'remote' && existing.name !== nameResult.value;
284
+ const writeDescription = descriptionResult.source === 'remote' && existing.description !== descriptionResult.value;
285
+ if (writeName || writeDescription) {
286
+ Obj.update(existing, (existing) => {
287
+ if (writeName) {
288
+ existing.name = nameResult.value;
289
+ }
290
+ if (writeDescription) {
291
+ existing.description = descriptionResult.value;
292
+ }
293
+ });
294
+ }
295
+ Cursor.writeSnapshot(binding, fid, remoteFields);
296
+ return existing;
297
+ }
298
+
299
+ const created = TaskSet.make({
300
+ [Obj.Meta]: { keys: [fkFor(repo.id)] },
301
+ name: repo.full_name,
302
+ description: repo.description ?? undefined,
303
+ });
304
+ const persisted = yield* Database.add(created);
305
+ Cursor.writeSnapshot(binding, fid, remoteFields);
306
+ return persisted;
307
+ });
308
+
309
+ /**
310
+ * Pull a GitHub milestone into ECHO. Mirrored remote-wins without a snapshot merge — milestones
311
+ * are pull-only (nothing here pushes milestone edits back), so there is no local side to protect.
312
+ */
313
+ export const upsertMilestone = Effect.fn('upsertMilestone')(function* (
314
+ taskSet: TaskSet.TaskSet,
315
+ remote: GitHubApi.GitHubMilestone,
316
+ ) {
317
+ const name = remote.title;
318
+ const description = remote.description ?? undefined;
319
+ // GitHub's open/closed milestone state is deliberately not mapped: `Milestone` derives progress
320
+ // from its tasks, so a stored status could contradict the work it contains.
321
+ const targetDate = dueOnToTargetDate(remote.due_on);
322
+
323
+ const existing = yield* findByForeignId<Milestone.Milestone>(Milestone.Milestone, remote.id);
324
+ const milestone =
325
+ existing ??
326
+ (yield* Database.add(Milestone.make({ [Obj.Meta]: { keys: [fkFor(remote.id)] }, name, description, targetDate })));
327
+ if (existing) {
328
+ Obj.update(existing, (existing) => {
329
+ existing.name = name;
330
+ existing.description = description;
331
+ existing.targetDate = targetDate;
332
+ });
333
+ }
334
+ // Sequence is the `milestones` array; the parent edge only carries deletion cascade.
335
+ if (!taskSet.milestones.some(Ref.hasEntityId(milestone.id))) {
336
+ Obj.update(taskSet, (taskSet) => {
337
+ taskSet.milestones.push(Ref.make(milestone));
338
+ });
339
+ Obj.setParent(milestone, taskSet);
340
+ }
341
+ return milestone;
342
+ });
343
+
344
+ /**
345
+ * Pull a GitHub issue into ECHO as a Task. Same merge shape as
346
+ * {@link upsertProject}, against snapshot fields {title, description, status}.
347
+ * Non-mapped fields (`Task.priority`, `Task.estimate`, etc.) are preserved.
348
+ *
349
+ * Set membership is NOT wired here: this runs concurrently per issue and `TaskSet.tasks` is a
350
+ * shared array, so the caller applies {@link setTaskContainer} sequentially afterwards.
351
+ *
352
+ * `milestone` is the local mirror of `issue.milestone`, mirrored remote-wins outside the snapshot
353
+ * merge (nothing pushes milestone edits back, so there is no local side to protect).
354
+ */
355
+ export const upsertTask = Effect.fn('upsertTask')(function* (
356
+ binding: Cursor.ExternalCursor,
357
+ issue: GitHubApi.GitHubIssue,
358
+ assignedPerson: Person.Person | undefined,
359
+ milestone: Milestone.Milestone | undefined,
360
+ ) {
361
+ const remoteFields: Required<TaskSnapshot> = {
362
+ title: issue.title,
363
+ description: issue.body ?? '',
364
+ status: issueStateToTaskStatus(issue.state),
365
+ };
366
+ const fid = String(issue.id);
367
+ const existing = yield* findByForeignId<Task.Task>(Task.Task, issue.id);
368
+
369
+ if (existing) {
370
+ const snapshot = Cursor.readSnapshot<TaskSnapshot>(binding, fid);
371
+ // Task.title is required (non-optional) so the merge produces a string.
372
+ const titleResult = mergeField<string>(existing.title, remoteFields.title, snapshotField(snapshot, 'title'));
373
+ const descriptionResult = mergeField<string | undefined>(
374
+ existing.description,
375
+ remoteFields.description,
376
+ snapshotField(snapshot, 'description'),
377
+ );
378
+ // existing.status is the full Task status union; the snapshot only ever holds GitHub's
379
+ // collapsed shape ('todo' | 'done'). Widen the merge type so both sides typecheck and let
380
+ // mergeField compare them as plain values.
381
+ const statusResult = mergeField<Task.Task['status']>(
382
+ existing.status,
383
+ remoteFields.status,
384
+ snapshotField(snapshot, 'status'),
385
+ );
386
+ const writeTitle = titleResult.source === 'remote' && existing.title !== titleResult.value;
387
+ const writeDescription = descriptionResult.source === 'remote' && existing.description !== descriptionResult.value;
388
+ const writeStatus = statusResult.source === 'remote' && existing.status !== statusResult.value;
389
+ Obj.update(existing, (existing) => {
390
+ if (writeTitle) {
391
+ existing.title = titleResult.value;
392
+ }
393
+ if (writeDescription) {
394
+ existing.description = descriptionResult.value;
395
+ }
396
+ if (writeStatus) {
397
+ existing.status = statusResult.value;
398
+ }
399
+ if (assignedPerson && !existing.assignee?.contact) {
400
+ existing.assignee = { contact: Ref.make(assignedPerson) };
401
+ }
402
+ if (milestone) {
403
+ if (existing.milestone?.target?.id !== milestone.id) {
404
+ existing.milestone = Ref.make(milestone);
405
+ }
406
+ // Clear only when GitHub itself reports no milestone; a merely unresolved one would
407
+ // otherwise wipe a perfectly good local assignment.
408
+ } else if (issue.milestone == null && existing.milestone !== undefined) {
409
+ existing.milestone = undefined;
410
+ }
411
+ });
412
+ Cursor.writeSnapshot(binding, fid, remoteFields);
413
+ return { task: existing, created: false };
414
+ }
415
+
416
+ const created = Task.make({
417
+ [Obj.Meta]: { keys: [fkFor(issue.id)] },
418
+ title: issue.title,
419
+ description: issue.body ?? '',
420
+ status: issueStateToTaskStatus(issue.state),
421
+ assignee: assignedPerson ? { contact: Ref.make(assignedPerson) } : undefined,
422
+ milestone: milestone ? Ref.make(milestone) : undefined,
423
+ });
424
+ const persisted = yield* Database.add(created);
425
+ Cursor.writeSnapshot(binding, fid, remoteFields);
426
+ return { task: persisted, created: true };
427
+ });
428
+
429
+ //
430
+ // Push (snapshot diff → GitHub mutations)
431
+ //
432
+
433
+ export type GitHubPushResult = {
434
+ projects: number;
435
+ tasks: number;
436
+ };
437
+
438
+ /**
439
+ * Push reconciler for one repo. Walks the locally-mirrored Project for the
440
+ * given repo plus every Task whose foreign key matches one of the issues we
441
+ * just pulled, diffs against the binding's snapshot, and PATCHes any diverged
442
+ * field back to GitHub.
443
+ *
444
+ * Behaviour:
445
+ * - Project description divergence → PATCH /repos/{owner}/{repo}.
446
+ * `name` divergence is logged but NOT pushed (rename is destructive and
447
+ * almost never desired from a sync mirror).
448
+ * - Task title / description / status divergence → PATCH the issue.
449
+ * - Tombstones (`Obj.isDeleted`) are skipped.
450
+ *
451
+ * After a successful push the snapshot is refreshed with the values just
452
+ * sent so the next pass sees no divergence even before the next pull.
453
+ *
454
+ * The push callbacks' error type is generic. The reconciler doesn't inspect
455
+ * or recover from those errors — it propagates so the outer `Effect.result`
456
+ * at the call site can record the binding's `lastError`. Generic-`E` keeps
457
+ * this module decoupled from the HTTP error hierarchy of `GitHubApi`.
458
+ */
459
+ export const pushRepoUpdates: <E, R>(
460
+ binding: Cursor.ExternalCursor,
461
+ repo: GitHubApi.GitHubRepo,
462
+ remoteIssuesById: ReadonlyMap<string, GitHubApi.GitHubIssue>,
463
+ push: {
464
+ /** Wraps `GitHubApi.updateIssue`. */
465
+ updateIssue: (
466
+ owner: string,
467
+ repo: string,
468
+ issueNumber: number,
469
+ input: GitHubApi.IssueUpdateInput,
470
+ ) => Effect.Effect<void, E, R>;
471
+ /** Wraps `GitHubApi.updateRepo`. */
472
+ updateRepo: (owner: string, repo: string, input: GitHubApi.RepoUpdateInput) => Effect.Effect<void, E, R>;
473
+ },
474
+ ) => Effect.Effect<GitHubPushResult, E, Database.Service | R> = Effect.fn('pushRepoUpdates')(
475
+ function* (binding, repo, remoteIssuesById, push) {
476
+ let projects = 0;
477
+ let tasks = 0;
478
+
479
+ // Project (repo) push — description only.
480
+ {
481
+ const fid = String(repo.id);
482
+ const local = yield* findByForeignId<TaskSet.TaskSet>(TaskSet.TaskSet, repo.id);
483
+ if (local && !Obj.isDeleted(local)) {
484
+ const snapshot = Cursor.readSnapshot<ProjectSnapshot>(binding, fid);
485
+ if (snapshot) {
486
+ const localName = local.name ?? '';
487
+ const localDescription = local.description ?? '';
488
+ const input: GitHubApi.RepoUpdateInput = {};
489
+ let diverged = false;
490
+ if (snapshot.description !== undefined && snapshot.description !== localDescription) {
491
+ input.description = localDescription;
492
+ diverged = true;
493
+ }
494
+ if (snapshot.name !== undefined && snapshot.name !== localName) {
495
+ // We deliberately don't push repo renames — see updateRepo above.
496
+ // Log instead so the user can chase down via the audit channel.
497
+ log.warn('github push: local repo name diverges from remote; rename push is intentionally disabled', {
498
+ repoId: fid,
499
+ localName,
500
+ snapshotName: snapshot.name,
501
+ });
502
+ }
503
+ if (diverged) {
504
+ yield* push.updateRepo(repo.owner.login, repo.name, input);
505
+ Cursor.writeSnapshot(binding, fid, {
506
+ ...snapshot,
507
+ description: localDescription,
508
+ });
509
+ projects++;
510
+ }
511
+ }
512
+ }
513
+ }
514
+
515
+ // Task (issue) push — title / body / state.
516
+ for (const [id, remoteIssue] of remoteIssuesById) {
517
+ const local = yield* findByForeignId<Task.Task>(Task.Task, id);
518
+ if (!local || Obj.isDeleted(local)) {
519
+ continue;
520
+ }
521
+ const snapshot = Cursor.readSnapshot<TaskSnapshot>(binding, id);
522
+ if (!snapshot) {
523
+ continue;
524
+ }
525
+ const localTitle = local.title ?? '';
526
+ const localDescription = local.description ?? '';
527
+ const localStatus = local.status;
528
+ const desiredStatusForSnapshot: 'todo' | 'done' | undefined =
529
+ localStatus === 'done' ? 'done' : localStatus === 'todo' || localStatus === 'started' ? 'todo' : undefined;
530
+
531
+ const input: GitHubApi.IssueUpdateInput = {};
532
+ let diverged = false;
533
+ if (snapshot.title !== undefined && snapshot.title !== localTitle) {
534
+ input.title = localTitle;
535
+ diverged = true;
536
+ }
537
+ if (snapshot.description !== undefined && snapshot.description !== localDescription) {
538
+ input.body = localDescription;
539
+ diverged = true;
540
+ }
541
+ // Status push is pull-only for PRs. GitHub uses the same /issues/{n}
542
+ // endpoint for issues and PRs, but `state: 'closed'` on a PR rejects
543
+ // it (the closed-without-merge red badge), and merging requires a
544
+ // separate `PUT /pulls/{n}/merge` endpoint we don't support. The safe
545
+ // default is to never push a status change for a PR — log if local
546
+ // and snapshot diverge so the divergence is auditable.
547
+ const isPullRequest = remoteIssue.pull_request != null;
548
+ if (
549
+ !isPullRequest &&
550
+ snapshot.status !== undefined &&
551
+ desiredStatusForSnapshot !== undefined &&
552
+ snapshot.status !== desiredStatusForSnapshot
553
+ ) {
554
+ const issueState = taskStatusToIssueState(localStatus);
555
+ if (issueState) {
556
+ input.state = issueState;
557
+ diverged = true;
558
+ }
559
+ } else if (
560
+ isPullRequest &&
561
+ snapshot.status !== undefined &&
562
+ desiredStatusForSnapshot !== undefined &&
563
+ snapshot.status !== desiredStatusForSnapshot
564
+ ) {
565
+ log.warn('github push: PR status diverged locally; pull-only for PRs (status will not be pushed)', {
566
+ issueId: id,
567
+ number: remoteIssue.number,
568
+ localStatus,
569
+ snapshotStatus: snapshot.status,
570
+ });
571
+ }
572
+ if (!diverged) {
573
+ continue;
574
+ }
575
+
576
+ yield* push.updateIssue(repo.owner.login, repo.name, remoteIssue.number, input);
577
+ // Refresh the snapshot: title / description / status sent. Status only
578
+ // gets refreshed when we actually pushed a state change (issues only) —
579
+ // for PRs we keep the snapshot's previous status, so the warning above
580
+ // keeps firing until either the user reverts locally or the PR's state
581
+ // changes on GitHub and a pull catches up.
582
+ Cursor.writeSnapshot(binding, id, {
583
+ ...snapshot,
584
+ title: localTitle,
585
+ description: localDescription,
586
+ status: input.state !== undefined ? (desiredStatusForSnapshot ?? snapshot.status) : snapshot.status,
587
+ });
588
+ tasks++;
589
+ }
590
+
591
+ return { projects, tasks };
592
+ },
593
+ );
594
+
595
+ //
596
+ // Main handler
597
+ //
598
+
599
+ /**
600
+ * Per-binding sync body run by the account-level fan-out: reconcile the one repo the
601
+ * binding targets, stamping success/failure onto the binding and toasting either way.
602
+ */
603
+ const syncRepoBinding = Effect.fn('syncRepoBinding')(function* (binding: Cursor.ExternalCursor) {
604
+ const project = yield* Database.load(binding.spec.target);
605
+ const db = Obj.getDatabase(binding) ?? Obj.getDatabase(project);
606
+ if (!db) {
607
+ return yield* Effect.fail(new ConnectorSync.DatabaseMissingError());
608
+ }
609
+
610
+ // The repo's foreign id: prefer the binding's `externalId`, falling back
611
+ // to the GitHub foreign key on the target object.
612
+ const externalId =
613
+ binding.spec.externalId ?? Obj.getMeta(project).keys.find((key) => key.source === GITHUB_SOURCE)?.id;
614
+
615
+ const outcome = yield* Effect.result(
616
+ Effect.gen(function* () {
617
+ if (externalId === undefined) {
618
+ return yield* Effect.fail(new GitHubRepoUnresolvedError());
619
+ }
620
+
621
+ // Fetch all repos visible to the token once so the binding can
622
+ // resolve its remote `GitHubRepo`. The binding stores the numeric
623
+ // repo id as `externalId` (a stringified integer).
624
+ // TODO(wittjosiah): Switch to `fetchRepo(owner, name)` once the
625
+ // binding carries `owner`/`name`. `fetchUserRepos()` walks every
626
+ // repo the token can see and dominates large-account syncs.
627
+ const allRepos = yield* GitHubApi.fetchUserRepos();
628
+ const remoteRepo = allRepos.find((repo) => String(repo.id) === externalId);
629
+ if (!remoteRepo) {
630
+ return yield* Effect.fail(new GitHubRepoInaccessibleError());
631
+ }
632
+
633
+ const options = (binding.spec.options ?? undefined) as GitHubOperation.SyncOptions | undefined;
634
+
635
+ // Upsert the local Project for this repo (three-way merge).
636
+ yield* upsertProject(binding, remoteRepo);
637
+
638
+ // Cross-issue dedup for Person upserts: parallel per-issue work can
639
+ // race two upserts for the same login (same assignee on multiple
640
+ // issues, or org members). Without serialization both fibers find no
641
+ // foreign-key match, both insert, leaving duplicate Person rows. The
642
+ // semaphore + cache pair gives us a single in-flight upsert per login;
643
+ // subsequent callers read from `personByLogin`.
644
+ const personByLogin = new Map<string, Person.Person>();
645
+ const personSemaphore = yield* Semaphore.make(1);
646
+ const ensurePerson = (user: GitHubApi.GitHubUser, organization: Organization.Organization | undefined) =>
647
+ personSemaphore.withPermits(1)(
648
+ Effect.gen(function* () {
649
+ const cached = personByLogin.get(user.login);
650
+ if (cached) {
651
+ return cached;
652
+ }
653
+ const person = yield* upsertPerson(user, organization);
654
+ personByLogin.set(user.login, person);
655
+ return person;
656
+ }),
657
+ );
658
+
659
+ // Auto-pull the owning org + members for this repo's owner.
660
+ // `/orgs/{owner}` returns 404 for user-owned repos (personal
661
+ // accounts are not orgs). Treat that as "no org to sync" and
662
+ // continue — the Project just won't have a parent organization.
663
+ let pulledOrganizations = 0;
664
+ const owner = remoteRepo.owner.login;
665
+ const orgResult = yield* Effect.result(GitHubApi.fetchOrg(owner));
666
+ if (orgResult._tag === 'Success') {
667
+ const organization = yield* upsertOrganization(orgResult.success);
668
+ pulledOrganizations++;
669
+ const members = yield* GitHubApi.fetchOrgMembers(owner);
670
+ for (const member of members) {
671
+ yield* ensurePerson(member, organization);
672
+ }
673
+ } else {
674
+ log.info('github sync: owner is not an org, skipping org/member pull', { owner });
675
+ }
676
+
677
+ // Re-resolve the local Project after upsert so issue upserts and
678
+ // pushes operate on the persisted record.
679
+ const localProject = yield* findByForeignId<TaskSet.TaskSet>(TaskSet.TaskSet, remoteRepo.id);
680
+ if (!localProject) {
681
+ return yield* Effect.fail(new GitHubProjectMissingError());
682
+ }
683
+
684
+ // Milestones before issues: an issue's `milestone` has to resolve to a local mirror, and the
685
+ // repo endpoint also surfaces milestones no open issue points at yet.
686
+ const milestoneByRemoteId = new Map<number, Milestone.Milestone>();
687
+ const remoteMilestones = yield* GitHubApi.fetchRepoMilestones(remoteRepo.owner.login, remoteRepo.name);
688
+ for (const remoteMilestone of remoteMilestones) {
689
+ milestoneByRemoteId.set(remoteMilestone.id, yield* upsertMilestone(localProject, remoteMilestone));
690
+ }
691
+
692
+ let pulledTasks = 0;
693
+ const since = sinceFromOptions(options);
694
+ const issues = yield* GitHubApi.fetchRepoIssues(remoteRepo.owner.login, remoteRepo.name, { since });
695
+ const remoteIssuesById = new Map<string, GitHubApi.GitHubIssue>();
696
+ const pulled = yield* Effect.forEach(
697
+ issues,
698
+ (issue) =>
699
+ Effect.gen(function* () {
700
+ // Resolve assignee — first assignee only for v1. External
701
+ // (non-org) assignees are upserted lazily; the semaphored
702
+ // `ensurePerson` dedups across fibers.
703
+ const assigneeUser = issue.assignees?.[0];
704
+ const assignedPerson = assigneeUser ? yield* ensurePerson(assigneeUser, undefined) : undefined;
705
+ const milestone = issue.milestone ? milestoneByRemoteId.get(issue.milestone.id) : undefined;
706
+
707
+ const { task, created } = yield* upsertTask(binding, issue, assignedPerson, milestone);
708
+ remoteIssuesById.set(String(issue.id), issue);
709
+ if (created) {
710
+ pulledTasks++;
711
+ }
712
+
713
+ // TODO(wittjosiah): Comments sync disabled — re-enable once
714
+ // chunked/yielded to avoid automerge_wasm crash when upserting
715
+ // many comments in one tick.
716
+ return task;
717
+ }),
718
+ { concurrency: ISSUE_CONCURRENCY },
719
+ );
720
+
721
+ // Sequential: `TaskSet.tasks` is one shared array, so concurrent appends would drop entries.
722
+ // `Effect.forEach` preserves input order, so the set's order mirrors GitHub's.
723
+ for (const task of pulled) {
724
+ yield* setTaskContainer(task, localProject);
725
+ }
726
+
727
+ // Push: snapshot-diff the local Project + Tasks for this repo and
728
+ // PATCH only the diverged fields.
729
+ //
730
+ // TODO(wittjosiah): `remoteIssuesById` is built from
731
+ // `fetchRepoIssues(..., { since })`, so when `maxDaysBack` is set
732
+ // this push pass cannot see locally-edited tasks whose remote issue
733
+ // is older than the window. Switch the candidate set to the local
734
+ // mirror (every Task with a GITHUB_SOURCE fk under this repo) once
735
+ // we store `number` per task so we don't need the remote payload to PATCH.
736
+ const pushResult = yield* pushRepoUpdates(binding, remoteRepo, remoteIssuesById, {
737
+ updateIssue: (owner, repoName, issueNumber, input) =>
738
+ GitHubApi.updateIssue(owner, repoName, issueNumber, input).pipe(Effect.map(() => undefined)),
739
+ updateRepo: (owner, repoName, input) =>
740
+ GitHubApi.updateRepo(owner, repoName, input).pipe(Effect.map(() => undefined)),
741
+ });
742
+
743
+ return {
744
+ pulled: {
745
+ organizations: pulledOrganizations,
746
+ people: personByLogin.size,
747
+ projects: 1,
748
+ tasks: pulledTasks,
749
+ comments: 0,
750
+ },
751
+ pushed: pushResult,
752
+ };
753
+ }).pipe(Effect.provide(Layer.provideMerge(Database.layer(db), GitHubApi.fromAccessToken(binding.spec.source)))),
754
+ );
755
+
756
+ // Write sync state onto the binding.
757
+ if (outcome._tag === 'Success') {
758
+ Cursor.advance(binding);
759
+ yield* Effect.ignore(
760
+ Operation.invoke(LayoutOperation.AddToast, {
761
+ id: `${meta.profile.key}.sync-success`,
762
+ icon: 'ph--check--regular',
763
+ title: ['sync-toast.success.label', { ns: meta.profile.key }],
764
+ }),
765
+ );
766
+ return { pulled: outcome.success.pulled };
767
+ } else {
768
+ const message = formatGitHubSyncFailure(outcome.failure);
769
+ Cursor.recordError(binding, message);
770
+ log.warn('github sync: binding failed', { error: outcome.failure });
771
+ yield* Effect.ignore(
772
+ Operation.invoke(LayoutOperation.AddToast, {
773
+ id: `${meta.profile.key}.sync-error`,
774
+ icon: 'ph--warning--regular',
775
+ title: ['sync-toast.error.label', { ns: meta.profile.key }],
776
+ description: message,
777
+ }),
778
+ );
779
+ return yield* Effect.fail(outcome.failure);
780
+ }
781
+ }, Effect.provide(FetchHttpClient.layer));
782
+
783
+ const handler: Operation.WithHandler<typeof GitHubOperation.SyncGitHubRepositories> =
784
+ GitHubOperation.SyncGitHubRepositories.pipe(
785
+ Operation.withHandler(({ connection, priority }) =>
786
+ Binding.syncAll({
787
+ connection,
788
+ priority,
789
+ sync: (binding) => syncRepoBinding(binding),
790
+ }).pipe(
791
+ Effect.map(({ outputs }) => ({
792
+ pulled: outputs.reduce(
793
+ (total, { pulled }) => ({
794
+ organizations: total.organizations + pulled.organizations,
795
+ people: total.people + pulled.people,
796
+ projects: total.projects + pulled.projects,
797
+ tasks: total.tasks + pulled.tasks,
798
+ comments: total.comments + pulled.comments,
799
+ }),
800
+ { organizations: 0, people: 0, projects: 0, tasks: 0, comments: 0 },
801
+ ),
802
+ })),
803
+ ),
804
+ ),
805
+ );
806
+
807
+ export default handler;