pi-workflow-engine 1.1.0 → 1.3.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.
Files changed (55) hide show
  1. package/.pi/extensions/pi-workflow-engine/index.ts +199 -85
  2. package/.pi/extensions/pi-workflow-engine/src/advisory-challenge.ts +5 -2
  3. package/.pi/extensions/pi-workflow-engine/src/advisory-schema.ts +8 -0
  4. package/.pi/extensions/pi-workflow-engine/src/agent-limits.ts +4 -0
  5. package/.pi/extensions/pi-workflow-engine/src/agent-runner-types.ts +3 -1
  6. package/.pi/extensions/pi-workflow-engine/src/agent-runner.ts +18 -4
  7. package/.pi/extensions/pi-workflow-engine/src/agent-session.ts +34 -7
  8. package/.pi/extensions/pi-workflow-engine/src/background-workflow-tool.ts +9 -3
  9. package/.pi/extensions/pi-workflow-engine/src/background-workflows.ts +51 -8
  10. package/.pi/extensions/pi-workflow-engine/src/cancellation.ts +14 -0
  11. package/.pi/extensions/pi-workflow-engine/src/codemode-slots.ts +73 -0
  12. package/.pi/extensions/pi-workflow-engine/src/config-file.ts +52 -0
  13. package/.pi/extensions/pi-workflow-engine/src/discovery.ts +127 -58
  14. package/.pi/extensions/pi-workflow-engine/src/dynamax.ts +112 -31
  15. package/.pi/extensions/pi-workflow-engine/src/engine.ts +75 -18
  16. package/.pi/extensions/pi-workflow-engine/src/filesystem-error.ts +5 -0
  17. package/.pi/extensions/pi-workflow-engine/src/inline-workflow.ts +7 -1
  18. package/.pi/extensions/pi-workflow-engine/src/journal.ts +22 -4
  19. package/.pi/extensions/pi-workflow-engine/src/model-profiles.ts +9 -47
  20. package/.pi/extensions/pi-workflow-engine/src/progress-types.ts +1 -1
  21. package/.pi/extensions/pi-workflow-engine/src/progress.ts +32 -4
  22. package/.pi/extensions/pi-workflow-engine/src/prompt-sections.ts +15 -0
  23. package/.pi/extensions/pi-workflow-engine/src/research-evidence.ts +4 -4
  24. package/.pi/extensions/pi-workflow-engine/src/resume-context.ts +1 -1
  25. package/.pi/extensions/pi-workflow-engine/src/types.ts +54 -9
  26. package/.pi/extensions/pi-workflow-engine/src/ui/kit.ts +1 -0
  27. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-format.ts +9 -3
  28. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-inspector.ts +69 -18
  29. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-runs-browser.ts +17 -6
  30. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-tool-renderer.ts +8 -3
  31. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-widget.ts +16 -8
  32. package/.pi/extensions/pi-workflow-engine/src/workflow-advisory-utils.ts +82 -23
  33. package/.pi/extensions/pi-workflow-engine/src/workflow-catalog.ts +79 -0
  34. package/.pi/extensions/pi-workflow-engine/src/workflow-execution.ts +6 -2
  35. package/.pi/extensions/pi-workflow-engine/src/workflow-input.ts +13 -0
  36. package/.pi/extensions/pi-workflow-engine/src/workflow-module.ts +21 -4
  37. package/.pi/extensions/pi-workflow-engine/src/workflow-result-context.ts +26 -6
  38. package/.pi/extensions/pi-workflow-engine/src/workflow-run-background.ts +2 -0
  39. package/.pi/extensions/pi-workflow-engine/src/workflow-run-controller.ts +69 -13
  40. package/.pi/extensions/pi-workflow-engine/src/workflow-run-history.ts +23 -4
  41. package/.pi/extensions/pi-workflow-engine/src/workflow-run-record.ts +27 -1
  42. package/.pi/extensions/pi-workflow-engine/src/workflow-run-seal.ts +83 -0
  43. package/.pi/extensions/pi-workflow-engine/src/workflow-run-store.ts +30 -4
  44. package/.pi/extensions/pi-workflow-engine/src/workflow-script-file.ts +50 -0
  45. package/.pi/extensions/pi-workflow-engine/src/workflow-settings.ts +147 -0
  46. package/.pi/extensions/pi-workflow-engine/src/workflow-tool-request.ts +61 -0
  47. package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +59 -22
  48. package/.pi/extensions/pi-workflow-engine/workflows/diagnose.ts +7 -4
  49. package/.pi/extensions/pi-workflow-engine/workflows/perf-review.ts +7 -4
  50. package/.pi/extensions/pi-workflow-engine/workflows/refactor-scout.ts +7 -4
  51. package/.pi/extensions/pi-workflow-engine/workflows/research.ts +12 -4
  52. package/README.md +11 -3
  53. package/USAGE.md +124 -23
  54. package/package.json +13 -4
  55. package/skills/workflow-authoring/SKILL.md +162 -0
@@ -1,5 +1,5 @@
1
1
  import { challengeFindings, parseChallengeArgs } from "../src/advisory-challenge.ts";
2
- import { Type } from "typebox";
2
+ import { Type, type Static } from "typebox";
3
3
  import {
4
4
  type AdvisoryVerified,
5
5
  type AdvisoryLens,
@@ -9,6 +9,7 @@ import {
9
9
  formatEvidence,
10
10
  formatLocation,
11
11
  runLensVerificationPipeline,
12
+ verifierCandidates,
12
13
  EMPTY_LENS_REVIEW_STATS,
13
14
  verdictConfidence,
14
15
  DEFAULT_ADVISORY_TOOL_HINTS,
@@ -23,6 +24,7 @@ import type { WorkflowApi, WorkflowMeta } from "../src/types.ts";
23
24
  export const meta: WorkflowMeta = {
24
25
  name: "code-review",
25
26
  description: "Fan-out review of the branch's open PR (or branch vs main): scope → per-angle find → independent verify → synthesize.",
27
+ whenToUse: "Use to review a PR, branch diff, ref range, or focused target for correctness bugs and cleanups, with every finding independently verified; args are the target or review instructions, blank auto-detects the diff.",
26
28
  phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Challenge" }, { title: "Synthesize" }],
27
29
  };
28
30
 
@@ -34,6 +36,16 @@ const ScopeSchema = Type.Object({
34
36
  conventions: Type.Optional(Type.String({ description: "Relevant AGENTS.md / project conventions" })),
35
37
  });
36
38
 
39
+ // With review instructions, scope splits them by stage: finders get only where to look, because a reporting bar in a
40
+ // finder prompt makes finders filter their own candidates before any verifier sees them; the bar goes to synthesis.
41
+ const InstructedScopeSchema = Type.Object({
42
+ ...ScopeSchema.properties,
43
+ conventions: Type.Optional(Type.String({ description: "Repository conventions from AGENTS.md and project docs only. Do not restate the user's review instructions, filtering rules or already-reported findings here." })),
44
+ focus: Type.String({ description: "Where the user wants reviewers to look: areas, files, risks and lenses named in the instructions. Exclude any filtering, evidence, reporting or deduplication rules; write (none) if the instructions name no focus." }),
45
+ reportingBar: Type.String({ description: "The user's filtering, evidence, severity and reporting rules restated so a reader without file access can apply them, listing every already-reported issue to exclude (read any files the instructions reference). Write (none) if the instructions set no bar." }),
46
+ });
47
+ type Scope = Static<typeof ScopeSchema> & Partial<Pick<Static<typeof InstructedScopeSchema>, "focus" | "reportingBar">>;
48
+
37
49
  // The review lenses — this is the part you customise to your codebase's real failure modes.
38
50
  const ANGLES: AdvisoryLens[] = [
39
51
  { label: "logic-bugs", category: "bug", text: "Off-by-one errors, wrong conditionals, incorrect return values, broken control flow." },
@@ -53,7 +65,7 @@ export function buildCodeReviewScopeBlock(input: {
53
65
  readonly summary: string;
54
66
  readonly conventions?: string;
55
67
  readonly diffText: string;
56
- readonly target: string;
68
+ readonly instructions?: { readonly heading: string; readonly text: string };
57
69
  }): string {
58
70
  const diffBlock = input.diffText
59
71
  ? `\n## Diff (review is bounded to these changed lines)\n\`\`\`diff\n${
@@ -66,7 +78,7 @@ export function buildCodeReviewScopeBlock(input: {
66
78
  `## Diff command\n${input.diffCommand}\n\n## Changed files\n${input.files.map((file) => `- ${file}`).join("\n")}\n\n` +
67
79
  `## Summary\n${input.summary}\n\n## Conventions\n${input.conventions ?? "(none noted)"}\n` +
68
80
  diffBlock +
69
- (input.target ? `\n## User instructions (verbatim)\n${input.target}\n` : "")
81
+ (input.instructions?.text ? `\n## ${input.instructions.heading}\n${input.instructions.text}\n` : "")
70
82
  );
71
83
  }
72
84
 
@@ -81,7 +93,7 @@ export default async function run(api: WorkflowApi, dependencies: CodeReviewDepe
81
93
 
82
94
  // ─── Phase 0: Scope ───
83
95
  phase("Scope");
84
- const scope = await agent(
96
+ const scopePrompt =
85
97
  "Establish the scope of a code review.\n" +
86
98
  (target
87
99
  ? `Target / instructions (verbatim): "${target}". If it names a PR number, branch, ref range, or files, build the matching diff command (use 'gh pr diff <number>' for a PR). Otherwise use the default selection below.\n`
@@ -97,9 +109,14 @@ export default async function run(api: WorkflowApi, dependencies: CodeReviewDepe
97
109
  "4. Run the chosen command to confirm the diff is non-empty.\n\n" +
98
110
  "Then: list the changed files, summarize the change in one paragraph (mention the PR if one was found), " +
99
111
  "and read any relevant AGENTS.md or project docs noting conventions a reviewer should know.\n" +
100
- "Return diffCommand exactly as a reviewer should run it. Structured output only.",
101
- { phase: "Scope", label: "scope", tools: DEFAULT_ADVISORY_TOOLS, toolHints: DEFAULT_ADVISORY_TOOL_HINTS, profile: "medium", schema: ScopeSchema },
102
- );
112
+ "Return diffCommand exactly as a reviewer should run it. Structured output only.";
113
+ const scopeOptions = { phase: "Scope", label: "scope", tools: DEFAULT_ADVISORY_TOOLS, toolHints: DEFAULT_ADVISORY_TOOL_HINTS, profile: "medium" } as const;
114
+ const scope: Scope = target
115
+ ? await agent(
116
+ `${scopePrompt}\nKeep the user's instructions out of conventions. Return focus (where to look) and reportingBar (the self-contained filtering and reporting bar, with every already-reported issue spelled out) as separate fields.`,
117
+ { ...scopeOptions, schema: InstructedScopeSchema },
118
+ )
119
+ : await agent(scopePrompt, { ...scopeOptions, schema: ScopeSchema });
103
120
 
104
121
  progress({ type: "summary", key: "files", value: scope.files.join(", ") || "(none)" });
105
122
  const diffTarget = parseAllowedDiffCommand(scope.diffCommand);
@@ -147,19 +164,27 @@ export default async function run(api: WorkflowApi, dependencies: CodeReviewDepe
147
164
  : {}),
148
165
  };
149
166
 
150
- const scopeBlock = `Reviewed snapshot identity: ${JSON.stringify(reviewContext.snapshot ?? "unavailable")}\n` + buildCodeReviewScopeBlock({
151
- diffCommand,
152
- files: scope.files,
153
- summary: scope.summary,
154
- conventions: scope.conventions,
155
- diffText,
156
- target,
157
- });
167
+ const blockFor = (instructions?: { heading: string; text: string }) =>
168
+ `Reviewed snapshot identity: ${JSON.stringify(reviewContext.snapshot ?? "unavailable")}\n` + buildCodeReviewScopeBlock({
169
+ diffCommand,
170
+ files: scope.files,
171
+ summary: scope.summary,
172
+ conventions: scope.conventions,
173
+ diffText,
174
+ ...(instructions ? { instructions } : {}),
175
+ });
176
+ const scopeBlock = blockFor({ heading: "User instructions (verbatim)", text: target });
177
+ const focus = stated(scope.focus);
178
+ const reportingBar = stated(scope.reportingBar);
179
+ const finderBlock = blockFor(focus ? { heading: "Review focus (from the user's instructions)", text: focus } : undefined);
180
+ if (focus) log(`review focus: ${focus}`);
158
181
 
159
182
  const pipelineResult = await runLensVerificationPipeline({
160
183
  api,
161
184
  lenses: ANGLES,
162
185
  perLens: PER_ANGLE,
186
+ // Finders on `small` (thinking low by default) returned nothing on real PRs; they are the stage that must dig.
187
+ finderProfile: "medium",
163
188
  boundCandidate: (candidate, lens) => {
164
189
  const reviewAnchor = candidate.reviewAnchor
165
190
  ? diffAnchor(changed, candidate.reviewAnchor)
@@ -167,18 +192,18 @@ export default async function run(api: WorkflowApi, dependencies: CodeReviewDepe
167
192
  return reviewAnchor ? { ...candidate, category: lens.category, reviewAnchor } : undefined;
168
193
  },
169
194
  finderPrompt: (lens) =>
170
- `## Code-review finder — ${lens.label}\n\n${scopeBlock}\n` +
195
+ `## Code-review finder — ${lens.label}\n\n${finderBlock}\n` +
171
196
  `Review the change through ONLY this lens:\n${lens.text}\n` +
172
197
  "Only flag issues on lines that are part of the diff above (run the diff command if it is not shown). " +
173
198
  "Set reviewAnchor to the changed line causing the issue; locations and discoveryEvidence may include unchanged callers and other files. " +
174
199
  `Surface up to ${PER_ANGLE} candidates. Use category exactly "${lens.category}". Each candidate must include a one-line summary, ` +
175
200
  "locations with the changed file and a line that appears in the diff, and impact describing the concrete failure or maintenance scenario. " +
176
201
  "Pass through anything with a nameable impact — a separate verifier judges them next. Structured output only.",
177
- verifierPrompt: (candidate) =>
178
- `## Code-review verifier\n\n${scopeBlock}\n## Candidate\n` +
179
- `Location: ${formatLocation(candidate)}\n` +
180
- `Category: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n\n` +
181
- "Run the diff command, read the relevant file(s), and return exactly one verdict (CONFIRMED / PLAUSIBLE / NOT_SUBSTANTIATED / REFUTED) " +
202
+ verifierPrompt: (candidates) =>
203
+ `## Code-review verifier\n\n${scopeBlock}\n` +
204
+ verifierCandidates(candidates, { one: "Candidate", many: "Candidates" }, (candidate) =>
205
+ `Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}`) + "\n\n" +
206
+ "Run the diff command, read the relevant file(s), and return exactly one verdict per candidate (CONFIRMED / PLAUSIBLE / NOT_SUBSTANTIATED / REFUTED) " +
182
207
  "with evidence quoting the line(s). Use NOT_SUBSTANTIATED when evidence is insufficient; use REFUTED only for concrete disproof. Structured output only.",
183
208
  });
184
209
  const verified = await challengeFindings(api, pipelineResult.verified, scopeBlock, challengeConfig.options, pipelineResult.coverage);
@@ -193,11 +218,23 @@ export default async function run(api: WorkflowApi, dependencies: CodeReviewDepe
193
218
  synthesisPrompt: (block, ranked) =>
194
219
  `## Synthesis: final code-review report\n\n${ranked.length} findings survived independent verification.\n\n${block}\n\n` +
195
220
  "Merge findings with the same root cause, rank most-severe first (correctness bugs above cleanups), and produce the final advisory report. " +
196
- "Return summary, ID selections with severity (low/medium/high) and advisory recommendation, and nextSteps. Evidence and confidence are reconstructed from verified records. Structured output only.",
221
+ "Return summary, ID selections with severity (low/medium/high) and advisory recommendation, and nextSteps. Evidence and confidence are reconstructed from verified records. Structured output only." +
222
+ // Only a stated bar filters: a focus alone must not drop verified findings outside it.
223
+ (reportingBar
224
+ ? `\n\n## Reporting bar (the user's rules with referenced context resolved)\n${reportingBar}` +
225
+ `\n\n## User instructions (verbatim)\n${target}\n\n` +
226
+ "Apply the reporting bar when choosing which IDs to report: leave out findings it excludes, and say in the summary what you left out and why."
227
+ : ""),
197
228
  });
198
229
  return { ...report, reviewContext };
199
230
  }
200
231
 
232
+ /** A scope field's text, or undefined when the scope agent left it blank or wrote (none). */
233
+ function stated(value: string | undefined): string | undefined {
234
+ const text = value?.trim();
235
+ return text && text !== "(none)" ? text : undefined;
236
+ }
237
+
201
238
  function rank(finding: AdvisoryVerified): number {
202
239
  return (finding.category === "cleanup" ? 2 : 0) + (finding.verdict !== "CONFIRMED" ? 1 : 0);
203
240
  }
@@ -8,6 +8,7 @@ import {
8
8
  formatLocation,
9
9
  formatRankedFinding,
10
10
  runLensVerificationPipeline,
11
+ verifierCandidates,
11
12
  DEFAULT_ADVISORY_TOOL_HINTS,
12
13
  DEFAULT_ADVISORY_TOOLS,
13
14
  } from "../src/workflow-advisory-utils.ts";
@@ -16,6 +17,7 @@ import type { WorkflowApi, WorkflowMeta } from "../src/types.ts";
16
17
  export const meta: WorkflowMeta = {
17
18
  name: "diagnose",
18
19
  description: "Advisory-only bug diagnosis: scope symptoms → competing hypotheses → independent verify → synthesize likely root causes.",
20
+ whenToUse: "Use when a bug, failing command, flaky test, or regression has an unclear cause and competing hypotheses should be checked independently before anyone edits code; args describe the symptom.",
19
21
  phases: [{ title: "Scope" }, { title: "Hypothesize" }, { title: "Verify" }, { title: "Challenge" }, { title: "Synthesize" }],
20
22
  };
21
23
 
@@ -76,10 +78,11 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
76
78
  `Consider ONLY this hypothesis lens:\n${lens.text}\n\n` +
77
79
  `Surface up to ${PER_LENS} root-cause hypotheses. Use category exactly "${lens.category}". ` +
78
80
  "Each hypothesis must include a one-line summary, locations, impact explaining how it produces the symptom, and an optional recommendation for the next validation step. Structured output only.",
79
- verifierPrompt: (candidate) =>
80
- `## Diagnose verifier\n\n${scopeBlock}\n## Hypothesis\n` +
81
- `Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n` +
82
- `Recommended validation: ${candidate.recommendation ?? "(none supplied)"}\n\n` +
81
+ verifierPrompt: (candidates) =>
82
+ `## Diagnose verifier\n\n${scopeBlock}\n` +
83
+ verifierCandidates(candidates, { one: "Hypothesis", many: "Hypotheses" }, (candidate) =>
84
+ `Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n` +
85
+ `Recommended validation: ${candidate.recommendation ?? "(none supplied)"}`) + "\n\n" +
83
86
  "Read relevant files and, when useful, run only safe read-only diagnostic commands from the scoped command list or commands explicitly requested by the user. " +
84
87
  "Do not run mutation, install, commit, network, or destructive commands. Return CONFIRMED, PLAUSIBLE, NOT_SUBSTANTIATED, or REFUTED with evidence. " +
85
88
  "Use NOT_SUBSTANTIATED when evidence is missing; REFUTED requires disproof. Structured output only.",
@@ -9,6 +9,7 @@ import {
9
9
  EMPTY_LENS_REVIEW_STATS,
10
10
  formatLocation,
11
11
  runLensVerificationPipeline,
12
+ verifierCandidates,
12
13
  DEFAULT_ADVISORY_TOOL_HINTS,
13
14
  DEFAULT_ADVISORY_TOOLS,
14
15
  } from "../src/workflow-advisory-utils.ts";
@@ -17,6 +18,7 @@ import type { WorkflowApi, WorkflowMeta } from "../src/types.ts";
17
18
  export const meta: WorkflowMeta = {
18
19
  name: "perf-review",
19
20
  description: "Advisory-only performance review: scope slow path → per-lens bottleneck hypotheses → verify evidence → synthesize measurements and safe optimizations.",
21
+ whenToUse: "Use for a slow path, startup cost, or performance concern where measured bottlenecks and safe optimizations matter more than guesses; args name the path, workload, or command.",
20
22
  phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Challenge" }, { title: "Synthesize" }],
21
23
  };
22
24
 
@@ -84,10 +86,11 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
84
86
  `Surface up to ${PER_LENS} candidates. Use category exactly "${lens.category}". ` +
85
87
  "Each candidate must include a one-line summary, locations, impact stating the suspected performance consequence and workload where it matters, " +
86
88
  "and an optional recommendation. Prefer measurement recommendations when evidence is weak. Structured output only.",
87
- verifierPrompt: (candidate) =>
88
- `## Perf-review verifier\n\n${scopeBlock}\n## Candidate\n` +
89
- `Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n` +
90
- `Recommendation: ${candidate.recommendation ?? "(none supplied)"}\n\n` +
89
+ verifierPrompt: (candidates) =>
90
+ `## Perf-review verifier\n\n${scopeBlock}\n` +
91
+ verifierCandidates(candidates, { one: "Candidate", many: "Candidates" }, (candidate) =>
92
+ `Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n` +
93
+ `Recommendation: ${candidate.recommendation ?? "(none supplied)"}`) + "\n\n" +
91
94
  "Read relevant files and package/scripts. Run only safe read-only measurement or inspection commands when useful. " +
92
95
  "Return CONFIRMED, PLAUSIBLE, NOT_SUBSTANTIATED, or REFUTED with evidence from code, scripts, config, or measurement output. " +
93
96
  "Default toward PLAUSIBLE or REFUTED when no measurement exists; do not overstate a bottleneck. Structured output only.",
@@ -10,6 +10,7 @@ import {
10
10
  formatLocation,
11
11
  formatRankedFinding,
12
12
  runLensVerificationPipeline,
13
+ verifierCandidates,
13
14
  DEFAULT_ADVISORY_TOOL_HINTS,
14
15
  DEFAULT_ADVISORY_TOOLS,
15
16
  } from "../src/workflow-advisory-utils.ts";
@@ -18,6 +19,7 @@ import type { WorkflowApi, WorkflowMeta } from "../src/types.ts";
18
19
  export const meta: WorkflowMeta = {
19
20
  name: "refactor-scout",
20
21
  description: "Advisory-only refactor scout: scope → per-lens find → independent verify → synthesize safe refactor opportunities.",
22
+ whenToUse: "Use to find small, behaviour-preserving refactors (duplication, complexity, type safety, boundaries, dead code) in a path or module without changing it; args name the target.",
21
23
  phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Challenge" }, { title: "Synthesize" }],
22
24
  };
23
25
 
@@ -82,10 +84,11 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
82
84
  `Surface up to ${PER_LENS} candidates. Use category exactly "${lens.category}". ` +
83
85
  "Each candidate must include a one-line summary, locations, impact on maintainability or future correctness, and an optional safe first recommendation. " +
84
86
  "Only include opportunities where a small, reviewable first step is plausible. Structured output only.",
85
- verifierPrompt: (candidate) =>
86
- `## Refactor-scout verifier\n\n${scopeBlock}\n## Candidate\n` +
87
- `Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n` +
88
- `Recommendation: ${candidate.recommendation ?? "(none supplied)"}\n\n` +
87
+ verifierPrompt: (candidates) =>
88
+ `## Refactor-scout verifier\n\n${scopeBlock}\n` +
89
+ verifierCandidates(candidates, { one: "Candidate", many: "Candidates" }, (candidate) =>
90
+ `Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n` +
91
+ `Recommendation: ${candidate.recommendation ?? "(none supplied)"}`) + "\n\n" +
89
92
  "Read the relevant files and return CONFIRMED, PLAUSIBLE, NOT_SUBSTANTIATED, or REFUTED. " +
90
93
  "Default toward REFUTED if the opportunity is generic, too broad, not evidenced by code, or lacks a safe first step. " +
91
94
  "Evidence must quote or cite code. Structured output only.",
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  MAX_RESEARCH_LANES,
3
+ MAX_VERIFICATION_CLAIMS,
3
4
  ResearchLaneResultSchema,
4
5
  ResearchPlanSchema,
5
6
  ResearchReportSchema,
@@ -23,6 +24,7 @@ import type { WorkflowApi, WorkflowMeta } from "../src/types.ts";
23
24
  export const meta: WorkflowMeta = {
24
25
  name: "research",
25
26
  description: "Source-grounded external research: decompose → gather direct-page evidence → independently verify claims → cited synthesis.",
27
+ whenToUse: "Use for questions that need current external sources and cited, independently verified claims; needs an installed web search or browsing tool; args are the question and any scope constraints.",
26
28
  phases: [{ title: "Plan" }, { title: "Gather" }, { title: "Verify" }, { title: "Synthesize" }],
27
29
  };
28
30
 
@@ -105,10 +107,11 @@ export default async function run(api: WorkflowApi): Promise<ResearchReport> {
105
107
  const gathered = gatherResults.flatMap((result) => (result.ok ? [result.value] : []));
106
108
  const failedLanes = lanes.filter((_, index) => !gatherResults[index]!.ok);
107
109
 
108
- const candidates = buildClaimCandidates(gathered);
110
+ const { candidates, overCap } = buildClaimCandidates(gathered);
109
111
  if (candidates.length === 0) return unavailableResearchReport(failedLanes.length > 0 ? "lanes-failed" : "no-evidence");
110
112
  progress({ type: "counter", key: "research.claims", label: "claims to verify", value: candidates.length });
111
113
  log(`${candidates.length} bounded claim(s) selected for independent verification`);
114
+ if (overCap > 0) log(`Dropped ${overCap} lower-priority claim(s) over the verification cap of ${MAX_VERIFICATION_CLAIMS}`);
112
115
 
113
116
  phase("Verify");
114
117
  const verificationResults = await parallel(
@@ -162,9 +165,14 @@ export default async function run(api: WorkflowApi): Promise<ResearchReport> {
162
165
  }
163
166
 
164
167
  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] };
168
+ const gaps: string[] = [];
169
+ if (failedLanes.length > 0) {
170
+ gaps.push(`${failedLanes.length} of ${lanes.length} research lane(s) failed (${failedLanes.map((lane) => lane.title).join(", ")}); coverage is incomplete.`);
171
+ }
172
+ if (overCap > 0) {
173
+ gaps.push(`${overCap} lower-priority claim(s) were not independently verified; verification is capped at ${MAX_VERIFICATION_CLAIMS} claims.`);
174
+ }
175
+ return gaps.length === 0 ? report : { ...report, limitations: [...report.limitations, ...gaps] };
168
176
  }
169
177
 
170
178
  function verificationPrompt(question: string, candidate: ResearchClaimCandidate): string {
package/README.md CHANGED
@@ -72,15 +72,21 @@ dynamax investigate this flaky test using separate timing, fixture, diff, and ex
72
72
  dynamax argue both implementation options, then have another agent challenge the recommendation
73
73
  ```
74
74
 
75
- The token enables Dynamax for one turn. Sticky mode remains active for the pi
76
- session:
75
+ The token enables Dynamax for that one request. Sticky mode instead makes
76
+ workflow orchestration the default for every substantive task in the pi session;
77
+ `/workflow:dynamax` with no argument toggles it:
77
78
 
78
79
  ```text
80
+ /workflow:dynamax
79
81
  /workflow:dynamax on
80
82
  /workflow:dynamax off
81
83
  /workflow:dynamax status
82
84
  ```
83
85
 
86
+ `/workflow:size small|medium|large|off` sets how many agents pi aims to keep
87
+ each authored workflow under (5, 10 by default, or 25); see the
88
+ [size guideline](USAGE.md#workflow-size-guideline).
89
+
84
90
  In the TUI, a standalone `dynamax` token receives a short moving-shine sweep and
85
91
  then stays highlighted. Use `PI_DYNAMAX_EFFECT=static` or
86
92
  `PI_DYNAMAX_EFFECT=off` to reduce or disable the cue; the
@@ -108,7 +114,9 @@ covers review actions and recent run history.
108
114
  ## Create your own workflows
109
115
 
110
116
  Saved workflows are TypeScript modules for repeatable orchestration; inline
111
- workflows are useful for one-off Dynamax tasks. The
117
+ workflows are useful for one-off Dynamax tasks. An inline script that proves
118
+ useful can be saved as a `.js` file in a project's `.pi/workflows/` or in
119
+ `~/.pi/agent/workflows/` and run by name. The
112
120
  [workflow authoring guide](USAGE.md#author-a-saved-workflow) covers the API,
113
121
  typed handoffs, model profiles, tool access, composition, and registration.
114
122