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.
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +9 -0
- package/dist/mod.d.ts +4 -0
- package/dist/mod.js +2 -0
- package/dist/package.json +54 -0
- package/dist/src/ai/batch.d.ts +16 -0
- package/dist/src/ai/batch.js +117 -0
- package/dist/src/ai/fake.d.ts +7 -0
- package/dist/src/ai/fake.js +40 -0
- package/dist/src/ai/hetzner.d.ts +16 -0
- package/dist/src/ai/hetzner.js +69 -0
- package/dist/src/ai/mermaid.d.ts +27 -0
- package/dist/src/ai/mermaid.js +457 -0
- package/dist/src/ai/mermaid_loop.d.ts +12 -0
- package/dist/src/ai/mermaid_loop.js +100 -0
- package/dist/src/ai/openrouter.d.ts +9 -0
- package/dist/src/ai/openrouter.js +29 -0
- package/dist/src/ai/provider.d.ts +4 -0
- package/dist/src/ai/provider.js +63 -0
- package/dist/src/cli/args.d.ts +5 -0
- package/dist/src/cli/args.js +252 -0
- package/dist/src/cli/commands/probe.d.ts +2 -0
- package/dist/src/cli/commands/probe.js +122 -0
- package/dist/src/cli/commands/review.d.ts +3 -0
- package/dist/src/cli/commands/review.js +84 -0
- package/dist/src/cli/commands/serve.d.ts +18 -0
- package/dist/src/cli/commands/serve.js +183 -0
- package/dist/src/cli/commands/set.d.ts +7 -0
- package/dist/src/cli/commands/set.js +171 -0
- package/dist/src/cli/main.d.ts +1 -0
- package/dist/src/cli/main.js +27 -0
- package/dist/src/cli/prompt.d.ts +4 -0
- package/dist/src/cli/prompt.js +26 -0
- package/dist/src/cli/review_args.d.ts +28 -0
- package/dist/src/cli/review_args.js +84 -0
- package/dist/src/cli/review_output.d.ts +2 -0
- package/dist/src/cli/review_output.js +10 -0
- package/dist/src/cli/review_result.d.ts +81 -0
- package/dist/src/cli/review_result.js +281 -0
- package/dist/src/config.d.ts +108 -0
- package/dist/src/config.js +138 -0
- package/dist/src/git/default_branch.d.ts +5 -0
- package/dist/src/git/default_branch.js +22 -0
- package/dist/src/github/app.d.ts +43 -0
- package/dist/src/github/app.js +120 -0
- package/dist/src/github/client.d.ts +17 -0
- package/dist/src/github/client.js +66 -0
- package/dist/src/github/collect.d.ts +7 -0
- package/dist/src/github/collect.js +368 -0
- package/dist/src/github/gh.d.ts +8 -0
- package/dist/src/github/gh.js +63 -0
- package/dist/src/github/jwt.d.ts +12 -0
- package/dist/src/github/jwt.js +84 -0
- package/dist/src/github/oauth.d.ts +9 -0
- package/dist/src/github/oauth.js +51 -0
- package/dist/src/github/pat.d.ts +10 -0
- package/dist/src/github/pat.js +60 -0
- package/dist/src/knowledge/facts.d.ts +3 -0
- package/dist/src/knowledge/facts.js +325 -0
- package/dist/src/knowledge/guide.d.ts +6 -0
- package/dist/src/knowledge/guide.js +82 -0
- package/dist/src/knowledge/probe.d.ts +12 -0
- package/dist/src/knowledge/probe.js +144 -0
- package/dist/src/knowledge/sections.d.ts +2 -0
- package/dist/src/knowledge/sections.js +13 -0
- package/dist/src/knowledge/skill.d.ts +10 -0
- package/dist/src/knowledge/skill.js +106 -0
- package/dist/src/knowledge/synthesis.d.ts +7 -0
- package/dist/src/knowledge/synthesis.js +315 -0
- package/dist/src/knowledge/types.d.ts +49 -0
- package/dist/src/knowledge/types.js +1 -0
- package/dist/src/knowledge/validate.d.ts +6 -0
- package/dist/src/knowledge/validate.js +134 -0
- package/dist/src/local/carry_over_store.d.ts +18 -0
- package/dist/src/local/carry_over_store.js +40 -0
- package/dist/src/local/codegraph_exclude.d.ts +3 -0
- package/dist/src/local/codegraph_exclude.js +26 -0
- package/dist/src/local/codegraph_prepare.d.ts +17 -0
- package/dist/src/local/codegraph_prepare.js +51 -0
- package/dist/src/local/git_ops.d.ts +12 -0
- package/dist/src/local/git_ops.js +98 -0
- package/dist/src/local/git_parse.d.ts +27 -0
- package/dist/src/local/git_parse.js +131 -0
- package/dist/src/local/git_revision.d.ts +15 -0
- package/dist/src/local/git_revision.js +141 -0
- package/dist/src/local/git_untracked.d.ts +21 -0
- package/dist/src/local/git_untracked.js +79 -0
- package/dist/src/local/review_local.d.ts +4 -0
- package/dist/src/local/review_local.js +273 -0
- package/dist/src/local/review_lock.d.ts +7 -0
- package/dist/src/local/review_lock.js +45 -0
- package/dist/src/pr/checkout.d.ts +25 -0
- package/dist/src/pr/checkout.js +136 -0
- package/dist/src/pr/codegraph_tool_args.d.ts +8 -0
- package/dist/src/pr/codegraph_tool_args.js +34 -0
- package/dist/src/pr/codegraph_tools.d.ts +15 -0
- package/dist/src/pr/codegraph_tools.js +408 -0
- package/dist/src/pr/diff_summary.d.ts +6 -0
- package/dist/src/pr/diff_summary.js +52 -0
- package/dist/src/pr/findings.d.ts +14 -0
- package/dist/src/pr/findings.js +94 -0
- package/dist/src/pr/github_revision.d.ts +4 -0
- package/dist/src/pr/github_revision.js +39 -0
- package/dist/src/pr/hunks.d.ts +18 -0
- package/dist/src/pr/hunks.js +73 -0
- package/dist/src/pr/reviewer.d.ts +40 -0
- package/dist/src/pr/reviewer.js +582 -0
- package/dist/src/pr/rounds.d.ts +17 -0
- package/dist/src/pr/rounds.js +43 -0
- package/dist/src/pr/scope.d.ts +35 -0
- package/dist/src/pr/scope.js +84 -0
- package/dist/src/pr/snapshot.d.ts +11 -0
- package/dist/src/pr/snapshot.js +1 -0
- package/dist/src/pr/suggestion.d.ts +17 -0
- package/dist/src/pr/suggestion.js +70 -0
- package/dist/src/remote/client.d.ts +4 -0
- package/dist/src/remote/client.js +199 -0
- package/dist/src/remote/client_tools.d.ts +13 -0
- package/dist/src/remote/client_tools.js +30 -0
- package/dist/src/remote/fixtures.d.ts +1 -0
- package/dist/src/remote/fixtures.js +30 -0
- package/dist/src/remote/revision_from_submit.d.ts +2 -0
- package/dist/src/remote/revision_from_submit.js +26 -0
- package/dist/src/remote/schema.d.ts +14 -0
- package/dist/src/remote/schema.js +30 -0
- package/dist/src/remote/server/body.d.ts +18 -0
- package/dist/src/remote/server/body.js +73 -0
- package/dist/src/remote/server/routes.d.ts +1 -0
- package/dist/src/remote/server/routes.js +150 -0
- package/dist/src/remote/server/sessions.d.ts +16 -0
- package/dist/src/remote/server/sessions.js +48 -0
- package/dist/src/remote/server/sync.d.ts +3 -0
- package/dist/src/remote/server/sync.js +90 -0
- package/dist/src/remote/tool_bridge.d.ts +15 -0
- package/dist/src/remote/tool_bridge.js +84 -0
- package/dist/src/remote/validate.d.ts +8 -0
- package/dist/src/remote/validate.js +248 -0
- package/dist/src/review/carry_over.d.ts +63 -0
- package/dist/src/review/carry_over.js +372 -0
- package/dist/src/review/engine.d.ts +13 -0
- package/dist/src/review/engine.js +4 -0
- package/dist/src/review/guides.d.ts +12 -0
- package/dist/src/review/guides.js +44 -0
- package/dist/src/review/revision.d.ts +26 -0
- package/dist/src/review/revision.js +57 -0
- package/dist/src/server/api/activity.d.ts +1 -0
- package/dist/src/server/api/activity.js +12 -0
- package/dist/src/server/api/analytics.d.ts +1 -0
- package/dist/src/server/api/analytics.js +9 -0
- package/dist/src/server/api/installations.d.ts +4 -0
- package/dist/src/server/api/installations.js +24 -0
- package/dist/src/server/api/jobs.d.ts +1 -0
- package/dist/src/server/api/jobs.js +86 -0
- package/dist/src/server/api/json_body.d.ts +1 -0
- package/dist/src/server/api/json_body.js +14 -0
- package/dist/src/server/api/remote_tokens.d.ts +1 -0
- package/dist/src/server/api/remote_tokens.js +71 -0
- package/dist/src/server/api/repos.d.ts +4 -0
- package/dist/src/server/api/repos.js +136 -0
- package/dist/src/server/api/settings.d.ts +1 -0
- package/dist/src/server/api/settings.js +210 -0
- package/dist/src/server/app.d.ts +27 -0
- package/dist/src/server/app.js +147 -0
- package/dist/src/server/assets.d.ts +3 -0
- package/dist/src/server/assets.js +21 -0
- package/dist/src/server/auth.d.ts +44 -0
- package/dist/src/server/auth.js +188 -0
- package/dist/src/server/errors.d.ts +8 -0
- package/dist/src/server/errors.js +10 -0
- package/dist/src/server/http.d.ts +8 -0
- package/dist/src/server/http.js +117 -0
- package/dist/src/server/logo.d.ts +1 -0
- package/dist/src/server/logo.js +2 -0
- package/dist/src/server/pages/activity.d.ts +4 -0
- package/dist/src/server/pages/activity.js +200 -0
- package/dist/src/server/pages/add_repo.d.ts +9 -0
- package/dist/src/server/pages/add_repo.js +56 -0
- package/dist/src/server/pages/analytics.d.ts +2 -0
- package/dist/src/server/pages/analytics.js +193 -0
- package/dist/src/server/pages/client.d.ts +2 -0
- package/dist/src/server/pages/client.js +2 -0
- package/dist/src/server/pages/home.d.ts +16 -0
- package/dist/src/server/pages/home.js +87 -0
- package/dist/src/server/pages/knowledge.d.ts +2 -0
- package/dist/src/server/pages/knowledge.js +64 -0
- package/dist/src/server/pages/layout.d.ts +32 -0
- package/dist/src/server/pages/layout.js +230 -0
- package/dist/src/server/pages/pr.d.ts +2 -0
- package/dist/src/server/pages/pr.js +109 -0
- package/dist/src/server/pages/repo.d.ts +2 -0
- package/dist/src/server/pages/repo.js +92 -0
- package/dist/src/server/pages/repo_prs.d.ts +2 -0
- package/dist/src/server/pages/repo_prs.js +75 -0
- package/dist/src/server/pages/repo_remote.d.ts +2 -0
- package/dist/src/server/pages/repo_remote.js +38 -0
- package/dist/src/server/pages/repo_settings.d.ts +3 -0
- package/dist/src/server/pages/repo_settings.js +127 -0
- package/dist/src/server/pages/router.d.ts +17 -0
- package/dist/src/server/pages/router.js +312 -0
- package/dist/src/server/pages/settings.d.ts +2 -0
- package/dist/src/server/pages/settings.js +337 -0
- package/dist/src/server/pages/setup.d.ts +5 -0
- package/dist/src/server/pages/setup.js +38 -0
- package/dist/src/server/pages/styles.d.ts +2 -0
- package/dist/src/server/pages/styles.js +2 -0
- package/dist/src/server/webhook/index.d.ts +1 -0
- package/dist/src/server/webhook/index.js +64 -0
- package/dist/src/server/webhook/signature.d.ts +7 -0
- package/dist/src/server/webhook/signature.js +41 -0
- package/dist/src/services/credentials.d.ts +19 -0
- package/dist/src/services/credentials.js +146 -0
- package/dist/src/services/dashboard.d.ts +165 -0
- package/dist/src/services/dashboard.js +200 -0
- package/dist/src/services/drift.d.ts +13 -0
- package/dist/src/services/drift.js +76 -0
- package/dist/src/services/jobs.d.ts +34 -0
- package/dist/src/services/jobs.js +222 -0
- package/dist/src/services/remote_review.d.ts +10 -0
- package/dist/src/services/remote_review.js +274 -0
- package/dist/src/services/remote_review_abort.d.ts +3 -0
- package/dist/src/services/remote_review_abort.js +11 -0
- package/dist/src/services/remote_token_jobs.d.ts +1 -0
- package/dist/src/services/remote_token_jobs.js +20 -0
- package/dist/src/services/remote_tokens.d.ts +13 -0
- package/dist/src/services/remote_tokens.js +57 -0
- package/dist/src/services/replies.d.ts +7 -0
- package/dist/src/services/replies.js +248 -0
- package/dist/src/services/review.d.ts +35 -0
- package/dist/src/services/review.js +671 -0
- package/dist/src/services/setup.d.ts +29 -0
- package/dist/src/services/setup.js +364 -0
- package/dist/src/services/webhook.d.ts +22 -0
- package/dist/src/services/webhook.js +401 -0
- package/dist/src/store/app_db.d.ts +10 -0
- package/dist/src/store/app_db.js +85 -0
- package/dist/src/store/cache_db.d.ts +4 -0
- package/dist/src/store/cache_db.js +63 -0
- package/dist/src/store/deliveries.d.ts +12 -0
- package/dist/src/store/deliveries.js +23 -0
- package/dist/src/store/drift.d.ts +4 -0
- package/dist/src/store/drift.js +22 -0
- package/dist/src/store/findings.d.ts +31 -0
- package/dist/src/store/findings.js +64 -0
- package/dist/src/store/installations.d.ts +9 -0
- package/dist/src/store/installations.js +35 -0
- package/dist/src/store/job_logs.d.ts +5 -0
- package/dist/src/store/job_logs.js +17 -0
- package/dist/src/store/jobs.d.ts +27 -0
- package/dist/src/store/jobs.js +86 -0
- package/dist/src/store/migrations.d.ts +5 -0
- package/dist/src/store/migrations.js +258 -0
- package/dist/src/store/remote_review_inputs.d.ts +11 -0
- package/dist/src/store/remote_review_inputs.js +25 -0
- package/dist/src/store/remote_tokens.d.ts +10 -0
- package/dist/src/store/remote_tokens.js +45 -0
- package/dist/src/store/replies.d.ts +24 -0
- package/dist/src/store/replies.js +79 -0
- package/dist/src/store/repos.d.ts +11 -0
- package/dist/src/store/repos.js +59 -0
- package/dist/src/store/reviews.d.ts +86 -0
- package/dist/src/store/reviews.js +220 -0
- package/dist/src/store/rows.d.ts +179 -0
- package/dist/src/store/rows.js +4 -0
- package/dist/src/store/sessions.d.ts +8 -0
- package/dist/src/store/sessions.js +34 -0
- package/dist/src/store/skill_state.d.ts +3 -0
- package/dist/src/store/skill_state.js +24 -0
- package/dist/src/store/sqlite.d.ts +39 -0
- package/dist/src/store/sqlite.js +77 -0
- package/dist/src/store/subjects.d.ts +15 -0
- package/dist/src/store/subjects.js +78 -0
- package/dist/src/tools/codegraph.d.ts +58 -0
- package/dist/src/tools/codegraph.js +165 -0
- package/dist/src/tools/codegraph_exec.d.ts +13 -0
- package/dist/src/tools/codegraph_exec.js +66 -0
- package/dist/src/types.d.ts +89 -0
- package/dist/src/types.js +1 -0
- package/dist/src/util/atomic.d.ts +4 -0
- package/dist/src/util/atomic.js +78 -0
- package/dist/src/util/keyed_lock.d.ts +3 -0
- package/dist/src/util/keyed_lock.js +33 -0
- package/dist/src/util/log.d.ts +11 -0
- package/dist/src/util/log.js +42 -0
- package/dist/src/util/redact.d.ts +5 -0
- package/dist/src/util/redact.js +30 -0
- package/dist/src/util/runtime.d.ts +90 -0
- package/dist/src/util/runtime.js +206 -0
- package/dist/src/util/time.d.ts +9 -0
- package/dist/src/util/time.js +36 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +5 -0
- 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
package/dist/main.js
ADDED
package/dist/mod.d.ts
ADDED
package/dist/mod.js
ADDED
|
@@ -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;
|