@open-cr-agent/cli 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/memory.js +2 -2
- package/dist/metrics.d.ts +47 -0
- package/dist/metrics.js +227 -0
- package/dist/review/args.d.ts +15 -3
- package/dist/review/args.js +86 -12
- package/dist/review/command.d.ts +2 -0
- package/dist/review/command.js +46 -15
- package/dist/review/config.d.ts +29 -4
- package/dist/review/config.js +125 -10
- package/dist/review/plan-render.d.ts +1 -1
- package/dist/review/progress.js +1 -1
- package/dist/review/provenance.d.ts +6 -0
- package/dist/review/provenance.js +38 -0
- package/dist/review/remote-config.d.ts +2 -0
- package/dist/review/remote-config.js +19 -9
- package/dist/review/render.d.ts +2 -1
- package/dist/review/render.js +16 -7
- package/dist/review/runtimes.d.ts +5 -0
- package/dist/review/runtimes.js +29 -0
- package/dist/review/sarif-input.d.ts +4 -0
- package/dist/review/sarif-input.js +34 -0
- package/dist/review/sarif.d.ts +4 -0
- package/dist/review/sarif.js +153 -0
- package/dist/review/target.d.ts +5 -3
- package/dist/review/target.js +125 -7
- package/dist/review/terminal.js +1 -1
- package/dist/run.d.ts +0 -1
- package/dist/run.js +18 -8
- package/dist/version.d.ts +2 -0
- package/dist/version.js +4 -0
- package/package.json +12 -6
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { coverageGaps, } from "@open-cr-agent/core";
|
|
2
|
+
import { serializeOutput } from "@open-cr-agent/core/internal";
|
|
3
|
+
// `--format sarif`: the review as a SARIF 2.1.0 log, for code scanning and
|
|
4
|
+
// security dashboards. One rule per reviewer category, one result per
|
|
5
|
+
// finding. Findings an earlier review reported in files this run did not
|
|
6
|
+
// review again stay open, so they are results too, marked unchanged.
|
|
7
|
+
// https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html
|
|
8
|
+
const SCHEMA = "https://json.schemastore.org/sarif-2.1.0.json";
|
|
9
|
+
const LEVEL = {
|
|
10
|
+
critical: "error",
|
|
11
|
+
warning: "warning",
|
|
12
|
+
suggestion: "note",
|
|
13
|
+
};
|
|
14
|
+
export function renderSarif(report, version) {
|
|
15
|
+
const open = [
|
|
16
|
+
...(report.rereview?.unchanged ?? []),
|
|
17
|
+
...(report.rereview?.notReproduced ?? []),
|
|
18
|
+
...(report.rereview?.notRechecked ?? []),
|
|
19
|
+
];
|
|
20
|
+
const categories = [
|
|
21
|
+
...new Set([...report.findings.map((f) => f.category), ...(open.length > 0 ? [CARRIED] : [])]),
|
|
22
|
+
].sort();
|
|
23
|
+
const index = new Map(categories.map((c, i) => [c, i]));
|
|
24
|
+
const { notReviewed } = coverageGaps(report);
|
|
25
|
+
const log = {
|
|
26
|
+
$schema: SCHEMA,
|
|
27
|
+
version: "2.1.0",
|
|
28
|
+
runs: [
|
|
29
|
+
{
|
|
30
|
+
automationDetails: { id: `ocra/${report.runId}` },
|
|
31
|
+
tool: {
|
|
32
|
+
driver: {
|
|
33
|
+
name: "ocra",
|
|
34
|
+
informationUri: "https://ocra.majincheng.com",
|
|
35
|
+
version,
|
|
36
|
+
semanticVersion: version,
|
|
37
|
+
rules: categories.map((id) => ({
|
|
38
|
+
id,
|
|
39
|
+
name: id,
|
|
40
|
+
shortDescription: {
|
|
41
|
+
text: id === CARRIED
|
|
42
|
+
? CARRIED_DESCRIPTION
|
|
43
|
+
: `Findings of the ${id} reviewer or analyzer`,
|
|
44
|
+
},
|
|
45
|
+
properties: { tags: [id] },
|
|
46
|
+
})),
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
invocations: [
|
|
50
|
+
{
|
|
51
|
+
executionSuccessful: notReviewed === 0 && report.unverifiedCriticals === 0,
|
|
52
|
+
toolExecutionNotifications: [
|
|
53
|
+
...(notReviewed > 0
|
|
54
|
+
? [
|
|
55
|
+
{
|
|
56
|
+
level: "warning",
|
|
57
|
+
message: { text: `${notReviewed} selected file(s) were not reviewed` },
|
|
58
|
+
},
|
|
59
|
+
]
|
|
60
|
+
: []),
|
|
61
|
+
...report.warnings.map((w) => ({
|
|
62
|
+
level: "warning",
|
|
63
|
+
message: { text: plainText(w) },
|
|
64
|
+
})),
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
results: [
|
|
69
|
+
...report.findings.map((f) => fromFinding(f, index)),
|
|
70
|
+
...open.map((f) => fromPrior(f, index)),
|
|
71
|
+
],
|
|
72
|
+
properties: {
|
|
73
|
+
verdict: report.verdict,
|
|
74
|
+
tier: report.tier,
|
|
75
|
+
baseSha: report.changeRequest.baseSha,
|
|
76
|
+
headSha: report.changeRequest.headSha,
|
|
77
|
+
notReviewed: report.coverage
|
|
78
|
+
.filter((c) => c.status === "failed" || c.status === "unreviewed")
|
|
79
|
+
.map((c) => c.path),
|
|
80
|
+
unverifiedCriticals: report.unverifiedCriticals,
|
|
81
|
+
costUsd: report.usage.costUsd,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
return `${serializeOutput(log)}\n`;
|
|
87
|
+
}
|
|
88
|
+
// Open findings an earlier review reported, carried over under their own
|
|
89
|
+
// rule: their reviewer category is not recorded in the review state.
|
|
90
|
+
const CARRIED = "ocra-carried-over";
|
|
91
|
+
const CARRIED_DESCRIPTION = "Findings an earlier review reported that are still open in files this run did not review again";
|
|
92
|
+
function fromFinding(f, index) {
|
|
93
|
+
const text = [f.title, f.body, ...(f.suggestion ? [`Suggestion: ${f.suggestion}`] : [])].join("\n\n");
|
|
94
|
+
return {
|
|
95
|
+
ruleId: f.category,
|
|
96
|
+
ruleIndex: index.get(f.category) ?? 0,
|
|
97
|
+
level: LEVEL[f.severity],
|
|
98
|
+
message: { text: plainText(text) },
|
|
99
|
+
locations: [location(f.file, f.lineRange)],
|
|
100
|
+
partialFingerprints: { "ocra/v1": f.fingerprint },
|
|
101
|
+
properties: {
|
|
102
|
+
reviewer: f.reviewer,
|
|
103
|
+
severity: f.severity,
|
|
104
|
+
verification: f.verification ?? "unchecked",
|
|
105
|
+
status: f.status === "unfixed" ? "unfixed" : "new",
|
|
106
|
+
...(f.lowConfidence ? { lowConfidence: true } : {}),
|
|
107
|
+
task: f.provenance.task,
|
|
108
|
+
...(f.provenance.model === undefined ? {} : { model: f.provenance.model }),
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function fromPrior(f, index) {
|
|
113
|
+
return {
|
|
114
|
+
ruleId: CARRIED,
|
|
115
|
+
ruleIndex: index.get(CARRIED) ?? 0,
|
|
116
|
+
level: LEVEL[f.severity],
|
|
117
|
+
message: { text: plainText(f.title) },
|
|
118
|
+
// The review state keeps no lines for them, so they apply to the file.
|
|
119
|
+
locations: [location(f.file)],
|
|
120
|
+
partialFingerprints: { "ocra/v1": f.fingerprint },
|
|
121
|
+
baselineState: "unchanged",
|
|
122
|
+
properties: { severity: f.severity, verification: f.verification ?? "unchecked" },
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function location(file, lines) {
|
|
126
|
+
return {
|
|
127
|
+
physicalLocation: {
|
|
128
|
+
// Each segment percent-encoded: a path may hold "#", "?" or spaces,
|
|
129
|
+
// which a URI would read as a fragment, a query or an error.
|
|
130
|
+
artifactLocation: {
|
|
131
|
+
uri: file.split("/").map(encodeURIComponent).join("/"),
|
|
132
|
+
uriBaseId: "%SRCROOT%",
|
|
133
|
+
},
|
|
134
|
+
...(lines ? { region: { startLine: lines.start, endLine: lines.end } } : {}),
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// SARIF viewers turn "[text](target)" in a message into a link (SARIF
|
|
139
|
+
// 3.11.6), and some turn bare web addresses into links too. Text from a
|
|
140
|
+
// reviewed change or a model must do neither: every backslash and bracket is
|
|
141
|
+
// escaped, backslashes first so a planted "\[" cannot undo the escape, and
|
|
142
|
+
// web addresses get a zero-width space in their scheme or after "www", as in
|
|
143
|
+
// pull request comments (vcs-platform's safeMarkdown, which also handles
|
|
144
|
+
// markup SARIF text does not have). Braces are doubled, since "{0}" is a
|
|
145
|
+
// placeholder (SARIF 3.11.5); GitHub shows "{{" as "{".
|
|
146
|
+
export function plainText(text) {
|
|
147
|
+
return text
|
|
148
|
+
.replace(/[\\[\]]/g, (c) => `\\${c}`)
|
|
149
|
+
.replace(/[{}]/g, (c) => c + c)
|
|
150
|
+
.replace(/\b(https?|ftp)(?=:\/\/)/gi, "$1")
|
|
151
|
+
.replace(/\bwww(?=\.)/gi, "www");
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=sarif.js.map
|
package/dist/review/target.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { PullRequestTarget, ReviewArgs } from "./args.js";
|
|
1
|
+
import { type OcraPlugin, type PluginRegistry, type VcsAdapter } from "@open-cr-agent/core";
|
|
2
|
+
import type { MergeRequestTarget, PullRequestTarget, ReviewArgs } from "./args.js";
|
|
3
3
|
import { type CliConfig } from "./config.js";
|
|
4
4
|
export interface ReviewTarget {
|
|
5
5
|
config: CliConfig;
|
|
@@ -7,9 +7,11 @@ export interface ReviewTarget {
|
|
|
7
7
|
createVcs(registry: PluginRegistry): VcsAdapter;
|
|
8
8
|
readTrusted?: (path: string) => Promise<string | undefined>;
|
|
9
9
|
publish: boolean;
|
|
10
|
+
publishesTo?: string;
|
|
10
11
|
}
|
|
11
12
|
type Env = Readonly<Record<string, string | undefined>>;
|
|
12
13
|
export declare function localTarget(args: ReviewArgs, cwd: string, root: string, env: Env, warn: (message: string) => void, fetchImpl?: typeof fetch): Promise<ReviewTarget>;
|
|
13
|
-
export declare function pullRequestTarget(pr: PullRequestTarget, cwd: string, root: string, env: Env, warn: (message: string) => void, fetchImpl?: typeof fetch, ignoreRepoConfig?: boolean): Promise<ReviewTarget>;
|
|
14
|
+
export declare function pullRequestTarget(pr: PullRequestTarget, cwd: string, root: string, env: Env, warn: (message: string) => void, fetchImpl?: typeof fetch, ignoreRepoConfig?: boolean, configFile?: string): Promise<ReviewTarget>;
|
|
15
|
+
export declare function mergeRequestTarget(mr: MergeRequestTarget, cwd: string, root: string, env: Env, warn: (message: string) => void, fetchImpl?: typeof fetch, ignoreRepoConfig?: boolean, configFile?: string): Promise<ReviewTarget>;
|
|
14
16
|
export {};
|
|
15
17
|
//# sourceMappingURL=target.d.ts.map
|
package/dist/review/target.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { dirname } from "node:path";
|
|
2
|
+
import { OcraError, } from "@open-cr-agent/core";
|
|
3
|
+
import { DEFAULT_BOT_LOGIN, GitHubApi } from "@open-cr-agent/vcs-github/internal";
|
|
4
|
+
import { GitLabApi } from "@open-cr-agent/vcs-gitlab/internal";
|
|
5
|
+
import { ensureCommits, filesChangedSince, LocalGitAdapter, } from "@open-cr-agent/vcs-local/internal";
|
|
3
6
|
import { ConfigError, loadConfig } from "./config.js";
|
|
4
7
|
import { loadExternalPlugins } from "./plugins.js";
|
|
5
8
|
export async function localTarget(args, cwd, root, env, warn, fetchImpl) {
|
|
6
9
|
const config = await loadConfig(root, env, {
|
|
7
10
|
repository: !args.ignoreRepoConfig,
|
|
11
|
+
...(args.configFile ? { file: args.configFile } : {}),
|
|
8
12
|
warn,
|
|
9
13
|
...(fetchImpl ? { fetch: fetchImpl } : {}),
|
|
10
14
|
});
|
|
11
15
|
return {
|
|
12
16
|
config,
|
|
13
|
-
|
|
17
|
+
// Plugins named by the user's own file are resolved from where it is.
|
|
18
|
+
plugins: await loadExternalPlugins(config.plugins, args.configFile ? dirname(args.configFile) : root),
|
|
14
19
|
createVcs: (registry) => registry.createVcs("local", { cwd, target: args.target }),
|
|
15
20
|
...(args.ignoreRepoConfig ? { readTrusted: untrustedTreeReader(args, cwd) } : {}),
|
|
16
21
|
publish: false,
|
|
@@ -28,10 +33,11 @@ function untrustedTreeReader(args, cwd) {
|
|
|
28
33
|
}
|
|
29
34
|
// The pull request's own files are untrusted: configuration, guidelines and
|
|
30
35
|
// rules come from its base commit, and repository plugins never load.
|
|
31
|
-
export async function pullRequestTarget(pr, cwd, root, env, warn, fetchImpl, ignoreRepoConfig = false) {
|
|
36
|
+
export async function pullRequestTarget(pr, cwd, root, env, warn, fetchImpl, ignoreRepoConfig = false, configFile) {
|
|
32
37
|
const token = env.GITHUB_TOKEN ?? env.GH_TOKEN;
|
|
33
|
-
if (!token)
|
|
34
|
-
throw new
|
|
38
|
+
if (!token) {
|
|
39
|
+
throw new OcraError("CONFIG_CREDENTIALS_MISSING", "--pr needs a GitHub token in GITHUB_TOKEN or GH_TOKEN");
|
|
40
|
+
}
|
|
35
41
|
const [owner = "", repo = ""] = (pr.repo ?? (await repositoryName(root, env))).split("/");
|
|
36
42
|
const apiOptions = { token, ...(env.GITHUB_API_URL ? { baseUrl: env.GITHUB_API_URL } : {}) };
|
|
37
43
|
const api = new GitHubApi({ owner, repo }, fetchImpl ? { ...apiOptions, fetch: fetchImpl } : apiOptions);
|
|
@@ -42,11 +48,12 @@ export async function pullRequestTarget(pr, cwd, root, env, warn, fetchImpl, ign
|
|
|
42
48
|
const config = await loadConfig(root, env, {
|
|
43
49
|
repository: !ignoreRepoConfig,
|
|
44
50
|
read: readTrusted,
|
|
51
|
+
...(configFile ? { file: configFile } : {}),
|
|
45
52
|
warn,
|
|
46
53
|
...(fetchImpl ? { fetch: fetchImpl } : {}),
|
|
47
54
|
});
|
|
48
55
|
if (config.plugins.length > 0) {
|
|
49
|
-
warn("plugins in
|
|
56
|
+
warn("plugins in the configuration are not loaded for pull requests");
|
|
50
57
|
}
|
|
51
58
|
const code = new LocalGitAdapter({
|
|
52
59
|
cwd,
|
|
@@ -72,8 +79,119 @@ export async function pullRequestTarget(pr, cwd, root, env, warn, fetchImpl, ign
|
|
|
72
79
|
}),
|
|
73
80
|
readTrusted,
|
|
74
81
|
publish: pr.publish,
|
|
82
|
+
publishesTo: "pull request",
|
|
75
83
|
};
|
|
76
84
|
}
|
|
85
|
+
const GITLAB_API = "https://gitlab.com/api/v4";
|
|
86
|
+
// Like a pull request: configuration, guidelines and rules come from the
|
|
87
|
+
// target branch the merge request was based on, and repository plugins never
|
|
88
|
+
// load. GitLab CI sets CI_API_V4_URL and CI_PROJECT_ID; GITLAB_TOKEN must be
|
|
89
|
+
// a token that may write notes, which CI_JOB_TOKEN may not.
|
|
90
|
+
export async function mergeRequestTarget(mr, cwd, root, env, warn, fetchImpl, ignoreRepoConfig = false, configFile) {
|
|
91
|
+
const token = env.GITLAB_TOKEN;
|
|
92
|
+
if (!token) {
|
|
93
|
+
throw new OcraError("CONFIG_CREDENTIALS_MISSING", "--mr needs a GitLab token in GITLAB_TOKEN: a project access token with the api scope and the Developer role (CI_JOB_TOKEN cannot post comments)");
|
|
94
|
+
}
|
|
95
|
+
const origin = await originUrl(root);
|
|
96
|
+
const project = mr.project ?? env.CI_PROJECT_ID ?? gitlabProject(origin);
|
|
97
|
+
const apiUrl = gitlabApi(env.CI_API_V4_URL, remoteHost(origin), warn);
|
|
98
|
+
const api = new GitLabApi(project, {
|
|
99
|
+
token,
|
|
100
|
+
baseUrl: apiUrl,
|
|
101
|
+
...(fetchImpl ? { fetch: fetchImpl } : {}),
|
|
102
|
+
});
|
|
103
|
+
const merge = await api.getMergeRequest(mr.iid);
|
|
104
|
+
const refs = merge.diff_refs;
|
|
105
|
+
if (!refs) {
|
|
106
|
+
throw new OcraError("VCS_NOT_READY", `merge request !${mr.iid} has no diff yet; try again shortly`);
|
|
107
|
+
}
|
|
108
|
+
await ensureCommits(root, [refs.start_sha, refs.head_sha], [`refs/merge-requests/${mr.iid}/head`]);
|
|
109
|
+
const base = new LocalGitAdapter({ cwd, target: { mode: "commit", commit: refs.start_sha } });
|
|
110
|
+
const readTrusted = (path) => base.readFile(path);
|
|
111
|
+
const config = await loadConfig(root, env, {
|
|
112
|
+
repository: !ignoreRepoConfig,
|
|
113
|
+
read: readTrusted,
|
|
114
|
+
...(configFile ? { file: configFile } : {}),
|
|
115
|
+
warn,
|
|
116
|
+
...(fetchImpl ? { fetch: fetchImpl } : {}),
|
|
117
|
+
});
|
|
118
|
+
if (config.plugins.length > 0) {
|
|
119
|
+
warn("plugins in the configuration are not loaded for merge requests");
|
|
120
|
+
}
|
|
121
|
+
const code = new LocalGitAdapter({
|
|
122
|
+
cwd,
|
|
123
|
+
target: { mode: "range", from: refs.start_sha, to: refs.head_sha },
|
|
124
|
+
});
|
|
125
|
+
return {
|
|
126
|
+
config,
|
|
127
|
+
plugins: [],
|
|
128
|
+
createVcs: (registry) => registry.createVcs("gitlab", {
|
|
129
|
+
project: /^\d+$/.test(project) ? Number(project) : project,
|
|
130
|
+
iid: mr.iid,
|
|
131
|
+
token,
|
|
132
|
+
apiUrl,
|
|
133
|
+
code,
|
|
134
|
+
snapshot: merge,
|
|
135
|
+
history: {
|
|
136
|
+
filesChangedSince: (from, to) => filesChangedSince(root, from, to),
|
|
137
|
+
},
|
|
138
|
+
...(fetchImpl ? { fetch: fetchImpl } : {}),
|
|
139
|
+
}),
|
|
140
|
+
readTrusted,
|
|
141
|
+
publish: mr.publish,
|
|
142
|
+
publishesTo: "merge request",
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function originUrl(root) {
|
|
146
|
+
return new LocalGitAdapter({ cwd: root, target: { mode: "workspace" } })
|
|
147
|
+
.remoteUrl("origin")
|
|
148
|
+
.catch(() => undefined);
|
|
149
|
+
}
|
|
150
|
+
// Where GITLAB_TOKEN goes: to CI_API_V4_URL, which GitLab CI sets, or else
|
|
151
|
+
// to gitlab.com, but only when origin is there too or is no remote host. A
|
|
152
|
+
// token for a self-managed instance must not travel to gitlab.com because
|
|
153
|
+
// the address was left out.
|
|
154
|
+
function gitlabApi(configured, origin, warn) {
|
|
155
|
+
if (configured) {
|
|
156
|
+
const api = remoteHost(configured);
|
|
157
|
+
if (api && origin && site(api) !== site(origin)) {
|
|
158
|
+
warn(`CI_API_V4_URL is on ${api} but origin is on ${origin}; GITLAB_TOKEN goes to ${api}`);
|
|
159
|
+
}
|
|
160
|
+
return configured;
|
|
161
|
+
}
|
|
162
|
+
if (origin && site(origin) !== "gitlab.com") {
|
|
163
|
+
throw new ConfigError(`origin is on ${origin}, not gitlab.com: set CI_API_V4_URL to its API, such as https://${origin}/api/v4, so that GITLAB_TOKEN goes only there`);
|
|
164
|
+
}
|
|
165
|
+
return GITLAB_API;
|
|
166
|
+
}
|
|
167
|
+
// GitLab.com answers on subdomains too, such as altssh.gitlab.com for SSH.
|
|
168
|
+
function site(host) {
|
|
169
|
+
return host === "gitlab.com" || host.endsWith(".gitlab.com") ? "gitlab.com" : host;
|
|
170
|
+
}
|
|
171
|
+
// The host of https://host/…, ssh://user@host:port/… or user@host:path; none
|
|
172
|
+
// for a local path, including C:\ on Windows.
|
|
173
|
+
function remoteHost(url) {
|
|
174
|
+
if (!url)
|
|
175
|
+
return undefined;
|
|
176
|
+
if (/^[\w+.-]+:\/\//.test(url)) {
|
|
177
|
+
try {
|
|
178
|
+
const parsed = new URL(url);
|
|
179
|
+
return parsed.protocol === "file:" ? undefined : parsed.hostname.toLowerCase() || undefined;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return /^(?:[^@\s/]+@)?([^:/\s\\]+):(?![\\/])/.exec(url)?.[1]?.toLowerCase();
|
|
186
|
+
}
|
|
187
|
+
// The project behind the origin remote: any host, since GitLab runs on many.
|
|
188
|
+
function gitlabProject(url) {
|
|
189
|
+
const match = url && /^(?:[\w+.-]+:\/\/[^/]+\/|[^@\s]+@[^:]+:)(.+?)(?:\.git)?\/?$/.exec(url);
|
|
190
|
+
if (!match?.[1]) {
|
|
191
|
+
throw new ConfigError("Cannot tell which GitLab project this is; pass --project <id|path>");
|
|
192
|
+
}
|
|
193
|
+
return match[1];
|
|
194
|
+
}
|
|
77
195
|
async function repositoryName(root, env) {
|
|
78
196
|
if (env.GITHUB_REPOSITORY)
|
|
79
197
|
return env.GITHUB_REPOSITORY;
|
package/dist/review/terminal.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isUnsafeCodePoint } from "@open-cr-agent/core";
|
|
1
|
+
import { isUnsafeCodePoint } from "@open-cr-agent/core/internal";
|
|
2
2
|
// Findings, commit messages and errors can carry text a diff planted; none of
|
|
3
3
|
// it may drive the user's terminal. C0 and C1 controls (ANSI and OSC escapes
|
|
4
4
|
// start with ESC or a C1 code) except tab and newline, carriage returns, and
|
package/dist/run.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type ReviewDeps } from "./review/command.js";
|
|
2
2
|
import type { Output } from "./review/progress.js";
|
|
3
|
-
export declare const VERSION: string;
|
|
4
3
|
export declare function defaultDeps(): ReviewDeps;
|
|
5
4
|
export declare function run(argv: string[], out: Output, err: Output, deps?: ReviewDeps): Promise<number>;
|
|
6
5
|
//# sourceMappingURL=run.d.ts.map
|
package/dist/run.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { writeFile } from "node:fs/promises";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
2
|
import { parseArgs } from "node:util";
|
|
4
|
-
import {
|
|
3
|
+
import { isOcraError } from "@open-cr-agent/core";
|
|
4
|
+
import { errorMessage } from "@open-cr-agent/core/internal";
|
|
5
5
|
import { memoryCommand } from "./memory.js";
|
|
6
|
+
import { metricsCommand } from "./metrics.js";
|
|
6
7
|
import { parseReviewArgs, REVIEW_USAGE, UsageError } from "./review/args.js";
|
|
7
8
|
import { BUILTIN_PLUGINS, EXIT, reviewCommand } from "./review/command.js";
|
|
9
|
+
import { BUILTIN_RUNTIMES } from "./review/runtimes.js";
|
|
8
10
|
import { forTerminal } from "./review/terminal.js";
|
|
9
|
-
|
|
10
|
-
export const VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
11
|
+
import { VERSION } from "./version.js";
|
|
11
12
|
const USAGE = `Usage: ocra <command> [options]
|
|
12
13
|
|
|
13
14
|
Commands:
|
|
14
15
|
review Review code changes (run "ocra review --help" for options)
|
|
15
16
|
memory Remember findings the team accepts (run "ocra memory --help")
|
|
17
|
+
metrics Counts over past reviews: runs, cost, findings, per reviewer (run "ocra metrics --help")
|
|
16
18
|
|
|
17
19
|
Options:
|
|
18
20
|
-h, --help Show help
|
|
@@ -23,6 +25,7 @@ export function defaultDeps() {
|
|
|
23
25
|
cwd: process.cwd(),
|
|
24
26
|
env: process.env,
|
|
25
27
|
builtinPlugins: BUILTIN_PLUGINS,
|
|
28
|
+
runtimes: BUILTIN_RUNTIMES,
|
|
26
29
|
writeFile: (path, content) => writeFile(path, content, "utf8"),
|
|
27
30
|
now: Date.now,
|
|
28
31
|
heartbeatMs: 30_000,
|
|
@@ -47,12 +50,14 @@ export async function run(argv, out, err, deps = defaultDeps()) {
|
|
|
47
50
|
const [command, ...rest] = argv;
|
|
48
51
|
if (command === "review")
|
|
49
52
|
return review(rest, out, err, deps);
|
|
50
|
-
if (command === "memory") {
|
|
53
|
+
if (command === "memory" || command === "metrics") {
|
|
51
54
|
try {
|
|
52
|
-
return
|
|
55
|
+
return command === "memory"
|
|
56
|
+
? await memoryCommand(rest, out, deps.cwd)
|
|
57
|
+
: await metricsCommand(rest, out, deps.cwd);
|
|
53
58
|
}
|
|
54
59
|
catch (error) {
|
|
55
|
-
err.write(
|
|
60
|
+
err.write(failure(error));
|
|
56
61
|
return EXIT.error;
|
|
57
62
|
}
|
|
58
63
|
}
|
|
@@ -93,9 +98,14 @@ async function review(argv, out, err, deps) {
|
|
|
93
98
|
err.write(`${error.message}\n\n${REVIEW_USAGE}`);
|
|
94
99
|
}
|
|
95
100
|
else {
|
|
96
|
-
err.write(
|
|
101
|
+
err.write(failure(error));
|
|
97
102
|
}
|
|
98
103
|
return EXIT.error;
|
|
99
104
|
}
|
|
100
105
|
}
|
|
106
|
+
// The code is what a script may match on; the message is for people.
|
|
107
|
+
function failure(error) {
|
|
108
|
+
const code = isOcraError(error) ? ` [${error.code}]` : "";
|
|
109
|
+
return `ocra${code}: ${forTerminal(errorMessage(error))}\n`;
|
|
110
|
+
}
|
|
101
111
|
//# sourceMappingURL=run.js.map
|
package/dist/version.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-cr-agent/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "ocra: open-source multi-agent code review for local changes and GitHub pull requests",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"code-review",
|
|
@@ -23,9 +23,11 @@
|
|
|
23
23
|
"type": "module",
|
|
24
24
|
"exports": {
|
|
25
25
|
".": {
|
|
26
|
+
"@open-cr-agent/source": "./src/index.ts",
|
|
26
27
|
"types": "./dist/index.d.ts",
|
|
27
28
|
"default": "./dist/index.js"
|
|
28
|
-
}
|
|
29
|
+
},
|
|
30
|
+
"./package.json": "./package.json"
|
|
29
31
|
},
|
|
30
32
|
"bin": {
|
|
31
33
|
"ocra": "./dist/main.js"
|
|
@@ -35,12 +37,16 @@
|
|
|
35
37
|
"!dist/**/*.map"
|
|
36
38
|
],
|
|
37
39
|
"dependencies": {
|
|
38
|
-
"@open-cr-agent/core": "0.
|
|
39
|
-
"@open-cr-agent/runtime-
|
|
40
|
-
"@open-cr-agent/vcs-github": "0.
|
|
41
|
-
"@open-cr-agent/vcs-
|
|
40
|
+
"@open-cr-agent/core": "0.3.0",
|
|
41
|
+
"@open-cr-agent/runtime-direct": "0.3.0",
|
|
42
|
+
"@open-cr-agent/vcs-github": "0.3.0",
|
|
43
|
+
"@open-cr-agent/vcs-gitlab": "0.3.0",
|
|
44
|
+
"@open-cr-agent/vcs-local": "0.3.0",
|
|
42
45
|
"zod": "^4.6.5"
|
|
43
46
|
},
|
|
47
|
+
"optionalDependencies": {
|
|
48
|
+
"@open-cr-agent/runtime-opencode": "0.3.0"
|
|
49
|
+
},
|
|
44
50
|
"publishConfig": {
|
|
45
51
|
"access": "public"
|
|
46
52
|
}
|