pi-workflow-engine 0.12.0 → 0.13.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/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 +13 -1
- package/.pi/extensions/pi-workflow-engine/src/agent-workspace.ts +16 -2
- package/.pi/extensions/pi-workflow-engine/src/concurrency.ts +12 -6
- package/.pi/extensions/pi-workflow-engine/src/journal.ts +1 -0
- package/.pi/extensions/pi-workflow-engine/src/resume-context.ts +53 -3
- package/.pi/extensions/pi-workflow-engine/src/review/code-review-orchestration.ts +0 -25
- package/.pi/extensions/pi-workflow-engine/src/review/patch-validation.ts +120 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-fix-workflow.ts +43 -7
- package/.pi/extensions/pi-workflow-engine/src/review/review-format.ts +2 -1
- package/.pi/extensions/pi-workflow-engine/src/review/review-issues.ts +1 -1
- package/.pi/extensions/pi-workflow-engine/src/types.ts +3 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-result-renderer.ts +5 -2
- package/.pi/extensions/pi-workflow-engine/src/workflow-advisory-utils.ts +131 -178
- package/.pi/extensions/pi-workflow-engine/src/worktree.ts +7 -0
- package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +55 -128
- package/.pi/extensions/pi-workflow-engine/workflows/diagnose.ts +45 -139
- package/.pi/extensions/pi-workflow-engine/workflows/perf-review.ts +25 -67
- package/.pi/extensions/pi-workflow-engine/workflows/refactor-scout.ts +25 -67
- package/USAGE.md +59 -0
- package/package.json +1 -1
|
@@ -1,11 +1,10 @@
|
|
|
1
|
+
import { challengeFindings, parseChallengeArgs } from "../src/advisory-challenge.ts";
|
|
1
2
|
import { Type } from "typebox";
|
|
2
3
|
import {
|
|
3
|
-
|
|
4
|
-
type
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
backfillAdvisoryFindings,
|
|
4
|
+
type AdvisoryVerified,
|
|
5
|
+
type AdvisoryLens,
|
|
6
|
+
synthesizeAdvisoryReport,
|
|
7
|
+
finishAdvisoryReport,
|
|
9
8
|
emptyAdvisoryReport,
|
|
10
9
|
formatEvidence,
|
|
11
10
|
formatLocation,
|
|
@@ -19,7 +18,7 @@ import type { WorkflowApi, WorkflowMeta, WorkflowRunStats } from "../src/types.t
|
|
|
19
18
|
export const meta: WorkflowMeta = {
|
|
20
19
|
name: "perf-review",
|
|
21
20
|
description: "Advisory-only performance review: scope slow path → per-lens bottleneck hypotheses → verify evidence → synthesize measurements and safe optimizations.",
|
|
22
|
-
phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Synthesize" }],
|
|
21
|
+
phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Challenge" }, { title: "Synthesize" }],
|
|
23
22
|
};
|
|
24
23
|
|
|
25
24
|
const ScopeSchema = Type.Object({
|
|
@@ -30,22 +29,7 @@ const ScopeSchema = Type.Object({
|
|
|
30
29
|
knownMeasurements: Type.Optional(Type.String({ description: "Existing measurements, timings, or explicit lack of measurements." })),
|
|
31
30
|
});
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
label: string;
|
|
35
|
-
category: string;
|
|
36
|
-
text: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
type Candidate = AdvisoryCandidate;
|
|
40
|
-
|
|
41
|
-
interface Verified extends Candidate {
|
|
42
|
-
verdict: AdvisoryVerdict["verdict"];
|
|
43
|
-
evidence: string[];
|
|
44
|
-
confidence?: AdvisoryVerdict["confidence"];
|
|
45
|
-
lens: PerfLens;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const PERF_LENSES: PerfLens[] = [
|
|
32
|
+
const PERF_LENSES: AdvisoryLens[] = [
|
|
49
33
|
{ label: "algorithmic", category: "algorithmic", text: "Complexity, repeated scans, avoidable nested loops, or data-structure choices that grow poorly with input size." },
|
|
50
34
|
{ label: "io", category: "io", text: "Filesystem, subprocess, network, or other I/O costs on hot paths or startup paths." },
|
|
51
35
|
{ label: "concurrency", category: "concurrency", text: "Unnecessary serialization, missing batching, excessive fan-out, contention, or concurrency limits." },
|
|
@@ -54,13 +38,12 @@ const PERF_LENSES: PerfLens[] = [
|
|
|
54
38
|
{ label: "measurement", category: "measurement", text: "Missing, misleading, noisy, or insufficient benchmark/measurement design." },
|
|
55
39
|
];
|
|
56
40
|
|
|
57
|
-
const TOOLS = DEFAULT_ADVISORY_TOOLS;
|
|
58
|
-
const TOOL_HINTS = DEFAULT_ADVISORY_TOOL_HINTS;
|
|
59
41
|
const PER_LENS = 4;
|
|
60
42
|
|
|
61
43
|
export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
62
|
-
const { agent,
|
|
63
|
-
const
|
|
44
|
+
const { agent, phase, log, progress, args } = api;
|
|
45
|
+
const challengeConfig = parseChallengeArgs(args);
|
|
46
|
+
const target = challengeConfig.args.trim() || "repository performance";
|
|
64
47
|
let fileCount = 0;
|
|
65
48
|
let rawCandidateCount = 0;
|
|
66
49
|
let droppedCandidateCount = 0;
|
|
@@ -81,15 +64,15 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
81
64
|
"Inspect repository structure, scripts, likely hot-path files, and any existing benchmark or measurement commands. " +
|
|
82
65
|
"Prefer identifying what to measure before claiming bottlenecks. Return files, commands, summary, and known measurements or the lack of them. " +
|
|
83
66
|
`This workflow will fan out across ${PERF_LENSES.length} lenses with up to ${PER_LENS} candidates per lens. Structured output only.`,
|
|
84
|
-
{ phase: "Scope", label: "scope", tools:
|
|
67
|
+
{ phase: "Scope", label: "scope", tools: DEFAULT_ADVISORY_TOOLS, toolHints: DEFAULT_ADVISORY_TOOL_HINTS, profile: "medium", schema: ScopeSchema },
|
|
85
68
|
);
|
|
86
69
|
|
|
87
70
|
if (!scope || scope.files.length === 0) {
|
|
88
|
-
return emptyAdvisoryReport(
|
|
71
|
+
return finishAdvisoryReport(emptyAdvisoryReport(
|
|
89
72
|
"No performance-relevant files were identified.",
|
|
90
73
|
["Provide a slow command, workload, file path, or user-visible latency concern to review."],
|
|
91
74
|
makeStats(0, 0),
|
|
92
|
-
);
|
|
75
|
+
), []);
|
|
93
76
|
}
|
|
94
77
|
|
|
95
78
|
fileCount = scope.files.length;
|
|
@@ -104,14 +87,10 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
104
87
|
`## Summary\n${scope.summary}\n\n## Known measurements\n${scope.knownMeasurements ?? "(none known)"}\n` +
|
|
105
88
|
(args.trim() ? `\n## User instructions (verbatim)\n${args.trim()}\n` : "");
|
|
106
89
|
|
|
107
|
-
phase("Find");
|
|
108
90
|
const pipelineResult = await runLensVerificationPipeline({
|
|
109
|
-
api
|
|
91
|
+
api,
|
|
110
92
|
lenses: PERF_LENSES,
|
|
111
|
-
schedulingMode: "finder-barrier",
|
|
112
93
|
perLens: PER_LENS,
|
|
113
|
-
tools: TOOLS,
|
|
114
|
-
toolHints: TOOL_HINTS,
|
|
115
94
|
finderPrompt: (lens) =>
|
|
116
95
|
`## Perf-review finder — ${lens.label}\n\n${scopeBlock}\n` +
|
|
117
96
|
"This workflow is advisory-only: identify bottleneck hypotheses, measurement gaps, and safe optimization directions, but do not edit files.\n" +
|
|
@@ -124,67 +103,46 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
124
103
|
`Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n` +
|
|
125
104
|
`Recommendation: ${candidate.recommendation ?? "(none supplied)"}\n\n` +
|
|
126
105
|
"Read relevant files and package/scripts. Run only safe read-only measurement or inspection commands when useful. " +
|
|
127
|
-
"Return CONFIRMED, PLAUSIBLE, or REFUTED with evidence from code, scripts, config, or measurement output. " +
|
|
106
|
+
"Return CONFIRMED, PLAUSIBLE, NOT_SUBSTANTIATED, or REFUTED with evidence from code, scripts, config, or measurement output. " +
|
|
128
107
|
"Default toward PLAUSIBLE or REFUTED when no measurement exists; do not overstate a bottleneck. Structured output only.",
|
|
129
|
-
makeVerified: (candidate, lens, judged): Verified => ({
|
|
130
|
-
...candidate,
|
|
131
|
-
verdict: judged.verdict,
|
|
132
|
-
evidence: judged.evidence,
|
|
133
|
-
confidence: judged.confidence,
|
|
134
|
-
lens,
|
|
135
|
-
}),
|
|
136
108
|
});
|
|
137
109
|
rawCandidateCount += pipelineResult.rawCandidates;
|
|
138
110
|
droppedCandidateCount += pipelineResult.dropped;
|
|
139
111
|
refutedCandidateCount += pipelineResult.refuted;
|
|
140
|
-
const
|
|
112
|
+
const { coverage } = pipelineResult;
|
|
113
|
+
const verified = await challengeFindings(api, pipelineResult.verified, scopeBlock, challengeConfig.options, coverage);
|
|
141
114
|
const surviving = verified.filter((finding) => finding.verdict !== "REFUTED");
|
|
142
115
|
const stats = makeStats(verified.length, surviving.length);
|
|
143
116
|
publishVerifiedKeptProgress({ progress, log }, verified.length, surviving.length);
|
|
144
117
|
|
|
145
118
|
if (surviving.length === 0) {
|
|
146
|
-
return emptyAdvisoryReport(
|
|
119
|
+
return finishAdvisoryReport(emptyAdvisoryReport(
|
|
147
120
|
"No performance finding survived verification.",
|
|
148
121
|
["Add or run a focused measurement for the target workload before optimizing.", "Rerun perf-review with benchmark output or a narrower slow path."],
|
|
149
122
|
stats,
|
|
150
|
-
);
|
|
123
|
+
), coverage, verified);
|
|
151
124
|
}
|
|
152
125
|
|
|
153
|
-
phase("Synthesize");
|
|
154
126
|
const ranked = [...surviving].sort((a, b) => rank(a) - rank(b));
|
|
155
127
|
const block = ranked
|
|
156
128
|
.map(
|
|
157
129
|
(finding, index) =>
|
|
158
|
-
`### [${index}] ${formatLocation(finding)} (${finding.verdict}, ${finding.category})\n` +
|
|
130
|
+
`### [${index}] IDs: ${finding.sourceCandidateIds.join(", ")} ${formatLocation(finding)} (${finding.verdict}, ${finding.category})\n` +
|
|
159
131
|
`${finding.summary}\nImpact: ${finding.impact}\nEvidence: ${formatEvidence(finding.evidence)}\nRecommendation: ${finding.recommendation ?? "(none supplied)"}`,
|
|
160
132
|
)
|
|
161
133
|
.join("\n\n");
|
|
162
134
|
|
|
163
|
-
const
|
|
135
|
+
const resolved = await synthesizeAdvisoryReport(api,
|
|
164
136
|
`## Synthesis: final perf-review report\n\n${ranked.length} candidates survived independent verification.\n\n${block}\n\n` +
|
|
165
|
-
"
|
|
137
|
+
"Select findings by ID. " +
|
|
166
138
|
"Severity is expected performance impact for the target workload. Prefer measurement recommendations before optimization recommendations when evidence is weak. " +
|
|
167
139
|
"Recommendations must be safe advisory next actions, not patches. Include risky optimizations to avoid in recommendations or nextSteps when relevant. Structured output only.",
|
|
168
|
-
|
|
169
|
-
phase: "Synthesize",
|
|
170
|
-
label: "synthesize",
|
|
171
|
-
tools: [],
|
|
172
|
-
profile: "medium",
|
|
173
|
-
resume: "read-only",
|
|
174
|
-
schema: AdvisoryReportSchema,
|
|
175
|
-
},
|
|
140
|
+
ranked, coverage,
|
|
176
141
|
);
|
|
177
|
-
|
|
178
|
-
if (!report) return emptyAdvisoryReport("Synthesis produced no output.", ["Inspect verifier evidence manually or rerun perf-review with a narrower target."], stats);
|
|
179
|
-
|
|
180
|
-
const findings = backfillAdvisoryFindings(report.findings, ranked, {
|
|
181
|
-
impact: "Performance impact not restated by synthesis.",
|
|
182
|
-
recommendation: "Measure the target workload before changing code.",
|
|
183
|
-
});
|
|
184
|
-
return { ...report, findings, stats };
|
|
142
|
+
return finishAdvisoryReport({ ...resolved, stats: { ...stats, kept: resolved.findings.length } }, coverage, verified);
|
|
185
143
|
}
|
|
186
144
|
|
|
187
|
-
function rank(finding:
|
|
145
|
+
function rank(finding: AdvisoryVerified): number {
|
|
188
146
|
const verdictRank = finding.verdict === "CONFIRMED" ? 0 : 1;
|
|
189
147
|
const measurementPenalty = finding.category === "measurement" ? 1 : 0;
|
|
190
148
|
return verdictRank + measurementPenalty;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
+
import { challengeFindings, parseChallengeArgs } from "../src/advisory-challenge.ts";
|
|
1
2
|
import { Type } from "typebox";
|
|
2
3
|
import {
|
|
3
|
-
|
|
4
|
-
type
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
backfillAdvisoryFindings,
|
|
4
|
+
type AdvisoryVerified,
|
|
5
|
+
type AdvisoryLens,
|
|
6
|
+
synthesizeAdvisoryReport,
|
|
7
|
+
finishAdvisoryReport,
|
|
9
8
|
emptyAdvisoryReport,
|
|
10
9
|
formatEvidence,
|
|
11
10
|
formatLocation,
|
|
@@ -19,7 +18,7 @@ import type { WorkflowApi, WorkflowMeta, WorkflowRunStats } from "../src/types.t
|
|
|
19
18
|
export const meta: WorkflowMeta = {
|
|
20
19
|
name: "refactor-scout",
|
|
21
20
|
description: "Advisory-only refactor scout: scope → per-lens find → independent verify → synthesize safe refactor opportunities.",
|
|
22
|
-
phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Synthesize" }],
|
|
21
|
+
phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Challenge" }, { title: "Synthesize" }],
|
|
23
22
|
};
|
|
24
23
|
|
|
25
24
|
const ScopeSchema = Type.Object({
|
|
@@ -29,22 +28,7 @@ const ScopeSchema = Type.Object({
|
|
|
29
28
|
conventions: Type.Optional(Type.String({ description: "Relevant project conventions from AGENTS.md / docs." })),
|
|
30
29
|
});
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
label: string;
|
|
34
|
-
category: string;
|
|
35
|
-
text: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
type Candidate = AdvisoryCandidate;
|
|
39
|
-
|
|
40
|
-
interface Verified extends Candidate {
|
|
41
|
-
verdict: AdvisoryVerdict["verdict"];
|
|
42
|
-
evidence: string[];
|
|
43
|
-
confidence?: AdvisoryVerdict["confidence"];
|
|
44
|
-
lens: RefactorLens;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const REFACTOR_LENSES: RefactorLens[] = [
|
|
31
|
+
const REFACTOR_LENSES: AdvisoryLens[] = [
|
|
48
32
|
{ label: "duplication", category: "duplication", text: "Repeated logic, copy-pasted structures, or near-duplicate flows that could share one clearer implementation." },
|
|
49
33
|
{ label: "complexity", category: "complexity", text: "Oversized functions, tangled control flow, or abstractions that make local reasoning harder than necessary." },
|
|
50
34
|
{ label: "type-safety", category: "type-safety", text: "Weak typing, avoidable casts, unchecked shapes, or places stronger types would prevent mistakes." },
|
|
@@ -53,13 +37,12 @@ const REFACTOR_LENSES: RefactorLens[] = [
|
|
|
53
37
|
{ label: "conventions", category: "conventions", text: "Departures from project conventions, naming, dependency rules, or local idioms." },
|
|
54
38
|
];
|
|
55
39
|
|
|
56
|
-
const TOOLS = DEFAULT_ADVISORY_TOOLS;
|
|
57
|
-
const TOOL_HINTS = DEFAULT_ADVISORY_TOOL_HINTS;
|
|
58
40
|
const PER_LENS = 5;
|
|
59
41
|
|
|
60
42
|
export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
61
|
-
const { agent,
|
|
62
|
-
const
|
|
43
|
+
const { agent, phase, log, progress, args } = api;
|
|
44
|
+
const challengeConfig = parseChallengeArgs(args);
|
|
45
|
+
const target = challengeConfig.args.trim() || ".";
|
|
63
46
|
let fileCount = 0;
|
|
64
47
|
let rawCandidateCount = 0;
|
|
65
48
|
let droppedCandidateCount = 0;
|
|
@@ -80,15 +63,15 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
80
63
|
"Inspect repository structure, the target path or module, and relevant AGENTS.md / project docs conventions. " +
|
|
81
64
|
"Return the concrete files that should be considered, a short summary, and any conventions that affect refactor advice. " +
|
|
82
65
|
`This workflow will fan out across ${REFACTOR_LENSES.length} lenses with up to ${PER_LENS} candidates per lens. Structured output only.`,
|
|
83
|
-
{ phase: "Scope", label: "scope", tools:
|
|
66
|
+
{ phase: "Scope", label: "scope", tools: DEFAULT_ADVISORY_TOOLS, toolHints: DEFAULT_ADVISORY_TOOL_HINTS, profile: "medium", schema: ScopeSchema },
|
|
84
67
|
);
|
|
85
68
|
|
|
86
69
|
if (!scope || scope.files.length === 0) {
|
|
87
|
-
return emptyAdvisoryReport(
|
|
70
|
+
return finishAdvisoryReport(emptyAdvisoryReport(
|
|
88
71
|
"No files were identified for refactor scouting.",
|
|
89
72
|
["Provide a target path, module, or subsystem to scout for refactor opportunities."],
|
|
90
73
|
makeStats(0, 0),
|
|
91
|
-
);
|
|
74
|
+
), []);
|
|
92
75
|
}
|
|
93
76
|
|
|
94
77
|
fileCount = scope.files.length;
|
|
@@ -101,14 +84,10 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
101
84
|
`## Summary\n${scope.summary}\n\n## Conventions\n${scope.conventions ?? "(none noted)"}\n` +
|
|
102
85
|
(args.trim() ? `\n## User instructions (verbatim)\n${args.trim()}\n` : "");
|
|
103
86
|
|
|
104
|
-
phase("Find");
|
|
105
87
|
const pipelineResult = await runLensVerificationPipeline({
|
|
106
|
-
api
|
|
88
|
+
api,
|
|
107
89
|
lenses: REFACTOR_LENSES,
|
|
108
|
-
schedulingMode: "finder-barrier",
|
|
109
90
|
perLens: PER_LENS,
|
|
110
|
-
tools: TOOLS,
|
|
111
|
-
toolHints: TOOL_HINTS,
|
|
112
91
|
finderPrompt: (lens) =>
|
|
113
92
|
`## Refactor-scout finder — ${lens.label}\n\n${scopeBlock}\n` +
|
|
114
93
|
"This workflow is advisory-only: do not edit files and do not propose broad rewrites.\n" +
|
|
@@ -120,65 +99,44 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
120
99
|
`## Refactor-scout verifier\n\n${scopeBlock}\n## Candidate\n` +
|
|
121
100
|
`Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n` +
|
|
122
101
|
`Recommendation: ${candidate.recommendation ?? "(none supplied)"}\n\n` +
|
|
123
|
-
"Read the relevant files and return CONFIRMED, PLAUSIBLE, or REFUTED. " +
|
|
102
|
+
"Read the relevant files and return CONFIRMED, PLAUSIBLE, NOT_SUBSTANTIATED, or REFUTED. " +
|
|
124
103
|
"Default toward REFUTED if the opportunity is generic, too broad, not evidenced by code, or lacks a safe first step. " +
|
|
125
104
|
"Evidence must quote or cite code. Structured output only.",
|
|
126
|
-
makeVerified: (candidate, lens, judged): Verified => ({
|
|
127
|
-
...candidate,
|
|
128
|
-
verdict: judged.verdict,
|
|
129
|
-
evidence: judged.evidence,
|
|
130
|
-
confidence: judged.confidence,
|
|
131
|
-
lens,
|
|
132
|
-
}),
|
|
133
105
|
});
|
|
134
106
|
rawCandidateCount += pipelineResult.rawCandidates;
|
|
135
107
|
droppedCandidateCount += pipelineResult.dropped;
|
|
136
108
|
refutedCandidateCount += pipelineResult.refuted;
|
|
137
|
-
const
|
|
109
|
+
const { coverage } = pipelineResult;
|
|
110
|
+
const verified = await challengeFindings(api, pipelineResult.verified, scopeBlock, challengeConfig.options, coverage);
|
|
138
111
|
const surviving = verified.filter((finding) => finding.verdict !== "REFUTED");
|
|
139
112
|
const stats = makeStats(verified.length, surviving.length);
|
|
140
113
|
publishVerifiedKeptProgress({ progress, log }, verified.length, surviving.length);
|
|
141
114
|
|
|
142
115
|
if (surviving.length === 0) {
|
|
143
|
-
return emptyAdvisoryReport("No refactor opportunities survived verification.", ["Leave the scoped code unchanged unless a human reviewer has additional context."], stats);
|
|
116
|
+
return finishAdvisoryReport(emptyAdvisoryReport("No refactor opportunities survived verification.", ["Leave the scoped code unchanged unless a human reviewer has additional context."], stats), coverage, verified);
|
|
144
117
|
}
|
|
145
118
|
|
|
146
|
-
phase("Synthesize");
|
|
147
119
|
const ranked = [...surviving].sort((a, b) => rank(a) - rank(b));
|
|
148
120
|
const block = ranked
|
|
149
121
|
.map(
|
|
150
122
|
(finding, index) =>
|
|
151
|
-
`### [${index}] ${formatLocation(finding)} (${finding.verdict}, ${finding.category})\n` +
|
|
123
|
+
`### [${index}] IDs: ${finding.sourceCandidateIds.join(", ")} ${formatLocation(finding)} (${finding.verdict}, ${finding.category})\n` +
|
|
152
124
|
`${finding.summary}\nImpact: ${finding.impact}\nEvidence: ${formatEvidence(finding.evidence)}\nSafe first step: ${finding.recommendation ?? "(none supplied)"}`,
|
|
153
125
|
)
|
|
154
126
|
.join("\n\n");
|
|
155
127
|
|
|
156
|
-
const
|
|
128
|
+
const resolved = await synthesizeAdvisoryReport(api,
|
|
157
129
|
`## Synthesis: final refactor-scout report\n\n${ranked.length} opportunities survived independent verification.\n\n${block}\n\n` +
|
|
158
130
|
"Merge findings with the same root cause and rank highest leverage / lowest risk first. " +
|
|
159
|
-
"
|
|
160
|
-
"Severity is maintenance or future-correctness impact.
|
|
131
|
+
"Select findings by ID. " +
|
|
132
|
+
"Severity is maintenance or future-correctness impact. " +
|
|
161
133
|
"Recommendations must be safe first refactor steps, not rewrites. Include concrete nextSteps for the host developer. Structured output only.",
|
|
162
|
-
|
|
163
|
-
phase: "Synthesize",
|
|
164
|
-
label: "synthesize",
|
|
165
|
-
tools: [],
|
|
166
|
-
profile: "medium",
|
|
167
|
-
resume: "read-only",
|
|
168
|
-
schema: AdvisoryReportSchema,
|
|
169
|
-
},
|
|
134
|
+
ranked, coverage,
|
|
170
135
|
);
|
|
171
|
-
|
|
172
|
-
if (!report) return emptyAdvisoryReport("Synthesis produced no output.", ["Inspect verifier evidence manually or rerun the workflow with a narrower target."], stats);
|
|
173
|
-
|
|
174
|
-
const findings = backfillAdvisoryFindings(report.findings, ranked, {
|
|
175
|
-
impact: "Impact not restated by synthesis.",
|
|
176
|
-
recommendation: "Choose a small, behavior-preserving refactor first step.",
|
|
177
|
-
});
|
|
178
|
-
return { ...report, findings, stats };
|
|
136
|
+
return finishAdvisoryReport({ ...resolved, stats: { ...stats, kept: resolved.findings.length } }, coverage, verified);
|
|
179
137
|
}
|
|
180
138
|
|
|
181
|
-
function rank(finding:
|
|
139
|
+
function rank(finding: AdvisoryVerified): number {
|
|
182
140
|
const verdictRank = finding.verdict === "CONFIRMED" ? 0 : 1;
|
|
183
141
|
const categoryRank = finding.category === "dead-code" || finding.category === "conventions" ? 2 : 0;
|
|
184
142
|
return verdictRank + categoryRank;
|
package/USAGE.md
CHANGED
|
@@ -543,3 +543,62 @@ failures, timeouts, and host aborts are never provider-retried.
|
|
|
543
543
|
- **Slow run**: lower fan-out, set `thinkingLevel`, or reduce `--concurrency`.
|
|
544
544
|
- **Budget exhausted**: narrow the target, raise `--budget`, reduce fan-out/concurrency, or guard custom loops with `api.budget.remaining()`.
|
|
545
545
|
- **Duplicate command/tool warnings while developing**: avoid loading both the global package and the working copy.
|
|
546
|
+
|
|
547
|
+
### Advisory evidence and coverage
|
|
548
|
+
|
|
549
|
+
`code-review`, `diagnose`, `refactor-scout`, and `perf-review` retain candidate IDs
|
|
550
|
+
from discovery through verification and synthesis. Findings contain
|
|
551
|
+
`sourceCandidateIds`; merged findings retain all contributing IDs, locations, and
|
|
552
|
+
evidence. Code review uses `reviewAnchor` for the changed line that caused the
|
|
553
|
+
finding. Other `locations` can identify unchanged callers or related files.
|
|
554
|
+
|
|
555
|
+
These results include `status` (`complete` or `incomplete`), `coverage` with
|
|
556
|
+
expected/completed/failed branch counts and failure reasons, `gaps`, and the
|
|
557
|
+
`verification` records. Failed verification is not a refutation. Verifiers use
|
|
558
|
+
`NOT_SUBSTANTIATED` when evidence is insufficient and `REFUTED` for concrete
|
|
559
|
+
disproof. A failed finder or verifier prevents a clean conclusion. If synthesis
|
|
560
|
+
fails, the result retains verified records and reports incomplete coverage.
|
|
561
|
+
Synthesis selects or merges IDs; the workflow reconstructs evidence-bearing
|
|
562
|
+
fields from those records.
|
|
563
|
+
|
|
564
|
+
### Selective adversarial challenges
|
|
565
|
+
|
|
566
|
+
Add `--challenge` to an advisory workflow to challenge up to three uncertain or
|
|
567
|
+
high-risk findings. `--challenge=N` requires a non-negative integer and sets the
|
|
568
|
+
limit, capped at ten; zero disables it. Invalid values stop the workflow before
|
|
569
|
+
agents start. Ordinary confirmed, low-risk findings bypass this stage.
|
|
570
|
+
|
|
571
|
+
```text
|
|
572
|
+
/workflow code-review --challenge=2
|
|
573
|
+
/workflow diagnose --challenge failing retry test
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
The challenger tries to disprove the finding or identify an alternative cause.
|
|
577
|
+
A separate adjudicator receives both sides. Unresolved disagreement remains
|
|
578
|
+
`NOT_SUBSTANTIATED`; finding no counterexample does not upgrade confidence.
|
|
579
|
+
Custom workflows can use the `challengeFindings` recipe with `maxChallenges`
|
|
580
|
+
and a `shouldChallenge` predicate, including selection based on domain-specific
|
|
581
|
+
severity. It uses the existing `agent` and settled `parallel` calls.
|
|
582
|
+
|
|
583
|
+
### Independently validated repair candidates
|
|
584
|
+
|
|
585
|
+
The results viewer's fix action produces candidate patches in disposable
|
|
586
|
+
worktrees. Each candidate retains the implementer's report separately from its
|
|
587
|
+
`validation` record: reviewed baseline fingerprint, baseline commit, patch
|
|
588
|
+
SHA-256, evaluator decision, executed commands, output, and process results.
|
|
589
|
+
|
|
590
|
+
A fresh evaluator receives the exact reviewed baseline plus captured patch.
|
|
591
|
+
The engine reconstructs another workspace and executes the evaluator's focused
|
|
592
|
+
checks. Where applicable, a test-only patch and expected failure text let it
|
|
593
|
+
check that a regression fails on the original baseline and passes after repair.
|
|
594
|
+
Outcomes are `verified`, `rejected`, `blocked`, or `no-patch`. Missing required
|
|
595
|
+
validation blocks verification. An empty patch does not prove that a finding
|
|
596
|
+
needs no repair. Rejected and blocked candidates retain their patch and failure
|
|
597
|
+
evidence for inspection. Cleanup failures also retain the validation evidence and
|
|
598
|
+
record a `cleanupError`; a passed candidate becomes `blocked` if cleanup fails,
|
|
599
|
+
while a rejected candidate remains `rejected`.
|
|
600
|
+
|
|
601
|
+
Isolated agent results now include `baselineOid`. For independent evaluation,
|
|
602
|
+
`agent()` accepts `candidatePatch: { baselineOid, patch }` with
|
|
603
|
+
`isolation: "worktree"`; the engine checks the fresh baseline before applying
|
|
604
|
+
it. Worktree isolation separates filesystem state; it is not a security sandbox.
|
package/package.json
CHANGED