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.
Files changed (23) hide show
  1. package/.pi/extensions/pi-workflow-engine/index.ts +199 -18
  2. package/.pi/extensions/pi-workflow-engine/src/discovery.ts +2 -33
  3. package/.pi/extensions/pi-workflow-engine/src/dynamax.ts +123 -0
  4. package/.pi/extensions/pi-workflow-engine/src/engine.ts +11 -7
  5. package/.pi/extensions/pi-workflow-engine/src/inline-workflow.ts +396 -0
  6. package/.pi/extensions/pi-workflow-engine/src/review/github-pr-comments.ts +185 -0
  7. package/.pi/extensions/pi-workflow-engine/src/review/review-actions.ts +114 -0
  8. package/.pi/extensions/pi-workflow-engine/src/review/review-format.ts +132 -0
  9. package/.pi/extensions/pi-workflow-engine/src/review/review-handoff.ts +90 -0
  10. package/.pi/extensions/pi-workflow-engine/src/review/review-issues.ts +61 -0
  11. package/.pi/extensions/pi-workflow-engine/src/review/review-results-flow.ts +56 -0
  12. package/.pi/extensions/pi-workflow-engine/src/review/review-results-viewer.ts +240 -0
  13. package/.pi/extensions/pi-workflow-engine/src/types.ts +4 -0
  14. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-inspector.ts +22 -4
  15. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-result-renderer.ts +11 -52
  16. package/.pi/extensions/pi-workflow-engine/src/workflow-module.ts +57 -0
  17. package/.pi/extensions/pi-workflow-engine/src/workflows.ts +2 -3
  18. package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +13 -3
  19. package/README.md +61 -12
  20. package/USAGE.md +189 -0
  21. package/package.json +4 -1
  22. package/skills/workflow-code-review-actions/SKILL.md +44 -0
  23. 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 { badge, formatCount, type WorkflowThemeColor } from "./workflow-format.ts";
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 findings = expanded ? result.findings : result.findings.slice(0, 3);
86
- lines.push(theme.fg("dim", expanded ? "Findings:" : "Top findings:"));
87
- for (const finding of findings) {
88
- lines.push(renderAdvisoryFinding(finding, expanded, theme));
89
- }
90
- if (!expanded && result.findings.length > findings.length) {
91
- lines.push(theme.fg("dim", `… ${result.findings.length - findings.length} more finding(s)`));
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, ping, refactorScout, diagnose, perfReview];
23
- export const BUILTIN_WORKFLOW_FILES = new Set(["code-review.ts", "ping.ts", "refactor-scout.ts", "diagnose.ts", "perf-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 ping # quick engine smoke test
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
- - **Two surfaces are registered**: `/workflow <name> [args]` for direct use, and a `workflow` tool for host-agent delegation.
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. Optional LLM smoke timing is separate from the required gates:
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 . -p "/workflow ping"
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.4.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
- }