@nanonets/graft 0.13.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. package/README.md +26 -10
  2. package/dist/ai/crux.d.ts +14 -1
  3. package/dist/ai/crux.d.ts.map +1 -1
  4. package/dist/ai/crux.js +39 -51
  5. package/dist/ai/crux.js.map +1 -1
  6. package/dist/ai/failure.d.ts +13 -2
  7. package/dist/ai/failure.d.ts.map +1 -1
  8. package/dist/ai/failure.js +16 -4
  9. package/dist/ai/failure.js.map +1 -1
  10. package/dist/ai/llm/factory.d.ts +10 -1
  11. package/dist/ai/llm/factory.d.ts.map +1 -1
  12. package/dist/ai/llm/factory.js +16 -0
  13. package/dist/ai/llm/factory.js.map +1 -1
  14. package/dist/ai/llm/litellm.d.ts +34 -0
  15. package/dist/ai/llm/litellm.d.ts.map +1 -0
  16. package/dist/ai/llm/litellm.js +45 -0
  17. package/dist/ai/llm/litellm.js.map +1 -0
  18. package/dist/ai/llm/openai.d.ts.map +1 -1
  19. package/dist/ai/llm/openai.js +66 -7
  20. package/dist/ai/llm/openai.js.map +1 -1
  21. package/dist/ai/llm/orcarouter.d.ts +34 -0
  22. package/dist/ai/llm/orcarouter.d.ts.map +1 -0
  23. package/dist/ai/llm/orcarouter.js +45 -0
  24. package/dist/ai/llm/orcarouter.js.map +1 -0
  25. package/dist/ai/llm/recover-tool.d.ts +19 -0
  26. package/dist/ai/llm/recover-tool.d.ts.map +1 -0
  27. package/dist/ai/llm/recover-tool.js +109 -0
  28. package/dist/ai/llm/recover-tool.js.map +1 -0
  29. package/dist/ai/providers.d.ts.map +1 -1
  30. package/dist/ai/providers.js +15 -3
  31. package/dist/ai/providers.js.map +1 -1
  32. package/dist/ai/synthesize.d.ts.map +1 -1
  33. package/dist/ai/synthesize.js +19 -5
  34. package/dist/ai/synthesize.js.map +1 -1
  35. package/dist/app/checkout.d.ts +28 -0
  36. package/dist/app/checkout.d.ts.map +1 -0
  37. package/dist/app/checkout.js +100 -0
  38. package/dist/app/checkout.js.map +1 -0
  39. package/dist/app/events.d.ts +37 -0
  40. package/dist/app/events.d.ts.map +1 -0
  41. package/dist/app/events.js +59 -0
  42. package/dist/app/events.js.map +1 -0
  43. package/dist/app/identity.d.ts +54 -0
  44. package/dist/app/identity.d.ts.map +1 -0
  45. package/dist/app/identity.js +96 -0
  46. package/dist/app/identity.js.map +1 -0
  47. package/dist/app/main.d.ts +2 -0
  48. package/dist/app/main.d.ts.map +1 -0
  49. package/dist/app/main.js +56 -0
  50. package/dist/app/main.js.map +1 -0
  51. package/dist/app/pages.d.ts +39 -0
  52. package/dist/app/pages.d.ts.map +1 -0
  53. package/dist/app/pages.js +86 -0
  54. package/dist/app/pages.js.map +1 -0
  55. package/dist/app/queue.d.ts +40 -0
  56. package/dist/app/queue.d.ts.map +1 -0
  57. package/dist/app/queue.js +68 -0
  58. package/dist/app/queue.js.map +1 -0
  59. package/dist/app/review.d.ts +23 -0
  60. package/dist/app/review.d.ts.map +1 -0
  61. package/dist/app/review.js +139 -0
  62. package/dist/app/review.js.map +1 -0
  63. package/dist/app/server.d.ts +40 -0
  64. package/dist/app/server.d.ts.map +1 -0
  65. package/dist/app/server.js +117 -0
  66. package/dist/app/server.js.map +1 -0
  67. package/dist/ask/ask.d.ts +35 -0
  68. package/dist/ask/ask.d.ts.map +1 -1
  69. package/dist/ask/ask.js +427 -63
  70. package/dist/ask/ask.js.map +1 -1
  71. package/dist/ask/file-rank.d.ts +72 -0
  72. package/dist/ask/file-rank.d.ts.map +1 -0
  73. package/dist/ask/file-rank.js +160 -0
  74. package/dist/ask/file-rank.js.map +1 -0
  75. package/dist/ask/file-selection.d.ts +4 -0
  76. package/dist/ask/file-selection.d.ts.map +1 -1
  77. package/dist/ask/file-selection.js +25 -0
  78. package/dist/ask/file-selection.js.map +1 -1
  79. package/dist/ask/fuse.d.ts +16 -0
  80. package/dist/ask/fuse.d.ts.map +1 -1
  81. package/dist/ask/fuse.js +21 -4
  82. package/dist/ask/fuse.js.map +1 -1
  83. package/dist/ask/graphrank.d.ts +18 -0
  84. package/dist/ask/graphrank.d.ts.map +1 -1
  85. package/dist/ask/graphrank.js +65 -19
  86. package/dist/ask/graphrank.js.map +1 -1
  87. package/dist/blast/blast-cli.d.ts +5 -0
  88. package/dist/blast/blast-cli.d.ts.map +1 -1
  89. package/dist/blast/blast-cli.js +21 -30
  90. package/dist/blast/blast-cli.js.map +1 -1
  91. package/dist/blast/blast.d.ts +9 -0
  92. package/dist/blast/blast.d.ts.map +1 -1
  93. package/dist/blast/blast.js.map +1 -1
  94. package/dist/blast/name.d.ts +13 -0
  95. package/dist/blast/name.d.ts.map +1 -1
  96. package/dist/blast/name.js +36 -0
  97. package/dist/blast/name.js.map +1 -1
  98. package/dist/blast/owners.d.ts +85 -0
  99. package/dist/blast/owners.d.ts.map +1 -0
  100. package/dist/blast/owners.js +272 -0
  101. package/dist/blast/owners.js.map +1 -0
  102. package/dist/blast/render.d.ts.map +1 -1
  103. package/dist/blast/render.js +105 -0
  104. package/dist/blast/render.js.map +1 -1
  105. package/dist/blast/viz.d.ts.map +1 -1
  106. package/dist/blast/viz.js +10 -0
  107. package/dist/blast/viz.js.map +1 -1
  108. package/dist/claude/format.js +1 -1
  109. package/dist/claude/format.js.map +1 -1
  110. package/dist/claude/hooks.d.ts.map +1 -1
  111. package/dist/claude/hooks.js +132 -22
  112. package/dist/claude/hooks.js.map +1 -1
  113. package/dist/claude/init.d.ts +1 -0
  114. package/dist/claude/init.d.ts.map +1 -1
  115. package/dist/claude/init.js +1 -1
  116. package/dist/claude/init.js.map +1 -1
  117. package/dist/claude/session-metrics.d.ts +63 -0
  118. package/dist/claude/session-metrics.d.ts.map +1 -0
  119. package/dist/claude/session-metrics.js +181 -0
  120. package/dist/claude/session-metrics.js.map +1 -0
  121. package/dist/claude/settings-merge.d.ts +18 -1
  122. package/dist/claude/settings-merge.d.ts.map +1 -1
  123. package/dist/claude/settings-merge.js +70 -27
  124. package/dist/claude/settings-merge.js.map +1 -1
  125. package/dist/claude/state.d.ts +25 -0
  126. package/dist/claude/state.d.ts.map +1 -1
  127. package/dist/claude/state.js +14 -1
  128. package/dist/claude/state.js.map +1 -1
  129. package/dist/claude/statusline.d.ts +5 -3
  130. package/dist/claude/statusline.d.ts.map +1 -1
  131. package/dist/claude/statusline.js +6 -4
  132. package/dist/claude/statusline.js.map +1 -1
  133. package/dist/claude/tally.d.ts +23 -0
  134. package/dist/claude/tally.d.ts.map +1 -0
  135. package/dist/claude/tally.js +138 -0
  136. package/dist/claude/tally.js.map +1 -0
  137. package/dist/cli.js +145 -4
  138. package/dist/cli.js.map +1 -1
  139. package/dist/context/build.d.ts +11 -0
  140. package/dist/context/build.d.ts.map +1 -1
  141. package/dist/context/build.js +49 -9
  142. package/dist/context/build.js.map +1 -1
  143. package/dist/context/check.d.ts.map +1 -1
  144. package/dist/context/check.js +5 -12
  145. package/dist/context/check.js.map +1 -1
  146. package/dist/context/node-file.d.ts +2 -1
  147. package/dist/context/node-file.d.ts.map +1 -1
  148. package/dist/context/node-file.js +70 -3
  149. package/dist/context/node-file.js.map +1 -1
  150. package/dist/context/savings.d.ts +8 -0
  151. package/dist/context/savings.d.ts.map +1 -1
  152. package/dist/context/savings.js +14 -0
  153. package/dist/context/savings.js.map +1 -1
  154. package/dist/engine.d.ts +4 -0
  155. package/dist/engine.d.ts.map +1 -1
  156. package/dist/engine.js +2 -0
  157. package/dist/engine.js.map +1 -1
  158. package/dist/graph/bindings.d.ts +6 -5
  159. package/dist/graph/bindings.d.ts.map +1 -1
  160. package/dist/graph/bindings.js +117 -5
  161. package/dist/graph/bindings.js.map +1 -1
  162. package/dist/graph/build.d.ts +4 -0
  163. package/dist/graph/build.d.ts.map +1 -1
  164. package/dist/graph/build.js +7 -4
  165. package/dist/graph/build.js.map +1 -1
  166. package/dist/graph/cards.d.ts +4 -3
  167. package/dist/graph/cards.d.ts.map +1 -1
  168. package/dist/graph/cards.js +34 -6
  169. package/dist/graph/cards.js.map +1 -1
  170. package/dist/graph/check.d.ts.map +1 -1
  171. package/dist/graph/check.js +36 -6
  172. package/dist/graph/check.js.map +1 -1
  173. package/dist/graph/enrich.d.ts +4 -4
  174. package/dist/graph/enrich.d.ts.map +1 -1
  175. package/dist/graph/enrich.js +32 -6
  176. package/dist/graph/enrich.js.map +1 -1
  177. package/dist/graph/extract.d.ts +9 -2
  178. package/dist/graph/extract.d.ts.map +1 -1
  179. package/dist/graph/extract.js +356 -12
  180. package/dist/graph/extract.js.map +1 -1
  181. package/dist/graph/fingerprint.d.ts +6 -1
  182. package/dist/graph/fingerprint.d.ts.map +1 -1
  183. package/dist/graph/fingerprint.js +7 -3
  184. package/dist/graph/fingerprint.js.map +1 -1
  185. package/dist/graph/generic.d.ts +10 -0
  186. package/dist/graph/generic.d.ts.map +1 -1
  187. package/dist/graph/generic.js +20 -3
  188. package/dist/graph/generic.js.map +1 -1
  189. package/dist/graph/invariants.d.ts +3 -2
  190. package/dist/graph/invariants.d.ts.map +1 -1
  191. package/dist/graph/invariants.js +6 -3
  192. package/dist/graph/invariants.js.map +1 -1
  193. package/dist/graph/refresh.d.ts.map +1 -1
  194. package/dist/graph/refresh.js +6 -2
  195. package/dist/graph/refresh.js.map +1 -1
  196. package/dist/graph/resolve.d.ts +1 -1
  197. package/dist/graph/resolve.d.ts.map +1 -1
  198. package/dist/graph/resolve.js +106 -5
  199. package/dist/graph/resolve.js.map +1 -1
  200. package/dist/graph/scopes.d.ts.map +1 -1
  201. package/dist/graph/scopes.js +2 -1
  202. package/dist/graph/scopes.js.map +1 -1
  203. package/dist/graph/source-files.d.ts +7 -2
  204. package/dist/graph/source-files.d.ts.map +1 -1
  205. package/dist/graph/source-files.js +19 -6
  206. package/dist/graph/source-files.js.map +1 -1
  207. package/dist/graph/workspace-cli.d.ts +2 -0
  208. package/dist/graph/workspace-cli.d.ts.map +1 -1
  209. package/dist/graph/workspace-cli.js +3 -0
  210. package/dist/graph/workspace-cli.js.map +1 -1
  211. package/dist/graph/workspace.d.ts +2 -0
  212. package/dist/graph/workspace.d.ts.map +1 -1
  213. package/dist/graph/workspace.js +173 -6
  214. package/dist/graph/workspace.js.map +1 -1
  215. package/dist/hosts/antigravity.d.ts +2 -2
  216. package/dist/hosts/antigravity.d.ts.map +1 -1
  217. package/dist/hosts/codex-hooks.d.ts +2 -6
  218. package/dist/hosts/codex-hooks.d.ts.map +1 -1
  219. package/dist/hosts/codex-hooks.js +7 -30
  220. package/dist/hosts/codex-hooks.js.map +1 -1
  221. package/dist/hosts/config-write.d.ts +36 -0
  222. package/dist/hosts/config-write.d.ts.map +1 -0
  223. package/dist/hosts/config-write.js +63 -0
  224. package/dist/hosts/config-write.js.map +1 -0
  225. package/dist/hosts/cursor-hooks.d.ts +18 -0
  226. package/dist/hosts/cursor-hooks.d.ts.map +1 -0
  227. package/dist/hosts/cursor-hooks.js +100 -0
  228. package/dist/hosts/cursor-hooks.js.map +1 -0
  229. package/dist/hosts/init.d.ts +2 -2
  230. package/dist/hosts/init.d.ts.map +1 -1
  231. package/dist/hosts/init.js +8 -2
  232. package/dist/hosts/init.js.map +1 -1
  233. package/dist/hosts/mcp-config.d.ts +15 -5
  234. package/dist/hosts/mcp-config.d.ts.map +1 -1
  235. package/dist/hosts/mcp-config.js +51 -15
  236. package/dist/hosts/mcp-config.js.map +1 -1
  237. package/dist/hosts/plan.d.ts.map +1 -1
  238. package/dist/hosts/plan.js +2 -0
  239. package/dist/hosts/plan.js.map +1 -1
  240. package/dist/hosts/retract.d.ts +47 -0
  241. package/dist/hosts/retract.d.ts.map +1 -0
  242. package/dist/hosts/retract.js +473 -0
  243. package/dist/hosts/retract.js.map +1 -0
  244. package/dist/index.d.ts +2 -0
  245. package/dist/index.d.ts.map +1 -1
  246. package/dist/index.js +2 -0
  247. package/dist/index.js.map +1 -1
  248. package/dist/ingest/fs.d.ts +39 -5
  249. package/dist/ingest/fs.d.ts.map +1 -1
  250. package/dist/ingest/fs.js +104 -11
  251. package/dist/ingest/fs.js.map +1 -1
  252. package/dist/mcp/tool-names.d.ts +20 -0
  253. package/dist/mcp/tool-names.d.ts.map +1 -0
  254. package/dist/mcp/tool-names.js +36 -0
  255. package/dist/mcp/tool-names.js.map +1 -0
  256. package/dist/mcp/tools.d.ts +11 -2
  257. package/dist/mcp/tools.d.ts.map +1 -1
  258. package/dist/mcp/tools.js +9 -21
  259. package/dist/mcp/tools.js.map +1 -1
  260. package/dist/telemetry/contract.d.ts.map +1 -1
  261. package/dist/telemetry/contract.js +17 -3
  262. package/dist/telemetry/contract.js.map +1 -1
  263. package/dist/telemetry/flush.d.ts.map +1 -1
  264. package/dist/telemetry/flush.js +1 -0
  265. package/dist/telemetry/flush.js.map +1 -1
  266. package/dist/telemetry/identity.d.ts +7 -0
  267. package/dist/telemetry/identity.d.ts.map +1 -1
  268. package/dist/telemetry/identity.js.map +1 -1
  269. package/dist/telemetry/index.d.ts +2 -2
  270. package/dist/telemetry/index.d.ts.map +1 -1
  271. package/dist/telemetry/index.js +2 -2
  272. package/dist/telemetry/index.js.map +1 -1
  273. package/dist/telemetry/sessions.d.ts +19 -2
  274. package/dist/telemetry/sessions.d.ts.map +1 -1
  275. package/dist/telemetry/sessions.js +57 -34
  276. package/dist/telemetry/sessions.js.map +1 -1
  277. package/dist/telemetry/track.d.ts +22 -3
  278. package/dist/telemetry/track.d.ts.map +1 -1
  279. package/dist/telemetry/track.js +34 -5
  280. package/dist/telemetry/track.js.map +1 -1
  281. package/dist/upkeep-run.js +1 -1
  282. package/dist/upkeep-run.js.map +1 -1
  283. package/dist/upkeep.d.ts +7 -4
  284. package/dist/upkeep.d.ts.map +1 -1
  285. package/dist/upkeep.js +2 -1
  286. package/dist/upkeep.js.map +1 -1
  287. package/dist/util/state.d.ts +12 -2
  288. package/dist/util/state.d.ts.map +1 -1
  289. package/dist/util/state.js +4 -0
  290. package/dist/util/state.js.map +1 -1
  291. package/dist/viewer/app.js +2 -1
  292. package/dist/viewer/style.css +14 -0
  293. package/dist/viz/assemble.d.ts +17 -0
  294. package/dist/viz/assemble.d.ts.map +1 -1
  295. package/dist/viz/assemble.js.map +1 -1
  296. package/package.json +12 -4
  297. package/scripts/graph-quality.mjs +3 -3
  298. package/scripts/postinstall.mjs +42 -3
  299. package/scripts/run-tests.mjs +14 -0
  300. package/scripts/tally-audit.mjs +140 -0
  301. package/dist/ai/llm/claude-cli.d.ts +0 -60
  302. package/dist/ai/llm/claude-cli.d.ts.map +0 -1
  303. package/dist/ai/llm/claude-cli.js +0 -417
  304. package/dist/ai/llm/claude-cli.js.map +0 -1
  305. package/dist/util/num.d.ts +0 -3
  306. package/dist/util/num.d.ts.map +0 -1
  307. package/dist/util/num.js +0 -20
  308. package/dist/util/num.js.map +0 -1
@@ -0,0 +1,37 @@
1
+ /**
2
+ * What arrived, and whether it is worth work.
3
+ *
4
+ * A busy installation delivers a lot of noise — labels, assignments, reviews —
5
+ * and every accepted delivery costs a clone and a parse. Narrowing happens here,
6
+ * once, so the queue only ever holds jobs that will produce a comment.
7
+ */
8
+ /** The subset of a `pull_request` delivery this App reads. */
9
+ export interface ReviewJob {
10
+ installationId: number;
11
+ owner: string;
12
+ repo: string;
13
+ /** Base repository's full name, which is where the comment goes — a fork PR
14
+ * carries a different head repo, and posting there would be posting on the
15
+ * contributor's copy. */
16
+ number: number;
17
+ baseRef: string;
18
+ headSha: string;
19
+ /** True when the head branch lives in a fork: the PR's code is not ours. */
20
+ fromFork: boolean;
21
+ }
22
+ /**
23
+ * A delivery to act on, or null with the reason it was skipped.
24
+ *
25
+ * Draft pull requests are skipped deliberately: a draft is a work in progress and
26
+ * a bot commenting on every push to one is the fastest way to be uninstalled.
27
+ * `ready_for_review` is in the accepted set so the comment appears the moment the
28
+ * author asks for eyes.
29
+ */
30
+ export declare function reviewJobFor(event: string, payload: unknown): {
31
+ job: ReviewJob;
32
+ } | {
33
+ skip: string;
34
+ };
35
+ /** Collapse queued work per pull request: only the newest push is worth reviewing. */
36
+ export declare const jobKey: (j: ReviewJob) => string;
37
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/app/events.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,8DAA8D;AAC9D,MAAM,WAAW,SAAS;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb;;6BAEyB;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,QAAQ,EAAE,OAAO,CAAC;CACnB;AAmBD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG;IAAE,GAAG,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAqCnG;AAED,sFAAsF;AACtF,eAAO,MAAM,MAAM,GAAI,GAAG,SAAS,KAAG,MAA4C,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * What arrived, and whether it is worth work.
3
+ *
4
+ * A busy installation delivers a lot of noise — labels, assignments, reviews —
5
+ * and every accepted delivery costs a clone and a parse. Narrowing happens here,
6
+ * once, so the queue only ever holds jobs that will produce a comment.
7
+ */
8
+ /** Actions that change the diff. `edited` (a retitled PR) and `labeled` do not. */
9
+ const ACTIONS = new Set(["opened", "synchronize", "reopened", "ready_for_review"]);
10
+ /**
11
+ * A delivery to act on, or null with the reason it was skipped.
12
+ *
13
+ * Draft pull requests are skipped deliberately: a draft is a work in progress and
14
+ * a bot commenting on every push to one is the fastest way to be uninstalled.
15
+ * `ready_for_review` is in the accepted set so the comment appears the moment the
16
+ * author asks for eyes.
17
+ */
18
+ export function reviewJobFor(event, payload) {
19
+ if (event === "ping")
20
+ return { skip: "ping" };
21
+ if (event !== "pull_request")
22
+ return { skip: `event ${event}` };
23
+ const p = payload;
24
+ const action = p.action ?? "";
25
+ if (!ACTIONS.has(action))
26
+ return { skip: `action ${action}` };
27
+ const pr = p.pull_request;
28
+ const installationId = p.installation?.id;
29
+ const owner = p.repository?.owner?.login;
30
+ const repo = p.repository?.name;
31
+ const number = pr?.number ?? p.number;
32
+ const baseRef = pr?.base?.ref;
33
+ const headSha = pr?.head?.sha;
34
+ if (!installationId || !owner || !repo || !number || !baseRef || !headSha) {
35
+ return { skip: "payload missing installation, repository or pull request fields" };
36
+ }
37
+ if (pr?.draft === true && action !== "ready_for_review")
38
+ return { skip: "draft" };
39
+ if (pr?.state === "closed")
40
+ return { skip: "closed" };
41
+ const base = pr?.base?.repo?.full_name;
42
+ const head = pr?.head?.repo?.full_name;
43
+ return {
44
+ job: {
45
+ installationId,
46
+ owner,
47
+ repo,
48
+ number,
49
+ baseRef,
50
+ headSha,
51
+ // A missing head repo means a deleted fork — treat it as one, since it is
52
+ // certainly not ours.
53
+ fromFork: head !== base,
54
+ },
55
+ };
56
+ }
57
+ /** Collapse queued work per pull request: only the newest push is worth reviewing. */
58
+ export const jobKey = (j) => `${j.owner}/${j.repo}#${j.number}`;
59
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/app/events.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA+BH,mFAAmF;AACnF,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,aAAa,EAAE,UAAU,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAEnF;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,OAAgB;IAC1D,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC9C,IAAI,KAAK,KAAK,cAAc;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,KAAK,EAAE,EAAE,CAAC;IAEhE,MAAM,CAAC,GAAG,OAAyB,CAAC;IACpC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,UAAU,MAAM,EAAE,EAAE,CAAC;IAE9D,MAAM,EAAE,GAAG,CAAC,CAAC,YAAY,CAAC;IAC1B,MAAM,cAAc,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC;IAC1C,MAAM,KAAK,GAAG,CAAC,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,CAAC;IACzC,MAAM,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC;IAChC,MAAM,MAAM,GAAG,EAAE,EAAE,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC;IACtC,MAAM,OAAO,GAAG,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC;IAC9B,MAAM,OAAO,GAAG,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC;IAE9B,IAAI,CAAC,cAAc,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;QAC1E,OAAO,EAAE,IAAI,EAAE,iEAAiE,EAAE,CAAC;IACrF,CAAC;IACD,IAAI,EAAE,EAAE,KAAK,KAAK,IAAI,IAAI,MAAM,KAAK,kBAAkB;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAClF,IAAI,EAAE,EAAE,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAEtD,MAAM,IAAI,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC;IACvC,MAAM,IAAI,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC;IACvC,OAAO;QACL,GAAG,EAAE;YACH,cAAc;YACd,KAAK;YACL,IAAI;YACJ,MAAM;YACN,OAAO;YACP,OAAO;YACP,0EAA0E;YAC1E,sBAAsB;YACtB,QAAQ,EAAE,IAAI,KAAK,IAAI;SACxB;KACF,CAAC;AACJ,CAAC;AAED,sFAAsF;AACtF,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAAY,EAAU,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC"}
@@ -0,0 +1,54 @@
1
+ export interface AppCredentials {
2
+ appId: string;
3
+ /** PEM, as GitHub hands it over. */
4
+ privateKey: string;
5
+ }
6
+ /**
7
+ * A signed App JWT.
8
+ *
9
+ * `iat` is backdated a minute: GitHub compares against ITS clock, and a server
10
+ * running even slightly fast has its tokens rejected as "issued in the future" —
11
+ * a failure that looks like a bad key and is not.
12
+ */
13
+ export declare function appJwt(creds: AppCredentials, nowMs: number): string;
14
+ export interface TokenResponse {
15
+ token: string;
16
+ expires_at: string;
17
+ }
18
+ /** Minimal shape of `fetch`, so tests need no network and no mocking library. */
19
+ export type Fetch = (url: string, init?: {
20
+ method?: string;
21
+ headers?: Record<string, string>;
22
+ body?: string;
23
+ }) => Promise<{
24
+ ok: boolean;
25
+ status: number;
26
+ text: () => Promise<string>;
27
+ }>;
28
+ /**
29
+ * Installation tokens, fetched once and reused until they are nearly expired.
30
+ *
31
+ * Tokens last an hour and a busy repository can fire a dozen webhooks a minute;
32
+ * without the cache every one of them spends a round trip and a signature.
33
+ */
34
+ export declare class InstallationTokens {
35
+ private readonly creds;
36
+ private readonly fetchImpl;
37
+ private readonly now;
38
+ private readonly api;
39
+ private readonly cache;
40
+ constructor(creds: AppCredentials, fetchImpl: Fetch, now?: () => number, api?: string);
41
+ get(installationId: number): Promise<string>;
42
+ /** Drop a token GitHub has rejected, so the next call fetches a fresh one. */
43
+ invalidate(installationId: number): void;
44
+ }
45
+ /**
46
+ * Is this delivery really from GitHub?
47
+ *
48
+ * The webhook endpoint is public, and everything downstream — cloning a repo,
49
+ * posting as the App — happens on its say-so. Compared in constant time because
50
+ * a byte-at-a-time comparison leaks the expected digest to anyone willing to
51
+ * measure, and forging a signature is a total compromise of the endpoint.
52
+ */
53
+ export declare function verifySignature(secret: string, body: string, header: string | undefined): boolean;
54
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/app/identity.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAOnE;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,iFAAiF;AACjF,MAAM,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,CAAC;IACxH,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CAC7B,CAAC,CAAC;AAOH;;;;;GAKG;AACH,qBAAa,kBAAkB;IAI3B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,GAAG;IANtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;gBAGpC,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,KAAK,EAChB,GAAG,GAAE,MAAM,MAAiB,EAC5B,GAAG,SAA2B;IAG3C,GAAG,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAuBlD,8EAA8E;IAC9E,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;CAGzC;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAMjG"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Who the App is, to GitHub.
3
+ *
4
+ * A GitHub App authenticates twice over: it signs a short JWT with its private
5
+ * key to prove it is the App, then trades that for an *installation* token scoped
6
+ * to one repository owner. The installation token is what matters here — it is
7
+ * the reason this exists at all. A workflow on a fork's pull request gets a
8
+ * read-only token and cannot comment; an App's installation token belongs to the
9
+ * base repository, so a fork PR is no different from any other.
10
+ *
11
+ * `node:crypto` signs the JWT, so there is no dependency to audit for something
12
+ * that handles a private key.
13
+ */
14
+ import { createHmac, createSign, timingSafeEqual } from "node:crypto";
15
+ /** GitHub rejects a JWT older than 60s or more than 10 minutes in the future. */
16
+ const JWT_TTL_S = 540; // 9 minutes, inside the limit with room for clock skew
17
+ /** Renew this long before expiry rather than racing it. */
18
+ const RENEW_MARGIN_MS = 60_000;
19
+ const b64url = (b) => Buffer.from(b).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
20
+ /**
21
+ * A signed App JWT.
22
+ *
23
+ * `iat` is backdated a minute: GitHub compares against ITS clock, and a server
24
+ * running even slightly fast has its tokens rejected as "issued in the future" —
25
+ * a failure that looks like a bad key and is not.
26
+ */
27
+ export function appJwt(creds, nowMs) {
28
+ const iat = Math.floor(nowMs / 1000) - 60;
29
+ const header = b64url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
30
+ const payload = b64url(JSON.stringify({ iat, exp: iat + JWT_TTL_S, iss: creds.appId }));
31
+ const signer = createSign("RSA-SHA256");
32
+ signer.update(`${header}.${payload}`);
33
+ return `${header}.${payload}.${b64url(signer.sign(creds.privateKey))}`;
34
+ }
35
+ /**
36
+ * Installation tokens, fetched once and reused until they are nearly expired.
37
+ *
38
+ * Tokens last an hour and a busy repository can fire a dozen webhooks a minute;
39
+ * without the cache every one of them spends a round trip and a signature.
40
+ */
41
+ export class InstallationTokens {
42
+ creds;
43
+ fetchImpl;
44
+ now;
45
+ api;
46
+ cache = new Map();
47
+ constructor(creds, fetchImpl, now = Date.now, api = "https://api.github.com") {
48
+ this.creds = creds;
49
+ this.fetchImpl = fetchImpl;
50
+ this.now = now;
51
+ this.api = api;
52
+ }
53
+ async get(installationId) {
54
+ const hit = this.cache.get(installationId);
55
+ if (hit && hit.expiresMs - RENEW_MARGIN_MS > this.now())
56
+ return hit.token;
57
+ const res = await this.fetchImpl(`${this.api}/app/installations/${installationId}/access_tokens`, {
58
+ method: "POST",
59
+ headers: {
60
+ authorization: `Bearer ${appJwt(this.creds, this.now())}`,
61
+ accept: "application/vnd.github+json",
62
+ "user-agent": "graft-app",
63
+ },
64
+ });
65
+ const body = await res.text();
66
+ if (!res.ok) {
67
+ // The body carries GitHub's reason ("integration not found", a key that does
68
+ // not match the app id); losing it turns every setup mistake into "401".
69
+ throw new Error(`installation token for ${installationId} failed: ${res.status} ${body.slice(0, 200)}`);
70
+ }
71
+ const parsed = JSON.parse(body);
72
+ this.cache.set(installationId, { token: parsed.token, expiresMs: Date.parse(parsed.expires_at) });
73
+ return parsed.token;
74
+ }
75
+ /** Drop a token GitHub has rejected, so the next call fetches a fresh one. */
76
+ invalidate(installationId) {
77
+ this.cache.delete(installationId);
78
+ }
79
+ }
80
+ /**
81
+ * Is this delivery really from GitHub?
82
+ *
83
+ * The webhook endpoint is public, and everything downstream — cloning a repo,
84
+ * posting as the App — happens on its say-so. Compared in constant time because
85
+ * a byte-at-a-time comparison leaks the expected digest to anyone willing to
86
+ * measure, and forging a signature is a total compromise of the endpoint.
87
+ */
88
+ export function verifySignature(secret, body, header) {
89
+ if (!header)
90
+ return false;
91
+ const expected = `sha256=${createHmac("sha256", secret).update(body).digest("hex")}`;
92
+ const a = Buffer.from(expected);
93
+ const b = Buffer.from(header);
94
+ return a.length === b.length && timingSafeEqual(a, b);
95
+ }
96
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/app/identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/app/main.ts"],"names":[],"mappings":""}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Entry point: read the environment, refuse to start half-configured, listen.
3
+ *
4
+ * Every misconfiguration is fatal at boot rather than at the first webhook. A
5
+ * server that starts without a webhook secret looks healthy and silently rejects
6
+ * every delivery; one that starts without a private key looks healthy and fails
7
+ * every review. Both are worth a loud death on line one.
8
+ */
9
+ import { createApp } from "./server.js";
10
+ function required(name) {
11
+ const value = process.env[name];
12
+ if (!value) {
13
+ console.error(`✗ ${name} is required`);
14
+ process.exit(1);
15
+ }
16
+ return value;
17
+ }
18
+ /**
19
+ * GitHub hands the private key over as a PEM file. Env vars cannot hold newlines
20
+ * comfortably, so both spellings are accepted: the raw PEM, or one with `\n`
21
+ * escaped — the shape you get from pasting a key into a hosting provider's UI.
22
+ */
23
+ function privateKey() {
24
+ const raw = required("GRAFT_APP_PRIVATE_KEY");
25
+ const pem = raw.includes("\\n") ? raw.replace(/\\n/g, "\n") : raw;
26
+ if (!pem.includes("BEGIN") || !pem.includes("PRIVATE KEY")) {
27
+ console.error("✗ GRAFT_APP_PRIVATE_KEY does not look like a PEM private key");
28
+ process.exit(1);
29
+ }
30
+ return pem;
31
+ }
32
+ const port = Number(process.env.PORT ?? 3000);
33
+ const { server, queue } = createApp({
34
+ appId: required("GRAFT_APP_ID"),
35
+ privateKey: privateKey(),
36
+ webhookSecret: required("GRAFT_WEBHOOK_SECRET"),
37
+ publicUrl: required("GRAFT_PUBLIC_URL"),
38
+ port,
39
+ concurrency: Number(process.env.GRAFT_CONCURRENCY ?? 2),
40
+ });
41
+ /**
42
+ * Finish what is in flight before dying.
43
+ *
44
+ * A container gets a TERM and a grace period on every deploy. Dropping a review
45
+ * mid-clone leaves a stale comment on a pull request and no retry, because the
46
+ * delivery was acknowledged long ago.
47
+ */
48
+ for (const signal of ["SIGTERM", "SIGINT"]) {
49
+ process.on(signal, () => {
50
+ console.log(`${signal}: draining ${queue.size} job(s)`);
51
+ server.close();
52
+ void queue.drain().then(() => process.exit(0));
53
+ setTimeout(() => process.exit(1), 25_000).unref();
54
+ });
55
+ }
56
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/app/main.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,cAAc,CAAC,CAAC;QACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU;IACjB,MAAM,GAAG,GAAG,QAAQ,CAAC,uBAAuB,CAAC,CAAC;IAC9C,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAClE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QAC3D,OAAO,CAAC,KAAK,CAAC,8DAA8D,CAAC,CAAC;QAC9E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;AAC9C,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC;IAClC,KAAK,EAAE,QAAQ,CAAC,cAAc,CAAC;IAC/B,UAAU,EAAE,UAAU,EAAE;IACxB,aAAa,EAAE,QAAQ,CAAC,sBAAsB,CAAC;IAC/C,SAAS,EAAE,QAAQ,CAAC,kBAAkB,CAAC;IACvC,IAAI;IACJ,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,CAAC,CAAC;CACxD,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,KAAK,MAAM,MAAM,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAU,EAAE,CAAC;IACpD,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;QACtB,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,cAAc,KAAK,CAAC,IAAI,SAAS,CAAC,CAAC;QACxD,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,39 @@
1
+ export interface StoredPage {
2
+ html: string;
3
+ storedMs: number;
4
+ }
5
+ export interface PageStoreOptions {
6
+ secret: string;
7
+ ttlMs?: number;
8
+ maxPages?: number;
9
+ now?: () => number;
10
+ }
11
+ export declare class PageStore {
12
+ private readonly pages;
13
+ private readonly secret;
14
+ private readonly ttlMs;
15
+ private readonly maxPages;
16
+ private readonly now;
17
+ constructor(opts: PageStoreOptions);
18
+ /** `owner/repo#number` → a path segment safe to put in a URL. */
19
+ static idFor(owner: string, repo: string, number: number): string;
20
+ /** Store (replacing any previous page for the same PR) and return `id` + `token`. */
21
+ put(owner: string, repo: string, number: number, html: string): {
22
+ id: string;
23
+ token: string;
24
+ };
25
+ /**
26
+ * The page, if the token is valid and it has not expired.
27
+ *
28
+ * Expiry is checked against the STORED time rather than encoded in the token,
29
+ * so a link cannot outlive the data it points at — and re-reviewing a PR
30
+ * refreshes both together.
31
+ */
32
+ get(id: string, token: string | undefined): string | null;
33
+ get size(): number;
34
+ private sign;
35
+ private valid;
36
+ /** Oldest out first, plus anything past its TTL. */
37
+ private evict;
38
+ }
39
+ //# sourceMappingURL=pages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/app/pages.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IACvD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,IAAI,EAAE,gBAAgB;IAOlC,iEAAiE;IACjE,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAIjE,qFAAqF;IACrF,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;IAO7F;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI;IAUzD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,KAAK;IAQb,oDAAoD;IACpD,OAAO,CAAC,KAAK;CASd"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Where a reviewed pull request's page lives.
3
+ *
4
+ * This is the part GitHub Pages could not do. A private repository's graph must
5
+ * not be readable by anyone who guesses a URL, and the Actions route had only two
6
+ * options: publish to a public branch, or hand the reader a zip. So the page is
7
+ * held here and linked with a signed, expiring URL.
8
+ *
9
+ * The signature is the whole access-control story, deliberately: it is a
10
+ * capability, not a login. Anyone with the link can read that one page until it
11
+ * expires, which is the same property a GitHub artifact link has, and the link
12
+ * only ever appears in a comment on the pull request it describes.
13
+ */
14
+ import { createHmac, timingSafeEqual } from "node:crypto";
15
+ /** Long enough to review a PR at leisure, short enough that a leaked link dies. */
16
+ const DEFAULT_TTL_MS = 14 * 24 * 60 * 60 * 1000;
17
+ /** A page is ~50 kB; a few hundred of them is nothing, and eviction is by age. */
18
+ const DEFAULT_MAX_PAGES = 500;
19
+ export class PageStore {
20
+ pages = new Map();
21
+ secret;
22
+ ttlMs;
23
+ maxPages;
24
+ now;
25
+ constructor(opts) {
26
+ this.secret = opts.secret;
27
+ this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
28
+ this.maxPages = opts.maxPages ?? DEFAULT_MAX_PAGES;
29
+ this.now = opts.now ?? Date.now;
30
+ }
31
+ /** `owner/repo#number` → a path segment safe to put in a URL. */
32
+ static idFor(owner, repo, number) {
33
+ return `${owner}__${repo}__${number}`.replace(/[^A-Za-z0-9_.-]/g, "-");
34
+ }
35
+ /** Store (replacing any previous page for the same PR) and return `id` + `token`. */
36
+ put(owner, repo, number, html) {
37
+ const id = PageStore.idFor(owner, repo, number);
38
+ this.pages.set(id, { html, storedMs: this.now() });
39
+ this.evict();
40
+ return { id, token: this.sign(id) };
41
+ }
42
+ /**
43
+ * The page, if the token is valid and it has not expired.
44
+ *
45
+ * Expiry is checked against the STORED time rather than encoded in the token,
46
+ * so a link cannot outlive the data it points at — and re-reviewing a PR
47
+ * refreshes both together.
48
+ */
49
+ get(id, token) {
50
+ const page = this.pages.get(id);
51
+ if (!page || !token || !this.valid(id, token))
52
+ return null;
53
+ if (this.now() - page.storedMs > this.ttlMs) {
54
+ this.pages.delete(id);
55
+ return null;
56
+ }
57
+ return page.html;
58
+ }
59
+ get size() {
60
+ return this.pages.size;
61
+ }
62
+ sign(id) {
63
+ return createHmac("sha256", this.secret).update(id).digest("hex").slice(0, 32);
64
+ }
65
+ valid(id, token) {
66
+ const a = Buffer.from(this.sign(id));
67
+ const b = Buffer.from(token);
68
+ // Length first: timingSafeEqual throws on a mismatch, which would turn a
69
+ // truncated token into a 500 instead of a 404.
70
+ return a.length === b.length && timingSafeEqual(a, b);
71
+ }
72
+ /** Oldest out first, plus anything past its TTL. */
73
+ evict() {
74
+ const cutoff = this.now() - this.ttlMs;
75
+ for (const [id, page] of this.pages)
76
+ if (page.storedMs < cutoff)
77
+ this.pages.delete(id);
78
+ while (this.pages.size > this.maxPages) {
79
+ const oldest = this.pages.keys().next();
80
+ if (oldest.done)
81
+ break;
82
+ this.pages.delete(oldest.value);
83
+ }
84
+ }
85
+ }
86
+ //# sourceMappingURL=pages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.js","sourceRoot":"","sources":["../../src/app/pages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The work queue.
3
+ *
4
+ * A webhook must be answered in seconds, and a review takes tens of them, so the
5
+ * HTTP handler's only job is to enqueue. Two properties matter and neither comes
6
+ * free from a bare array:
7
+ *
8
+ * - **Superseding.** Five pushes to one pull request in a minute is normal, and
9
+ * reviewing the first four is wasted work whose comments are immediately
10
+ * overwritten. A queued job for the same PR is replaced, not appended.
11
+ * - **A ceiling.** One repository must not be able to starve every other
12
+ * installation, so a fixed number of workers drain the queue.
13
+ */
14
+ export interface QueueOptions {
15
+ concurrency?: number;
16
+ /** Called for every failure: a job that throws must not take the process down,
17
+ * and a silent catch would hide a broken installation forever. */
18
+ onError?: (err: unknown, key: string) => void;
19
+ }
20
+ export declare class WorkQueue<T> {
21
+ private readonly run;
22
+ private readonly pending;
23
+ private readonly running;
24
+ private readonly concurrency;
25
+ private readonly onError;
26
+ private idle;
27
+ constructor(run: (item: T) => Promise<void>, opts?: QueueOptions);
28
+ /**
29
+ * Queue work under `key`, replacing anything queued under it and not yet started.
30
+ *
31
+ * A job already RUNNING is left alone: cancelling mid-clone buys nothing, and
32
+ * the newer job simply runs after it and overwrites the comment.
33
+ */
34
+ push(key: string, item: T): void;
35
+ get size(): number;
36
+ /** Resolves when nothing is queued or running — for tests and for shutdown. */
37
+ drain(): Promise<void>;
38
+ private pump;
39
+ }
40
+ //# sourceMappingURL=queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/app/queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;sEACkE;IAClE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/C;AAED,qBAAa,SAAS,CAAC,CAAC;IAQpB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAPtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,IAAI,CAAyB;gBAGlB,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,EAChD,IAAI,GAAE,YAAiB;IAMzB;;;;;OAKG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI;IAKhC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,+EAA+E;IACzE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B,OAAO,CAAC,IAAI;CAoBb"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The work queue.
3
+ *
4
+ * A webhook must be answered in seconds, and a review takes tens of them, so the
5
+ * HTTP handler's only job is to enqueue. Two properties matter and neither comes
6
+ * free from a bare array:
7
+ *
8
+ * - **Superseding.** Five pushes to one pull request in a minute is normal, and
9
+ * reviewing the first four is wasted work whose comments are immediately
10
+ * overwritten. A queued job for the same PR is replaced, not appended.
11
+ * - **A ceiling.** One repository must not be able to starve every other
12
+ * installation, so a fixed number of workers drain the queue.
13
+ */
14
+ export class WorkQueue {
15
+ run;
16
+ pending = new Map();
17
+ running = new Set();
18
+ concurrency;
19
+ onError;
20
+ idle = [];
21
+ constructor(run, opts = {}) {
22
+ this.run = run;
23
+ this.concurrency = Math.max(1, opts.concurrency ?? 2);
24
+ this.onError = opts.onError ?? (() => { });
25
+ }
26
+ /**
27
+ * Queue work under `key`, replacing anything queued under it and not yet started.
28
+ *
29
+ * A job already RUNNING is left alone: cancelling mid-clone buys nothing, and
30
+ * the newer job simply runs after it and overwrites the comment.
31
+ */
32
+ push(key, item) {
33
+ this.pending.set(key, item);
34
+ this.pump();
35
+ }
36
+ get size() {
37
+ return this.pending.size + this.running.size;
38
+ }
39
+ /** Resolves when nothing is queued or running — for tests and for shutdown. */
40
+ async drain() {
41
+ if (this.size === 0)
42
+ return;
43
+ await new Promise((resolve) => this.idle.push(resolve));
44
+ }
45
+ pump() {
46
+ while (this.running.size < this.concurrency) {
47
+ const next = this.pending.entries().next();
48
+ if (next.done)
49
+ break;
50
+ const [key, item] = next.value;
51
+ this.pending.delete(key);
52
+ this.running.add(key);
53
+ void this.run(item)
54
+ .catch((err) => this.onError(err, key))
55
+ .finally(() => {
56
+ this.running.delete(key);
57
+ this.pump();
58
+ if (this.size === 0) {
59
+ const waiting = this.idle;
60
+ this.idle = [];
61
+ for (const resolve of waiting)
62
+ resolve();
63
+ }
64
+ });
65
+ }
66
+ }
67
+ }
68
+ //# sourceMappingURL=queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/app/queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH,MAAM,OAAO,SAAS;IAQD;IAPF,OAAO,GAAG,IAAI,GAAG,EAAa,CAAC;IAC/B,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAC5B,WAAW,CAAS;IACpB,OAAO,CAAsC;IACtD,IAAI,GAAsB,EAAE,CAAC;IAErC,YACmB,GAA+B,EAChD,OAAqB,EAAE;QADN,QAAG,GAAH,GAAG,CAA4B;QAGhD,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC;QACtD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED;;;;;OAKG;IACH,IAAI,CAAC,GAAW,EAAE,IAAO;QACvB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC/C,CAAC;IAED,+EAA+E;IAC/E,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO;QAC5B,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAChE,CAAC;IAEO,IAAI;QACV,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YAC5C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC;YAC3C,IAAI,IAAI,CAAC,IAAI;gBAAE,MAAM;YACrB,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;YAC/B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACzB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACtB,KAAK,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;iBAChB,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;iBACtC,OAAO,CAAC,GAAG,EAAE;gBACZ,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACzB,IAAI,CAAC,IAAI,EAAE,CAAC;gBACZ,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;oBACpB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC;oBAC1B,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;oBACf,KAAK,MAAM,OAAO,IAAI,OAAO;wBAAE,OAAO,EAAE,CAAC;gBAC3C,CAAC;YACH,CAAC,CAAC,CAAC;QACP,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,23 @@
1
+ import type { ReviewJob } from "./events.js";
2
+ import type { Fetch } from "./identity.js";
3
+ /** Hidden marker: how the App finds the comment it owns, on every later push. */
4
+ export declare const MARKER = "<!-- graft-blast-radius -->";
5
+ export interface ReviewDeps {
6
+ token: (installationId: number) => Promise<string>;
7
+ fetch: Fetch;
8
+ api?: string;
9
+ /** Somewhere to put the viewer page; returns the URL to link. Optional: the
10
+ * comment is useful on its own, and a page that fails to publish must not cost
11
+ * the review. */
12
+ publish?: (job: ReviewJob, html: string) => Promise<string | null>;
13
+ log?: (msg: string) => void;
14
+ }
15
+ export interface ReviewResult {
16
+ /** `null` when the diff had nothing indexed to say. */
17
+ commentUrl: string | null;
18
+ areas: number;
19
+ affected: number;
20
+ viewerUrl: string | null;
21
+ }
22
+ export declare function reviewPullRequest(job: ReviewJob, deps: ReviewDeps): Promise<ReviewResult>;
23
+ //# sourceMappingURL=review.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/app/review.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAE3C,iFAAiF;AACjF,eAAO,MAAM,MAAM,gCAAgC,CAAC;AAIpD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACnD,KAAK,EAAE,KAAK,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;qBAEiB;IACjB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACnE,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,uDAAuD;IACvD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,CAmD/F"}