@jameslovespancakes/pi-plus 1.0.11 → 1.0.13
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/README.md +67 -22
- package/config/skills/workflow-code-review-actions/SKILL.md +44 -0
- package/package.json +9 -4
- package/src/core/accounts/oauth-pool.ts +137 -0
- package/src/core/accounts/registry.ts +12 -25
- package/src/core/accounts/routing.ts +144 -0
- package/src/core/anthropic/client-identity.ts +9 -64
- package/src/core/anthropic/identity.ts +54 -0
- package/src/core/anthropic/oauth.ts +3 -0
- package/src/core/anthropic/quota.ts +232 -78
- package/src/core/anthropic/routing.ts +40 -115
- package/src/core/anthropic/store.ts +11 -10
- package/src/core/catalog/quality.ts +33 -15
- package/src/core/codex/quota.ts +46 -8
- package/src/core/codex/store.ts +29 -16
- package/src/core/config.ts +16 -17
- package/src/core/store.ts +13 -12
- package/src/domains/agents/format.ts +97 -0
- package/src/domains/agents/index.ts +58 -47
- package/src/domains/compact/archive.ts +140 -0
- package/src/domains/compact/chunking.ts +166 -0
- package/src/domains/compact/index.ts +452 -0
- package/src/domains/compact/jev.ts +242 -0
- package/src/domains/compact/policy.ts +255 -0
- package/src/domains/compact/types.ts +79 -0
- package/src/domains/models/provider-picker.ts +1 -1
- package/src/domains/setup/index.ts +23 -23
- package/src/domains/subscriptions/accounts-picker.ts +61 -40
- package/src/domains/subscriptions/accounts.ts +53 -36
- package/src/domains/subscriptions/footer.ts +48 -29
- package/src/domains/subscriptions/index.ts +11 -27
- package/src/domains/subscriptions/provider.ts +79 -103
- package/src/domains/subscriptions/providers/anthropic.ts +42 -22
- package/src/domains/subscriptions/providers/codex.ts +146 -113
- package/src/domains/subscriptions/providers/hosted.ts +18 -0
- package/src/domains/subscriptions/providers/oauth-pool.ts +325 -0
- package/src/domains/subscriptions/routing.ts +19 -11
- package/src/domains/workflows/LICENSE.md +21 -0
- package/src/domains/workflows/index.ts +836 -0
- package/src/domains/workflows/runtime/advisory-challenge.ts +75 -0
- package/src/domains/workflows/runtime/advisory-evidence.ts +90 -0
- package/src/domains/workflows/runtime/advisory-schema.ts +83 -0
- package/src/domains/workflows/runtime/agent-attempt.ts +177 -0
- package/src/domains/workflows/runtime/agent-failure.ts +14 -0
- package/src/domains/workflows/runtime/agent-limits.ts +66 -0
- package/src/domains/workflows/runtime/agent-replay.ts +399 -0
- package/src/domains/workflows/runtime/agent-retry.ts +116 -0
- package/src/domains/workflows/runtime/agent-runner-types.ts +96 -0
- package/src/domains/workflows/runtime/agent-runner.ts +225 -0
- package/src/domains/workflows/runtime/agent-session-identity.ts +256 -0
- package/src/domains/workflows/runtime/agent-session-providers.ts +50 -0
- package/src/domains/workflows/runtime/agent-session.ts +382 -0
- package/src/domains/workflows/runtime/agent-skills.ts +270 -0
- package/src/domains/workflows/runtime/agent-workspace.ts +79 -0
- package/src/domains/workflows/runtime/background-workflow-tool.ts +75 -0
- package/src/domains/workflows/runtime/background-workflows.ts +492 -0
- package/src/domains/workflows/runtime/budget.ts +53 -0
- package/src/domains/workflows/runtime/cancellation.ts +87 -0
- package/src/domains/workflows/runtime/command-completions.ts +36 -0
- package/src/domains/workflows/runtime/concurrency.ts +403 -0
- package/src/domains/workflows/runtime/debug.ts +3 -0
- package/src/domains/workflows/runtime/diff-capture.ts +81 -0
- package/src/domains/workflows/runtime/discovery.ts +137 -0
- package/src/domains/workflows/runtime/dynamax-shortcuts.ts +122 -0
- package/src/domains/workflows/runtime/dynamax.ts +330 -0
- package/src/domains/workflows/runtime/engine.ts +579 -0
- package/src/domains/workflows/runtime/filesystem-error.ts +4 -0
- package/src/domains/workflows/runtime/finalizers.ts +66 -0
- package/src/domains/workflows/runtime/identity-canonicalization.ts +138 -0
- package/src/domains/workflows/runtime/identity-fingerprint.ts +15 -0
- package/src/domains/workflows/runtime/inline-workflow.ts +403 -0
- package/src/domains/workflows/runtime/journal.ts +313 -0
- package/src/domains/workflows/runtime/model-profiles.ts +310 -0
- package/src/domains/workflows/runtime/options.ts +157 -0
- package/src/domains/workflows/runtime/perf.ts +146 -0
- package/src/domains/workflows/runtime/pi-compat.ts +32 -0
- package/src/domains/workflows/runtime/process-runner.ts +260 -0
- package/src/domains/workflows/runtime/progress-types.ts +59 -0
- package/src/domains/workflows/runtime/progress.ts +400 -0
- package/src/domains/workflows/runtime/provider-usage-limit.ts +191 -0
- package/src/domains/workflows/runtime/replay-path-identity.ts +48 -0
- package/src/domains/workflows/runtime/research-contract.ts +89 -0
- package/src/domains/workflows/runtime/research-evidence.ts +289 -0
- package/src/domains/workflows/runtime/resume-context.ts +751 -0
- package/src/domains/workflows/runtime/review/code-review-orchestration.ts +24 -0
- package/src/domains/workflows/runtime/review/github-pr-comments.ts +249 -0
- package/src/domains/workflows/runtime/review/patch-validation.ts +120 -0
- package/src/domains/workflows/runtime/review/review-actions.ts +180 -0
- package/src/domains/workflows/runtime/review/review-budget.ts +45 -0
- package/src/domains/workflows/runtime/review/review-fix-workflow.ts +153 -0
- package/src/domains/workflows/runtime/review/review-format.ts +133 -0
- package/src/domains/workflows/runtime/review/review-handoff.ts +38 -0
- package/src/domains/workflows/runtime/review/review-issues.ts +87 -0
- package/src/domains/workflows/runtime/review/review-report.ts +42 -0
- package/src/domains/workflows/runtime/review/review-results-flow.ts +52 -0
- package/src/domains/workflows/runtime/review/review-results-viewer.ts +312 -0
- package/src/domains/workflows/runtime/review/review-session-coordinator.ts +153 -0
- package/src/domains/workflows/runtime/review/review-snapshot.ts +294 -0
- package/src/domains/workflows/runtime/review-diff-target.ts +130 -0
- package/src/domains/workflows/runtime/session-identity.ts +6 -0
- package/src/domains/workflows/runtime/structured-output.ts +32 -0
- package/src/domains/workflows/runtime/tool-capabilities.ts +44 -0
- package/src/domains/workflows/runtime/tool-source-identity.ts +150 -0
- package/src/domains/workflows/runtime/tree-fingerprint.ts +404 -0
- package/src/domains/workflows/runtime/types.ts +255 -0
- package/src/domains/workflows/runtime/ui/display-text.ts +28 -0
- package/src/domains/workflows/runtime/ui/dynamax-editor-decoration.ts +220 -0
- package/src/domains/workflows/runtime/ui/workflow-format.ts +165 -0
- package/src/domains/workflows/runtime/ui/workflow-inspector.ts +435 -0
- package/src/domains/workflows/runtime/ui/workflow-result-renderer.ts +171 -0
- package/src/domains/workflows/runtime/ui/workflow-viewer-layout.ts +51 -0
- package/src/domains/workflows/runtime/ui/workflow-widget.ts +78 -0
- package/src/domains/workflows/runtime/unknown-error.ts +8 -0
- package/src/domains/workflows/runtime/usage.ts +341 -0
- package/src/domains/workflows/runtime/workflow-advisory-utils.ts +245 -0
- package/src/domains/workflows/runtime/workflow-execution.ts +121 -0
- package/src/domains/workflows/runtime/workflow-module.ts +75 -0
- package/src/domains/workflows/runtime/workflow-run-background.ts +72 -0
- package/src/domains/workflows/runtime/workflow-run-controller.ts +342 -0
- package/src/domains/workflows/runtime/workflow-run-history.ts +164 -0
- package/src/domains/workflows/runtime/workflow-run-record.ts +636 -0
- package/src/domains/workflows/runtime/workflow-run-store.ts +176 -0
- package/src/domains/workflows/runtime/workflow-usage-limit-scheduler.ts +80 -0
- package/src/domains/workflows/runtime/workflows.ts +40 -0
- package/src/domains/workflows/runtime/worktree.ts +615 -0
- package/src/domains/workflows/workflows/code-review.ts +232 -0
- package/src/domains/workflows/workflows/diagnose.ts +154 -0
- package/src/domains/workflows/workflows/perf-review.ts +149 -0
- package/src/domains/workflows/workflows/refactor-scout.ts +143 -0
- package/src/domains/workflows/workflows/research.ts +169 -0
- package/src/services/usage-service.ts +6 -30
- package/src/ui/usage-bars.ts +1 -2
- package/src/core/codex/oauth.ts +0 -129
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { challengeFindings, parseChallengeArgs } from "../runtime/advisory-challenge.ts";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import {
|
|
4
|
+
type AdvisoryVerified,
|
|
5
|
+
type AdvisoryLens,
|
|
6
|
+
synthesizeAdvisoryReport,
|
|
7
|
+
finishAdvisoryReport,
|
|
8
|
+
formatEvidence,
|
|
9
|
+
formatLocation,
|
|
10
|
+
normalizePath,
|
|
11
|
+
publishVerifiedKeptProgress,
|
|
12
|
+
runLensVerificationPipeline,
|
|
13
|
+
verdictConfidence,
|
|
14
|
+
DEFAULT_ADVISORY_TOOL_HINTS,
|
|
15
|
+
DEFAULT_ADVISORY_TOOLS,
|
|
16
|
+
} from "../runtime/workflow-advisory-utils.ts";
|
|
17
|
+
import { formatReviewDiffTarget, parseAllowedDiffCommand } from "../runtime/review-diff-target.ts";
|
|
18
|
+
import { buildCodeReviewScopeBlock } from "../runtime/review/code-review-orchestration.ts";
|
|
19
|
+
import type { ReviewContext } from "../runtime/review/review-report.ts";
|
|
20
|
+
import { captureReviewMaterial, type ReviewMaterialCaptureResult } from "../runtime/review/review-snapshot.ts";
|
|
21
|
+
import type { WorkflowApi, WorkflowMeta, WorkflowRunStats } from "../runtime/types.ts";
|
|
22
|
+
|
|
23
|
+
export const meta: WorkflowMeta = {
|
|
24
|
+
name: "code-review",
|
|
25
|
+
description: "Fan-out review of the branch's open PR (or branch vs main): scope → per-angle find → independent verify → synthesize.",
|
|
26
|
+
phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Challenge" }, { title: "Synthesize" }],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ─── Schemas (the contracts that make orchestration plain code) ───
|
|
30
|
+
const ScopeSchema = Type.Object({
|
|
31
|
+
diffCommand: Type.String({ description: "Exact git command that produces the review diff" }),
|
|
32
|
+
files: Type.Array(Type.String(), { description: "Changed file paths" }),
|
|
33
|
+
summary: Type.String({ description: "One-paragraph summary of the change" }),
|
|
34
|
+
conventions: Type.Optional(Type.String({ description: "Relevant AGENTS.md / project conventions" })),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// The review lenses — this is the part you customise to your codebase's real failure modes.
|
|
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)." },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const PER_ANGLE = 6;
|
|
47
|
+
|
|
48
|
+
/** Parse a unified diff into the set of added/changed new-file line numbers per file. */
|
|
49
|
+
export function changedLines(diff: string): Map<string, Set<number>> {
|
|
50
|
+
const byFile = new Map<string, Set<number>>();
|
|
51
|
+
let file: string | null = null;
|
|
52
|
+
let newLine = 0;
|
|
53
|
+
for (const raw of diff.split("\n")) {
|
|
54
|
+
if (raw.startsWith("+++ ")) {
|
|
55
|
+
const path = raw.slice(4).trim();
|
|
56
|
+
file = path === "/dev/null" ? null : normalizePath(path);
|
|
57
|
+
if (file && !byFile.has(file)) byFile.set(file, new Set());
|
|
58
|
+
} else if (raw.startsWith("@@")) {
|
|
59
|
+
const match = /\+(\d+)/.exec(raw);
|
|
60
|
+
newLine = match ? Number(match[1]) : 0;
|
|
61
|
+
} else if (file === null || raw.startsWith("---") || raw.startsWith("\\")) {
|
|
62
|
+
// file header, deletion, or "No newline" marker — record nothing
|
|
63
|
+
} else if (raw.startsWith("+")) {
|
|
64
|
+
byFile.get(file)!.add(newLine++);
|
|
65
|
+
} else if (!raw.startsWith("-")) {
|
|
66
|
+
newLine++; // context line advances the new-file counter; deletions do not
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return byFile;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Is a finding inside the diff? File-level findings count if the file changed; ±1 line of fuzz. */
|
|
73
|
+
export function inDiff(changed: Map<string, Set<number>>, file: string, line?: number): boolean {
|
|
74
|
+
const set = changed.get(normalizePath(file));
|
|
75
|
+
if (!set) return false;
|
|
76
|
+
if (line == null) return true;
|
|
77
|
+
return set.has(line) || set.has(line - 1) || set.has(line + 1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface CodeReviewDependencies {
|
|
81
|
+
readonly captureReviewMaterial?: (
|
|
82
|
+
target: Parameters<typeof captureReviewMaterial>[0],
|
|
83
|
+
cwd: string,
|
|
84
|
+
signal?: AbortSignal,
|
|
85
|
+
) => Promise<ReviewMaterialCaptureResult>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default async function run(api: WorkflowApi, dependencies: CodeReviewDependencies = {}): Promise<unknown> {
|
|
89
|
+
const { agent, phase, log, progress, args, cwd, signal } = api;
|
|
90
|
+
const challengeConfig = parseChallengeArgs(args);
|
|
91
|
+
const target = challengeConfig.args.trim();
|
|
92
|
+
let fileCount = 0;
|
|
93
|
+
let rawCandidateCount = 0;
|
|
94
|
+
let droppedCandidateCount = 0;
|
|
95
|
+
const makeStats = (verified: number, kept: number): WorkflowRunStats => ({
|
|
96
|
+
files: fileCount,
|
|
97
|
+
candidates: rawCandidateCount,
|
|
98
|
+
verified,
|
|
99
|
+
kept,
|
|
100
|
+
dropped: droppedCandidateCount,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// ─── Phase 0: Scope ───
|
|
104
|
+
phase("Scope");
|
|
105
|
+
const scope = await agent(
|
|
106
|
+
"Establish the scope of a code review.\n" +
|
|
107
|
+
(target
|
|
108
|
+
? `Target / instructions (verbatim): "${target}". If it names a PR number, branch, ref range, or files, build the matching diff command (use 'gh pr diff <number>' for a PR). Otherwise use the default selection below.\n`
|
|
109
|
+
: "No explicit target — select the diff to review using the default below.\n") +
|
|
110
|
+
"Canonical Git syntax: use `git diff -- <path> [<path>...]` for file paths; use one `A..B` or `A...B` range operand for two revisions. Never emit ambiguous two-operand forms such as `git diff A B`.\n" +
|
|
111
|
+
"Default selection — run commands to decide, falling through until you get a NON-EMPTY diff:\n" +
|
|
112
|
+
"1. Get the current branch: `git branch --show-current`.\n" +
|
|
113
|
+
"2. Check for an OPEN GitHub PR for this branch: `gh pr list --head <branch> --state open --json number,title`. " +
|
|
114
|
+
"If one exists, the diff command is `gh pr diff <number>` — note the PR number and title in the summary.\n" +
|
|
115
|
+
"3. If there is no open PR (or `gh` is unavailable / there is no GitHub remote), diff the branch against its base: " +
|
|
116
|
+
"prefer `git diff main...HEAD`, then `git diff master...HEAD`, then `git diff HEAD~1`. " +
|
|
117
|
+
"If the branch itself is main/master, use `git diff HEAD~1`.\n" +
|
|
118
|
+
"4. Run the chosen command to confirm the diff is non-empty.\n\n" +
|
|
119
|
+
"Then: list the changed files, summarize the change in one paragraph (mention the PR if one was found), " +
|
|
120
|
+
"and read any relevant AGENTS.md or project docs noting conventions a reviewer should know.\n" +
|
|
121
|
+
"Return diffCommand exactly as a reviewer should run it. Structured output only.",
|
|
122
|
+
{ phase: "Scope", label: "scope", tools: DEFAULT_ADVISORY_TOOLS, toolHints: DEFAULT_ADVISORY_TOOL_HINTS, profile: "medium", schema: ScopeSchema },
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (!scope) {
|
|
126
|
+
return { summary: "No changes found to review.", findings: [], nextSteps: ["Provide a PR, ref range, or changed files to review."], stats: makeStats(0, 0) };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fileCount = scope.files.length;
|
|
130
|
+
progress({ type: "summary", key: "files", value: scope.files.join(", ") || "(none)" });
|
|
131
|
+
const diffTarget = parseAllowedDiffCommand(scope.diffCommand);
|
|
132
|
+
if ("error" in diffTarget) {
|
|
133
|
+
throw new Error(`Code-review target rejected: ${diffTarget.error}`);
|
|
134
|
+
}
|
|
135
|
+
const diffCommand = formatReviewDiffTarget(diffTarget);
|
|
136
|
+
progress({ type: "summary", key: "diffCommand", value: diffCommand });
|
|
137
|
+
progress({ type: "counter", key: "files", label: "files", value: fileCount });
|
|
138
|
+
|
|
139
|
+
if (scope.files.length === 0) {
|
|
140
|
+
return { summary: "No changes found to review.", findings: [], nextSteps: ["Provide a PR, ref range, or changed files to review."], stats: makeStats(0, 0) };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
log(`${scope.files.length} changed files`);
|
|
144
|
+
|
|
145
|
+
// Capture the diff once, deterministically, so findings can be bounded to changed lines in code.
|
|
146
|
+
const reviewMaterial = await (dependencies.captureReviewMaterial ?? captureReviewMaterial)(diffTarget, cwd, signal);
|
|
147
|
+
if (!reviewMaterial.ok) {
|
|
148
|
+
throw new Error(`Code-review diff capture failed: ${reviewMaterial.error}`);
|
|
149
|
+
}
|
|
150
|
+
const diffText = reviewMaterial.diff;
|
|
151
|
+
const changed = changedLines(diffText);
|
|
152
|
+
progress({ type: "summary", key: "diffBytes", value: Buffer.byteLength(diffText) });
|
|
153
|
+
if (reviewMaterial.snapshot.status === "unavailable") {
|
|
154
|
+
log(`review snapshot unavailable (${reviewMaterial.snapshot.reason}) — patch previews will be unavailable`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const reviewContext: ReviewContext = {
|
|
158
|
+
workflowName: "code-review",
|
|
159
|
+
target,
|
|
160
|
+
diffTarget,
|
|
161
|
+
files: scope.files,
|
|
162
|
+
summary: scope.summary,
|
|
163
|
+
...(reviewMaterial.snapshot.status === "verified"
|
|
164
|
+
? { snapshot: reviewMaterial.snapshot.identity }
|
|
165
|
+
: {}),
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const scopeBlock = `Reviewed snapshot identity: ${JSON.stringify(reviewContext.snapshot ?? "unavailable")}\n` + buildCodeReviewScopeBlock({
|
|
169
|
+
diffCommand,
|
|
170
|
+
files: scope.files,
|
|
171
|
+
summary: scope.summary,
|
|
172
|
+
conventions: scope.conventions,
|
|
173
|
+
diffText,
|
|
174
|
+
target,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const pipelineResult = await runLensVerificationPipeline({
|
|
178
|
+
api,
|
|
179
|
+
lenses: ANGLES,
|
|
180
|
+
perLens: PER_ANGLE,
|
|
181
|
+
boundCandidate: (candidate, lens) => {
|
|
182
|
+
const anchor = candidate.reviewAnchor ?? candidate.locations.find((location) => inDiff(changed, location.file, location.line));
|
|
183
|
+
return anchor && inDiff(changed, anchor.file, anchor.line)
|
|
184
|
+
? { ...candidate, category: lens.category, reviewAnchor: anchor } : undefined;
|
|
185
|
+
},
|
|
186
|
+
finderPrompt: (lens) =>
|
|
187
|
+
`## Code-review finder — ${lens.label}\n\n${scopeBlock}\n` +
|
|
188
|
+
`Review the change through ONLY this lens:\n${lens.text}\n` +
|
|
189
|
+
"Only flag issues on lines that are part of the diff above (run the diff command if it is not shown). " +
|
|
190
|
+
"Set reviewAnchor to the changed line causing the issue; locations and discoveryEvidence may include unchanged callers and other files. " +
|
|
191
|
+
`Surface up to ${PER_ANGLE} candidates. Use category exactly "${lens.category}". Each candidate must include a one-line summary, ` +
|
|
192
|
+
"locations with the changed file and a line that appears in the diff, and impact describing the concrete failure or maintenance scenario. " +
|
|
193
|
+
"Pass through anything with a nameable impact — a separate verifier judges them next. Structured output only.",
|
|
194
|
+
verifierPrompt: (candidate) =>
|
|
195
|
+
`## Code-review verifier\n\n${scopeBlock}\n## Candidate\n` +
|
|
196
|
+
`Location: ${formatLocation(candidate)}\n` +
|
|
197
|
+
`Category: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n\n` +
|
|
198
|
+
"Run the diff command, read the relevant file(s), and return exactly one verdict (CONFIRMED / PLAUSIBLE / NOT_SUBSTANTIATED / REFUTED) " +
|
|
199
|
+
"with evidence quoting the line(s). Use NOT_SUBSTANTIATED when evidence is insufficient; use REFUTED only for concrete disproof. Structured output only.",
|
|
200
|
+
});
|
|
201
|
+
rawCandidateCount += pipelineResult.rawCandidates;
|
|
202
|
+
droppedCandidateCount += pipelineResult.dropped;
|
|
203
|
+
const { coverage } = pipelineResult;
|
|
204
|
+
const verified = await challengeFindings(api, pipelineResult.verified, scopeBlock, challengeConfig.options, coverage);
|
|
205
|
+
const surviving = verified.filter((finding) => finding.verdict !== "REFUTED");
|
|
206
|
+
const stats = makeStats(verified.length, surviving.length);
|
|
207
|
+
publishVerifiedKeptProgress({ progress, log }, verified.length, surviving.length);
|
|
208
|
+
|
|
209
|
+
if (surviving.length === 0) {
|
|
210
|
+
return finishAdvisoryReport({ summary: "No findings survived verification.", findings: [], nextSteps: ["No code-review action is recommended from this workflow run."], stats, reviewContext }, coverage, verified);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ─── Synthesize: rank, merge, report ───
|
|
214
|
+
const rank = (finding: AdvisoryVerified): number => (finding.category === "cleanup" ? 2 : 0) + (finding.verdict !== "CONFIRMED" ? 1 : 0);
|
|
215
|
+
const ranked = [...surviving].sort((a, b) => rank(a) - rank(b));
|
|
216
|
+
const block = ranked
|
|
217
|
+
.map(
|
|
218
|
+
(finding, index) =>
|
|
219
|
+
`### [${index}] IDs: ${finding.sourceCandidateIds.join(", ")} ${formatLocation(finding)} (${finding.verdict}${finding.category === "cleanup" ? ", cleanup" : ""})\n` +
|
|
220
|
+
`Category: ${finding.category}\nConfidence: ${verdictConfidence(finding.verdict)}\n` +
|
|
221
|
+
`${finding.summary}\nImpact: ${finding.impact}\nEvidence: ${formatEvidence(finding.evidence)}`,
|
|
222
|
+
)
|
|
223
|
+
.join("\n\n");
|
|
224
|
+
|
|
225
|
+
const resolved = await synthesizeAdvisoryReport(api,
|
|
226
|
+
`## Synthesis: final code-review report\n\n${ranked.length} findings survived independent verification.\n\n${block}\n\n` +
|
|
227
|
+
"Merge findings with the same root cause, rank most-severe first (correctness bugs above cleanups), and produce the final advisory report. " +
|
|
228
|
+
"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.",
|
|
229
|
+
ranked, coverage,
|
|
230
|
+
);
|
|
231
|
+
return finishAdvisoryReport({ ...resolved, stats: { ...stats, kept: resolved.findings.length }, reviewContext }, coverage, verified);
|
|
232
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { challengeFindings, parseChallengeArgs } from "../runtime/advisory-challenge.ts";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import {
|
|
4
|
+
type AdvisoryVerified,
|
|
5
|
+
type AdvisoryLens,
|
|
6
|
+
synthesizeAdvisoryReport,
|
|
7
|
+
finishAdvisoryReport,
|
|
8
|
+
emptyAdvisoryReport,
|
|
9
|
+
formatEvidence,
|
|
10
|
+
formatLocation,
|
|
11
|
+
publishVerifiedKeptProgress,
|
|
12
|
+
runLensVerificationPipeline,
|
|
13
|
+
DEFAULT_ADVISORY_TOOL_HINTS,
|
|
14
|
+
DEFAULT_ADVISORY_TOOLS,
|
|
15
|
+
} from "../runtime/workflow-advisory-utils.ts";
|
|
16
|
+
import type { WorkflowApi, WorkflowMeta, WorkflowRunStats } from "../runtime/types.ts";
|
|
17
|
+
|
|
18
|
+
export const meta: WorkflowMeta = {
|
|
19
|
+
name: "diagnose",
|
|
20
|
+
description: "Advisory-only bug diagnosis: scope symptoms → competing hypotheses → independent verify → synthesize likely root causes.",
|
|
21
|
+
phases: [{ title: "Scope" }, { title: "Hypothesize" }, { title: "Verify" }, { title: "Challenge" }, { title: "Synthesize" }],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const ScopeSchema = Type.Object({
|
|
25
|
+
symptom: Type.String({ description: "Observed bug, failing command, regression, or unclear behavior." }),
|
|
26
|
+
commands: Type.Array(Type.String(), { description: "Safe read-only or diagnostic commands relevant to the symptom." }),
|
|
27
|
+
files: Type.Array(Type.String(), { description: "Repository-relative files likely involved." }),
|
|
28
|
+
observations: Type.Array(Type.String(), { description: "Concrete observations from files, tests, config, or command output." }),
|
|
29
|
+
constraints: Type.Optional(Type.String({ description: "Safety constraints, missing evidence, or commands intentionally not run." })),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const HYPOTHESIS_LENSES: AdvisoryLens[] = [
|
|
33
|
+
{ label: "recent-change", category: "regression", text: "A recent code change broke a previously working path or changed an implicit contract." },
|
|
34
|
+
{ label: "control-flow", category: "root-cause", text: "Incorrect branching, ordering, async flow, data flow, or state transition causes the symptom." },
|
|
35
|
+
{ label: "configuration", category: "configuration", text: "Configuration, environment, package scripts, or runtime assumptions differ from what the code expects." },
|
|
36
|
+
{ label: "dependency-api", category: "dependency", text: "A dependency API, version, import mode, or bundled peer behavior does not match the implementation." },
|
|
37
|
+
{ label: "test-fixture", category: "test-fixture", text: "The failure is caused by test setup, fixtures, mocks, generated files, or stale local state rather than product code." },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const PER_LENS = 4;
|
|
41
|
+
|
|
42
|
+
export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
43
|
+
const { agent, phase, log, progress, args } = api;
|
|
44
|
+
const challengeConfig = parseChallengeArgs(args);
|
|
45
|
+
const symptom = challengeConfig.args.trim();
|
|
46
|
+
let fileCount = 0;
|
|
47
|
+
let rawCandidateCount = 0;
|
|
48
|
+
let droppedCandidateCount = 0;
|
|
49
|
+
let refutedCandidateCount = 0;
|
|
50
|
+
const makeStats = (verified: number, kept: number): WorkflowRunStats => ({
|
|
51
|
+
files: fileCount,
|
|
52
|
+
candidates: rawCandidateCount,
|
|
53
|
+
verified,
|
|
54
|
+
kept,
|
|
55
|
+
dropped: droppedCandidateCount,
|
|
56
|
+
refuted: refutedCandidateCount,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
phase("Scope");
|
|
60
|
+
const scope = await agent(
|
|
61
|
+
"Establish the scope for an advisory-only diagnosis workflow. Do not edit files.\n" +
|
|
62
|
+
(symptom
|
|
63
|
+
? `Bug / failure description (verbatim): ${symptom}\n\n`
|
|
64
|
+
: "No explicit symptom was provided. Infer likely failing commands from repository manifests and scripts without running destructive commands.\n\n") +
|
|
65
|
+
"Inspect relevant files, package/test configuration, and safe diagnostic commands. " +
|
|
66
|
+
"Safe commands are read-only commands such as status, grep, listing files, typecheck/test commands, or commands explicitly requested by the user. " +
|
|
67
|
+
"Do not run mutation, install, commit, network, or destructive commands. Return scoped files, observations, and constraints. Structured output only.",
|
|
68
|
+
{ phase: "Scope", label: "scope", tools: DEFAULT_ADVISORY_TOOLS, toolHints: DEFAULT_ADVISORY_TOOL_HINTS, profile: "medium", schema: ScopeSchema },
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (!scope) {
|
|
72
|
+
return finishAdvisoryReport(emptyAdvisoryReport(
|
|
73
|
+
"Diagnosis could not establish a scope.",
|
|
74
|
+
["Provide the failing command, error message, or regression description and rerun diagnose."],
|
|
75
|
+
makeStats(0, 0),
|
|
76
|
+
), []);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
fileCount = scope.files.length;
|
|
80
|
+
progress({ type: "counter", key: "files", label: "files", value: fileCount });
|
|
81
|
+
progress({ type: "summary", key: "symptom", value: scope.symptom });
|
|
82
|
+
progress({ type: "summary", key: "files", value: scope.files.join(", ") || "(none)" });
|
|
83
|
+
log(`${scope.files.length} files scoped for diagnosis`);
|
|
84
|
+
|
|
85
|
+
const scopeBlock =
|
|
86
|
+
`## Symptom\n${scope.symptom}\n\n## Relevant commands\n${scope.commands.map((command) => `- ${command}`).join("\n") || "(none)"}\n\n` +
|
|
87
|
+
`## Files\n${scope.files.map((file) => `- ${file}`).join("\n") || "(none)"}\n\n` +
|
|
88
|
+
`## Observations\n${scope.observations.map((observation) => `- ${observation}`).join("\n") || "(none)"}\n\n` +
|
|
89
|
+
`## Constraints\n${scope.constraints ?? "(none noted)"}\n`;
|
|
90
|
+
|
|
91
|
+
const pipelineResult = await runLensVerificationPipeline({
|
|
92
|
+
api,
|
|
93
|
+
lenses: HYPOTHESIS_LENSES,
|
|
94
|
+
perLens: PER_LENS,
|
|
95
|
+
finderPhase: "Hypothesize",
|
|
96
|
+
finderPrompt: (lens) =>
|
|
97
|
+
`## Diagnose hypothesis generator — ${lens.label}\n\n${scopeBlock}\n` +
|
|
98
|
+
"This workflow is advisory-only: diagnose and recommend validation/fix plans, but do not edit files.\n" +
|
|
99
|
+
`Consider ONLY this hypothesis lens:\n${lens.text}\n\n` +
|
|
100
|
+
`Surface up to ${PER_LENS} root-cause hypotheses. Use category exactly "${lens.category}". ` +
|
|
101
|
+
"Each hypothesis must include a one-line summary, locations, impact explaining how it produces the symptom, and an optional recommendation for the next validation step. Structured output only.",
|
|
102
|
+
verifierPrompt: (candidate) =>
|
|
103
|
+
`## Diagnose verifier\n\n${scopeBlock}\n## Hypothesis\n` +
|
|
104
|
+
`Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n` +
|
|
105
|
+
`Recommended validation: ${candidate.recommendation ?? "(none supplied)"}\n\n` +
|
|
106
|
+
"Read relevant files and, when useful, run only safe read-only diagnostic commands from the scoped command list or commands explicitly requested by the user. " +
|
|
107
|
+
"Do not run mutation, install, commit, network, or destructive commands. Return CONFIRMED, PLAUSIBLE, NOT_SUBSTANTIATED, or REFUTED with evidence. " +
|
|
108
|
+
"Use NOT_SUBSTANTIATED when evidence is missing; REFUTED requires disproof. Structured output only.",
|
|
109
|
+
});
|
|
110
|
+
rawCandidateCount += pipelineResult.rawCandidates;
|
|
111
|
+
droppedCandidateCount += pipelineResult.dropped;
|
|
112
|
+
refutedCandidateCount += pipelineResult.refuted;
|
|
113
|
+
const { coverage } = pipelineResult;
|
|
114
|
+
const verified = await challengeFindings(api, pipelineResult.verified, scopeBlock, challengeConfig.options, coverage);
|
|
115
|
+
const surviving = verified.filter((finding) => finding.verdict !== "REFUTED");
|
|
116
|
+
const refuted = verified.filter((finding) => finding.verdict === "REFUTED");
|
|
117
|
+
const stats = makeStats(verified.length, surviving.length);
|
|
118
|
+
publishVerifiedKeptProgress({ progress, log }, verified.length, surviving.length);
|
|
119
|
+
|
|
120
|
+
if (surviving.length === 0) {
|
|
121
|
+
return finishAdvisoryReport(emptyAdvisoryReport(
|
|
122
|
+
"No root-cause hypothesis survived verification.",
|
|
123
|
+
["Capture the exact failing command and error output.", "Rerun diagnose with a narrower symptom or more evidence."],
|
|
124
|
+
stats,
|
|
125
|
+
), coverage, verified);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const ranked = [...surviving].sort((a, b) => rank(a) - rank(b));
|
|
129
|
+
const block = ranked
|
|
130
|
+
.map(
|
|
131
|
+
(finding, index) =>
|
|
132
|
+
`### [${index}] IDs: ${finding.sourceCandidateIds.join(", ")} ${formatLocation(finding)} (${finding.verdict}, ${finding.category})\n` +
|
|
133
|
+
`${finding.summary}\nImpact: ${finding.impact}\nEvidence: ${formatEvidence(finding.evidence)}\nValidation/fix plan: ${finding.recommendation ?? "(none supplied)"}`,
|
|
134
|
+
)
|
|
135
|
+
.join("\n\n");
|
|
136
|
+
const refutedBlock = refuted
|
|
137
|
+
.slice(0, 8)
|
|
138
|
+
.map((finding) => `- ${finding.summary} — REFUTED because ${formatEvidence(finding.evidence)}`)
|
|
139
|
+
.join("\n");
|
|
140
|
+
|
|
141
|
+
const resolved = await synthesizeAdvisoryReport(api,
|
|
142
|
+
`## Synthesis: final diagnosis report\n\n${ranked.length} hypotheses survived independent verification.\n\n${block}\n\n` +
|
|
143
|
+
`## Refuted hypotheses for context\n${refutedBlock || "(none recorded)"}\n\n` +
|
|
144
|
+
"Select confirmed, plausible or explicitly unresolved root causes by ID. " +
|
|
145
|
+
"Recommendation must be a validation/fix plan, not a patch. nextSteps must be the minimum commands or code inspections needed to confirm the top diagnosis. Structured output only.",
|
|
146
|
+
ranked, coverage,
|
|
147
|
+
);
|
|
148
|
+
return finishAdvisoryReport({ ...resolved, stats: { ...stats, kept: resolved.findings.length } }, coverage, verified);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function rank(finding: AdvisoryVerified): number {
|
|
152
|
+
if (finding.verdict === "CONFIRMED") return 0;
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { challengeFindings, parseChallengeArgs } from "../runtime/advisory-challenge.ts";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import {
|
|
4
|
+
type AdvisoryVerified,
|
|
5
|
+
type AdvisoryLens,
|
|
6
|
+
synthesizeAdvisoryReport,
|
|
7
|
+
finishAdvisoryReport,
|
|
8
|
+
emptyAdvisoryReport,
|
|
9
|
+
formatEvidence,
|
|
10
|
+
formatLocation,
|
|
11
|
+
publishVerifiedKeptProgress,
|
|
12
|
+
runLensVerificationPipeline,
|
|
13
|
+
DEFAULT_ADVISORY_TOOL_HINTS,
|
|
14
|
+
DEFAULT_ADVISORY_TOOLS,
|
|
15
|
+
} from "../runtime/workflow-advisory-utils.ts";
|
|
16
|
+
import type { WorkflowApi, WorkflowMeta, WorkflowRunStats } from "../runtime/types.ts";
|
|
17
|
+
|
|
18
|
+
export const meta: WorkflowMeta = {
|
|
19
|
+
name: "perf-review",
|
|
20
|
+
description: "Advisory-only performance review: scope slow path → per-lens bottleneck hypotheses → verify evidence → synthesize measurements and safe optimizations.",
|
|
21
|
+
phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Challenge" }, { title: "Synthesize" }],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const ScopeSchema = Type.Object({
|
|
25
|
+
target: Type.String({ description: "Verbatim slow path, workload, command, or performance concern." }),
|
|
26
|
+
files: Type.Array(Type.String(), { description: "Repository-relative files likely involved in the performance path." }),
|
|
27
|
+
commands: Type.Array(Type.String(), { description: "Existing benchmark, smoke, or measurement commands relevant to this path." }),
|
|
28
|
+
summary: Type.String({ description: "One-paragraph summary of the performance-relevant path." }),
|
|
29
|
+
knownMeasurements: Type.Optional(Type.String({ description: "Existing measurements, timings, or explicit lack of measurements." })),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const PERF_LENSES: AdvisoryLens[] = [
|
|
33
|
+
{ label: "algorithmic", category: "algorithmic", text: "Complexity, repeated scans, avoidable nested loops, or data-structure choices that grow poorly with input size." },
|
|
34
|
+
{ label: "io", category: "io", text: "Filesystem, subprocess, network, or other I/O costs on hot paths or startup paths." },
|
|
35
|
+
{ label: "concurrency", category: "concurrency", text: "Unnecessary serialization, missing batching, excessive fan-out, contention, or concurrency limits." },
|
|
36
|
+
{ label: "startup", category: "startup", text: "Import/module loading, initialization, discovery, or cold-start overhead." },
|
|
37
|
+
{ label: "allocation", category: "allocation", text: "Memory churn, large intermediate strings/objects, repeated serialization, or retained state growth." },
|
|
38
|
+
{ label: "measurement", category: "measurement", text: "Missing, misleading, noisy, or insufficient benchmark/measurement design." },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const PER_LENS = 4;
|
|
42
|
+
|
|
43
|
+
export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
44
|
+
const { agent, phase, log, progress, args } = api;
|
|
45
|
+
const challengeConfig = parseChallengeArgs(args);
|
|
46
|
+
const target = challengeConfig.args.trim() || "repository performance";
|
|
47
|
+
let fileCount = 0;
|
|
48
|
+
let rawCandidateCount = 0;
|
|
49
|
+
let droppedCandidateCount = 0;
|
|
50
|
+
let refutedCandidateCount = 0;
|
|
51
|
+
const makeStats = (verified: number, kept: number): WorkflowRunStats => ({
|
|
52
|
+
files: fileCount,
|
|
53
|
+
candidates: rawCandidateCount,
|
|
54
|
+
verified,
|
|
55
|
+
kept,
|
|
56
|
+
dropped: droppedCandidateCount,
|
|
57
|
+
refuted: refutedCandidateCount,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
phase("Scope");
|
|
61
|
+
const scope = await agent(
|
|
62
|
+
"Establish the scope for an advisory-only performance review. Do not edit files.\n" +
|
|
63
|
+
`Performance target / concern (verbatim): ${target}\n\n` +
|
|
64
|
+
"Inspect repository structure, scripts, likely hot-path files, and any existing benchmark or measurement commands. " +
|
|
65
|
+
"Prefer identifying what to measure before claiming bottlenecks. Return files, commands, summary, and known measurements or the lack of them. " +
|
|
66
|
+
`This workflow will fan out across ${PERF_LENSES.length} lenses with up to ${PER_LENS} candidates per lens. Structured output only.`,
|
|
67
|
+
{ phase: "Scope", label: "scope", tools: DEFAULT_ADVISORY_TOOLS, toolHints: DEFAULT_ADVISORY_TOOL_HINTS, profile: "medium", schema: ScopeSchema },
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (!scope || scope.files.length === 0) {
|
|
71
|
+
return finishAdvisoryReport(emptyAdvisoryReport(
|
|
72
|
+
"No performance-relevant files were identified.",
|
|
73
|
+
["Provide a slow command, workload, file path, or user-visible latency concern to review."],
|
|
74
|
+
makeStats(0, 0),
|
|
75
|
+
), []);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fileCount = scope.files.length;
|
|
79
|
+
progress({ type: "counter", key: "files", label: "files", value: fileCount });
|
|
80
|
+
progress({ type: "summary", key: "target", value: scope.target });
|
|
81
|
+
progress({ type: "summary", key: "files", value: scope.files.join(", ") });
|
|
82
|
+
log(`${scope.files.length} files scoped for performance review`);
|
|
83
|
+
|
|
84
|
+
const scopeBlock =
|
|
85
|
+
`## Target\n${scope.target}\n\n## Files\n${scope.files.map((file) => `- ${file}`).join("\n")}\n\n` +
|
|
86
|
+
`## Measurement commands\n${scope.commands.map((command) => `- ${command}`).join("\n") || "(none identified)"}\n\n` +
|
|
87
|
+
`## Summary\n${scope.summary}\n\n## Known measurements\n${scope.knownMeasurements ?? "(none known)"}\n` +
|
|
88
|
+
(args.trim() ? `\n## User instructions (verbatim)\n${args.trim()}\n` : "");
|
|
89
|
+
|
|
90
|
+
const pipelineResult = await runLensVerificationPipeline({
|
|
91
|
+
api,
|
|
92
|
+
lenses: PERF_LENSES,
|
|
93
|
+
perLens: PER_LENS,
|
|
94
|
+
finderPrompt: (lens) =>
|
|
95
|
+
`## Perf-review finder — ${lens.label}\n\n${scopeBlock}\n` +
|
|
96
|
+
"This workflow is advisory-only: identify bottleneck hypotheses, measurement gaps, and safe optimization directions, but do not edit files.\n" +
|
|
97
|
+
`Investigate ONLY this lens:\n${lens.text}\n\n` +
|
|
98
|
+
`Surface up to ${PER_LENS} candidates. Use category exactly "${lens.category}". ` +
|
|
99
|
+
"Each candidate must include a one-line summary, locations, impact stating the suspected performance consequence and workload where it matters, " +
|
|
100
|
+
"and an optional recommendation. Prefer measurement recommendations when evidence is weak. Structured output only.",
|
|
101
|
+
verifierPrompt: (candidate) =>
|
|
102
|
+
`## Perf-review verifier\n\n${scopeBlock}\n## Candidate\n` +
|
|
103
|
+
`Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n` +
|
|
104
|
+
`Recommendation: ${candidate.recommendation ?? "(none supplied)"}\n\n` +
|
|
105
|
+
"Read relevant files and package/scripts. Run only safe read-only measurement or inspection commands when useful. " +
|
|
106
|
+
"Return CONFIRMED, PLAUSIBLE, NOT_SUBSTANTIATED, or REFUTED with evidence from code, scripts, config, or measurement output. " +
|
|
107
|
+
"Default toward PLAUSIBLE or REFUTED when no measurement exists; do not overstate a bottleneck. Structured output only.",
|
|
108
|
+
});
|
|
109
|
+
rawCandidateCount += pipelineResult.rawCandidates;
|
|
110
|
+
droppedCandidateCount += pipelineResult.dropped;
|
|
111
|
+
refutedCandidateCount += pipelineResult.refuted;
|
|
112
|
+
const { coverage } = pipelineResult;
|
|
113
|
+
const verified = await challengeFindings(api, pipelineResult.verified, scopeBlock, challengeConfig.options, coverage);
|
|
114
|
+
const surviving = verified.filter((finding) => finding.verdict !== "REFUTED");
|
|
115
|
+
const stats = makeStats(verified.length, surviving.length);
|
|
116
|
+
publishVerifiedKeptProgress({ progress, log }, verified.length, surviving.length);
|
|
117
|
+
|
|
118
|
+
if (surviving.length === 0) {
|
|
119
|
+
return finishAdvisoryReport(emptyAdvisoryReport(
|
|
120
|
+
"No performance finding survived verification.",
|
|
121
|
+
["Add or run a focused measurement for the target workload before optimizing.", "Rerun perf-review with benchmark output or a narrower slow path."],
|
|
122
|
+
stats,
|
|
123
|
+
), coverage, verified);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const ranked = [...surviving].sort((a, b) => rank(a) - rank(b));
|
|
127
|
+
const block = ranked
|
|
128
|
+
.map(
|
|
129
|
+
(finding, index) =>
|
|
130
|
+
`### [${index}] IDs: ${finding.sourceCandidateIds.join(", ")} ${formatLocation(finding)} (${finding.verdict}, ${finding.category})\n` +
|
|
131
|
+
`${finding.summary}\nImpact: ${finding.impact}\nEvidence: ${formatEvidence(finding.evidence)}\nRecommendation: ${finding.recommendation ?? "(none supplied)"}`,
|
|
132
|
+
)
|
|
133
|
+
.join("\n\n");
|
|
134
|
+
|
|
135
|
+
const resolved = await synthesizeAdvisoryReport(api,
|
|
136
|
+
`## Synthesis: final perf-review report\n\n${ranked.length} candidates survived independent verification.\n\n${block}\n\n` +
|
|
137
|
+
"Select findings by ID. " +
|
|
138
|
+
"Severity is expected performance impact for the target workload. Prefer measurement recommendations before optimization recommendations when evidence is weak. " +
|
|
139
|
+
"Recommendations must be safe advisory next actions, not patches. Include risky optimizations to avoid in recommendations or nextSteps when relevant. Structured output only.",
|
|
140
|
+
ranked, coverage,
|
|
141
|
+
);
|
|
142
|
+
return finishAdvisoryReport({ ...resolved, stats: { ...stats, kept: resolved.findings.length } }, coverage, verified);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function rank(finding: AdvisoryVerified): number {
|
|
146
|
+
const verdictRank = finding.verdict === "CONFIRMED" ? 0 : 1;
|
|
147
|
+
const measurementPenalty = finding.category === "measurement" ? 1 : 0;
|
|
148
|
+
return verdictRank + measurementPenalty;
|
|
149
|
+
}
|