pi-workflow-engine 0.12.0 → 1.0.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/.pi/extensions/pi-workflow-engine/index.ts +106 -391
- package/.pi/extensions/pi-workflow-engine/src/advisory-challenge.ts +75 -0
- package/.pi/extensions/pi-workflow-engine/src/advisory-evidence.ts +90 -0
- package/.pi/extensions/pi-workflow-engine/src/advisory-schema.ts +20 -2
- package/.pi/extensions/pi-workflow-engine/src/agent-attempt.ts +48 -62
- package/.pi/extensions/pi-workflow-engine/src/agent-replay.ts +46 -67
- package/.pi/extensions/pi-workflow-engine/src/agent-retry.ts +14 -37
- package/.pi/extensions/pi-workflow-engine/src/agent-runner-types.ts +5 -11
- package/.pi/extensions/pi-workflow-engine/src/agent-runner.ts +15 -21
- package/.pi/extensions/pi-workflow-engine/src/agent-session-identity.ts +13 -43
- package/.pi/extensions/pi-workflow-engine/src/agent-session-providers.ts +17 -5
- package/.pi/extensions/pi-workflow-engine/src/agent-session.ts +93 -100
- package/.pi/extensions/pi-workflow-engine/src/agent-skills.ts +11 -43
- package/.pi/extensions/pi-workflow-engine/src/agent-workspace.ts +18 -18
- package/.pi/extensions/pi-workflow-engine/src/background-workflows.ts +33 -86
- package/.pi/extensions/pi-workflow-engine/src/cancellation.ts +1 -5
- package/.pi/extensions/pi-workflow-engine/src/concurrency.ts +37 -86
- package/.pi/extensions/pi-workflow-engine/src/discovery.ts +16 -23
- package/.pi/extensions/pi-workflow-engine/src/dynamax-shortcuts.ts +1 -4
- package/.pi/extensions/pi-workflow-engine/src/dynamax.ts +39 -98
- package/.pi/extensions/pi-workflow-engine/src/engine.ts +37 -80
- package/.pi/extensions/pi-workflow-engine/src/finalizers.ts +2 -10
- package/.pi/extensions/pi-workflow-engine/src/git.ts +55 -0
- package/.pi/extensions/pi-workflow-engine/src/guards.ts +8 -0
- package/.pi/extensions/pi-workflow-engine/src/identity-canonicalization.ts +7 -19
- package/.pi/extensions/pi-workflow-engine/src/inline-workflow.ts +171 -106
- package/.pi/extensions/pi-workflow-engine/src/journal.ts +1 -12
- package/.pi/extensions/pi-workflow-engine/src/model-profile-command.ts +37 -8
- package/.pi/extensions/pi-workflow-engine/src/model-profiles.ts +15 -66
- package/.pi/extensions/pi-workflow-engine/src/options.ts +97 -16
- package/.pi/extensions/pi-workflow-engine/src/perf.ts +43 -72
- package/.pi/extensions/pi-workflow-engine/src/process-runner.ts +14 -4
- package/.pi/extensions/pi-workflow-engine/src/progress-types.ts +2 -0
- package/.pi/extensions/pi-workflow-engine/src/progress.ts +79 -80
- package/.pi/extensions/pi-workflow-engine/src/provider-usage-limit.ts +28 -20
- package/.pi/extensions/pi-workflow-engine/src/replay-path-identity.ts +2 -7
- package/.pi/extensions/pi-workflow-engine/src/research-contract.ts +0 -1
- package/.pi/extensions/pi-workflow-engine/src/research-evidence.ts +27 -31
- package/.pi/extensions/pi-workflow-engine/src/resume-context.ts +128 -142
- package/.pi/extensions/pi-workflow-engine/src/review/github-pr-comments.ts +14 -31
- package/.pi/extensions/pi-workflow-engine/src/review/patch-validation.ts +119 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-actions.ts +1 -1
- package/.pi/extensions/pi-workflow-engine/src/review/review-diff-lines.ts +54 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-fix-workflow.ts +41 -7
- package/.pi/extensions/pi-workflow-engine/src/review/review-format.ts +51 -116
- package/.pi/extensions/pi-workflow-engine/src/review/review-handoff.ts +1 -1
- package/.pi/extensions/pi-workflow-engine/src/review/review-issues.ts +9 -14
- package/.pi/extensions/pi-workflow-engine/src/review/review-report.ts +3 -7
- package/.pi/extensions/pi-workflow-engine/src/review/review-results-viewer.ts +158 -241
- package/.pi/extensions/pi-workflow-engine/src/review/review-session-coordinator.ts +28 -38
- package/.pi/extensions/pi-workflow-engine/src/review/review-snapshot.ts +35 -44
- package/.pi/extensions/pi-workflow-engine/src/review-diff-target.ts +13 -14
- package/.pi/extensions/pi-workflow-engine/src/text.ts +29 -0
- package/.pi/extensions/pi-workflow-engine/src/tool-capabilities.ts +2 -1
- package/.pi/extensions/pi-workflow-engine/src/tool-source-identity.ts +16 -28
- package/.pi/extensions/pi-workflow-engine/src/tree-fingerprint.ts +34 -14
- package/.pi/extensions/pi-workflow-engine/src/types.ts +9 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/kit.ts +193 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/research-result-renderer.ts +177 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/result-layout.ts +209 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-format.ts +17 -44
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-inspector.ts +703 -235
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-picker.ts +48 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-result-renderer.ts +239 -126
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-runs-browser.ts +461 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-tool-renderer.ts +91 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-widget.ts +316 -98
- package/.pi/extensions/pi-workflow-engine/src/usage.ts +21 -32
- package/.pi/extensions/pi-workflow-engine/src/workflow-advisory-utils.ts +178 -188
- package/.pi/extensions/pi-workflow-engine/src/workflow-execution.ts +19 -24
- package/.pi/extensions/pi-workflow-engine/src/workflow-module.ts +17 -37
- package/.pi/extensions/pi-workflow-engine/src/workflow-result-context.ts +79 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-background.ts +4 -14
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-controller.ts +77 -63
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-history.ts +42 -29
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-record.ts +15 -14
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-store.ts +27 -31
- package/.pi/extensions/pi-workflow-engine/src/workflow-usage-limit-scheduler.ts +1 -5
- package/.pi/extensions/pi-workflow-engine/src/workflows.ts +3 -6
- package/.pi/extensions/pi-workflow-engine/src/worktree.ts +55 -110
- package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +101 -203
- package/.pi/extensions/pi-workflow-engine/workflows/diagnose.ts +51 -187
- package/.pi/extensions/pi-workflow-engine/workflows/perf-review.ts +33 -109
- package/.pi/extensions/pi-workflow-engine/workflows/refactor-scout.ts +36 -106
- package/.pi/extensions/pi-workflow-engine/workflows/research.ts +74 -66
- package/USAGE.md +90 -17
- package/package.json +1 -2
- package/skills/workflow-code-review-actions/SKILL.md +12 -23
- package/.pi/extensions/pi-workflow-engine/src/diff-capture.ts +0 -81
- package/.pi/extensions/pi-workflow-engine/src/review/code-review-orchestration.ts +0 -49
- package/.pi/extensions/pi-workflow-engine/src/review/review-results-flow.ts +0 -52
- package/.pi/settings.json +0 -3
|
@@ -1,34 +1,29 @@
|
|
|
1
|
+
import { challengeFindings, parseChallengeArgs } from "../src/advisory-challenge.ts";
|
|
1
2
|
import { Type } from "typebox";
|
|
2
3
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from "../src/advisory-schema.ts";
|
|
9
|
-
import {
|
|
10
|
-
backfillAdvisoryFindings,
|
|
4
|
+
type AdvisoryVerified,
|
|
5
|
+
type AdvisoryLens,
|
|
6
|
+
concludeLensReview,
|
|
7
|
+
emptyAdvisoryReport,
|
|
8
|
+
finishAdvisoryReport,
|
|
11
9
|
formatEvidence,
|
|
12
10
|
formatLocation,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
publishVerifiedKeptProgress,
|
|
16
|
-
recordVerdictProgress,
|
|
11
|
+
runLensVerificationPipeline,
|
|
12
|
+
EMPTY_LENS_REVIEW_STATS,
|
|
17
13
|
verdictConfidence,
|
|
18
14
|
DEFAULT_ADVISORY_TOOL_HINTS,
|
|
19
15
|
DEFAULT_ADVISORY_TOOLS,
|
|
20
16
|
} from "../src/workflow-advisory-utils.ts";
|
|
21
|
-
import { compactResults } from "../src/concurrency.ts";
|
|
22
17
|
import { formatReviewDiffTarget, parseAllowedDiffCommand } from "../src/review-diff-target.ts";
|
|
23
|
-
import {
|
|
18
|
+
import { changedLines, diffAnchor } from "../src/review/review-diff-lines.ts";
|
|
24
19
|
import type { ReviewContext } from "../src/review/review-report.ts";
|
|
25
|
-
import { captureReviewMaterial
|
|
26
|
-
import type { WorkflowApi, WorkflowMeta
|
|
20
|
+
import { captureReviewMaterial } from "../src/review/review-snapshot.ts";
|
|
21
|
+
import type { WorkflowApi, WorkflowMeta } from "../src/types.ts";
|
|
27
22
|
|
|
28
23
|
export const meta: WorkflowMeta = {
|
|
29
24
|
name: "code-review",
|
|
30
25
|
description: "Fan-out review of the branch's open PR (or branch vs main): scope → per-angle find → independent verify → synthesize.",
|
|
31
|
-
phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Synthesize" }],
|
|
26
|
+
phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Challenge" }, { title: "Synthesize" }],
|
|
32
27
|
};
|
|
33
28
|
|
|
34
29
|
// ─── Schemas (the contracts that make orchestration plain code) ───
|
|
@@ -39,85 +34,50 @@ const ScopeSchema = Type.Object({
|
|
|
39
34
|
conventions: Type.Optional(Type.String({ description: "Relevant AGENTS.md / project conventions" })),
|
|
40
35
|
});
|
|
41
36
|
|
|
42
|
-
type Candidate = AdvisoryCandidate;
|
|
43
|
-
|
|
44
|
-
interface Angle {
|
|
45
|
-
label: string;
|
|
46
|
-
kind: "bug" | "cleanup";
|
|
47
|
-
text: string;
|
|
48
|
-
}
|
|
49
|
-
interface Verified extends Candidate {
|
|
50
|
-
verdict: AdvisoryVerdict["verdict"];
|
|
51
|
-
evidence: string[];
|
|
52
|
-
kind: "bug" | "cleanup";
|
|
53
|
-
}
|
|
54
|
-
|
|
55
37
|
// The review lenses — this is the part you customise to your codebase's real failure modes.
|
|
56
|
-
const ANGLES:
|
|
57
|
-
{ label: "logic-bugs",
|
|
58
|
-
{ label: "error-paths",
|
|
59
|
-
{ label: "edge-cases",
|
|
60
|
-
{ label: "simplification",
|
|
61
|
-
{ label: "conventions",
|
|
38
|
+
const ANGLES: AdvisoryLens[] = [
|
|
39
|
+
{ label: "logic-bugs", category: "bug", text: "Off-by-one errors, wrong conditionals, incorrect return values, broken control flow." },
|
|
40
|
+
{ label: "error-paths", category: "bug", text: "Unhandled errors, swallowed exceptions, missing awaits, partial failure leaving inconsistent state." },
|
|
41
|
+
{ label: "edge-cases", category: "bug", text: "Empty/null inputs, boundary values, concurrency races, resource leaks." },
|
|
42
|
+
{ label: "simplification", category: "cleanup", text: "Dead code, needless complexity, duplicated logic, clearer equivalents." },
|
|
43
|
+
{ label: "conventions", category: "cleanup", text: "Violations of the project conventions noted in scope (naming, idioms, banned patterns)." },
|
|
62
44
|
];
|
|
63
45
|
|
|
64
|
-
const TOOLS = DEFAULT_ADVISORY_TOOLS;
|
|
65
|
-
const TOOL_HINTS = DEFAULT_ADVISORY_TOOL_HINTS;
|
|
66
46
|
const PER_ANGLE = 6;
|
|
67
47
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
/** Is a finding inside the diff? File-level findings count if the file changed; ±1 line of fuzz. */
|
|
93
|
-
export function inDiff(changed: Map<string, Set<number>>, file: string, line?: number): boolean {
|
|
94
|
-
const set = changed.get(normalizePath(file));
|
|
95
|
-
if (!set) return false;
|
|
96
|
-
if (line == null) return true;
|
|
97
|
-
return set.has(line) || set.has(line - 1) || set.has(line + 1);
|
|
48
|
+
const DIFF_EMBED_CAP = 60_000;
|
|
49
|
+
|
|
50
|
+
export function buildCodeReviewScopeBlock(input: {
|
|
51
|
+
readonly diffCommand: string;
|
|
52
|
+
readonly files: readonly string[];
|
|
53
|
+
readonly summary: string;
|
|
54
|
+
readonly conventions?: string;
|
|
55
|
+
readonly diffText: string;
|
|
56
|
+
readonly target: string;
|
|
57
|
+
}): string {
|
|
58
|
+
const diffBlock = input.diffText
|
|
59
|
+
? `\n## Diff (review is bounded to these changed lines)\n\`\`\`diff\n${
|
|
60
|
+
input.diffText.length > DIFF_EMBED_CAP
|
|
61
|
+
? `${input.diffText.slice(0, DIFF_EMBED_CAP)}\n... (truncated — run \`${input.diffCommand}\` for the full diff)`
|
|
62
|
+
: input.diffText
|
|
63
|
+
}\n\`\`\`\n`
|
|
64
|
+
: "";
|
|
65
|
+
return (
|
|
66
|
+
`## Diff command\n${input.diffCommand}\n\n## Changed files\n${input.files.map((file) => `- ${file}`).join("\n")}\n\n` +
|
|
67
|
+
`## Summary\n${input.summary}\n\n## Conventions\n${input.conventions ?? "(none noted)"}\n` +
|
|
68
|
+
diffBlock +
|
|
69
|
+
(input.target ? `\n## User instructions (verbatim)\n${input.target}\n` : "")
|
|
70
|
+
);
|
|
98
71
|
}
|
|
99
72
|
|
|
100
73
|
export interface CodeReviewDependencies {
|
|
101
|
-
readonly captureReviewMaterial?:
|
|
102
|
-
target: Parameters<typeof captureReviewMaterial>[0],
|
|
103
|
-
cwd: string,
|
|
104
|
-
signal?: AbortSignal,
|
|
105
|
-
) => Promise<ReviewMaterialCaptureResult>;
|
|
74
|
+
readonly captureReviewMaterial?: typeof captureReviewMaterial;
|
|
106
75
|
}
|
|
107
76
|
|
|
108
77
|
export default async function run(api: WorkflowApi, dependencies: CodeReviewDependencies = {}): Promise<unknown> {
|
|
109
|
-
const { agent,
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
let rawCandidateCount = 0;
|
|
113
|
-
let droppedCandidateCount = 0;
|
|
114
|
-
const makeStats = (verified: number, kept: number): WorkflowRunStats => ({
|
|
115
|
-
files: fileCount,
|
|
116
|
-
candidates: rawCandidateCount,
|
|
117
|
-
verified,
|
|
118
|
-
kept,
|
|
119
|
-
dropped: droppedCandidateCount,
|
|
120
|
-
});
|
|
78
|
+
const { agent, phase, log, progress, args, cwd, signal } = api;
|
|
79
|
+
const challengeConfig = parseChallengeArgs(args);
|
|
80
|
+
const target = challengeConfig.args;
|
|
121
81
|
|
|
122
82
|
// ─── Phase 0: Scope ───
|
|
123
83
|
phase("Scope");
|
|
@@ -138,14 +98,9 @@ export default async function run(api: WorkflowApi, dependencies: CodeReviewDepe
|
|
|
138
98
|
"Then: list the changed files, summarize the change in one paragraph (mention the PR if one was found), " +
|
|
139
99
|
"and read any relevant AGENTS.md or project docs noting conventions a reviewer should know.\n" +
|
|
140
100
|
"Return diffCommand exactly as a reviewer should run it. Structured output only.",
|
|
141
|
-
{ phase: "Scope", label: "scope", tools:
|
|
101
|
+
{ phase: "Scope", label: "scope", tools: DEFAULT_ADVISORY_TOOLS, toolHints: DEFAULT_ADVISORY_TOOL_HINTS, profile: "medium", schema: ScopeSchema },
|
|
142
102
|
);
|
|
143
103
|
|
|
144
|
-
if (!scope) {
|
|
145
|
-
return { summary: "No changes found to review.", findings: [], nextSteps: ["Provide a PR, ref range, or changed files to review."], stats: makeStats(0, 0) };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
fileCount = scope.files.length;
|
|
149
104
|
progress({ type: "summary", key: "files", value: scope.files.join(", ") || "(none)" });
|
|
150
105
|
const diffTarget = parseAllowedDiffCommand(scope.diffCommand);
|
|
151
106
|
if ("error" in diffTarget) {
|
|
@@ -153,11 +108,11 @@ export default async function run(api: WorkflowApi, dependencies: CodeReviewDepe
|
|
|
153
108
|
}
|
|
154
109
|
const diffCommand = formatReviewDiffTarget(diffTarget);
|
|
155
110
|
progress({ type: "summary", key: "diffCommand", value: diffCommand });
|
|
156
|
-
progress({ type: "counter", key: "files", label: "files", value:
|
|
111
|
+
progress({ type: "counter", key: "files", label: "files", value: scope.files.length });
|
|
112
|
+
const noChanges = (summary: string, nextSteps = ["Provide a PR, ref range, or changed files to review."]) =>
|
|
113
|
+
finishAdvisoryReport(emptyAdvisoryReport(summary, nextSteps, { ...EMPTY_LENS_REVIEW_STATS, files: scope.files.length }), []);
|
|
157
114
|
|
|
158
|
-
if (scope.files.length === 0)
|
|
159
|
-
return { summary: "No changes found to review.", findings: [], nextSteps: ["Provide a PR, ref range, or changed files to review."], stats: makeStats(0, 0) };
|
|
160
|
-
}
|
|
115
|
+
if (scope.files.length === 0) return noChanges("No changes found to review.");
|
|
161
116
|
|
|
162
117
|
log(`${scope.files.length} changed files`);
|
|
163
118
|
|
|
@@ -169,6 +124,14 @@ export default async function run(api: WorkflowApi, dependencies: CodeReviewDepe
|
|
|
169
124
|
const diffText = reviewMaterial.diff;
|
|
170
125
|
const changed = changedLines(diffText);
|
|
171
126
|
progress({ type: "summary", key: "diffBytes", value: Buffer.byteLength(diffText) });
|
|
127
|
+
// Findings anchor only on files with a new-side text hunk; without one, skip the finder fan-out.
|
|
128
|
+
if (changed.size === 0) {
|
|
129
|
+
if (!diffText.trim()) return noChanges(`No changes found to review: \`${diffCommand}\` has no added or modified files.`);
|
|
130
|
+
return noChanges(
|
|
131
|
+
`Nothing to review line by line: \`${diffCommand}\` only deletes or renames files, changes file modes, or touches binary files; there are no added lines to anchor findings on.`,
|
|
132
|
+
["Inspect the deleted, renamed, mode-changed or binary files directly; code-review anchors findings on added lines only."],
|
|
133
|
+
);
|
|
134
|
+
}
|
|
172
135
|
if (reviewMaterial.snapshot.status === "unavailable") {
|
|
173
136
|
log(`review snapshot unavailable (${reviewMaterial.snapshot.reason}) — patch previews will be unavailable`);
|
|
174
137
|
}
|
|
@@ -184,7 +147,7 @@ export default async function run(api: WorkflowApi, dependencies: CodeReviewDepe
|
|
|
184
147
|
: {}),
|
|
185
148
|
};
|
|
186
149
|
|
|
187
|
-
const scopeBlock = buildCodeReviewScopeBlock({
|
|
150
|
+
const scopeBlock = `Reviewed snapshot identity: ${JSON.stringify(reviewContext.snapshot ?? "unavailable")}\n` + buildCodeReviewScopeBlock({
|
|
188
151
|
diffCommand,
|
|
189
152
|
files: scope.files,
|
|
190
153
|
summary: scope.summary,
|
|
@@ -193,113 +156,48 @@ export default async function run(api: WorkflowApi, dependencies: CodeReviewDepe
|
|
|
193
156
|
target,
|
|
194
157
|
});
|
|
195
158
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
`Surface up to ${PER_ANGLE} candidates. Use category exactly "${angle.kind}". Each candidate must include a one-line summary, ` +
|
|
206
|
-
"locations with the changed file and a line that appears in the diff, and impact describing the concrete failure or maintenance scenario. " +
|
|
207
|
-
"Pass through anything with a nameable impact — a separate verifier judges them next. Structured output only.",
|
|
208
|
-
{ phase: "Find", label: `find:${angle.label}`, tools: TOOLS, toolHints: TOOL_HINTS, profile: "small", schema: AdvisoryCandidatesSchema },
|
|
209
|
-
);
|
|
210
|
-
const raw = (found?.candidates ?? []).slice(0, PER_ANGLE);
|
|
211
|
-
rawCandidateCount += raw.length;
|
|
212
|
-
progress({ type: "counter_delta", key: "candidates", label: "candidates", delta: raw.length });
|
|
213
|
-
const bounded = raw.filter((candidate) => inDiff(changed, primaryLocation(candidate).file, primaryLocation(candidate).line));
|
|
214
|
-
const dropped = raw.length - bounded.length;
|
|
215
|
-
if (dropped > 0) {
|
|
216
|
-
droppedCandidateCount += dropped;
|
|
217
|
-
progress({ type: "counter_delta", key: "dropped", label: "dropped", delta: dropped });
|
|
218
|
-
log(`find:${angle.label}: dropped ${dropped} out-of-diff candidate(s)`);
|
|
219
|
-
}
|
|
220
|
-
for (const candidate of bounded) {
|
|
221
|
-
progress({
|
|
222
|
-
type: "lane_item",
|
|
223
|
-
lane: "Candidates",
|
|
224
|
-
title: candidate.summary,
|
|
225
|
-
subtitle: formatLocation(candidate),
|
|
226
|
-
status: "pending",
|
|
227
|
-
details: candidate.impact,
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
return { angle, candidates: bounded };
|
|
231
|
-
}),
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
// Dedup after all finders complete so verifier agents cannot consume the global cap before full candidate discovery.
|
|
235
|
-
const novel = dedupeCodeReviewCandidates(compactResults(perAngle));
|
|
236
|
-
|
|
237
|
-
phase("Verify");
|
|
238
|
-
const verdicts = await parallel(
|
|
239
|
-
novel.map(({ angle, candidate }) => async (): Promise<Verified | null> => {
|
|
240
|
-
const location = primaryLocation(candidate);
|
|
241
|
-
const judged = await agent(
|
|
242
|
-
`## Code-review verifier\n\n${scopeBlock}\n## Candidate\n` +
|
|
243
|
-
`Location: ${formatLocation(candidate)}\n` +
|
|
244
|
-
`Category: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n\n` +
|
|
245
|
-
"Run the diff command, read the relevant file(s), and return exactly one verdict (CONFIRMED / PLAUSIBLE / REFUTED) " +
|
|
246
|
-
"with evidence quoting the line(s). Default toward REFUTED if you cannot substantiate it. Structured output only.",
|
|
247
|
-
{
|
|
248
|
-
phase: "Verify",
|
|
249
|
-
label: `verify:${location.file.split("/").pop() ?? location.file}`,
|
|
250
|
-
tools: TOOLS,
|
|
251
|
-
toolHints: TOOL_HINTS,
|
|
252
|
-
profile: "small",
|
|
253
|
-
schema: AdvisoryVerdictSchema,
|
|
254
|
-
},
|
|
255
|
-
);
|
|
256
|
-
if (!judged) return null;
|
|
257
|
-
recordVerdictProgress(progress, candidate, judged);
|
|
258
|
-
return { ...candidate, verdict: judged.verdict, evidence: judged.evidence, kind: angle.kind };
|
|
259
|
-
}),
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
const verified = compactResults(verdicts);
|
|
263
|
-
const surviving = verified.filter((finding) => finding.verdict !== "REFUTED");
|
|
264
|
-
const stats = makeStats(verified.length, surviving.length);
|
|
265
|
-
publishVerifiedKeptProgress({ progress, log }, verified.length, surviving.length);
|
|
266
|
-
|
|
267
|
-
if (surviving.length === 0) {
|
|
268
|
-
return { summary: "No findings survived verification.", findings: [], nextSteps: ["No code-review action is recommended from this workflow run."], stats, reviewContext };
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// ─── Synthesize: rank, merge, report ───
|
|
272
|
-
phase("Synthesize");
|
|
273
|
-
const rank = (finding: Verified): number => (finding.kind === "cleanup" ? 2 : 0) + (finding.verdict === "PLAUSIBLE" ? 1 : 0);
|
|
274
|
-
const ranked = [...surviving].sort((a, b) => rank(a) - rank(b));
|
|
275
|
-
const block = ranked
|
|
276
|
-
.map(
|
|
277
|
-
(finding, index) =>
|
|
278
|
-
`### [${index}] ${formatLocation(finding)} (${finding.verdict}${finding.kind === "cleanup" ? ", cleanup" : ""})\n` +
|
|
279
|
-
`Category: ${finding.kind}\nConfidence: ${verdictConfidence(finding.verdict)}\n` +
|
|
280
|
-
`${finding.summary}\nImpact: ${finding.impact}\nEvidence: ${formatEvidence(finding.evidence)}`,
|
|
281
|
-
)
|
|
282
|
-
.join("\n\n");
|
|
283
|
-
|
|
284
|
-
const report = await agent(
|
|
285
|
-
`## Synthesis: final code-review report\n\n${ranked.length} findings survived independent verification.\n\n${block}\n\n` +
|
|
286
|
-
"Merge findings with the same root cause, rank most-severe first (correctness bugs above cleanups), and produce the final advisory report. " +
|
|
287
|
-
"Return summary, findings, and nextSteps. For each finding: category must be bug or cleanup; severity is impact level (low/medium/high), not category; " +
|
|
288
|
-
"confidence must be high for CONFIRMED and medium for PLAUSIBLE; copy locations and evidence arrays from the verified finding; impact is the concrete failure or maintenance scenario; recommendation is an advisory fix direction, not an edit. Structured output only.",
|
|
289
|
-
{
|
|
290
|
-
phase: "Synthesize",
|
|
291
|
-
label: "synthesize",
|
|
292
|
-
tools: [],
|
|
293
|
-
profile: "medium",
|
|
294
|
-
resume: "read-only",
|
|
295
|
-
schema: AdvisoryReportSchema,
|
|
159
|
+
const pipelineResult = await runLensVerificationPipeline({
|
|
160
|
+
api,
|
|
161
|
+
lenses: ANGLES,
|
|
162
|
+
perLens: PER_ANGLE,
|
|
163
|
+
boundCandidate: (candidate, lens) => {
|
|
164
|
+
const reviewAnchor = candidate.reviewAnchor
|
|
165
|
+
? diffAnchor(changed, candidate.reviewAnchor)
|
|
166
|
+
: candidate.locations.map((location) => diffAnchor(changed, location)).find((anchor) => anchor !== undefined);
|
|
167
|
+
return reviewAnchor ? { ...candidate, category: lens.category, reviewAnchor } : undefined;
|
|
296
168
|
},
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
169
|
+
finderPrompt: (lens) =>
|
|
170
|
+
`## Code-review finder — ${lens.label}\n\n${scopeBlock}\n` +
|
|
171
|
+
`Review the change through ONLY this lens:\n${lens.text}\n` +
|
|
172
|
+
"Only flag issues on lines that are part of the diff above (run the diff command if it is not shown). " +
|
|
173
|
+
"Set reviewAnchor to the changed line causing the issue; locations and discoveryEvidence may include unchanged callers and other files. " +
|
|
174
|
+
`Surface up to ${PER_ANGLE} candidates. Use category exactly "${lens.category}". Each candidate must include a one-line summary, ` +
|
|
175
|
+
"locations with the changed file and a line that appears in the diff, and impact describing the concrete failure or maintenance scenario. " +
|
|
176
|
+
"Pass through anything with a nameable impact — a separate verifier judges them next. Structured output only.",
|
|
177
|
+
verifierPrompt: (candidate) =>
|
|
178
|
+
`## Code-review verifier\n\n${scopeBlock}\n## Candidate\n` +
|
|
179
|
+
`Location: ${formatLocation(candidate)}\n` +
|
|
180
|
+
`Category: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n\n` +
|
|
181
|
+
"Run the diff command, read the relevant file(s), and return exactly one verdict (CONFIRMED / PLAUSIBLE / NOT_SUBSTANTIATED / REFUTED) " +
|
|
182
|
+
"with evidence quoting the line(s). Use NOT_SUBSTANTIATED when evidence is insufficient; use REFUTED only for concrete disproof. Structured output only.",
|
|
303
183
|
});
|
|
304
|
-
|
|
184
|
+
const verified = await challengeFindings(api, pipelineResult.verified, scopeBlock, challengeConfig.options, pipelineResult.coverage);
|
|
185
|
+
const report = await concludeLensReview(api, pipelineResult, verified, {
|
|
186
|
+
files: scope.files.length,
|
|
187
|
+
rank,
|
|
188
|
+
formatFinding: (finding, index) =>
|
|
189
|
+
`### [${index}] IDs: ${finding.sourceCandidateIds.join(", ")} ${formatLocation(finding)} (${finding.verdict}${finding.category === "cleanup" ? ", cleanup" : ""})\n` +
|
|
190
|
+
`Category: ${finding.category}\nConfidence: ${verdictConfidence(finding.verdict)}\n` +
|
|
191
|
+
`${finding.summary}\nImpact: ${finding.impact}\nEvidence: ${formatEvidence(finding.evidence)}`,
|
|
192
|
+
empty: { summary: "No findings survived verification.", nextSteps: ["No code-review action is recommended from this workflow run."] },
|
|
193
|
+
synthesisPrompt: (block, ranked) =>
|
|
194
|
+
`## Synthesis: final code-review report\n\n${ranked.length} findings survived independent verification.\n\n${block}\n\n` +
|
|
195
|
+
"Merge findings with the same root cause, rank most-severe first (correctness bugs above cleanups), and produce the final advisory report. " +
|
|
196
|
+
"Return summary, ID selections with severity (low/medium/high) and advisory recommendation, and nextSteps. Evidence and confidence are reconstructed from verified records. Structured output only.",
|
|
197
|
+
});
|
|
198
|
+
return { ...report, reviewContext };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function rank(finding: AdvisoryVerified): number {
|
|
202
|
+
return (finding.category === "cleanup" ? 2 : 0) + (finding.verdict !== "CONFIRMED" ? 1 : 0);
|
|
305
203
|
}
|