pi-workflow-engine 0.13.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 +1 -1
- package/.pi/extensions/pi-workflow-engine/src/advisory-evidence.ts +8 -8
- package/.pi/extensions/pi-workflow-engine/src/advisory-schema.ts +7 -1
- 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 +4 -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 +25 -80
- 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 +0 -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 +75 -139
- 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 +3 -4
- 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 +7 -9
- package/.pi/extensions/pi-workflow-engine/src/review/review-format.ts +51 -117
- package/.pi/extensions/pi-workflow-engine/src/review/review-handoff.ts +1 -1
- package/.pi/extensions/pi-workflow-engine/src/review/review-issues.ts +8 -13
- 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 +6 -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 -129
- 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 +56 -19
- 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 +48 -110
- package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +64 -93
- package/.pi/extensions/pi-workflow-engine/workflows/diagnose.ts +25 -67
- package/.pi/extensions/pi-workflow-engine/workflows/perf-review.ts +19 -53
- package/.pi/extensions/pi-workflow-engine/workflows/refactor-scout.ts +21 -49
- package/.pi/extensions/pi-workflow-engine/workflows/research.ts +74 -66
- package/USAGE.md +31 -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 -24
- package/.pi/extensions/pi-workflow-engine/src/review/review-results-flow.ts +0 -52
- package/.pi/settings.json +0 -3
|
@@ -3,17 +3,15 @@ import { Type } from "typebox";
|
|
|
3
3
|
import {
|
|
4
4
|
type AdvisoryVerified,
|
|
5
5
|
type AdvisoryLens,
|
|
6
|
-
|
|
7
|
-
finishAdvisoryReport,
|
|
8
|
-
emptyAdvisoryReport,
|
|
6
|
+
concludeLensReview,
|
|
9
7
|
formatEvidence,
|
|
10
8
|
formatLocation,
|
|
11
|
-
|
|
9
|
+
formatRankedFinding,
|
|
12
10
|
runLensVerificationPipeline,
|
|
13
11
|
DEFAULT_ADVISORY_TOOL_HINTS,
|
|
14
12
|
DEFAULT_ADVISORY_TOOLS,
|
|
15
13
|
} from "../src/workflow-advisory-utils.ts";
|
|
16
|
-
import type { WorkflowApi, WorkflowMeta
|
|
14
|
+
import type { WorkflowApi, WorkflowMeta } from "../src/types.ts";
|
|
17
15
|
|
|
18
16
|
export const meta: WorkflowMeta = {
|
|
19
17
|
name: "diagnose",
|
|
@@ -42,19 +40,7 @@ const PER_LENS = 4;
|
|
|
42
40
|
export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
43
41
|
const { agent, phase, log, progress, args } = api;
|
|
44
42
|
const challengeConfig = parseChallengeArgs(args);
|
|
45
|
-
const symptom = challengeConfig.args
|
|
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
|
-
});
|
|
43
|
+
const symptom = challengeConfig.args;
|
|
58
44
|
|
|
59
45
|
phase("Scope");
|
|
60
46
|
const scope = await agent(
|
|
@@ -68,16 +54,7 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
68
54
|
{ phase: "Scope", label: "scope", tools: DEFAULT_ADVISORY_TOOLS, toolHints: DEFAULT_ADVISORY_TOOL_HINTS, profile: "medium", schema: ScopeSchema },
|
|
69
55
|
);
|
|
70
56
|
|
|
71
|
-
|
|
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 });
|
|
57
|
+
progress({ type: "counter", key: "files", label: "files", value: scope.files.length });
|
|
81
58
|
progress({ type: "summary", key: "symptom", value: scope.symptom });
|
|
82
59
|
progress({ type: "summary", key: "files", value: scope.files.join(", ") || "(none)" });
|
|
83
60
|
log(`${scope.files.length} files scoped for diagnosis`);
|
|
@@ -107,45 +84,26 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
107
84
|
"Do not run mutation, install, commit, network, or destructive commands. Return CONFIRMED, PLAUSIBLE, NOT_SUBSTANTIATED, or REFUTED with evidence. " +
|
|
108
85
|
"Use NOT_SUBSTANTIATED when evidence is missing; REFUTED requires disproof. Structured output only.",
|
|
109
86
|
});
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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);
|
|
87
|
+
const verified = await challengeFindings(api, pipelineResult.verified, scopeBlock, challengeConfig.options, pipelineResult.coverage);
|
|
88
|
+
return concludeLensReview(api, pipelineResult, verified, {
|
|
89
|
+
files: scope.files.length,
|
|
90
|
+
rank,
|
|
91
|
+
formatFinding: (finding, index) => formatRankedFinding(finding, index, "Validation/fix plan"),
|
|
92
|
+
empty: {
|
|
93
|
+
summary: "No root-cause hypothesis survived verification.",
|
|
94
|
+
nextSteps: ["Capture the exact failing command and error output.", "Rerun diagnose with a narrower symptom or more evidence."],
|
|
95
|
+
},
|
|
96
|
+
synthesisPrompt: (block, ranked, refuted) => {
|
|
97
|
+
const refutedBlock = refuted
|
|
98
|
+
.slice(0, 8)
|
|
99
|
+
.map((finding) => `- ${finding.summary} — REFUTED because ${formatEvidence(finding.evidence)}`)
|
|
100
|
+
.join("\n");
|
|
101
|
+
return `## Synthesis: final diagnosis report\n\n${ranked.length} hypotheses survived independent verification.\n\n${block}\n\n` +
|
|
102
|
+
`## Refuted hypotheses for context\n${refutedBlock || "(none recorded)"}\n\n` +
|
|
103
|
+
"Select confirmed, plausible or explicitly unresolved root causes by ID. " +
|
|
104
|
+
"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.";
|
|
105
|
+
},
|
|
106
|
+
});
|
|
149
107
|
}
|
|
150
108
|
|
|
151
109
|
function rank(finding: AdvisoryVerified): number {
|
|
@@ -3,17 +3,16 @@ import { Type } from "typebox";
|
|
|
3
3
|
import {
|
|
4
4
|
type AdvisoryVerified,
|
|
5
5
|
type AdvisoryLens,
|
|
6
|
-
|
|
6
|
+
concludeLensReview,
|
|
7
7
|
finishAdvisoryReport,
|
|
8
8
|
emptyAdvisoryReport,
|
|
9
|
-
|
|
9
|
+
EMPTY_LENS_REVIEW_STATS,
|
|
10
10
|
formatLocation,
|
|
11
|
-
publishVerifiedKeptProgress,
|
|
12
11
|
runLensVerificationPipeline,
|
|
13
12
|
DEFAULT_ADVISORY_TOOL_HINTS,
|
|
14
13
|
DEFAULT_ADVISORY_TOOLS,
|
|
15
14
|
} from "../src/workflow-advisory-utils.ts";
|
|
16
|
-
import type { WorkflowApi, WorkflowMeta
|
|
15
|
+
import type { WorkflowApi, WorkflowMeta } from "../src/types.ts";
|
|
17
16
|
|
|
18
17
|
export const meta: WorkflowMeta = {
|
|
19
18
|
name: "perf-review",
|
|
@@ -43,19 +42,7 @@ const PER_LENS = 4;
|
|
|
43
42
|
export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
44
43
|
const { agent, phase, log, progress, args } = api;
|
|
45
44
|
const challengeConfig = parseChallengeArgs(args);
|
|
46
|
-
const target = challengeConfig.args
|
|
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
|
-
});
|
|
45
|
+
const target = challengeConfig.args || "repository performance";
|
|
59
46
|
|
|
60
47
|
phase("Scope");
|
|
61
48
|
const scope = await agent(
|
|
@@ -67,16 +54,15 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
67
54
|
{ phase: "Scope", label: "scope", tools: DEFAULT_ADVISORY_TOOLS, toolHints: DEFAULT_ADVISORY_TOOL_HINTS, profile: "medium", schema: ScopeSchema },
|
|
68
55
|
);
|
|
69
56
|
|
|
70
|
-
if (
|
|
57
|
+
if (scope.files.length === 0) {
|
|
71
58
|
return finishAdvisoryReport(emptyAdvisoryReport(
|
|
72
59
|
"No performance-relevant files were identified.",
|
|
73
60
|
["Provide a slow command, workload, file path, or user-visible latency concern to review."],
|
|
74
|
-
|
|
61
|
+
EMPTY_LENS_REVIEW_STATS,
|
|
75
62
|
), []);
|
|
76
63
|
}
|
|
77
64
|
|
|
78
|
-
|
|
79
|
-
progress({ type: "counter", key: "files", label: "files", value: fileCount });
|
|
65
|
+
progress({ type: "counter", key: "files", label: "files", value: scope.files.length });
|
|
80
66
|
progress({ type: "summary", key: "target", value: scope.target });
|
|
81
67
|
progress({ type: "summary", key: "files", value: scope.files.join(", ") });
|
|
82
68
|
log(`${scope.files.length} files scoped for performance review`);
|
|
@@ -85,7 +71,7 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
85
71
|
`## Target\n${scope.target}\n\n## Files\n${scope.files.map((file) => `- ${file}`).join("\n")}\n\n` +
|
|
86
72
|
`## Measurement commands\n${scope.commands.map((command) => `- ${command}`).join("\n") || "(none identified)"}\n\n` +
|
|
87
73
|
`## Summary\n${scope.summary}\n\n## Known measurements\n${scope.knownMeasurements ?? "(none known)"}\n` +
|
|
88
|
-
(args
|
|
74
|
+
(challengeConfig.args ? `\n## User instructions (verbatim)\n${challengeConfig.args}\n` : "");
|
|
89
75
|
|
|
90
76
|
const pipelineResult = await runLensVerificationPipeline({
|
|
91
77
|
api,
|
|
@@ -106,40 +92,20 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
106
92
|
"Return CONFIRMED, PLAUSIBLE, NOT_SUBSTANTIATED, or REFUTED with evidence from code, scripts, config, or measurement output. " +
|
|
107
93
|
"Default toward PLAUSIBLE or REFUTED when no measurement exists; do not overstate a bottleneck. Structured output only.",
|
|
108
94
|
});
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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` +
|
|
95
|
+
const verified = await challengeFindings(api, pipelineResult.verified, scopeBlock, challengeConfig.options, pipelineResult.coverage);
|
|
96
|
+
return concludeLensReview(api, pipelineResult, verified, {
|
|
97
|
+
files: scope.files.length,
|
|
98
|
+
rank,
|
|
99
|
+
empty: {
|
|
100
|
+
summary: "No performance finding survived verification.",
|
|
101
|
+
nextSteps: ["Add or run a focused measurement for the target workload before optimizing.", "Rerun perf-review with benchmark output or a narrower slow path."],
|
|
102
|
+
},
|
|
103
|
+
synthesisPrompt: (block, ranked) =>
|
|
104
|
+
`## Synthesis: final perf-review report\n\n${ranked.length} candidates survived independent verification.\n\n${block}\n\n` +
|
|
137
105
|
"Select findings by ID. " +
|
|
138
106
|
"Severity is expected performance impact for the target workload. Prefer measurement recommendations before optimization recommendations when evidence is weak. " +
|
|
139
107
|
"Recommendations must be safe advisory next actions, not patches. Include risky optimizations to avoid in recommendations or nextSteps when relevant. Structured output only.",
|
|
140
|
-
|
|
141
|
-
);
|
|
142
|
-
return finishAdvisoryReport({ ...resolved, stats: { ...stats, kept: resolved.findings.length } }, coverage, verified);
|
|
108
|
+
});
|
|
143
109
|
}
|
|
144
110
|
|
|
145
111
|
function rank(finding: AdvisoryVerified): number {
|
|
@@ -3,17 +3,17 @@ import { Type } from "typebox";
|
|
|
3
3
|
import {
|
|
4
4
|
type AdvisoryVerified,
|
|
5
5
|
type AdvisoryLens,
|
|
6
|
-
|
|
6
|
+
concludeLensReview,
|
|
7
7
|
finishAdvisoryReport,
|
|
8
8
|
emptyAdvisoryReport,
|
|
9
|
-
|
|
9
|
+
EMPTY_LENS_REVIEW_STATS,
|
|
10
10
|
formatLocation,
|
|
11
|
-
|
|
11
|
+
formatRankedFinding,
|
|
12
12
|
runLensVerificationPipeline,
|
|
13
13
|
DEFAULT_ADVISORY_TOOL_HINTS,
|
|
14
14
|
DEFAULT_ADVISORY_TOOLS,
|
|
15
15
|
} from "../src/workflow-advisory-utils.ts";
|
|
16
|
-
import type { WorkflowApi, WorkflowMeta
|
|
16
|
+
import type { WorkflowApi, WorkflowMeta } from "../src/types.ts";
|
|
17
17
|
|
|
18
18
|
export const meta: WorkflowMeta = {
|
|
19
19
|
name: "refactor-scout",
|
|
@@ -42,19 +42,7 @@ const PER_LENS = 5;
|
|
|
42
42
|
export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
43
43
|
const { agent, phase, log, progress, args } = api;
|
|
44
44
|
const challengeConfig = parseChallengeArgs(args);
|
|
45
|
-
const target = challengeConfig.args
|
|
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
|
-
});
|
|
45
|
+
const target = challengeConfig.args || ".";
|
|
58
46
|
|
|
59
47
|
phase("Scope");
|
|
60
48
|
const scope = await agent(
|
|
@@ -66,23 +54,22 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
66
54
|
{ phase: "Scope", label: "scope", tools: DEFAULT_ADVISORY_TOOLS, toolHints: DEFAULT_ADVISORY_TOOL_HINTS, profile: "medium", schema: ScopeSchema },
|
|
67
55
|
);
|
|
68
56
|
|
|
69
|
-
if (
|
|
57
|
+
if (scope.files.length === 0) {
|
|
70
58
|
return finishAdvisoryReport(emptyAdvisoryReport(
|
|
71
59
|
"No files were identified for refactor scouting.",
|
|
72
60
|
["Provide a target path, module, or subsystem to scout for refactor opportunities."],
|
|
73
|
-
|
|
61
|
+
EMPTY_LENS_REVIEW_STATS,
|
|
74
62
|
), []);
|
|
75
63
|
}
|
|
76
64
|
|
|
77
|
-
|
|
78
|
-
progress({ type: "counter", key: "files", label: "files", value: fileCount });
|
|
65
|
+
progress({ type: "counter", key: "files", label: "files", value: scope.files.length });
|
|
79
66
|
progress({ type: "summary", key: "files", value: scope.files.join(", ") });
|
|
80
67
|
log(`${scope.files.length} files scoped for refactor scouting`);
|
|
81
68
|
|
|
82
69
|
const scopeBlock =
|
|
83
70
|
`## Target\n${scope.target}\n\n## Files in scope\n${scope.files.map((file) => `- ${file}`).join("\n")}\n\n` +
|
|
84
71
|
`## Summary\n${scope.summary}\n\n## Conventions\n${scope.conventions ?? "(none noted)"}\n` +
|
|
85
|
-
(args
|
|
72
|
+
(challengeConfig.args ? `\n## User instructions (verbatim)\n${challengeConfig.args}\n` : "");
|
|
86
73
|
|
|
87
74
|
const pipelineResult = await runLensVerificationPipeline({
|
|
88
75
|
api,
|
|
@@ -103,37 +90,22 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
103
90
|
"Default toward REFUTED if the opportunity is generic, too broad, not evidenced by code, or lacks a safe first step. " +
|
|
104
91
|
"Evidence must quote or cite code. Structured output only.",
|
|
105
92
|
});
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const ranked = [...surviving].sort((a, b) => rank(a) - rank(b));
|
|
120
|
-
const block = ranked
|
|
121
|
-
.map(
|
|
122
|
-
(finding, index) =>
|
|
123
|
-
`### [${index}] IDs: ${finding.sourceCandidateIds.join(", ")} ${formatLocation(finding)} (${finding.verdict}, ${finding.category})\n` +
|
|
124
|
-
`${finding.summary}\nImpact: ${finding.impact}\nEvidence: ${formatEvidence(finding.evidence)}\nSafe first step: ${finding.recommendation ?? "(none supplied)"}`,
|
|
125
|
-
)
|
|
126
|
-
.join("\n\n");
|
|
127
|
-
|
|
128
|
-
const resolved = await synthesizeAdvisoryReport(api,
|
|
129
|
-
`## Synthesis: final refactor-scout report\n\n${ranked.length} opportunities survived independent verification.\n\n${block}\n\n` +
|
|
93
|
+
const verified = await challengeFindings(api, pipelineResult.verified, scopeBlock, challengeConfig.options, pipelineResult.coverage);
|
|
94
|
+
return concludeLensReview(api, pipelineResult, verified, {
|
|
95
|
+
files: scope.files.length,
|
|
96
|
+
rank,
|
|
97
|
+
formatFinding: (finding, index) => formatRankedFinding(finding, index, "Safe first step"),
|
|
98
|
+
empty: {
|
|
99
|
+
summary: "No refactor opportunities survived verification.",
|
|
100
|
+
nextSteps: ["Leave the scoped code unchanged unless a human reviewer has additional context."],
|
|
101
|
+
},
|
|
102
|
+
synthesisPrompt: (block, ranked) =>
|
|
103
|
+
`## Synthesis: final refactor-scout report\n\n${ranked.length} opportunities survived independent verification.\n\n${block}\n\n` +
|
|
130
104
|
"Merge findings with the same root cause and rank highest leverage / lowest risk first. " +
|
|
131
105
|
"Select findings by ID. " +
|
|
132
106
|
"Severity is maintenance or future-correctness impact. " +
|
|
133
107
|
"Recommendations must be safe first refactor steps, not rewrites. Include concrete nextSteps for the host developer. Structured output only.",
|
|
134
|
-
|
|
135
|
-
);
|
|
136
|
-
return finishAdvisoryReport({ ...resolved, stats: { ...stats, kept: resolved.findings.length } }, coverage, verified);
|
|
108
|
+
});
|
|
137
109
|
}
|
|
138
110
|
|
|
139
111
|
function rank(finding: AdvisoryVerified): number {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { compactResults } from "../src/concurrency.ts";
|
|
2
1
|
import {
|
|
3
2
|
MAX_RESEARCH_LANES,
|
|
4
3
|
ResearchLaneResultSchema,
|
|
@@ -7,18 +6,17 @@ import {
|
|
|
7
6
|
ResearchVerificationSchema,
|
|
8
7
|
type ResearchClaimCandidate,
|
|
9
8
|
type ResearchReport,
|
|
10
|
-
type ResearchVerification,
|
|
11
9
|
} from "../src/research-contract.ts";
|
|
12
10
|
import {
|
|
13
11
|
buildClaimCandidates,
|
|
14
12
|
fallbackResearchReport,
|
|
15
13
|
normalizeResearchLanes,
|
|
16
|
-
sanitizeLaneResults,
|
|
17
14
|
sanitizeResearchReport,
|
|
18
15
|
sanitizeVerification,
|
|
19
16
|
unavailableResearchReport,
|
|
20
17
|
unavailableVerification,
|
|
21
18
|
} from "../src/research-evidence.ts";
|
|
19
|
+
import { isFatalWorkflowError } from "../src/cancellation.ts";
|
|
22
20
|
import { WorkflowToolHintUnavailableError } from "../src/tool-capabilities.ts";
|
|
23
21
|
import type { WorkflowApi, WorkflowMeta } from "../src/types.ts";
|
|
24
22
|
|
|
@@ -32,7 +30,7 @@ const EXTERNAL_TOOLS: string[] = [];
|
|
|
32
30
|
const EXTERNAL_TOOL_HINTS = ["external-search"] as const;
|
|
33
31
|
|
|
34
32
|
export default async function run(api: WorkflowApi): Promise<ResearchReport> {
|
|
35
|
-
const { agent, parallel, phase, log, progress, args } = api;
|
|
33
|
+
const { agent, parallel, phase, log, progress, args, signal } = api;
|
|
36
34
|
const question = args.trim();
|
|
37
35
|
if (!question) return unavailableResearchReport("empty-question");
|
|
38
36
|
|
|
@@ -61,54 +59,54 @@ export default async function run(api: WorkflowApi): Promise<ResearchReport> {
|
|
|
61
59
|
throw error;
|
|
62
60
|
}
|
|
63
61
|
|
|
64
|
-
if (!plan) return unavailableResearchReport("no-evidence");
|
|
65
62
|
const lanes = normalizeResearchLanes(plan);
|
|
66
63
|
if (lanes.length === 0) return unavailableResearchReport("no-evidence");
|
|
67
64
|
progress({ type: "counter", key: "research.lanes", label: "research lanes", value: lanes.length });
|
|
68
65
|
progress({ type: "summary", key: "research.question", value: question });
|
|
69
66
|
|
|
70
67
|
phase("Gather");
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
`
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
68
|
+
const gatherResults = await parallel(
|
|
69
|
+
lanes.map((lane) => async () => {
|
|
70
|
+
const result = await agent(
|
|
71
|
+
`Research one bounded lane using only installed external web-search, browsing, or URL-extraction tools.\n\n` +
|
|
72
|
+
`Question: ${question}\n` +
|
|
73
|
+
`Scope constraints: ${plan.scopeConstraints.join("; ") || "(none)"}\n` +
|
|
74
|
+
`Lane: ${lane.title}\nObjective: ${lane.objective}\n` +
|
|
75
|
+
`Queries:\n${lane.queries.map((query) => `- ${query}`).join("\n")}\n\n` +
|
|
76
|
+
"Open the supporting pages instead of citing a search-results page. Prefer primary and authoritative sources; use independent sources when useful. " +
|
|
77
|
+
"Return concrete claims with importance, whether each page supports or conflicts with the claim, a short passage or precise paraphrase, and the exact page title and URL. " +
|
|
78
|
+
"State evidence gaps. Never invent a URL or claim that the opened page does not support. Structured output only.",
|
|
79
|
+
{
|
|
80
|
+
phase: "Gather",
|
|
81
|
+
label: `gather:${lane.id}`,
|
|
82
|
+
tools: EXTERNAL_TOOLS,
|
|
83
|
+
toolHints: EXTERNAL_TOOL_HINTS,
|
|
84
|
+
requireToolHints: true,
|
|
85
|
+
// The prompt embeds lane text planned by a web-enabled agent; never infer skills (and the read tool) from it.
|
|
86
|
+
skills: [],
|
|
87
|
+
profile: "small",
|
|
88
|
+
resume: "off",
|
|
89
|
+
schema: ResearchLaneResultSchema,
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
progress({ type: "counter_delta", key: "research.evidence", label: "evidence items", delta: result.evidence.length });
|
|
93
|
+
progress({
|
|
94
|
+
type: "lane_item",
|
|
95
|
+
lane: "Research lanes",
|
|
96
|
+
title: lane.title,
|
|
97
|
+
subtitle: `${result.evidence.length} evidence item(s)`,
|
|
98
|
+
status: result.evidence.length > 0 ? "success" : "warning",
|
|
99
|
+
details: result.gaps.join("; ") || lane.objective,
|
|
100
|
+
});
|
|
101
|
+
return result;
|
|
102
|
+
}),
|
|
103
|
+
{ settled: true },
|
|
107
104
|
);
|
|
105
|
+
const gathered = gatherResults.flatMap((result) => (result.ok ? [result.value] : []));
|
|
106
|
+
const failedLanes = lanes.filter((_, index) => !gatherResults[index]!.ok);
|
|
108
107
|
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
if (candidates.length === 0) return unavailableResearchReport("no-evidence");
|
|
108
|
+
const candidates = buildClaimCandidates(gathered);
|
|
109
|
+
if (candidates.length === 0) return unavailableResearchReport(failedLanes.length > 0 ? "lanes-failed" : "no-evidence");
|
|
112
110
|
progress({ type: "counter", key: "research.claims", label: "claims to verify", value: candidates.length });
|
|
113
111
|
log(`${candidates.length} bounded claim(s) selected for independent verification`);
|
|
114
112
|
|
|
@@ -121,11 +119,13 @@ export default async function run(api: WorkflowApi): Promise<ResearchReport> {
|
|
|
121
119
|
tools: EXTERNAL_TOOLS,
|
|
122
120
|
toolHints: EXTERNAL_TOOL_HINTS,
|
|
123
121
|
requireToolHints: true,
|
|
122
|
+
// The prompt embeds web-derived evidence; never infer skills (and the read tool) from it.
|
|
123
|
+
skills: [],
|
|
124
124
|
profile: "medium",
|
|
125
125
|
resume: "off",
|
|
126
126
|
schema: ResearchVerificationSchema,
|
|
127
127
|
});
|
|
128
|
-
return
|
|
128
|
+
return sanitizeVerification(result, candidate);
|
|
129
129
|
}),
|
|
130
130
|
);
|
|
131
131
|
const verifications = verificationResults.map((result, index) => result ?? unavailableVerification(candidates[index]!));
|
|
@@ -135,28 +135,36 @@ export default async function run(api: WorkflowApi): Promise<ResearchReport> {
|
|
|
135
135
|
const synthesisInputs = verifications.filter((verification) => verification.verdict !== "REJECTED");
|
|
136
136
|
|
|
137
137
|
phase("Synthesize");
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
`
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
138
|
+
let synthesis: ResearchReport;
|
|
139
|
+
try {
|
|
140
|
+
synthesis = await agent(
|
|
141
|
+
`Answer the research question using only the independently verified handoff below.\n\n` +
|
|
142
|
+
`Question: ${question}\nScope constraints: ${plan.scopeConstraints.join("; ") || "(none)"}\n\n` +
|
|
143
|
+
`Verified claims JSON:\n${JSON.stringify(synthesisInputs)}\n\n` +
|
|
144
|
+
"Keep SUPPORTED claims, CONFLICTED evidence, UNCERTAIN claims, and model INFERENCE in their separate fields. Exclude REJECTED claims. " +
|
|
145
|
+
"Copy each verified claim string exactly so its citations remain bound to that claim during validation. " +
|
|
146
|
+
"Every supported or conflicting claim must cite exact title/URL objects from its verification; do not add URLs, cite search-results pages, or turn inference into fact. " +
|
|
147
|
+
"Answer concisely, disclose limited coverage, and provide useful next steps. Structured output only.",
|
|
148
|
+
{
|
|
149
|
+
phase: "Synthesize",
|
|
150
|
+
label: "synthesize",
|
|
151
|
+
tools: [],
|
|
152
|
+
// The prompt embeds web-derived claims; never infer skills (and the read tool) from them.
|
|
153
|
+
skills: [],
|
|
154
|
+
profile: "medium",
|
|
155
|
+
resume: "off",
|
|
156
|
+
schema: ResearchReportSchema,
|
|
157
|
+
},
|
|
158
|
+
);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (isFatalWorkflowError(error, signal)) throw error;
|
|
161
|
+
synthesis = fallbackResearchReport(synthesisInputs);
|
|
162
|
+
}
|
|
155
163
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
164
|
+
const report = sanitizeResearchReport(synthesis, synthesisInputs);
|
|
165
|
+
if (failedLanes.length === 0) return report;
|
|
166
|
+
const laneGap = `${failedLanes.length} of ${lanes.length} research lane(s) failed (${failedLanes.map((lane) => lane.title).join(", ")}); coverage is incomplete.`;
|
|
167
|
+
return { ...report, limitations: [...report.limitations, laneGap] };
|
|
160
168
|
}
|
|
161
169
|
|
|
162
170
|
function verificationPrompt(question: string, candidate: ResearchClaimCandidate): string {
|