neondeck 1.0.0-beta.10

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 (804) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/DEVELOPMENT.md +304 -0
  3. package/LICENSE +21 -0
  4. package/README.md +129 -0
  5. package/SOUL.md +8 -0
  6. package/bin/neondeck.mjs +27 -0
  7. package/config/dashboard.json +224 -0
  8. package/config/dashboard.schema.json +247 -0
  9. package/dist/SOUL.md +8 -0
  10. package/dist/assets/app-CnXGMuTl.js +3878 -0
  11. package/dist/assets/app-CnXGMuTl.js.map +1 -0
  12. package/dist/assets/autopilot-B8lfWyA1.js +68813 -0
  13. package/dist/assets/autopilot-B8lfWyA1.js.map +1 -0
  14. package/dist/assets/briefing-BT8h9XXT.js +52 -0
  15. package/dist/assets/briefing-BT8h9XXT.js.map +1 -0
  16. package/dist/assets/briefing-VMdQPEGc.js +2 -0
  17. package/dist/assets/briefings-DuApznpN.js +1596 -0
  18. package/dist/assets/briefings-DuApznpN.js.map +1 -0
  19. package/dist/assets/command-run-CnPOA8iY.js +20 -0
  20. package/dist/assets/command-run-CnPOA8iY.js.map +1 -0
  21. package/dist/assets/command-run-Smpq7waT.js +2 -0
  22. package/dist/assets/display-assistant-CKU4YJlc.js +392 -0
  23. package/dist/assets/display-assistant-CKU4YJlc.js.map +1 -0
  24. package/dist/assets/fix-pr-ci-Ce2SIcyf.js +46 -0
  25. package/dist/assets/fix-pr-ci-Ce2SIcyf.js.map +1 -0
  26. package/dist/assets/fix-pr-ci-P3dLxmbx.js +2 -0
  27. package/dist/assets/migrations/20260710061339_neondeck_pre_1_0_baseline/migration.sql +748 -0
  28. package/dist/assets/migrations/20260710061339_neondeck_pre_1_0_baseline/snapshot.json +7126 -0
  29. package/dist/assets/migrations/20260711171708_conversational_briefings/migration.sql +35 -0
  30. package/dist/assets/migrations/20260711171708_conversational_briefings/snapshot.json +7458 -0
  31. package/dist/assets/migrations/20260714191624_worktree_lock_revocation/migration.sql +1 -0
  32. package/dist/assets/migrations/20260714191624_worktree_lock_revocation/snapshot.json +7468 -0
  33. package/dist/assets/migrations/20260714213018_pr_reviews/migration.sql +34 -0
  34. package/dist/assets/migrations/20260714213018_pr_reviews/snapshot.json +7809 -0
  35. package/dist/assets/pr-reviews-BtJ5cm5P.js +521 -0
  36. package/dist/assets/pr-reviews-BtJ5cm5P.js.map +1 -0
  37. package/dist/assets/prepare-pr-worktree-B0qMZMRP.js +2 -0
  38. package/dist/assets/prepare-pr-worktree-C7SmiaTp.js +17 -0
  39. package/dist/assets/prepare-pr-worktree-C7SmiaTp.js.map +1 -0
  40. package/dist/assets/push-pr-autofix-CsMbrnXR.js +17 -0
  41. package/dist/assets/push-pr-autofix-CsMbrnXR.js.map +1 -0
  42. package/dist/assets/push-pr-autofix-njuM6ej2.js +2 -0
  43. package/dist/assets/review-pr-for-human-CEciM46A.js +2 -0
  44. package/dist/assets/review-pr-for-human-TPiQNRWV.js +790 -0
  45. package/dist/assets/review-pr-for-human-TPiQNRWV.js.map +1 -0
  46. package/dist/assets/safety-BQC7exnI.js +1931 -0
  47. package/dist/assets/safety-BQC7exnI.js.map +1 -0
  48. package/dist/assets/scheduled-agent-instruction-BbxwCKbU.js +2 -0
  49. package/dist/assets/scheduled-agent-instruction-C58itQyU.js +22 -0
  50. package/dist/assets/scheduled-agent-instruction-C58itQyU.js.map +1 -0
  51. package/dist/assets/scheduler-tick-B2S9jCYS.js +90 -0
  52. package/dist/assets/scheduler-tick-B2S9jCYS.js.map +1 -0
  53. package/dist/assets/scheduler-workflow-CnI0tWbg.js +21 -0
  54. package/dist/assets/scheduler-workflow-CnI0tWbg.js.map +1 -0
  55. package/dist/assets/skills/github-gh/SKILL.md +56 -0
  56. package/dist/assets/skills/neon-ci-fix/SKILL.md +15 -0
  57. package/dist/assets/skills/neon-docs-fix/SKILL.md +15 -0
  58. package/dist/assets/skills/neon-issue-triage/SKILL.md +13 -0
  59. package/dist/assets/skills/neon-pr-review/SKILL.md +19 -0
  60. package/dist/assets/skills/neondeck/SKILL.md +127 -0
  61. package/dist/assets/skills/neondeck-handoff/SKILL.md +31 -0
  62. package/dist/assets/triage-pr-event-CBje6df5.js +2 -0
  63. package/dist/assets/triage-pr-event-IgC-IrNs.js +17 -0
  64. package/dist/assets/triage-pr-event-IgC-IrNs.js.map +1 -0
  65. package/dist/assets/verify-then-push-pr-autofix-C5Dpomtd.js +75 -0
  66. package/dist/assets/verify-then-push-pr-autofix-C5Dpomtd.js.map +1 -0
  67. package/dist/assets/verify-then-push-pr-autofix-oZ0-wa7L.js +2 -0
  68. package/dist/config/dashboard.json +224 -0
  69. package/dist/config/dashboard.schema.json +247 -0
  70. package/dist/server.mjs +583 -0
  71. package/dist/server.mjs.map +1 -0
  72. package/dist/skills/github-gh/SKILL.md +56 -0
  73. package/dist/skills/neon-ci-fix/SKILL.md +15 -0
  74. package/dist/skills/neon-docs-fix/SKILL.md +15 -0
  75. package/dist/skills/neon-issue-triage/SKILL.md +13 -0
  76. package/dist/skills/neon-pr-review/SKILL.md +19 -0
  77. package/dist/skills/neondeck/SKILL.md +127 -0
  78. package/dist/skills/neondeck-handoff/SKILL.md +31 -0
  79. package/drizzle.config.ts +7 -0
  80. package/package.json +156 -0
  81. package/shared/patch-anchors.ts +78 -0
  82. package/shared/report-document.ts +91 -0
  83. package/src/agents/busywork-workflow.ts +24 -0
  84. package/src/agents/display-assistant.ts +137 -0
  85. package/src/agents/pr-review-assistant.ts +25 -0
  86. package/src/agents/scheduler-workflow.ts +20 -0
  87. package/src/agents/support/tools.ts +346 -0
  88. package/src/app.ts +1 -0
  89. package/src/cli/index.ts +560 -0
  90. package/src/cli/learning.ts +44 -0
  91. package/src/cli/mcp.ts +496 -0
  92. package/src/cli/modules.ts +107 -0
  93. package/src/cli/onboarding.ts +666 -0
  94. package/src/cli/options.ts +102 -0
  95. package/src/cli/output.ts +399 -0
  96. package/src/cli/preapprovals.ts +204 -0
  97. package/src/cli/prompts.ts +108 -0
  98. package/src/cli/types.ts +59 -0
  99. package/src/db.ts +41 -0
  100. package/src/desktop/open.ts +698 -0
  101. package/src/desktop/service.ts +863 -0
  102. package/src/domains/mcp/actions.ts +420 -0
  103. package/src/domains/mcp/config.ts +501 -0
  104. package/src/domains/mcp/format.ts +35 -0
  105. package/src/domains/mcp/gate.ts +188 -0
  106. package/src/domains/mcp/index.ts +9 -0
  107. package/src/domains/mcp/instructions.ts +18 -0
  108. package/src/domains/mcp/oauth.ts +1063 -0
  109. package/src/domains/mcp/policy.ts +15 -0
  110. package/src/domains/mcp/registry.ts +461 -0
  111. package/src/domains/mcp/schemas.ts +171 -0
  112. package/src/domains/mcp/stdio.ts +451 -0
  113. package/src/domains/mcp/store.ts +905 -0
  114. package/src/domains/mcp/tools.ts +119 -0
  115. package/src/lib/action-result.ts +51 -0
  116. package/src/lib/exec.ts +118 -0
  117. package/src/lib/git.ts +20 -0
  118. package/src/lib/report-html.ts +193 -0
  119. package/src/lib/sqlite.ts +51 -0
  120. package/src/lib/valibot.ts +33 -0
  121. package/src/modules/app-state/index.ts +4 -0
  122. package/src/modules/app-state/notification-events.ts +44 -0
  123. package/src/modules/app-state/notifications.ts +259 -0
  124. package/src/modules/app-state/types.ts +42 -0
  125. package/src/modules/app-state/workflow-summaries.ts +201 -0
  126. package/src/modules/autonomous-audit/index.ts +225 -0
  127. package/src/modules/autopilot/actions.ts +186 -0
  128. package/src/modules/autopilot/admissions.ts +865 -0
  129. package/src/modules/autopilot/approvals.ts +59 -0
  130. package/src/modules/autopilot/ci-fix-run.ts +1332 -0
  131. package/src/modules/autopilot/ci-fix.ts +718 -0
  132. package/src/modules/autopilot/comments.ts +426 -0
  133. package/src/modules/autopilot/fixtures.ts +344 -0
  134. package/src/modules/autopilot/github-facts.ts +306 -0
  135. package/src/modules/autopilot/index.ts +64 -0
  136. package/src/modules/autopilot/notifications.ts +271 -0
  137. package/src/modules/autopilot/push-support.ts +260 -0
  138. package/src/modules/autopilot/push.ts +670 -0
  139. package/src/modules/autopilot/recovery.ts +656 -0
  140. package/src/modules/autopilot/review-feedback.ts +649 -0
  141. package/src/modules/autopilot/review-support.ts +524 -0
  142. package/src/modules/autopilot/revision-run.ts +445 -0
  143. package/src/modules/autopilot/schemas.ts +485 -0
  144. package/src/modules/autopilot/state-mappers.ts +517 -0
  145. package/src/modules/autopilot/state-schemas.ts +450 -0
  146. package/src/modules/autopilot/state-store.ts +183 -0
  147. package/src/modules/autopilot/state.ts +255 -0
  148. package/src/modules/autopilot/triage-support.ts +234 -0
  149. package/src/modules/autopilot/triage.ts +156 -0
  150. package/src/modules/autopilot/utils.ts +145 -0
  151. package/src/modules/autopilot/worktree.ts +637 -0
  152. package/src/modules/autopilot-policy/config.ts +101 -0
  153. package/src/modules/autopilot-policy/index.ts +4 -0
  154. package/src/modules/autopilot-policy/risk.ts +359 -0
  155. package/src/modules/autopilot-policy/schemas.ts +229 -0
  156. package/src/modules/autopilot-policy/service.ts +272 -0
  157. package/src/modules/briefings/actions.ts +80 -0
  158. package/src/modules/briefings/index.ts +5 -0
  159. package/src/modules/briefings/schemas.ts +125 -0
  160. package/src/modules/briefings/service.ts +642 -0
  161. package/src/modules/briefings/snapshot.ts +291 -0
  162. package/src/modules/briefings/store.ts +644 -0
  163. package/src/modules/commands/actions.ts +112 -0
  164. package/src/modules/commands/handlers/index.ts +3 -0
  165. package/src/modules/commands/handlers/misc.ts +367 -0
  166. package/src/modules/commands/handlers/queue.ts +710 -0
  167. package/src/modules/commands/handlers/repo.ts +339 -0
  168. package/src/modules/commands/index.ts +5 -0
  169. package/src/modules/commands/registry.ts +157 -0
  170. package/src/modules/commands/runner.ts +152 -0
  171. package/src/modules/commands/schemas.ts +109 -0
  172. package/src/modules/commands/summaries.ts +108 -0
  173. package/src/modules/commands/utils.ts +113 -0
  174. package/src/modules/config/actions.ts +253 -0
  175. package/src/modules/config/events.ts +80 -0
  176. package/src/modules/config/files.ts +9 -0
  177. package/src/modules/config/history.ts +74 -0
  178. package/src/modules/config/index.ts +9 -0
  179. package/src/modules/config/mutations/dashboard.ts +333 -0
  180. package/src/modules/config/mutations/execution.ts +89 -0
  181. package/src/modules/config/mutations/models.ts +467 -0
  182. package/src/modules/config/mutations/providers.ts +163 -0
  183. package/src/modules/config/mutations/repos.ts +663 -0
  184. package/src/modules/config/read.ts +130 -0
  185. package/src/modules/config/result.ts +66 -0
  186. package/src/modules/config/schemas.ts +180 -0
  187. package/src/modules/docs-drift/index.ts +1 -0
  188. package/src/modules/docs-drift/service.ts +863 -0
  189. package/src/modules/execution/actions.ts +35 -0
  190. package/src/modules/execution/approvals.ts +322 -0
  191. package/src/modules/execution/exedev/checkouts.ts +356 -0
  192. package/src/modules/execution/exedev/context.ts +286 -0
  193. package/src/modules/execution/index.ts +7 -0
  194. package/src/modules/execution/policy.ts +102 -0
  195. package/src/modules/execution/run.ts +589 -0
  196. package/src/modules/execution/schemas.ts +116 -0
  197. package/src/modules/execution/scope.ts +137 -0
  198. package/src/modules/execution/store.ts +320 -0
  199. package/src/modules/execution/utils.ts +105 -0
  200. package/src/modules/execution-policy/index.ts +424 -0
  201. package/src/modules/flue/execution-context.ts +40 -0
  202. package/src/modules/flue/index.ts +2 -0
  203. package/src/modules/flue/origin.ts +7 -0
  204. package/src/modules/github/actions.ts +334 -0
  205. package/src/modules/github/checks.ts +484 -0
  206. package/src/modules/github/client.ts +110 -0
  207. package/src/modules/github/comments.ts +204 -0
  208. package/src/modules/github/errors.ts +50 -0
  209. package/src/modules/github/index.ts +93 -0
  210. package/src/modules/github/issues.ts +116 -0
  211. package/src/modules/github/pr-file-cache.ts +264 -0
  212. package/src/modules/github/pull-requests.ts +411 -0
  213. package/src/modules/github/queue.ts +350 -0
  214. package/src/modules/github/reviews.ts +1446 -0
  215. package/src/modules/github/schemas.ts +794 -0
  216. package/src/modules/github/state-truncation.ts +31 -0
  217. package/src/modules/handoff/index.ts +2 -0
  218. package/src/modules/handoff/schemas.ts +61 -0
  219. package/src/modules/handoff/service.ts +587 -0
  220. package/src/modules/hygiene/index.ts +1 -0
  221. package/src/modules/hygiene/service.ts +513 -0
  222. package/src/modules/kilo/actions.ts +229 -0
  223. package/src/modules/kilo/ci-fix-run-reconcile.ts +299 -0
  224. package/src/modules/kilo/docs-drift-boundary.ts +178 -0
  225. package/src/modules/kilo/index.ts +8 -0
  226. package/src/modules/kilo/notifications.ts +295 -0
  227. package/src/modules/kilo/process.ts +637 -0
  228. package/src/modules/kilo/results/actions.ts +66 -0
  229. package/src/modules/kilo/results/gates.ts +227 -0
  230. package/src/modules/kilo/results/index.ts +23 -0
  231. package/src/modules/kilo/results/schemas.ts +112 -0
  232. package/src/modules/kilo/results/service.ts +512 -0
  233. package/src/modules/kilo/results/state.ts +508 -0
  234. package/src/modules/kilo/results/verify.ts +328 -0
  235. package/src/modules/kilo/revision-reconcile.ts +187 -0
  236. package/src/modules/kilo/runtime-facts.ts +128 -0
  237. package/src/modules/kilo/schemas.ts +265 -0
  238. package/src/modules/kilo/service.ts +714 -0
  239. package/src/modules/kilo/sessions-adapters.ts +343 -0
  240. package/src/modules/kilo/sessions.ts +446 -0
  241. package/src/modules/kilo/store.ts +689 -0
  242. package/src/modules/kilo/utils.ts +166 -0
  243. package/src/modules/learning/automation-health.ts +441 -0
  244. package/src/modules/learning/automation-memory.ts +179 -0
  245. package/src/modules/learning/index.ts +6 -0
  246. package/src/modules/learning/observability.ts +616 -0
  247. package/src/modules/learning/operator.ts +404 -0
  248. package/src/modules/learning/reviews/agents.ts +33 -0
  249. package/src/modules/learning/reviews/complete.ts +321 -0
  250. package/src/modules/learning/reviews/context.ts +145 -0
  251. package/src/modules/learning/reviews/events.ts +306 -0
  252. package/src/modules/learning/reviews/index.ts +6 -0
  253. package/src/modules/learning/reviews/pr-cadence.ts +157 -0
  254. package/src/modules/learning/reviews/pr-context.ts +749 -0
  255. package/src/modules/learning/reviews/prepare.ts +387 -0
  256. package/src/modules/learning/reviews/schemas.ts +184 -0
  257. package/src/modules/learning/reviews/store.ts +405 -0
  258. package/src/modules/learning/skill-patches/actions.ts +86 -0
  259. package/src/modules/learning/skill-patches/index.ts +3 -0
  260. package/src/modules/learning/skill-patches/schemas.ts +60 -0
  261. package/src/modules/learning/skill-patches/service.ts +571 -0
  262. package/src/modules/learning/skill-patches/support.ts +417 -0
  263. package/src/modules/memory/actions.ts +183 -0
  264. package/src/modules/memory/candidates.ts +525 -0
  265. package/src/modules/memory/events.ts +2 -0
  266. package/src/modules/memory/index.ts +6 -0
  267. package/src/modules/memory/prompt.ts +214 -0
  268. package/src/modules/memory/schemas.ts +170 -0
  269. package/src/modules/memory/service.ts +578 -0
  270. package/src/modules/memory/store.ts +489 -0
  271. package/src/modules/pr-events/actions.ts +191 -0
  272. package/src/modules/pr-events/index.ts +46 -0
  273. package/src/modules/pr-events/schemas.ts +186 -0
  274. package/src/modules/pr-events/service.ts +1414 -0
  275. package/src/modules/pr-events/target.ts +222 -0
  276. package/src/modules/pr-events/utils.ts +85 -0
  277. package/src/modules/pr-events/watermarks.ts +308 -0
  278. package/src/modules/pr-local-diffs/index.ts +334 -0
  279. package/src/modules/pr-review-assist/actions.ts +239 -0
  280. package/src/modules/pr-review-assist/index.ts +3 -0
  281. package/src/modules/pr-review-assist/schemas.ts +86 -0
  282. package/src/modules/pr-review-assist/service.ts +706 -0
  283. package/src/modules/pr-reviews/events.ts +39 -0
  284. package/src/modules/pr-reviews/index.ts +4 -0
  285. package/src/modules/pr-reviews/service.ts +655 -0
  286. package/src/modules/pr-reviews/store.ts +186 -0
  287. package/src/modules/pr-reviews/types.ts +48 -0
  288. package/src/modules/prepared-diffs/actions.ts +132 -0
  289. package/src/modules/prepared-diffs/index.ts +53 -0
  290. package/src/modules/prepared-diffs/schemas.ts +255 -0
  291. package/src/modules/prepared-diffs/service.ts +791 -0
  292. package/src/modules/prepared-diffs/store.ts +678 -0
  293. package/src/modules/repo-guardrails/config.ts +61 -0
  294. package/src/modules/repo-guardrails/index.ts +162 -0
  295. package/src/modules/repo-guardrails/risk.ts +2 -0
  296. package/src/modules/reports/index.ts +1 -0
  297. package/src/modules/reports/service.ts +337 -0
  298. package/src/modules/repos/index.ts +3 -0
  299. package/src/modules/repos/model-discovery.ts +233 -0
  300. package/src/modules/repos/providers.ts +194 -0
  301. package/src/modules/repos/registry.ts +232 -0
  302. package/src/modules/runtime/agent-config.ts +167 -0
  303. package/src/modules/runtime/doctor.ts +528 -0
  304. package/src/modules/runtime/env.ts +123 -0
  305. package/src/modules/runtime/index.ts +12 -0
  306. package/src/modules/runtime/local-api-auth.ts +43 -0
  307. package/src/modules/runtime/metrics.ts +211 -0
  308. package/src/modules/runtime/skills-actions.ts +141 -0
  309. package/src/modules/runtime/skills.ts +689 -0
  310. package/src/modules/runtime/soul.ts +68 -0
  311. package/src/modules/runtime/status-database.ts +252 -0
  312. package/src/modules/runtime/status-schema.ts +149 -0
  313. package/src/modules/runtime/status.ts +481 -0
  314. package/src/modules/runtime/subagents.ts +37 -0
  315. package/src/modules/runtime/utility-model.ts +47 -0
  316. package/src/modules/safety/index.ts +3 -0
  317. package/src/modules/safety/policy-entries.ts +2528 -0
  318. package/src/modules/safety/schemas.ts +63 -0
  319. package/src/modules/safety/service.ts +58 -0
  320. package/src/modules/safety/tools.ts +15 -0
  321. package/src/modules/scheduled-tasks/actions.ts +113 -0
  322. package/src/modules/scheduled-tasks/dispatch.ts +107 -0
  323. package/src/modules/scheduled-tasks/index.ts +6 -0
  324. package/src/modules/scheduled-tasks/schemas.ts +91 -0
  325. package/src/modules/scheduled-tasks/service.ts +296 -0
  326. package/src/modules/scheduled-tasks/store.ts +814 -0
  327. package/src/modules/scheduled-tasks/triggers.ts +87 -0
  328. package/src/modules/scheduler/actions.ts +37 -0
  329. package/src/modules/scheduler/dispatch.ts +192 -0
  330. package/src/modules/scheduler/index.ts +4 -0
  331. package/src/modules/scheduler/lease.ts +247 -0
  332. package/src/modules/scheduler/pr-watch-event-deltas.ts +367 -0
  333. package/src/modules/scheduler/pr-watch-events.ts +834 -0
  334. package/src/modules/scheduler/schemas.ts +79 -0
  335. package/src/modules/scheduler/service.ts +228 -0
  336. package/src/modules/scheduler/utils.ts +106 -0
  337. package/src/modules/scheduler/workflow-invocation.ts +48 -0
  338. package/src/modules/sessions/actions.ts +213 -0
  339. package/src/modules/sessions/active-session.ts +46 -0
  340. package/src/modules/sessions/activity.ts +122 -0
  341. package/src/modules/sessions/agent-context.ts +152 -0
  342. package/src/modules/sessions/approval-nudges.ts +156 -0
  343. package/src/modules/sessions/events.ts +57 -0
  344. package/src/modules/sessions/index.ts +12 -0
  345. package/src/modules/sessions/queries.ts +252 -0
  346. package/src/modules/sessions/references.ts +113 -0
  347. package/src/modules/sessions/repo-context.ts +131 -0
  348. package/src/modules/sessions/schemas.ts +285 -0
  349. package/src/modules/sessions/service.ts +845 -0
  350. package/src/modules/sessions/store.ts +560 -0
  351. package/src/modules/sessions/summaries.ts +129 -0
  352. package/src/modules/sessions/utils.ts +18 -0
  353. package/src/modules/watches/actions.ts +115 -0
  354. package/src/modules/watches/index.ts +6 -0
  355. package/src/modules/watches/polling.ts +245 -0
  356. package/src/modules/watches/references.ts +293 -0
  357. package/src/modules/watches/schemas.ts +156 -0
  358. package/src/modules/watches/service.ts +557 -0
  359. package/src/modules/watches/store.ts +398 -0
  360. package/src/modules/watches/utils.ts +51 -0
  361. package/src/modules/worktree-verification/index.ts +256 -0
  362. package/src/modules/worktrees/access.ts +129 -0
  363. package/src/modules/worktrees/actions.ts +108 -0
  364. package/src/modules/worktrees/cleanup.ts +98 -0
  365. package/src/modules/worktrees/ensure-pr-worktree.ts +63 -0
  366. package/src/modules/worktrees/errors.ts +38 -0
  367. package/src/modules/worktrees/git.ts +48 -0
  368. package/src/modules/worktrees/index.ts +7 -0
  369. package/src/modules/worktrees/locks.ts +231 -0
  370. package/src/modules/worktrees/paths.ts +209 -0
  371. package/src/modules/worktrees/push-target.ts +39 -0
  372. package/src/modules/worktrees/queries.ts +31 -0
  373. package/src/modules/worktrees/schemas.ts +237 -0
  374. package/src/modules/worktrees/service.ts +680 -0
  375. package/src/modules/worktrees/store.ts +415 -0
  376. package/src/repo-edit/actions.ts +509 -0
  377. package/src/repo-edit/audit.ts +180 -0
  378. package/src/repo-edit/fuzzy-replace.ts +256 -0
  379. package/src/repo-edit/git.ts +830 -0
  380. package/src/repo-edit/index.ts +4 -0
  381. package/src/repo-edit/locks.ts +27 -0
  382. package/src/repo-edit/patch-parser.ts +212 -0
  383. package/src/repo-edit/patch-service.ts +572 -0
  384. package/src/repo-edit/path-safety.ts +299 -0
  385. package/src/repo-edit/schemas.ts +239 -0
  386. package/src/repo-edit/service.ts +488 -0
  387. package/src/repo-edit/support.ts +459 -0
  388. package/src/runtime-home/app-db/index.ts +74 -0
  389. package/src/runtime-home/app-db/migrate.ts +491 -0
  390. package/src/runtime-home/app-db/migrations/20260710061339_neondeck_pre_1_0_baseline/migration.sql +748 -0
  391. package/src/runtime-home/app-db/migrations/20260710061339_neondeck_pre_1_0_baseline/snapshot.json +7126 -0
  392. package/src/runtime-home/app-db/migrations/20260711171708_conversational_briefings/migration.sql +35 -0
  393. package/src/runtime-home/app-db/migrations/20260711171708_conversational_briefings/snapshot.json +7458 -0
  394. package/src/runtime-home/app-db/migrations/20260714191624_worktree_lock_revocation/migration.sql +1 -0
  395. package/src/runtime-home/app-db/migrations/20260714191624_worktree_lock_revocation/snapshot.json +7468 -0
  396. package/src/runtime-home/app-db/migrations/20260714213018_pr_reviews/migration.sql +34 -0
  397. package/src/runtime-home/app-db/migrations/20260714213018_pr_reviews/snapshot.json +7809 -0
  398. package/src/runtime-home/app-db/reconcile.ts +155 -0
  399. package/src/runtime-home/app-db/schema.ts +1209 -0
  400. package/src/runtime-home/bootstrap.ts +177 -0
  401. package/src/runtime-home/dashboard-migrations.ts +101 -0
  402. package/src/runtime-home/defaults.ts +54 -0
  403. package/src/runtime-home/files.ts +193 -0
  404. package/src/runtime-home/guardrails.ts +77 -0
  405. package/src/runtime-home/index.ts +71 -0
  406. package/src/runtime-home/paths.ts +75 -0
  407. package/src/runtime-home/schemas.ts +520 -0
  408. package/src/sandboxes/exedev.ts +676 -0
  409. package/src/server/app.ts +5 -0
  410. package/src/server/autopilot-push-dispatch.ts +326 -0
  411. package/src/server/create-app.ts +160 -0
  412. package/src/server/events/event-stream.ts +109 -0
  413. package/src/server/http.ts +56 -0
  414. package/src/server/learning-hooks.ts +489 -0
  415. package/src/server/middleware.ts +81 -0
  416. package/src/server/routes/autopilot.ts +318 -0
  417. package/src/server/routes/briefings.ts +34 -0
  418. package/src/server/routes/commands.ts +12 -0
  419. package/src/server/routes/config.ts +124 -0
  420. package/src/server/routes/execution.ts +71 -0
  421. package/src/server/routes/github.ts +559 -0
  422. package/src/server/routes/handoff.ts +69 -0
  423. package/src/server/routes/kilo.ts +179 -0
  424. package/src/server/routes/learning.ts +384 -0
  425. package/src/server/routes/mcp.ts +301 -0
  426. package/src/server/routes/memory.ts +99 -0
  427. package/src/server/routes/metrics.ts +12 -0
  428. package/src/server/routes/notifications.ts +48 -0
  429. package/src/server/routes/repo-edit.ts +115 -0
  430. package/src/server/routes/reports.ts +128 -0
  431. package/src/server/routes/repos.ts +50 -0
  432. package/src/server/routes/reviews.ts +257 -0
  433. package/src/server/routes/runtime.ts +48 -0
  434. package/src/server/routes/safety.ts +13 -0
  435. package/src/server/routes/scheduled-tasks.ts +60 -0
  436. package/src/server/routes/scheduler.ts +13 -0
  437. package/src/server/routes/sessions.ts +229 -0
  438. package/src/server/routes/skills.ts +103 -0
  439. package/src/server/routes/watches.ts +81 -0
  440. package/src/server/routes/workflows.ts +21 -0
  441. package/src/server/routes/worktrees.ts +64 -0
  442. package/src/server/scheduler-workflow.ts +541 -0
  443. package/src/server/serve.ts +100 -0
  444. package/src/setup.ts +162 -0
  445. package/src/skills/github-gh/SKILL.md +56 -0
  446. package/src/skills/neon-ci-fix/SKILL.md +15 -0
  447. package/src/skills/neon-docs-fix/SKILL.md +15 -0
  448. package/src/skills/neon-issue-triage/SKILL.md +13 -0
  449. package/src/skills/neon-pr-review/SKILL.md +19 -0
  450. package/src/skills/neondeck/SKILL.md +127 -0
  451. package/src/skills/neondeck-handoff/SKILL.md +31 -0
  452. package/src/workflows/briefing.ts +50 -0
  453. package/src/workflows/command-run.ts +15 -0
  454. package/src/workflows/comment-pr-autofix-result.ts +10 -0
  455. package/src/workflows/curate_learning_store.ts +37 -0
  456. package/src/workflows/dev-doctor.ts +10 -0
  457. package/src/workflows/fix-pr-ci-failure.ts +10 -0
  458. package/src/workflows/fix-pr-ci.ts +15 -0
  459. package/src/workflows/fix-pr-review-feedback.ts +10 -0
  460. package/src/workflows/handoff_to_kilo.ts +10 -0
  461. package/src/workflows/prepare-pr-worktree.ts +10 -0
  462. package/src/workflows/promote_kilo_result.ts +10 -0
  463. package/src/workflows/push-pr-autofix.ts +10 -0
  464. package/src/workflows/reconcile_kilo_task.ts +10 -0
  465. package/src/workflows/review-pr-for-human.ts +15 -0
  466. package/src/workflows/review_conversation_for_learning.ts +37 -0
  467. package/src/workflows/review_kilo_result.ts +10 -0
  468. package/src/workflows/review_pr_batch_for_learning.ts +37 -0
  469. package/src/workflows/scheduled-agent-instruction.ts +16 -0
  470. package/src/workflows/scheduler-tick.ts +10 -0
  471. package/src/workflows/summarize_kilo_session.ts +25 -0
  472. package/src/workflows/triage-pr-event.ts +10 -0
  473. package/src/workflows/verify-pr-worktree.ts +10 -0
  474. package/src/workflows/verify-then-push-pr-autofix.ts +82 -0
  475. package/src/workflows/verify_kilo_result.ts +10 -0
  476. package/src/workflows/watch-pr.ts +10 -0
  477. package/web/dist/assets/GitHubPrReview-Bf8PXCaq.js +3 -0
  478. package/web/dist/assets/MultiFileView-CFYStqTV.js +2970 -0
  479. package/web/dist/assets/PrReviewPopoutPage-CZN1CV82.js +1 -0
  480. package/web/dist/assets/abap-CLvhMVsD.js +1 -0
  481. package/web/dist/assets/actionscript-3--17pq3dv.js +1 -0
  482. package/web/dist/assets/ada-C5qYipkI.js +1 -0
  483. package/web/dist/assets/andromeeda-vGVdxbeo.js +1 -0
  484. package/web/dist/assets/angular-html-BVBpGdXr.js +1 -0
  485. package/web/dist/assets/angular-ts-BfdufMKP.js +1 -0
  486. package/web/dist/assets/apache-U0d_L8uA.js +1 -0
  487. package/web/dist/assets/apex-VAyPSnFM.js +1 -0
  488. package/web/dist/assets/apl-DWBSSoBH.js +1 -0
  489. package/web/dist/assets/applescript-CCn79oCD.js +1 -0
  490. package/web/dist/assets/ara-4CJ0cIlV.js +1 -0
  491. package/web/dist/assets/asciidoc-DE70LPWp.js +1 -0
  492. package/web/dist/assets/asm-Cmm7eHzH.js +1 -0
  493. package/web/dist/assets/astro-3LtMP0Sq.js +1 -0
  494. package/web/dist/assets/aurora-x-CDeNXAV0.js +1 -0
  495. package/web/dist/assets/awk-BWXHIvNe.js +1 -0
  496. package/web/dist/assets/ayu-dark-DluEY0Gj.js +1 -0
  497. package/web/dist/assets/ayu-light-C3h-C4tm.js +1 -0
  498. package/web/dist/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  499. package/web/dist/assets/ballerina-B7ZEbQpA.js +1 -0
  500. package/web/dist/assets/bat-Bo4NYOV-.js +1 -0
  501. package/web/dist/assets/beancount-D-usSTwE.js +1 -0
  502. package/web/dist/assets/berry-DKpUyyne.js +1 -0
  503. package/web/dist/assets/bibtex-Ci_nEsc7.js +1 -0
  504. package/web/dist/assets/bicep-CUHmPFLl.js +1 -0
  505. package/web/dist/assets/bird2-C6vDhewU.js +1 -0
  506. package/web/dist/assets/blade-llJRbbtR.js +1 -0
  507. package/web/dist/assets/bsl-BkkzgIyY.js +1 -0
  508. package/web/dist/assets/c-Wt1voDr2.js +1 -0
  509. package/web/dist/assets/c3-BFHwR3_K.js +1 -0
  510. package/web/dist/assets/cadence-CQ2zXKGN.js +1 -0
  511. package/web/dist/assets/cairo-DLTphjLi.js +1 -0
  512. package/web/dist/assets/catppuccin-frappe-3VR1Za6u.js +1 -0
  513. package/web/dist/assets/catppuccin-latte-DwIHMF0Q.js +1 -0
  514. package/web/dist/assets/catppuccin-macchiato-DYnBP6_5.js +1 -0
  515. package/web/dist/assets/catppuccin-mocha-DYhrFGRu.js +1 -0
  516. package/web/dist/assets/ccount-CMHa3wy6.js +1 -0
  517. package/web/dist/assets/clarity-SemFz856.js +1 -0
  518. package/web/dist/assets/clojure-DqKBuwfJ.js +1 -0
  519. package/web/dist/assets/cmake-Bj61d0ZC.js +1 -0
  520. package/web/dist/assets/cobol-x_HIyl2P.js +1 -0
  521. package/web/dist/assets/codeowners-C8r90Shi.js +1 -0
  522. package/web/dist/assets/codeql-oeQT6MSM.js +1 -0
  523. package/web/dist/assets/coffee-CThvmt4R.js +1 -0
  524. package/web/dist/assets/common-lisp-Cv5bFMCO.js +1 -0
  525. package/web/dist/assets/coq-BrsZFFmf.js +1 -0
  526. package/web/dist/assets/cpp-NtAeskI3.js +1 -0
  527. package/web/dist/assets/crystal-DNu_sX0G.js +1 -0
  528. package/web/dist/assets/csharp-oqKa8noW.js +1 -0
  529. package/web/dist/assets/css-DJp_X0uY.js +1 -0
  530. package/web/dist/assets/csv-Dx-8-gkx.js +1 -0
  531. package/web/dist/assets/cue-CE9AQfxI.js +1 -0
  532. package/web/dist/assets/cypher-ClKdZ_lG.js +1 -0
  533. package/web/dist/assets/d-qD-0Kul2.js +1 -0
  534. package/web/dist/assets/dark-plus-Cs2F2srj.js +1 -0
  535. package/web/dist/assets/dart-CnvKMtbv.js +1 -0
  536. package/web/dist/assets/dax-BkyTk9wS.js +1 -0
  537. package/web/dist/assets/desktop-Dlh5hvp9.js +1 -0
  538. package/web/dist/assets/diff-woXpYk--.js +1 -0
  539. package/web/dist/assets/docker-IyjqRm3v.js +1 -0
  540. package/web/dist/assets/dotenv-_5a1GRtc.js +1 -0
  541. package/web/dist/assets/dracula-BHWKrbxM.js +1 -0
  542. package/web/dist/assets/dracula-soft-5eyTD99u.js +1 -0
  543. package/web/dist/assets/dream-maker-DW3nJb8Q.js +1 -0
  544. package/web/dist/assets/edge-ozw5tpLl.js +1 -0
  545. package/web/dist/assets/elixir-VhA6FeZt.js +1 -0
  546. package/web/dist/assets/elm-dREJmIFz.js +1 -0
  547. package/web/dist/assets/emacs-lisp-B4R74twV.js +1 -0
  548. package/web/dist/assets/erb-CIg6G69l.js +1 -0
  549. package/web/dist/assets/erlang-Cphh6RMH.js +1 -0
  550. package/web/dist/assets/everforest-dark-sB-x3p7T.js +1 -0
  551. package/web/dist/assets/everforest-light-Df2xbC6M.js +1 -0
  552. package/web/dist/assets/fennel-DQxkIbk2.js +1 -0
  553. package/web/dist/assets/fish-BJitypiv.js +1 -0
  554. package/web/dist/assets/fluent-C03EYrpw.js +1 -0
  555. package/web/dist/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
  556. package/web/dist/assets/fortran-free-form-CYNrtFtB.js +1 -0
  557. package/web/dist/assets/fsharp-D13ZGOAj.js +1 -0
  558. package/web/dist/assets/gdresource-C0sCabJj.js +1 -0
  559. package/web/dist/assets/gdscript-Cp2uCuqX.js +1 -0
  560. package/web/dist/assets/gdshader-CBce3t8t.js +1 -0
  561. package/web/dist/assets/genie-CV2tkWYe.js +1 -0
  562. package/web/dist/assets/gherkin-DExj1W_8.js +1 -0
  563. package/web/dist/assets/git-commit-BSykSTBG.js +1 -0
  564. package/web/dist/assets/git-rebase-B44mJPta.js +1 -0
  565. package/web/dist/assets/github-dark-C-LZuMrd.js +1 -0
  566. package/web/dist/assets/github-dark-default-DXG-b-1a.js +1 -0
  567. package/web/dist/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  568. package/web/dist/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  569. package/web/dist/assets/github-light-EUqPIrTm.js +1 -0
  570. package/web/dist/assets/github-light-default-BXViO-2h.js +1 -0
  571. package/web/dist/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  572. package/web/dist/assets/gleam-CSRkHgEL.js +1 -0
  573. package/web/dist/assets/glimmer-js-vH_gHG0-.js +1 -0
  574. package/web/dist/assets/glimmer-ts--abOzSAQ.js +1 -0
  575. package/web/dist/assets/glsl-Dv5r7kPw.js +1 -0
  576. package/web/dist/assets/gn-ilITqXS6.js +1 -0
  577. package/web/dist/assets/gnuplot-7GGW24-e.js +1 -0
  578. package/web/dist/assets/go-rLFTqkRN.js +1 -0
  579. package/web/dist/assets/graphql-CB4jsw2E.js +1 -0
  580. package/web/dist/assets/groovy-CacY0gHj.js +1 -0
  581. package/web/dist/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  582. package/web/dist/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  583. package/web/dist/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  584. package/web/dist/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  585. package/web/dist/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  586. package/web/dist/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  587. package/web/dist/assets/hack-DvEYX148.js +1 -0
  588. package/web/dist/assets/haml-zE6W3STP.js +1 -0
  589. package/web/dist/assets/handlebars-CzBR2SDs.js +1 -0
  590. package/web/dist/assets/haskell-D8IpX4py.js +1 -0
  591. package/web/dist/assets/haxe-OTjmBuCE.js +1 -0
  592. package/web/dist/assets/hcl-Dh228itO.js +1 -0
  593. package/web/dist/assets/hjson-CxZEssPk.js +1 -0
  594. package/web/dist/assets/hlsl-Cvrh5tZx.js +1 -0
  595. package/web/dist/assets/horizon-CE9ld1lL.js +1 -0
  596. package/web/dist/assets/horizon-bright-DSNQnXHK.js +1 -0
  597. package/web/dist/assets/houston-CsvMBhTu.js +1 -0
  598. package/web/dist/assets/html-C8UlPnhE.js +1 -0
  599. package/web/dist/assets/html-derivative-CY6NRz-J.js +1 -0
  600. package/web/dist/assets/http-Cyd7bS_S.js +1 -0
  601. package/web/dist/assets/hurl-CWPsiEpf.js +1 -0
  602. package/web/dist/assets/hxml-B0Qn7Nwc.js +1 -0
  603. package/web/dist/assets/hy-CZbG8q4J.js +1 -0
  604. package/web/dist/assets/imba-DsUTQ-LC.js +1 -0
  605. package/web/dist/assets/index-BTjK1c82.js +44 -0
  606. package/web/dist/assets/index-D0kg2-9O.css +2 -0
  607. package/web/dist/assets/ini-B5eOa1yu.js +1 -0
  608. package/web/dist/assets/java-CGc3VwQr.js +1 -0
  609. package/web/dist/assets/javascript-CUt1pgmJ.js +1 -0
  610. package/web/dist/assets/jinja-CD-Z-FLd.js +1 -0
  611. package/web/dist/assets/jison-imPNup1l.js +1 -0
  612. package/web/dist/assets/json-Bg9ijW3F.js +1 -0
  613. package/web/dist/assets/json5-BR5RXkoi.js +1 -0
  614. package/web/dist/assets/jsonc-CYpm1nAK.js +1 -0
  615. package/web/dist/assets/jsonl-CmCQp5Yx.js +1 -0
  616. package/web/dist/assets/jsonnet-CJTPZ8u_.js +1 -0
  617. package/web/dist/assets/jssm-DXw9l8Rf.js +1 -0
  618. package/web/dist/assets/jsx-CY6oMTks.js +1 -0
  619. package/web/dist/assets/julia-Dc3O-irA.js +1 -0
  620. package/web/dist/assets/just-BhOq_Kbv.js +1 -0
  621. package/web/dist/assets/kanagawa-dragon-CXtmUGW6.js +1 -0
  622. package/web/dist/assets/kanagawa-lotus-BN08jTvb.js +1 -0
  623. package/web/dist/assets/kanagawa-wave-CTweb8Dz.js +1 -0
  624. package/web/dist/assets/kdl-CsD5j6eV.js +1 -0
  625. package/web/dist/assets/kilo-CvN-tXHs.js +1 -0
  626. package/web/dist/assets/kotlin-DhhofPvG.js +1 -0
  627. package/web/dist/assets/kusto-C7mF5XQf.js +1 -0
  628. package/web/dist/assets/laserwave-C_8bwKvT.js +1 -0
  629. package/web/dist/assets/latex-Cu4Y1d5w.js +1 -0
  630. package/web/dist/assets/lean-CewbzKMR.js +1 -0
  631. package/web/dist/assets/less-DVTAwKKz.js +1 -0
  632. package/web/dist/assets/light-plus-DVQuIRkW.js +1 -0
  633. package/web/dist/assets/liquid-CvXMrjlQ.js +1 -0
  634. package/web/dist/assets/llvm-Cm23YOpf.js +1 -0
  635. package/web/dist/assets/log-BNLmms1o.js +1 -0
  636. package/web/dist/assets/logo-Cluzi2Zq.js +1 -0
  637. package/web/dist/assets/lua-BjLEUjKY.js +1 -0
  638. package/web/dist/assets/luau-FMPmPwt6.js +1 -0
  639. package/web/dist/assets/make-Dixweg8N.js +1 -0
  640. package/web/dist/assets/markdown-BYOwaDjH.js +1 -0
  641. package/web/dist/assets/marko-DvhNOisQ.js +1 -0
  642. package/web/dist/assets/material-theme-Bm3Qr25_.js +1 -0
  643. package/web/dist/assets/material-theme-darker-2IIEA8gg.js +1 -0
  644. package/web/dist/assets/material-theme-lighter-uhdI0v04.js +1 -0
  645. package/web/dist/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  646. package/web/dist/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  647. package/web/dist/assets/matlab-D7qyCx1q.js +1 -0
  648. package/web/dist/assets/mdc-Bm9TpL1X.js +1 -0
  649. package/web/dist/assets/mdx-DQZ5AkYe.js +1 -0
  650. package/web/dist/assets/mermaid-Bk4SNUv9.js +1 -0
  651. package/web/dist/assets/min-dark-BSWPekZh.js +1 -0
  652. package/web/dist/assets/min-light-DDpmG2fV.js +1 -0
  653. package/web/dist/assets/mipsasm-BMqwQI7S.js +1 -0
  654. package/web/dist/assets/mojo-BgCJLMeH.js +1 -0
  655. package/web/dist/assets/monokai-CdkpiU2Y.js +1 -0
  656. package/web/dist/assets/moonbit-CaWjb8XO.js +1 -0
  657. package/web/dist/assets/move-B1IS1UjX.js +1 -0
  658. package/web/dist/assets/narrat-_X_XdTYD.js +1 -0
  659. package/web/dist/assets/nextflow-BJtWHP5T.js +1 -0
  660. package/web/dist/assets/nextflow-groovy-DJMQeKeT.js +1 -0
  661. package/web/dist/assets/nginx-Bhc82uuv.js +1 -0
  662. package/web/dist/assets/night-owl-DhmEMT88.js +1 -0
  663. package/web/dist/assets/night-owl-light-eJ-hLW7d.js +1 -0
  664. package/web/dist/assets/nim-DXTVBFnF.js +1 -0
  665. package/web/dist/assets/nix-IvuFDN5E.js +1 -0
  666. package/web/dist/assets/nord-Cb4Vim4T.js +1 -0
  667. package/web/dist/assets/nushell-DcLAeLz5.js +1 -0
  668. package/web/dist/assets/objective-c-D1A_Heim.js +1 -0
  669. package/web/dist/assets/objective-cpp-BsSzOQcm.js +1 -0
  670. package/web/dist/assets/ocaml-O90oeIOV.js +1 -0
  671. package/web/dist/assets/odin-B1RWQWA5.js +1 -0
  672. package/web/dist/assets/one-dark-pro-CLwyXe_n.js +1 -0
  673. package/web/dist/assets/one-light-D7Lr4KcI.js +1 -0
  674. package/web/dist/assets/openscad-BUDT5pXO.js +1 -0
  675. package/web/dist/assets/pascal-4ZHwLPI5.js +1 -0
  676. package/web/dist/assets/perl-C7veXV9z.js +1 -0
  677. package/web/dist/assets/php-BRiuMnnr.js +1 -0
  678. package/web/dist/assets/pierre-dark-CpLgRqie.js +1 -0
  679. package/web/dist/assets/pierre-dark-protanopia-deuteranopia-B35FxJx-.js +1 -0
  680. package/web/dist/assets/pierre-dark-soft-kZQmAZld.js +1 -0
  681. package/web/dist/assets/pierre-dark-tritanopia-CpjhbsIL.js +1 -0
  682. package/web/dist/assets/pierre-dark-vibrant-CpQYzh95.js +1 -0
  683. package/web/dist/assets/pierre-light-CoaEpmwp.js +1 -0
  684. package/web/dist/assets/pierre-light-protanopia-deuteranopia-0fSaH845.js +1 -0
  685. package/web/dist/assets/pierre-light-soft-lWLdNTOI.js +1 -0
  686. package/web/dist/assets/pierre-light-tritanopia-CEbqgOJL.js +1 -0
  687. package/web/dist/assets/pierre-light-vibrant-D80Fkn33.js +1 -0
  688. package/web/dist/assets/pkl-ot-7Btpt.js +1 -0
  689. package/web/dist/assets/plastic-DQwYfKfQ.js +1 -0
  690. package/web/dist/assets/plsql-DGHpHOYJ.js +1 -0
  691. package/web/dist/assets/po-BiJDBrnU.js +1 -0
  692. package/web/dist/assets/poimandres-DRFjx7u4.js +1 -0
  693. package/web/dist/assets/polar-C7UOKdEL.js +1 -0
  694. package/web/dist/assets/postcss-BXeXVLqQ.js +1 -0
  695. package/web/dist/assets/powerquery-DNMTfnFr.js +1 -0
  696. package/web/dist/assets/powershell-DshXNtvi.js +1 -0
  697. package/web/dist/assets/prisma-BsRQq5mF.js +1 -0
  698. package/web/dist/assets/prolog-iXnhIJG7.js +1 -0
  699. package/web/dist/assets/proto-DB4EqR-F.js +1 -0
  700. package/web/dist/assets/pug-C5hz5LQ7.js +1 -0
  701. package/web/dist/assets/puppet-CDv2pdJW.js +1 -0
  702. package/web/dist/assets/purescript-9MfHhQsQ.js +1 -0
  703. package/web/dist/assets/python-gzcpVVnB.js +1 -0
  704. package/web/dist/assets/qml-BdUV3aTS.js +1 -0
  705. package/web/dist/assets/qmldir-DCQb3MpD.js +1 -0
  706. package/web/dist/assets/qss-Fe1Jh2GI.js +1 -0
  707. package/web/dist/assets/r-8R7vtdQc.js +1 -0
  708. package/web/dist/assets/racket-DcIDlBhZ.js +1 -0
  709. package/web/dist/assets/raku-B3gFvitq.js +1 -0
  710. package/web/dist/assets/razor-DRL52XO2.js +1 -0
  711. package/web/dist/assets/red-CJ3rzSJv.js +1 -0
  712. package/web/dist/assets/reg-CRGYupPL.js +1 -0
  713. package/web/dist/assets/regexp-Omp9DhTb.js +1 -0
  714. package/web/dist/assets/rel-BtDbiS_P.js +1 -0
  715. package/web/dist/assets/reviews-CUXp47M8.js +1 -0
  716. package/web/dist/assets/riscv-Ckw8ddFX.js +1 -0
  717. package/web/dist/assets/rolldown-runtime-QTnfLwEv.js +1 -0
  718. package/web/dist/assets/ron-VUp2lXgN.js +1 -0
  719. package/web/dist/assets/rose-pine-BthvhNj6.js +1 -0
  720. package/web/dist/assets/rose-pine-dawn-Dg85fqjY.js +1 -0
  721. package/web/dist/assets/rose-pine-moon-hon4tzzS.js +1 -0
  722. package/web/dist/assets/rosmsg-CAekHB0j.js +1 -0
  723. package/web/dist/assets/rst-BHX71KW9.js +1 -0
  724. package/web/dist/assets/ruby-B--HzjGU.js +1 -0
  725. package/web/dist/assets/rust-Cfkwpbl8.js +1 -0
  726. package/web/dist/assets/sas-DrLaYOK_.js +1 -0
  727. package/web/dist/assets/sass-DXrisJhu.js +1 -0
  728. package/web/dist/assets/scala-DKOlJaKm.js +1 -0
  729. package/web/dist/assets/scheme-DQCgrYNe.js +1 -0
  730. package/web/dist/assets/scss-DdSxiZKl.js +1 -0
  731. package/web/dist/assets/sdbl-bTVj8UrX.js +1 -0
  732. package/web/dist/assets/shaderlab-TOUzSsQk.js +1 -0
  733. package/web/dist/assets/shellscript-DwcUjJBL.js +1 -0
  734. package/web/dist/assets/shellsession-CPZkydE6.js +1 -0
  735. package/web/dist/assets/slack-dark-DnToyrRv.js +1 -0
  736. package/web/dist/assets/slack-ochin-B2OO5cIa.js +1 -0
  737. package/web/dist/assets/smalltalk-B16xEiuN.js +1 -0
  738. package/web/dist/assets/snazzy-light-4G7pJPwS.js +1 -0
  739. package/web/dist/assets/solarized-dark-DV17i1UV.js +1 -0
  740. package/web/dist/assets/solarized-light-DSh2HLQt.js +1 -0
  741. package/web/dist/assets/solidity-CKzVLygQ.js +1 -0
  742. package/web/dist/assets/soy-Br5FhD7c.js +1 -0
  743. package/web/dist/assets/sparql-D_iOobhT.js +1 -0
  744. package/web/dist/assets/splunk-BC2Px7Mm.js +1 -0
  745. package/web/dist/assets/sql-DNssxck8.js +1 -0
  746. package/web/dist/assets/ssh-config-BgfXC-Er.js +1 -0
  747. package/web/dist/assets/stata-DXn1tqOr.js +1 -0
  748. package/web/dist/assets/stylus-B6D30XZt.js +1 -0
  749. package/web/dist/assets/surfaces-Dlg1xLbg.js +1 -0
  750. package/web/dist/assets/surrealql-IeLNQw0f.js +1 -0
  751. package/web/dist/assets/svelte-DOdLCIlh.js +1 -0
  752. package/web/dist/assets/swift-DonLKvLd.js +1 -0
  753. package/web/dist/assets/synthwave-84-nFMaYfgc.js +1 -0
  754. package/web/dist/assets/system-verilog-DJ5XKQeo.js +1 -0
  755. package/web/dist/assets/systemd-BxMlprV5.js +1 -0
  756. package/web/dist/assets/talonscript-CohzipZa.js +1 -0
  757. package/web/dist/assets/tasl-DMoTqEGO.js +1 -0
  758. package/web/dist/assets/tcl-CZd0xW_V.js +1 -0
  759. package/web/dist/assets/templ-CIwIngms.js +1 -0
  760. package/web/dist/assets/terraform-DswuEJGm.js +1 -0
  761. package/web/dist/assets/tex-D8QMumu5.js +1 -0
  762. package/web/dist/assets/tokyo-night-oM2G3aXe.js +1 -0
  763. package/web/dist/assets/toml-CcmNWLt0.js +1 -0
  764. package/web/dist/assets/ts-tags-BMVY4q-l.js +1 -0
  765. package/web/dist/assets/tsv-sltzmVWM.js +1 -0
  766. package/web/dist/assets/tsx-5Eka4NBX.js +1 -0
  767. package/web/dist/assets/turtle-ByJddavk.js +1 -0
  768. package/web/dist/assets/twig-C8o_5mgw.js +1 -0
  769. package/web/dist/assets/typescript-DOu2WMV5.js +1 -0
  770. package/web/dist/assets/typespec-BRdr0IET.js +1 -0
  771. package/web/dist/assets/typst-DI99ib-x.js +1 -0
  772. package/web/dist/assets/v-DETTlOr0.js +1 -0
  773. package/web/dist/assets/vala-zf12oZj6.js +1 -0
  774. package/web/dist/assets/vb-Djn5o6TS.js +1 -0
  775. package/web/dist/assets/verilog-CiiDBU1e.js +1 -0
  776. package/web/dist/assets/vesper-DdrHHSXu.js +1 -0
  777. package/web/dist/assets/vhdl-BroJfC0k.js +1 -0
  778. package/web/dist/assets/viml-DvXPmvsu.js +1 -0
  779. package/web/dist/assets/vitesse-black-fwtXNY1n.js +1 -0
  780. package/web/dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
  781. package/web/dist/assets/vitesse-light-VbXTXTou.js +1 -0
  782. package/web/dist/assets/vue-BU18DNDL.js +1 -0
  783. package/web/dist/assets/vue-html-BeluIYX0.js +1 -0
  784. package/web/dist/assets/vue-vine-DGUAbOCX.js +1 -0
  785. package/web/dist/assets/vyper-CgoNMtux.js +1 -0
  786. package/web/dist/assets/wasm-BnjxR4X6.js +1 -0
  787. package/web/dist/assets/wasm-ByWQv1Qj.js +1 -0
  788. package/web/dist/assets/wenyan-C8pVoKbM.js +1 -0
  789. package/web/dist/assets/wgsl-BsKzXJz4.js +1 -0
  790. package/web/dist/assets/wikitext-ClFFjSW2.js +1 -0
  791. package/web/dist/assets/wit-DdvCle-K.js +1 -0
  792. package/web/dist/assets/wolfram-DLL8P-h_.js +1 -0
  793. package/web/dist/assets/worker-05jVtNwz.js +160 -0
  794. package/web/dist/assets/xml-DIqSwXR3.js +1 -0
  795. package/web/dist/assets/xsl-Ct_-YIAy.js +1 -0
  796. package/web/dist/assets/yaml-DTtCYNlS.js +1 -0
  797. package/web/dist/assets/zenscript-BnlCZFoB.js +1 -0
  798. package/web/dist/assets/zig-CMLA9XwU.js +1 -0
  799. package/web/dist/icons/neondeck-icon-192.png +0 -0
  800. package/web/dist/icons/neondeck-icon-512.png +0 -0
  801. package/web/dist/icons/neondeck-icon.svg +41 -0
  802. package/web/dist/icons/neondeck-maskable-512.png +0 -0
  803. package/web/dist/index.html +27 -0
  804. package/web/dist/manifest.webmanifest +28 -0
@@ -0,0 +1,1446 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import * as v from 'valibot';
3
+ import { isUniqueConstraintError, openDb } from '../../lib/sqlite';
4
+ import type { RuntimePaths } from '../../runtime-home';
5
+ import { addWorkflowSummary } from '../app-state';
6
+ import {
7
+ encodePathSegment,
8
+ githubFetch,
9
+ githubGraphqlFetch,
10
+ nextLink,
11
+ } from './client';
12
+ import { fetchPullRequestReviewThread } from './comments';
13
+ import { GitHubApiError, errorMessage } from './errors';
14
+ import { fetchPullRequestDetail } from './pull-requests';
15
+ import {
16
+ githubPullRequestReviewApiItemSchema,
17
+ githubPullRequestReviewCreatedApiResponseSchema,
18
+ } from './schemas';
19
+ import type {
20
+ GitHubPullRequestRequestedChangesState,
21
+ GitHubPullRequestReview,
22
+ GitHubPullRequestReviewApiItem,
23
+ GitHubPullRequestReviewThread,
24
+ GitHubSubmittedPullRequestReview,
25
+ } from './schemas';
26
+
27
+ export type GitHubPrReviewVerdict = 'comment' | 'approve' | 'request-changes';
28
+
29
+ export type GitHubPrReviewDraftStatus = 'draft' | 'submitted' | 'discarded';
30
+
31
+ export type GitHubPrReviewDraftCommentSide = 'RIGHT' | 'LEFT';
32
+
33
+ export type GitHubPrReviewDraftCommentOrigin = 'human' | 'neon';
34
+
35
+ export type GitHubPrReviewDraftComment = {
36
+ id: string;
37
+ draftId: string;
38
+ path: string;
39
+ side: GitHubPrReviewDraftCommentSide;
40
+ line: number;
41
+ startLine: number | null;
42
+ startSide: GitHubPrReviewDraftCommentSide | null;
43
+ body: string;
44
+ origin: GitHubPrReviewDraftCommentOrigin;
45
+ createdAt: string;
46
+ updatedAt: string;
47
+ };
48
+
49
+ export type GitHubPrReviewNeonSeedSeverity =
50
+ 'critical' | 'major' | 'minor' | 'nit';
51
+
52
+ export type GitHubPrReviewNeonSeedOutcome = 'submitted' | 'skipped' | 'deleted';
53
+
54
+ export type GitHubPrReviewNeonSeededComment = {
55
+ commentId: string;
56
+ draftId: string;
57
+ repo: string;
58
+ prNumber: number;
59
+ headSha: string;
60
+ path: string;
61
+ side: GitHubPrReviewDraftCommentSide;
62
+ line: number;
63
+ startLine: number | null;
64
+ startSide: GitHubPrReviewDraftCommentSide | null;
65
+ severity: GitHubPrReviewNeonSeedSeverity;
66
+ summary: string;
67
+ source: string;
68
+ outcome: GitHubPrReviewNeonSeedOutcome | null;
69
+ outcomeAt: string | null;
70
+ seededAt: string;
71
+ };
72
+
73
+ export type GitHubPrReviewDraft = {
74
+ id: string;
75
+ repo: string;
76
+ prNumber: number;
77
+ headSha: string;
78
+ verdict: GitHubPrReviewVerdict | null;
79
+ body: string | null;
80
+ status: GitHubPrReviewDraftStatus;
81
+ createdAt: string;
82
+ updatedAt: string;
83
+ submittedAt: string | null;
84
+ comments: GitHubPrReviewDraftComment[];
85
+ };
86
+
87
+ export type GitHubPrReviewSubmitFailure = {
88
+ code:
89
+ | 'invalid-review'
90
+ | 'draft-not-found'
91
+ | 'stale-draft'
92
+ | 'insufficient-scope'
93
+ | 'github-review-submit-failed';
94
+ message: string;
95
+ failingCommentIds?: string[];
96
+ requires?: string[];
97
+ };
98
+
99
+ export class GitHubPrReviewSubmitError extends Error {
100
+ constructor(readonly failure: GitHubPrReviewSubmitFailure) {
101
+ super(failure.message);
102
+ this.name = 'GitHubPrReviewSubmitError';
103
+ }
104
+ }
105
+
106
+ const reviewVerdictSchema = v.picklist([
107
+ 'comment',
108
+ 'approve',
109
+ 'request-changes',
110
+ ]);
111
+ const reviewCommentSideSchema = v.picklist(['RIGHT', 'LEFT']);
112
+ const reviewCommentOriginSchema = v.picklist(['human', 'neon']);
113
+ const reviewNeonSeedSeveritySchema = v.picklist([
114
+ 'critical',
115
+ 'major',
116
+ 'minor',
117
+ 'nit',
118
+ ]);
119
+ const reviewNeonSeedOutcomeSchema = v.picklist([
120
+ 'submitted',
121
+ 'skipped',
122
+ 'deleted',
123
+ ]);
124
+ const draftStatusSchema = v.picklist(['draft', 'submitted', 'discarded']);
125
+
126
+ const draftRowSchema = v.object({
127
+ id: v.string(),
128
+ repo: v.string(),
129
+ pr_number: v.number(),
130
+ head_sha: v.string(),
131
+ verdict: v.nullable(reviewVerdictSchema),
132
+ body: v.nullable(v.string()),
133
+ status: draftStatusSchema,
134
+ created_at: v.string(),
135
+ updated_at: v.string(),
136
+ submitted_at: v.nullable(v.string()),
137
+ });
138
+
139
+ const draftCommentRowSchema = v.object({
140
+ id: v.string(),
141
+ draft_id: v.string(),
142
+ path: v.string(),
143
+ side: reviewCommentSideSchema,
144
+ line: v.number(),
145
+ start_line: v.nullable(v.number()),
146
+ start_side: v.nullable(reviewCommentSideSchema),
147
+ body: v.string(),
148
+ origin: reviewCommentOriginSchema,
149
+ created_at: v.string(),
150
+ updated_at: v.string(),
151
+ });
152
+
153
+ const neonSeedRowSchema = v.object({
154
+ comment_id: v.string(),
155
+ draft_id: v.string(),
156
+ repo: v.string(),
157
+ pr_number: v.number(),
158
+ head_sha: v.string(),
159
+ path: v.string(),
160
+ side: reviewCommentSideSchema,
161
+ line: v.number(),
162
+ start_line: v.nullable(v.number()),
163
+ start_side: v.nullable(reviewCommentSideSchema),
164
+ severity: reviewNeonSeedSeveritySchema,
165
+ summary: v.string(),
166
+ source: v.string(),
167
+ outcome: v.nullable(reviewNeonSeedOutcomeSchema),
168
+ outcome_at: v.nullable(v.string()),
169
+ seeded_at: v.string(),
170
+ });
171
+
172
+ const replyReviewThreadMutation = `
173
+ mutation NeondeckReplyPullRequestReviewThread($threadId: ID!, $body: String!) {
174
+ addPullRequestReviewThreadReply(input: { pullRequestReviewThreadId: $threadId, body: $body }) {
175
+ comment {
176
+ id
177
+ }
178
+ }
179
+ }
180
+ `;
181
+
182
+ const resolveReviewThreadMutation = `
183
+ mutation NeondeckResolvePullRequestReviewThread($threadId: ID!) {
184
+ resolveReviewThread(input: { threadId: $threadId }) {
185
+ thread {
186
+ id
187
+ }
188
+ }
189
+ }
190
+ `;
191
+
192
+ const unresolveReviewThreadMutation = `
193
+ mutation NeondeckUnresolvePullRequestReviewThread($threadId: ID!) {
194
+ unresolveReviewThread(input: { threadId: $threadId }) {
195
+ thread {
196
+ id
197
+ }
198
+ }
199
+ }
200
+ `;
201
+
202
+ export function readLivePrReviewDraft(options: {
203
+ databasePath: string;
204
+ repo: string;
205
+ prNumber: number;
206
+ }): GitHubPrReviewDraft | null {
207
+ const database = openDb(options.databasePath);
208
+ try {
209
+ const row = database
210
+ .prepare(
211
+ `
212
+ SELECT *
213
+ FROM pr_review_drafts
214
+ WHERE repo = ?
215
+ AND pr_number = ?
216
+ AND status = 'draft'
217
+ ORDER BY updated_at DESC
218
+ LIMIT 1;
219
+ `,
220
+ )
221
+ .get(options.repo, options.prNumber);
222
+ return row ? readDraftWithComments(database, row) : null;
223
+ } finally {
224
+ database.close();
225
+ }
226
+ }
227
+
228
+ export function readPrReviewDraft(options: {
229
+ databasePath: string;
230
+ draftId: string;
231
+ }): GitHubPrReviewDraft | null {
232
+ const database = openDb(options.databasePath);
233
+ try {
234
+ const row = database
235
+ .prepare('SELECT * FROM pr_review_drafts WHERE id = ?;')
236
+ .get(options.draftId);
237
+ return row ? readDraftWithComments(database, row) : null;
238
+ } finally {
239
+ database.close();
240
+ }
241
+ }
242
+
243
+ export function readPrReviewDraftForComment(options: {
244
+ databasePath: string;
245
+ commentId: string;
246
+ }): GitHubPrReviewDraft | null {
247
+ const database = openDb(options.databasePath);
248
+ try {
249
+ const row = database
250
+ .prepare('SELECT draft_id FROM pr_review_draft_comments WHERE id = ?;')
251
+ .get(options.commentId) as { draft_id?: unknown } | undefined;
252
+ const draftId = typeof row?.draft_id === 'string' ? row.draft_id : null;
253
+ return draftId ? readDraftWithCommentsById(database, draftId) : null;
254
+ } finally {
255
+ database.close();
256
+ }
257
+ }
258
+
259
+ export function upsertPrReviewDraft(options: {
260
+ databasePath: string;
261
+ repo: string;
262
+ prNumber: number;
263
+ headSha: string;
264
+ verdict?: GitHubPrReviewVerdict | null;
265
+ body?: string | null;
266
+ reanchorHeadSha?: boolean;
267
+ }): GitHubPrReviewDraft {
268
+ const database = openDb(options.databasePath);
269
+ const now = new Date().toISOString();
270
+ const body = normalizeNullableBody(options.body);
271
+ try {
272
+ const existing = database
273
+ .prepare(
274
+ `
275
+ SELECT *
276
+ FROM pr_review_drafts
277
+ WHERE repo = ?
278
+ AND pr_number = ?
279
+ AND status = 'draft'
280
+ LIMIT 1;
281
+ `,
282
+ )
283
+ .get(options.repo, options.prNumber);
284
+
285
+ if (existing) {
286
+ return updateExistingReviewDraft(database, existing, options, now);
287
+ }
288
+
289
+ const id = randomUUID();
290
+ try {
291
+ database
292
+ .prepare(
293
+ `
294
+ INSERT INTO pr_review_drafts (
295
+ id,
296
+ repo,
297
+ pr_number,
298
+ head_sha,
299
+ verdict,
300
+ body,
301
+ status,
302
+ created_at,
303
+ updated_at
304
+ )
305
+ VALUES (?, ?, ?, ?, ?, ?, 'draft', ?, ?);
306
+ `,
307
+ )
308
+ .run(
309
+ id,
310
+ options.repo,
311
+ options.prNumber,
312
+ options.headSha,
313
+ options.verdict ?? null,
314
+ body,
315
+ now,
316
+ now,
317
+ );
318
+ } catch (error) {
319
+ if (!isUniqueConstraintError(error)) throw error;
320
+ const winner = database
321
+ .prepare(
322
+ `
323
+ SELECT *
324
+ FROM pr_review_drafts
325
+ WHERE repo = ?
326
+ AND pr_number = ?
327
+ AND status = 'draft'
328
+ LIMIT 1;
329
+ `,
330
+ )
331
+ .get(options.repo, options.prNumber);
332
+ if (!winner) throw error;
333
+ return updateExistingReviewDraft(database, winner, options, now);
334
+ }
335
+ return readDraftWithCommentsById(database, id);
336
+ } finally {
337
+ database.close();
338
+ }
339
+ }
340
+
341
+ export function discardPrReviewDraft(options: {
342
+ databasePath: string;
343
+ repo: string;
344
+ prNumber: number;
345
+ }): GitHubPrReviewDraft | null {
346
+ const database = openDb(options.databasePath);
347
+ const now = new Date().toISOString();
348
+ try {
349
+ const row = database
350
+ .prepare(
351
+ `
352
+ SELECT *
353
+ FROM pr_review_drafts
354
+ WHERE repo = ?
355
+ AND pr_number = ?
356
+ AND status = 'draft'
357
+ LIMIT 1;
358
+ `,
359
+ )
360
+ .get(options.repo, options.prNumber);
361
+ if (!row) return null;
362
+ const draft = readDraftRow(row);
363
+ database
364
+ .prepare(
365
+ `
366
+ UPDATE pr_review_drafts
367
+ SET status = 'discarded',
368
+ updated_at = ?
369
+ WHERE id = ?;
370
+ `,
371
+ )
372
+ .run(now, draft.id);
373
+ return readDraftWithCommentsById(database, draft.id);
374
+ } finally {
375
+ database.close();
376
+ }
377
+ }
378
+
379
+ export function addPrReviewDraftComment(options: {
380
+ databasePath: string;
381
+ draftId: string;
382
+ path: string;
383
+ side: GitHubPrReviewDraftCommentSide;
384
+ line: number;
385
+ startLine?: number | null;
386
+ startSide?: GitHubPrReviewDraftCommentSide | null;
387
+ body: string;
388
+ origin?: GitHubPrReviewDraftCommentOrigin;
389
+ }): GitHubPrReviewDraft {
390
+ const database = openDb(options.databasePath);
391
+ const now = new Date().toISOString();
392
+ try {
393
+ assertDraftIsLive(database, options.draftId);
394
+ assertValidReviewCommentAnchor(options);
395
+ database
396
+ .prepare(
397
+ `
398
+ INSERT INTO pr_review_draft_comments (
399
+ id,
400
+ draft_id,
401
+ path,
402
+ side,
403
+ line,
404
+ start_line,
405
+ start_side,
406
+ body,
407
+ origin,
408
+ created_at,
409
+ updated_at
410
+ )
411
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
412
+ `,
413
+ )
414
+ .run(
415
+ randomUUID(),
416
+ options.draftId,
417
+ options.path,
418
+ options.side,
419
+ options.line,
420
+ options.startLine ?? null,
421
+ options.startSide ?? null,
422
+ options.body.trim(),
423
+ options.origin ?? 'human',
424
+ now,
425
+ now,
426
+ );
427
+ touchDraft(database, options.draftId, now);
428
+ return readDraftWithCommentsById(database, options.draftId);
429
+ } finally {
430
+ database.close();
431
+ }
432
+ }
433
+
434
+ export function updatePrReviewDraftComment(options: {
435
+ databasePath: string;
436
+ commentId: string;
437
+ body: string;
438
+ path?: string;
439
+ side?: GitHubPrReviewDraftCommentSide;
440
+ line?: number;
441
+ startLine?: number | null;
442
+ startSide?: GitHubPrReviewDraftCommentSide | null;
443
+ }): GitHubPrReviewDraft {
444
+ const database = openDb(options.databasePath);
445
+ const now = new Date().toISOString();
446
+ try {
447
+ const row = database
448
+ .prepare('SELECT draft_id FROM pr_review_draft_comments WHERE id = ?;')
449
+ .get(options.commentId) as { draft_id?: unknown } | undefined;
450
+ const draftId = typeof row?.draft_id === 'string' ? row.draft_id : null;
451
+ if (!draftId) throw new Error('Review draft comment not found.');
452
+ assertDraftIsLive(database, draftId);
453
+ const existing = database
454
+ .prepare(
455
+ `
456
+ SELECT path, side, line, start_line, start_side
457
+ FROM pr_review_draft_comments
458
+ WHERE id = ?;
459
+ `,
460
+ )
461
+ .get(options.commentId) as
462
+ | {
463
+ path: string;
464
+ side: GitHubPrReviewDraftCommentSide;
465
+ line: number;
466
+ start_line: number | null;
467
+ start_side: GitHubPrReviewDraftCommentSide | null;
468
+ }
469
+ | undefined;
470
+ if (!existing) throw new Error('Review draft comment not found.');
471
+ const nextAnchor = {
472
+ path: options.path ?? existing.path,
473
+ side: options.side ?? existing.side,
474
+ line: options.line ?? existing.line,
475
+ startLine:
476
+ 'startLine' in options
477
+ ? (options.startLine ?? null)
478
+ : existing.start_line,
479
+ startSide:
480
+ 'startSide' in options
481
+ ? (options.startSide ?? null)
482
+ : existing.start_side,
483
+ };
484
+ assertValidReviewCommentAnchor(nextAnchor);
485
+ database
486
+ .prepare(
487
+ `
488
+ UPDATE pr_review_draft_comments
489
+ SET path = ?,
490
+ side = ?,
491
+ line = ?,
492
+ start_line = ?,
493
+ start_side = ?,
494
+ body = ?,
495
+ origin = 'human',
496
+ updated_at = ?
497
+ WHERE id = ?;
498
+ `,
499
+ )
500
+ .run(
501
+ nextAnchor.path,
502
+ nextAnchor.side,
503
+ nextAnchor.line,
504
+ nextAnchor.startLine,
505
+ nextAnchor.startSide,
506
+ options.body.trim(),
507
+ now,
508
+ options.commentId,
509
+ );
510
+ touchDraft(database, draftId, now);
511
+ return readDraftWithCommentsById(database, draftId);
512
+ } finally {
513
+ database.close();
514
+ }
515
+ }
516
+
517
+ export function clearPrReviewNeonDraftComments(options: {
518
+ databasePath: string;
519
+ draftId: string;
520
+ }): GitHubPrReviewDraft {
521
+ const database = openDb(options.databasePath);
522
+ const now = new Date().toISOString();
523
+ try {
524
+ assertDraftIsLive(database, options.draftId);
525
+ const result = database
526
+ .prepare(
527
+ `
528
+ DELETE FROM pr_review_draft_comments
529
+ WHERE draft_id = ?
530
+ AND origin = 'neon';
531
+ `,
532
+ )
533
+ .run(options.draftId);
534
+ if (result.changes > 0) touchDraft(database, options.draftId, now);
535
+ return readDraftWithCommentsById(database, options.draftId);
536
+ } finally {
537
+ database.close();
538
+ }
539
+ }
540
+
541
+ export function deletePrReviewDraftComment(options: {
542
+ databasePath: string;
543
+ commentId: string;
544
+ }): GitHubPrReviewDraft {
545
+ const database = openDb(options.databasePath);
546
+ const now = new Date().toISOString();
547
+ try {
548
+ const row = database
549
+ .prepare('SELECT draft_id FROM pr_review_draft_comments WHERE id = ?;')
550
+ .get(options.commentId) as { draft_id?: unknown } | undefined;
551
+ const draftId = typeof row?.draft_id === 'string' ? row.draft_id : null;
552
+ if (!draftId) throw new Error('Review draft comment not found.');
553
+ assertDraftIsLive(database, draftId);
554
+ database
555
+ .prepare('DELETE FROM pr_review_draft_comments WHERE id = ?;')
556
+ .run(options.commentId);
557
+ touchDraft(database, draftId, now);
558
+ return readDraftWithCommentsById(database, draftId);
559
+ } finally {
560
+ database.close();
561
+ }
562
+ }
563
+
564
+ export function recordPrReviewNeonSeed(options: {
565
+ databasePath: string;
566
+ draft: GitHubPrReviewDraft;
567
+ comment: GitHubPrReviewDraftComment;
568
+ severity: GitHubPrReviewNeonSeedSeverity;
569
+ summary: string;
570
+ source: string;
571
+ }): GitHubPrReviewNeonSeededComment {
572
+ const database = openDb(options.databasePath);
573
+ const now = new Date().toISOString();
574
+ try {
575
+ database
576
+ .prepare(
577
+ `
578
+ INSERT OR IGNORE INTO pr_review_neon_seeded_comments (
579
+ comment_id,
580
+ draft_id,
581
+ repo,
582
+ pr_number,
583
+ head_sha,
584
+ path,
585
+ side,
586
+ line,
587
+ start_line,
588
+ start_side,
589
+ severity,
590
+ summary,
591
+ source,
592
+ seeded_at
593
+ )
594
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
595
+ `,
596
+ )
597
+ .run(
598
+ options.comment.id,
599
+ options.draft.id,
600
+ options.draft.repo,
601
+ options.draft.prNumber,
602
+ options.draft.headSha,
603
+ options.comment.path,
604
+ options.comment.side,
605
+ options.comment.line,
606
+ options.comment.startLine,
607
+ options.comment.startSide,
608
+ options.severity,
609
+ truncateSeedSummary(options.summary),
610
+ options.source,
611
+ now,
612
+ );
613
+ return readNeonSeedByCommentId(database, options.comment.id);
614
+ } finally {
615
+ database.close();
616
+ }
617
+ }
618
+
619
+ export function deletePrReviewNeonSeedsForComments(options: {
620
+ databasePath: string;
621
+ commentIds: string[];
622
+ }) {
623
+ const commentIds = [...new Set(options.commentIds.filter(Boolean))];
624
+ if (commentIds.length === 0) return 0;
625
+ const database = openDb(options.databasePath);
626
+ try {
627
+ const placeholders = commentIds.map(() => '?').join(', ');
628
+ const result = database
629
+ .prepare(
630
+ `
631
+ DELETE FROM pr_review_neon_seeded_comments
632
+ WHERE comment_id IN (${placeholders});
633
+ `,
634
+ )
635
+ .run(...commentIds);
636
+ return Number(result.changes);
637
+ } finally {
638
+ database.close();
639
+ }
640
+ }
641
+
642
+ export async function submitPullRequestReview(options: {
643
+ token: string;
644
+ owner: string;
645
+ repo: string;
646
+ number: number;
647
+ databasePath: string;
648
+ paths: RuntimePaths;
649
+ draftId: string;
650
+ headSha: string;
651
+ commentIds?: string[];
652
+ fetchHeadSha?: (options: {
653
+ token: string;
654
+ owner: string;
655
+ repo: string;
656
+ number: number;
657
+ }) => Promise<string | null | undefined>;
658
+ }): Promise<{
659
+ draft: GitHubPrReviewDraft;
660
+ review: GitHubSubmittedPullRequestReview;
661
+ }> {
662
+ const draft = readPrReviewDraft({
663
+ databasePath: options.databasePath,
664
+ draftId: options.draftId,
665
+ });
666
+ if (!draft || draft.status !== 'draft') {
667
+ throw new GitHubPrReviewSubmitError({
668
+ code: 'draft-not-found',
669
+ message: 'Review draft was not found.',
670
+ });
671
+ }
672
+
673
+ const expectedRepo = `${options.owner}/${options.repo}`;
674
+ if (draft.repo !== expectedRepo || draft.prNumber !== options.number) {
675
+ throw new GitHubPrReviewSubmitError({
676
+ code: 'draft-not-found',
677
+ message: 'Review draft does not belong to this pull request.',
678
+ });
679
+ }
680
+
681
+ const currentHeadSha = await readCurrentPullRequestHeadSha(options);
682
+ if (draft.headSha !== currentHeadSha || options.headSha !== currentHeadSha) {
683
+ throw new GitHubPrReviewSubmitError({
684
+ code: 'stale-draft',
685
+ message: 'PR changed since this review draft was anchored.',
686
+ failingCommentIds: draft.comments.map((comment) => comment.id),
687
+ });
688
+ }
689
+
690
+ const selectedCommentIds = options.commentIds
691
+ ? new Set(options.commentIds)
692
+ : null;
693
+ const comments =
694
+ selectedCommentIds === null
695
+ ? draft.comments
696
+ : draft.comments.filter((comment) => selectedCommentIds.has(comment.id));
697
+ const missingCommentIds = options.commentIds
698
+ ? options.commentIds.filter(
699
+ (id) => !draft.comments.some((comment) => comment.id === id),
700
+ )
701
+ : [];
702
+ if (missingCommentIds.length > 0) {
703
+ throw new GitHubPrReviewSubmitError({
704
+ code: 'invalid-review',
705
+ message: 'Review draft comment ids do not belong to this draft.',
706
+ failingCommentIds: missingCommentIds,
707
+ });
708
+ }
709
+
710
+ const verdict = draft.verdict ?? 'comment';
711
+ const body = draft.body?.trim() ?? '';
712
+ if (verdict !== 'approve' && body.length === 0 && comments.length === 0) {
713
+ throw new GitHubPrReviewSubmitError({
714
+ code: 'invalid-review',
715
+ message: 'Review body or inline comments are required.',
716
+ });
717
+ }
718
+
719
+ let review: GitHubSubmittedPullRequestReview;
720
+ try {
721
+ review = await createPullRequestReview({
722
+ token: options.token,
723
+ owner: options.owner,
724
+ repo: options.repo,
725
+ number: options.number,
726
+ headSha: currentHeadSha,
727
+ verdict,
728
+ body,
729
+ comments,
730
+ });
731
+ } catch (error) {
732
+ const message = errorMessage(error);
733
+ if (isLikelyInsufficientScopeError(error, message)) {
734
+ throw new GitHubPrReviewSubmitError({
735
+ code: 'insufficient-scope',
736
+ message:
737
+ 'GitHub token needs pull request write access to submit reviews.',
738
+ requires: ['pull_requests:write'],
739
+ });
740
+ }
741
+ throw new GitHubPrReviewSubmitError({
742
+ code: 'github-review-submit-failed',
743
+ message,
744
+ failingCommentIds: failingReviewCommentIdsFromGitHubError(
745
+ error,
746
+ comments,
747
+ ),
748
+ });
749
+ }
750
+
751
+ const neonDraftOutcome = markNeonDraftSeedOutcomes(
752
+ options.databasePath,
753
+ draft,
754
+ comments,
755
+ );
756
+ const submitted = markDraftSubmitted(options.databasePath, draft.id);
757
+ await addWorkflowSummary(
758
+ {
759
+ workflow: 'github_pr_review',
760
+ runId: `github-pr-review:${draft.repo}#${draft.prNumber}:${review.id}`,
761
+ status: 'submitted',
762
+ summary: {
763
+ repo: draft.repo,
764
+ prNumber: draft.prNumber,
765
+ verdict,
766
+ commentCount: comments.length,
767
+ skippedCommentCount: draft.comments.length - comments.length,
768
+ neonDraftOutcome,
769
+ reviewUrl: review.url,
770
+ headSha: currentHeadSha,
771
+ },
772
+ },
773
+ options.paths,
774
+ );
775
+
776
+ return { draft: submitted, review };
777
+ }
778
+
779
+ function markNeonDraftSeedOutcomes(
780
+ databasePath: string,
781
+ draft: GitHubPrReviewDraft,
782
+ submittedComments: GitHubPrReviewDraftComment[],
783
+ ) {
784
+ const database = openDb(databasePath);
785
+ try {
786
+ return summarizeAndPersistNeonDraftOutcome(
787
+ database,
788
+ draft,
789
+ submittedComments,
790
+ );
791
+ } finally {
792
+ database.close();
793
+ }
794
+ }
795
+
796
+ function summarizeAndPersistNeonDraftOutcome(
797
+ database: ReturnType<typeof openDb>,
798
+ draft: GitHubPrReviewDraft,
799
+ submittedComments: GitHubPrReviewDraftComment[],
800
+ ) {
801
+ const seeds = readNeonSeedsForDraft(database, draft.id);
802
+ const survivingCommentsById = new Map(
803
+ draft.comments.map((comment) => [comment.id, comment]),
804
+ );
805
+ const submittedCommentsById = new Map(
806
+ submittedComments.map((comment) => [comment.id, comment]),
807
+ );
808
+ const now = new Date().toISOString();
809
+ const submittedSeeds = [];
810
+ const skippedSeeds = [];
811
+ const deletedSeeds = [];
812
+ const severityCounts: Record<
813
+ string,
814
+ {
815
+ seeded: number;
816
+ submitted: number;
817
+ skipped: number;
818
+ deleted: number;
819
+ editedSubmitted: number;
820
+ }
821
+ > = {};
822
+
823
+ for (const seed of seeds) {
824
+ const severity = severityCounts[seed.severity] ?? {
825
+ seeded: 0,
826
+ submitted: 0,
827
+ skipped: 0,
828
+ deleted: 0,
829
+ editedSubmitted: 0,
830
+ };
831
+ severity.seeded += 1;
832
+ severityCounts[seed.severity] = severity;
833
+
834
+ const submittedComment = submittedCommentsById.get(seed.commentId);
835
+ const outcome: GitHubPrReviewNeonSeedOutcome = submittedComment
836
+ ? 'submitted'
837
+ : survivingCommentsById.has(seed.commentId)
838
+ ? 'skipped'
839
+ : 'deleted';
840
+ database
841
+ .prepare(
842
+ `
843
+ UPDATE pr_review_neon_seeded_comments
844
+ SET outcome = ?,
845
+ outcome_at = ?
846
+ WHERE comment_id = ?;
847
+ `,
848
+ )
849
+ .run(outcome, now, seed.commentId);
850
+
851
+ if (outcome === 'submitted') {
852
+ if (!submittedComment) continue;
853
+ submittedSeeds.push(seed);
854
+ severity.submitted += 1;
855
+ if (submittedComment.updatedAt !== submittedComment.createdAt) {
856
+ severity.editedSubmitted += 1;
857
+ }
858
+ continue;
859
+ }
860
+ if (outcome === 'skipped') {
861
+ skippedSeeds.push(seed);
862
+ severity.skipped += 1;
863
+ continue;
864
+ }
865
+ deletedSeeds.push(seed);
866
+ severity.deleted += 1;
867
+ }
868
+
869
+ const editedSubmitted = submittedSeeds.filter((seed) => {
870
+ const comment = submittedCommentsById.get(seed.commentId);
871
+ return Boolean(comment && comment.updatedAt !== comment.createdAt);
872
+ });
873
+
874
+ return {
875
+ seededNeonCommentCount: seeds.length,
876
+ survivingNeonCommentCount: seeds.filter((seed) =>
877
+ survivingCommentsById.has(seed.commentId),
878
+ ).length,
879
+ submittedNeonCommentCount: submittedSeeds.length,
880
+ skippedNeonCommentCount: skippedSeeds.length,
881
+ deletedNeonCommentCount: deletedSeeds.length,
882
+ skippedOrDeletedNeonCommentCount: skippedSeeds.length + deletedSeeds.length,
883
+ editedSubmittedNeonCommentCount: editedSubmitted.length,
884
+ submittedNeonCommentIds: submittedSeeds.map((seed) => seed.commentId),
885
+ skippedNeonCommentIds: skippedSeeds.map((seed) => seed.commentId),
886
+ deletedNeonCommentIds: deletedSeeds.map((seed) => seed.commentId),
887
+ bySeverity: severityCounts,
888
+ };
889
+ }
890
+
891
+ function readNeonSeedByCommentId(
892
+ database: ReturnType<typeof openDb>,
893
+ commentId: string,
894
+ ) {
895
+ const row = database
896
+ .prepare(
897
+ `
898
+ SELECT *
899
+ FROM pr_review_neon_seeded_comments
900
+ WHERE comment_id = ?
901
+ LIMIT 1;
902
+ `,
903
+ )
904
+ .get(commentId);
905
+ if (!row) throw new Error('Neon review seed ledger row was not recorded.');
906
+ return readNeonSeedRow(row);
907
+ }
908
+
909
+ function readNeonSeedsForDraft(
910
+ database: ReturnType<typeof openDb>,
911
+ draftId: string,
912
+ ) {
913
+ return database
914
+ .prepare(
915
+ `
916
+ SELECT *
917
+ FROM pr_review_neon_seeded_comments
918
+ WHERE draft_id = ?
919
+ ORDER BY seeded_at ASC;
920
+ `,
921
+ )
922
+ .all(draftId)
923
+ .map(readNeonSeedRow);
924
+ }
925
+
926
+ function readNeonSeedRow(row: unknown): GitHubPrReviewNeonSeededComment {
927
+ const parsed = v.parse(neonSeedRowSchema, row);
928
+ return {
929
+ commentId: parsed.comment_id,
930
+ draftId: parsed.draft_id,
931
+ repo: parsed.repo,
932
+ prNumber: parsed.pr_number,
933
+ headSha: parsed.head_sha,
934
+ path: parsed.path,
935
+ side: parsed.side,
936
+ line: parsed.line,
937
+ startLine: parsed.start_line,
938
+ startSide: parsed.start_side,
939
+ severity: parsed.severity,
940
+ summary: parsed.summary,
941
+ source: parsed.source,
942
+ outcome: parsed.outcome,
943
+ outcomeAt: parsed.outcome_at,
944
+ seededAt: parsed.seeded_at,
945
+ };
946
+ }
947
+
948
+ function truncateSeedSummary(value: string) {
949
+ const trimmed = value.trim();
950
+ return trimmed.length > 1_000 ? `${trimmed.slice(0, 1_000)}...` : trimmed;
951
+ }
952
+
953
+ export async function replyToPullRequestReviewThread(options: {
954
+ token: string;
955
+ threadId: string;
956
+ body: string;
957
+ }): Promise<GitHubPullRequestReviewThread> {
958
+ try {
959
+ await githubGraphqlFetch(options.token, replyReviewThreadMutation, {
960
+ threadId: options.threadId,
961
+ body: options.body,
962
+ });
963
+ } catch (error) {
964
+ throw rewriteThreadMutationError(error);
965
+ }
966
+
967
+ return fetchPullRequestReviewThread({
968
+ token: options.token,
969
+ threadId: options.threadId,
970
+ });
971
+ }
972
+
973
+ export async function resolvePullRequestReviewThread(options: {
974
+ token: string;
975
+ threadId: string;
976
+ }): Promise<GitHubPullRequestReviewThread> {
977
+ try {
978
+ await githubGraphqlFetch(options.token, resolveReviewThreadMutation, {
979
+ threadId: options.threadId,
980
+ });
981
+ } catch (error) {
982
+ throw rewriteThreadMutationError(error);
983
+ }
984
+
985
+ return fetchPullRequestReviewThread({
986
+ token: options.token,
987
+ threadId: options.threadId,
988
+ });
989
+ }
990
+
991
+ export async function unresolvePullRequestReviewThread(options: {
992
+ token: string;
993
+ threadId: string;
994
+ }): Promise<GitHubPullRequestReviewThread> {
995
+ try {
996
+ await githubGraphqlFetch(options.token, unresolveReviewThreadMutation, {
997
+ threadId: options.threadId,
998
+ });
999
+ } catch (error) {
1000
+ throw rewriteThreadMutationError(error);
1001
+ }
1002
+
1003
+ return fetchPullRequestReviewThread({
1004
+ token: options.token,
1005
+ threadId: options.threadId,
1006
+ });
1007
+ }
1008
+
1009
+ export async function fetchPullRequestReviews(options: {
1010
+ token: string;
1011
+ owner: string;
1012
+ repo: string;
1013
+ number: number;
1014
+ }): Promise<GitHubPullRequestReview[]> {
1015
+ return (await fetchPullRequestReviewsWithMetadata(options)).reviews;
1016
+ }
1017
+
1018
+ export async function fetchPullRequestReviewsWithMetadata(options: {
1019
+ token: string;
1020
+ owner: string;
1021
+ repo: string;
1022
+ number: number;
1023
+ }): Promise<{ reviews: GitHubPullRequestReview[]; truncated: boolean }> {
1024
+ const reviews: GitHubPullRequestReviewApiItem[] = [];
1025
+ let nextUrl: string | undefined =
1026
+ `https://api.github.com/repos/${encodePathSegment(options.owner)}/${encodePathSegment(options.repo)}/pulls/${options.number}/reviews?per_page=100`;
1027
+ let pageCount = 0;
1028
+
1029
+ while (nextUrl && pageCount < 3) {
1030
+ pageCount += 1;
1031
+ const response = await githubFetch(options.token, nextUrl);
1032
+ const data = v.parse(
1033
+ v.array(githubPullRequestReviewApiItemSchema),
1034
+ await response.json(),
1035
+ );
1036
+ reviews.push(...data);
1037
+ nextUrl = nextLink(response.headers.get('link'));
1038
+ }
1039
+
1040
+ return {
1041
+ reviews: reviews.map((review) => ({
1042
+ id: review.id,
1043
+ nodeId: review.node_id ?? null,
1044
+ state: review.state,
1045
+ authorLogin: review.user?.login ?? null,
1046
+ submittedAt: review.submitted_at ?? null,
1047
+ commitId: review.commit_id ?? null,
1048
+ url: review.html_url ?? null,
1049
+ })),
1050
+ truncated: Boolean(nextUrl),
1051
+ };
1052
+ }
1053
+
1054
+ export function requestedChangesStateFromReviews(
1055
+ reviews: GitHubPullRequestReview[],
1056
+ ): GitHubPullRequestRequestedChangesState {
1057
+ const relevantStates = new Set([
1058
+ 'APPROVED',
1059
+ 'CHANGES_REQUESTED',
1060
+ 'DISMISSED',
1061
+ ]);
1062
+ const history = reviews
1063
+ .filter((review) => relevantStates.has(review.state))
1064
+ .sort(compareReviewAge);
1065
+ const latestByReviewer = Array.from(
1066
+ history
1067
+ .reduce((items, review) => {
1068
+ items.set(review.authorLogin ?? `review:${review.id}`, review);
1069
+ return items;
1070
+ }, new Map<string, GitHubPullRequestReview>())
1071
+ .values(),
1072
+ ).sort(compareReviewAge);
1073
+
1074
+ return {
1075
+ active: latestByReviewer.filter(
1076
+ (review) => review.state === 'CHANGES_REQUESTED',
1077
+ ),
1078
+ latestByReviewer,
1079
+ history,
1080
+ };
1081
+ }
1082
+
1083
+ function compareReviewAge(
1084
+ left: GitHubPullRequestReview,
1085
+ right: GitHubPullRequestReview,
1086
+ ) {
1087
+ const leftTime = left.submittedAt ? Date.parse(left.submittedAt) : 0;
1088
+ const rightTime = right.submittedAt ? Date.parse(right.submittedAt) : 0;
1089
+ return leftTime - rightTime || left.id - right.id;
1090
+ }
1091
+
1092
+ function readDraftWithComments(
1093
+ database: ReturnType<typeof openDb>,
1094
+ row: unknown,
1095
+ ) {
1096
+ const draft = readDraftRow(row);
1097
+ return {
1098
+ ...draft,
1099
+ comments: readDraftComments(database, draft.id),
1100
+ };
1101
+ }
1102
+
1103
+ function updateExistingReviewDraft(
1104
+ database: ReturnType<typeof openDb>,
1105
+ row: unknown,
1106
+ options: {
1107
+ headSha?: string;
1108
+ verdict?: GitHubPrReviewVerdict | null;
1109
+ body?: string | null;
1110
+ reanchorHeadSha?: boolean;
1111
+ },
1112
+ updatedAt: string,
1113
+ ) {
1114
+ const draft = readDraftRow(row);
1115
+ const nextVerdict =
1116
+ 'verdict' in options ? (options.verdict ?? null) : draft.verdict;
1117
+ const nextBody =
1118
+ 'body' in options ? normalizeNullableBody(options.body) : draft.body;
1119
+ database
1120
+ .prepare(
1121
+ `
1122
+ UPDATE pr_review_drafts
1123
+ SET head_sha = ?,
1124
+ verdict = ?,
1125
+ body = ?,
1126
+ updated_at = ?
1127
+ WHERE id = ?;
1128
+ `,
1129
+ )
1130
+ .run(
1131
+ options.reanchorHeadSha
1132
+ ? (options.headSha ?? draft.headSha)
1133
+ : draft.headSha,
1134
+ nextVerdict,
1135
+ nextBody,
1136
+ updatedAt,
1137
+ draft.id,
1138
+ );
1139
+ return readDraftWithCommentsById(database, draft.id);
1140
+ }
1141
+
1142
+ function readDraftWithCommentsById(
1143
+ database: ReturnType<typeof openDb>,
1144
+ draftId: string,
1145
+ ) {
1146
+ const row = database
1147
+ .prepare('SELECT * FROM pr_review_drafts WHERE id = ?;')
1148
+ .get(draftId);
1149
+ if (!row) throw new Error('Review draft not found.');
1150
+ return readDraftWithComments(database, row);
1151
+ }
1152
+
1153
+ function readDraftRow(row: unknown): Omit<GitHubPrReviewDraft, 'comments'> {
1154
+ const parsed = v.parse(draftRowSchema, row);
1155
+ return {
1156
+ id: parsed.id,
1157
+ repo: parsed.repo,
1158
+ prNumber: parsed.pr_number,
1159
+ headSha: parsed.head_sha,
1160
+ verdict: parsed.verdict,
1161
+ body: parsed.body,
1162
+ status: parsed.status,
1163
+ createdAt: parsed.created_at,
1164
+ updatedAt: parsed.updated_at,
1165
+ submittedAt: parsed.submitted_at,
1166
+ };
1167
+ }
1168
+
1169
+ function readDraftComments(
1170
+ database: ReturnType<typeof openDb>,
1171
+ draftId: string,
1172
+ ): GitHubPrReviewDraftComment[] {
1173
+ return database
1174
+ .prepare(
1175
+ `
1176
+ SELECT *
1177
+ FROM pr_review_draft_comments
1178
+ WHERE draft_id = ?
1179
+ ORDER BY created_at ASC;
1180
+ `,
1181
+ )
1182
+ .all(draftId)
1183
+ .map((row) => {
1184
+ const parsed = v.parse(draftCommentRowSchema, row);
1185
+ return {
1186
+ id: parsed.id,
1187
+ draftId: parsed.draft_id,
1188
+ path: parsed.path,
1189
+ side: parsed.side,
1190
+ line: parsed.line,
1191
+ startLine: parsed.start_line,
1192
+ startSide: parsed.start_side,
1193
+ body: parsed.body,
1194
+ origin: parsed.origin,
1195
+ createdAt: parsed.created_at,
1196
+ updatedAt: parsed.updated_at,
1197
+ };
1198
+ });
1199
+ }
1200
+
1201
+ function assertDraftIsLive(
1202
+ database: ReturnType<typeof openDb>,
1203
+ draftId: string,
1204
+ ) {
1205
+ const row = database
1206
+ .prepare('SELECT status FROM pr_review_drafts WHERE id = ?;')
1207
+ .get(draftId) as { status?: unknown } | undefined;
1208
+ if (row?.status !== 'draft') {
1209
+ throw new Error('Review draft is not editable.');
1210
+ }
1211
+ }
1212
+
1213
+ function touchDraft(
1214
+ database: ReturnType<typeof openDb>,
1215
+ draftId: string,
1216
+ updatedAt: string,
1217
+ ) {
1218
+ database
1219
+ .prepare('UPDATE pr_review_drafts SET updated_at = ? WHERE id = ?;')
1220
+ .run(updatedAt, draftId);
1221
+ }
1222
+
1223
+ function markDraftSubmitted(
1224
+ databasePath: string,
1225
+ draftId: string,
1226
+ ): GitHubPrReviewDraft {
1227
+ const database = openDb(databasePath);
1228
+ const now = new Date().toISOString();
1229
+ try {
1230
+ database
1231
+ .prepare(
1232
+ `
1233
+ UPDATE pr_review_drafts
1234
+ SET status = 'submitted',
1235
+ submitted_at = ?,
1236
+ updated_at = ?
1237
+ WHERE id = ?;
1238
+ `,
1239
+ )
1240
+ .run(now, now, draftId);
1241
+ return readDraftWithCommentsById(database, draftId);
1242
+ } finally {
1243
+ database.close();
1244
+ }
1245
+ }
1246
+
1247
+ async function readCurrentPullRequestHeadSha(options: {
1248
+ token: string;
1249
+ owner: string;
1250
+ repo: string;
1251
+ number: number;
1252
+ fetchHeadSha?: (options: {
1253
+ token: string;
1254
+ owner: string;
1255
+ repo: string;
1256
+ number: number;
1257
+ }) => Promise<string | null | undefined>;
1258
+ }) {
1259
+ const fetchHeadSha =
1260
+ options.fetchHeadSha ??
1261
+ (async (input: {
1262
+ token: string;
1263
+ owner: string;
1264
+ repo: string;
1265
+ number: number;
1266
+ }) => (await fetchPullRequestDetail(input)).headSha);
1267
+ const headSha = await fetchHeadSha({
1268
+ token: options.token,
1269
+ owner: options.owner,
1270
+ repo: options.repo,
1271
+ number: options.number,
1272
+ });
1273
+ if (!headSha) {
1274
+ throw new GitHubPrReviewSubmitError({
1275
+ code: 'stale-draft',
1276
+ message: 'Current PR head SHA is unavailable.',
1277
+ });
1278
+ }
1279
+ return headSha;
1280
+ }
1281
+
1282
+ async function createPullRequestReview(options: {
1283
+ token: string;
1284
+ owner: string;
1285
+ repo: string;
1286
+ number: number;
1287
+ headSha: string;
1288
+ verdict: GitHubPrReviewVerdict;
1289
+ body: string;
1290
+ comments: GitHubPrReviewDraftComment[];
1291
+ }): Promise<GitHubSubmittedPullRequestReview> {
1292
+ const response = await githubFetch(
1293
+ options.token,
1294
+ `https://api.github.com/repos/${encodePathSegment(options.owner)}/${encodePathSegment(options.repo)}/pulls/${options.number}/reviews`,
1295
+ {
1296
+ method: 'POST',
1297
+ body: JSON.stringify({
1298
+ commit_id: options.headSha,
1299
+ event: reviewEvent(options.verdict),
1300
+ body: options.body,
1301
+ comments: options.comments.map(reviewCommentPayload),
1302
+ }),
1303
+ },
1304
+ );
1305
+ const review = v.parse(
1306
+ githubPullRequestReviewCreatedApiResponseSchema,
1307
+ await response.json(),
1308
+ );
1309
+
1310
+ return {
1311
+ id: review.id,
1312
+ nodeId: review.node_id ?? null,
1313
+ state: review.state,
1314
+ authorLogin: review.user?.login ?? null,
1315
+ submittedAt: review.submitted_at ?? null,
1316
+ commitId: review.commit_id ?? null,
1317
+ url: review.html_url ?? null,
1318
+ body:
1319
+ 'body' in review && typeof review.body === 'string' ? review.body : null,
1320
+ };
1321
+ }
1322
+
1323
+ function reviewEvent(verdict: GitHubPrReviewVerdict) {
1324
+ if (verdict === 'approve') return 'APPROVE';
1325
+ if (verdict === 'request-changes') return 'REQUEST_CHANGES';
1326
+ return 'COMMENT';
1327
+ }
1328
+
1329
+ function reviewCommentPayload(comment: GitHubPrReviewDraftComment) {
1330
+ return {
1331
+ path: comment.path,
1332
+ side: comment.side,
1333
+ line: comment.line,
1334
+ ...(comment.startLine ? { start_line: comment.startLine } : {}),
1335
+ ...(comment.startSide ? { start_side: comment.startSide } : {}),
1336
+ body: comment.body,
1337
+ };
1338
+ }
1339
+
1340
+ function assertValidReviewCommentAnchor(options: {
1341
+ side: GitHubPrReviewDraftCommentSide;
1342
+ line: number;
1343
+ startLine?: number | null;
1344
+ startSide?: GitHubPrReviewDraftCommentSide | null;
1345
+ }) {
1346
+ if (options.line < 1 || !Number.isInteger(options.line)) {
1347
+ throw new Error('Review comment line must be a positive integer.');
1348
+ }
1349
+ if (options.startLine == null) return;
1350
+ if (options.startLine < 1 || !Number.isInteger(options.startLine)) {
1351
+ throw new Error('Review comment start line must be a positive integer.');
1352
+ }
1353
+ const startSide = options.startSide ?? options.side;
1354
+ if (startSide === options.side && options.startLine > options.line) {
1355
+ throw new Error('Review comment range start must not follow the end line.');
1356
+ }
1357
+ if (startSide === 'RIGHT' && options.side === 'LEFT') {
1358
+ throw new Error('Review comment cross-side range must start on LEFT.');
1359
+ }
1360
+ }
1361
+
1362
+ function normalizeNullableBody(value: string | null | undefined) {
1363
+ if (value == null) return null;
1364
+ const trimmed = value.trim();
1365
+ return trimmed.length > 0 ? trimmed : null;
1366
+ }
1367
+
1368
+ function isLikelyInsufficientScopeError(error: unknown, message: string) {
1369
+ if (error instanceof GitHubApiError && error.status === 403) return true;
1370
+ return /Resource not accessible by integration|pull request write access/i.test(
1371
+ message,
1372
+ );
1373
+ }
1374
+
1375
+ function failingReviewCommentIdsFromGitHubError(
1376
+ error: unknown,
1377
+ comments: GitHubPrReviewDraftComment[],
1378
+ ) {
1379
+ if (!(error instanceof GitHubApiError)) {
1380
+ return comments.map((comment) => comment.id);
1381
+ }
1382
+ const indexed = failingReviewCommentIndexes(error.data);
1383
+ if (indexed.size > 0) {
1384
+ return comments
1385
+ .filter((_comment, index) => indexed.has(index))
1386
+ .map((comment) => comment.id);
1387
+ }
1388
+ const matched = comments.filter((comment) =>
1389
+ githubErrorDataMentionsComment(error.data, comment),
1390
+ );
1391
+ return matched.length > 0
1392
+ ? matched.map((comment) => comment.id)
1393
+ : comments.map((comment) => comment.id);
1394
+ }
1395
+
1396
+ function failingReviewCommentIndexes(data: unknown) {
1397
+ const indexes = new Set<number>();
1398
+ for (const item of githubErrorItems(data)) {
1399
+ const text = Object.values(item)
1400
+ .filter((value) => typeof value === 'string' || typeof value === 'number')
1401
+ .join(' ');
1402
+ for (const match of text.matchAll(/comments\[(\d+)]/gi)) {
1403
+ indexes.add(Number(match[1]));
1404
+ }
1405
+ }
1406
+ return indexes;
1407
+ }
1408
+
1409
+ function githubErrorDataMentionsComment(
1410
+ data: unknown,
1411
+ comment: GitHubPrReviewDraftComment,
1412
+ ) {
1413
+ const lineNeedles = [
1414
+ `line ${comment.line}`,
1415
+ `line:${comment.line}`,
1416
+ `line: ${comment.line}`,
1417
+ `"line":${comment.line}`,
1418
+ ];
1419
+ return githubErrorItems(data).some((item) => {
1420
+ const text = JSON.stringify(item);
1421
+ return (
1422
+ text.includes(comment.path) &&
1423
+ lineNeedles.some((needle) => text.includes(needle))
1424
+ );
1425
+ });
1426
+ }
1427
+
1428
+ function githubErrorItems(data: unknown): Array<Record<string, unknown>> {
1429
+ if (!data || typeof data !== 'object') return [];
1430
+ const errors = 'errors' in data ? data.errors : null;
1431
+ if (!Array.isArray(errors)) return [];
1432
+ return errors.filter(
1433
+ (item): item is Record<string, unknown> =>
1434
+ Boolean(item) && typeof item === 'object',
1435
+ );
1436
+ }
1437
+
1438
+ function rewriteThreadMutationError(error: unknown) {
1439
+ const message = errorMessage(error);
1440
+ if (isLikelyInsufficientScopeError(error, message)) {
1441
+ return new Error(
1442
+ 'GitHub token needs pull request write access to update review threads.',
1443
+ );
1444
+ }
1445
+ return error instanceof Error ? error : new Error(message);
1446
+ }