co-maintainer 0.4.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (294) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -0
  3. package/dist/main.d.ts +2 -0
  4. package/dist/main.js +9 -0
  5. package/dist/mod.d.ts +4 -0
  6. package/dist/mod.js +2 -0
  7. package/dist/package.json +54 -0
  8. package/dist/src/ai/batch.d.ts +16 -0
  9. package/dist/src/ai/batch.js +117 -0
  10. package/dist/src/ai/fake.d.ts +7 -0
  11. package/dist/src/ai/fake.js +40 -0
  12. package/dist/src/ai/hetzner.d.ts +16 -0
  13. package/dist/src/ai/hetzner.js +69 -0
  14. package/dist/src/ai/mermaid.d.ts +27 -0
  15. package/dist/src/ai/mermaid.js +457 -0
  16. package/dist/src/ai/mermaid_loop.d.ts +12 -0
  17. package/dist/src/ai/mermaid_loop.js +100 -0
  18. package/dist/src/ai/openrouter.d.ts +9 -0
  19. package/dist/src/ai/openrouter.js +29 -0
  20. package/dist/src/ai/provider.d.ts +4 -0
  21. package/dist/src/ai/provider.js +63 -0
  22. package/dist/src/cli/args.d.ts +5 -0
  23. package/dist/src/cli/args.js +252 -0
  24. package/dist/src/cli/commands/probe.d.ts +2 -0
  25. package/dist/src/cli/commands/probe.js +122 -0
  26. package/dist/src/cli/commands/review.d.ts +3 -0
  27. package/dist/src/cli/commands/review.js +84 -0
  28. package/dist/src/cli/commands/serve.d.ts +18 -0
  29. package/dist/src/cli/commands/serve.js +183 -0
  30. package/dist/src/cli/commands/set.d.ts +7 -0
  31. package/dist/src/cli/commands/set.js +171 -0
  32. package/dist/src/cli/main.d.ts +1 -0
  33. package/dist/src/cli/main.js +27 -0
  34. package/dist/src/cli/prompt.d.ts +4 -0
  35. package/dist/src/cli/prompt.js +26 -0
  36. package/dist/src/cli/review_args.d.ts +28 -0
  37. package/dist/src/cli/review_args.js +84 -0
  38. package/dist/src/cli/review_output.d.ts +2 -0
  39. package/dist/src/cli/review_output.js +10 -0
  40. package/dist/src/cli/review_result.d.ts +81 -0
  41. package/dist/src/cli/review_result.js +281 -0
  42. package/dist/src/config.d.ts +108 -0
  43. package/dist/src/config.js +138 -0
  44. package/dist/src/git/default_branch.d.ts +5 -0
  45. package/dist/src/git/default_branch.js +22 -0
  46. package/dist/src/github/app.d.ts +43 -0
  47. package/dist/src/github/app.js +120 -0
  48. package/dist/src/github/client.d.ts +17 -0
  49. package/dist/src/github/client.js +66 -0
  50. package/dist/src/github/collect.d.ts +7 -0
  51. package/dist/src/github/collect.js +368 -0
  52. package/dist/src/github/gh.d.ts +8 -0
  53. package/dist/src/github/gh.js +63 -0
  54. package/dist/src/github/jwt.d.ts +12 -0
  55. package/dist/src/github/jwt.js +84 -0
  56. package/dist/src/github/oauth.d.ts +9 -0
  57. package/dist/src/github/oauth.js +51 -0
  58. package/dist/src/github/pat.d.ts +10 -0
  59. package/dist/src/github/pat.js +60 -0
  60. package/dist/src/knowledge/facts.d.ts +3 -0
  61. package/dist/src/knowledge/facts.js +325 -0
  62. package/dist/src/knowledge/guide.d.ts +6 -0
  63. package/dist/src/knowledge/guide.js +82 -0
  64. package/dist/src/knowledge/probe.d.ts +12 -0
  65. package/dist/src/knowledge/probe.js +144 -0
  66. package/dist/src/knowledge/sections.d.ts +2 -0
  67. package/dist/src/knowledge/sections.js +13 -0
  68. package/dist/src/knowledge/skill.d.ts +10 -0
  69. package/dist/src/knowledge/skill.js +106 -0
  70. package/dist/src/knowledge/synthesis.d.ts +7 -0
  71. package/dist/src/knowledge/synthesis.js +315 -0
  72. package/dist/src/knowledge/types.d.ts +49 -0
  73. package/dist/src/knowledge/types.js +1 -0
  74. package/dist/src/knowledge/validate.d.ts +6 -0
  75. package/dist/src/knowledge/validate.js +134 -0
  76. package/dist/src/local/carry_over_store.d.ts +18 -0
  77. package/dist/src/local/carry_over_store.js +40 -0
  78. package/dist/src/local/codegraph_exclude.d.ts +3 -0
  79. package/dist/src/local/codegraph_exclude.js +26 -0
  80. package/dist/src/local/codegraph_prepare.d.ts +17 -0
  81. package/dist/src/local/codegraph_prepare.js +51 -0
  82. package/dist/src/local/git_ops.d.ts +12 -0
  83. package/dist/src/local/git_ops.js +98 -0
  84. package/dist/src/local/git_parse.d.ts +27 -0
  85. package/dist/src/local/git_parse.js +131 -0
  86. package/dist/src/local/git_revision.d.ts +15 -0
  87. package/dist/src/local/git_revision.js +141 -0
  88. package/dist/src/local/git_untracked.d.ts +21 -0
  89. package/dist/src/local/git_untracked.js +79 -0
  90. package/dist/src/local/review_local.d.ts +4 -0
  91. package/dist/src/local/review_local.js +273 -0
  92. package/dist/src/local/review_lock.d.ts +7 -0
  93. package/dist/src/local/review_lock.js +45 -0
  94. package/dist/src/pr/checkout.d.ts +25 -0
  95. package/dist/src/pr/checkout.js +136 -0
  96. package/dist/src/pr/codegraph_tool_args.d.ts +8 -0
  97. package/dist/src/pr/codegraph_tool_args.js +34 -0
  98. package/dist/src/pr/codegraph_tools.d.ts +15 -0
  99. package/dist/src/pr/codegraph_tools.js +408 -0
  100. package/dist/src/pr/diff_summary.d.ts +6 -0
  101. package/dist/src/pr/diff_summary.js +52 -0
  102. package/dist/src/pr/findings.d.ts +14 -0
  103. package/dist/src/pr/findings.js +94 -0
  104. package/dist/src/pr/github_revision.d.ts +4 -0
  105. package/dist/src/pr/github_revision.js +39 -0
  106. package/dist/src/pr/hunks.d.ts +18 -0
  107. package/dist/src/pr/hunks.js +73 -0
  108. package/dist/src/pr/reviewer.d.ts +40 -0
  109. package/dist/src/pr/reviewer.js +582 -0
  110. package/dist/src/pr/rounds.d.ts +17 -0
  111. package/dist/src/pr/rounds.js +43 -0
  112. package/dist/src/pr/scope.d.ts +35 -0
  113. package/dist/src/pr/scope.js +84 -0
  114. package/dist/src/pr/snapshot.d.ts +11 -0
  115. package/dist/src/pr/snapshot.js +1 -0
  116. package/dist/src/pr/suggestion.d.ts +17 -0
  117. package/dist/src/pr/suggestion.js +70 -0
  118. package/dist/src/remote/client.d.ts +4 -0
  119. package/dist/src/remote/client.js +199 -0
  120. package/dist/src/remote/client_tools.d.ts +13 -0
  121. package/dist/src/remote/client_tools.js +30 -0
  122. package/dist/src/remote/fixtures.d.ts +1 -0
  123. package/dist/src/remote/fixtures.js +30 -0
  124. package/dist/src/remote/revision_from_submit.d.ts +2 -0
  125. package/dist/src/remote/revision_from_submit.js +26 -0
  126. package/dist/src/remote/schema.d.ts +14 -0
  127. package/dist/src/remote/schema.js +30 -0
  128. package/dist/src/remote/server/body.d.ts +18 -0
  129. package/dist/src/remote/server/body.js +73 -0
  130. package/dist/src/remote/server/routes.d.ts +1 -0
  131. package/dist/src/remote/server/routes.js +150 -0
  132. package/dist/src/remote/server/sessions.d.ts +16 -0
  133. package/dist/src/remote/server/sessions.js +48 -0
  134. package/dist/src/remote/server/sync.d.ts +3 -0
  135. package/dist/src/remote/server/sync.js +90 -0
  136. package/dist/src/remote/tool_bridge.d.ts +15 -0
  137. package/dist/src/remote/tool_bridge.js +84 -0
  138. package/dist/src/remote/validate.d.ts +8 -0
  139. package/dist/src/remote/validate.js +248 -0
  140. package/dist/src/review/carry_over.d.ts +63 -0
  141. package/dist/src/review/carry_over.js +372 -0
  142. package/dist/src/review/engine.d.ts +13 -0
  143. package/dist/src/review/engine.js +4 -0
  144. package/dist/src/review/guides.d.ts +12 -0
  145. package/dist/src/review/guides.js +44 -0
  146. package/dist/src/review/revision.d.ts +26 -0
  147. package/dist/src/review/revision.js +57 -0
  148. package/dist/src/server/api/activity.d.ts +1 -0
  149. package/dist/src/server/api/activity.js +12 -0
  150. package/dist/src/server/api/analytics.d.ts +1 -0
  151. package/dist/src/server/api/analytics.js +9 -0
  152. package/dist/src/server/api/installations.d.ts +4 -0
  153. package/dist/src/server/api/installations.js +24 -0
  154. package/dist/src/server/api/jobs.d.ts +1 -0
  155. package/dist/src/server/api/jobs.js +86 -0
  156. package/dist/src/server/api/json_body.d.ts +1 -0
  157. package/dist/src/server/api/json_body.js +14 -0
  158. package/dist/src/server/api/remote_tokens.d.ts +1 -0
  159. package/dist/src/server/api/remote_tokens.js +71 -0
  160. package/dist/src/server/api/repos.d.ts +4 -0
  161. package/dist/src/server/api/repos.js +136 -0
  162. package/dist/src/server/api/settings.d.ts +1 -0
  163. package/dist/src/server/api/settings.js +210 -0
  164. package/dist/src/server/app.d.ts +27 -0
  165. package/dist/src/server/app.js +147 -0
  166. package/dist/src/server/assets.d.ts +3 -0
  167. package/dist/src/server/assets.js +21 -0
  168. package/dist/src/server/auth.d.ts +44 -0
  169. package/dist/src/server/auth.js +188 -0
  170. package/dist/src/server/errors.d.ts +8 -0
  171. package/dist/src/server/errors.js +10 -0
  172. package/dist/src/server/http.d.ts +8 -0
  173. package/dist/src/server/http.js +117 -0
  174. package/dist/src/server/logo.d.ts +1 -0
  175. package/dist/src/server/logo.js +2 -0
  176. package/dist/src/server/pages/activity.d.ts +4 -0
  177. package/dist/src/server/pages/activity.js +200 -0
  178. package/dist/src/server/pages/add_repo.d.ts +9 -0
  179. package/dist/src/server/pages/add_repo.js +56 -0
  180. package/dist/src/server/pages/analytics.d.ts +2 -0
  181. package/dist/src/server/pages/analytics.js +193 -0
  182. package/dist/src/server/pages/client.d.ts +2 -0
  183. package/dist/src/server/pages/client.js +2 -0
  184. package/dist/src/server/pages/home.d.ts +16 -0
  185. package/dist/src/server/pages/home.js +87 -0
  186. package/dist/src/server/pages/knowledge.d.ts +2 -0
  187. package/dist/src/server/pages/knowledge.js +64 -0
  188. package/dist/src/server/pages/layout.d.ts +32 -0
  189. package/dist/src/server/pages/layout.js +230 -0
  190. package/dist/src/server/pages/pr.d.ts +2 -0
  191. package/dist/src/server/pages/pr.js +109 -0
  192. package/dist/src/server/pages/repo.d.ts +2 -0
  193. package/dist/src/server/pages/repo.js +92 -0
  194. package/dist/src/server/pages/repo_prs.d.ts +2 -0
  195. package/dist/src/server/pages/repo_prs.js +75 -0
  196. package/dist/src/server/pages/repo_remote.d.ts +2 -0
  197. package/dist/src/server/pages/repo_remote.js +38 -0
  198. package/dist/src/server/pages/repo_settings.d.ts +3 -0
  199. package/dist/src/server/pages/repo_settings.js +127 -0
  200. package/dist/src/server/pages/router.d.ts +17 -0
  201. package/dist/src/server/pages/router.js +312 -0
  202. package/dist/src/server/pages/settings.d.ts +2 -0
  203. package/dist/src/server/pages/settings.js +337 -0
  204. package/dist/src/server/pages/setup.d.ts +5 -0
  205. package/dist/src/server/pages/setup.js +38 -0
  206. package/dist/src/server/pages/styles.d.ts +2 -0
  207. package/dist/src/server/pages/styles.js +2 -0
  208. package/dist/src/server/webhook/index.d.ts +1 -0
  209. package/dist/src/server/webhook/index.js +64 -0
  210. package/dist/src/server/webhook/signature.d.ts +7 -0
  211. package/dist/src/server/webhook/signature.js +41 -0
  212. package/dist/src/services/credentials.d.ts +19 -0
  213. package/dist/src/services/credentials.js +146 -0
  214. package/dist/src/services/dashboard.d.ts +165 -0
  215. package/dist/src/services/dashboard.js +200 -0
  216. package/dist/src/services/drift.d.ts +13 -0
  217. package/dist/src/services/drift.js +76 -0
  218. package/dist/src/services/jobs.d.ts +34 -0
  219. package/dist/src/services/jobs.js +222 -0
  220. package/dist/src/services/remote_review.d.ts +10 -0
  221. package/dist/src/services/remote_review.js +274 -0
  222. package/dist/src/services/remote_review_abort.d.ts +3 -0
  223. package/dist/src/services/remote_review_abort.js +11 -0
  224. package/dist/src/services/remote_token_jobs.d.ts +1 -0
  225. package/dist/src/services/remote_token_jobs.js +20 -0
  226. package/dist/src/services/remote_tokens.d.ts +13 -0
  227. package/dist/src/services/remote_tokens.js +57 -0
  228. package/dist/src/services/replies.d.ts +7 -0
  229. package/dist/src/services/replies.js +248 -0
  230. package/dist/src/services/review.d.ts +35 -0
  231. package/dist/src/services/review.js +671 -0
  232. package/dist/src/services/setup.d.ts +29 -0
  233. package/dist/src/services/setup.js +364 -0
  234. package/dist/src/services/webhook.d.ts +22 -0
  235. package/dist/src/services/webhook.js +401 -0
  236. package/dist/src/store/app_db.d.ts +10 -0
  237. package/dist/src/store/app_db.js +85 -0
  238. package/dist/src/store/cache_db.d.ts +4 -0
  239. package/dist/src/store/cache_db.js +63 -0
  240. package/dist/src/store/deliveries.d.ts +12 -0
  241. package/dist/src/store/deliveries.js +23 -0
  242. package/dist/src/store/drift.d.ts +4 -0
  243. package/dist/src/store/drift.js +22 -0
  244. package/dist/src/store/findings.d.ts +31 -0
  245. package/dist/src/store/findings.js +64 -0
  246. package/dist/src/store/installations.d.ts +9 -0
  247. package/dist/src/store/installations.js +35 -0
  248. package/dist/src/store/job_logs.d.ts +5 -0
  249. package/dist/src/store/job_logs.js +17 -0
  250. package/dist/src/store/jobs.d.ts +27 -0
  251. package/dist/src/store/jobs.js +86 -0
  252. package/dist/src/store/migrations.d.ts +5 -0
  253. package/dist/src/store/migrations.js +258 -0
  254. package/dist/src/store/remote_review_inputs.d.ts +11 -0
  255. package/dist/src/store/remote_review_inputs.js +25 -0
  256. package/dist/src/store/remote_tokens.d.ts +10 -0
  257. package/dist/src/store/remote_tokens.js +45 -0
  258. package/dist/src/store/replies.d.ts +24 -0
  259. package/dist/src/store/replies.js +79 -0
  260. package/dist/src/store/repos.d.ts +11 -0
  261. package/dist/src/store/repos.js +59 -0
  262. package/dist/src/store/reviews.d.ts +86 -0
  263. package/dist/src/store/reviews.js +220 -0
  264. package/dist/src/store/rows.d.ts +179 -0
  265. package/dist/src/store/rows.js +4 -0
  266. package/dist/src/store/sessions.d.ts +8 -0
  267. package/dist/src/store/sessions.js +34 -0
  268. package/dist/src/store/skill_state.d.ts +3 -0
  269. package/dist/src/store/skill_state.js +24 -0
  270. package/dist/src/store/sqlite.d.ts +39 -0
  271. package/dist/src/store/sqlite.js +77 -0
  272. package/dist/src/store/subjects.d.ts +15 -0
  273. package/dist/src/store/subjects.js +78 -0
  274. package/dist/src/tools/codegraph.d.ts +58 -0
  275. package/dist/src/tools/codegraph.js +165 -0
  276. package/dist/src/tools/codegraph_exec.d.ts +13 -0
  277. package/dist/src/tools/codegraph_exec.js +66 -0
  278. package/dist/src/types.d.ts +89 -0
  279. package/dist/src/types.js +1 -0
  280. package/dist/src/util/atomic.d.ts +4 -0
  281. package/dist/src/util/atomic.js +78 -0
  282. package/dist/src/util/keyed_lock.d.ts +3 -0
  283. package/dist/src/util/keyed_lock.js +33 -0
  284. package/dist/src/util/log.d.ts +11 -0
  285. package/dist/src/util/log.js +42 -0
  286. package/dist/src/util/redact.d.ts +5 -0
  287. package/dist/src/util/redact.js +30 -0
  288. package/dist/src/util/runtime.d.ts +90 -0
  289. package/dist/src/util/runtime.js +206 -0
  290. package/dist/src/util/time.d.ts +9 -0
  291. package/dist/src/util/time.js +36 -0
  292. package/dist/src/version.d.ts +1 -0
  293. package/dist/src/version.js +5 -0
  294. package/package.json +54 -0
@@ -0,0 +1,64 @@
1
+ import { getAppDb } from "./app_db.js";
2
+ export function insertFinding(row) {
3
+ getAppDb()
4
+ .prepare(`INSERT INTO findings
5
+ (id, review_id, severity, path, line_from, line_to, title, body_md,
6
+ first_seen_review_id, thread_comment_id, anchor_text, state,
7
+ carried_from_finding_id, close_reason)
8
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
9
+ .run(row.id, row.reviewId, row.severity, row.path ?? null, row.lineFrom ?? null, row.lineTo ?? null, row.title, row.bodyMd, row.firstSeenReviewId ?? null, row.threadCommentId ?? null, row.anchorText ?? null, row.state ?? "new", row.carriedFromFindingId ?? null, row.closeReason ?? null);
10
+ }
11
+ export function listCarryableFindingsForReview(reviewId) {
12
+ return getAppDb()
13
+ .prepare(`SELECT * FROM findings
14
+ WHERE review_id = ? AND state IN ('new', 'open')
15
+ ORDER BY severity, id`)
16
+ .all(reviewId);
17
+ }
18
+ export function setFindingPosted(id, postedCommentId, threadCommentId) {
19
+ getAppDb()
20
+ .prepare(`UPDATE findings SET posted_comment_id = ?, thread_comment_id = COALESCE(?, thread_comment_id) WHERE id = ?`)
21
+ .run(postedCommentId, threadCommentId ?? null, id);
22
+ }
23
+ export function listFindingsForReview(reviewId) {
24
+ return getAppDb()
25
+ .prepare(`SELECT * FROM findings WHERE review_id = ? ORDER BY severity, id`)
26
+ .all(reviewId);
27
+ }
28
+ export function listFindingsForPr(repo, prNumber) {
29
+ return getAppDb()
30
+ .prepare(`SELECT f.* FROM findings f
31
+ INNER JOIN reviews r ON r.id = f.review_id
32
+ WHERE r.repo = ? AND r.pr_number = ?
33
+ ORDER BY r.round, f.id`)
34
+ .all(repo, prNumber);
35
+ }
36
+ export function findFindingByPostedComment(repo, prNumber, commentId) {
37
+ return getAppDb()
38
+ .prepare(`SELECT f.* FROM findings f
39
+ INNER JOIN reviews r ON r.id = f.review_id
40
+ WHERE r.repo = ? AND r.pr_number = ? AND f.posted_comment_id = ?
41
+ ORDER BY r.created_at DESC LIMIT 1`)
42
+ .get(repo, prNumber, commentId);
43
+ }
44
+ /** A repeat is a finding the previous round already raised, so the repeat
45
+ * column reads as "raised again after the author saw it". */
46
+ export function findingStatsBySeverity(sinceIso) {
47
+ return getAppDb()
48
+ .prepare(`SELECT f.severity AS severity,
49
+ COUNT(*) AS findings,
50
+ COALESCE(SUM(f.state = 'open' OR f.first_seen_review_id IS NOT NULL), 0) AS repeats,
51
+ COALESCE(SUM(f.posted_comment_id IS NOT NULL), 0) AS posted
52
+ FROM findings f
53
+ INNER JOIN reviews r ON r.id = f.review_id
54
+ WHERE r.created_at >= ?
55
+ GROUP BY f.severity
56
+ ORDER BY f.severity`)
57
+ .all(sinceIso)
58
+ .map((row) => ({
59
+ severity: String(row.severity),
60
+ findings: Number(row.findings),
61
+ repeats: Number(row.repeats),
62
+ posted: Number(row.posted),
63
+ }));
64
+ }
@@ -0,0 +1,9 @@
1
+ import type { InstallationRow } from "./rows.ts";
2
+ export declare function upsertInstallation(row: Pick<InstallationRow, "id" | "account_login" | "account_type"> & {
3
+ permissions: unknown;
4
+ events: string[];
5
+ }): void;
6
+ export declare function markInstallationSuspended(id: number, suspended: boolean): void;
7
+ export declare function markInstallationRemoved(id: number): void;
8
+ export declare function getInstallation(id: number): InstallationRow | undefined;
9
+ export declare function listInstallations(): InstallationRow[];
@@ -0,0 +1,35 @@
1
+ import { getAppDb } from "./app_db.js";
2
+ import { nowIso } from "../util/time.js";
3
+ export function upsertInstallation(row) {
4
+ getAppDb()
5
+ .prepare(`INSERT INTO installations (id, account_login, account_type, permissions, events, created_at)
6
+ VALUES (?, ?, ?, ?, ?, ?)
7
+ ON CONFLICT(id) DO UPDATE SET
8
+ account_login = excluded.account_login,
9
+ account_type = excluded.account_type,
10
+ permissions = excluded.permissions,
11
+ events = excluded.events,
12
+ suspended_at = NULL,
13
+ removed_at = NULL`)
14
+ .run(row.id, row.account_login, row.account_type, JSON.stringify(row.permissions), JSON.stringify(row.events), nowIso());
15
+ }
16
+ export function markInstallationSuspended(id, suspended) {
17
+ getAppDb()
18
+ .prepare(`UPDATE installations SET suspended_at = ? WHERE id = ?`)
19
+ .run(suspended ? nowIso() : null, id);
20
+ }
21
+ export function markInstallationRemoved(id) {
22
+ getAppDb()
23
+ .prepare(`UPDATE installations SET removed_at = ? WHERE id = ?`)
24
+ .run(nowIso(), id);
25
+ }
26
+ export function getInstallation(id) {
27
+ return getAppDb()
28
+ .prepare(`SELECT * FROM installations WHERE id = ?`)
29
+ .get(id);
30
+ }
31
+ export function listInstallations() {
32
+ return getAppDb()
33
+ .prepare(`SELECT * FROM installations ORDER BY account_login`)
34
+ .all();
35
+ }
@@ -0,0 +1,5 @@
1
+ import type { JobLogRow } from "./rows.ts";
2
+ export declare function appendLog(jobId: string, level: string, message: string): number;
3
+ /** `fromSeq` makes the SSE stream resumable: a client that reconnects asks
4
+ * for everything after the last `seq` it saw. */
5
+ export declare function listLogs(jobId: string, fromSeq?: number): JobLogRow[];
@@ -0,0 +1,17 @@
1
+ import { getAppDb } from "./app_db.js";
2
+ import { nowIso } from "../util/time.js";
3
+ export function appendLog(jobId, level, message) {
4
+ const db = getAppDb();
5
+ const nextSeq = (db
6
+ .prepare(`SELECT MAX(seq) AS max_seq FROM job_logs WHERE job_id = ?`)
7
+ .get(jobId)?.max_seq ?? 0) + 1;
8
+ db.prepare(`INSERT INTO job_logs (job_id, seq, at, level, message) VALUES (?, ?, ?, ?, ?)`).run(jobId, nextSeq, nowIso(), level, message);
9
+ return nextSeq;
10
+ }
11
+ /** `fromSeq` makes the SSE stream resumable: a client that reconnects asks
12
+ * for everything after the last `seq` it saw. */
13
+ export function listLogs(jobId, fromSeq = 0) {
14
+ return getAppDb()
15
+ .prepare(`SELECT * FROM job_logs WHERE job_id = ? AND seq > ? ORDER BY seq`)
16
+ .all(jobId, fromSeq);
17
+ }
@@ -0,0 +1,27 @@
1
+ import type { JobRow } from "./rows.ts";
2
+ export declare function insertJob(row: {
3
+ id: string;
4
+ type: string;
5
+ repo: string;
6
+ prNumber?: number;
7
+ args?: unknown;
8
+ deliveryId?: string;
9
+ queueKey?: string;
10
+ }): void;
11
+ export declare function setJobStatus(id: string, status: JobRow["status"], patch?: Partial<Pick<JobRow, "error" | "superseded_by" | "cancel_reason">>): void;
12
+ export declare function getJob(id: string): JobRow | undefined;
13
+ export declare function listJobs(filter?: {
14
+ status?: string;
15
+ repo?: string;
16
+ }): JobRow[];
17
+ /** The queued job for this (repo, PR), if any — the unique partial index
18
+ * guarantees there is at most one. */
19
+ export declare function getQueuedJob(repo: string, prNumber: number): JobRow | undefined;
20
+ export declare function getQueuedJobByKey(queueKey: string): JobRow | undefined;
21
+ export declare function getRunningJobByKey(queueKey: string): JobRow | undefined;
22
+ export declare function listJobsByQueueKey(queueKey: string, statuses: string[]): JobRow[];
23
+ export declare function getOldestQueuedJob(): JobRow | undefined;
24
+ /** Atomically queued -> running. Returns false if someone else claimed it
25
+ * first (or it was superseded/canceled in the meantime) — the caller must
26
+ * not assume a claim it did not win. */
27
+ export declare function claimJob(id: string): boolean;
@@ -0,0 +1,86 @@
1
+ /** Row storage only; queue behaviour (claim, supersede, debounce) lives in
2
+ * `services/jobs.ts`, built on top of these primitives. */
3
+ import { getAppDb } from "./app_db.js";
4
+ import { nowIso } from "../util/time.js";
5
+ export function insertJob(row) {
6
+ getAppDb()
7
+ .prepare(`INSERT INTO jobs
8
+ (id, type, repo, pr_number, status, args, delivery_id, queue_key, created_at)
9
+ VALUES (?, ?, ?, ?, 'queued', ?, ?, ?, ?)`)
10
+ .run(row.id, row.type, row.repo, row.prNumber ?? null, JSON.stringify(row.args ?? {}), row.deliveryId ?? null, row.queueKey ?? null, nowIso());
11
+ }
12
+ export function setJobStatus(id, status, patch = {}) {
13
+ const startedAt = status === "running" ? nowIso() : undefined;
14
+ const finishedAt = ["done", "failed", "canceled"].includes(status)
15
+ ? nowIso()
16
+ : undefined;
17
+ getAppDb()
18
+ .prepare(`UPDATE jobs SET status = ?,
19
+ started_at = COALESCE(?, started_at),
20
+ finished_at = COALESCE(?, finished_at),
21
+ error = COALESCE(?, error),
22
+ superseded_by = COALESCE(?, superseded_by),
23
+ cancel_reason = COALESCE(?, cancel_reason)
24
+ WHERE id = ?`)
25
+ .run(status, startedAt ?? null, finishedAt ?? null, patch.error ?? null, patch.superseded_by ?? null, patch.cancel_reason ?? null, id);
26
+ }
27
+ export function getJob(id) {
28
+ return getAppDb().prepare(`SELECT * FROM jobs WHERE id = ?`).get(id);
29
+ }
30
+ export function listJobs(filter = {}) {
31
+ const clauses = [];
32
+ const params = [];
33
+ if (filter.status) {
34
+ clauses.push("status = ?");
35
+ params.push(filter.status);
36
+ }
37
+ if (filter.repo) {
38
+ clauses.push("repo = ?");
39
+ params.push(filter.repo);
40
+ }
41
+ const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
42
+ return getAppDb()
43
+ .prepare(`SELECT * FROM jobs ${where} ORDER BY created_at DESC`)
44
+ .all(...params);
45
+ }
46
+ /** The queued job for this (repo, PR), if any — the unique partial index
47
+ * guarantees there is at most one. */
48
+ export function getQueuedJob(repo, prNumber) {
49
+ return getAppDb()
50
+ .prepare(`SELECT * FROM jobs
51
+ WHERE repo = ? AND pr_number = ? AND status = 'queued'
52
+ AND (queue_key = ? OR (queue_key IS NULL AND type = 'review'))`)
53
+ .get(repo, prNumber, `review:${repo}:${prNumber}`);
54
+ }
55
+ export function getQueuedJobByKey(queueKey) {
56
+ return getAppDb()
57
+ .prepare(`SELECT * FROM jobs WHERE queue_key = ? AND status = 'queued'`)
58
+ .get(queueKey);
59
+ }
60
+ export function getRunningJobByKey(queueKey) {
61
+ return getAppDb()
62
+ .prepare(`SELECT * FROM jobs WHERE queue_key = ? AND status = 'running' LIMIT 1`)
63
+ .get(queueKey);
64
+ }
65
+ export function listJobsByQueueKey(queueKey, statuses) {
66
+ if (statuses.length === 0)
67
+ return [];
68
+ const placeholders = statuses.map(() => "?").join(", ");
69
+ return getAppDb()
70
+ .prepare(`SELECT * FROM jobs WHERE queue_key = ? AND status IN (${placeholders})
71
+ ORDER BY created_at ASC`)
72
+ .all(queueKey, ...statuses);
73
+ }
74
+ export function getOldestQueuedJob() {
75
+ return getAppDb()
76
+ .prepare(`SELECT * FROM jobs WHERE status = 'queued' ORDER BY created_at ASC LIMIT 1`)
77
+ .get();
78
+ }
79
+ /** Atomically queued -> running. Returns false if someone else claimed it
80
+ * first (or it was superseded/canceled in the meantime) — the caller must
81
+ * not assume a claim it did not win. */
82
+ export function claimJob(id) {
83
+ const db = getAppDb();
84
+ db.prepare(`UPDATE jobs SET status = 'running', started_at = ? WHERE id = ? AND status = 'queued'`).run(nowIso(), id);
85
+ return db.changes > 0;
86
+ }
@@ -0,0 +1,5 @@
1
+ /** Numbered, forward-only migrations — PLAN.md Section 5. Each entry is a
2
+ * list of individual statements (not one multi-statement blob) run in a
3
+ * transaction; `PRAGMA user_version` tracks how many have applied. Never
4
+ * edit an already-released migration — add a new one instead. */
5
+ export declare const migrations: string[][];
@@ -0,0 +1,258 @@
1
+ /** Numbered, forward-only migrations — PLAN.md Section 5. Each entry is a
2
+ * list of individual statements (not one multi-statement blob) run in a
3
+ * transaction; `PRAGMA user_version` tracks how many have applied. Never
4
+ * edit an already-released migration — add a new one instead. */
5
+ export const migrations = [
6
+ // 1 — initial schema
7
+ [
8
+ `CREATE TABLE installations (
9
+ id INTEGER PRIMARY KEY,
10
+ account_login TEXT NOT NULL,
11
+ account_type TEXT NOT NULL,
12
+ permissions TEXT NOT NULL DEFAULT '{}',
13
+ events TEXT NOT NULL DEFAULT '[]',
14
+ created_at TEXT NOT NULL,
15
+ suspended_at TEXT,
16
+ removed_at TEXT
17
+ )`,
18
+ `CREATE TABLE repos (
19
+ full_name TEXT PRIMARY KEY,
20
+ installation_id INTEGER,
21
+ active INTEGER NOT NULL DEFAULT 0,
22
+ auto_review INTEGER NOT NULL DEFAULT 0,
23
+ review_scope TEXT NOT NULL DEFAULT 'whole-pr',
24
+ skip_drafts INTEGER NOT NULL DEFAULT 1,
25
+ skip_bots INTEGER NOT NULL DEFAULT 1,
26
+ knowledge_built_at TEXT,
27
+ knowledge_base_sha TEXT,
28
+ settings TEXT NOT NULL DEFAULT '{}',
29
+ created_at TEXT NOT NULL
30
+ )`,
31
+ `CREATE TABLE jobs (
32
+ id TEXT PRIMARY KEY,
33
+ type TEXT NOT NULL,
34
+ repo TEXT NOT NULL,
35
+ pr_number INTEGER,
36
+ status TEXT NOT NULL,
37
+ args TEXT NOT NULL DEFAULT '{}',
38
+ delivery_id TEXT,
39
+ attempt INTEGER NOT NULL DEFAULT 0,
40
+ created_at TEXT NOT NULL,
41
+ started_at TEXT,
42
+ finished_at TEXT,
43
+ error TEXT,
44
+ superseded_by TEXT
45
+ )`,
46
+ `CREATE INDEX idx_jobs_status_created ON jobs(status, created_at)`,
47
+ `CREATE UNIQUE INDEX idx_jobs_one_queued_per_pr
48
+ ON jobs(repo, pr_number) WHERE status = 'queued'`,
49
+ `CREATE TABLE job_logs (
50
+ job_id TEXT NOT NULL,
51
+ seq INTEGER NOT NULL,
52
+ at TEXT NOT NULL,
53
+ level TEXT NOT NULL,
54
+ message TEXT NOT NULL,
55
+ PRIMARY KEY (job_id, seq)
56
+ )`,
57
+ `CREATE TABLE reviews (
58
+ id TEXT PRIMARY KEY,
59
+ repo TEXT NOT NULL,
60
+ pr_number INTEGER NOT NULL,
61
+ job_id TEXT NOT NULL,
62
+ head_sha TEXT NOT NULL,
63
+ base_sha TEXT NOT NULL,
64
+ scope TEXT NOT NULL,
65
+ model TEXT NOT NULL,
66
+ findings_count INTEGER NOT NULL DEFAULT 0,
67
+ tokens_in INTEGER NOT NULL DEFAULT 0,
68
+ tokens_out INTEGER NOT NULL DEFAULT 0,
69
+ cost REAL,
70
+ duration_ms INTEGER,
71
+ posted_review_id TEXT,
72
+ status TEXT NOT NULL,
73
+ created_at TEXT NOT NULL,
74
+ round INTEGER NOT NULL DEFAULT 1,
75
+ "trigger" TEXT,
76
+ check_run_id TEXT
77
+ )`,
78
+ `CREATE INDEX idx_reviews_repo_pr ON reviews(repo, pr_number)`,
79
+ `CREATE TABLE findings (
80
+ id TEXT PRIMARY KEY,
81
+ review_id TEXT NOT NULL,
82
+ severity TEXT NOT NULL,
83
+ path TEXT,
84
+ line_from INTEGER,
85
+ line_to INTEGER,
86
+ title TEXT NOT NULL,
87
+ body_md TEXT NOT NULL,
88
+ posted_comment_id TEXT,
89
+ thread_comment_id TEXT,
90
+ first_seen_review_id TEXT
91
+ )`,
92
+ `CREATE INDEX idx_findings_review ON findings(review_id)`,
93
+ `CREATE TABLE deliveries (
94
+ delivery_id TEXT PRIMARY KEY,
95
+ event TEXT NOT NULL,
96
+ action TEXT,
97
+ repo TEXT,
98
+ pr_number INTEGER,
99
+ received_at TEXT NOT NULL,
100
+ outcome TEXT NOT NULL,
101
+ reason TEXT
102
+ )`,
103
+ `CREATE INDEX idx_deliveries_repo ON deliveries(repo, received_at)`,
104
+ `CREATE TABLE sessions (
105
+ token_hash TEXT PRIMARY KEY,
106
+ username TEXT NOT NULL,
107
+ created_at TEXT NOT NULL,
108
+ expires_at TEXT NOT NULL,
109
+ last_seen_at TEXT NOT NULL
110
+ )`,
111
+ `CREATE TABLE drift (
112
+ repo TEXT PRIMARY KEY,
113
+ as_of TEXT NOT NULL,
114
+ prs_since INTEGER NOT NULL,
115
+ commits_since INTEGER NOT NULL,
116
+ files_changed INTEGER NOT NULL
117
+ )`,
118
+ ],
119
+ // 2 — P7 records when a review fell back to an issue comment
120
+ [`ALTER TABLE reviews ADD COLUMN posted_fallback INTEGER NOT NULL DEFAULT 0`],
121
+ // 3 — durable conversation replies and purpose-specific queued jobs
122
+ [
123
+ `ALTER TABLE jobs ADD COLUMN queue_key TEXT`,
124
+ `UPDATE jobs SET queue_key = 'review:' || repo || ':' || pr_number
125
+ WHERE type = 'review' AND status = 'queued' AND pr_number IS NOT NULL`,
126
+ `DROP INDEX idx_jobs_one_queued_per_pr`,
127
+ `CREATE UNIQUE INDEX idx_jobs_one_queued_key
128
+ ON jobs(queue_key) WHERE status = 'queued' AND queue_key IS NOT NULL`,
129
+ `CREATE UNIQUE INDEX idx_jobs_one_legacy_review_per_pr
130
+ ON jobs(repo, pr_number)
131
+ WHERE status = 'queued' AND type = 'review' AND queue_key IS NULL`,
132
+ `CREATE TABLE reply_requests (
133
+ id TEXT PRIMARY KEY,
134
+ repo TEXT NOT NULL,
135
+ pr_number INTEGER NOT NULL,
136
+ source_kind TEXT NOT NULL,
137
+ source_comment_id TEXT NOT NULL,
138
+ target_comment_id TEXT,
139
+ source_body TEXT NOT NULL,
140
+ source_author TEXT,
141
+ source_path TEXT,
142
+ source_line INTEGER,
143
+ source_commit_id TEXT,
144
+ status TEXT NOT NULL,
145
+ answer_md TEXT,
146
+ posted_comment_id TEXT,
147
+ job_id TEXT,
148
+ error TEXT,
149
+ attempts INTEGER NOT NULL DEFAULT 0,
150
+ created_at TEXT NOT NULL,
151
+ updated_at TEXT NOT NULL,
152
+ UNIQUE(repo, source_kind, source_comment_id)
153
+ )`,
154
+ `CREATE INDEX idx_reply_requests_status
155
+ ON reply_requests(status, created_at)`,
156
+ ],
157
+ // 4 — drift separates pull requests opened since the build from older
158
+ // ones that were merely touched again
159
+ [`ALTER TABLE drift ADD COLUMN prs_updated INTEGER NOT NULL DEFAULT 0`],
160
+ // 5 — codegraph tools are an opt-in cost per repo, off until asked for
161
+ [`ALTER TABLE repos ADD COLUMN use_codegraph INTEGER NOT NULL DEFAULT 0`],
162
+ // 6 — why a job was canceled (dashboard, superseded, remote abort, …)
163
+ [`ALTER TABLE jobs ADD COLUMN cancel_reason TEXT`],
164
+ // 7 — subjects, carry-over fields, reviews table rebuild for nullable PR keys
165
+ [
166
+ `CREATE TABLE reviews_new (
167
+ id TEXT PRIMARY KEY,
168
+ kind TEXT NOT NULL DEFAULT 'pr',
169
+ subject_id TEXT,
170
+ repo TEXT NOT NULL,
171
+ pr_number INTEGER,
172
+ branch TEXT,
173
+ token_id TEXT,
174
+ token_name TEXT,
175
+ job_id TEXT NOT NULL,
176
+ head_sha TEXT,
177
+ base_sha TEXT,
178
+ scope TEXT NOT NULL,
179
+ model TEXT NOT NULL,
180
+ findings_count INTEGER NOT NULL DEFAULT 0,
181
+ open_count INTEGER NOT NULL DEFAULT 0,
182
+ closed_count INTEGER NOT NULL DEFAULT 0,
183
+ tokens_in INTEGER NOT NULL DEFAULT 0,
184
+ tokens_out INTEGER NOT NULL DEFAULT 0,
185
+ cost REAL,
186
+ duration_ms INTEGER,
187
+ posted_review_id TEXT,
188
+ status TEXT NOT NULL,
189
+ created_at TEXT NOT NULL,
190
+ round INTEGER NOT NULL DEFAULT 1,
191
+ "trigger" TEXT,
192
+ check_run_id TEXT,
193
+ posted_fallback INTEGER NOT NULL DEFAULT 0,
194
+ guide_built_at TEXT,
195
+ codegraph TEXT
196
+ )`,
197
+ `INSERT INTO reviews_new
198
+ (id, kind, repo, pr_number, job_id, head_sha, base_sha, scope, model,
199
+ findings_count, tokens_in, tokens_out, cost, duration_ms, posted_review_id,
200
+ status, created_at, round, "trigger", check_run_id, posted_fallback)
201
+ SELECT id, 'pr', repo, pr_number, job_id, head_sha, base_sha, scope, model,
202
+ findings_count, tokens_in, tokens_out, cost, duration_ms, posted_review_id,
203
+ status, created_at, round, "trigger", check_run_id, posted_fallback
204
+ FROM reviews`,
205
+ `DROP TABLE reviews`,
206
+ `ALTER TABLE reviews_new RENAME TO reviews`,
207
+ `CREATE INDEX idx_reviews_repo_pr ON reviews(repo, pr_number)`,
208
+ `CREATE INDEX idx_reviews_subject ON reviews(subject_id, created_at)`,
209
+ `CREATE INDEX idx_reviews_token ON reviews(token_id, created_at)`,
210
+ `ALTER TABLE findings ADD COLUMN anchor_text TEXT`,
211
+ `ALTER TABLE findings ADD COLUMN state TEXT NOT NULL DEFAULT 'new'`,
212
+ `ALTER TABLE findings ADD COLUMN carried_from_finding_id TEXT`,
213
+ `ALTER TABLE findings ADD COLUMN close_reason TEXT`,
214
+ `UPDATE findings SET state = 'open' WHERE first_seen_review_id IS NOT NULL`,
215
+ `CREATE TABLE subjects (
216
+ id TEXT PRIMARY KEY,
217
+ kind TEXT NOT NULL,
218
+ repo TEXT NOT NULL,
219
+ pr_number INTEGER,
220
+ branch TEXT,
221
+ token_id TEXT,
222
+ created_at TEXT NOT NULL,
223
+ updated_at TEXT NOT NULL
224
+ )`,
225
+ `CREATE UNIQUE INDEX idx_subjects_pr ON subjects(repo, pr_number) WHERE kind = 'pr'`,
226
+ `CREATE UNIQUE INDEX idx_subjects_remote ON subjects(repo, branch, token_id) WHERE kind = 'remote'`,
227
+ `CREATE TABLE subject_revisions (
228
+ subject_id TEXT PRIMARY KEY,
229
+ review_id TEXT NOT NULL,
230
+ files_json TEXT NOT NULL,
231
+ visible_paths_json TEXT NOT NULL,
232
+ guide_built_at TEXT,
233
+ created_at TEXT NOT NULL
234
+ )`,
235
+ ],
236
+ // 8 — remote review bearer tokens and large submit payloads
237
+ [
238
+ `CREATE TABLE remote_tokens (
239
+ id TEXT PRIMARY KEY,
240
+ name TEXT NOT NULL,
241
+ token_hash TEXT NOT NULL UNIQUE,
242
+ active INTEGER NOT NULL DEFAULT 1,
243
+ created_at TEXT NOT NULL,
244
+ last_used_at TEXT
245
+ )`,
246
+ `CREATE UNIQUE INDEX idx_remote_tokens_name ON remote_tokens(name)`,
247
+ `CREATE TABLE remote_review_inputs (
248
+ job_id TEXT PRIMARY KEY,
249
+ revision_json TEXT NOT NULL,
250
+ capabilities_json TEXT NOT NULL,
251
+ request_id TEXT NOT NULL,
252
+ token_id TEXT NOT NULL,
253
+ created_at TEXT NOT NULL
254
+ )`,
255
+ `CREATE UNIQUE INDEX idx_remote_inputs_request
256
+ ON remote_review_inputs(token_id, request_id)`,
257
+ ],
258
+ ];
@@ -0,0 +1,11 @@
1
+ import type { RemoteReviewInputRow } from "./rows.ts";
2
+ export declare function insertRemoteReviewInput(input: {
3
+ jobId: string;
4
+ revisionJson: string;
5
+ capabilitiesJson: string;
6
+ requestId: string;
7
+ tokenId: string;
8
+ }): void;
9
+ export declare function findRemoteReviewInputByRequest(tokenId: string, requestId: string): RemoteReviewInputRow | undefined;
10
+ export declare function getRemoteReviewInput(jobId: string): RemoteReviewInputRow | undefined;
11
+ export declare function deleteRemoteReviewInput(jobId: string): void;
@@ -0,0 +1,25 @@
1
+ import { getAppDb } from "./app_db.js";
2
+ import { nowIso } from "../util/time.js";
3
+ export function insertRemoteReviewInput(input) {
4
+ getAppDb()
5
+ .prepare(`INSERT INTO remote_review_inputs
6
+ (job_id, revision_json, capabilities_json, request_id, token_id, created_at)
7
+ VALUES (?, ?, ?, ?, ?, ?)`)
8
+ .run(input.jobId, input.revisionJson, input.capabilitiesJson, input.requestId, input.tokenId, nowIso());
9
+ }
10
+ export function findRemoteReviewInputByRequest(tokenId, requestId) {
11
+ return getAppDb()
12
+ .prepare(`SELECT * FROM remote_review_inputs
13
+ WHERE token_id = ? AND request_id = ?`)
14
+ .get(tokenId, requestId);
15
+ }
16
+ export function getRemoteReviewInput(jobId) {
17
+ return getAppDb()
18
+ .prepare(`SELECT * FROM remote_review_inputs WHERE job_id = ?`)
19
+ .get(jobId);
20
+ }
21
+ export function deleteRemoteReviewInput(jobId) {
22
+ getAppDb()
23
+ .prepare(`DELETE FROM remote_review_inputs WHERE job_id = ?`)
24
+ .run(jobId);
25
+ }
@@ -0,0 +1,10 @@
1
+ import type { RemoteTokenRow } from "./rows.ts";
2
+ export declare function insertRemoteToken(id: string, name: string, tokenHash: string): void;
3
+ export declare function findRemoteTokenByHash(tokenHash: string): RemoteTokenRow | undefined;
4
+ export declare function getRemoteToken(id: string): RemoteTokenRow | undefined;
5
+ export declare function findRemoteTokenByName(name: string): RemoteTokenRow | undefined;
6
+ export declare function listRemoteTokens(): RemoteTokenRow[];
7
+ export declare function setRemoteTokenActive(id: string, active: boolean): void;
8
+ export declare function deleteRemoteToken(id: string): void;
9
+ /** At most one write per minute per token (plan §14.2). */
10
+ export declare function touchRemoteToken(id: string): void;
@@ -0,0 +1,45 @@
1
+ import { getAppDb } from "./app_db.js";
2
+ import { nowIso } from "../util/time.js";
3
+ export function insertRemoteToken(id, name, tokenHash) {
4
+ getAppDb()
5
+ .prepare(`INSERT INTO remote_tokens (id, name, token_hash, active, created_at)
6
+ VALUES (?, ?, ?, 1, ?)`)
7
+ .run(id, name, tokenHash, nowIso());
8
+ }
9
+ export function findRemoteTokenByHash(tokenHash) {
10
+ return getAppDb()
11
+ .prepare(`SELECT * FROM remote_tokens WHERE token_hash = ?`)
12
+ .get(tokenHash);
13
+ }
14
+ export function getRemoteToken(id) {
15
+ return getAppDb()
16
+ .prepare(`SELECT * FROM remote_tokens WHERE id = ?`)
17
+ .get(id);
18
+ }
19
+ export function findRemoteTokenByName(name) {
20
+ return getAppDb()
21
+ .prepare(`SELECT * FROM remote_tokens WHERE name = ?`)
22
+ .get(name);
23
+ }
24
+ export function listRemoteTokens() {
25
+ return getAppDb()
26
+ .prepare(`SELECT * FROM remote_tokens ORDER BY created_at DESC`)
27
+ .all();
28
+ }
29
+ export function setRemoteTokenActive(id, active) {
30
+ getAppDb()
31
+ .prepare(`UPDATE remote_tokens SET active = ? WHERE id = ?`)
32
+ .run(active ? 1 : 0, id);
33
+ }
34
+ export function deleteRemoteToken(id) {
35
+ getAppDb().prepare(`DELETE FROM remote_tokens WHERE id = ?`).run(id);
36
+ }
37
+ /** At most one write per minute per token (plan §14.2). */
38
+ export function touchRemoteToken(id) {
39
+ const now = nowIso();
40
+ const minuteAgo = new Date(Date.now() - 60_000).toISOString();
41
+ getAppDb()
42
+ .prepare(`UPDATE remote_tokens SET last_used_at = ?
43
+ WHERE id = ? AND (last_used_at IS NULL OR last_used_at < ?)`)
44
+ .run(now, id, minuteAgo);
45
+ }
@@ -0,0 +1,24 @@
1
+ import type { ReplyRequestRow } from "./rows.ts";
2
+ export type ReplyRequestInput = {
3
+ repo: string;
4
+ prNumber: number;
5
+ sourceKind: "review_comment" | "issue_comment";
6
+ sourceCommentId: string;
7
+ targetCommentId?: string;
8
+ sourceBody: string;
9
+ sourceAuthor?: string;
10
+ sourcePath?: string;
11
+ sourceLine?: number;
12
+ sourceCommitId?: string;
13
+ deliveryId?: string;
14
+ };
15
+ export declare function createReplyRequestAndJob(input: ReplyRequestInput): {
16
+ request: ReplyRequestRow;
17
+ created: boolean;
18
+ };
19
+ export declare function getReplyRequest(id: string): ReplyRequestRow | undefined;
20
+ export declare function findReplyRequest(repo: string, sourceKind: string, sourceCommentId: string): ReplyRequestRow | undefined;
21
+ export declare function findReplyByPostedComment(repo: string, prNumber: number, commentId: string): ReplyRequestRow | undefined;
22
+ export declare function setReplyStatus(id: string, status: ReplyRequestRow["status"], patch?: Partial<Pick<ReplyRequestRow, "answer_md" | "posted_comment_id" | "error">>): void;
23
+ export declare function setReplyJobId(id: string, jobId: string): void;
24
+ export declare function listRecoverableReplies(): ReplyRequestRow[];