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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Murat Kirazkaya
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # co-maintainer
2
+
3
+ `co-maintainer` analyzes a GitHub repository and writes repository-specific
4
+ `SKILL.md` guidance that helps developers contribute changes more reliably.
5
+
6
+ It collects current code and workflows, selected pull requests and diffs, and
7
+ default-branch commits. A low-cost model extracts evidence-bound observations; a
8
+ higher-reasoning model turns them into concise contribution guidance for
9
+ implementation, testing, review, and release decisions.
10
+
11
+ ## Installation
12
+
13
+ ```sh
14
+ npm install -g co-maintainer
15
+ ```
16
+
17
+ Requires Node.js 24 or newer.
18
+
19
+ > **Migrating from JSR:** co-maintainer used to be published as
20
+ > `jsr:@murat/co-maintainer` for Deno. JSR is deprecated and no longer updated;
21
+ > install the npm package instead.
22
+
23
+ On a Linux VPS, persist npm's global bin directory if the installer prints a
24
+ PATH notice:
25
+
26
+ ```sh
27
+ grep -qxF 'export PATH="$(npm prefix -g)/bin:$PATH"' ~/.bashrc || printf '\nexport PATH="$(npm prefix -g)/bin:$PATH"\n' >> ~/.bashrc
28
+ source ~/.bashrc
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ```sh
34
+ co-maintainer probe owner/repo --auth=gh
35
+ ```
36
+
37
+ ```sh
38
+ co-maintainer probe owner/repo --auth=gh
39
+
40
+ co-maintainer init owner/repo --auth=gh --ai=openrouter --token=... \
41
+ --low-model=openai/gpt-oss-120b \
42
+ --high-model=openai/gpt-5.6-luna \
43
+ --include-codebase --include-pull-requests \
44
+ --include-pull-request-changes --include-commit-history \
45
+ --include-how-repo-works
46
+
47
+ co-maintainer review owner/repo 123 --improve-matrix=2 --debug
48
+
49
+ co-maintainer review # local branch (staged, unstaged, untracked)
50
+ co-maintainer review --json # machine-readable stdout; logs on stderr
51
+
52
+ npm run review-local-e2e # fake-AI local loop (developers, from repo root)
53
+ ```
54
+
55
+ Documentation: [Getting started](docs/getting-started.html) (install → first
56
+ review), [full docs site](docs/index.html), sources in [`docs/md/`](docs/md/).
57
+
58
+ Use `co-maintainer help`, `co-maintainer -h`, or `co-maintainer --help` for the
59
+ full CLI help.
60
+
61
+ ## Compared to other tools
62
+
63
+ co-maintainer is a newly tool so not compared a lot of other tools. But did
64
+ benchmarks with [OCR](https://open-codereview.ai/). Although I cannot offer any
65
+ guarantees because I am working with very small datasets, but it shows promise.
66
+
67
+ In core_v2 benchmarks:
68
+
69
+ - **2-3x better results** than OCR
70
+ - **2-8x faster** than OCR
71
+ - **70-200x fewer tokens** than OCR
72
+ - **60-270x cheaper** than
73
+
74
+ ```mermaid
75
+ xychart-beta
76
+ title "Monthly PR Review Cost by Developer Count"
77
+ x-axis [1, 5, 10, 20, 30, 40, 50, 60, 70, 80, 100]
78
+ y-axis "Monthly Cost ($)" 0 --> 13000
79
+ line "OCR" [129.6, 648, 1296, 2592, 3888, 5184, 6480, 7776, 9072, 10368, 12960]
80
+ line "co-maintainer" [2, 10, 20, 40, 60, 80, 100, 120, 140, 160, 200]
81
+ ```
82
+
83
+ _Calculated based on co-maintainer's `core_v2` benchmark, which reports co-maintainer as 65x cheaper per review than OCR ($0.025/review vs. $1.62/review). Assumes 80 PR reviews per developer per month (40 PRs merged, avg. ~2 reviews each)._
84
+
85
+ ## Benchmarks
86
+
87
+ We recommend consider core_v3 benchmark results because of more suits for
88
+ co-maintainer use cases. It is a much more meaningful metric than other
89
+ benchmarks. core_v3 is co-maintainer only benchmark.
90
+
91
+ Unlike the OCR comparison above (real human review comments as gold, see
92
+ `benchmark/swe-prbench`), `bench core v3` scores against deliberately seeded,
93
+ hand-verified defects in a dedicated target repo, half of the PRs kept as
94
+ defect-free controls so precision is measurable too, not just recall. Full
95
+ breakdown, per-PR results, and methodology:
96
+ [`benchmark/core_v3/README.md`](benchmark/core_v3/README.md).
97
+
98
+ | metric | value |
99
+ | ------------- | ----------------------------- |
100
+ | PRs reviewed | 30 (14 defective, 16 control) |
101
+ | F1 | 0.500 |
102
+ | Precision | 0.353 |
103
+ | Recall | 0.857 |
104
+ | Avg cost / PR | $0.0045 |
105
+ | Avg time / PR | 28.9s |
package/dist/main.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/main.js ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import { run } from "./src/cli/main.js";
3
+ try {
4
+ await run(process.argv.slice(2));
5
+ }
6
+ catch (error) {
7
+ console.error(`[error] ${error instanceof Error ? error.message : String(error)}`);
8
+ process.exit(1);
9
+ }
package/dist/mod.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { run } from "./src/cli/main.ts";
2
+ export { run };
3
+ export type { AiProvider, AiRequest, AiResponse, GitHubClient, Json, Options, } from "./src/types.ts";
4
+ export type { Fact, PullRequest, Source, State, } from "./src/knowledge/types.ts";
package/dist/mod.js ADDED
@@ -0,0 +1,2 @@
1
+ import { run } from "./src/cli/main.js";
2
+ export { run };
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "co-maintainer",
3
+ "version": "0.4.0-beta.1",
4
+ "description": "Analyzes a GitHub repository and writes repository-specific contribution guidance.",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "co-maintainer": "./dist/main.js"
8
+ },
9
+ "files": [
10
+ "dist/",
11
+ "LICENSE",
12
+ "README.md"
13
+ ],
14
+ "type": "module",
15
+ "exports": {
16
+ ".": "./dist/mod.js"
17
+ },
18
+ "scripts": {
19
+ "build": "npm run assets:embed && tsc -p tsconfig.build.json",
20
+ "assets:embed": "node scripts/embed_assets.mjs",
21
+ "prepack": "npm run build",
22
+ "test": "node --test \"src/**/*.test.ts\" \"benchmark/**/*.test.ts\"",
23
+ "check": "tsc --noEmit",
24
+ "lint": "oxlint",
25
+ "fmt": "oxfmt --write .",
26
+ "fmt:check": "oxfmt --check .",
27
+ "dev": "node --watch main.ts",
28
+ "probe": "node main.ts probe",
29
+ "init": "node main.ts init",
30
+ "remake": "node main.ts remake",
31
+ "review": "node main.ts review",
32
+ "serve": "node main.ts serve",
33
+ "bench": "node benchmark/core_v2/run.ts",
34
+ "bench-rereview": "node benchmark/core_v2/run_rereview.ts",
35
+ "bench-swe-prbench": "node benchmark/swe-prbench/run.ts",
36
+ "bench-core-v3": "node benchmark/core_v3/run.ts",
37
+ "swe-prbench-prepare": "node benchmark/swe-prbench/prepare_dataset.ts",
38
+ "e2e": "node scripts/e2e.ts",
39
+ "review-local-e2e": "node scripts/review_local_e2e.ts",
40
+ "docs:build": "node scripts/build_docs.ts"
41
+ },
42
+ "dependencies": {
43
+ "marked": "^15.0.7"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^24.0.0",
47
+ "oxfmt": "^0.68.0",
48
+ "oxlint": "^1.83.0",
49
+ "typescript": "^5.7.0"
50
+ },
51
+ "engines": {
52
+ "node": ">=24"
53
+ }
54
+ }
@@ -0,0 +1,16 @@
1
+ import type { AiProvider, AiRequest, AiResponse } from "../types.ts";
2
+ type UsageSink = (job: string, response: AiResponse) => Promise<void>;
3
+ export declare class AiBatch {
4
+ private records;
5
+ private loaded;
6
+ private saveChain;
7
+ private readonly provider;
8
+ private readonly repo;
9
+ private readonly concurrency;
10
+ private readonly profile;
11
+ constructor(provider: AiProvider, repo: string, concurrency: number, profile: string);
12
+ run(requests: AiRequest[], usage?: UsageSink): Promise<(AiResponse | undefined)[]>;
13
+ private load;
14
+ private persist;
15
+ }
16
+ export {};
@@ -0,0 +1,117 @@
1
+ import { cacheGet, cacheSet } from "../store/cache_db.js";
2
+ import { isNotFound, readTextFile } from "../util/runtime.js";
3
+ async function digest(value) {
4
+ const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
5
+ return [...new Uint8Array(bytes)]
6
+ .map((byte) => byte.toString(16).padStart(2, "0"))
7
+ .join("");
8
+ }
9
+ export class AiBatch {
10
+ records = {};
11
+ loaded = false;
12
+ saveChain = Promise.resolve();
13
+ provider;
14
+ repo;
15
+ concurrency;
16
+ profile;
17
+ constructor(provider, repo, concurrency, profile) {
18
+ this.provider = provider;
19
+ this.repo = repo;
20
+ this.concurrency = concurrency;
21
+ this.profile = profile;
22
+ }
23
+ async run(requests, usage) {
24
+ await this.load();
25
+ const results = new Array(requests.length);
26
+ const pending = [];
27
+ for (let index = 0; index < requests.length; index++) {
28
+ const request = requests[index];
29
+ const id = await digest(JSON.stringify({
30
+ profile: this.profile,
31
+ job: request.job,
32
+ system: request.system,
33
+ prompt: request.prompt,
34
+ maxTokens: request.maxTokens,
35
+ }));
36
+ const cached = this.records[id];
37
+ if (cached?.status === "done" && cached.response) {
38
+ results[index] = cached.response;
39
+ console.log(`[ai] cache hit ${request.job} ${id.slice(0, 8)}`);
40
+ }
41
+ else if (cached?.status === "quarantine") {
42
+ console.log(`[ai] quarantined job skipped: ${request.job} ${id.slice(0, 8)}`);
43
+ }
44
+ else {
45
+ pending.push({ index, id, request });
46
+ }
47
+ }
48
+ let cursor = 0;
49
+ console.log(`[ai] queue ${requests.length} jobs · ${pending.length} pending · concurrency ${this.concurrency}`);
50
+ const worker = async () => {
51
+ while (cursor < pending.length) {
52
+ const item = pending[cursor++];
53
+ const startedAt = Date.now();
54
+ console.log(`[ai] start ${item.request.job} ${item.id.slice(0, 8)}`);
55
+ const heartbeat = setInterval(() => {
56
+ const seconds = Math.round((Date.now() - startedAt) / 1000);
57
+ console.log(`[ai] still running ${item.request.job} ${item.id.slice(0, 8)} · ${seconds}s`);
58
+ }, 15_000);
59
+ try {
60
+ const response = await this.provider.complete(item.request);
61
+ this.records[item.id] = {
62
+ status: "done",
63
+ response,
64
+ updatedAt: new Date().toISOString(),
65
+ };
66
+ results[item.index] = response;
67
+ await this.persist();
68
+ if (usage)
69
+ await usage(item.request.job, response);
70
+ console.log(`[ai] done ${item.request.job} ${item.id.slice(0, 8)} · ${Math.round((Date.now() - startedAt) / 1000)}s`);
71
+ }
72
+ catch (error) {
73
+ this.records[item.id] = {
74
+ status: "quarantine",
75
+ error: String(error),
76
+ updatedAt: new Date().toISOString(),
77
+ };
78
+ await this.persist();
79
+ console.log(`[ai] quarantined ${item.request.job} ${item.id.slice(0, 8)}: ${String(error)}`);
80
+ }
81
+ finally {
82
+ clearInterval(heartbeat);
83
+ }
84
+ }
85
+ };
86
+ await Promise.all(Array.from({ length: Math.min(this.concurrency, Math.max(1, pending.length)) }, () => worker()));
87
+ await this.saveChain;
88
+ return results;
89
+ }
90
+ async load() {
91
+ if (this.loaded)
92
+ return;
93
+ const key = `${this.repo}:${this.profile}`;
94
+ const cached = await cacheGet("ai-jobs", key);
95
+ if (cached) {
96
+ this.records = JSON.parse(cached);
97
+ this.loaded = true;
98
+ return;
99
+ }
100
+ try {
101
+ this.records = JSON.parse(await readTextFile(`.cache/${this.repo}/ai-jobs.json`));
102
+ await cacheSet("ai-jobs", key, JSON.stringify(this.records));
103
+ }
104
+ catch (error) {
105
+ if (!isNotFound(error))
106
+ throw error;
107
+ }
108
+ this.loaded = true;
109
+ }
110
+ async persist() {
111
+ const snapshot = JSON.stringify(this.records, null, 2) + "\n";
112
+ this.saveChain = this.saveChain.then(async () => {
113
+ await cacheSet("ai-jobs", `${this.repo}:${this.profile}`, snapshot);
114
+ });
115
+ await this.saveChain;
116
+ }
117
+ }
@@ -0,0 +1,7 @@
1
+ import type { AiProvider, AiRequest, AiResponse } from "../types.ts";
2
+ export declare const FAKE_REVIEW_MARKDOWN = "## Findings\n\n### [P2 \u00B7 non-blocking] `src/app.ts` \u2014 `helper()`\nLocation: `src/app.ts:4`\n\nThe helper ignores its argument, so the new behavior is never applied.\n\nThe helper accepts an argument but returns the same result without reading it.\n\nIf you'd like me to explain it in more detail, please ask.\n";
3
+ export declare class FakeAiProvider implements AiProvider {
4
+ private readonly text;
5
+ constructor(text?: string);
6
+ complete(_request: AiRequest): Promise<AiResponse>;
7
+ }
@@ -0,0 +1,40 @@
1
+ import { getEnv, readTextFileSync } from "../util/runtime.js";
2
+ export const FAKE_REVIEW_MARKDOWN = `## Findings
3
+
4
+ ### [P2 · non-blocking] \`src/app.ts\` — \`helper()\`
5
+ Location: \`src/app.ts:4\`
6
+
7
+ The helper ignores its argument, so the new behavior is never applied.
8
+
9
+ The helper accepts an argument but returns the same result without reading it.
10
+
11
+ If you'd like me to explain it in more detail, please ask.
12
+ `;
13
+ function fakeReviewText() {
14
+ const path = getEnv("CM_FAKE_REVIEW_FILE");
15
+ if (path) {
16
+ try {
17
+ return readTextFileSync(path);
18
+ }
19
+ catch {
20
+ // fall through
21
+ }
22
+ }
23
+ return FAKE_REVIEW_MARKDOWN;
24
+ }
25
+ export class FakeAiProvider {
26
+ text;
27
+ constructor(text = fakeReviewText()) {
28
+ this.text = text;
29
+ }
30
+ complete(_request) {
31
+ return Promise.resolve({
32
+ text: this.text,
33
+ tokensIn: 10,
34
+ tokensOut: 20,
35
+ cost: 0,
36
+ model: "fake",
37
+ provider: "openrouter",
38
+ });
39
+ }
40
+ }
@@ -0,0 +1,16 @@
1
+ import type { AiProvider, AiRequest, AiResponse } from "../types.ts";
2
+ declare const sleep: (milliseconds: number) => Promise<void>;
3
+ export declare class HetznerProvider implements AiProvider {
4
+ readonly supportsTools = false;
5
+ private readonly endpoint;
6
+ private readonly apiKey;
7
+ private readonly model;
8
+ private readonly sleeper;
9
+ private nextRequest;
10
+ private spacing;
11
+ constructor(apiKey: string, model: string, endpoint?: string, sleeper?: typeof sleep);
12
+ complete(request: AiRequest): Promise<AiResponse>;
13
+ private waitForQuota;
14
+ private jitter;
15
+ }
16
+ export {};
@@ -0,0 +1,69 @@
1
+ import { chatBody, parseChatResponse } from "./provider.js";
2
+ const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
3
+ export class HetznerProvider {
4
+ supportsTools = false;
5
+ endpoint;
6
+ apiKey;
7
+ model;
8
+ sleeper;
9
+ nextRequest = 0;
10
+ spacing = 8_000;
11
+ constructor(apiKey, model, endpoint = "https://inference.hetzner.com/api/v1/chat/completions", sleeper = sleep) {
12
+ if (!apiKey)
13
+ throw new Error("Hetzner requires HETZNER_API_KEY");
14
+ if (!model)
15
+ throw new Error("Hetzner requires HETZNER_MODEL");
16
+ this.apiKey = apiKey;
17
+ this.model = model;
18
+ this.sleeper = sleeper;
19
+ this.endpoint = endpoint;
20
+ }
21
+ async complete(request) {
22
+ let attempt = 0;
23
+ for (;;) {
24
+ attempt++;
25
+ await this.waitForQuota();
26
+ const response = await fetch(this.endpoint, {
27
+ method: "POST",
28
+ headers: {
29
+ Authorization: `Bearer ${this.apiKey}`,
30
+ "Content-Type": "application/json",
31
+ },
32
+ body: JSON.stringify(chatBody(this.model, request)),
33
+ });
34
+ this.nextRequest = Date.now() + this.spacing;
35
+ if (response.ok) {
36
+ this.spacing = Math.max(8_000, this.spacing * 0.9);
37
+ return parseChatResponse((await response.json()), "hetzner", this.model);
38
+ }
39
+ if ([401, 403, 404].includes(response.status)) {
40
+ throw new Error(`Hetzner fatal ${response.status}: ${await response.text()}`);
41
+ }
42
+ if (response.status === 429) {
43
+ this.spacing = Math.min(180_000, this.spacing * 1.5);
44
+ const wait = this.jitter(this.spacing);
45
+ console.log(`[ai] hetzner 429 on attempt ${attempt}; retrying in ${Math.round(wait / 1000)}s`);
46
+ await this.sleeper(wait);
47
+ continue;
48
+ }
49
+ if (response.status >= 500 || response.status === 408) {
50
+ const wait = this.jitter(Math.min(180_000, this.spacing * 1.5));
51
+ console.log(`[ai] hetzner ${response.status} on attempt ${attempt}; retrying in ${Math.round(wait / 1000)}s`);
52
+ await this.sleeper(wait);
53
+ continue;
54
+ }
55
+ throw new Error(`Hetzner ${response.status}: ${await response.text()}`);
56
+ }
57
+ }
58
+ async waitForQuota() {
59
+ const wait = this.nextRequest - Date.now();
60
+ if (wait > 0) {
61
+ const jittered = this.jitter(wait);
62
+ console.log(`[ai] hetzner quota spacing; waiting ${Math.round(jittered / 1000)}s`);
63
+ await this.sleeper(jittered);
64
+ }
65
+ }
66
+ jitter(milliseconds) {
67
+ return Math.max(250, Math.round(milliseconds * (0.8 + Math.random() * 0.4)));
68
+ }
69
+ }
@@ -0,0 +1,27 @@
1
+ import type { Json } from "../types.ts";
2
+ export declare const MERMAID_TYPES: readonly ["flowchart", "swimlane-beta", "sequenceDiagram", "classDiagram", "stateDiagram-v2", "erDiagram", "requirementDiagram", "usecase-beta", "C4Context", "zenuml", "packet", "architecture-beta", "eventmodeling", "treeView-beta"];
3
+ export type MermaidType = (typeof MERMAID_TYPES)[number];
4
+ export declare const MERMAID_TOOL: {
5
+ readonly type: "function";
6
+ readonly function: {
7
+ readonly name: "read-mermaid-syntaxes";
8
+ readonly description: "Read the LLM-friendly syntax, configuration, and examples for selected Mermaid diagram types.";
9
+ readonly parameters: {
10
+ readonly type: "object";
11
+ readonly properties: {
12
+ readonly tools: {
13
+ readonly type: "array";
14
+ readonly items: {
15
+ readonly type: "string";
16
+ readonly enum: readonly ["flowchart", "swimlane-beta", "sequenceDiagram", "classDiagram", "stateDiagram-v2", "erDiagram", "requirementDiagram", "usecase-beta", "C4Context", "zenuml", "packet", "architecture-beta", "eventmodeling", "treeView-beta"];
17
+ };
18
+ readonly uniqueItems: true;
19
+ readonly maxItems: 5;
20
+ };
21
+ };
22
+ readonly required: readonly ["tools"];
23
+ readonly additionalProperties: false;
24
+ };
25
+ };
26
+ };
27
+ export declare function readMermaidSyntaxes(args: Json, maxTools: number): string;