co-maintainer 0.5.0 → 0.5.1-beta.2
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 -21
- package/README.md +5 -3
- package/dist/package.json +1 -1
- package/dist/src/ai/anthropic.d.ts +55 -0
- package/dist/src/ai/anthropic.js +262 -0
- package/dist/src/ai/batch.d.ts +2 -2
- package/dist/src/ai/batch.js +1 -1
- package/dist/src/ai/estimate.d.ts +4 -0
- package/dist/src/ai/estimate.js +17 -2
- package/dist/src/ai/mermaid_loop.js +7 -4
- package/dist/src/ai/openai.d.ts +45 -0
- package/dist/src/ai/openai.js +206 -0
- package/dist/src/ai/opencode.d.ts +10 -0
- package/dist/src/ai/opencode.js +76 -0
- package/dist/src/ai/openrouter.d.ts +23 -7
- package/dist/src/ai/openrouter.js +39 -23
- package/dist/src/ai/pricing.js +1 -1
- package/dist/src/ai/provider.d.ts +9 -0
- package/dist/src/ai/provider.js +42 -3
- package/dist/src/ai/verify.d.ts +9 -1
- package/dist/src/ai/verify.js +81 -3
- package/dist/src/cli/args.d.ts +1 -2
- package/dist/src/cli/args.js +38 -26
- package/dist/src/cli/commands/config.d.ts +1 -1
- package/dist/src/cli/commands/config.js +10 -4
- package/dist/src/cli/commands/probe.js +3 -11
- package/dist/src/cli/commands/registry.d.ts +1 -9
- package/dist/src/cli/commands/registry.js +75 -10
- package/dist/src/cli/commands/review.js +18 -13
- package/dist/src/cli/commands/rollback.d.ts +1 -0
- package/dist/src/cli/commands/rollback.js +42 -0
- package/dist/src/cli/commands/serve.js +2 -3
- package/dist/src/cli/commands/set.js +71 -7
- package/dist/src/cli/commands/view.js +10 -9
- package/dist/src/cli/error.d.ts +4 -4
- package/dist/src/cli/error.js +4 -4
- package/dist/src/cli/main.d.ts +4 -4
- package/dist/src/cli/main.js +13 -8
- package/dist/src/cli/prompt.js +1 -1
- package/dist/src/cli/review_args.d.ts +9 -3
- package/dist/src/cli/review_args.js +28 -5
- package/dist/src/cli/review_github.d.ts +31 -0
- package/dist/src/cli/review_github.js +128 -0
- package/dist/src/cli/review_result.d.ts +27 -16
- package/dist/src/cli/review_result.js +7 -7
- package/dist/src/config.d.ts +28 -6
- package/dist/src/config.js +15 -1
- package/dist/src/git/default_branch.d.ts +1 -1
- package/dist/src/git/default_branch.js +1 -1
- package/dist/src/github/app.js +2 -2
- package/dist/src/github/app_manifest.d.ts +1 -7
- package/dist/src/github/app_manifest.js +2 -7
- package/dist/src/github/client.d.ts +4 -1
- package/dist/src/github/client.js +14 -2
- package/dist/src/github/gh.js +10 -7
- package/dist/src/github/pat.js +4 -4
- package/dist/src/github/permissions.d.ts +36 -0
- package/dist/src/github/permissions.js +334 -0
- package/dist/src/knowledge/facts.js +1 -1
- package/dist/src/knowledge/guide.d.ts +3 -3
- package/dist/src/knowledge/guide.js +5 -5
- package/dist/src/knowledge/probe.js +1 -2
- package/dist/src/knowledge/sections.d.ts +3 -3
- package/dist/src/knowledge/sections.js +3 -3
- package/dist/src/knowledge/skill.d.ts +1 -1
- package/dist/src/knowledge/skill.js +4 -4
- package/dist/src/knowledge/synthesis.d.ts +1 -1
- package/dist/src/knowledge/synthesis.js +5 -5
- package/dist/src/knowledge/types.d.ts +1 -1
- package/dist/src/local/codegraph_exclude.d.ts +1 -1
- package/dist/src/local/codegraph_exclude.js +1 -1
- package/dist/src/local/codegraph_prepare.js +1 -1
- package/dist/src/local/git_ops.d.ts +1 -1
- package/dist/src/local/git_ops.js +1 -1
- package/dist/src/local/git_parse.d.ts +2 -2
- package/dist/src/local/git_parse.js +2 -2
- package/dist/src/local/git_revision.d.ts +1 -1
- package/dist/src/local/git_revision.js +1 -1
- package/dist/src/local/git_untracked.d.ts +1 -1
- package/dist/src/local/git_untracked.js +1 -1
- package/dist/src/local/review_local.js +17 -14
- package/dist/src/local/review_lock.d.ts +1 -1
- package/dist/src/local/review_lock.js +1 -1
- package/dist/src/pr/checkout.js +2 -2
- package/dist/src/pr/findings.js +1 -1
- package/dist/src/pr/findings_json.d.ts +2 -2
- package/dist/src/pr/findings_json.js +1 -1
- package/dist/src/pr/github_revision.js +1 -1
- package/dist/src/pr/review_copy.d.ts +2 -2
- package/dist/src/pr/review_copy.js +2 -2
- package/dist/src/pr/reviewer.d.ts +6 -0
- package/dist/src/pr/reviewer.js +49 -39
- package/dist/src/remote/client.d.ts +4 -0
- package/dist/src/remote/client.js +84 -23
- package/dist/src/remote/fixtures.js +1 -1
- package/dist/src/remote/http.d.ts +10 -1
- package/dist/src/remote/http.js +15 -3
- package/dist/src/remote/schema.d.ts +3 -3
- package/dist/src/remote/schema.js +3 -3
- package/dist/src/remote/server/body.d.ts +1 -1
- package/dist/src/remote/server/body.js +1 -1
- package/dist/src/remote/server/byok_keys.d.ts +15 -0
- package/dist/src/remote/server/byok_keys.js +40 -0
- package/dist/src/remote/server/guides.js +2 -2
- package/dist/src/remote/server/routes.js +8 -1
- package/dist/src/remote/validate.js +15 -1
- package/dist/src/review/blocking.d.ts +2 -2
- package/dist/src/review/blocking.js +2 -2
- package/dist/src/review/carry_over.d.ts +2 -2
- package/dist/src/review/carry_over.js +2 -2
- package/dist/src/review/guides.js +1 -1
- package/dist/src/server/api/remote_tokens.js +2 -0
- package/dist/src/server/api/repos.js +51 -0
- package/dist/src/server/api/settings.js +10 -0
- package/dist/src/server/errors.d.ts +1 -1
- package/dist/src/server/http.js +1 -1
- package/dist/src/server/pages/activity.d.ts +2 -0
- package/dist/src/server/pages/activity.js +18 -4
- package/dist/src/server/pages/add_repo.js +61 -1
- package/dist/src/server/pages/analytics.js +18 -8
- package/dist/src/server/pages/cost.d.ts +21 -0
- package/dist/src/server/pages/cost.js +72 -0
- package/dist/src/server/pages/pr.js +5 -4
- package/dist/src/server/pages/repo.js +3 -2
- package/dist/src/server/pages/repo_prs.js +3 -2
- package/dist/src/server/pages/repo_remote.js +3 -2
- package/dist/src/server/pages/repo_settings.js +326 -12
- package/dist/src/server/pages/router.js +3 -2
- package/dist/src/server/pages/settings.js +50 -6
- package/dist/src/server/pages/styles.d.ts +1 -1
- package/dist/src/server/pages/styles.js +1 -1
- package/dist/src/services/credentials.js +8 -11
- package/dist/src/services/dashboard.d.ts +22 -9
- package/dist/src/services/dashboard.js +16 -2
- package/dist/src/services/probe.js +1 -1
- package/dist/src/services/remote_review.js +142 -35
- package/dist/src/services/remote_review_abort.d.ts +5 -1
- package/dist/src/services/remote_review_abort.js +9 -1
- package/dist/src/services/review.d.ts +1 -1
- package/dist/src/services/review.js +19 -5
- package/dist/src/services/review_policy.d.ts +116 -0
- package/dist/src/services/review_policy.js +489 -0
- package/dist/src/services/setup.d.ts +4 -3
- package/dist/src/services/setup.js +18 -16
- package/dist/src/services/setup_checklist.js +2 -2
- package/dist/src/services/webhook.d.ts +23 -0
- package/dist/src/services/webhook.js +141 -34
- package/dist/src/store/app_db.d.ts +5 -0
- package/dist/src/store/app_db.js +69 -13
- package/dist/src/store/backup.d.ts +31 -0
- package/dist/src/store/backup.js +158 -0
- package/dist/src/store/migrations.d.ts +1 -1
- package/dist/src/store/migrations.js +21 -1
- package/dist/src/store/remote_review_inputs.d.ts +3 -0
- package/dist/src/store/remote_review_inputs.js +3 -3
- package/dist/src/store/remote_tokens.d.ts +1 -1
- package/dist/src/store/remote_tokens.js +1 -1
- package/dist/src/store/repos.d.ts +2 -2
- package/dist/src/store/repos.js +1 -1
- package/dist/src/store/reviews.d.ts +21 -13
- package/dist/src/store/reviews.js +60 -15
- package/dist/src/store/rows.d.ts +10 -0
- package/dist/src/tools/codegraph.d.ts +4 -4
- package/dist/src/tools/codegraph.js +5 -5
- package/dist/src/tools/codegraph_exec.d.ts +1 -1
- package/dist/src/tools/codegraph_exec.js +1 -1
- package/dist/src/types.d.ts +12 -6
- package/dist/src/util/cost.d.ts +52 -0
- package/dist/src/util/cost.js +70 -0
- package/dist/src/util/log.d.ts +1 -1
- package/dist/src/util/log.js +2 -2
- package/dist/src/util/redact.js +2 -0
- package/dist/src/util/run_summary.d.ts +7 -2
- package/dist/src/util/run_summary.js +28 -2
- package/dist/src/util/time.d.ts +1 -1
- package/dist/src/util/time.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/** The one list of GitHub calls this codebase makes and what each needs.
|
|
2
|
+
*
|
|
3
|
+
* The App manifest, the App permission check on save, the 403 and 404 hints
|
|
4
|
+
* and the permission tables in the docs are all read from `ENDPOINTS`.
|
|
5
|
+
* `permissions.test.ts` scans the source for every endpoint it calls and fails
|
|
6
|
+
* when one is missing here, so a new call cannot ship without its permission. */
|
|
7
|
+
/** Key order is the order the manifest and the doc tables list them in. */
|
|
8
|
+
export const PERMISSION_LABEL = {
|
|
9
|
+
contents: "Contents",
|
|
10
|
+
issues: "Issues",
|
|
11
|
+
pull_requests: "Pull requests",
|
|
12
|
+
checks: "Checks",
|
|
13
|
+
metadata: "Metadata",
|
|
14
|
+
};
|
|
15
|
+
const ORDER = Object.keys(PERMISSION_LABEL);
|
|
16
|
+
const READERS = ["gh", "pat", "app"];
|
|
17
|
+
const APP_ONLY = ["app"];
|
|
18
|
+
export const ENDPOINTS = [
|
|
19
|
+
{
|
|
20
|
+
method: "GET",
|
|
21
|
+
pattern: "repos/{repo}",
|
|
22
|
+
permission: "metadata",
|
|
23
|
+
level: "read",
|
|
24
|
+
identities: READERS,
|
|
25
|
+
feature: "Read the repository and its default branch",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
method: "GET",
|
|
29
|
+
pattern: "repos/{repo}/pulls",
|
|
30
|
+
permission: "pull_requests",
|
|
31
|
+
level: "read",
|
|
32
|
+
identities: READERS,
|
|
33
|
+
feature: "List pull requests",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
method: "GET",
|
|
37
|
+
pattern: "repos/{repo}/pulls/{n}",
|
|
38
|
+
permission: "pull_requests",
|
|
39
|
+
level: "read",
|
|
40
|
+
identities: READERS,
|
|
41
|
+
feature: "Read a pull request",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
method: "GET",
|
|
45
|
+
pattern: "repos/{repo}/pulls/{n}/files",
|
|
46
|
+
permission: "pull_requests",
|
|
47
|
+
level: "read",
|
|
48
|
+
identities: READERS,
|
|
49
|
+
feature: "Read the files a pull request changes",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
method: "GET",
|
|
53
|
+
pattern: "repos/{repo}/pulls/{n}/reviews",
|
|
54
|
+
permission: "pull_requests",
|
|
55
|
+
level: "read",
|
|
56
|
+
identities: READERS,
|
|
57
|
+
feature: "Read earlier reviews",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
method: "GET",
|
|
61
|
+
pattern: "repos/{repo}/pulls/{n}/comments",
|
|
62
|
+
permission: "pull_requests",
|
|
63
|
+
level: "read",
|
|
64
|
+
identities: READERS,
|
|
65
|
+
feature: "Read review comment threads",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
method: "POST",
|
|
69
|
+
pattern: "repos/{repo}/pulls/{n}/reviews",
|
|
70
|
+
permission: "pull_requests",
|
|
71
|
+
level: "write",
|
|
72
|
+
identities: APP_ONLY,
|
|
73
|
+
feature: "Post a review",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
method: "POST",
|
|
77
|
+
pattern: "repos/{repo}/pulls/{n}/comments",
|
|
78
|
+
permission: "pull_requests",
|
|
79
|
+
level: "write",
|
|
80
|
+
identities: APP_ONLY,
|
|
81
|
+
feature: "Reply in a review comment thread",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
method: "GET",
|
|
85
|
+
pattern: "repos/{repo}/issues/{n}/comments",
|
|
86
|
+
permission: "issues",
|
|
87
|
+
level: "read",
|
|
88
|
+
identities: READERS,
|
|
89
|
+
feature: "Read the conversation on a pull request",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
method: "POST",
|
|
93
|
+
pattern: "repos/{repo}/issues/{n}/comments",
|
|
94
|
+
permission: "issues",
|
|
95
|
+
level: "write",
|
|
96
|
+
identities: APP_ONLY,
|
|
97
|
+
feature: "Post a conversation comment",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
method: "POST",
|
|
101
|
+
pattern: "repos/{repo}/issues/{n}/reactions",
|
|
102
|
+
permission: "issues",
|
|
103
|
+
level: "write",
|
|
104
|
+
identities: APP_ONLY,
|
|
105
|
+
feature: "Acknowledge a review request on the pull request",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
method: "POST",
|
|
109
|
+
pattern: "repos/{repo}/issues/comments/{id}/reactions",
|
|
110
|
+
permission: "issues",
|
|
111
|
+
level: "write",
|
|
112
|
+
identities: APP_ONLY,
|
|
113
|
+
feature: "Acknowledge a review request in a comment",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
method: "GET",
|
|
117
|
+
pattern: "repos/{repo}/contents/{path}",
|
|
118
|
+
permission: "contents",
|
|
119
|
+
level: "read",
|
|
120
|
+
identities: READERS,
|
|
121
|
+
feature: "Read files and PR_REVIEW_GUIDE.md",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
method: "GET",
|
|
125
|
+
pattern: "repos/{repo}/git/trees/{ref}",
|
|
126
|
+
permission: "contents",
|
|
127
|
+
level: "read",
|
|
128
|
+
identities: READERS,
|
|
129
|
+
feature: "Read the file tree",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
method: "GET",
|
|
133
|
+
pattern: "repos/{repo}/commits",
|
|
134
|
+
permission: "contents",
|
|
135
|
+
level: "read",
|
|
136
|
+
identities: READERS,
|
|
137
|
+
feature: "Read commit history",
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
method: "GET",
|
|
141
|
+
pattern: "repos/{repo}/compare/{range}",
|
|
142
|
+
permission: "contents",
|
|
143
|
+
level: "read",
|
|
144
|
+
identities: READERS,
|
|
145
|
+
feature: "Compare two commits",
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
method: "GET",
|
|
149
|
+
pattern: "repos/{repo}/releases",
|
|
150
|
+
permission: "contents",
|
|
151
|
+
level: "read",
|
|
152
|
+
identities: READERS,
|
|
153
|
+
feature: "Read releases in the probe",
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
method: "POST",
|
|
157
|
+
pattern: "repos/{repo}/check-runs",
|
|
158
|
+
permission: "checks",
|
|
159
|
+
level: "write",
|
|
160
|
+
identities: APP_ONLY,
|
|
161
|
+
feature: "Create the review check run",
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
method: "PATCH",
|
|
165
|
+
pattern: "repos/{repo}/check-runs/{id}",
|
|
166
|
+
permission: "checks",
|
|
167
|
+
level: "write",
|
|
168
|
+
identities: APP_ONLY,
|
|
169
|
+
feature: "Update the review check run",
|
|
170
|
+
},
|
|
171
|
+
// Calls that need no repository permission. `credentials.ts` and `gh.ts`
|
|
172
|
+
// make the first two, `app.ts` the App ones, `oauth.ts` the dashboard sign-in.
|
|
173
|
+
{
|
|
174
|
+
method: "GET",
|
|
175
|
+
pattern: "user",
|
|
176
|
+
identities: ["gh", "pat"],
|
|
177
|
+
feature: "Check who the credential belongs to",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
method: "GET",
|
|
181
|
+
pattern: "user/repos",
|
|
182
|
+
permission: "metadata",
|
|
183
|
+
level: "read",
|
|
184
|
+
identities: ["pat"],
|
|
185
|
+
feature: "Check the token can list repositories",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
method: "GET",
|
|
189
|
+
pattern: "rate_limit",
|
|
190
|
+
identities: ["gh"],
|
|
191
|
+
feature: "Print the remaining quota with --debug",
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
method: "GET",
|
|
195
|
+
pattern: "app/installations",
|
|
196
|
+
identities: APP_ONLY,
|
|
197
|
+
feature: "List where the App is installed",
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
method: "POST",
|
|
201
|
+
pattern: "app/installations/{id}/access_tokens",
|
|
202
|
+
identities: APP_ONLY,
|
|
203
|
+
feature: "Mint an installation token",
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
method: "GET",
|
|
207
|
+
pattern: "installation/repositories",
|
|
208
|
+
identities: APP_ONLY,
|
|
209
|
+
feature: "List the repositories an installation can see",
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
method: "POST",
|
|
213
|
+
pattern: "app-manifests/{code}/conversions",
|
|
214
|
+
identities: [],
|
|
215
|
+
feature: "Finish creating the App from the dashboard",
|
|
216
|
+
},
|
|
217
|
+
];
|
|
218
|
+
function strongest(identity) {
|
|
219
|
+
const result = {};
|
|
220
|
+
for (const endpoint of ENDPOINTS) {
|
|
221
|
+
if (!endpoint.permission || !endpoint.identities.includes(identity)) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const entry = (result[endpoint.permission] ??= {
|
|
225
|
+
level: "read",
|
|
226
|
+
features: [],
|
|
227
|
+
});
|
|
228
|
+
if (endpoint.level === "write")
|
|
229
|
+
entry.level = "write";
|
|
230
|
+
entry.features.push(endpoint.feature);
|
|
231
|
+
}
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
/** What the App must be granted, in the shape the manifest and the save-time
|
|
235
|
+
* check want. */
|
|
236
|
+
export const APP_PERMISSIONS = Object.fromEntries(ORDER.flatMap((name) => {
|
|
237
|
+
const entry = strongest("app")[name];
|
|
238
|
+
return entry ? [[name, entry.level]] : [];
|
|
239
|
+
}));
|
|
240
|
+
function patternRegex(pattern) {
|
|
241
|
+
const source = pattern
|
|
242
|
+
.split(/(\{[a-z]+\})/)
|
|
243
|
+
.map((part) => {
|
|
244
|
+
if (part === "{repo}")
|
|
245
|
+
return "[^/]+/[^/]+";
|
|
246
|
+
if (part === "{path}")
|
|
247
|
+
return ".+";
|
|
248
|
+
if (part.startsWith("{"))
|
|
249
|
+
return "[^/]+";
|
|
250
|
+
return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
251
|
+
})
|
|
252
|
+
.join("");
|
|
253
|
+
return new RegExp(`^${source}$`);
|
|
254
|
+
}
|
|
255
|
+
const REGEXES = ENDPOINTS.map((endpoint) => patternRegex(endpoint.pattern));
|
|
256
|
+
/** The entry for a real request such as `repos/o/r/pulls/4/files?page=2`. */
|
|
257
|
+
export function endpointFor(method, endpoint) {
|
|
258
|
+
const path = endpoint.replace(/\?.*$/, "").replace(/^\/+/, "");
|
|
259
|
+
const index = ENDPOINTS.findIndex((candidate, at) => candidate.method === method && REGEXES[at].test(path));
|
|
260
|
+
return index === -1 ? undefined : ENDPOINTS[index];
|
|
261
|
+
}
|
|
262
|
+
function accessLabel(level) {
|
|
263
|
+
return level === "write" ? "Read and write" : "Read";
|
|
264
|
+
}
|
|
265
|
+
/** `who` is what the failing credential is called in the sentence. */
|
|
266
|
+
export function refusalHint(method, endpoint, who) {
|
|
267
|
+
const base = `GitHub refused ${method} ${endpoint}.`;
|
|
268
|
+
const found = endpointFor(method, endpoint);
|
|
269
|
+
if (!found?.permission)
|
|
270
|
+
return base;
|
|
271
|
+
if (who === "token" && !found.identities.includes("pat")) {
|
|
272
|
+
return `${base} Only a GitHub App can do this.`;
|
|
273
|
+
}
|
|
274
|
+
return `${base} ${needs(found, who)}`;
|
|
275
|
+
}
|
|
276
|
+
/** A fine-grained token is granted permissions and a classic one scopes, so a
|
|
277
|
+
* token error names both. */
|
|
278
|
+
function needs(found, who) {
|
|
279
|
+
const grant = `${PERMISSION_LABEL[found.permission]}: ${accessLabel(found.level)}`;
|
|
280
|
+
return who === "App"
|
|
281
|
+
? `The GitHub App needs ${grant}.`
|
|
282
|
+
: `A fine-grained token needs ${grant}. A classic token needs the repo scope.`;
|
|
283
|
+
}
|
|
284
|
+
export function missingHint(method, endpoint, who) {
|
|
285
|
+
const name = who === "token" ? "the token" : "the GitHub App";
|
|
286
|
+
const found = endpointFor(method, endpoint);
|
|
287
|
+
const base = `GitHub returned 404 for ${method} ${endpoint}. The repository may not exist or ${name} cannot see it.`;
|
|
288
|
+
if (!found?.permission)
|
|
289
|
+
return base;
|
|
290
|
+
return `${base} ${needs(found, who)}`;
|
|
291
|
+
}
|
|
292
|
+
/** Classic scopes cover every repository call above, for `gh` and a classic
|
|
293
|
+
* PAT alike. No scope is finer than that. */
|
|
294
|
+
const SCOPES = [
|
|
295
|
+
{ scope: "repo", why: "Read public and private repositories" },
|
|
296
|
+
{ scope: "public_repo", why: "Read public repositories only" },
|
|
297
|
+
];
|
|
298
|
+
function rows(header, body) {
|
|
299
|
+
const line = (cells) => `| ${cells.join(" | ")} |`;
|
|
300
|
+
return [
|
|
301
|
+
line(header),
|
|
302
|
+
line(header.map((cell) => "-".repeat(Math.max(3, cell.length)))),
|
|
303
|
+
...body.map(line),
|
|
304
|
+
].join("\n");
|
|
305
|
+
}
|
|
306
|
+
function byPermission(identity, why) {
|
|
307
|
+
const strong = strongest(identity);
|
|
308
|
+
return rows(identity === "app"
|
|
309
|
+
? ["Permission", "Access", why]
|
|
310
|
+
: ["Repository permission", "Access", why], ORDER.filter((name) => strong[name]).map((name) => [
|
|
311
|
+
PERMISSION_LABEL[name],
|
|
312
|
+
accessLabel(strong[name].level),
|
|
313
|
+
[...new Set(strong[name].features)]
|
|
314
|
+
.map((text, at) => at === 0 ? text : text[0].toLowerCase() + text.slice(1))
|
|
315
|
+
.join(", "),
|
|
316
|
+
]));
|
|
317
|
+
}
|
|
318
|
+
/** A markdown table for the docs. `build_docs.ts` writes it between the
|
|
319
|
+
* `permissions:<kind>` markers and a test fails when the page is stale. */
|
|
320
|
+
export function permissionTable(kind) {
|
|
321
|
+
if (kind === "app")
|
|
322
|
+
return byPermission("app", "Why");
|
|
323
|
+
if (kind === "pat-fine")
|
|
324
|
+
return byPermission("pat", "Needed for");
|
|
325
|
+
if (kind === "oauth") {
|
|
326
|
+
return rows(["Scope", "Why"], [
|
|
327
|
+
[
|
|
328
|
+
"None requested",
|
|
329
|
+
"The sign-in only reads your GitHub login from `GET /user`",
|
|
330
|
+
],
|
|
331
|
+
]);
|
|
332
|
+
}
|
|
333
|
+
return rows(["Scope", "Why"], SCOPES.map(({ scope, why }) => [`\`${scope}\``, why]));
|
|
334
|
+
}
|
|
@@ -295,7 +295,7 @@ export function extractFacts(source, options) {
|
|
|
295
295
|
add(facts, {
|
|
296
296
|
...fact("review-bar", instruction, `review discussion (${count} mentions)`, count),
|
|
297
297
|
// A rule mined from discussion across several pull requests is
|
|
298
|
-
// repository policy, not one request's narrative
|
|
298
|
+
// repository policy, not one request's narrative.
|
|
299
299
|
origin: "repository",
|
|
300
300
|
});
|
|
301
301
|
}
|
|
@@ -5,10 +5,10 @@ export type ReviewDocuments = {
|
|
|
5
5
|
};
|
|
6
6
|
/** A fact read out of a single pull request describes that request, not the
|
|
7
7
|
* repository. `origin` is absent on facts written before the field existed, so
|
|
8
|
-
* those count as repository policy
|
|
8
|
+
* those count as repository policy. */
|
|
9
9
|
export declare function isPullRequestFact(item: Fact): boolean;
|
|
10
10
|
/** The selected review signals, before the `>2` threshold is applied. Exported
|
|
11
|
-
* so `init` can tell the user how far short of the threshold a repository is
|
|
12
|
-
*
|
|
11
|
+
* so `init` can tell the user how far short of the threshold a repository is,
|
|
12
|
+
* rather than silently skipping the two guide files. */
|
|
13
13
|
export declare function reviewSignalCount(facts: Fact[]): number;
|
|
14
14
|
export declare function buildReviewDocuments(facts: Fact[]): ReviewDocuments | undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** A fact read out of a single pull request describes that request, not the
|
|
2
2
|
* repository. `origin` is absent on facts written before the field existed, so
|
|
3
|
-
* those count as repository policy
|
|
3
|
+
* those count as repository policy. */
|
|
4
4
|
export function isPullRequestFact(item) {
|
|
5
5
|
return item.origin === "pull-request";
|
|
6
6
|
}
|
|
@@ -22,12 +22,12 @@ function reviewBarFacts(facts) {
|
|
|
22
22
|
.sort((a, b) => b.weight - a.weight || a.claim.localeCompare(b.claim));
|
|
23
23
|
}
|
|
24
24
|
/** The short guide's checklist: every recurring review signal, including the
|
|
25
|
-
* ones mined from pull requests
|
|
25
|
+
* ones mined from pull requests. */
|
|
26
26
|
function reviewFacts(facts) {
|
|
27
27
|
return dedupe(reviewBarFacts(facts));
|
|
28
28
|
}
|
|
29
29
|
/** The detailed guide's checklist: only expectations backed by repository-wide
|
|
30
|
-
* evidence, never a single pull request's narrative
|
|
30
|
+
* evidence, never a single pull request's narrative. */
|
|
31
31
|
function detailedFacts(facts) {
|
|
32
32
|
return dedupe(reviewBarFacts(facts).filter((item) => !isPullRequestFact(item)));
|
|
33
33
|
}
|
|
@@ -65,8 +65,8 @@ ${bullets.join("\n")}
|
|
|
65
65
|
`;
|
|
66
66
|
}
|
|
67
67
|
/** The selected review signals, before the `>2` threshold is applied. Exported
|
|
68
|
-
* so `init` can tell the user how far short of the threshold a repository is
|
|
69
|
-
*
|
|
68
|
+
* so `init` can tell the user how far short of the threshold a repository is,
|
|
69
|
+
* rather than silently skipping the two guide files. */
|
|
70
70
|
export function reviewSignalCount(facts) {
|
|
71
71
|
return reviewFacts(facts).length;
|
|
72
72
|
}
|
|
@@ -114,8 +114,7 @@ export function analyzeProbe(meta, pulls, detailSamples, commits) {
|
|
|
114
114
|
else if (commits.length) {
|
|
115
115
|
// Only claim the history is noise when the useful-commit ratio actually
|
|
116
116
|
// supports it. With `commits: 8 · useful: 5` the ratio is above a half,
|
|
117
|
-
// and the old unconditional sentence contradicted the numbers beside it
|
|
118
|
-
// (CORE-24).
|
|
117
|
+
// and the old unconditional sentence contradicted the numbers beside it.
|
|
119
118
|
const usefulRatio = usefulCommits / commits.length;
|
|
120
119
|
if (usefulRatio < 0.5) {
|
|
121
120
|
reasons.push("Commit history is mostly merge/noise commits, so it is not recommended as a source.");
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export declare const sectionTitles: Record<string, string>;
|
|
2
2
|
export declare const sectionKeys: string[];
|
|
3
3
|
/** Sections whose content also lives in `CODEBASE.md`. The skill links to that
|
|
4
|
-
* file instead of keeping a second copy
|
|
4
|
+
* file instead of keeping a second copy. */
|
|
5
5
|
export declare const codebaseSectionKeys: Set<string>;
|
|
6
6
|
/** A fact read out of a single pull request describes that request, not the
|
|
7
7
|
* repository, so it may only feed the `review-bar` checklist. Every other
|
|
8
|
-
* section states repository policy and must not learn from one PR's narrative
|
|
9
|
-
*
|
|
8
|
+
* section states repository policy and must not learn from one PR's narrative.
|
|
9
|
+
* Keep this in sync with `guide.ts`, which applies the same
|
|
10
10
|
* rule to the review guides. */
|
|
11
11
|
export declare function sectionAllowsPrFacts(key: string): boolean;
|
|
@@ -12,7 +12,7 @@ export const sectionTitles = {
|
|
|
12
12
|
};
|
|
13
13
|
export const sectionKeys = Object.keys(sectionTitles);
|
|
14
14
|
/** Sections whose content also lives in `CODEBASE.md`. The skill links to that
|
|
15
|
-
* file instead of keeping a second copy
|
|
15
|
+
* file instead of keeping a second copy. */
|
|
16
16
|
export const codebaseSectionKeys = new Set([
|
|
17
17
|
"layout",
|
|
18
18
|
"style",
|
|
@@ -21,8 +21,8 @@ export const codebaseSectionKeys = new Set([
|
|
|
21
21
|
]);
|
|
22
22
|
/** A fact read out of a single pull request describes that request, not the
|
|
23
23
|
* repository, so it may only feed the `review-bar` checklist. Every other
|
|
24
|
-
* section states repository policy and must not learn from one PR's narrative
|
|
25
|
-
*
|
|
24
|
+
* section states repository policy and must not learn from one PR's narrative.
|
|
25
|
+
* Keep this in sync with `guide.ts`, which applies the same
|
|
26
26
|
* rule to the review guides. */
|
|
27
27
|
export function sectionAllowsPrFacts(key) {
|
|
28
28
|
return key === "review-bar";
|
|
@@ -2,7 +2,7 @@ import type { Fact } from "./types.ts";
|
|
|
2
2
|
/** The full text of the sections that live in `CODEBASE.md`. The skill itself
|
|
3
3
|
* only links to that file, so this is the single place the codebase guidance is
|
|
4
4
|
* written out: the synthesized override when there is one, otherwise the
|
|
5
|
-
* rendered facts
|
|
5
|
+
* rendered facts. */
|
|
6
6
|
export declare function codebaseBody(facts: Fact[], overrides?: Record<string, string>): string;
|
|
7
7
|
/** Slices an assembled skill's `## Heading` sections back out by section key,
|
|
8
8
|
* from `<!-- section:key -->` markers or (fallback) canonical heading text. */
|
|
@@ -6,8 +6,8 @@ function sectionFacts(facts, key) {
|
|
|
6
6
|
.sort((a, b) => b.weight - a.weight);
|
|
7
7
|
}
|
|
8
8
|
/** Facts a section may state. A fact read out of a single pull request is that
|
|
9
|
-
* request's narrative, not a rule, so only `review-bar` may use it
|
|
10
|
-
*
|
|
9
|
+
* request's narrative, not a rule, so only `review-bar` may use it. Facts
|
|
10
|
+
* written before `origin` existed count as repository policy. */
|
|
11
11
|
function usableFacts(facts, key) {
|
|
12
12
|
const items = sectionFacts(facts, key);
|
|
13
13
|
return sectionAllowsPrFacts(key)
|
|
@@ -42,7 +42,7 @@ function codebaseSectionText(key, facts, overrides) {
|
|
|
42
42
|
/** The full text of the sections that live in `CODEBASE.md`. The skill itself
|
|
43
43
|
* only links to that file, so this is the single place the codebase guidance is
|
|
44
44
|
* written out: the synthesized override when there is one, otherwise the
|
|
45
|
-
* rendered facts
|
|
45
|
+
* rendered facts. */
|
|
46
46
|
export function codebaseBody(facts, overrides = {}) {
|
|
47
47
|
return [...codebaseSectionKeys]
|
|
48
48
|
.map((key) => codebaseSectionText(key, facts, overrides))
|
|
@@ -83,7 +83,7 @@ async function hash(value) {
|
|
|
83
83
|
}
|
|
84
84
|
/** The fields that decide a section's text. `origin` is included so a fact
|
|
85
85
|
* moving from PR narrative to repository policy (or the reverse) counts as a
|
|
86
|
-
* change rather than reusing a section built under the old rule
|
|
86
|
+
* change rather than reusing a section built under the old rule. */
|
|
87
87
|
function hashInput(item) {
|
|
88
88
|
return [
|
|
89
89
|
item.id,
|
|
@@ -10,7 +10,7 @@ export declare function extractAiFactsWithReport(provider: AiProvider, repo: str
|
|
|
10
10
|
skipped: SkippedUnit[];
|
|
11
11
|
}>;
|
|
12
12
|
/** Same as {@link extractAiFacts}, but also reports what the batch skipped so
|
|
13
|
-
* the caller can put it in the final `[done]` line
|
|
13
|
+
* the caller can put it in the final `[done]` line. */
|
|
14
14
|
export declare function enrichFactsWithReport(provider: AiProvider, repo: string, base: Fact[], source: Source, options: Options, usage?: UsageSink): Promise<{
|
|
15
15
|
facts: Fact[];
|
|
16
16
|
skipped: SkippedUnit[];
|
|
@@ -131,7 +131,7 @@ function queueFor(provider, repo, ai, model, concurrency, validate) {
|
|
|
131
131
|
}
|
|
132
132
|
/** `factsFromResponse` as a validator: `null` when the text parses into facts,
|
|
133
133
|
* otherwise the parse error's message. Used so an unparseable response is never
|
|
134
|
-
* cached
|
|
134
|
+
* cached. */
|
|
135
135
|
function validateFacts(response, evidence, defaultScope) {
|
|
136
136
|
try {
|
|
137
137
|
factsFromResponse(response.text, evidence, defaultScope, "repository");
|
|
@@ -187,7 +187,7 @@ function sectionGoal(key) {
|
|
|
187
187
|
function synthesisFacts(facts, key) {
|
|
188
188
|
return (facts
|
|
189
189
|
// A section that states repository policy never learns from a single pull
|
|
190
|
-
// request's narrative; only `review-bar` may
|
|
190
|
+
// request's narrative; only `review-bar` may.
|
|
191
191
|
.filter((item) => item.sectionKey === key &&
|
|
192
192
|
(sectionAllowsPrFacts(key) || !isPullRequestFact(item)))
|
|
193
193
|
.sort((a, b) => (b.status === "active" ? 1 : 0) - (a.status === "active" ? 1 : 0) ||
|
|
@@ -283,7 +283,7 @@ ${files}`, 2_200));
|
|
|
283
283
|
return { facts, skipped: queue.skippedUnits() };
|
|
284
284
|
}
|
|
285
285
|
/** Same as {@link extractAiFacts}, but also reports what the batch skipped so
|
|
286
|
-
* the caller can put it in the final `[done]` line
|
|
286
|
+
* the caller can put it in the final `[done]` line. */
|
|
287
287
|
export async function enrichFactsWithReport(provider, repo, base, source, options, usage) {
|
|
288
288
|
const { facts, skipped } = await extractAiFactsWithReport(provider, repo, source, options, usage);
|
|
289
289
|
return { facts: mergeFacts(base, facts), skipped };
|
|
@@ -315,7 +315,7 @@ export async function synthesizeSections(provider, repo, facts, _previousMarkdow
|
|
|
315
315
|
const relevant = synthesisFacts(facts, key);
|
|
316
316
|
// A section whose only facts came from a single pull request has no
|
|
317
317
|
// repository policy to synthesize, so it is left to the deterministic
|
|
318
|
-
// renderer instead of asking the model for an empty answer
|
|
318
|
+
// renderer instead of asking the model for an empty answer.
|
|
319
319
|
if (!relevant.length)
|
|
320
320
|
continue;
|
|
321
321
|
keys.push(key);
|
|
@@ -354,7 +354,7 @@ FACTS:
|
|
|
354
354
|
${JSON.stringify(relevant)}`,
|
|
355
355
|
});
|
|
356
356
|
}
|
|
357
|
-
const queue = queueFor(provider, repo, ai, model,
|
|
357
|
+
const queue = queueFor(provider, repo, ai, model, concurrency, (request, response) => validateSection(response, keys[requests.indexOf(request)] ?? ""));
|
|
358
358
|
const responses = await queue.run(requests, usage);
|
|
359
359
|
// A section that the batch skipped has no response, and a unit that failed
|
|
360
360
|
// validation never produces one: omit those keys entirely rather than
|
|
@@ -41,7 +41,7 @@ export type Fact = {
|
|
|
41
41
|
status: "active" | "contradicted" | "stale";
|
|
42
42
|
/** Where the fact was mined from. A fact read out of a single pull request
|
|
43
43
|
* describes that request, not the repository, so it may not become a
|
|
44
|
-
* repository-wide rule
|
|
44
|
+
* repository-wide rule. Missing means `repository`, which
|
|
45
45
|
* keeps facts written before this field existed. */
|
|
46
46
|
origin?: "repository" | "pull-request";
|
|
47
47
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { dirname } from "node:path";
|
|
2
2
|
import { isNotFound, mkdir, readTextFile, writeTextFile, } from "../util/runtime.js";
|
|
3
3
|
const EXCLUDE_LINE = "/.co-maintainer-codegraph/";
|
|
4
|
-
/**
|
|
4
|
+
/** Hide the local codegraph index from git status. */
|
|
5
5
|
export async function ensureCodegraphGitExclude(gitRoot, run) {
|
|
6
6
|
const pathResult = await run("git", ["rev-parse", "--git-path", "info/exclude"], gitRoot);
|
|
7
7
|
if (pathResult.code !== 0)
|
|
@@ -13,7 +13,7 @@ export async function prepareLocalCodegraph(input) {
|
|
|
13
13
|
const resolved = await ensureCodegraphForReview({
|
|
14
14
|
allowInstall: input.allowInstall,
|
|
15
15
|
// The prompt is safe only when a human can answer it; `canPrompt` checks
|
|
16
|
-
// stdin, stdout and `CI`, so a `</dev/null` or piped run never asks
|
|
16
|
+
// stdin, stdout and `CI`, so a `</dev/null` or piped run never asks.
|
|
17
17
|
interactive: input.interactive ?? canPrompt(),
|
|
18
18
|
log: (message) => log("codegraph", message.replace(/^\[codegraph\]\s*/, "")),
|
|
19
19
|
});
|
|
@@ -2,7 +2,7 @@ import { type Run } from "../pr/checkout.ts";
|
|
|
2
2
|
import { CliError } from "../cli/error.ts";
|
|
3
3
|
export declare function gitRoot(cwd: string, run?: Run): Promise<string>;
|
|
4
4
|
/** Backward-compatible name for the shared CLI error. Local and remote review
|
|
5
|
-
* threw this before
|
|
5
|
+
* threw this before `CliError` existed, so it stays as a thin
|
|
6
6
|
* subclass and every existing call site keeps working. */
|
|
7
7
|
export declare class ReviewCliError extends CliError {
|
|
8
8
|
constructor(code: string, message: string, hint?: string, exitCode?: number);
|
|
@@ -10,7 +10,7 @@ export async function gitRoot(cwd, run = runCommand) {
|
|
|
10
10
|
return result.stdout.trim();
|
|
11
11
|
}
|
|
12
12
|
/** Backward-compatible name for the shared CLI error. Local and remote review
|
|
13
|
-
* threw this before
|
|
13
|
+
* threw this before `CliError` existed, so it stays as a thin
|
|
14
14
|
* subclass and every existing call site keeps working. */
|
|
15
15
|
export class ReviewCliError extends CliError {
|
|
16
16
|
constructor(code, message, hint, exitCode = EXIT_USAGE) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Pure parsers for git -z output and remote URL normalization
|
|
1
|
+
/** Pure parsers for git -z output and remote URL normalization. */
|
|
2
2
|
export type NameStatusEntry = {
|
|
3
3
|
status: "added" | "modified" | "removed" | "renamed";
|
|
4
4
|
path: string;
|
|
@@ -21,7 +21,7 @@ export declare function normalizeGithubRemote(url: string): NormalizeRemoteResul
|
|
|
21
21
|
export declare function parseNameStatusZ(raw: string): NameStatusEntry[];
|
|
22
22
|
/** Parse `git diff --numstat -z` (`add\tdelete\tpath\0` per file). */
|
|
23
23
|
export declare function parseNumstatZ(raw: string): Map<string, NumstatEntry>;
|
|
24
|
-
/** Paths from name-status that have no entry in a bulk unified diff
|
|
24
|
+
/** Paths from name-status that have no entry in a bulk unified diff. */
|
|
25
25
|
export declare function pathsMissingPatches(statuses: NameStatusEntry[], patches: Map<string, string>): string[];
|
|
26
26
|
/** Split unified diff output into per-file patch bodies (without diff --git header). */
|
|
27
27
|
export declare function splitDiffPatches(raw: string): Map<string, string>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Pure parsers for git -z output and remote URL normalization
|
|
1
|
+
/** Pure parsers for git -z output and remote URL normalization. */
|
|
2
2
|
export function normalizeGithubRemote(url) {
|
|
3
3
|
const trimmed = url.trim().replace(/\/+$/, "");
|
|
4
4
|
let host = "";
|
|
@@ -102,7 +102,7 @@ export function parseNumstatZ(raw) {
|
|
|
102
102
|
}
|
|
103
103
|
return map;
|
|
104
104
|
}
|
|
105
|
-
/** Paths from name-status that have no entry in a bulk unified diff
|
|
105
|
+
/** Paths from name-status that have no entry in a bulk unified diff. */
|
|
106
106
|
export function pathsMissingPatches(statuses, patches) {
|
|
107
107
|
return statuses
|
|
108
108
|
.filter((entry) => entry.status !== "removed")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Run } from "../pr/checkout.ts";
|
|
2
2
|
import { type Revision } from "../review/revision.ts";
|
|
3
3
|
import { type UntrackedWarning } from "./git_untracked.ts";
|
|
4
|
-
/** Single-file unified patch when bulk `git diff` did not map a path
|
|
4
|
+
/** Single-file unified patch when bulk `git diff` did not map a path. */
|
|
5
5
|
export declare function perFileUnifiedPatch(cwd: string, base: string, path: string, run: Run): Promise<string>;
|
|
6
6
|
export declare function mergeBase(cwd: string, baseRef: string, remote: string, run: Run): Promise<string>;
|
|
7
7
|
export declare function resolveBaseRef(cwd: string, remote: string, toBranch: string | undefined, run: Run): Promise<{
|
|
@@ -12,7 +12,7 @@ const unifiedDiffArgs = [
|
|
|
12
12
|
"-M",
|
|
13
13
|
"--unified=3",
|
|
14
14
|
];
|
|
15
|
-
/** Single-file unified patch when bulk `git diff` did not map a path
|
|
15
|
+
/** Single-file unified patch when bulk `git diff` did not map a path. */
|
|
16
16
|
export async function perFileUnifiedPatch(cwd, base, path, run) {
|
|
17
17
|
const result = await run("git", [...diffEnv, ...unifiedDiffArgs, base, "--", path], cwd);
|
|
18
18
|
if (result.code !== 0 || !result.stdout.trim())
|
|
@@ -3,7 +3,7 @@ export type UntrackedWarning = {
|
|
|
3
3
|
code: "untracked_unreadable";
|
|
4
4
|
message: string;
|
|
5
5
|
};
|
|
6
|
-
/** Build patch for a new untracked file
|
|
6
|
+
/** Build patch for a new untracked file. */
|
|
7
7
|
export declare function patchFromNewFileContent(content: Uint8Array): {
|
|
8
8
|
patch: string;
|
|
9
9
|
additions: number;
|