@open-cr-agent/core 0.4.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 (131) 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} +21 -7
  5. package/dist/agent/settings.js +86 -0
  6. package/dist/anchor/match.js +5 -4
  7. package/dist/anchor/relocate.d.ts +3 -3
  8. package/dist/anchor/relocate.js +4 -4
  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} +7 -16
  16. package/dist/contracts.d.ts +8 -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 +28 -14
  28. package/dist/index.js +16 -5
  29. package/dist/internal.d.ts +10 -13
  30. package/dist/internal.js +10 -13
  31. package/dist/judge/judge.d.ts +3 -3
  32. package/dist/judge/judge.js +10 -9
  33. package/dist/judge/verdict.d.ts +0 -1
  34. package/dist/judge/verdict.js +1 -1
  35. package/dist/memory/memory.d.ts +7 -2
  36. package/dist/memory/memory.js +14 -2
  37. package/dist/net/proxied-fetch.js +2 -1
  38. package/dist/pipeline/assemble-report.d.ts +22 -0
  39. package/dist/pipeline/assemble-report.js +99 -0
  40. package/dist/pipeline/check-stage.d.ts +13 -0
  41. package/dist/pipeline/check-stage.js +70 -0
  42. package/dist/pipeline/context.d.ts +2 -1
  43. package/dist/pipeline/coverage.d.ts +1 -1
  44. package/dist/pipeline/coverage.js +2 -1
  45. package/dist/pipeline/execute-stage.d.ts +26 -0
  46. package/dist/pipeline/execute-stage.js +104 -0
  47. package/dist/pipeline/execute.d.ts +2 -4
  48. package/dist/pipeline/execute.js +65 -58
  49. package/dist/pipeline/filter-stage.d.ts +16 -0
  50. package/dist/pipeline/filter-stage.js +46 -0
  51. package/dist/pipeline/findings.js +3 -2
  52. package/dist/pipeline/imports.d.ts +1 -1
  53. package/dist/pipeline/imports.js +1 -1
  54. package/dist/pipeline/matrix.d.ts +1 -7
  55. package/dist/pipeline/options.d.ts +65 -0
  56. package/dist/pipeline/options.js +9 -0
  57. package/dist/pipeline/plan-output.d.ts +90 -0
  58. package/dist/pipeline/plan-output.js +109 -0
  59. package/dist/pipeline/plan.d.ts +7 -8
  60. package/dist/pipeline/plan.js +12 -9
  61. package/dist/pipeline/preview.d.ts +24 -7
  62. package/dist/pipeline/preview.js +31 -7
  63. package/dist/pipeline/provenance.d.ts +8 -17
  64. package/dist/pipeline/provenance.js +20 -8
  65. package/dist/pipeline/run.d.ts +2 -44
  66. package/dist/pipeline/run.js +18 -273
  67. package/dist/pipeline/task.d.ts +1 -3
  68. package/dist/pipeline/task.js +4 -4
  69. package/dist/plugin/registry.d.ts +5 -4
  70. package/dist/plugin/registry.js +3 -3
  71. package/dist/plugin/types.d.ts +4 -5
  72. package/dist/{pipeline → report}/output-schema.d.ts +70 -58
  73. package/dist/{pipeline → report}/output-schema.js +60 -36
  74. package/dist/report/output.d.ts +8 -0
  75. package/dist/report/output.js +211 -0
  76. package/dist/report/provenance.d.ts +26 -0
  77. package/dist/report/provenance.js +2 -0
  78. package/dist/report/read.d.ts +3 -0
  79. package/dist/report/read.js +34 -0
  80. package/dist/{pipeline → report}/report.d.ts +3 -3
  81. package/dist/report/serialize.d.ts +3 -0
  82. package/dist/report/serialize.js +27 -0
  83. package/dist/rereview/presence.js +1 -1
  84. package/dist/rereview/quote.js +2 -2
  85. package/dist/rereview/reconcile.d.ts +1 -1
  86. package/dist/review/impact.d.ts +0 -2
  87. package/dist/review/impact.js +2 -2
  88. package/dist/review/plan-phase.d.ts +3 -3
  89. package/dist/review/plan-phase.js +4 -4
  90. package/dist/review/prompt-text.d.ts +1 -1
  91. package/dist/review/prompt-text.js +1 -1
  92. package/dist/review/prompt.d.ts +0 -3
  93. package/dist/review/prompt.js +3 -3
  94. package/dist/rules/builtin/index.d.ts +0 -4
  95. package/dist/rules/builtin/index.js +1 -2
  96. package/dist/rules/repo-rules.d.ts +4 -6
  97. package/dist/rules/repo-rules.js +3 -3
  98. package/dist/runtime/chain-runner.d.ts +19 -0
  99. package/dist/runtime/chain-runner.js +63 -0
  100. package/dist/runtime/effort-capability.d.ts +14 -0
  101. package/dist/runtime/effort-capability.js +70 -0
  102. package/dist/runtime/failback.d.ts +2 -9
  103. package/dist/runtime/failback.js +23 -8
  104. package/dist/runtime/models.d.ts +2 -0
  105. package/dist/runtime/models.js +6 -0
  106. package/dist/runtime/tools.d.ts +0 -16
  107. package/dist/runtime/tools.js +3 -3
  108. package/dist/sarif/candidates.d.ts +2 -1
  109. package/dist/sarif/schema.d.ts +2 -2
  110. package/dist/sarif/schema.js +4 -4
  111. package/dist/session/jsonl.d.ts +2 -2
  112. package/dist/session/jsonl.js +5 -4
  113. package/dist/triage.d.ts +2 -2
  114. package/dist/triage.js +1 -1
  115. package/dist/vcs.d.ts +15 -0
  116. package/dist/vcs.js +2 -0
  117. package/dist/verify/prompt.d.ts +0 -6
  118. package/dist/verify/prompt.js +1 -1
  119. package/dist/verify/verify.d.ts +4 -4
  120. package/dist/verify/verify.js +5 -5
  121. package/package.json +1 -1
  122. package/dist/pipeline/agents.js +0 -55
  123. package/dist/pipeline/helpers.d.ts +0 -6
  124. package/dist/pipeline/output.d.ts +0 -77
  125. package/dist/pipeline/output.js +0 -132
  126. /package/dist/{pipeline → agent}/budget.d.ts +0 -0
  127. /package/dist/{pipeline → agent}/budget.js +0 -0
  128. /package/dist/{pipeline → agent}/pool.d.ts +0 -0
  129. /package/dist/{pipeline → agent}/usage.d.ts +0 -0
  130. /package/dist/{pipeline → agent}/usage.js +0 -0
  131. /package/dist/{pipeline → report}/report.js +0 -0
@@ -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;
@@ -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 { reviewerEffort } 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
- const effort = reviewerEffort(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, effort);
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
- }
23
+ const call = reviewerCall(job.reviewer, options.agents ?? {});
24
+ const prepared = await preparePrompt(job, plan, options, call);
55
25
  emit({
56
26
  type: "task_started",
57
27
  taskId: job.taskId,
@@ -64,9 +34,9 @@ export async function runJob(job, plan, options) {
64
34
  taskId: job.taskId,
65
35
  reviewer: job.reviewer.id,
66
36
  modelTier: job.reviewer.modelTier,
67
- ...(effort === undefined ? {} : { effort }),
68
- systemPrompt: prompt.system,
69
- userPrompt: prompt.user,
37
+ ...call,
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,15 +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
- }
12
- export type ReviewerOverrides = Readonly<Record<string, ReviewerOverride>>;
13
7
  export interface MatrixCell {
14
8
  taskId: string;
15
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