@preventive/triage 1.0.0-alpha.5 → 1.0.0-alpha.51

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 (368) hide show
  1. package/LICENSE-APACHE +202 -0
  2. package/README.md +16 -0
  3. package/api/reap.ts +17 -0
  4. package/cli.js +6 -0
  5. package/client/dependency-paths.d.ts +3 -0
  6. package/client/dependency-paths.js +36 -0
  7. package/client/finding-link.js +305 -0
  8. package/client/linked-findings.d.ts +1 -0
  9. package/client/linked-findings.js +115 -0
  10. package/common/bundle-commit.d.ts +1 -0
  11. package/common/bundle-commit.js +4 -0
  12. package/common/bundle-compare-diff.d.ts +3 -0
  13. package/common/bundle-compare-diff.js +357 -0
  14. package/common/bundle-compare-inputs.d.ts +2 -0
  15. package/common/bundle-compare-inputs.js +65 -0
  16. package/common/bundle-compare-versions.js +172 -0
  17. package/common/bundle-comparison.d.ts +8 -0
  18. package/common/bundle-comparison.js +62 -0
  19. package/common/bundle-metadata.d.ts +15 -0
  20. package/common/bundle-metadata.js +226 -0
  21. package/common/bundle-pkg-of.d.ts +1 -0
  22. package/common/bundle-pkg-of.js +90 -0
  23. package/common/bundle-reasons.d.ts +2 -0
  24. package/common/bundle-reasons.js +21 -0
  25. package/common/bundle-source-links.d.ts +6 -0
  26. package/common/bundle-source-links.js +216 -0
  27. package/common/bundle-sourcemap.d.ts +10 -0
  28. package/common/bundle-sourcemap.js +100 -0
  29. package/common/bundle-sources.d.ts +7 -0
  30. package/common/bundle-sources.js +333 -0
  31. package/common/bundle-stats.js +41 -0
  32. package/common/bundle-tabs.js +1 -0
  33. package/common/code-language.js +65 -0
  34. package/common/default-scan-models.ts +31 -0
  35. package/common/dependency-reviews.d.ts +18 -0
  36. package/common/dependency-reviews.js +64 -0
  37. package/common/finding-duplicates.js +58 -0
  38. package/common/finding-filters.js +274 -0
  39. package/common/finding-id.js +47 -0
  40. package/common/github-issue-labels.d.ts +1 -0
  41. package/common/github-issue-labels.js +11 -0
  42. package/common/github-pr.ts +56 -0
  43. package/common/gzip.js +59 -0
  44. package/common/line-diff.d.ts +8 -0
  45. package/common/line-diff.js +57 -0
  46. package/common/linked-workspace-groups.js +63 -0
  47. package/common/managed/agent-tasks.ts +292 -0
  48. package/common/managed/comments.ts +34 -0
  49. package/common/managed/finding-detail.d.ts +3 -0
  50. package/common/managed/finding-detail.js +66 -0
  51. package/common/managed/github-agents.ts +114 -0
  52. package/common/managed/github-logins.ts +49 -0
  53. package/common/managed/npm-packages.d.ts +7 -0
  54. package/common/managed/npm-packages.js +51 -0
  55. package/common/managed/permissions.ts +31 -0
  56. package/common/managed/report-content.ts +72 -0
  57. package/common/managed/report-filter.ts +109 -0
  58. package/common/managed/repository-alias.ts +65 -0
  59. package/common/managed/roles.ts +35 -0
  60. package/common/managed/routes.d.ts +2 -0
  61. package/common/managed/routes.js +359 -0
  62. package/common/managed/scan-models.ts +6 -0
  63. package/common/managed/team-app.d.ts +6 -0
  64. package/common/managed/team-app.js +47 -0
  65. package/common/managed/triage.ts +83 -0
  66. package/common/managed/upload-seal.ts +126 -0
  67. package/common/pretty-print.d.ts +5 -0
  68. package/common/pretty-print.js +24 -0
  69. package/common/revalidation-conflicts.js +48 -0
  70. package/common/revalidation-input-groups.js +56 -0
  71. package/common/save-error-reason.ts +20 -7
  72. package/common/scan-server.ts +13 -0
  73. package/common/security-links.d.ts +5 -0
  74. package/common/security-links.js +33 -0
  75. package/common/server-info.ts +46 -0
  76. package/common/utf8.d.ts +3 -0
  77. package/common/utf8.js +45 -0
  78. package/common/workspace-app.js +39 -0
  79. package/common/workspace-groups.js +176 -0
  80. package/out/activity.js +4 -0
  81. package/out/brotli-fallback.js +3 -3
  82. package/out/chunk-2K6UN3PC.js +3 -0
  83. package/out/chunk-2XUEZHIP.js +28 -0
  84. package/out/chunk-2ZDZWLFY.js +1 -0
  85. package/out/chunk-4HI2MSAV.js +17 -0
  86. package/out/chunk-6LNQTKAV.js +52 -0
  87. package/out/chunk-7BKKYVWR.js +7 -0
  88. package/out/chunk-7C7DWL7I.js +1 -0
  89. package/out/chunk-7JBSSRD7.js +3 -0
  90. package/out/chunk-7VAWINQF.js +6 -0
  91. package/out/chunk-C4Z6ZBXV.js +10 -0
  92. package/out/chunk-FMXWXDR4.js +36 -0
  93. package/out/chunk-GDPCLSOS.js +2 -0
  94. package/out/chunk-GO7FT4OD.js +1 -0
  95. package/out/chunk-HT2SSA3V.js +1 -0
  96. package/out/chunk-IPAH4LKF.js +232 -0
  97. package/out/chunk-IYPY47MC.js +1 -0
  98. package/out/chunk-IZ7POWI3.js +18 -0
  99. package/out/chunk-JCIIBLKR.js +1 -0
  100. package/out/chunk-KOGNQDSJ.js +1 -0
  101. package/out/chunk-NRPC74AM.js +1 -0
  102. package/out/chunk-OOJM4CTU.js +1 -0
  103. package/out/chunk-PBZPYIG7.js +1 -0
  104. package/out/chunk-PNHBZGDD.js +1 -0
  105. package/out/chunk-PWFUOLZK.js +17 -0
  106. package/out/chunk-PXK7EU4V.js +1 -0
  107. package/out/chunk-QB4CBOX2.js +1 -0
  108. package/out/chunk-R3NZFXVR.js +1 -0
  109. package/out/chunk-R5O6FAW4.js +2 -0
  110. package/out/chunk-SXV2T5C4.js +80 -0
  111. package/out/chunk-TQJDYHVE.js +2 -0
  112. package/out/chunk-TUII34K2.js +1 -0
  113. package/out/chunk-VDRXHIHN.js +3 -0
  114. package/out/chunk-VFWMQ3LG.js +2 -0
  115. package/out/chunk-VH4XMZIW.js +2 -0
  116. package/out/chunk-WCPJDAYA.js +10 -0
  117. package/out/chunk-WFYLMF6Z.js +10 -0
  118. package/out/chunk-WRAJVD35.js +1 -0
  119. package/out/chunk-WYLQWYLA.js +1 -0
  120. package/out/client-managed-import.js +4 -0
  121. package/out/client-managed.js +33 -0
  122. package/out/client-sync.js +11 -17
  123. package/out/compare.js +35 -0
  124. package/out/dialogs.js +55 -0
  125. package/out/github-issue-authorized.html +3 -0
  126. package/out/github-issue-authorized.js +1 -0
  127. package/out/graph.js +17 -4
  128. package/out/index.html +90 -8
  129. package/out/prism.js +14 -10
  130. package/out/scan.js +2 -0
  131. package/out/stasis.svg +20 -0
  132. package/out/terminal.js +366 -39
  133. package/out/view.css +1 -1
  134. package/out/view.js +19 -144
  135. package/package.json +364 -59
  136. package/risk/actions/findings.js +161 -0
  137. package/risk/actions/resolve.js +219 -0
  138. package/risk/actions/scripts/commands.js +182 -0
  139. package/risk/actions/scripts/grade.js +113 -0
  140. package/risk/actions/scripts/packages.js +123 -0
  141. package/risk/actions/scripts/shell.js +169 -0
  142. package/risk/actions/uses.js +76 -0
  143. package/risk/actions/workflow.js +110 -0
  144. package/risk/files/characters.js +196 -0
  145. package/risk/files/hosts.js +205 -0
  146. package/risk/files/readability.js +177 -0
  147. package/risk/files/runs-code.js +615 -0
  148. package/risk/files/script.js +118 -0
  149. package/risk/files/stamps.js +47 -0
  150. package/risk/index.d.ts +254 -0
  151. package/risk/index.js +11 -0
  152. package/risk/npm/assess.js +285 -0
  153. package/risk/npm/combined.js +68 -0
  154. package/risk/npm/compare.js +257 -0
  155. package/risk/npm/dependencies.js +172 -0
  156. package/risk/npm/diff.js +79 -0
  157. package/risk/npm/downloads.js +84 -0
  158. package/risk/npm/files.js +445 -0
  159. package/risk/npm/held.js +63 -0
  160. package/risk/npm/install-scripts.js +34 -0
  161. package/risk/npm/malware.js +36 -0
  162. package/risk/npm/manifest.js +65 -0
  163. package/risk/npm/new-dependency.js +60 -0
  164. package/risk/npm/old-version.js +108 -0
  165. package/risk/npm/registry.js +388 -0
  166. package/risk/npm/release.js +85 -0
  167. package/risk/npm/score.js +59 -0
  168. package/risk/npm/specs.js +124 -0
  169. package/risk/npm/tarball.js +96 -0
  170. package/server-common/database-config.ts +16 -0
  171. package/server-common/initialize.ts +18 -0
  172. package/server-common/npm-advisories.ts +101 -0
  173. package/{server → server-common}/origin.ts +5 -5
  174. package/server-common/reap.ts +48 -0
  175. package/server-common/scan-config.ts +19 -0
  176. package/server-common/standalone.ts +29 -0
  177. package/server-common/storage-crypto.ts +182 -0
  178. package/server-common/storage-log.ts +38 -0
  179. package/server-common/vercel-blob.ts +103 -0
  180. package/server-e2e/app.ts +487 -0
  181. package/{server → server-e2e}/bus-receiver.ts +2 -1
  182. package/{server → server-e2e}/cli.js +9 -4
  183. package/{server → server-e2e}/config.ts +50 -42
  184. package/{server → server-e2e}/db-neon.ts +2 -2
  185. package/{server → server-e2e}/db-revision-sql.ts +1 -1
  186. package/{server → server-e2e}/db-stmt.ts +2 -2
  187. package/{server → server-e2e}/db.ts +7 -7
  188. package/{server → server-e2e}/http.ts +44 -13
  189. package/{server → server-e2e}/hub.ts +40 -9
  190. package/server-e2e/index.ts +17 -0
  191. package/server-e2e/lifecycle.ts +95 -0
  192. package/{server → server-e2e}/neon-driver.ts +2 -2
  193. package/{server → server-e2e}/npm-proxy.ts +12 -145
  194. package/{server → server-e2e}/objstore/blob-vercel.ts +10 -114
  195. package/{server → server-e2e}/objstore/blob.ts +1 -1
  196. package/server-e2e/objstore/fetch-mint-guard.ts +74 -0
  197. package/{server → server-e2e}/objstore/handlers.ts +9 -8
  198. package/{server → server-e2e}/objstore/init.ts +19 -27
  199. package/{server → server-e2e}/objstore/reaper.ts +1 -1
  200. package/server-e2e/objstore/rest-deny.ts +28 -0
  201. package/server-e2e/objstore/rest-mint.ts +224 -0
  202. package/{server → server-e2e}/objstore/rest.ts +3 -3
  203. package/{server → server-e2e}/objstore/store-neon.ts +6 -10
  204. package/{server → server-e2e}/objstore/store.ts +7 -7
  205. package/{server → server-e2e}/objstore/tokens.ts +2 -2
  206. package/{server → server-e2e}/pubsub.ts +12 -9
  207. package/{server → server-e2e}/sign.ts +4 -4
  208. package/{server → server-e2e}/sse-server.ts +60 -33
  209. package/{server → server-e2e}/static.ts +31 -21
  210. package/{server → server-e2e}/sync-handlers.ts +64 -30
  211. package/{server → server-e2e}/ws-server.ts +20 -9
  212. package/server-managed/activity.ts +258 -0
  213. package/server-managed/agent-merges.ts +109 -0
  214. package/server-managed/app-agents.ts +23 -0
  215. package/server-managed/avatar-store.ts +13 -0
  216. package/server-managed/blob-store.ts +17 -0
  217. package/server-managed/brotli.ts +10 -0
  218. package/server-managed/bundle-advisories.ts +120 -0
  219. package/server-managed/bundle-advisory-inventory.ts +77 -0
  220. package/server-managed/bundle-build-diagnostics.d.ts +10 -0
  221. package/server-managed/bundle-build-diagnostics.js +23 -0
  222. package/server-managed/bundle-build-leases.ts +41 -0
  223. package/server-managed/bundle-build-worker.js +115 -0
  224. package/server-managed/bundle-build.ts +200 -0
  225. package/server-managed/bundle-cache.ts +211 -0
  226. package/server-managed/bundle-catalog.ts +29 -0
  227. package/server-managed/bundle-commits.ts +140 -0
  228. package/server-managed/bundle-store.ts +24 -0
  229. package/server-managed/bundle-summary-cache.ts +130 -0
  230. package/server-managed/bundle.ts +119 -0
  231. package/server-managed/cache-storage.ts +20 -0
  232. package/server-managed/cli.js +21 -0
  233. package/server-managed/combined.ts +47 -0
  234. package/server-managed/comments.ts +151 -0
  235. package/server-managed/commit-coauthors.ts +106 -0
  236. package/server-managed/commit-lockfiles.ts +76 -0
  237. package/server-managed/comparison.ts +52 -0
  238. package/server-managed/config.ts +164 -0
  239. package/server-managed/content-access.ts +15 -0
  240. package/server-managed/crypto.ts +25 -0
  241. package/server-managed/db-methods.ts +1923 -0
  242. package/server-managed/db-neon.ts +468 -0
  243. package/server-managed/db-schema.ts +238 -0
  244. package/server-managed/db-table-names.ts +22 -0
  245. package/server-managed/db.ts +143 -0
  246. package/server-managed/dependabot-access.ts +60 -0
  247. package/server-managed/dependabot-alerts.ts +185 -0
  248. package/server-managed/dependency-review-comments.ts +98 -0
  249. package/server-managed/dependency-reviews.ts +364 -0
  250. package/server-managed/github-accounts.ts +60 -0
  251. package/server-managed/github-actions.ts +71 -0
  252. package/server-managed/github-app.ts +376 -0
  253. package/server-managed/github-errors.ts +70 -0
  254. package/server-managed/github-graphql.ts +39 -0
  255. package/server-managed/github-issue-links.ts +140 -0
  256. package/server-managed/github-issue-oauth.ts +47 -0
  257. package/server-managed/github-issues.ts +152 -0
  258. package/server-managed/github-link-oauth.ts +69 -0
  259. package/server-managed/github-links.ts +204 -0
  260. package/server-managed/github-metadata.ts +213 -0
  261. package/server-managed/github-oauth.ts +238 -0
  262. package/server-managed/github-pulls.ts +227 -0
  263. package/server-managed/github-requests.ts +136 -0
  264. package/server-managed/github-users.ts +30 -0
  265. package/server-managed/http-response.ts +76 -0
  266. package/server-managed/http.ts +5503 -0
  267. package/server-managed/import-triage.ts +51 -0
  268. package/server-managed/index.ts +192 -0
  269. package/server-managed/kept-briefly.ts +37 -0
  270. package/server-managed/kept.ts +28 -0
  271. package/server-managed/link-reports.ts +162 -0
  272. package/server-managed/listed-activity.ts +39 -0
  273. package/server-managed/locked-packages.ts +152 -0
  274. package/server-managed/lockfile-scope.ts +49 -0
  275. package/server-managed/lru.ts +30 -0
  276. package/server-managed/managed-issues.ts +78 -0
  277. package/server-managed/management-catalog.ts +45 -0
  278. package/server-managed/management.ts +99 -0
  279. package/server-managed/npm-delta-worker.js +23 -0
  280. package/server-managed/npm-delta.ts +194 -0
  281. package/server-managed/npm-insights.ts +449 -0
  282. package/server-managed/npm-kept.ts +225 -0
  283. package/server-managed/npm-loads.ts +264 -0
  284. package/server-managed/npm-manifest.ts +115 -0
  285. package/server-managed/npm-packages.ts +454 -0
  286. package/server-managed/npm-patch.ts +325 -0
  287. package/server-managed/npm-provenance.ts +123 -0
  288. package/server-managed/npm-risk.ts +285 -0
  289. package/server-managed/object-storage-disk.ts +148 -0
  290. package/server-managed/object-storage-vercel.ts +67 -0
  291. package/server-managed/object-storage.ts +75 -0
  292. package/server-managed/package-entry-points.ts +51 -0
  293. package/server-managed/pretty-print.ts +434 -0
  294. package/server-managed/public-workspace.ts +238 -0
  295. package/server-managed/pull-comment-reviews.ts +161 -0
  296. package/server-managed/pull-dependencies.ts +351 -0
  297. package/server-managed/pull-dependency-reviews.ts +334 -0
  298. package/server-managed/pull-reviews.ts +205 -0
  299. package/server-managed/queue.ts +39 -0
  300. package/server-managed/release-commits.ts +84 -0
  301. package/server-managed/rename-diff-worker.js +26 -0
  302. package/server-managed/rename-diff.ts +279 -0
  303. package/server-managed/repo-path.ts +21 -0
  304. package/server-managed/report-migration.ts +35 -0
  305. package/server-managed/report-query.ts +10 -0
  306. package/server-managed/report-response.ts +16 -0
  307. package/server-managed/report-sources.ts +178 -0
  308. package/server-managed/report-store.ts +85 -0
  309. package/server-managed/repository-actions.ts +218 -0
  310. package/server-managed/repository-agents.ts +448 -0
  311. package/server-managed/repository-aliases.ts +91 -0
  312. package/server-managed/repository-browser.ts +294 -0
  313. package/server-managed/repository-changes.ts +284 -0
  314. package/server-managed/repository-code.ts +215 -0
  315. package/server-managed/repository-dependency-reviews.ts +166 -0
  316. package/server-managed/repository-discovery.ts +82 -0
  317. package/server-managed/repository-history.ts +432 -0
  318. package/server-managed/repository-issues.ts +195 -0
  319. package/server-managed/repository-policy.ts +27 -0
  320. package/server-managed/repository-pulls.ts +212 -0
  321. package/server-managed/repository-pulse.ts +460 -0
  322. package/server-managed/repository-risk-assess.ts +105 -0
  323. package/server-managed/repository-risk-audit.ts +79 -0
  324. package/server-managed/repository-risk-read.ts +170 -0
  325. package/server-managed/repository-risk.ts +491 -0
  326. package/server-managed/repository-squashes.ts +168 -0
  327. package/server-managed/request-timing.ts +63 -0
  328. package/server-managed/reviewed-dependencies.ts +149 -0
  329. package/server-managed/revisions.ts +35 -0
  330. package/server-managed/scan-downloads.ts +87 -0
  331. package/server-managed/session.ts +132 -0
  332. package/server-managed/slugs.ts +38 -0
  333. package/server-managed/solidity-entry-points.ts +71 -0
  334. package/server-managed/sql-postgres.ts +34 -0
  335. package/server-managed/sql.ts +88 -0
  336. package/server-managed/static.ts +28 -0
  337. package/server-managed/storage-command.ts +16 -0
  338. package/server-managed/storage-db.ts +183 -0
  339. package/server-managed/storage-encryption.ts +290 -0
  340. package/server-managed/storage-maintenance.ts +259 -0
  341. package/server-managed/storage-payload.ts +121 -0
  342. package/server-managed/storage-stores.ts +91 -0
  343. package/server-managed/storage.ts +82 -0
  344. package/server-managed/team-app.ts +134 -0
  345. package/server-managed/team-catalog.ts +7 -0
  346. package/server-managed/team-feed.ts +180 -0
  347. package/server-managed/team-npm-scopes.ts +106 -0
  348. package/server-managed/team-reports.ts +319 -0
  349. package/server-managed/triage-response.ts +16 -0
  350. package/server-managed/type-only-worker.js +19 -0
  351. package/server-managed/type-only.ts +105 -0
  352. package/server-managed/uploads.ts +106 -0
  353. package/server-managed/upstream-cache.ts +243 -0
  354. package/server-managed/view-as.ts +58 -0
  355. package/server-managed/workspace-shares.ts +169 -0
  356. package/server.ts +70 -0
  357. package/server/index.ts +0 -480
  358. package/server/lifecycle.ts +0 -208
  359. /package/{LICENSE → LICENSE-MIT} +0 -0
  360. /package/{server → server-e2e}/auth.ts +0 -0
  361. /package/{server → server-e2e}/config.example.json +0 -0
  362. /package/{server → server-e2e}/objstore/blob-fs.ts +0 -0
  363. /package/{server → server-e2e}/objstore/fs.ts +0 -0
  364. /package/{server → server-e2e}/objstore/sign.ts +0 -0
  365. /package/{server → server-e2e}/peer.ts +0 -0
  366. /package/{server → server-e2e}/sse-session.ts +0 -0
  367. /package/{server → server-e2e}/util.ts +0 -0
  368. /package/{server → server-e2e}/validation.ts +0 -0
@@ -0,0 +1,27 @@
1
+ import type { StoredUser } from './db.ts'
2
+
3
+ const WHITEHAT = [
4
+ 291301, // ChALkeR
5
+ 259122746, // exo-nikita
6
+ 247161625, // ex0sec
7
+ ]
8
+ const whitehatIds = new Set(WHITEHAT)
9
+
10
+ // (1) Server permission to add repositories. Keep this independent of GitHub
11
+ // identity and ecosystem exceptions if other install modes broaden it later.
12
+ export function canAddRepositories(user: StoredUser): boolean {
13
+ return user.role === 'admin'
14
+ }
15
+
16
+ // (3b) Only the ecosystem safeguard for public repositories is bypassed by
17
+ // WHITEHAT. Callers must separately establish (0) server readability, (1) add
18
+ // permission, and (2) server admin or GitHub access to this repository.
19
+ export function passesPublicRepositorySafeguard(githubUserId: number | null, involved: boolean): boolean {
20
+ return involved || (githubUserId != null && whitehatIds.has(githubUserId))
21
+ }
22
+
23
+ // Arbitrary public repositories have no involvement evidence, so (2) requires
24
+ // admin even if (1) expands in the future. This capability is also the UI gate.
25
+ export function canAddAnyPublicRepository(user: StoredUser, githubUserId: number | null): boolean {
26
+ return canAddRepositories(user) && user.role === 'admin' && passesPublicRepositorySafeguard(githubUserId, false)
27
+ }
@@ -0,0 +1,212 @@
1
+ // A repository's pull requests and its issues, the most recently updated
2
+ // first, each kept once for all of those that read them so (http.ts
3
+ // keptList): the Pulse, telling whose its year's commits are by the pull
4
+ // requests merged (agent-merges.ts agentMerges) and counting those of a
5
+ // period (repository-pulse.ts listedActivity), and the Agents tab, its pages
6
+ // of pull requests (repository-agents.ts agentPulls) and its latest issues.
7
+ // Those updated since the Pulse's year's start, a few queries an answer, the
8
+ // rest read by the next, and those updated since read as they are. An issue
9
+ // kept as both show it; a pull request as the Agents tab shows it
10
+ // (repository-agents.ts pull: the marks an agent left in its texts, never
11
+ // the texts) and, one merged, what it brought the default branch, as the
12
+ // Pulse tells it: an agent that
13
+ // commits as whoever ran it (Codex's own) leaves its commits no mark, its
14
+ // pull request's branch alone naming it (app-agents.ts
15
+ // AGENT_BRANCH_PREFIXES), or a label one who ran it gave it naming the agent
16
+ // (agent-tasks.ts labelAgents), as the Agents tab tells an agent's work; or
17
+ // its author, an account agents work as (github-agents.ts AGENT_OPERATED),
18
+ // or any GitHub App's bot.
19
+ import { type AgentIssue, type AgentPull, labelAgents } from '../common/managed/agent-tasks.ts'
20
+ import { GITHUB_AGENTS, agentOperated } from '../common/managed/github-agents.ts'
21
+ import { AGENT_BRANCH_PREFIXES } from './app-agents.ts'
22
+ import { AGENT_ISSUE_FIELDS, AGENT_PULL_FIELDS, type IssueNode, type PullNode, issue, pull } from './repository-agents.ts'
23
+ import { githubGraphql } from './repository-history.ts'
24
+
25
+ // What the pull requests kept are read by, its version: bumped as what a
26
+ // page reads, or how one is told, changes, its pull requests read again
27
+ // (http.ts keptList keys them by AGENTS_READ_VERSION and the Pulse's
28
+ // PULSE_YEAR_VERSION too, as either changes how it tells an agent's); and
29
+ // the issues kept.
30
+ export const PULLS_READ_VERSION = 2
31
+ export const ISSUES_LIST_READ_VERSION = 1
32
+ // The pull requests or issues read an answer, a page a query, as many
33
+ // queries as LIST_QUERIES, the rest left for the next to ask; of each pull
34
+ // request merged, as many of its latest commits as MERGED_COMMITS. Those
35
+ // updated since the newest read, few as they mostly are, read TOP_UP_PAGE
36
+ // first, the rest LIST_PAGE a page (GitHub reading on from a page's end
37
+ // whatever its size).
38
+ const LIST_PAGE = 100
39
+ const TOP_UP_PAGE = 10
40
+ const LIST_QUERIES = 5
41
+ const MERGED_COMMITS = 100
42
+ const LABEL_AGENTS = Object.keys(GITHUB_AGENTS)
43
+ const SHA = /^[a-f\d]{40}$/u
44
+
45
+ const count = (value: unknown) => Number.isSafeInteger(value) && (value as number) >= 0 ? value as number : 0
46
+ const timeOf = (value: unknown) => {
47
+ const at = typeof value === 'string' ? Date.parse(value) : Number.NaN
48
+ return Number.isSafeInteger(at) ? at : null
49
+ }
50
+
51
+ // A pull request as read: its row on the Agents tab, whether a GitHub App's
52
+ // bot opened it (`bot`, GitHub's GraphQL API naming one by its app's login
53
+ // alone), and one merged, what it brought the default branch (`merged`).
54
+ export type KeptPull = AgentPull & { bot?: true; merged?: Merged }
55
+ // What a merged pull request brought: its merge commit (`sha`), when it was
56
+ // merged (`at`) and whether it was an agent's (agentPull); an agent's merged
57
+ // with a merge commit, which a year counts none of, its own commits too
58
+ // (`own`, its latest MERGED_COMMITS); and of one of more commits whose merge
59
+ // commit has one parent, its commits' subjects, the oldest first, for those
60
+ // it was rebased as to be told by (agent-merges.ts agentCommits), those of
61
+ // its empty commits left out, as a rebase leaves them, and how many more it
62
+ // had than those read (`more`).
63
+ export interface Merged { sha: string; at: number; agent: boolean; own?: string[]; subjects?: string[]; more?: number }
64
+ // An issue as read: as the Agents tab shows it, whether a bot opened it
65
+ // (`bot`, as a pull request's), when it was closed, and why (`reason`, as
66
+ // GitHub tells it: completed, not planned or a duplicate).
67
+ export type KeptIssue = AgentIssue & { bot?: true; closedAt: number | null; reason: IssueReason | null }
68
+ export type IssueReason = 'completed' | 'not-planned' | 'duplicate'
69
+ export const ISSUE_REASONS: Record<string, IssueReason> = { COMPLETED: 'completed', NOT_PLANNED: 'not-planned', DUPLICATE: 'duplicate' }
70
+ // The pull requests or issues updated since a year's start (`since`) as
71
+ // read so far (readList), the most recently updated first (`rows`); when the
72
+ // newest read was last updated, those updated since read again (`through`,
73
+ // null till all are read once), and where those read reach back no further
74
+ // than was needed, when the oldest of them was updated (`reach`); whether
75
+ // every one the repository has is read (`all`); and a read not finished:
76
+ // where it goes on (`after`), when the newest it began with was updated
77
+ // (`from`), the oldest it reads to (`stop`), how many it has read (`read`)
78
+ // and when the oldest of them was updated (`to`).
79
+ export interface Listed<Row> { rows: Row[]; since: number; through: number | null; reach?: number; all?: true
80
+ next: { after: string; from: number; stop: number; read: number; to?: number } | null }
81
+ export type PullList = Listed<KeptPull>
82
+ export type IssueList = Listed<KeptIssue>
83
+
84
+ // Since when every one updated is read: all of them since the year's
85
+ // start, -∞; none yet, ∞.
86
+ export const pullsReadTo = (list: Pick<Listed<unknown>, 'next' | 'reach'>) => list.next ? list.next.to ?? Number.POSITIVE_INFINITY : list.reach ?? Number.NEGATIVE_INFINITY
87
+
88
+ const PULLS_QUERY = `query($owner: String!, $name: String!, $after: String, $first: Int!) {
89
+ repository(owner: $owner, name: $name) {
90
+ pullRequests(first: $first, after: $after, orderBy: { field: UPDATED_AT, direction: DESC }) {
91
+ pageInfo { hasNextPage endCursor }
92
+ nodes { ${AGENT_PULL_FIELDS} mergedAt mergeCommit { oid parents { totalCount } }
93
+ commits(last: ${MERGED_COMMITS}) { totalCount nodes { commit { oid messageHeadline changedFilesIfAvailable } } } }
94
+ }
95
+ }
96
+ }`
97
+ type MergedNode = PullNode & { mergedAt?: unknown; mergeCommit?: { oid?: unknown; message?: unknown; parents?: { totalCount?: unknown } | null } | null
98
+ author?: PullNode['author'] & { __typename?: unknown }
99
+ commits?: { totalCount?: unknown; nodes?: ({ commit?: { oid?: unknown; messageHeadline?: unknown; changedFilesIfAvailable?: unknown } | null } | null)[] } | null }
100
+ const ISSUES_QUERY = `query($owner: String!, $name: String!, $after: String, $first: Int!) {
101
+ repository(owner: $owner, name: $name) {
102
+ issues(first: $first, after: $after, orderBy: { field: UPDATED_AT, direction: DESC }) {
103
+ pageInfo { hasNextPage endCursor }
104
+ nodes { ${AGENT_ISSUE_FIELDS} stateReason closedAt }
105
+ }
106
+ }
107
+ }`
108
+ type ClosedNode = IssueNode & { stateReason?: unknown; closedAt?: unknown; author?: IssueNode['author'] & { __typename?: unknown } }
109
+ const botOf = (author: { __typename?: unknown } | null | undefined) => author?.__typename === 'Bot' ? { bot: true as const } : {}
110
+ type ListPage<Node> = { repository?: Record<string, { pageInfo?: { hasNextPage?: unknown; endCursor?: unknown }; nodes?: (Node | null)[] } | null> | null }
111
+
112
+ const agentBranch = (name: unknown) => typeof name === 'string' && AGENT_BRANCH_PREFIXES.some(prefix => name.startsWith(prefix))
113
+ // Whether a pull request is an agent's: by its branch, a label naming one,
114
+ // or its author, an account agents work as or a bot (GitHub's GraphQL API
115
+ // naming a bot by its app's login alone, its type telling it).
116
+ const agentPull = ({ headRefName, labels, author }: MergedNode) => agentBranch(headRefName)
117
+ || labelAgents((labels?.nodes ?? []).map(label => label?.name), LABEL_AGENTS).length > 0 || author?.__typename === 'Bot'
118
+ || agentOperated(typeof author?.login === 'string' ? author.login : null, Number.isSafeInteger(author?.databaseId) ? author!.databaseId as number : null)
119
+
120
+ // What a pull request merged brought (Merged); null for one not merged.
121
+ function mergedOf(node: MergedNode): Merged | null {
122
+ const [at, sha] = [timeOf(node.mergedAt), node.mergeCommit?.oid]
123
+ if (at === null || typeof sha !== 'string' || !SHA.test(sha)) return null
124
+ const commits = (node.commits?.nodes ?? []).map(item => item?.commit)
125
+ const merging = count(node.mergeCommit?.parents?.totalCount) > 1
126
+ const copied = commits.filter(commit => commit?.changedFilesIfAvailable !== 0)
127
+ // Of one of more commits than its latest read, how many more it had.
128
+ const more = Math.max(0, count(node.commits?.totalCount) - commits.length)
129
+ const subjects = !merging && (copied.length > 1 || more > 0) && copied.length > 0 && copied.every(commit => typeof commit?.messageHeadline === 'string')
130
+ ? copied.map(commit => commit!.messageHeadline as string) : null
131
+ const agent = agentPull(node)
132
+ const own = agent && merging ? commits.flatMap(commit => typeof commit?.oid === 'string' && SHA.test(commit.oid) ? [commit.oid] : []) : []
133
+ return { sha, at, agent, ...own.length > 0 ? { own } : {}, ...subjects ? { subjects, ...more > 0 ? { more } : {} } : {} }
134
+ }
135
+
136
+ // What an issue closed tells beside its row (KeptIssue).
137
+ function closedOf(node: ClosedNode): KeptIssue | null {
138
+ const row = issue(node)
139
+ return row ? { ...row, ...botOf(node.author), closedAt: timeOf(node.closedAt), reason: ISSUE_REASONS[String(node.stateReason)] ?? null } : null
140
+ }
141
+
142
+ // The most recently updated first, the higher number first of those
143
+ // updated together.
144
+ const newestFirst = (a: { number: number; updatedAt: number | null }, b: { number: number; updatedAt: number | null }) =>
145
+ (b.updatedAt ?? 0) - (a.updatedAt ?? 0) || b.number - a.number
146
+
147
+ // How a list is read: the query of its pages, the repository's connection
148
+ // they are of, and what each of them is kept as.
149
+ interface ListSpec<Node, Row> { query: string; connection: 'pullRequests' | 'issues'; row(node: Node): Row | null }
150
+ const PULLS: ListSpec<MergedNode, KeptPull> = { query: PULLS_QUERY, connection: 'pullRequests', row: node => {
151
+ const row = pull(node)
152
+ const merged = mergedOf(node)
153
+ return row ? { ...row, ...botOf(node.author), ...merged ? { merged } : {} } : null
154
+ } }
155
+ const ISSUES: ListSpec<ClosedNode, KeptIssue> = { query: ISSUES_QUERY, connection: 'issues', row: closedOf }
156
+
157
+ // The pull requests updated since `since` (readList), and what each merged
158
+ // brought.
159
+ export const readPulls = (fullName: string, token: string, since: number, kept: PullList | null, fetchImpl: typeof fetch = globalThis.fetch, need = since) =>
160
+ readList(PULLS, fullName, token, since, kept, fetchImpl, need)
161
+ // The issues updated since `since` (readList), and when and why each closed was.
162
+ export const readIssues = (fullName: string, token: string, since: number, kept: IssueList | null, fetchImpl: typeof fetch = globalThis.fetch, need = since) =>
163
+ readList(ISSUES, fullName, token, since, kept, fetchImpl, need)
164
+
165
+ // Those of a list (`spec`) updated since `since`, read the most recently
166
+ // updated first (a pull request is updated when merged, or after, an issue
167
+ // when closed), on from what was `kept`: till one updated before the year,
168
+ // or before the newest read before was, as many queries an answer as
169
+ // LIST_QUERIES, a read not finished going on from where it was left by the
170
+ // next, or once those updated since `need` are all read; where those read
171
+ // reached no further back than they needed, and `need` is further, read
172
+ // again from the newest on, as far back as it needs. Each kept as last read,
173
+ // one updated before the year forgotten.
174
+ async function readList<Node extends { updatedAt?: unknown }, Row extends { number: number; updatedAt: number | null }>(spec: ListSpec<Node, Row>,
175
+ fullName: string, token: string, since: number, kept: Listed<Row> | null, fetchImpl: typeof fetch, need: number): Promise<Listed<Row>> {
176
+ const [owner, name] = fullName.split('/') as [string, string]
177
+ const rows = new Map((kept?.rows ?? []).filter(item => (item.updatedAt ?? 0) >= since).map(item => [item.number, item]))
178
+ // Every one read, as long as none was forgotten, the year begun since.
179
+ const whole = kept?.all === true && rows.size === kept.rows.length
180
+ const further = !kept?.next && kept?.reach !== undefined && kept.reach > need
181
+ const stop = kept?.next?.stop ?? (further ? since : Math.max(since, kept?.through ?? since))
182
+ const result = (through: number | null, next: Listed<Row>['next'], reach: number | undefined, all = whole): Listed<Row> => ({
183
+ rows: [...rows.values()].toSorted(newestFirst), since, through, ...reach === undefined ? {} : { reach }, ...all ? { all: true as const } : {}, next })
184
+ // A read of those updated since the newest read alone.
185
+ const topping = !kept?.next && !further && kept?.through !== null && kept?.through !== undefined
186
+ let { after = null, from = null, read = 0, to = null }: { after?: string | null; from?: number | null; read?: number; to?: number | null } = kept?.next ?? {}
187
+ for (let queries = 0; queries < LIST_QUERIES; queries++) {
188
+ const first = topping && queries === 0 ? TOP_UP_PAGE : LIST_PAGE
189
+ const page = (await githubGraphql(spec.query, { owner, name, after, first }, token, fetchImpl) as ListPage<Node> | null)?.repository?.[spec.connection]
190
+ const end = page?.pageInfo?.hasNextPage !== true || typeof page.pageInfo.endCursor !== 'string'
191
+ let crossed = false
192
+ for (const node of page?.nodes ?? []) {
193
+ const updated = timeOf(node?.updatedAt)
194
+ if (!node || updated === null) continue
195
+ if (updated < stop) { crossed = true; break }
196
+ from ??= updated
197
+ to = updated
198
+ read++
199
+ const row = spec.row(node)
200
+ if (row) rows.set(row.number, row)
201
+ }
202
+ // Read as far back as it reads to: the year's start, every one since
203
+ // read; else as far as those read before reached. Read to the last
204
+ // the repository has, every one of them.
205
+ if (crossed || end) return result(from ?? stop, null, stop > since ? kept?.reach : undefined, (end && !crossed) || (whole && stop > since))
206
+ after = page!.pageInfo!.endCursor as string
207
+ // Read as far back as `need`: finished, so far back, those updated
208
+ // since read by the next as ever.
209
+ if (need > since && to !== null && to < need) return result(Math.max(from ?? stop, kept?.through ?? Number.NEGATIVE_INFINITY), null, to)
210
+ }
211
+ return result(kept?.through ?? null, { after: after!, from: from ?? stop, stop, read, ...to === null ? {} : { to } }, kept?.reach)
212
+ }