@open-cr-agent/core 0.5.0 → 0.6.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/agent/json.d.ts +2 -0
- package/dist/agent/json.js +12 -0
- package/dist/{pipeline → agent}/pool.js +2 -1
- package/dist/{pipeline/agents.d.ts → agent/settings.d.ts} +10 -5
- package/dist/{pipeline/agents.js → agent/settings.js} +15 -6
- package/dist/anchor/match.js +5 -4
- package/dist/anchor/relocate.d.ts +1 -2
- package/dist/anchor/relocate.js +2 -2
- package/dist/at.d.ts +2 -0
- package/dist/at.js +12 -0
- package/dist/bundle/bundle.d.ts +2 -1
- package/dist/bundle/bundle.js +3 -2
- package/dist/bundle/grouping.d.ts +0 -1
- package/dist/bundle/runtime-grouper.d.ts +5 -0
- package/dist/{pipeline/helpers.js → bundle/runtime-grouper.js} +5 -14
- package/dist/contracts.d.ts +6 -14
- package/dist/contracts.js +2 -1
- package/dist/diff/hunks.js +3 -2
- package/dist/diff/parse.js +5 -4
- package/dist/diff/quoted-path.js +16 -15
- package/dist/domain.d.ts +10 -2
- package/dist/domain.js +2 -2
- package/dist/errors.d.ts +3 -1
- package/dist/errors.js +12 -0
- package/dist/hash.d.ts +2 -0
- package/dist/hash.js +7 -0
- package/dist/index.d.ts +26 -12
- package/dist/index.js +16 -5
- package/dist/internal.d.ts +9 -12
- package/dist/internal.js +9 -12
- package/dist/judge/judge.d.ts +1 -2
- package/dist/judge/judge.js +9 -8
- package/dist/judge/verdict.d.ts +0 -1
- package/dist/judge/verdict.js +1 -1
- package/dist/memory/memory.js +2 -2
- package/dist/net/proxied-fetch.js +2 -1
- package/dist/pipeline/assemble-report.d.ts +22 -0
- package/dist/pipeline/assemble-report.js +99 -0
- package/dist/pipeline/check-stage.d.ts +13 -0
- package/dist/pipeline/check-stage.js +70 -0
- package/dist/pipeline/context.d.ts +2 -1
- package/dist/pipeline/coverage.d.ts +1 -1
- package/dist/pipeline/coverage.js +2 -1
- package/dist/pipeline/execute-stage.d.ts +26 -0
- package/dist/pipeline/execute-stage.js +104 -0
- package/dist/pipeline/execute.d.ts +2 -4
- package/dist/pipeline/execute.js +63 -56
- package/dist/pipeline/filter-stage.d.ts +16 -0
- package/dist/pipeline/filter-stage.js +46 -0
- package/dist/pipeline/findings.js +3 -2
- package/dist/pipeline/imports.d.ts +1 -1
- package/dist/pipeline/imports.js +1 -1
- package/dist/pipeline/matrix.d.ts +1 -8
- package/dist/pipeline/options.d.ts +65 -0
- package/dist/pipeline/options.js +9 -0
- package/dist/pipeline/plan-output.d.ts +90 -0
- package/dist/pipeline/plan-output.js +109 -0
- package/dist/pipeline/plan.d.ts +2 -3
- package/dist/pipeline/plan.js +4 -4
- package/dist/pipeline/preview.d.ts +23 -9
- package/dist/pipeline/preview.js +25 -4
- package/dist/pipeline/provenance.d.ts +4 -27
- package/dist/pipeline/provenance.js +2 -2
- package/dist/pipeline/run.d.ts +2 -48
- package/dist/pipeline/run.js +18 -273
- package/dist/pipeline/task.d.ts +1 -3
- package/dist/pipeline/task.js +4 -4
- package/dist/plugin/registry.d.ts +5 -4
- package/dist/plugin/registry.js +3 -3
- package/dist/plugin/types.d.ts +3 -5
- package/dist/{pipeline → report}/output-schema.d.ts +57 -63
- package/dist/{pipeline → report}/output-schema.js +55 -40
- package/dist/report/output.d.ts +8 -0
- package/dist/report/output.js +211 -0
- package/dist/report/provenance.d.ts +26 -0
- package/dist/report/provenance.js +2 -0
- package/dist/report/read.d.ts +3 -0
- package/dist/report/read.js +34 -0
- package/dist/{pipeline → report}/report.d.ts +1 -1
- package/dist/report/serialize.d.ts +3 -0
- package/dist/report/serialize.js +27 -0
- package/dist/rereview/presence.js +1 -1
- package/dist/rereview/quote.js +2 -2
- package/dist/rereview/reconcile.d.ts +1 -1
- package/dist/review/impact.d.ts +0 -2
- package/dist/review/impact.js +2 -2
- package/dist/review/plan-phase.d.ts +1 -2
- package/dist/review/plan-phase.js +2 -2
- package/dist/review/prompt-text.d.ts +1 -1
- package/dist/review/prompt-text.js +1 -1
- package/dist/review/prompt.d.ts +0 -3
- package/dist/review/prompt.js +3 -3
- package/dist/rules/builtin/index.d.ts +0 -4
- package/dist/rules/builtin/index.js +1 -2
- package/dist/rules/repo-rules.d.ts +0 -6
- package/dist/rules/repo-rules.js +3 -3
- package/dist/runtime/chain-runner.d.ts +19 -0
- package/dist/runtime/chain-runner.js +63 -0
- package/dist/runtime/effort-capability.d.ts +14 -0
- package/dist/runtime/effort-capability.js +70 -0
- package/dist/runtime/failback.d.ts +0 -9
- package/dist/runtime/failback.js +14 -4
- package/dist/runtime/models.d.ts +1 -2
- package/dist/runtime/tools.d.ts +0 -16
- package/dist/runtime/tools.js +3 -3
- package/dist/sarif/candidates.d.ts +2 -1
- package/dist/sarif/schema.d.ts +2 -2
- package/dist/sarif/schema.js +4 -4
- package/dist/session/jsonl.d.ts +2 -2
- package/dist/session/jsonl.js +5 -4
- package/dist/triage.d.ts +2 -2
- package/dist/triage.js +1 -1
- package/dist/vcs.d.ts +15 -0
- package/dist/vcs.js +2 -0
- package/dist/verify/prompt.d.ts +0 -6
- package/dist/verify/prompt.js +1 -1
- package/dist/verify/verify.d.ts +2 -3
- package/dist/verify/verify.js +4 -4
- package/package.json +1 -1
- package/dist/pipeline/helpers.d.ts +0 -7
- package/dist/pipeline/output.d.ts +0 -79
- package/dist/pipeline/output.js +0 -132
- /package/dist/{pipeline → agent}/budget.d.ts +0 -0
- /package/dist/{pipeline → agent}/budget.js +0 -0
- /package/dist/{pipeline → agent}/pool.d.ts +0 -0
- /package/dist/{pipeline → agent}/usage.d.ts +0 -0
- /package/dist/{pipeline → agent}/usage.js +0 -0
- /package/dist/{pipeline → report}/report.js +0 -0
package/dist/pipeline/execute.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
+
import { reviewerCall } from "../agent/settings.js";
|
|
2
|
+
import { addUsage } from "../agent/usage.js";
|
|
1
3
|
import { anchorFinding } from "../anchor/anchor.js";
|
|
2
4
|
import { memoryFor } from "../memory/memory.js";
|
|
3
5
|
import { findCallers } from "../review/impact.js";
|
|
4
6
|
import { planBundle } from "../review/plan-phase.js";
|
|
5
7
|
import { buildReviewPrompt } from "../review/prompt.js";
|
|
6
8
|
import { resolveRules } from "../rules/resolve.js";
|
|
7
|
-
import { reviewerCall } from "./agents.js";
|
|
8
9
|
import { toFinding } from "./findings.js";
|
|
9
10
|
import { executeTask } from "./task.js";
|
|
10
|
-
import { addUsage } from "./usage.js";
|
|
11
11
|
// In default mode only bundles large enough that a reviewer's 30 steps may
|
|
12
12
|
// not cover them get a plan phase; --ultra plans every task.
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const PLAN_MIN_FILES = 5;
|
|
14
|
+
const PLAN_MIN_PATCH_CHARS = 40_000;
|
|
15
15
|
export function isLargeBundle(files) {
|
|
16
16
|
const chars = files.reduce((sum, f) => sum + f.patch.length, 0);
|
|
17
17
|
return files.length >= PLAN_MIN_FILES || chars >= PLAN_MIN_PATCH_CHARS;
|
|
@@ -20,38 +20,8 @@ export function isLargeBundle(files) {
|
|
|
20
20
|
export async function runJob(job, plan, options) {
|
|
21
21
|
const { emit } = options;
|
|
22
22
|
const files = job.bundle.files.map((f) => f.newPath);
|
|
23
|
-
const input = {
|
|
24
|
-
reviewer: job.reviewer,
|
|
25
|
-
changeRequest: plan.changeRequest,
|
|
26
|
-
changedFiles: plan.selected,
|
|
27
|
-
bundle: job.bundle.files,
|
|
28
|
-
rules: resolveRules(files, plan.repoRules, job.reviewer.rules),
|
|
29
|
-
guidelines: plan.guidelines,
|
|
30
|
-
accepted: memoryFor(files, plan.memory),
|
|
31
|
-
};
|
|
32
23
|
const call = reviewerCall(job.reviewer, options.agents ?? {});
|
|
33
|
-
const
|
|
34
|
-
const extraWarnings = [];
|
|
35
|
-
let prompt = buildReviewPrompt(input);
|
|
36
|
-
if (options.ultra || isLargeBundle(job.bundle.files)) {
|
|
37
|
-
const callers = options.ultra ? await findCallers(job.bundle.files, plan.context) : [];
|
|
38
|
-
const key = `${job.reviewer.id}\0${job.bundle.label}`;
|
|
39
|
-
const shared = options.plans?.get(key);
|
|
40
|
-
const planning = shared ??
|
|
41
|
-
planBundle(options.runtime, job.reviewer, buildReviewPrompt({ ...input, forPlanning: true }), options.signal, call);
|
|
42
|
-
if (!shared)
|
|
43
|
-
options.plans?.set(key, planning);
|
|
44
|
-
const planned = await planning;
|
|
45
|
-
// The sample that made the call pays for it and reports its warning.
|
|
46
|
-
if (!shared) {
|
|
47
|
-
extraUsage.push(...planned.usage);
|
|
48
|
-
for (const usage of planned.usage)
|
|
49
|
-
options.onUsage?.(usage);
|
|
50
|
-
if (planned.warning)
|
|
51
|
-
extraWarnings.push(planned.warning);
|
|
52
|
-
}
|
|
53
|
-
prompt = buildReviewPrompt({ ...input, callers, plan: planned.plan });
|
|
54
|
-
}
|
|
24
|
+
const prepared = await preparePrompt(job, plan, options, call);
|
|
55
25
|
emit({
|
|
56
26
|
type: "task_started",
|
|
57
27
|
taskId: job.taskId,
|
|
@@ -65,8 +35,8 @@ export async function runJob(job, plan, options) {
|
|
|
65
35
|
reviewer: job.reviewer.id,
|
|
66
36
|
modelTier: job.reviewer.modelTier,
|
|
67
37
|
...call,
|
|
68
|
-
systemPrompt: prompt.system,
|
|
69
|
-
userPrompt: prompt.user,
|
|
38
|
+
systemPrompt: prepared.prompt.system,
|
|
39
|
+
userPrompt: prepared.prompt.user,
|
|
70
40
|
context: plan.context,
|
|
71
41
|
timeoutMs: options.taskTimeoutMs,
|
|
72
42
|
}, options.signal, {
|
|
@@ -75,7 +45,58 @@ export async function runJob(job, plan, options) {
|
|
|
75
45
|
category: job.reviewer.category,
|
|
76
46
|
abortGraceMs: options.abortGraceMs,
|
|
77
47
|
});
|
|
78
|
-
const
|
|
48
|
+
const anchored = await anchorFindings(job, plan, options, result.findings);
|
|
49
|
+
const warnings = [...prepared.warnings, ...result.warnings, ...anchored.warnings];
|
|
50
|
+
const usage = prepared.usage.reduce(addUsage, result.usage);
|
|
51
|
+
const outcome = {
|
|
52
|
+
taskId: job.taskId,
|
|
53
|
+
reviewer: job.reviewer.id,
|
|
54
|
+
bundle: job.bundle.label,
|
|
55
|
+
files,
|
|
56
|
+
status: result.status,
|
|
57
|
+
findings: anchored.findings.length,
|
|
58
|
+
durationMs: Date.now() - started,
|
|
59
|
+
usage,
|
|
60
|
+
};
|
|
61
|
+
if (result.error !== undefined)
|
|
62
|
+
outcome.error = result.error;
|
|
63
|
+
emit({ type: "task_finished", outcome });
|
|
64
|
+
return { outcome, findings: anchored.findings, usage, warnings };
|
|
65
|
+
}
|
|
66
|
+
// The review prompt, with a plan phase and, under --ultra, the callers of
|
|
67
|
+
// changed symbols.
|
|
68
|
+
async function preparePrompt(job, plan, options, call) {
|
|
69
|
+
const files = job.bundle.files.map((f) => f.newPath);
|
|
70
|
+
const input = {
|
|
71
|
+
reviewer: job.reviewer,
|
|
72
|
+
changeRequest: plan.changeRequest,
|
|
73
|
+
changedFiles: plan.selected,
|
|
74
|
+
bundle: job.bundle.files,
|
|
75
|
+
rules: resolveRules(files, plan.repoRules, job.reviewer.rules),
|
|
76
|
+
guidelines: plan.guidelines,
|
|
77
|
+
accepted: memoryFor(files, plan.memory),
|
|
78
|
+
};
|
|
79
|
+
if (!options.ultra && !isLargeBundle(job.bundle.files)) {
|
|
80
|
+
return { prompt: buildReviewPrompt(input), usage: [], warnings: [] };
|
|
81
|
+
}
|
|
82
|
+
const callers = options.ultra ? await findCallers(job.bundle.files, plan.context) : [];
|
|
83
|
+
const key = `${job.reviewer.id}\0${job.bundle.label}`;
|
|
84
|
+
const shared = options.plans?.get(key);
|
|
85
|
+
const planning = shared ??
|
|
86
|
+
planBundle(options.runtime, job.reviewer, buildReviewPrompt({ ...input, forPlanning: true }), options.signal, call);
|
|
87
|
+
if (!shared)
|
|
88
|
+
options.plans?.set(key, planning);
|
|
89
|
+
const planned = await planning;
|
|
90
|
+
const prompt = buildReviewPrompt({ ...input, callers, plan: planned.plan });
|
|
91
|
+
// The sample that made the call pays for it and reports its warning.
|
|
92
|
+
if (shared)
|
|
93
|
+
return { prompt, usage: [], warnings: [] };
|
|
94
|
+
for (const usage of planned.usage)
|
|
95
|
+
options.onUsage?.(usage);
|
|
96
|
+
return { prompt, usage: [...planned.usage], warnings: planned.warning ? [planned.warning] : [] };
|
|
97
|
+
}
|
|
98
|
+
async function anchorFindings(job, plan, options, reportedFindings) {
|
|
99
|
+
const warnings = [];
|
|
79
100
|
const findings = [];
|
|
80
101
|
const anchorContext = {
|
|
81
102
|
diffs: plan.selected,
|
|
@@ -86,9 +107,9 @@ export async function runJob(job, plan, options) {
|
|
|
86
107
|
// A finding on a file outside the bundle belongs to the task that reviews
|
|
87
108
|
// that file; keeping it would report the same issue once per bundle that
|
|
88
109
|
// happened to read the file.
|
|
89
|
-
const bundleFiles = new Set(files);
|
|
110
|
+
const bundleFiles = new Set(job.bundle.files.map((f) => f.newPath));
|
|
90
111
|
let outside = 0;
|
|
91
|
-
for (const { reported, model } of
|
|
112
|
+
for (const { reported, model } of reportedFindings) {
|
|
92
113
|
// Relocating a quote on a file outside the bundle would pay for an
|
|
93
114
|
// answer the next check throws away.
|
|
94
115
|
const context = bundleFiles.has(reported.file)
|
|
@@ -107,25 +128,11 @@ export async function runJob(job, plan, options) {
|
|
|
107
128
|
const provenance = model === undefined ? { task: job.taskId } : { task: job.taskId, model };
|
|
108
129
|
const finding = toFinding(reported, job.reviewer.id, provenance, anchor, content);
|
|
109
130
|
findings.push(finding);
|
|
110
|
-
emit({ type: "finding", taskId: job.taskId, finding });
|
|
131
|
+
options.emit({ type: "finding", taskId: job.taskId, finding });
|
|
111
132
|
}
|
|
112
133
|
if (outside > 0) {
|
|
113
134
|
warnings.push(`${job.taskId}: dropped ${outside} finding(s) on files outside its bundle`);
|
|
114
135
|
}
|
|
115
|
-
|
|
116
|
-
const outcome = {
|
|
117
|
-
taskId: job.taskId,
|
|
118
|
-
reviewer: job.reviewer.id,
|
|
119
|
-
bundle: job.bundle.label,
|
|
120
|
-
files,
|
|
121
|
-
status: result.status,
|
|
122
|
-
findings: findings.length,
|
|
123
|
-
durationMs: Date.now() - started,
|
|
124
|
-
usage,
|
|
125
|
-
};
|
|
126
|
-
if (result.error !== undefined)
|
|
127
|
-
outcome.error = result.error;
|
|
128
|
-
emit({ type: "task_finished", outcome });
|
|
129
|
-
return { outcome, findings, usage, warnings };
|
|
136
|
+
return { findings, warnings };
|
|
130
137
|
}
|
|
131
138
|
//# sourceMappingURL=execute.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PriorReview } from "../domain.js";
|
|
2
|
+
import { type RememberedEntry } from "../memory/memory.js";
|
|
3
|
+
import { type CoverageEntry } from "../report/report.js";
|
|
4
|
+
import { type Reconciled } from "../rereview/reconcile.js";
|
|
5
|
+
import type { ExecuteStage, StageContext } from "./execute-stage.js";
|
|
6
|
+
import { type SarifImport } from "./imports.js";
|
|
7
|
+
export interface FilterStage {
|
|
8
|
+
imported: SarifImport;
|
|
9
|
+
coverage: CoverageEntry[];
|
|
10
|
+
remembered: RememberedEntry[];
|
|
11
|
+
reconciled: Reconciled;
|
|
12
|
+
nothingReviewed: boolean;
|
|
13
|
+
warnings: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function filterStage(executed: ExecuteStage, prior: PriorReview | undefined, { options, plan, emit }: StageContext): Promise<FilterStage>;
|
|
16
|
+
//# sourceMappingURL=filter-stage.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { applyMemory } from "../memory/memory.js";
|
|
2
|
+
import { coverageGaps } from "../report/report.js";
|
|
3
|
+
import { priorCodePresence } from "../rereview/presence.js";
|
|
4
|
+
import { reconcile } from "../rereview/reconcile.js";
|
|
5
|
+
import { coverageOf } from "./coverage.js";
|
|
6
|
+
import { dedupeFindings } from "./findings.js";
|
|
7
|
+
import { importSarif } from "./imports.js";
|
|
8
|
+
// Memory and the previous review filter first, so Verify and Judge are not
|
|
9
|
+
// paid for findings that will not be reported, and a person's dismissal
|
|
10
|
+
// keeps a finding out of the verdict whatever the models say.
|
|
11
|
+
export async function filterStage(executed, prior, { options, plan, emit }) {
|
|
12
|
+
const imported = options.sarif && options.sarif.length > 0
|
|
13
|
+
? await importSarif(options.sarif, plan, emit)
|
|
14
|
+
: { findings: [], outcomes: [], warnings: [] };
|
|
15
|
+
const { results } = executed;
|
|
16
|
+
const found = dedupeFindings([...results.flatMap((r) => r.findings), ...imported.findings]);
|
|
17
|
+
const coverage = coverageOf(plan.decisions, results, plan.unchanged, executed.matrix.limited ?? [], executed.notStarted);
|
|
18
|
+
const remembered = applyMemory(found, plan.memory);
|
|
19
|
+
const reported = new Set(found.map((f) => f.fingerprint));
|
|
20
|
+
const priorReview = withoutRemembered(prior, plan.memory);
|
|
21
|
+
const reconciled = reconcile({
|
|
22
|
+
findings: remembered.kept,
|
|
23
|
+
reported,
|
|
24
|
+
prior: priorReview,
|
|
25
|
+
coverage,
|
|
26
|
+
stillPresent: await priorCodePresence(priorReview, reported, plan.context.readFile),
|
|
27
|
+
});
|
|
28
|
+
const { nothingReviewed } = coverageGaps({ coverage, tasks: results.map((r) => r.outcome) });
|
|
29
|
+
return {
|
|
30
|
+
imported,
|
|
31
|
+
coverage,
|
|
32
|
+
remembered: remembered.remembered,
|
|
33
|
+
reconciled,
|
|
34
|
+
nothingReviewed,
|
|
35
|
+
warnings: imported.warnings,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// An earlier finding the team has since accepted is no longer open.
|
|
39
|
+
function withoutRemembered(review, memory) {
|
|
40
|
+
if (!review)
|
|
41
|
+
return undefined;
|
|
42
|
+
const accepted = new Set(memory.map((e) => e.fingerprint));
|
|
43
|
+
// Everything else the earlier review carries (replies, tier, head) stays.
|
|
44
|
+
return { ...review, findings: review.findings.filter((f) => !accepted.has(f.fingerprint)) };
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=filter-stage.js.map
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { normalizeSnippet } from "../anchor/match.js";
|
|
3
|
+
import { shortHash } from "../hash.js";
|
|
3
4
|
import { quoteSignature } from "../rereview/quote.js";
|
|
4
5
|
const SEVERITY_RANK = { suggestion: 0, warning: 1, critical: 2 };
|
|
5
6
|
export function fingerprint(category, file, existingCode) {
|
|
6
7
|
const key = [category, file, ...normalizeSnippet(existingCode)].join("\n");
|
|
7
|
-
return
|
|
8
|
+
return shortHash(key);
|
|
8
9
|
}
|
|
9
10
|
// content is the anchored file at head, when the anchor found lines in it.
|
|
10
11
|
export function toFinding(reported, reviewer, provenance, anchor, content) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Finding } from "../domain.js";
|
|
2
|
+
import type { ReviewEvent, TaskOutcome } from "../report/report.js";
|
|
2
3
|
import type { SarifLog } from "../sarif/schema.js";
|
|
3
4
|
import type { ReviewPlan } from "./plan.js";
|
|
4
|
-
import type { ReviewEvent, TaskOutcome } from "./report.js";
|
|
5
5
|
export interface SarifImport {
|
|
6
6
|
findings: Finding[];
|
|
7
7
|
outcomes: TaskOutcome[];
|
package/dist/pipeline/imports.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { emptyUsage } from "../agent/usage.js";
|
|
1
2
|
import { anchorFinding } from "../anchor/anchor.js";
|
|
2
3
|
import { sarifCandidates } from "../sarif/candidates.js";
|
|
3
4
|
import { toFinding } from "./findings.js";
|
|
4
5
|
import { boundFinding } from "./task.js";
|
|
5
|
-
import { emptyUsage } from "./usage.js";
|
|
6
6
|
// A hostile log could carry thousands of results on the change.
|
|
7
7
|
export const MAX_IMPORTED_PER_RUN = 200;
|
|
8
8
|
export async function importSarif(logs, plan, emit) {
|
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import type { ReviewerOverrides } from "../agent/settings.js";
|
|
2
3
|
import type { Bundle } from "../bundle/bundle.js";
|
|
3
|
-
import type { Effort } from "../contracts.js";
|
|
4
4
|
import type { RiskTier } from "../domain.js";
|
|
5
5
|
import type { ReviewerDefinition } from "../review/reviewer.js";
|
|
6
6
|
export declare const RISK_TIERS: readonly RiskTier[];
|
|
7
|
-
export interface ReviewerOverride {
|
|
8
|
-
enabled?: boolean | undefined;
|
|
9
|
-
minTier?: RiskTier | undefined;
|
|
10
|
-
effort?: Effort | undefined;
|
|
11
|
-
models?: readonly string[] | undefined;
|
|
12
|
-
}
|
|
13
|
-
export type ReviewerOverrides = Readonly<Record<string, ReviewerOverride>>;
|
|
14
7
|
export interface MatrixCell {
|
|
15
8
|
taskId: string;
|
|
16
9
|
reviewer: ReviewerDefinition;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { ReviewerOverrides, RoleSettings, TierEfforts } from "../agent/settings.js";
|
|
2
|
+
import type { AnchorContext } from "../anchor/anchor.js";
|
|
3
|
+
import type { BundlePolicy } from "../bundle/bundle.js";
|
|
4
|
+
import type { FileGrouper } from "../bundle/grouping.js";
|
|
5
|
+
import type { AgentRuntime, ModelChains } from "../contracts.js";
|
|
6
|
+
import type { MemoryEntry } from "../memory/memory.js";
|
|
7
|
+
import type { ReviewEvent } from "../report/report.js";
|
|
8
|
+
import type { ReviewerDefinition } from "../review/reviewer.js";
|
|
9
|
+
import type { SourcedRule } from "../rules/repo-rules.js";
|
|
10
|
+
import type { SarifLog } from "../sarif/schema.js";
|
|
11
|
+
import type { SelectionPolicy } from "../select/select.js";
|
|
12
|
+
import type { VcsAdapter } from "../vcs.js";
|
|
13
|
+
import type { ProvenanceInput } from "./provenance.js";
|
|
14
|
+
export interface ReviewOptions {
|
|
15
|
+
vcs: VcsAdapter;
|
|
16
|
+
runtime: AgentRuntime;
|
|
17
|
+
reviewers?: readonly ReviewerDefinition[];
|
|
18
|
+
reviewerOverrides?: ReviewerOverrides;
|
|
19
|
+
effort?: TierEfforts;
|
|
20
|
+
roles?: RoleSettings;
|
|
21
|
+
models?: ModelChains;
|
|
22
|
+
rules?: readonly SourcedRule[];
|
|
23
|
+
readTrusted?: (path: string) => Promise<string | undefined>;
|
|
24
|
+
accountMemory?: readonly MemoryEntry[];
|
|
25
|
+
selection?: Partial<SelectionPolicy>;
|
|
26
|
+
limits?: ReviewLimits;
|
|
27
|
+
stages?: ReviewStages;
|
|
28
|
+
mode?: ReviewMode;
|
|
29
|
+
identity?: RunIdentity;
|
|
30
|
+
sarif?: readonly SarifLog[];
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
onEvent?: (event: ReviewEvent) => void;
|
|
33
|
+
}
|
|
34
|
+
export interface ReviewLimits {
|
|
35
|
+
concurrency?: number;
|
|
36
|
+
taskTimeoutMs?: number;
|
|
37
|
+
runTimeoutMs?: number;
|
|
38
|
+
maxCostUsd?: number;
|
|
39
|
+
maxTasks?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface ReviewStages {
|
|
42
|
+
verify?: boolean;
|
|
43
|
+
judge?: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface ReviewMode {
|
|
46
|
+
full?: boolean;
|
|
47
|
+
ultra?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export interface RunIdentity {
|
|
50
|
+
runId?: string;
|
|
51
|
+
provenance?: ProvenanceInput;
|
|
52
|
+
}
|
|
53
|
+
export interface ReviewHooks {
|
|
54
|
+
bundling?: BundlePolicy;
|
|
55
|
+
grouper?: FileGrouper;
|
|
56
|
+
relocate?: AnchorContext["relocate"] | false;
|
|
57
|
+
abortGraceMs?: number;
|
|
58
|
+
}
|
|
59
|
+
export declare const REVIEW_DEFAULTS: {
|
|
60
|
+
readonly concurrency: 4;
|
|
61
|
+
readonly taskTimeoutMs: number;
|
|
62
|
+
readonly runTimeoutMs: number;
|
|
63
|
+
};
|
|
64
|
+
export declare const MAX_TIMER_MS: number;
|
|
65
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const REVIEW_DEFAULTS = {
|
|
2
|
+
concurrency: 4,
|
|
3
|
+
taskTimeoutMs: 10 * 60_000,
|
|
4
|
+
runTimeoutMs: 25 * 60_000,
|
|
5
|
+
};
|
|
6
|
+
// Timers fire at once past 2^31 - 1 ms (about 24.8 days); a longer timeout
|
|
7
|
+
// means no practical limit, so it is cut to the longest one a timer keeps.
|
|
8
|
+
export const MAX_TIMER_MS = 2 ** 31 - 1;
|
|
9
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ReviewPreview } from "./preview.js";
|
|
3
|
+
export declare const PLAN_VERSION = 1;
|
|
4
|
+
export declare const planOutputSchema: z.ZodObject<{
|
|
5
|
+
version: z.ZodLiteral<1>;
|
|
6
|
+
changeRequest: z.ZodObject<{
|
|
7
|
+
id: z.ZodString;
|
|
8
|
+
title: z.ZodString;
|
|
9
|
+
description: z.ZodString;
|
|
10
|
+
baseSha: z.ZodString;
|
|
11
|
+
headSha: z.ZodString;
|
|
12
|
+
override: z.ZodExactOptional<z.ZodObject<{
|
|
13
|
+
by: z.ZodString;
|
|
14
|
+
reason: z.ZodString;
|
|
15
|
+
}, z.core.$strict>>;
|
|
16
|
+
}, z.core.$strict>;
|
|
17
|
+
tier: z.ZodEnum<{
|
|
18
|
+
full: "full";
|
|
19
|
+
lite: "lite";
|
|
20
|
+
trivial: "trivial";
|
|
21
|
+
}>;
|
|
22
|
+
selected: z.ZodArray<z.ZodString>;
|
|
23
|
+
excluded: z.ZodArray<z.ZodObject<{
|
|
24
|
+
path: z.ZodString;
|
|
25
|
+
reason: z.ZodEnum<{
|
|
26
|
+
binary: "binary";
|
|
27
|
+
deleted: "deleted";
|
|
28
|
+
extension: "extension";
|
|
29
|
+
generated: "generated";
|
|
30
|
+
secret: "secret";
|
|
31
|
+
too_large: "too_large";
|
|
32
|
+
user_exclude: "user_exclude";
|
|
33
|
+
}>;
|
|
34
|
+
}, z.core.$strict>>;
|
|
35
|
+
bundles: z.ZodArray<z.ZodObject<{
|
|
36
|
+
label: z.ZodString;
|
|
37
|
+
files: z.ZodArray<z.ZodString>;
|
|
38
|
+
}, z.core.$strict>>;
|
|
39
|
+
groupingSkipped: z.ZodBoolean;
|
|
40
|
+
tasks: z.ZodArray<z.ZodObject<{
|
|
41
|
+
taskId: z.ZodString;
|
|
42
|
+
reviewer: z.ZodString;
|
|
43
|
+
bundle: z.ZodString;
|
|
44
|
+
files: z.ZodArray<z.ZodString>;
|
|
45
|
+
promptTokens: z.ZodInt;
|
|
46
|
+
effort: z.ZodExactOptional<z.ZodEnum<{
|
|
47
|
+
high: "high";
|
|
48
|
+
low: "low";
|
|
49
|
+
medium: "medium";
|
|
50
|
+
minimal: "minimal";
|
|
51
|
+
none: "none";
|
|
52
|
+
}>>;
|
|
53
|
+
models: z.ZodExactOptional<z.ZodArray<z.ZodString>>;
|
|
54
|
+
planPromptTokens: z.ZodExactOptional<z.ZodInt>;
|
|
55
|
+
inputCost: z.ZodExactOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
56
|
+
model: z.ZodString;
|
|
57
|
+
status: z.ZodLiteral<"priced">;
|
|
58
|
+
usd: z.ZodNumber;
|
|
59
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
60
|
+
model: z.ZodString;
|
|
61
|
+
status: z.ZodLiteral<"unpriced">;
|
|
62
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
63
|
+
model: z.ZodString;
|
|
64
|
+
status: z.ZodLiteral<"unknown">;
|
|
65
|
+
}, z.core.$strict>], "status">>;
|
|
66
|
+
}, z.core.$strict>>;
|
|
67
|
+
skipped: z.ZodArray<z.ZodObject<{
|
|
68
|
+
reviewer: z.ZodString;
|
|
69
|
+
bundle: z.ZodString;
|
|
70
|
+
reason: z.ZodEnum<{
|
|
71
|
+
below_tier: "below_tier";
|
|
72
|
+
disabled: "disabled";
|
|
73
|
+
no_guidelines: "no_guidelines";
|
|
74
|
+
no_matching_files: "no_matching_files";
|
|
75
|
+
task_limit: "task_limit";
|
|
76
|
+
}>;
|
|
77
|
+
}, z.core.$strict>>;
|
|
78
|
+
promptTokens: z.ZodInt;
|
|
79
|
+
planCalls: z.ZodInt;
|
|
80
|
+
inputCost: z.ZodExactOptional<z.ZodObject<{
|
|
81
|
+
usd: z.ZodNumber;
|
|
82
|
+
priced: z.ZodInt;
|
|
83
|
+
unpriced: z.ZodInt;
|
|
84
|
+
unknown: z.ZodInt;
|
|
85
|
+
}, z.core.$strict>>;
|
|
86
|
+
warnings: z.ZodArray<z.ZodString>;
|
|
87
|
+
}, z.core.$strict>;
|
|
88
|
+
export type PlanOutput = z.output<typeof planOutputSchema>;
|
|
89
|
+
export declare function toPlanOutput(preview: ReviewPreview): PlanOutput;
|
|
90
|
+
//# sourceMappingURL=plan-output.d.ts.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { EFFORT_LEVELS } from "../agent/settings.js";
|
|
3
|
+
import { riskTierSchema } from "../domain.js";
|
|
4
|
+
import { changeRequestSchema, skippedCellSchema } from "../report/output-schema.js";
|
|
5
|
+
import { exclusionReasonSchema } from "../select/select.js";
|
|
6
|
+
// The published shape of `ocra review --plan --format json` without what
|
|
7
|
+
// the CLI adds (where each setting came from), versioned like the report: a
|
|
8
|
+
// version only gains optional fields. The type is derived from the schema,
|
|
9
|
+
// and the CLI publishes the JSON Schema of the whole output.
|
|
10
|
+
export const PLAN_VERSION = 1;
|
|
11
|
+
const inputCostSchema = z.discriminatedUnion("status", [
|
|
12
|
+
z.strictObject({ model: z.string(), status: z.literal("priced"), usd: z.number() }),
|
|
13
|
+
z.strictObject({ model: z.string(), status: z.literal("unpriced") }),
|
|
14
|
+
z.strictObject({ model: z.string(), status: z.literal("unknown") }),
|
|
15
|
+
]);
|
|
16
|
+
const planTaskSchema = z.strictObject({
|
|
17
|
+
taskId: z.string(),
|
|
18
|
+
reviewer: z.string(),
|
|
19
|
+
bundle: z.string(),
|
|
20
|
+
files: z.array(z.string()),
|
|
21
|
+
promptTokens: z.int().nonnegative(),
|
|
22
|
+
effort: z.enum(EFFORT_LEVELS).exactOptional(),
|
|
23
|
+
models: z.array(z.string()).exactOptional(),
|
|
24
|
+
planPromptTokens: z.int().nonnegative().exactOptional(),
|
|
25
|
+
inputCost: inputCostSchema.exactOptional(),
|
|
26
|
+
});
|
|
27
|
+
export const planOutputSchema = z.strictObject({
|
|
28
|
+
version: z.literal(PLAN_VERSION),
|
|
29
|
+
changeRequest: changeRequestSchema,
|
|
30
|
+
tier: riskTierSchema,
|
|
31
|
+
selected: z.array(z.string()),
|
|
32
|
+
excluded: z.array(z.strictObject({ path: z.string(), reason: exclusionReasonSchema })),
|
|
33
|
+
bundles: z.array(z.strictObject({ label: z.string(), files: z.array(z.string()) })),
|
|
34
|
+
groupingSkipped: z.boolean(),
|
|
35
|
+
tasks: z.array(planTaskSchema),
|
|
36
|
+
skipped: z.array(skippedCellSchema),
|
|
37
|
+
promptTokens: z.int().nonnegative(),
|
|
38
|
+
// Added in version 1 without a bump: a new field older readers ignore.
|
|
39
|
+
planCalls: z.int().nonnegative(),
|
|
40
|
+
// Added in version 1 without a bump, like planCalls.
|
|
41
|
+
inputCost: z
|
|
42
|
+
.strictObject({
|
|
43
|
+
usd: z.number(),
|
|
44
|
+
priced: z.int().nonnegative(),
|
|
45
|
+
unpriced: z.int().nonnegative(),
|
|
46
|
+
unknown: z.int().nonnegative(),
|
|
47
|
+
})
|
|
48
|
+
.exactOptional(),
|
|
49
|
+
warnings: z.array(z.string()),
|
|
50
|
+
});
|
|
51
|
+
export function toPlanOutput(preview) {
|
|
52
|
+
const { changeRequest: c, inputCost } = preview;
|
|
53
|
+
return {
|
|
54
|
+
version: PLAN_VERSION,
|
|
55
|
+
changeRequest: {
|
|
56
|
+
id: c.id,
|
|
57
|
+
title: c.title,
|
|
58
|
+
description: c.description,
|
|
59
|
+
baseSha: c.baseSha,
|
|
60
|
+
headSha: c.headSha,
|
|
61
|
+
...(c.override ? { override: { by: c.override.by, reason: c.override.reason } } : {}),
|
|
62
|
+
},
|
|
63
|
+
tier: preview.tier,
|
|
64
|
+
selected: [...preview.selected],
|
|
65
|
+
excluded: preview.excluded.map((e) => ({ path: e.path, reason: e.reason })),
|
|
66
|
+
bundles: preview.bundles.map((b) => ({ label: b.label, files: [...b.files] })),
|
|
67
|
+
groupingSkipped: preview.groupingSkipped,
|
|
68
|
+
tasks: preview.tasks.map(planTask),
|
|
69
|
+
skipped: preview.skipped.map((s) => ({
|
|
70
|
+
reviewer: s.reviewer,
|
|
71
|
+
bundle: s.bundle,
|
|
72
|
+
reason: s.reason,
|
|
73
|
+
})),
|
|
74
|
+
promptTokens: preview.promptTokens,
|
|
75
|
+
planCalls: preview.planCalls,
|
|
76
|
+
...(inputCost
|
|
77
|
+
? {
|
|
78
|
+
inputCost: {
|
|
79
|
+
usd: inputCost.usd,
|
|
80
|
+
priced: inputCost.priced,
|
|
81
|
+
unpriced: inputCost.unpriced,
|
|
82
|
+
unknown: inputCost.unknown,
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
: {}),
|
|
86
|
+
warnings: [...preview.warnings],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function planTask(t) {
|
|
90
|
+
const cost = t.inputCost;
|
|
91
|
+
return {
|
|
92
|
+
taskId: t.taskId,
|
|
93
|
+
reviewer: t.reviewer,
|
|
94
|
+
bundle: t.bundle,
|
|
95
|
+
files: [...t.files],
|
|
96
|
+
promptTokens: t.promptTokens,
|
|
97
|
+
...(t.effort === undefined ? {} : { effort: t.effort }),
|
|
98
|
+
...(t.models ? { models: [...t.models] } : {}),
|
|
99
|
+
...(t.planPromptTokens === undefined ? {} : { planPromptTokens: t.planPromptTokens }),
|
|
100
|
+
...(cost
|
|
101
|
+
? {
|
|
102
|
+
inputCost: cost.status === "priced"
|
|
103
|
+
? { model: cost.model, status: cost.status, usd: cost.usd }
|
|
104
|
+
: { model: cost.model, status: cost.status },
|
|
105
|
+
}
|
|
106
|
+
: {}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=plan-output.js.map
|
package/dist/pipeline/plan.d.ts
CHANGED
|
@@ -2,11 +2,10 @@ import { type Bundle } from "../bundle/bundle.js";
|
|
|
2
2
|
import type { AgentRuntime, ReviewContext, Usage } from "../contracts.js";
|
|
3
3
|
import type { ChangeRequest, FileDiff, RiskTier } from "../domain.js";
|
|
4
4
|
import { type RememberedEntry } from "../memory/memory.js";
|
|
5
|
+
import type { ReviewEvent } from "../report/report.js";
|
|
5
6
|
import { type SourcedRule } from "../rules/repo-rules.js";
|
|
6
7
|
import { type FileDecision } from "../select/select.js";
|
|
7
|
-
import type {
|
|
8
|
-
import type { ReviewHooks, ReviewOptions } from "./run.js";
|
|
9
|
-
export declare const GUIDELINES_PATH = "AGENTS.md";
|
|
8
|
+
import type { ReviewHooks, ReviewOptions } from "./options.js";
|
|
10
9
|
export interface ReviewPlan {
|
|
11
10
|
changeRequest: ChangeRequest;
|
|
12
11
|
decisions: FileDecision[];
|
package/dist/pipeline/plan.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
+
import { roleCall } from "../agent/settings.js";
|
|
1
2
|
import { bundleFiles, defaultBundlePolicy } from "../bundle/bundle.js";
|
|
3
|
+
import { runtimeGrouper } from "../bundle/runtime-grouper.js";
|
|
2
4
|
import { MEMORY_PATH, mergeMemory, parseMemory } from "../memory/memory.js";
|
|
3
5
|
import { parseRepoRules, REPO_RULES_PATH, } from "../rules/repo-rules.js";
|
|
4
6
|
import { defaultSelectionPolicy, selectFiles } from "../select/select.js";
|
|
5
7
|
import { triage } from "../triage.js";
|
|
6
|
-
import { roleCall } from "./agents.js";
|
|
7
8
|
import { reviewContext } from "./context.js";
|
|
8
|
-
import { runtimeGrouper } from "./helpers.js";
|
|
9
9
|
import { rank } from "./matrix.js";
|
|
10
10
|
import { newRunId } from "./run-id.js";
|
|
11
|
-
|
|
11
|
+
const GUIDELINES_PATH = "AGENTS.md";
|
|
12
12
|
export async function planReview(options, emit, signal) {
|
|
13
13
|
const { vcs } = options;
|
|
14
14
|
const readTrusted = options.readTrusted ?? ((path) => vcs.readFile(path));
|
|
15
15
|
const changeRequest = await vcs.getChangeRequest();
|
|
16
16
|
emit({ type: "run_started", runId: options.runId ?? newRunId(), changeRequest });
|
|
17
17
|
const diffs = await vcs.getDiff();
|
|
18
|
-
const decisions = selectFiles(diffs, options.selection
|
|
18
|
+
const decisions = selectFiles(diffs, { ...defaultSelectionPolicy, ...options.selection });
|
|
19
19
|
const selected = decisions.filter((d) => d.selected).map((d) => d.diff);
|
|
20
20
|
const tier = triage(selected);
|
|
21
21
|
emit({
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { Effort } from "../contracts.js";
|
|
2
2
|
import type { ChangeRequest, RiskTier } from "../domain.js";
|
|
3
|
-
import type { ModelChains } from "../plugin/types.js";
|
|
4
|
-
import type { ReviewerDefinition } from "../review/reviewer.js";
|
|
5
3
|
import type { FileDecision } from "../select/select.js";
|
|
6
|
-
import { type
|
|
4
|
+
import { type SkippedCell } from "./matrix.js";
|
|
5
|
+
import type { ReviewOptions } from "./options.js";
|
|
7
6
|
import { type PlanOptions } from "./plan.js";
|
|
8
7
|
export interface PreviewTask {
|
|
9
8
|
taskId: string;
|
|
@@ -14,7 +13,19 @@ export interface PreviewTask {
|
|
|
14
13
|
planPromptTokens?: number;
|
|
15
14
|
effort?: Effort;
|
|
16
15
|
models?: string[];
|
|
16
|
+
inputCost?: InputCost;
|
|
17
17
|
}
|
|
18
|
+
type InputCost = {
|
|
19
|
+
model: string;
|
|
20
|
+
status: "priced";
|
|
21
|
+
usd: number;
|
|
22
|
+
} | {
|
|
23
|
+
model: string;
|
|
24
|
+
status: "unpriced";
|
|
25
|
+
} | {
|
|
26
|
+
model: string;
|
|
27
|
+
status: "unknown";
|
|
28
|
+
};
|
|
18
29
|
export interface ReviewPreview {
|
|
19
30
|
changeRequest: ChangeRequest;
|
|
20
31
|
tier: RiskTier;
|
|
@@ -34,14 +45,17 @@ export interface ReviewPreview {
|
|
|
34
45
|
skipped: SkippedCell[];
|
|
35
46
|
promptTokens: number;
|
|
36
47
|
planCalls: number;
|
|
48
|
+
inputCost?: {
|
|
49
|
+
usd: number;
|
|
50
|
+
priced: number;
|
|
51
|
+
unpriced: number;
|
|
52
|
+
unknown: number;
|
|
53
|
+
};
|
|
37
54
|
warnings: string[];
|
|
38
55
|
}
|
|
39
|
-
export type PreviewOptions = Omit<PlanOptions, "runtime"> & {
|
|
40
|
-
|
|
41
|
-
reviewerOverrides?: ReviewerOverrides;
|
|
42
|
-
ultra?: boolean;
|
|
43
|
-
maxTasks?: number;
|
|
44
|
-
models?: ModelChains;
|
|
56
|
+
export type PreviewOptions = Omit<PlanOptions, "runtime"> & Omit<ReviewOptions, "vcs" | "runtime"> & {
|
|
57
|
+
inputPrice?: (model: string) => number | undefined;
|
|
45
58
|
};
|
|
46
59
|
export declare function previewReview(options: PreviewOptions): Promise<ReviewPreview>;
|
|
60
|
+
export {};
|
|
47
61
|
//# sourceMappingURL=preview.d.ts.map
|