@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.
Files changed (128) hide show
  1. package/dist/agent/json.d.ts +2 -0
  2. package/dist/agent/json.js +12 -0
  3. package/dist/{pipeline → agent}/pool.js +2 -1
  4. package/dist/{pipeline/agents.d.ts → agent/settings.d.ts} +10 -5
  5. package/dist/{pipeline/agents.js → agent/settings.js} +15 -6
  6. package/dist/anchor/match.js +5 -4
  7. package/dist/anchor/relocate.d.ts +1 -2
  8. package/dist/anchor/relocate.js +2 -2
  9. package/dist/at.d.ts +2 -0
  10. package/dist/at.js +12 -0
  11. package/dist/bundle/bundle.d.ts +2 -1
  12. package/dist/bundle/bundle.js +3 -2
  13. package/dist/bundle/grouping.d.ts +0 -1
  14. package/dist/bundle/runtime-grouper.d.ts +5 -0
  15. package/dist/{pipeline/helpers.js → bundle/runtime-grouper.js} +5 -14
  16. package/dist/contracts.d.ts +6 -14
  17. package/dist/contracts.js +2 -1
  18. package/dist/diff/hunks.js +3 -2
  19. package/dist/diff/parse.js +5 -4
  20. package/dist/diff/quoted-path.js +16 -15
  21. package/dist/domain.d.ts +10 -2
  22. package/dist/domain.js +2 -2
  23. package/dist/errors.d.ts +3 -1
  24. package/dist/errors.js +12 -0
  25. package/dist/hash.d.ts +2 -0
  26. package/dist/hash.js +7 -0
  27. package/dist/index.d.ts +26 -12
  28. package/dist/index.js +16 -5
  29. package/dist/internal.d.ts +9 -12
  30. package/dist/internal.js +9 -12
  31. package/dist/judge/judge.d.ts +1 -2
  32. package/dist/judge/judge.js +9 -8
  33. package/dist/judge/verdict.d.ts +0 -1
  34. package/dist/judge/verdict.js +1 -1
  35. package/dist/memory/memory.js +2 -2
  36. package/dist/net/proxied-fetch.js +2 -1
  37. package/dist/pipeline/assemble-report.d.ts +22 -0
  38. package/dist/pipeline/assemble-report.js +99 -0
  39. package/dist/pipeline/check-stage.d.ts +13 -0
  40. package/dist/pipeline/check-stage.js +70 -0
  41. package/dist/pipeline/context.d.ts +2 -1
  42. package/dist/pipeline/coverage.d.ts +1 -1
  43. package/dist/pipeline/coverage.js +2 -1
  44. package/dist/pipeline/execute-stage.d.ts +26 -0
  45. package/dist/pipeline/execute-stage.js +104 -0
  46. package/dist/pipeline/execute.d.ts +2 -4
  47. package/dist/pipeline/execute.js +63 -56
  48. package/dist/pipeline/filter-stage.d.ts +16 -0
  49. package/dist/pipeline/filter-stage.js +46 -0
  50. package/dist/pipeline/findings.js +3 -2
  51. package/dist/pipeline/imports.d.ts +1 -1
  52. package/dist/pipeline/imports.js +1 -1
  53. package/dist/pipeline/matrix.d.ts +1 -8
  54. package/dist/pipeline/options.d.ts +65 -0
  55. package/dist/pipeline/options.js +9 -0
  56. package/dist/pipeline/plan-output.d.ts +90 -0
  57. package/dist/pipeline/plan-output.js +109 -0
  58. package/dist/pipeline/plan.d.ts +2 -3
  59. package/dist/pipeline/plan.js +4 -4
  60. package/dist/pipeline/preview.d.ts +23 -9
  61. package/dist/pipeline/preview.js +25 -4
  62. package/dist/pipeline/provenance.d.ts +4 -27
  63. package/dist/pipeline/provenance.js +2 -2
  64. package/dist/pipeline/run.d.ts +2 -48
  65. package/dist/pipeline/run.js +18 -273
  66. package/dist/pipeline/task.d.ts +1 -3
  67. package/dist/pipeline/task.js +4 -4
  68. package/dist/plugin/registry.d.ts +5 -4
  69. package/dist/plugin/registry.js +3 -3
  70. package/dist/plugin/types.d.ts +3 -5
  71. package/dist/{pipeline → report}/output-schema.d.ts +57 -63
  72. package/dist/{pipeline → report}/output-schema.js +55 -40
  73. package/dist/report/output.d.ts +8 -0
  74. package/dist/report/output.js +211 -0
  75. package/dist/report/provenance.d.ts +26 -0
  76. package/dist/report/provenance.js +2 -0
  77. package/dist/report/read.d.ts +3 -0
  78. package/dist/report/read.js +34 -0
  79. package/dist/{pipeline → report}/report.d.ts +1 -1
  80. package/dist/report/serialize.d.ts +3 -0
  81. package/dist/report/serialize.js +27 -0
  82. package/dist/rereview/presence.js +1 -1
  83. package/dist/rereview/quote.js +2 -2
  84. package/dist/rereview/reconcile.d.ts +1 -1
  85. package/dist/review/impact.d.ts +0 -2
  86. package/dist/review/impact.js +2 -2
  87. package/dist/review/plan-phase.d.ts +1 -2
  88. package/dist/review/plan-phase.js +2 -2
  89. package/dist/review/prompt-text.d.ts +1 -1
  90. package/dist/review/prompt-text.js +1 -1
  91. package/dist/review/prompt.d.ts +0 -3
  92. package/dist/review/prompt.js +3 -3
  93. package/dist/rules/builtin/index.d.ts +0 -4
  94. package/dist/rules/builtin/index.js +1 -2
  95. package/dist/rules/repo-rules.d.ts +0 -6
  96. package/dist/rules/repo-rules.js +3 -3
  97. package/dist/runtime/chain-runner.d.ts +19 -0
  98. package/dist/runtime/chain-runner.js +63 -0
  99. package/dist/runtime/effort-capability.d.ts +14 -0
  100. package/dist/runtime/effort-capability.js +70 -0
  101. package/dist/runtime/failback.d.ts +0 -9
  102. package/dist/runtime/failback.js +14 -4
  103. package/dist/runtime/models.d.ts +1 -2
  104. package/dist/runtime/tools.d.ts +0 -16
  105. package/dist/runtime/tools.js +3 -3
  106. package/dist/sarif/candidates.d.ts +2 -1
  107. package/dist/sarif/schema.d.ts +2 -2
  108. package/dist/sarif/schema.js +4 -4
  109. package/dist/session/jsonl.d.ts +2 -2
  110. package/dist/session/jsonl.js +5 -4
  111. package/dist/triage.d.ts +2 -2
  112. package/dist/triage.js +1 -1
  113. package/dist/vcs.d.ts +15 -0
  114. package/dist/vcs.js +2 -0
  115. package/dist/verify/prompt.d.ts +0 -6
  116. package/dist/verify/prompt.js +1 -1
  117. package/dist/verify/verify.d.ts +2 -3
  118. package/dist/verify/verify.js +4 -4
  119. package/package.json +1 -1
  120. package/dist/pipeline/helpers.d.ts +0 -7
  121. package/dist/pipeline/output.d.ts +0 -79
  122. package/dist/pipeline/output.js +0 -132
  123. /package/dist/{pipeline → agent}/budget.d.ts +0 -0
  124. /package/dist/{pipeline → agent}/budget.js +0 -0
  125. /package/dist/{pipeline → agent}/pool.d.ts +0 -0
  126. /package/dist/{pipeline → agent}/usage.d.ts +0 -0
  127. /package/dist/{pipeline → agent}/usage.js +0 -0
  128. /package/dist/{pipeline → report}/report.js +0 -0
@@ -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
- export const PLAN_MIN_FILES = 5;
14
- export const PLAN_MIN_PATCH_CHARS = 40_000;
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 extraUsage = [];
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 warnings = [...extraWarnings, ...result.warnings];
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 result.findings) {
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
- const usage = extraUsage.reduce(addUsage, result.usage);
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 { createHash, randomUUID } from "node:crypto";
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 createHash("sha256").update(key).digest("hex").slice(0, 16);
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[];
@@ -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
@@ -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 { ReviewEvent } from "./report.js";
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[];
@@ -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
- export const GUIDELINES_PATH = "AGENTS.md";
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 ?? defaultSelectionPolicy);
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 ReviewerOverrides, type SkippedCell } from "./matrix.js";
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
- reviewers?: readonly ReviewerDefinition[];
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