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,63 @@
1
+ import type { ParsedFinding } from "../pr/findings.ts";
2
+ import { type Revision, type RevisionFile } from "./revision.ts";
3
+ export type StoredFinding = {
4
+ id: string;
5
+ path: string | null;
6
+ lineFrom: number | null;
7
+ lineTo: number | null;
8
+ title: string;
9
+ bodyMd: string;
10
+ anchorText: string | null;
11
+ severity: string;
12
+ firstSeenReviewId: string | null;
13
+ };
14
+ export type CarryPrevious = {
15
+ files: RevisionFile[];
16
+ visiblePaths: Set<string>;
17
+ findings: StoredFinding[];
18
+ guideBuiltAt: string | null;
19
+ };
20
+ export type CarryClass = "file_reverted" | "file_removed" | "unverifiable" | "verify_guide_changed" | "unchanged" | "verify_present" | "verify_likely_fixed" | "verify_unlocated";
21
+ export type CarryItem = {
22
+ id: string;
23
+ finding: StoredFinding;
24
+ class: CarryClass;
25
+ targetPath: string | null;
26
+ lineFrom: number;
27
+ lineTo: number;
28
+ };
29
+ export type ResolvedFinding = {
30
+ id: string;
31
+ state: "new" | "open" | "closed";
32
+ closeReason?: "fixed" | "file_reverted";
33
+ path: string | null;
34
+ lineFrom: number | null;
35
+ lineTo: number | null;
36
+ title: string;
37
+ bodyMd: string;
38
+ anchorText: string | null;
39
+ severity: string;
40
+ carriedFromId: string | null;
41
+ firstSeenReviewId: string | null;
42
+ };
43
+ export declare function anchorTextFromPatch(patch: string, from: number, to: number): string | null;
44
+ export declare function relocate(anchorText: string | null, oldFrom: number, patch: string): {
45
+ from: number;
46
+ to: number;
47
+ } | null;
48
+ export declare function classifyCarryItems(previous: CarryPrevious, current: Revision, visiblePaths: Set<string>, currentGuideBuiltAt: string | null): CarryItem[];
49
+ export declare function buildCarryPromptSection(items: CarryItem[], current: Revision): string;
50
+ export type PreviousVerdict = {
51
+ id: string;
52
+ state: "open" | "closed";
53
+ path?: string;
54
+ from?: number;
55
+ to?: number;
56
+ };
57
+ export declare function parsePreviousVerdicts(markdown: string): PreviousVerdict[];
58
+ export declare function findingsMarkdownForParse(markdown: string): string;
59
+ export declare function resolveCarryOutcomes(items: CarryItem[], current: Revision, visiblePaths: Set<string>, verdicts: PreviousVerdict[], parsedNew: ParsedFinding[], currentGuideBuiltAt: string | null, previous: CarryPrevious): ResolvedFinding[];
60
+ export declare function incrementalDiffPaths(current: Revision, previousFiles: RevisionFile[] | null): {
61
+ changed: RevisionFile[];
62
+ unchanged: string[];
63
+ };
@@ -0,0 +1,372 @@
1
+ import { spansOverlap } from "../pr/rounds.js";
2
+ import { rightLines } from "../pr/hunks.js";
3
+ import { normalizeAnchor, normalizeBody, normalizePath, revisionFilesEquivalent, resolvePathAfterRename, } from "./revision.js";
4
+ export function anchorTextFromPatch(patch, from, to) {
5
+ const lines = rightLines(patch);
6
+ const low = Math.min(from, to);
7
+ const high = Math.max(from, to);
8
+ const picked = [];
9
+ for (let line = low; line <= high; line++) {
10
+ const text = lines.get(line);
11
+ if (text === undefined)
12
+ return null;
13
+ picked.push(text.replace(/\r$/, "").trimEnd());
14
+ }
15
+ return picked.length ? picked.join("\n") : null;
16
+ }
17
+ function anchorInPatch(anchor, patch) {
18
+ if (!anchor)
19
+ return false;
20
+ const want = normalizeAnchor(anchor).split("\n");
21
+ const numbered = [...rightLines(patch).entries()].sort((a, b) => a[0] - b[0]);
22
+ for (let i = 0; i <= numbered.length - want.length; i++) {
23
+ let ok = true;
24
+ for (let j = 0; j < want.length; j++) {
25
+ if (normalizeAnchor(numbered[i + j][1]) !== want[j]) {
26
+ ok = false;
27
+ break;
28
+ }
29
+ }
30
+ if (ok)
31
+ return true;
32
+ }
33
+ return false;
34
+ }
35
+ export function relocate(anchorText, oldFrom, patch) {
36
+ if (!anchorText)
37
+ return null;
38
+ const want = normalizeAnchor(anchorText).split("\n");
39
+ if (want.length === 0)
40
+ return null;
41
+ const numbered = [...rightLines(patch).entries()].sort((a, b) => a[0] - b[0]);
42
+ const matches = [];
43
+ for (let i = 0; i <= numbered.length - want.length; i++) {
44
+ let ok = true;
45
+ for (let j = 0; j < want.length; j++) {
46
+ if (normalizeAnchor(numbered[i + j][1]) !== want[j]) {
47
+ ok = false;
48
+ break;
49
+ }
50
+ }
51
+ if (!ok)
52
+ continue;
53
+ const from = numbered[i][0];
54
+ const to = numbered[i + want.length - 1][0];
55
+ matches.push({
56
+ from,
57
+ to,
58
+ distance: Math.abs(from - oldFrom),
59
+ });
60
+ }
61
+ if (matches.length === 0)
62
+ return null;
63
+ if (want.length === 1 && want[0].length < 12 && matches.length > 1) {
64
+ return null;
65
+ }
66
+ matches.sort((a, b) => a.distance - b.distance || a.from - b.from);
67
+ const best = matches[0];
68
+ return { from: best.from, to: best.to };
69
+ }
70
+ function guideChanged(previous, currentGuideBuiltAt) {
71
+ return (previous.guideBuiltAt ?? "") !== (currentGuideBuiltAt ?? "");
72
+ }
73
+ export function classifyCarryItems(previous, current, visiblePaths, currentGuideBuiltAt) {
74
+ const currentByPath = new Map(current.files.map((file) => [normalizePath(file.path), file]));
75
+ const items = [];
76
+ for (const finding of previous.findings) {
77
+ const targetPath = finding.path
78
+ ? resolvePathAfterRename(current, finding.path)
79
+ : null;
80
+ const file = targetPath
81
+ ? currentByPath.get(normalizePath(targetPath))
82
+ : undefined;
83
+ const lineFrom = finding.lineFrom ?? 0;
84
+ const lineTo = finding.lineTo ?? lineFrom;
85
+ let klass = "verify_unlocated";
86
+ const prevFile = finding.path
87
+ ? previous.files.find((f) => normalizePath(f.path) === normalizePath(finding.path))
88
+ : undefined;
89
+ const prevPatch = prevFile?.patch ?? "";
90
+ const sameBody = Boolean(prevFile && file && revisionFilesEquivalent(prevFile, file));
91
+ if (!finding.path) {
92
+ klass = "unverifiable";
93
+ }
94
+ else if (!targetPath || !file) {
95
+ klass = "file_reverted";
96
+ }
97
+ else if (file.status === "removed") {
98
+ klass = "file_removed";
99
+ }
100
+ else if (finding.path &&
101
+ !previous.visiblePaths.has(normalizePath(finding.path))) {
102
+ klass = "unverifiable";
103
+ }
104
+ else if (sameBody) {
105
+ klass = "unchanged";
106
+ }
107
+ else if (!visiblePaths.has(normalizePath(file.path))) {
108
+ klass = "unverifiable";
109
+ }
110
+ else if (guideChanged(previous, currentGuideBuiltAt)) {
111
+ klass = "verify_guide_changed";
112
+ }
113
+ else {
114
+ if (anchorInPatch(finding.anchorText, file.patch)) {
115
+ klass = "verify_present";
116
+ }
117
+ else if (finding.anchorText &&
118
+ finding.anchorText
119
+ .split("\n")
120
+ .every((line) => line.trim().startsWith("+"))) {
121
+ klass = "verify_likely_fixed";
122
+ }
123
+ else {
124
+ klass = "verify_unlocated";
125
+ }
126
+ }
127
+ items.push({
128
+ id: finding.id,
129
+ finding,
130
+ class: klass,
131
+ targetPath,
132
+ lineFrom,
133
+ lineTo,
134
+ });
135
+ }
136
+ return items;
137
+ }
138
+ const VERIFY_HINT = {
139
+ file_reverted: "",
140
+ file_removed: "",
141
+ unverifiable: "",
142
+ verify_guide_changed: "review guide was rebuilt",
143
+ unchanged: "",
144
+ verify_present: "still present in changed code",
145
+ verify_likely_fixed: "anchored code no longer found",
146
+ verify_unlocated: "could not locate",
147
+ };
148
+ export function buildCarryPromptSection(items, current) {
149
+ const verify = [];
150
+ const unchanged = [];
151
+ let index = 0;
152
+ for (const item of items) {
153
+ if (item.class === "file_reverted" || item.class === "file_removed") {
154
+ continue;
155
+ }
156
+ if (item.class === "unchanged") {
157
+ const file = current.files.find((f) => normalizePath(f.path) === normalizePath(item.targetPath ?? ""));
158
+ const relocated = file
159
+ ? relocate(item.finding.anchorText, item.lineFrom, file.patch)
160
+ : null;
161
+ const from = relocated?.from ?? item.lineFrom;
162
+ const to = relocated?.to ?? item.lineTo;
163
+ unchanged.push(`- ${item.targetPath}:${from}${to !== from ? `-${to}` : ""} · ${item.finding.title}`);
164
+ continue;
165
+ }
166
+ if (item.class === "unverifiable") {
167
+ continue;
168
+ }
169
+ index++;
170
+ const label = `F${index}`;
171
+ item.promptId = label;
172
+ const loc = item.targetPath
173
+ ? `${item.targetPath}:${item.lineFrom}${item.lineTo !== item.lineFrom ? `-${item.lineTo}` : ""}`
174
+ : "";
175
+ const body = item.finding.bodyMd.slice(0, 600);
176
+ verify.push(`${label} · ${VERIFY_HINT[item.class]} · ${loc}\n${item.finding.title}\n${body}`);
177
+ }
178
+ if (verify.length === 0 && unchanged.length === 0)
179
+ return "";
180
+ const lines = [
181
+ "PREVIOUS FINDINGS:",
182
+ "These were raised by an earlier review of this same change. For each one",
183
+ "listed under VERIFY, decide whether the code in the DIFF still has the",
184
+ 'problem. Report every decision in a final "## Previous findings" section,',
185
+ 'one line per id, exactly as "- F1: open path:from-to" or "- F1: closed".',
186
+ 'Do not repeat an open previous finding under "## Findings".',
187
+ "",
188
+ ];
189
+ if (verify.length) {
190
+ lines.push("VERIFY:", verify.join("\n\n"), "");
191
+ }
192
+ if (unchanged.length) {
193
+ lines.push("STILL OPEN, UNCHANGED (do not report again):", unchanged.join("\n"), "");
194
+ }
195
+ return lines.join("\n");
196
+ }
197
+ export function parsePreviousVerdicts(markdown) {
198
+ const heading = markdown.search(/^## Previous findings\b/im);
199
+ if (heading === -1)
200
+ return [];
201
+ const section = markdown.slice(heading);
202
+ const verdicts = [];
203
+ const lineRe = /^-\s*(F\d+)\s*:\s*(open|closed)(?:\s+([^:\s]+):(\d+)(?:-(\d+))?)?\s*$/im;
204
+ for (const line of section.split("\n")) {
205
+ const match = lineRe.exec(line.trim());
206
+ if (!match)
207
+ continue;
208
+ verdicts.push({
209
+ id: match[1],
210
+ state: match[2].toLowerCase(),
211
+ path: match[3],
212
+ from: match[4] ? Number(match[4]) : undefined,
213
+ to: match[5] ? Number(match[5]) : match[4] ? Number(match[4]) : undefined,
214
+ });
215
+ }
216
+ return verdicts;
217
+ }
218
+ export function findingsMarkdownForParse(markdown) {
219
+ const cut = markdown.search(/^## Previous findings\b/im);
220
+ return cut === -1 ? markdown : markdown.slice(0, cut);
221
+ }
222
+ export function resolveCarryOutcomes(items, current, visiblePaths, verdicts, parsedNew, currentGuideBuiltAt, previous) {
223
+ const verdictByPromptId = new Map(verdicts.map((v) => [v.id, v]));
224
+ const promptIds = new Map();
225
+ let seq = 0;
226
+ for (const item of items) {
227
+ if (item.class === "file_reverted" ||
228
+ item.class === "file_removed" ||
229
+ item.class === "unchanged" ||
230
+ item.class === "unverifiable") {
231
+ continue;
232
+ }
233
+ seq++;
234
+ promptIds.set(item.finding.id, `F${seq}`);
235
+ }
236
+ const resolved = [];
237
+ const openForMerge = [];
238
+ for (const item of items) {
239
+ const base = item.finding;
240
+ if (item.class === "file_reverted") {
241
+ resolved.push({
242
+ id: crypto.randomUUID(),
243
+ state: "closed",
244
+ closeReason: "file_reverted",
245
+ path: base.path,
246
+ lineFrom: base.lineFrom,
247
+ lineTo: base.lineTo,
248
+ title: base.title,
249
+ bodyMd: base.bodyMd,
250
+ anchorText: base.anchorText,
251
+ severity: base.severity,
252
+ carriedFromId: base.id,
253
+ firstSeenReviewId: base.firstSeenReviewId,
254
+ });
255
+ continue;
256
+ }
257
+ if (item.class === "file_removed") {
258
+ resolved.push({
259
+ id: crypto.randomUUID(),
260
+ state: "closed",
261
+ closeReason: "fixed",
262
+ path: base.path,
263
+ lineFrom: base.lineFrom,
264
+ lineTo: base.lineTo,
265
+ title: base.title,
266
+ bodyMd: base.bodyMd,
267
+ anchorText: base.anchorText,
268
+ severity: base.severity,
269
+ carriedFromId: base.id,
270
+ firstSeenReviewId: base.firstSeenReviewId,
271
+ });
272
+ continue;
273
+ }
274
+ const file = item.targetPath
275
+ ? current.files.find((f) => normalizePath(f.path) === normalizePath(item.targetPath))
276
+ : undefined;
277
+ let lineFrom = item.lineFrom;
278
+ let lineTo = item.lineTo;
279
+ if (item.class === "unchanged" && file) {
280
+ const moved = relocate(base.anchorText, lineFrom, file.patch);
281
+ if (moved) {
282
+ lineFrom = moved.from;
283
+ lineTo = moved.to;
284
+ }
285
+ }
286
+ if (item.class === "unchanged" || item.class === "unverifiable") {
287
+ const row = {
288
+ id: crypto.randomUUID(),
289
+ state: "open",
290
+ path: item.targetPath ?? base.path,
291
+ lineFrom,
292
+ lineTo,
293
+ title: base.title,
294
+ bodyMd: base.bodyMd,
295
+ anchorText: file
296
+ ? anchorTextFromPatch(file.patch, lineFrom, lineTo)
297
+ : base.anchorText,
298
+ severity: base.severity,
299
+ carriedFromId: base.id,
300
+ firstSeenReviewId: base.firstSeenReviewId,
301
+ };
302
+ resolved.push(row);
303
+ openForMerge.push(row);
304
+ continue;
305
+ }
306
+ const promptId = promptIds.get(base.id);
307
+ const verdict = promptId ? verdictByPromptId.get(promptId) : undefined;
308
+ const state = verdict?.state ?? "open";
309
+ const anchor = file && verdict?.path
310
+ ? anchorTextFromPatch(file.patch, verdict.from ?? lineFrom, verdict.to ?? verdict.from ?? lineTo)
311
+ : base.anchorText;
312
+ const row = {
313
+ id: crypto.randomUUID(),
314
+ state,
315
+ path: verdict?.path ?? item.targetPath ?? base.path,
316
+ lineFrom: verdict?.from ?? lineFrom,
317
+ lineTo: verdict?.to ?? verdict?.from ?? lineTo,
318
+ title: base.title,
319
+ bodyMd: base.bodyMd,
320
+ anchorText: anchor,
321
+ severity: base.severity,
322
+ carriedFromId: base.id,
323
+ firstSeenReviewId: base.firstSeenReviewId,
324
+ };
325
+ resolved.push(row);
326
+ if (state === "open")
327
+ openForMerge.push(row);
328
+ }
329
+ for (const finding of parsedNew) {
330
+ const overlap = openForMerge.find((open) => open.path &&
331
+ spansOverlap({ path: open.path, from: open.lineFrom ?? 0, to: open.lineTo ?? 0 }, finding));
332
+ if (overlap) {
333
+ overlap.bodyMd = finding.excerpt;
334
+ overlap.title = finding.heading || finding.path;
335
+ overlap.lineFrom = finding.from;
336
+ overlap.lineTo = finding.to;
337
+ continue;
338
+ }
339
+ resolved.push({
340
+ id: crypto.randomUUID(),
341
+ state: "new",
342
+ path: finding.path,
343
+ lineFrom: finding.from,
344
+ lineTo: finding.to,
345
+ title: finding.heading || finding.path,
346
+ bodyMd: finding.excerpt,
347
+ anchorText: null,
348
+ severity: finding.severity ?? "P2",
349
+ carriedFromId: null,
350
+ firstSeenReviewId: null,
351
+ });
352
+ }
353
+ return resolved;
354
+ }
355
+ export function incrementalDiffPaths(current, previousFiles) {
356
+ if (!previousFiles) {
357
+ return { changed: current.files, unchanged: [] };
358
+ }
359
+ const prevByPath = new Map(previousFiles.map((file) => [normalizePath(file.path), file]));
360
+ const changed = [];
361
+ const unchanged = [];
362
+ for (const file of current.files) {
363
+ const prev = prevByPath.get(normalizePath(file.path));
364
+ if (!prev || !revisionFilesEquivalent(prev, file)) {
365
+ changed.push(file);
366
+ }
367
+ else {
368
+ unchanged.push(file.path);
369
+ }
370
+ }
371
+ return { changed, unchanged };
372
+ }
@@ -0,0 +1,13 @@
1
+ /** Review prompt, tool loop, and improvement passes — PR adapter calls this
2
+ * after building a `Revision` and carry-over input. */
3
+ import type { GitHubClient, Options } from "../types.ts";
4
+ import type { Snapshot } from "../pr/snapshot.ts";
5
+ import type { AiProvider, AiResponse } from "../types.ts";
6
+ import { type ReviewExtras } from "../pr/reviewer.ts";
7
+ type UsageSink = (response: AiResponse) => Promise<void>;
8
+ type ProgressSink = (message: string) => void;
9
+ export declare function runReviewEngine(client: GitHubClient, options: Options, usage?: UsageSink, snapshot?: Snapshot, ai?: AiProvider, progress?: ProgressSink, extras?: ReviewExtras): Promise<AiResponse & {
10
+ visiblePaths: string[];
11
+ guideBuiltAt: string | null;
12
+ }>;
13
+ export {};
@@ -0,0 +1,4 @@
1
+ import { reviewPullRequest } from "../pr/reviewer.js";
2
+ export async function runReviewEngine(client, options, usage, snapshot, ai, progress, extras) {
3
+ return reviewPullRequest(client, options, usage, snapshot, ai, progress, extras);
4
+ }
@@ -0,0 +1,12 @@
1
+ export declare function knowledgeLockKey(repo: string): string;
2
+ export type LoadedGuides = {
3
+ shortGuide: string;
4
+ detailed: string;
5
+ codebase: string;
6
+ skill: string;
7
+ guideBuiltAt: string | null;
8
+ };
9
+ /** Reads all review guides and `knowledge_built_at` under one lock so every
10
+ * file belongs to the same generation. */
11
+ export declare function loadGuides(repo: string): Promise<LoadedGuides>;
12
+ export declare function withKnowledgeLock<T>(repo: string, fn: () => Promise<T>): Promise<T>;
@@ -0,0 +1,44 @@
1
+ import { reposDir } from "../config.js";
2
+ import { getRepo } from "../store/repos.js";
3
+ import { withKeyedLock } from "../util/keyed_lock.js";
4
+ import { readTextFile } from "../util/runtime.js";
5
+ export function knowledgeLockKey(repo) {
6
+ return `knowledge:${repo}`;
7
+ }
8
+ async function readGuideFile(repo, name) {
9
+ try {
10
+ return await readTextFile(`${reposDir()}/${repo}/${name}`);
11
+ }
12
+ catch {
13
+ return "";
14
+ }
15
+ }
16
+ /** Reads all review guides and `knowledge_built_at` under one lock so every
17
+ * file belongs to the same generation. */
18
+ export async function loadGuides(repo) {
19
+ return withKeyedLock(knowledgeLockKey(repo), async () => {
20
+ const [shortGuide, detailed, codebase, skill] = await Promise.all([
21
+ readGuideFile(repo, "PR_REVIEW_GUIDE.md"),
22
+ readGuideFile(repo, "PR_REVIEW_DETAILED_GUIDE.md"),
23
+ readGuideFile(repo, "CODEBASE.md"),
24
+ readGuideFile(repo, "SKILL.md"),
25
+ ]);
26
+ let guideBuiltAt = null;
27
+ try {
28
+ guideBuiltAt = getRepo(repo)?.knowledge_built_at ?? null;
29
+ }
30
+ catch {
31
+ // Local CLI review without `serve` — guides still load from disk.
32
+ }
33
+ return {
34
+ shortGuide,
35
+ detailed,
36
+ codebase,
37
+ skill,
38
+ guideBuiltAt,
39
+ };
40
+ });
41
+ }
42
+ export async function withKnowledgeLock(repo, fn) {
43
+ return withKeyedLock(knowledgeLockKey(repo), fn);
44
+ }
@@ -0,0 +1,26 @@
1
+ export type RevisionFileStatus = "added" | "modified" | "removed" | "renamed";
2
+ export type RevisionFile = {
3
+ path: string;
4
+ previousPath: string | null;
5
+ status: RevisionFileStatus;
6
+ binary: boolean;
7
+ additions: number;
8
+ deletions: number;
9
+ patch: string;
10
+ };
11
+ export type Revision = {
12
+ files: RevisionFile[];
13
+ title: string;
14
+ description: string;
15
+ baseLabel: string;
16
+ producer: "github" | "local" | "remote";
17
+ };
18
+ export declare function normalizePath(path: string): string;
19
+ /** Patch body for carry-over comparison (11.2). */
20
+ /** Whether two revision files represent the same change for carry-over scope. */
21
+ export declare function revisionFilesEquivalent(a: RevisionFile, b: RevisionFile): boolean;
22
+ export declare function normalizeBody(patch: string): string;
23
+ export declare function normalizeAnchor(text: string): string;
24
+ export declare function revisionHash(revision: Revision): Promise<string>;
25
+ export declare function fileByPath(revision: Revision, path: string): RevisionFile | undefined;
26
+ export declare function resolvePathAfterRename(revision: Revision, previousPath: string | null): string | null;
@@ -0,0 +1,57 @@
1
+ export function normalizePath(path) {
2
+ return path.replaceAll("\\", "/").replace(/^\.\//, "");
3
+ }
4
+ /** Patch body for carry-over comparison (11.2). */
5
+ /** Whether two revision files represent the same change for carry-over scope. */
6
+ export function revisionFilesEquivalent(a, b) {
7
+ if (a.status !== b.status)
8
+ return false;
9
+ if (a.binary !== b.binary)
10
+ return false;
11
+ if (a.additions !== b.additions || a.deletions !== b.deletions)
12
+ return false;
13
+ if ((a.previousPath ?? "") !== (b.previousPath ?? ""))
14
+ return false;
15
+ return normalizeBody(a.patch) === normalizeBody(b.patch);
16
+ }
17
+ export function normalizeBody(patch) {
18
+ const lines = patch.split("\n").flatMap((line) => {
19
+ if (line.startsWith("@@"))
20
+ return ["@@"];
21
+ if (line === "\")
22
+ return [];
23
+ return [line.replace(/\r$/, "").trimEnd()];
24
+ });
25
+ return lines.join("\n");
26
+ }
27
+ export function normalizeAnchor(text) {
28
+ return text
29
+ .split("\n")
30
+ .map((line) => line.trim().replace(/\s+/g, " "))
31
+ .filter(Boolean)
32
+ .join("\n");
33
+ }
34
+ export async function revisionHash(revision) {
35
+ const parts = [];
36
+ const files = [...revision.files].sort((a, b) => a.path.localeCompare(b.path));
37
+ for (const file of files) {
38
+ parts.push(file.path, file.status, file.previousPath ?? "", String(file.binary), normalizeBody(file.patch));
39
+ }
40
+ const data = new TextEncoder().encode(parts.join("\0"));
41
+ const digest = await crypto.subtle.digest("SHA-256", data);
42
+ return [...new Uint8Array(digest)]
43
+ .map((b) => b.toString(16).padStart(2, "0"))
44
+ .join("");
45
+ }
46
+ export function fileByPath(revision, path) {
47
+ const normalized = normalizePath(path);
48
+ return revision.files.find((file) => normalizePath(file.path) === normalized);
49
+ }
50
+ export function resolvePathAfterRename(revision, previousPath) {
51
+ if (!previousPath)
52
+ return null;
53
+ const renamed = revision.files.find((file) => file.status === "renamed" &&
54
+ file.previousPath &&
55
+ normalizePath(file.previousPath) === normalizePath(previousPath));
56
+ return renamed ? renamed.path : previousPath;
57
+ }
@@ -0,0 +1 @@
1
+ export declare function handleActivityRoute(request: Request, url: URL): Response;
@@ -0,0 +1,12 @@
1
+ import { errorResponse } from "../errors.js";
2
+ import { activityFeed, skippedDeliveries } from "../../services/dashboard.js";
3
+ export function handleActivityRoute(request, url) {
4
+ if (url.pathname === "/api/activity" && request.method === "GET") {
5
+ const page = Number(url.searchParams.get("page") ?? 1);
6
+ return Response.json(activityFeed(page, 20));
7
+ }
8
+ if (url.pathname === "/api/activity/skipped" && request.method === "GET") {
9
+ return Response.json({ items: skippedDeliveries() });
10
+ }
11
+ return errorResponse(404, "not_found", `no route for ${url.pathname}`);
12
+ }
@@ -0,0 +1 @@
1
+ export declare function handleAnalyticsRoute(request: Request, url: URL): Response;
@@ -0,0 +1,9 @@
1
+ import { errorResponse } from "../errors.js";
2
+ import { statsForRange } from "../../services/dashboard.js";
3
+ export function handleAnalyticsRoute(request, url) {
4
+ if (url.pathname === "/api/analytics" && request.method === "GET") {
5
+ const range = url.searchParams.get("range") ?? "30d";
6
+ return Response.json(statsForRange(range));
7
+ }
8
+ return errorResponse(404, "not_found", `no route for ${url.pathname}`);
9
+ }
@@ -0,0 +1,4 @@
1
+ export declare function handleInstallationsRoute(request: Request, githubApp: {
2
+ appId: string;
3
+ privateKeyPem: string;
4
+ } | undefined): Promise<Response>;
@@ -0,0 +1,24 @@
1
+ import { errorResponse } from "../errors.js";
2
+ import { listInstallationsWithRepos } from "../../github/app.js";
3
+ import { getRepo } from "../../store/repos.js";
4
+ export async function handleInstallationsRoute(request, githubApp) {
5
+ if (request.method !== "GET") {
6
+ return errorResponse(404, "not_found", "no route for /api/installations");
7
+ }
8
+ if (!githubApp) {
9
+ return errorResponse(422, "app_not_configured", "GitHub App is not configured; run: co-maintainer set --github-app-id=... --github-app-private-key=...");
10
+ }
11
+ const installations = await listInstallationsWithRepos(githubApp.appId, githubApp.privateKeyPem);
12
+ return Response.json({
13
+ items: installations.map(({ installation, repos }) => ({
14
+ id: installation.id,
15
+ accountLogin: installation.account?.login ?? "",
16
+ suspended: installation.suspended_at !== null,
17
+ repos: repos.map((repo) => ({
18
+ fullName: repo.fullName,
19
+ private: repo.private,
20
+ alreadyActive: Boolean(getRepo(repo.fullName)?.active),
21
+ })),
22
+ })),
23
+ });
24
+ }
@@ -0,0 +1 @@
1
+ export declare function handleJobsRoute(request: Request, url: URL): Response;