@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/internal.d.ts
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
|
+
export { AGENT_ROLES } from "./agent/settings.js";
|
|
2
|
+
export { at } from "./at.js";
|
|
1
3
|
export { parseUnifiedDiff } from "./diff/parse.js";
|
|
2
4
|
export { severitySchema, verificationSchema } from "./domain.js";
|
|
3
|
-
export {
|
|
5
|
+
export { errnoCode, isNotFound, usageSpent } from "./errors.js";
|
|
6
|
+
export { shortHash } from "./hash.js";
|
|
4
7
|
export { MEMORY_PATH, memoryEntrySchema, parseMemory, serializeMemory, } from "./memory/memory.js";
|
|
5
|
-
export { proxiedFetch } from "./net/proxied-fetch.js";
|
|
6
|
-
export { AGENT_ROLES, EFFORT_LEVELS } from "./pipeline/agents.js";
|
|
7
8
|
export { reviewContext } from "./pipeline/context.js";
|
|
8
9
|
export { RISK_TIERS } from "./pipeline/matrix.js";
|
|
9
|
-
export {
|
|
10
|
+
export { MAX_TIMER_MS, REVIEW_DEFAULTS } from "./pipeline/options.js";
|
|
11
|
+
export { PLAN_VERSION, type PlanOutput, planOutputSchema, toPlanOutput, } from "./pipeline/plan-output.js";
|
|
10
12
|
export { previewReview, type ReviewPreview } from "./pipeline/preview.js";
|
|
11
13
|
export { stableHash } from "./pipeline/provenance.js";
|
|
12
14
|
export { newRunId } from "./pipeline/run-id.js";
|
|
13
|
-
export {
|
|
14
|
-
export {
|
|
15
|
+
export { readReport } from "./report/read.js";
|
|
16
|
+
export { isUnsafeCodePoint, serializeOutput } from "./report/serialize.js";
|
|
15
17
|
export { repoRuleSchema } from "./rules/repo-rules.js";
|
|
16
|
-
export {
|
|
17
|
-
export { completeWithFailback, withFailback } from "./runtime/failback.js";
|
|
18
|
-
export { callChain, ModelHealth, parseModel } from "./runtime/models.js";
|
|
19
|
-
export { parseQuotaError, type QuotaError, sleep } from "./runtime/quota.js";
|
|
20
|
-
export { MAX_READ_LINES, reviewTools } from "./runtime/tools.js";
|
|
21
|
-
export { defaultSelectionPolicy } from "./select/select.js";
|
|
18
|
+
export { REPORT_FILE } from "./session/jsonl.js";
|
|
22
19
|
//# sourceMappingURL=internal.d.ts.map
|
package/dist/internal.js
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
// "@open-cr-agent/core/internal": what ocra's own packages share beyond the
|
|
2
2
|
// public API (index.ts). Not a contract: any release may change or remove
|
|
3
3
|
// any of it, and nothing outside this repository should import it.
|
|
4
|
+
export { AGENT_ROLES } from "./agent/settings.js";
|
|
5
|
+
export { at } from "./at.js";
|
|
4
6
|
export { parseUnifiedDiff } from "./diff/parse.js";
|
|
5
7
|
export { severitySchema, verificationSchema } from "./domain.js";
|
|
6
|
-
export {
|
|
8
|
+
export { errnoCode, isNotFound, usageSpent } from "./errors.js";
|
|
9
|
+
export { shortHash } from "./hash.js";
|
|
7
10
|
export { MEMORY_PATH, memoryEntrySchema, parseMemory, serializeMemory, } from "./memory/memory.js";
|
|
8
|
-
export { proxiedFetch } from "./net/proxied-fetch.js";
|
|
9
|
-
export { AGENT_ROLES, EFFORT_LEVELS } from "./pipeline/agents.js";
|
|
10
11
|
export { reviewContext } from "./pipeline/context.js";
|
|
11
12
|
export { RISK_TIERS } from "./pipeline/matrix.js";
|
|
12
|
-
export {
|
|
13
|
+
export { MAX_TIMER_MS, REVIEW_DEFAULTS } from "./pipeline/options.js";
|
|
14
|
+
export { PLAN_VERSION, planOutputSchema, toPlanOutput, } from "./pipeline/plan-output.js";
|
|
13
15
|
export { previewReview } from "./pipeline/preview.js";
|
|
14
16
|
export { stableHash } from "./pipeline/provenance.js";
|
|
15
17
|
export { newRunId } from "./pipeline/run-id.js";
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
18
|
+
export { readReport } from "./report/read.js";
|
|
19
|
+
export { isUnsafeCodePoint, serializeOutput } from "./report/serialize.js";
|
|
18
20
|
export { repoRuleSchema } from "./rules/repo-rules.js";
|
|
19
|
-
export {
|
|
20
|
-
export { completeWithFailback, withFailback } from "./runtime/failback.js";
|
|
21
|
-
export { callChain, ModelHealth, parseModel } from "./runtime/models.js";
|
|
22
|
-
export { parseQuotaError, sleep } from "./runtime/quota.js";
|
|
23
|
-
export { MAX_READ_LINES, reviewTools } from "./runtime/tools.js";
|
|
24
|
-
export { defaultSelectionPolicy } from "./select/select.js";
|
|
21
|
+
export { REPORT_FILE } from "./session/jsonl.js";
|
|
25
22
|
//# sourceMappingURL=internal.js.map
|
package/dist/judge/judge.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
+
import { type AgentCallSettings } from "../agent/settings.js";
|
|
1
2
|
import type { AgentRuntime, Usage } from "../contracts.js";
|
|
2
3
|
import type { ChangeRequest, Finding, PriorFinding, RiskTier, Severity, Verdict } from "../domain.js";
|
|
3
|
-
import { type AgentCallSettings } from "../pipeline/agents.js";
|
|
4
4
|
import { type JudgeResponse } from "./prompt.js";
|
|
5
|
-
export declare const JUDGE_TIMEOUT_MS = 180000;
|
|
6
5
|
export interface JudgeDecisions {
|
|
7
6
|
merged: {
|
|
8
7
|
kept: string;
|
package/dist/judge/judge.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { parseJsonAnswer } from "../agent/json.js";
|
|
2
|
+
import { agentCall } from "../agent/settings.js";
|
|
3
|
+
import { at } from "../at.js";
|
|
1
4
|
import { errorMessage, OcraError, usageSpent } from "../errors.js";
|
|
2
|
-
import { agentCall } from "../pipeline/agents.js";
|
|
3
|
-
import { parseJsonAnswer } from "../pipeline/helpers.js";
|
|
4
5
|
import { buildJudgePrompt, judgeResponseSchema } from "./prompt.js";
|
|
5
6
|
import { decideVerdict, defaultSummary } from "./verdict.js";
|
|
6
|
-
|
|
7
|
+
const JUDGE_TIMEOUT_MS = 180_000;
|
|
7
8
|
export async function judgeFindings(findings, options) {
|
|
8
9
|
const carried = options.carried ?? [];
|
|
9
10
|
const fallback = (warnings = [], usage = []) => ({
|
|
@@ -69,7 +70,7 @@ export function applyDecisions(findings, response) {
|
|
|
69
70
|
const decisions = { merged: [], dropped: [], recalibrated: [] };
|
|
70
71
|
const warnings = [];
|
|
71
72
|
const removed = new Set();
|
|
72
|
-
const isProtected = (i) => protectedFinding(findings
|
|
73
|
+
const isProtected = (i) => protectedFinding(at(findings, i));
|
|
73
74
|
for (const group of response.duplicates) {
|
|
74
75
|
const members = [...new Set(group.filter(valid))].filter((i) => !removed.has(i));
|
|
75
76
|
// Merging keeps one member; keep a protected one so it is not merged away.
|
|
@@ -82,19 +83,19 @@ export function applyDecisions(findings, response) {
|
|
|
82
83
|
for (const i of rest)
|
|
83
84
|
removed.add(i);
|
|
84
85
|
decisions.merged.push({
|
|
85
|
-
kept: findings
|
|
86
|
-
merged: rest.map((i) => findings
|
|
86
|
+
kept: at(findings, keep).fingerprint,
|
|
87
|
+
merged: rest.map((i) => at(findings, i).fingerprint),
|
|
87
88
|
});
|
|
88
89
|
}
|
|
89
90
|
for (const { index, reason } of response.drop) {
|
|
90
91
|
if (!valid(index) || removed.has(index))
|
|
91
92
|
continue;
|
|
92
93
|
if (isProtected(index)) {
|
|
93
|
-
warnings.push(`judge tried to drop the confirmed critical finding ${findings
|
|
94
|
+
warnings.push(`judge tried to drop the confirmed critical finding ${at(findings, index).fingerprint.slice(0, 8)}; kept it`);
|
|
94
95
|
continue;
|
|
95
96
|
}
|
|
96
97
|
removed.add(index);
|
|
97
|
-
const f = findings
|
|
98
|
+
const f = at(findings, index);
|
|
98
99
|
decisions.dropped.push({ fingerprint: f.fingerprint, file: f.file, title: f.title, reason });
|
|
99
100
|
}
|
|
100
101
|
const severities = new Map();
|
package/dist/judge/verdict.d.ts
CHANGED
package/dist/judge/verdict.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
const PATTERN_WARNINGS = 3;
|
|
2
2
|
// The rubric is code, so the same findings always give the same verdict and
|
|
3
3
|
// a failed judge call cannot change it; it leans towards approval. Only a
|
|
4
4
|
// critical finding the verifier confirmed blocks: one model's unchecked claim
|
package/dist/memory/memory.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { OcraError } from "../errors.js";
|
|
2
|
+
import { errorMessage, OcraError } from "../errors.js";
|
|
3
3
|
export const MEMORY_PATH = ".ocra/memory.json";
|
|
4
4
|
const MAX_ENTRIES = 1_000;
|
|
5
5
|
export const memoryEntrySchema = z.object({
|
|
@@ -19,7 +19,7 @@ export function parseMemory(json) {
|
|
|
19
19
|
data = JSON.parse(json);
|
|
20
20
|
}
|
|
21
21
|
catch (error) {
|
|
22
|
-
throw new OcraError("CONFIG_INVALID", `${MEMORY_PATH} is not valid JSON: ${error
|
|
22
|
+
throw new OcraError("CONFIG_INVALID", `${MEMORY_PATH} is not valid JSON: ${errorMessage(error)}`, { cause: error });
|
|
23
23
|
}
|
|
24
24
|
const parsed = memoryFileSchema.safeParse(data);
|
|
25
25
|
if (!parsed.success)
|
|
@@ -5,7 +5,8 @@ import { EnvHttpProxyAgent, fetch as undiciFetch } from "undici";
|
|
|
5
5
|
// EnvHttpProxyAgent, reading the given environment rather than the
|
|
6
6
|
// process's; without one, Node's fetch is used as it is. undici's fetch and
|
|
7
7
|
// agent are used together so that no object crosses between it and Node's
|
|
8
|
-
// bundled copy (
|
|
8
|
+
// bundled copy (runtime-opencode's transport keeps its own copy of the
|
|
9
|
+
// adapter on purpose; see there).
|
|
9
10
|
export function proxiedFetch(env) {
|
|
10
11
|
const httpProxy = env.HTTP_PROXY ?? env.http_proxy;
|
|
11
12
|
const httpsProxy = env.HTTPS_PROXY ?? env.https_proxy;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PriorReview } from "../domain.js";
|
|
2
|
+
import { type ReviewReport } from "../report/report.js";
|
|
3
|
+
import type { ReviewerDefinition } from "../review/reviewer.js";
|
|
4
|
+
import type { CheckStage } from "./check-stage.js";
|
|
5
|
+
import type { ExecuteStage, StageContext } from "./execute-stage.js";
|
|
6
|
+
import type { FilterStage } from "./filter-stage.js";
|
|
7
|
+
export interface RunFacts {
|
|
8
|
+
runId: string;
|
|
9
|
+
reviewers: readonly ReviewerDefinition[];
|
|
10
|
+
prior: {
|
|
11
|
+
review?: PriorReview;
|
|
12
|
+
warning?: string;
|
|
13
|
+
};
|
|
14
|
+
scopeNote?: NonNullable<ReviewReport["scope"]> | undefined;
|
|
15
|
+
}
|
|
16
|
+
export interface Stages {
|
|
17
|
+
executed: ExecuteStage;
|
|
18
|
+
filtered: FilterStage;
|
|
19
|
+
checked: CheckStage;
|
|
20
|
+
}
|
|
21
|
+
export declare function assembleReport(run: RunFacts, stages: Stages, context: StageContext): ReviewReport;
|
|
22
|
+
//# sourceMappingURL=assemble-report.d.ts.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { effortWarnings, resolveAgents } from "../agent/settings.js";
|
|
2
|
+
import { addUsage, emptyUsage, unpricedCalls } from "../agent/usage.js";
|
|
3
|
+
import { summarizeAnchoring } from "../report/report.js";
|
|
4
|
+
import { runProvenance } from "./provenance.js";
|
|
5
|
+
export function assembleReport(run, stages, context) {
|
|
6
|
+
const report = reportBody(run, stages, context);
|
|
7
|
+
const { options, plan, budget } = context;
|
|
8
|
+
const { executed, filtered, checked } = stages;
|
|
9
|
+
if (checked.judged.decisions)
|
|
10
|
+
report.judgement = checked.judged.decisions;
|
|
11
|
+
if (options.limits?.maxCostUsd !== undefined) {
|
|
12
|
+
const reached = budget.exhausted()
|
|
13
|
+
? "total"
|
|
14
|
+
: executed.reviewLimitReached()
|
|
15
|
+
? "review"
|
|
16
|
+
: undefined;
|
|
17
|
+
report.spendLimit = { usd: options.limits?.maxCostUsd, ...(reached ? { reached } : {}) };
|
|
18
|
+
}
|
|
19
|
+
report.anchoring = summarizeAnchoring(report.findings, executed.relocations.length);
|
|
20
|
+
const agents = resolveAgents(run.reviewers, options);
|
|
21
|
+
report.warnings.push(...effortWarnings(agents, options.runtime));
|
|
22
|
+
if (options.identity?.provenance) {
|
|
23
|
+
const { runtime, reviewerOverrides } = options;
|
|
24
|
+
report.provenance = runProvenance(options.identity?.provenance, runtime, run.reviewers, agents, reviewerOverrides, plan.repoRules);
|
|
25
|
+
}
|
|
26
|
+
if (run.prior.review) {
|
|
27
|
+
const { fixed, notReproduced, notRechecked, unchanged, dismissed } = filtered.reconciled;
|
|
28
|
+
report.rereview = { fixed, notReproduced, notRechecked, unchanged, dismissed };
|
|
29
|
+
}
|
|
30
|
+
if (plan.widened) {
|
|
31
|
+
report.scope = {
|
|
32
|
+
mode: "full",
|
|
33
|
+
reason: `the risk tier rose from ${plan.widened.from} to ${plan.widened.to}, which adds reviewers`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
else if (run.scopeNote)
|
|
37
|
+
report.scope = run.scopeNote;
|
|
38
|
+
if (run.prior.warning)
|
|
39
|
+
report.warnings.push(run.prior.warning);
|
|
40
|
+
return report;
|
|
41
|
+
}
|
|
42
|
+
function reportBody(run, { executed, filtered, checked }, { plan }) {
|
|
43
|
+
const { verification, judged } = checked;
|
|
44
|
+
const calls = [...plan.usage, ...executed.usage(), ...checked.usage];
|
|
45
|
+
return {
|
|
46
|
+
runId: run.runId,
|
|
47
|
+
changeRequest: plan.changeRequest,
|
|
48
|
+
tier: plan.tier,
|
|
49
|
+
verdict: judged.verdict,
|
|
50
|
+
summary: filtered.nothingReviewed
|
|
51
|
+
? "Nothing was reviewed: no reviewer covered or finished any selected file."
|
|
52
|
+
: judged.summary,
|
|
53
|
+
coverage: filtered.coverage,
|
|
54
|
+
bundles: plan.bundles.map((b) => ({ label: b.label, files: b.files.map((f) => f.newPath) })),
|
|
55
|
+
tasks: [...executed.results.map((r) => r.outcome), ...filtered.imported.outcomes],
|
|
56
|
+
skipped: executed.matrix.skipped,
|
|
57
|
+
findings: sortFindings(judged.findings),
|
|
58
|
+
// Counted before the judge: dropping or downgrading a critical nobody
|
|
59
|
+
// could check must not turn an incomplete run into a clean one.
|
|
60
|
+
unverifiedCriticals: countMissedCriticals(verification.kept, verification.missed),
|
|
61
|
+
refuted: verification.refuted,
|
|
62
|
+
remembered: filtered.remembered,
|
|
63
|
+
usage: sumUsage(calls),
|
|
64
|
+
warnings: [
|
|
65
|
+
...unpricedWarning(calls),
|
|
66
|
+
...plan.warnings,
|
|
67
|
+
...executed.warnings,
|
|
68
|
+
...filtered.warnings,
|
|
69
|
+
...checked.warnings,
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const SEVERITY_ORDER = { critical: 0, warning: 1, suggestion: 2 };
|
|
74
|
+
function sortFindings(findings) {
|
|
75
|
+
return findings.sort((a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity] ||
|
|
76
|
+
a.file.localeCompare(b.file) ||
|
|
77
|
+
(a.lineRange?.start ?? 0) - (b.lineRange?.start ?? 0));
|
|
78
|
+
}
|
|
79
|
+
function unpricedWarning(calls) {
|
|
80
|
+
const unpriced = unpricedCalls(calls);
|
|
81
|
+
return unpriced > 0
|
|
82
|
+
? [
|
|
83
|
+
`${unpriced} model call(s) used tokens but reported no cost: their model has no price, so the reported cost and the spend limit do not count them`,
|
|
84
|
+
]
|
|
85
|
+
: [];
|
|
86
|
+
}
|
|
87
|
+
function sumUsage(usages) {
|
|
88
|
+
return usages.reduce(addUsage, emptyUsage());
|
|
89
|
+
}
|
|
90
|
+
// Low-confidence findings do not count toward the verdict, so their missed
|
|
91
|
+
// verification does not make the run incomplete either.
|
|
92
|
+
function countMissedCriticals(findings, missed) {
|
|
93
|
+
const set = new Set(missed);
|
|
94
|
+
return findings.filter((f) => f.severity === "critical" &&
|
|
95
|
+
!f.lowConfidence &&
|
|
96
|
+
f.verification === "unchecked" &&
|
|
97
|
+
set.has(f.fingerprint)).length;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=assemble-report.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Usage } from "../contracts.js";
|
|
2
|
+
import { type JudgeResult } from "../judge/judge.js";
|
|
3
|
+
import { type VerificationResult } from "../verify/verify.js";
|
|
4
|
+
import type { StageContext } from "./execute-stage.js";
|
|
5
|
+
import type { FilterStage } from "./filter-stage.js";
|
|
6
|
+
export interface CheckStage {
|
|
7
|
+
verification: VerificationResult;
|
|
8
|
+
judged: JudgeResult;
|
|
9
|
+
usage: Usage[];
|
|
10
|
+
warnings: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function checkStage(filtered: FilterStage, context: StageContext): Promise<CheckStage>;
|
|
13
|
+
//# sourceMappingURL=check-stage.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { roleCall } from "../agent/settings.js";
|
|
2
|
+
import { judgeFindings } from "../judge/judge.js";
|
|
3
|
+
import { stillOpen } from "../rereview/reconcile.js";
|
|
4
|
+
import { markUnchecked, verifyFindings } from "../verify/verify.js";
|
|
5
|
+
import { REVIEW_DEFAULTS } from "./options.js";
|
|
6
|
+
// Verify fact-checks the findings, then the judge merges, filters and
|
|
7
|
+
// recalibrates them and gives the verdict.
|
|
8
|
+
export async function checkStage(filtered, context) {
|
|
9
|
+
const verification = await verify(filtered, context);
|
|
10
|
+
const judged = await judge(verification, filtered, context);
|
|
11
|
+
return {
|
|
12
|
+
verification,
|
|
13
|
+
judged,
|
|
14
|
+
usage: [...verification.usage, ...judged.usage],
|
|
15
|
+
warnings: [...verification.warnings, ...judged.warnings],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
async function verify({ reconciled }, { options, plan, budget, signal, emit }) {
|
|
19
|
+
if (options.stages?.verify === false) {
|
|
20
|
+
return {
|
|
21
|
+
checked: 0,
|
|
22
|
+
kept: markUnchecked(reconciled.findings),
|
|
23
|
+
refuted: [],
|
|
24
|
+
missed: [],
|
|
25
|
+
usage: [],
|
|
26
|
+
warnings: [],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const verification = await verifyFindings(reconciled.findings, {
|
|
30
|
+
runtime: options.runtime,
|
|
31
|
+
diffs: plan.selected,
|
|
32
|
+
context: plan.context,
|
|
33
|
+
signal,
|
|
34
|
+
concurrency: options.limits?.concurrency ?? REVIEW_DEFAULTS.concurrency,
|
|
35
|
+
budget,
|
|
36
|
+
call: roleCall("verifier", options),
|
|
37
|
+
});
|
|
38
|
+
if (verification.checked > 0) {
|
|
39
|
+
emit({
|
|
40
|
+
type: "verification_finished",
|
|
41
|
+
checked: verification.checked,
|
|
42
|
+
refuted: verification.refuted,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return verification;
|
|
46
|
+
}
|
|
47
|
+
async function judge(verification, { reconciled, nothingReviewed }, { options, plan, budget, signal, emit }) {
|
|
48
|
+
const wanted = options.stages?.judge !== false && verification.kept.length > 0;
|
|
49
|
+
const affordable = !budget.exhausted();
|
|
50
|
+
const judged = await judgeFindings(verification.kept, {
|
|
51
|
+
runtime: options.runtime,
|
|
52
|
+
changeRequest: plan.changeRequest,
|
|
53
|
+
tier: plan.tier,
|
|
54
|
+
signal,
|
|
55
|
+
call: roleCall("judge", options),
|
|
56
|
+
enabled: wanted && affordable,
|
|
57
|
+
keepDropped: options.mode?.ultra === true,
|
|
58
|
+
carried: stillOpen(reconciled),
|
|
59
|
+
});
|
|
60
|
+
if (wanted && !affordable) {
|
|
61
|
+
judged.warnings.push(`spend limit of $${options.limits?.maxCostUsd} reached: findings were not judged`);
|
|
62
|
+
}
|
|
63
|
+
if (!nothingReviewed) {
|
|
64
|
+
emit(judged.decisions
|
|
65
|
+
? { type: "judge_finished", verdict: judged.verdict, judgement: judged.decisions }
|
|
66
|
+
: { type: "judge_finished", verdict: judged.verdict });
|
|
67
|
+
}
|
|
68
|
+
return judged;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=check-stage.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { ReviewContext
|
|
1
|
+
import type { ReviewContext } from "../contracts.js";
|
|
2
2
|
import type { FileDiff } from "../domain.js";
|
|
3
3
|
import { OcraError } from "../errors.js";
|
|
4
|
+
import type { VcsAdapter } from "../vcs.js";
|
|
4
5
|
export declare class AccessDeniedError extends OcraError {
|
|
5
6
|
constructor(message: string);
|
|
6
7
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import type { CoverageEntry } from "../report/report.js";
|
|
1
2
|
import type { FileDecision } from "../select/select.js";
|
|
2
3
|
import type { JobResult } from "./execute.js";
|
|
3
4
|
import type { MatrixCell } from "./matrix.js";
|
|
4
|
-
import type { CoverageEntry } from "./report.js";
|
|
5
5
|
export declare function coverageOf(decisions: readonly FileDecision[], results: readonly JobResult[], unchanged: ReadonlySet<string>, limited: readonly MatrixCell[], notStarted: ReadonlySet<string>): CoverageEntry[];
|
|
6
6
|
//# sourceMappingURL=coverage.d.ts.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { at } from "../at.js";
|
|
1
2
|
export function coverageOf(decisions, results, unchanged, limited, notStarted) {
|
|
2
3
|
// A file is reviewed when every reviewer assigned to it finished at least
|
|
3
4
|
// one of its tasks: under --ultra one completed sample is enough. A
|
|
@@ -24,7 +25,7 @@ export function coverageOf(decisions, results, unchanged, limited, notStarted) {
|
|
|
24
25
|
}
|
|
25
26
|
const status = new Map();
|
|
26
27
|
for (const [k, completed] of done) {
|
|
27
|
-
const file = k.split("\0")
|
|
28
|
+
const file = at(k.split("\0"), 1);
|
|
28
29
|
const now = completed ? "reviewed" : ran.has(k) ? "failed" : "unreviewed";
|
|
29
30
|
const before = status.get(file);
|
|
30
31
|
// failed outranks unreviewed, which outranks reviewed.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type SpendTracker } from "../agent/budget.js";
|
|
2
|
+
import type { Usage } from "../contracts.js";
|
|
3
|
+
import type { ReviewEvent } from "../report/report.js";
|
|
4
|
+
import type { ReviewerDefinition } from "../review/reviewer.js";
|
|
5
|
+
import { type JobResult } from "./execute.js";
|
|
6
|
+
import { type ReviewMatrix } from "./matrix.js";
|
|
7
|
+
import { type ReviewHooks, type ReviewOptions } from "./options.js";
|
|
8
|
+
import type { ReviewPlan } from "./plan.js";
|
|
9
|
+
export interface StageContext {
|
|
10
|
+
options: ReviewOptions & ReviewHooks;
|
|
11
|
+
plan: ReviewPlan;
|
|
12
|
+
budget: SpendTracker;
|
|
13
|
+
signal: AbortSignal;
|
|
14
|
+
emit: (event: ReviewEvent) => void;
|
|
15
|
+
}
|
|
16
|
+
export interface ExecuteStage {
|
|
17
|
+
matrix: ReviewMatrix;
|
|
18
|
+
results: JobResult[];
|
|
19
|
+
notStarted: ReadonlySet<string>;
|
|
20
|
+
relocations: readonly Usage[];
|
|
21
|
+
usage(): Usage[];
|
|
22
|
+
warnings: string[];
|
|
23
|
+
reviewLimitReached(): boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function executeStage(reviewers: readonly ReviewerDefinition[], context: StageContext): Promise<ExecuteStage>;
|
|
26
|
+
//# sourceMappingURL=execute-stage.d.ts.map
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { SpendLimitReached } from "../agent/budget.js";
|
|
2
|
+
import { mapWithConcurrency } from "../agent/pool.js";
|
|
3
|
+
import { roleCall } from "../agent/settings.js";
|
|
4
|
+
import { emptyUsage } from "../agent/usage.js";
|
|
5
|
+
import { runtimeRelocator } from "../anchor/relocate.js";
|
|
6
|
+
import { runJob } from "./execute.js";
|
|
7
|
+
import { DEFAULT_MAX_TASKS, planTasks } from "./matrix.js";
|
|
8
|
+
import { MAX_TIMER_MS, REVIEW_DEFAULTS } from "./options.js";
|
|
9
|
+
// One agent task per (bundle, reviewer) cell of the matrix, within the task
|
|
10
|
+
// limit, the run's signal and the review share of the spend limit.
|
|
11
|
+
export async function executeStage(reviewers, context) {
|
|
12
|
+
const { options, plan, emit } = context;
|
|
13
|
+
const matrix = planTasks(plan.bundles, reviewers, plan.tier, options.reviewerOverrides, {
|
|
14
|
+
ultra: options.mode?.ultra === true,
|
|
15
|
+
...(options.limits?.maxTasks !== undefined ? { maxTasks: options.limits?.maxTasks } : {}),
|
|
16
|
+
hasGuidelines: Boolean(plan.guidelines?.trim()),
|
|
17
|
+
});
|
|
18
|
+
const warnings = [];
|
|
19
|
+
const limited = matrix.skipped.filter((s) => s.reason === "task_limit").length;
|
|
20
|
+
if (limited > 0) {
|
|
21
|
+
warnings.push(`task limit of ${options.limits?.maxTasks ?? DEFAULT_MAX_TASKS} reached: ${limited} review task(s) skipped; their files are reported as not reviewed`);
|
|
22
|
+
}
|
|
23
|
+
emit({ type: "matrix_planned", tasks: matrix.cells.length, skipped: matrix.skipped });
|
|
24
|
+
const relocations = [];
|
|
25
|
+
const spendLimit = new AbortController();
|
|
26
|
+
const execute = executeOptions(context, relocations, spendLimit);
|
|
27
|
+
const notStarted = new Set();
|
|
28
|
+
let unaffordable = 0;
|
|
29
|
+
const results = await mapWithConcurrency(matrix.cells, options.limits?.concurrency ?? REVIEW_DEFAULTS.concurrency, async (cell) => {
|
|
30
|
+
// Once the run is cancelled or timed out, remaining cells are not started.
|
|
31
|
+
if (context.signal.aborted) {
|
|
32
|
+
notStarted.add(cell.taskId);
|
|
33
|
+
return skipCell(cell, "run cancelled before this task started", emit);
|
|
34
|
+
}
|
|
35
|
+
if (context.budget.reviewExhausted()) {
|
|
36
|
+
notStarted.add(cell.taskId);
|
|
37
|
+
unaffordable += 1;
|
|
38
|
+
return skipCell(cell, `spend limit of $${options.limits?.maxCostUsd} reached`, emit);
|
|
39
|
+
}
|
|
40
|
+
return runJob(cell, plan, execute);
|
|
41
|
+
});
|
|
42
|
+
if (unaffordable > 0) {
|
|
43
|
+
warnings.push(`spend limit of $${options.limits?.maxCostUsd} reached: ${unaffordable} review task(s) did not start; their files are reported as not reviewed`);
|
|
44
|
+
}
|
|
45
|
+
warnings.push(...results.flatMap((r) => r.warnings));
|
|
46
|
+
return {
|
|
47
|
+
matrix,
|
|
48
|
+
results,
|
|
49
|
+
notStarted,
|
|
50
|
+
relocations,
|
|
51
|
+
usage: () => [...results.map((r) => r.usage), ...relocations],
|
|
52
|
+
warnings,
|
|
53
|
+
reviewLimitReached: () => spendLimit.signal.aborted || unaffordable > 0,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function executeOptions({ options, budget, signal, emit }, relocations, spendLimit) {
|
|
57
|
+
const relocator = relocatorOf(options, signal, (u) => {
|
|
58
|
+
relocations.push(u);
|
|
59
|
+
budget.add(u);
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
runtime: options.runtime,
|
|
63
|
+
taskTimeoutMs: Math.min(options.limits?.taskTimeoutMs ?? REVIEW_DEFAULTS.taskTimeoutMs, MAX_TIMER_MS),
|
|
64
|
+
abortGraceMs: options.abortGraceMs,
|
|
65
|
+
// Past the spend limit a quote that does not match stays file-level.
|
|
66
|
+
relocate: relocator &&
|
|
67
|
+
(async (request) => (budget.exhausted() ? undefined : relocator(request))),
|
|
68
|
+
ultra: options.mode?.ultra === true,
|
|
69
|
+
plans: new Map(),
|
|
70
|
+
agents: options,
|
|
71
|
+
emit,
|
|
72
|
+
// Tasks report spend while they run, so the one that uses up the review
|
|
73
|
+
// share stops every task still running, not only the ones not yet started.
|
|
74
|
+
onUsage: (usage) => {
|
|
75
|
+
budget.add(usage);
|
|
76
|
+
if (budget.reviewExhausted() && !spendLimit.signal.aborted) {
|
|
77
|
+
spendLimit.abort(new SpendLimitReached(options.limits?.maxCostUsd ?? 0));
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
signal: AbortSignal.any([signal, spendLimit.signal]),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function relocatorOf(options, signal, onUsage) {
|
|
84
|
+
if (options.relocate === false)
|
|
85
|
+
return undefined;
|
|
86
|
+
return (options.relocate ??
|
|
87
|
+
runtimeRelocator(options.runtime, signal, onUsage, roleCall("helper", options)));
|
|
88
|
+
}
|
|
89
|
+
function skipCell(cell, reason, emit) {
|
|
90
|
+
const outcome = {
|
|
91
|
+
taskId: cell.taskId,
|
|
92
|
+
reviewer: cell.reviewer.id,
|
|
93
|
+
bundle: cell.bundle.label,
|
|
94
|
+
files: cell.bundle.files.map((f) => f.newPath),
|
|
95
|
+
status: "cancelled",
|
|
96
|
+
error: reason,
|
|
97
|
+
findings: 0,
|
|
98
|
+
durationMs: 0,
|
|
99
|
+
usage: emptyUsage(),
|
|
100
|
+
};
|
|
101
|
+
emit({ type: "task_finished", outcome });
|
|
102
|
+
return { outcome, findings: [], usage: emptyUsage(), warnings: [] };
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=execute-stage.js.map
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { type AgentSettings } from "../agent/settings.js";
|
|
1
2
|
import { type AnchorContext } from "../anchor/anchor.js";
|
|
2
3
|
import type { AgentRuntime, Usage } from "../contracts.js";
|
|
3
4
|
import type { Finding } from "../domain.js";
|
|
5
|
+
import type { ReviewEvent, TaskOutcome } from "../report/report.js";
|
|
4
6
|
import { planBundle } from "../review/plan-phase.js";
|
|
5
|
-
import { type AgentSettings } from "./agents.js";
|
|
6
7
|
import type { MatrixCell } from "./matrix.js";
|
|
7
8
|
import type { ReviewPlan } from "./plan.js";
|
|
8
|
-
import type { ReviewEvent, TaskOutcome } from "./report.js";
|
|
9
9
|
export interface JobResult {
|
|
10
10
|
outcome: TaskOutcome;
|
|
11
11
|
findings: Finding[];
|
|
@@ -25,8 +25,6 @@ export interface ExecuteOptions {
|
|
|
25
25
|
agents?: AgentSettings;
|
|
26
26
|
}
|
|
27
27
|
type PlannedBundle = Awaited<ReturnType<typeof planBundle>>;
|
|
28
|
-
export declare const PLAN_MIN_FILES = 5;
|
|
29
|
-
export declare const PLAN_MIN_PATCH_CHARS = 40000;
|
|
30
28
|
export declare function isLargeBundle(files: readonly {
|
|
31
29
|
patch: string;
|
|
32
30
|
}[]): boolean;
|