pi-workflow-engine 0.4.0 → 0.6.1
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 +199 -18
- package/.pi/extensions/pi-workflow-engine/src/discovery.ts +2 -33
- package/.pi/extensions/pi-workflow-engine/src/dynamax.ts +123 -0
- package/.pi/extensions/pi-workflow-engine/src/engine.ts +11 -7
- package/.pi/extensions/pi-workflow-engine/src/inline-workflow.ts +396 -0
- package/.pi/extensions/pi-workflow-engine/src/review/github-pr-comments.ts +185 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-actions.ts +114 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-format.ts +132 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-handoff.ts +90 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-issues.ts +61 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-results-flow.ts +56 -0
- package/.pi/extensions/pi-workflow-engine/src/review/review-results-viewer.ts +240 -0
- package/.pi/extensions/pi-workflow-engine/src/types.ts +4 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-inspector.ts +22 -4
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-result-renderer.ts +11 -52
- package/.pi/extensions/pi-workflow-engine/src/workflow-module.ts +57 -0
- package/.pi/extensions/pi-workflow-engine/src/workflows.ts +2 -3
- package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +13 -3
- package/README.md +61 -12
- package/USAGE.md +189 -0
- package/package.json +4 -1
- package/skills/workflow-code-review-actions/SKILL.md +44 -0
- package/.pi/extensions/pi-workflow-engine/workflows/ping.ts +0 -29
|
@@ -2,7 +2,9 @@ import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import { Box, type Component, Text } from "@earendil-works/pi-tui";
|
|
3
3
|
import type { AdvisoryFinding, AdvisoryLocation, AdvisoryReport } from "../advisory-schema.ts";
|
|
4
4
|
import type { PerfAggregate } from "../perf.ts";
|
|
5
|
-
import {
|
|
5
|
+
import { renderIssueDetails, renderIssuesTable } from "../review/review-format.ts";
|
|
6
|
+
import { toReviewIssues, type ReviewContext } from "../review/review-issues.ts";
|
|
7
|
+
import { formatCount } from "./workflow-format.ts";
|
|
6
8
|
|
|
7
9
|
export interface WorkflowPerfDetails {
|
|
8
10
|
readonly enabled: boolean;
|
|
@@ -19,6 +21,7 @@ export interface WorkflowResultEnvelope {
|
|
|
19
21
|
|
|
20
22
|
export interface AdvisoryWorkflowResult extends AdvisoryReport {
|
|
21
23
|
stats?: Record<string, string | number>;
|
|
24
|
+
reviewContext?: ReviewContext;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
export function isWorkflowResult(value: unknown): value is WorkflowResultEnvelope {
|
|
@@ -82,33 +85,18 @@ function renderAdvisoryResult(name: string, result: AdvisoryWorkflowResult, expa
|
|
|
82
85
|
return lines.join("\n");
|
|
83
86
|
}
|
|
84
87
|
|
|
85
|
-
const
|
|
86
|
-
lines.push(theme.fg("dim",
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
const issues = toReviewIssues(name, result);
|
|
89
|
+
lines.push(theme.fg("dim", "Findings:"));
|
|
90
|
+
lines.push(renderIssuesTable(issues, theme, { maxRows: expanded ? issues.length : 12 }));
|
|
91
|
+
if (expanded) {
|
|
92
|
+
for (const issue of issues) {
|
|
93
|
+
lines.push(renderIssueDetails(issue, theme));
|
|
94
|
+
}
|
|
92
95
|
}
|
|
93
96
|
if (expanded && result.nextSteps.length > 0) renderNextSteps(result.nextSteps, lines, theme);
|
|
94
97
|
return lines.join("\n");
|
|
95
98
|
}
|
|
96
99
|
|
|
97
|
-
function renderAdvisoryFinding(finding: AdvisoryFinding, expanded: boolean, theme: Theme): string {
|
|
98
|
-
const location = formatLocation(finding.locations[0]);
|
|
99
|
-
let text =
|
|
100
|
-
` ${badge(finding.category, "accent", theme)} ` +
|
|
101
|
-
`${badge(finding.severity, severityColor(finding.severity), theme)} ` +
|
|
102
|
-
`${badge(`${finding.confidence} confidence`, confidenceColor(finding.confidence), theme)} ` +
|
|
103
|
-
`${theme.fg("accent", location)} ${theme.fg("muted", finding.summary)}`;
|
|
104
|
-
if (expanded) {
|
|
105
|
-
text += `\n ${theme.fg("dim", `Impact: ${finding.impact}`)}`;
|
|
106
|
-
text += `\n ${theme.fg("dim", `Evidence: ${finding.evidence.join("; ") || "(none cited)"}`)}`;
|
|
107
|
-
text += `\n ${theme.fg("dim", `Recommendation: ${finding.recommendation}`)}`;
|
|
108
|
-
}
|
|
109
|
-
return text;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
100
|
function renderNextSteps(nextSteps: string[], lines: string[], theme: Theme): void {
|
|
113
101
|
lines.push(theme.fg("dim", "Next steps:"));
|
|
114
102
|
for (const step of nextSteps) {
|
|
@@ -116,35 +104,6 @@ function renderNextSteps(nextSteps: string[], lines: string[], theme: Theme): vo
|
|
|
116
104
|
}
|
|
117
105
|
}
|
|
118
106
|
|
|
119
|
-
function formatLocation(location: AdvisoryLocation | undefined): string {
|
|
120
|
-
if (!location) return "(no location)";
|
|
121
|
-
const line = location.line != null ? `:${location.line}` : "";
|
|
122
|
-
const symbol = location.symbol ? ` (${location.symbol})` : "";
|
|
123
|
-
return `${location.file}${line}${symbol}`;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function severityColor(severity: AdvisoryFinding["severity"]): WorkflowThemeColor {
|
|
127
|
-
switch (severity) {
|
|
128
|
-
case "high":
|
|
129
|
-
return "error";
|
|
130
|
-
case "medium":
|
|
131
|
-
return "warning";
|
|
132
|
-
case "low":
|
|
133
|
-
return "muted";
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function confidenceColor(confidence: AdvisoryFinding["confidence"]): WorkflowThemeColor {
|
|
138
|
-
switch (confidence) {
|
|
139
|
-
case "high":
|
|
140
|
-
return "success";
|
|
141
|
-
case "medium":
|
|
142
|
-
return "warning";
|
|
143
|
-
case "low":
|
|
144
|
-
return "muted";
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
107
|
function renderGenericWorkflowResult(name: string, result: unknown, expanded: boolean, theme: Theme): string {
|
|
149
108
|
const lines = [`${theme.fg("success", "✓")} ${theme.fg("accent", theme.bold(`Workflow: ${name}`))}`];
|
|
150
109
|
const summary = extractSummary(result);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { WorkflowMeta, WorkflowModule } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
type WorkflowMetaCandidate = {
|
|
4
|
+
readonly name?: unknown;
|
|
5
|
+
readonly description?: unknown;
|
|
6
|
+
readonly phases?: unknown;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type WorkflowModuleCandidate = {
|
|
10
|
+
readonly meta?: unknown;
|
|
11
|
+
readonly default?: unknown;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** Validate and normalize workflow metadata without executing workflow code. */
|
|
15
|
+
export function parseWorkflowMeta(value: unknown): { meta: WorkflowMeta } | { reason: string } {
|
|
16
|
+
if (!isRecord(value)) return { reason: "meta export must be an object" };
|
|
17
|
+
return parseWorkflowMetaObject(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function parseWorkflowModule(value: unknown): { module: WorkflowModule } | { reason: string } {
|
|
21
|
+
if (!isRecord(value)) return { reason: "module export is not an object" };
|
|
22
|
+
const candidate = value as WorkflowModuleCandidate;
|
|
23
|
+
const meta = candidate.meta;
|
|
24
|
+
if (!isRecord(meta)) return { reason: "missing meta export" };
|
|
25
|
+
|
|
26
|
+
const parsedMeta = parseWorkflowMetaObject(meta);
|
|
27
|
+
if ("reason" in parsedMeta) return parsedMeta;
|
|
28
|
+
if (!isWorkflowRun(candidate.default)) return { reason: "default export must be a function" };
|
|
29
|
+
|
|
30
|
+
return { module: { meta: parsedMeta.meta, default: candidate.default } };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseWorkflowMetaObject(meta: WorkflowMetaCandidate): { meta: WorkflowMeta } | { reason: string } {
|
|
34
|
+
if (typeof meta.name !== "string") return { reason: "meta.name must be a string" };
|
|
35
|
+
|
|
36
|
+
const description = meta.description;
|
|
37
|
+
if (description !== undefined && typeof description !== "string") return { reason: "meta.description must be a string when provided" };
|
|
38
|
+
|
|
39
|
+
const phases = meta.phases;
|
|
40
|
+
if (phases !== undefined && !isWorkflowPhases(phases)) return { reason: "meta.phases must be an array of { title: string }" };
|
|
41
|
+
|
|
42
|
+
const workflowMeta: WorkflowMeta = { name: meta.name, description: description ?? "" };
|
|
43
|
+
if (phases !== undefined) workflowMeta.phases = phases;
|
|
44
|
+
return { meta: workflowMeta };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isWorkflowRun(value: unknown): value is WorkflowModule["default"] {
|
|
48
|
+
return typeof value === "function";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isWorkflowPhases(value: unknown): value is Array<{ title: string }> {
|
|
52
|
+
return Array.isArray(value) && value.every((phase) => isRecord(phase) && typeof phase.title === "string");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
56
|
+
return typeof value === "object" && value !== null;
|
|
57
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { WorkflowModule } from "./types.ts";
|
|
2
2
|
import * as codeReview from "../workflows/code-review.ts";
|
|
3
|
-
import * as ping from "../workflows/ping.ts";
|
|
4
3
|
import * as refactorScout from "../workflows/refactor-scout.ts";
|
|
5
4
|
import * as diagnose from "../workflows/diagnose.ts";
|
|
6
5
|
import * as perfReview from "../workflows/perf-review.ts";
|
|
@@ -19,6 +18,6 @@ import * as perfReview from "../workflows/perf-review.ts";
|
|
|
19
18
|
* dynamically imported built-ins. Startup optimizations should happen at the extension
|
|
20
19
|
* entrypoint/discovery boundary first.
|
|
21
20
|
*/
|
|
22
|
-
export const BUILTIN_WORKFLOWS: WorkflowModule[] = [codeReview,
|
|
23
|
-
export const BUILTIN_WORKFLOW_FILES = new Set(["code-review.ts", "
|
|
21
|
+
export const BUILTIN_WORKFLOWS: WorkflowModule[] = [codeReview, refactorScout, diagnose, perfReview];
|
|
22
|
+
export const BUILTIN_WORKFLOW_FILES = new Set(["code-review.ts", "refactor-scout.ts", "diagnose.ts", "perf-review.ts"]);
|
|
24
23
|
export const BUILTIN_WORKFLOW_NAMES = BUILTIN_WORKFLOWS.map((mod) => mod.meta.name);
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
verdictConfidence,
|
|
17
17
|
} from "../src/workflow-advisory-utils.ts";
|
|
18
18
|
import { captureDiff } from "../src/diff-capture.ts";
|
|
19
|
+
import type { ReviewContext } from "../src/review/review-issues.ts";
|
|
19
20
|
import type { WorkflowApi, WorkflowMeta, WorkflowRunStats } from "../src/types.ts";
|
|
20
21
|
|
|
21
22
|
export const meta: WorkflowMeta = {
|
|
@@ -137,6 +138,15 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
137
138
|
if (scope.files.length === 0) {
|
|
138
139
|
return { summary: "No changes found to review.", findings: [], nextSteps: ["Provide a PR, ref range, or changed files to review."], stats: makeStats(0, 0) };
|
|
139
140
|
}
|
|
141
|
+
|
|
142
|
+
const reviewContext: ReviewContext = {
|
|
143
|
+
workflowName: "code-review",
|
|
144
|
+
target,
|
|
145
|
+
diffCommand: scope.diffCommand,
|
|
146
|
+
files: scope.files,
|
|
147
|
+
summary: scope.summary,
|
|
148
|
+
};
|
|
149
|
+
|
|
140
150
|
log(`${scope.files.length} changed files`);
|
|
141
151
|
|
|
142
152
|
// Capture the diff once, deterministically, so findings can be bounded to changed lines in code.
|
|
@@ -259,7 +269,7 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
259
269
|
log(`${verified.length} verified → ${surviving.length} kept`);
|
|
260
270
|
|
|
261
271
|
if (surviving.length === 0) {
|
|
262
|
-
return { summary: "No findings survived verification.", findings: [], nextSteps: ["No code-review action is recommended from this workflow run."], stats };
|
|
272
|
+
return { summary: "No findings survived verification.", findings: [], nextSteps: ["No code-review action is recommended from this workflow run."], stats, reviewContext };
|
|
263
273
|
}
|
|
264
274
|
|
|
265
275
|
// ─── Synthesize: rank, merge, report ───
|
|
@@ -283,10 +293,10 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
|
283
293
|
{ phase: "Synthesize", label: "synthesize", thinkingLevel: "medium", schema: AdvisoryReportSchema },
|
|
284
294
|
);
|
|
285
295
|
|
|
286
|
-
if (!report) return { summary: "Synthesis produced no output.", findings: [], nextSteps: ["Re-run the workflow or inspect verifier evidence manually."], stats };
|
|
296
|
+
if (!report) return { summary: "Synthesis produced no output.", findings: [], nextSteps: ["Re-run the workflow or inspect verifier evidence manually."], stats, reviewContext };
|
|
287
297
|
|
|
288
298
|
const findings = backfillAdvisoryFindings(report.findings, ranked, {
|
|
289
299
|
impact: "Impact not restated by synthesis.",
|
|
290
300
|
});
|
|
291
|
-
return { ...report, findings, stats };
|
|
301
|
+
return { ...report, findings, stats, reviewContext };
|
|
292
302
|
}
|
package/README.md
CHANGED
|
@@ -55,6 +55,8 @@ pi remove git:github.com/timbrinded/pi-workflow-engine
|
|
|
55
55
|
|
|
56
56
|
## Usage
|
|
57
57
|
|
|
58
|
+
For the complete installation, invocation, authoring, tuning, and troubleshooting guide, see [USAGE.md](USAGE.md).
|
|
59
|
+
|
|
58
60
|
In a pi session, from inside a git repo with changes:
|
|
59
61
|
|
|
60
62
|
```text
|
|
@@ -63,25 +65,40 @@ In a pi session, from inside a git repo with changes:
|
|
|
63
65
|
/workflow refactor-scout src/ # find safe refactor opportunities
|
|
64
66
|
/workflow diagnose "typecheck fails after the schema change"
|
|
65
67
|
/workflow perf-review "workflow startup latency"
|
|
66
|
-
/workflow code-review --inspect # open the live workflow inspector
|
|
67
|
-
/workflow
|
|
68
|
+
/workflow code-review --inspect # open the live workflow inspector while the run is active
|
|
69
|
+
/workflow:inspector # reopen the last completed workflow inspector
|
|
70
|
+
/workflow code-review --result-viewer # explicitly open the post-review results viewer
|
|
71
|
+
/workflow code-review --review-viewer # alias for --result-viewer
|
|
72
|
+
/workflow code-review --no-result-viewer # explicitly keep the post-review viewer closed
|
|
68
73
|
```
|
|
69
74
|
|
|
70
|
-
The host agent can also invoke the `workflow` tool mid-conversation
|
|
75
|
+
The host agent can also invoke the `workflow` tool mid-conversation. It accepts either a registered workflow `name` or a one-off inline workflow `script`:
|
|
71
76
|
|
|
72
77
|
```text
|
|
73
78
|
Run the code-review workflow on this PR and use the result before deciding what to fix.
|
|
74
79
|
```
|
|
75
80
|
|
|
81
|
+
Opt into dynamic multi-agent orchestration with the literal `dynamax` token, or keep it sticky for the session:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
dynamax inspect this bug with multiple focused agents
|
|
85
|
+
/workflow:dynamax on
|
|
86
|
+
/workflow:dynamax status
|
|
87
|
+
/workflow:dynamax off
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`dynamax` is a permission signal for the host agent: once opted in, it may run an existing named workflow or author an inline workflow script through the `workflow` tool. In interactive mode, `/workflow` with no arguments also offers `✍ Author temporary one-shot workflow…`; choose it, type a brief, and pi will ask the host agent to author/run a temporary inline workflow.
|
|
91
|
+
|
|
76
92
|
The advisory workflows inspect and report only; they do not edit files. They return the same shape: summary, ranked findings, evidence, impact, recommendations, and next steps.
|
|
77
93
|
|
|
94
|
+
The package also ships the `workflow-code-review-actions` skill. When you select review findings in the results viewer, the parent agent can use that skill to either make minimal local fixes or raise GitHub PR inline comments through GitHub MCP/tools or `gh`.
|
|
95
|
+
|
|
78
96
|
## Built-in workflows
|
|
79
97
|
|
|
80
98
|
- `code-review`: Reviews the current branch, open PR, ref range, or target. It looks for correctness bugs and cleanup issues, then independently verifies candidates before ranking them.
|
|
81
99
|
- `refactor-scout`: Looks for small, safe refactor opportunities: duplication, complexity, weak types, boundary leaks, dead code, and convention drift.
|
|
82
100
|
- `diagnose`: Investigates a symptom, failing command, or regression. It generates competing root-cause hypotheses, verifies them, and returns the most likely causes with next validation steps.
|
|
83
101
|
- `perf-review`: Reviews a slow path or workload for bottleneck hypotheses, measurement gaps, and safe optimization directions. It avoids claiming certainty when measurement evidence is missing.
|
|
84
|
-
- `ping`: Minimal smoke workflow for checking that the engine can run one structured agent call.
|
|
85
102
|
|
|
86
103
|
## The code-review workflow
|
|
87
104
|
|
|
@@ -93,6 +110,22 @@ The bundled review workflow is deliberately shaped like a serious review process
|
|
|
93
110
|
4. **Verify**: send each survivor to an independent verifier that must confirm, mark plausible, or refute with evidence.
|
|
94
111
|
5. **Synthesize**: produce one ranked report with stats, verdicts, and concrete locations.
|
|
95
112
|
|
|
113
|
+
After a direct TUI `/workflow code-review` run produces findings, pi records a readable table-formatted result message without asking whether to open another surface. Use `--result-viewer`/`--review-viewer` when you want the interactive findings viewer to open immediately, or `--no-result-viewer`/`--no-review-viewer` when a script or alias should explicitly keep it closed. Use `/workflow:inspector` after a run to reopen the last completed workflow inspector.
|
|
114
|
+
|
|
115
|
+
Viewer controls:
|
|
116
|
+
|
|
117
|
+
- `↑`/`↓`: move between findings.
|
|
118
|
+
- `1`-`9`: jump directly to a visible finding.
|
|
119
|
+
- `space`: tag/untag the current finding.
|
|
120
|
+
- `a`: tag or untag all visible findings.
|
|
121
|
+
- `enter`: expand/collapse the detail pane.
|
|
122
|
+
- `←`/`→` or `pageup`/`pagedown`: scroll detail text.
|
|
123
|
+
- `f`: hand selected findings to the parent agent for minimal local fixes.
|
|
124
|
+
- `c`: comment on selected findings upstream.
|
|
125
|
+
- `q`/`escape`: close the viewer.
|
|
126
|
+
|
|
127
|
+
GitHub inline comments require a GitHub PR checkout, an authenticated `gh`, and findings with changed-file line numbers. If direct PR context resolution or posting is unavailable, the viewer queues a parent-agent fallback prompt that can use installed GitHub MCP/tools, project skills, or `gh`. Workflow tool invocations remain non-interactive: they do not prompt or open the viewer, and they return table-formatted results only.
|
|
128
|
+
|
|
96
129
|
The review lenses live in `.pi/extensions/pi-workflow-engine/workflows/code-review.ts`. That is where your repo's real failure modes belong.
|
|
97
130
|
|
|
98
131
|
## Authoring workflows
|
|
@@ -135,12 +168,33 @@ export default async function run({ agent, parallel, phase }: WorkflowApi) {
|
|
|
135
168
|
}
|
|
136
169
|
```
|
|
137
170
|
|
|
171
|
+
Inline workflow scripts use the same primitives, but are passed as a string to the `workflow` tool instead of being saved as files. A minimal inline workflow starts with `export const meta` and uses the injected Type object for schemas:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
export const meta = {
|
|
175
|
+
name: "inline-review",
|
|
176
|
+
description: "One-off focused review workflow.",
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export default async function run({ agent, parallel, phase, args }) {
|
|
180
|
+
phase("Find");
|
|
181
|
+
const Finding = Type.Object({ summary: Type.String() });
|
|
182
|
+
const findings = await parallel([
|
|
183
|
+
() => agent(`Find correctness issues: ${args}`, { schema: Finding, thinkingLevel: "low" }),
|
|
184
|
+
() => agent(`Find edge cases: ${args}`, { schema: Finding, thinkingLevel: "low" }),
|
|
185
|
+
]);
|
|
186
|
+
return { summary: JSON.stringify(findings) };
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Inline v1 rules: no imports, no dynamic `import()`, pure-literal `meta`, schemas must use the injected Type value, and scripts run in-process with the extension's permissions. This v1 path is permissive rather than sandboxed.
|
|
191
|
+
|
|
138
192
|
Under the hood:
|
|
139
193
|
|
|
140
194
|
- **Each `agent()` is an in-process pi `AgentSession`** using `createAgentSession` and `SessionManager.inMemory()`.
|
|
141
195
|
- **Structured output is a terminating tool**. The engine registers one tool whose `parameters` is your schema; pi validates the call and the engine captures the args. There is no JSON scraping.
|
|
142
196
|
- **A single global semaphore caps concurrent agents**, so `parallel` and `pipeline` can nest freely while every `agent()` call still respects the run cap.
|
|
143
|
-
- **
|
|
197
|
+
- **Three surfaces are registered**: `/workflow <name> [args]` for direct use, grouped workflow commands such as `/workflow:dynamax on|off|status` and `/workflow:inspector`, and a `workflow` tool for host-agent delegation by `name` or inline `script`.
|
|
144
198
|
|
|
145
199
|
## Local development
|
|
146
200
|
|
|
@@ -177,11 +231,7 @@ bun run bench:startup -- --json
|
|
|
177
231
|
bun run bench:ui -- --agents 1000 --lane-items 1000 --json
|
|
178
232
|
```
|
|
179
233
|
|
|
180
|
-
Add `--out` to write machine-local JSON under `.artifacts/benchmarks/`. These timings are advisory, not portable thresholds.
|
|
181
|
-
|
|
182
|
-
```bash
|
|
183
|
-
pi -e . -p "/workflow ping"
|
|
184
|
-
```
|
|
234
|
+
Add `--out` to write machine-local JSON under `.artifacts/benchmarks/`. These timings are advisory, not portable thresholds.
|
|
185
235
|
|
|
186
236
|
Guardrails:
|
|
187
237
|
|
|
@@ -192,7 +242,7 @@ Guardrails:
|
|
|
192
242
|
Load your working copy through the package manifest without installing it. This is ephemeral and exercises the same `.pi/extensions/pi-workflow-engine/index.ts` entrypoint that installed packages use:
|
|
193
243
|
|
|
194
244
|
```bash
|
|
195
|
-
pi -e .
|
|
245
|
+
pi -e .
|
|
196
246
|
```
|
|
197
247
|
|
|
198
248
|
This repo also includes `.pi/settings.json` for project-local auto-discovery. If you also have the global package installed, pi may report duplicate `/workflow` or `workflow` tool diagnostics; remove one source or force pi to ignore discovered extensions and load this working copy instead:
|
|
@@ -227,7 +277,6 @@ Tune the built-in review workflow by editing the `ANGLES` array in `.pi/extensio
|
|
|
227
277
|
refactor-scout.ts advisory refactor opportunities
|
|
228
278
|
diagnose.ts advisory bug diagnosis
|
|
229
279
|
perf-review.ts advisory performance investigation
|
|
230
|
-
ping.ts minimal smoke workflow
|
|
231
280
|
```
|
|
232
281
|
|
|
233
282
|
## License
|
package/USAGE.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Usage
|
|
2
|
+
|
|
3
|
+
`pi-workflow-engine` adds multi-agent workflows to pi. Most users only need two surfaces:
|
|
4
|
+
|
|
5
|
+
- `/workflow <name> [args]` to run a saved workflow directly.
|
|
6
|
+
- `/workflow:*` commands for related actions such as inspector reopening and dynamax sticky mode.
|
|
7
|
+
- `dynamax` as a literal one-shot opt-in token when you want the host agent to author a one-off inline workflow for investigation/review.
|
|
8
|
+
|
|
9
|
+
## Run a workflow
|
|
10
|
+
|
|
11
|
+
From inside a repo:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
/workflow code-review
|
|
15
|
+
/workflow code-review HEAD~3
|
|
16
|
+
/workflow refactor-scout src/
|
|
17
|
+
/workflow diagnose "typecheck fails after the schema change"
|
|
18
|
+
/workflow perf-review "workflow startup latency"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The built-in workflows are advisory. They inspect, verify, and report; they do **not** edit files.
|
|
22
|
+
|
|
23
|
+
Useful flags:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
/workflow code-review --inspect # show the live inspector while the run is active
|
|
27
|
+
/workflow:inspector # reopen the last completed workflow inspector
|
|
28
|
+
/workflow code-review --result-viewer # explicitly open the findings viewer
|
|
29
|
+
/workflow code-review --review-viewer # alias for --result-viewer
|
|
30
|
+
/workflow code-review --concurrency=4 # cap concurrent subagents
|
|
31
|
+
/workflow code-review --refresh # rediscover newly added workflow files
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Built-in workflows
|
|
35
|
+
|
|
36
|
+
| Name | Purpose |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `code-review` | Reviews a PR, branch diff, ref range, or focus area. |
|
|
39
|
+
| `refactor-scout` | Finds small, safe refactor opportunities. |
|
|
40
|
+
| `diagnose` | Investigates a bug, failing command, or regression. |
|
|
41
|
+
| `perf-review` | Reviews a slow path or performance concern. |
|
|
42
|
+
|
|
43
|
+
`code-review` auto-detects a diff when you do not pass args. If there is an open GitHub PR for the current branch, it uses that PR diff; otherwise it falls back to branch-vs-main/master or `HEAD~1`.
|
|
44
|
+
|
|
45
|
+
## Use `dynamax` for inline workflows
|
|
46
|
+
|
|
47
|
+
Use `dynamax` when the built-in workflows are not specific enough and you want the main pi agent to author a temporary workflow for the current question.
|
|
48
|
+
|
|
49
|
+
Interactive shortcut: run `/workflow` with no arguments, choose `✍ Author temporary one-shot workflow…`, and type a brief. pi will send that brief back to the host agent as a `dynamax` request so it can author and run an inline workflow with the `workflow` tool's `script` argument.
|
|
50
|
+
|
|
51
|
+
That inline workflow should investigate, fan out, verify, and summarize. It is **not** where code edits happen. If you want changes made, use the workflow result as evidence and then ask pi to edit separately.
|
|
52
|
+
|
|
53
|
+
One-off examples:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
dynamax author an inline workflow to investigate why typecheck is failing
|
|
57
|
+
dynamax create a one-off workflow to compare the parser and tests before we change code
|
|
58
|
+
dynamax use parallel agents to review this design and report the risks
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Sticky for the session:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
/workflow:dynamax on
|
|
65
|
+
/workflow:dynamax off
|
|
66
|
+
/workflow:dynamax status
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
With dynamax enabled, the host agent usually calls the `workflow` tool with `script`: a one-off inline workflow. It can still call a saved workflow by `name` when one already fits.
|
|
70
|
+
|
|
71
|
+
## Workflow results
|
|
72
|
+
|
|
73
|
+
Workflow results are rendered in pi with:
|
|
74
|
+
|
|
75
|
+
- summary;
|
|
76
|
+
- ranked findings;
|
|
77
|
+
- locations and evidence;
|
|
78
|
+
- recommendations;
|
|
79
|
+
- next steps;
|
|
80
|
+
- run stats when available.
|
|
81
|
+
|
|
82
|
+
During a run, pi shows live phases and subagent status. Use `--inspect` if you want a larger live view while the workflow is active, then `/workflow:inspector` if you want to bring the last completed inspector back up afterward.
|
|
83
|
+
|
|
84
|
+
Code-review findings are rendered as a formatted result message by default. pi no longer asks whether to open the findings viewer. Use `--result-viewer` or `--review-viewer` when you want to inspect findings interactively, press `enter` to expand/collapse the nicely formatted finding text, and use `1`-`9` to jump directly to a visible finding.
|
|
85
|
+
|
|
86
|
+
## Author a saved workflow
|
|
87
|
+
|
|
88
|
+
Saved workflows are TypeScript modules with `meta` plus a default async function:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { Type } from "typebox";
|
|
92
|
+
import type { WorkflowApi, WorkflowMeta } from "../src/types.ts";
|
|
93
|
+
|
|
94
|
+
export const meta: WorkflowMeta = {
|
|
95
|
+
name: "my-workflow",
|
|
96
|
+
description: "Find and summarize something important.",
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const Finding = Type.Object({
|
|
100
|
+
summary: Type.String(),
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
export default async function run({ agent, parallel, phase, args }: WorkflowApi) {
|
|
104
|
+
phase("Find");
|
|
105
|
+
const findings = await parallel([
|
|
106
|
+
() => agent(`Find correctness issues: ${args}`, { schema: Finding, tools: ["read", "bash"], thinkingLevel: "low" }),
|
|
107
|
+
() => agent(`Find edge cases: ${args}`, { schema: Finding, tools: ["read", "bash"], thinkingLevel: "low" }),
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
phase("Synthesize");
|
|
111
|
+
return agent(`Summarize: ${JSON.stringify(findings)}`, { thinkingLevel: "medium" });
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Core primitives:
|
|
116
|
+
|
|
117
|
+
| Primitive | What it does |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| `agent(prompt, opts)` | Runs one isolated subagent. With `schema`, returns validated structured data. |
|
|
120
|
+
| `parallel(thunks)` | Runs thunks concurrently and waits for all. |
|
|
121
|
+
| `pipeline(items, ...stages)` | Runs each item through stages independently. |
|
|
122
|
+
| `phase(title)` / `log(message)` | Updates workflow progress UI. |
|
|
123
|
+
| `progress(event)` | Emits counters, summaries, and lane items. |
|
|
124
|
+
|
|
125
|
+
Set `thinkingLevel` on fan-out agents. Otherwise many subagents can inherit an expensive global reasoning level.
|
|
126
|
+
|
|
127
|
+
### Where workflows live
|
|
128
|
+
|
|
129
|
+
Guaranteed built-ins are statically registered:
|
|
130
|
+
|
|
131
|
+
1. Add `.pi/extensions/pi-workflow-engine/workflows/<name>.ts`.
|
|
132
|
+
2. Import it in `.pi/extensions/pi-workflow-engine/src/workflows.ts`.
|
|
133
|
+
3. Add it to `BUILTIN_WORKFLOWS`.
|
|
134
|
+
|
|
135
|
+
Drop-in workflows are also discovered best-effort from:
|
|
136
|
+
|
|
137
|
+
- `.pi/extensions/pi-workflow-engine/workflows/*.ts`
|
|
138
|
+
- `~/.pi/agent/workflows/*.ts`
|
|
139
|
+
|
|
140
|
+
Use `/workflow <name> --refresh` after adding a drop-in file.
|
|
141
|
+
|
|
142
|
+
## Author an inline workflow
|
|
143
|
+
|
|
144
|
+
Inline workflows are passed to the `workflow` tool as a script string. They are useful for one-off dynamax orchestration.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
export const meta = {
|
|
148
|
+
name: "inline-review",
|
|
149
|
+
description: "One-off focused review.",
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export default async function run({ agent, parallel, phase, args }) {
|
|
153
|
+
phase("Find");
|
|
154
|
+
const Finding = Type.Object({ summary: Type.String() });
|
|
155
|
+
const findings = await parallel([
|
|
156
|
+
() => agent(`Find correctness issues: ${args}`, { schema: Finding, thinkingLevel: "low" }),
|
|
157
|
+
() => agent(`Find edge cases: ${args}`, { schema: Finding, thinkingLevel: "low" }),
|
|
158
|
+
]);
|
|
159
|
+
return { summary: JSON.stringify(findings) };
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Inline rules:
|
|
164
|
+
|
|
165
|
+
- start with `export const meta = { ... }`;
|
|
166
|
+
- default-export an async workflow function;
|
|
167
|
+
- no imports or dynamic `import()`;
|
|
168
|
+
- use the injected `Type` object for schemas;
|
|
169
|
+
- no extra exports or code after the default export.
|
|
170
|
+
|
|
171
|
+
Inline workflows run in-process with extension permissions, so treat them as trusted task automation.
|
|
172
|
+
|
|
173
|
+
## Runtime knobs
|
|
174
|
+
|
|
175
|
+
Only tune these when a workflow is too slow, too expensive, or too noisy:
|
|
176
|
+
|
|
177
|
+
| Knob | Meaning |
|
|
178
|
+
| --- | --- |
|
|
179
|
+
| `--concurrency=N` / `PI_WORKFLOW_CONCURRENCY=N` | Cap concurrent subagents. Default is `min(8, max(2, CPU count))`. |
|
|
180
|
+
| `--parallel-limit=N` / `PI_WORKFLOW_PARALLEL_SUBMISSION_LIMIT=N` | Limit eager `parallel()` submission. |
|
|
181
|
+
| `--perf` / `PI_WORKFLOW_PERF=1` | Include internal timing aggregates. |
|
|
182
|
+
| `PI_WORKFLOW_LANE_ITEM_LIMIT=N` | Cap retained progress lane items. |
|
|
183
|
+
|
|
184
|
+
## Common fixes
|
|
185
|
+
|
|
186
|
+
- **Unknown workflow**: confirm the extension is loaded with `pi list`; use `--refresh` for new drop-ins.
|
|
187
|
+
- **Inline compile error**: check the inline rules above.
|
|
188
|
+
- **Slow run**: lower fan-out, set `thinkingLevel`, or reduce `--concurrency`.
|
|
189
|
+
- **Duplicate command/tool warnings while developing**: avoid loading both the global package and the working copy.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-workflow-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A customisable multi-agent workflow-orchestration engine for the pi coding agent.",
|
|
6
6
|
"keywords": [
|
|
@@ -24,11 +24,14 @@
|
|
|
24
24
|
".pi/extensions/pi-workflow-engine",
|
|
25
25
|
".pi/settings.json",
|
|
26
26
|
"assets",
|
|
27
|
+
"skills",
|
|
27
28
|
"README.md",
|
|
29
|
+
"USAGE.md",
|
|
28
30
|
"LICENSE"
|
|
29
31
|
],
|
|
30
32
|
"pi": {
|
|
31
33
|
"extensions": [".pi/extensions/pi-workflow-engine/index.ts"],
|
|
34
|
+
"skills": ["skills"],
|
|
32
35
|
"image": "https://raw.githubusercontent.com/timbrinded/pi-workflow-engine/master/assets/preview.png"
|
|
33
36
|
},
|
|
34
37
|
"scripts": {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: workflow-code-review-actions
|
|
3
|
+
description: "Act on selected code-review findings from pi-workflow-engine: make minimal fixes or post GitHub PR inline comments using gh, GitHub MCP/tools, or project-specific tools."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use this skill when the parent agent receives selected code-review finding JSON from `pi-workflow-engine`.
|
|
7
|
+
|
|
8
|
+
## Inputs
|
|
9
|
+
|
|
10
|
+
The prompt will include compact JSON with:
|
|
11
|
+
|
|
12
|
+
- `context`: workflow name, target, diff command, changed files, and optional summary.
|
|
13
|
+
- `issues`: selected findings with `id`, `summary`, `category`, `severity`, `confidence`, `location`, `impact`, `evidence`, and `recommendation`.
|
|
14
|
+
|
|
15
|
+
## Fix mode
|
|
16
|
+
|
|
17
|
+
When mode is `fix selected code-review findings`:
|
|
18
|
+
|
|
19
|
+
1. Inspect the selected issue JSON before editing.
|
|
20
|
+
2. Make minimal edits that address only the selected findings.
|
|
21
|
+
3. Preserve unrelated user changes and avoid broad refactors.
|
|
22
|
+
4. Run focused validation if available for the touched files or behavior.
|
|
23
|
+
5. Summarize changed files and validation results.
|
|
24
|
+
6. Do not post GitHub PR comments in fix mode.
|
|
25
|
+
|
|
26
|
+
## Comment mode
|
|
27
|
+
|
|
28
|
+
When mode is `post inline GitHub PR comments`:
|
|
29
|
+
|
|
30
|
+
1. Do not edit files or make code changes.
|
|
31
|
+
2. Prefer installed GitHub MCP/tools if visible in the active tool list.
|
|
32
|
+
3. If no GitHub MCP/tools are available, use `gh`:
|
|
33
|
+
- Resolve the PR with `gh pr view --json number,headRefOid,url,headRepositoryOwner,headRepository`.
|
|
34
|
+
- Resolve the repository with `gh repo view --json nameWithOwner` when owner/name is missing.
|
|
35
|
+
- Post each inline comment with `gh api repos/{owner}/{repo}/pulls/{number}/comments` and include `commit_id`, `path`, `line`, and `side=RIGHT`.
|
|
36
|
+
4. Keep each comment concise: summary, severity/confidence/category, impact, evidence, and recommendation.
|
|
37
|
+
5. Report posted, skipped, and failed counts.
|
|
38
|
+
|
|
39
|
+
## Safety rules
|
|
40
|
+
|
|
41
|
+
- Do not post duplicate comments.
|
|
42
|
+
- Do not comment line-less findings inline.
|
|
43
|
+
- Ask the user if the upstream PR cannot be identified.
|
|
44
|
+
- Keep actions scoped to the selected issue IDs only.
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { Type } from "typebox";
|
|
2
|
-
import type { WorkflowApi, WorkflowMeta } from "../src/types.ts";
|
|
3
|
-
|
|
4
|
-
/** Minimal engine smoke test: a single structured-output agent call. Fast + cheap. */
|
|
5
|
-
export const meta: WorkflowMeta = {
|
|
6
|
-
name: "ping",
|
|
7
|
-
description: "Engine smoke test — one structured agent call, no tools.",
|
|
8
|
-
phases: [{ title: "Ping" }],
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
12
|
-
const { agent, phase, log } = api;
|
|
13
|
-
phase("Ping");
|
|
14
|
-
log("calling one structured agent");
|
|
15
|
-
const result = await agent(
|
|
16
|
-
"You are a code-review workflow engine. Return a one-word headline and a one-sentence summary of what you do.",
|
|
17
|
-
{
|
|
18
|
-
phase: "Ping",
|
|
19
|
-
label: "ping",
|
|
20
|
-
tools: [],
|
|
21
|
-
thinkingLevel: "low",
|
|
22
|
-
schema: Type.Object({
|
|
23
|
-
headline: Type.String({ description: "One word" }),
|
|
24
|
-
summary: Type.String({ description: "One sentence" }),
|
|
25
|
-
}),
|
|
26
|
-
},
|
|
27
|
-
);
|
|
28
|
-
return result ?? { headline: "none", summary: "no structured answer returned" };
|
|
29
|
-
}
|