@yycholla/pi-dynamic-workflows 3.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 (179) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +325 -0
  3. package/assets/readme/hero.png +0 -0
  4. package/assets/readme/hero.svg +70 -0
  5. package/assets/readme/package-cover.png +0 -0
  6. package/assets/readme/package-cover.svg +66 -0
  7. package/assets/readme/workflow.png +0 -0
  8. package/assets/readme/workflow.svg +77 -0
  9. package/dist/accept-workflow-guidance.d.ts +14 -0
  10. package/dist/accept-workflow-guidance.js +53 -0
  11. package/dist/adversarial-review.d.ts +30 -0
  12. package/dist/adversarial-review.js +107 -0
  13. package/dist/agent-history.d.ts +20 -0
  14. package/dist/agent-history.js +122 -0
  15. package/dist/agent-registry.d.ts +83 -0
  16. package/dist/agent-registry.js +190 -0
  17. package/dist/agent.d.ts +364 -0
  18. package/dist/agent.js +714 -0
  19. package/dist/builtin-commands.d.ts +19 -0
  20. package/dist/builtin-commands.js +251 -0
  21. package/dist/builtin-workflows.d.ts +45 -0
  22. package/dist/builtin-workflows.js +121 -0
  23. package/dist/code-review.d.ts +26 -0
  24. package/dist/code-review.js +181 -0
  25. package/dist/config.d.ts +37 -0
  26. package/dist/config.js +44 -0
  27. package/dist/deep-research.d.ts +30 -0
  28. package/dist/deep-research.js +124 -0
  29. package/dist/display.d.ts +134 -0
  30. package/dist/display.js +248 -0
  31. package/dist/effort-command.d.ts +28 -0
  32. package/dist/effort-command.js +68 -0
  33. package/dist/enums.d.ts +69 -0
  34. package/dist/enums.js +78 -0
  35. package/dist/errors.d.ts +113 -0
  36. package/dist/errors.js +140 -0
  37. package/dist/extension-reload.d.ts +37 -0
  38. package/dist/extension-reload.js +78 -0
  39. package/dist/fs-persistence.d.ts +63 -0
  40. package/dist/fs-persistence.js +102 -0
  41. package/dist/index.d.ts +57 -0
  42. package/dist/index.js +35 -0
  43. package/dist/logger.d.ts +21 -0
  44. package/dist/logger.js +66 -0
  45. package/dist/model-routing.d.ts +30 -0
  46. package/dist/model-routing.js +50 -0
  47. package/dist/model-spec.d.ts +29 -0
  48. package/dist/model-spec.js +252 -0
  49. package/dist/model-tier-config.d.ts +133 -0
  50. package/dist/model-tier-config.js +249 -0
  51. package/dist/run-persistence.d.ts +180 -0
  52. package/dist/run-persistence.js +294 -0
  53. package/dist/saved-commands.d.ts +28 -0
  54. package/dist/saved-commands.js +100 -0
  55. package/dist/shared-store.d.ts +98 -0
  56. package/dist/shared-store.js +212 -0
  57. package/dist/structured-output.d.ts +19 -0
  58. package/dist/structured-output.js +30 -0
  59. package/dist/task-panel.d.ts +61 -0
  60. package/dist/task-panel.js +422 -0
  61. package/dist/usage-limit-scheduler.d.ts +145 -0
  62. package/dist/usage-limit-scheduler.js +368 -0
  63. package/dist/web-tools.d.ts +20 -0
  64. package/dist/web-tools.js +120 -0
  65. package/dist/workflow-authoring-coverage.d.ts +70 -0
  66. package/dist/workflow-authoring-coverage.js +421 -0
  67. package/dist/workflow-authoring-reference.d.ts +20 -0
  68. package/dist/workflow-authoring-reference.js +156 -0
  69. package/dist/workflow-capability-contract.d.ts +131 -0
  70. package/dist/workflow-capability-contract.js +604 -0
  71. package/dist/workflow-commands.d.ts +18 -0
  72. package/dist/workflow-commands.js +260 -0
  73. package/dist/workflow-comprehension.d.ts +133 -0
  74. package/dist/workflow-comprehension.js +1321 -0
  75. package/dist/workflow-context-measurement.d.ts +72 -0
  76. package/dist/workflow-context-measurement.js +213 -0
  77. package/dist/workflow-control-tool.d.ts +30 -0
  78. package/dist/workflow-control-tool.js +176 -0
  79. package/dist/workflow-delivery-choice.d.ts +20 -0
  80. package/dist/workflow-delivery-choice.js +48 -0
  81. package/dist/workflow-editor.d.ts +93 -0
  82. package/dist/workflow-editor.js +363 -0
  83. package/dist/workflow-manager.d.ts +492 -0
  84. package/dist/workflow-manager.js +1124 -0
  85. package/dist/workflow-paths.d.ts +22 -0
  86. package/dist/workflow-paths.js +46 -0
  87. package/dist/workflow-release-gate.d.ts +39 -0
  88. package/dist/workflow-release-gate.js +309 -0
  89. package/dist/workflow-saved.d.ts +38 -0
  90. package/dist/workflow-saved.js +126 -0
  91. package/dist/workflow-settings.d.ts +70 -0
  92. package/dist/workflow-settings.js +131 -0
  93. package/dist/workflow-tool.d.ts +71 -0
  94. package/dist/workflow-tool.js +367 -0
  95. package/dist/workflow-ui.d.ts +182 -0
  96. package/dist/workflow-ui.js +1587 -0
  97. package/dist/workflow.d.ts +333 -0
  98. package/dist/workflow.js +1151 -0
  99. package/dist/workflows-models-command.d.ts +31 -0
  100. package/dist/workflows-models-command.js +156 -0
  101. package/dist/worktree.d.ts +25 -0
  102. package/dist/worktree.js +61 -0
  103. package/extensions/workflow.ts +151 -0
  104. package/package.json +104 -0
  105. package/skills/workflow-authoring/SKILL.md +30 -0
  106. package/skills/workflow-authoring/examples/adversarial-verification.js +63 -0
  107. package/skills/workflow-authoring/examples/bounded-semantic-retry.js +56 -0
  108. package/skills/workflow-authoring/examples/classify-and-act.js +67 -0
  109. package/skills/workflow-authoring/examples/fan-out-and-synthesize.js +47 -0
  110. package/skills/workflow-authoring/examples/generate-and-filter.js +68 -0
  111. package/skills/workflow-authoring/examples/loop-until-done.js +68 -0
  112. package/skills/workflow-authoring/examples/phased-budgets.js +75 -0
  113. package/skills/workflow-authoring/examples/saved-nested-workflows.js +44 -0
  114. package/skills/workflow-authoring/examples/structured-output.js +37 -0
  115. package/skills/workflow-authoring/examples/tournament.js +81 -0
  116. package/skills/workflow-authoring/examples/validated-gate.js +63 -0
  117. package/skills/workflow-authoring/references/capabilities.md +41 -0
  118. package/skills/workflow-authoring/references/capability-details.md +357 -0
  119. package/skills/workflow-authoring/references/common-helpers.md +4 -0
  120. package/skills/workflow-authoring/references/debugging.md +27 -0
  121. package/skills/workflow-authoring/references/focused-recipes.md +11 -0
  122. package/skills/workflow-authoring/references/helpers.md +9 -0
  123. package/skills/workflow-authoring/references/lifecycle.md +33 -0
  124. package/skills/workflow-authoring/references/pattern-selection.md +15 -0
  125. package/skills/workflow-authoring/references/quality-helpers.md +8 -0
  126. package/skills/workflow-authoring/references/registry-ownership.md +15 -0
  127. package/skills/workflow-authoring/references/retry-helper.md +5 -0
  128. package/skills/workflow-authoring/references/review.md +43 -0
  129. package/skills/workflow-authoring/references/runtime.md +27 -0
  130. package/skills/workflow-authoring/references/specialized-helpers.md +19 -0
  131. package/skills/workflow-authoring/references/versions.md +13 -0
  132. package/skills/workflow-patterns/SKILL.md +51 -0
  133. package/src/accept-workflow-guidance.ts +71 -0
  134. package/src/adversarial-review.ts +120 -0
  135. package/src/agent-history.ts +157 -0
  136. package/src/agent-registry.ts +221 -0
  137. package/src/agent.ts +929 -0
  138. package/src/builtin-commands.ts +286 -0
  139. package/src/builtin-workflows.ts +155 -0
  140. package/src/code-review.ts +183 -0
  141. package/src/config.ts +55 -0
  142. package/src/deep-research.ts +135 -0
  143. package/src/display.ts +367 -0
  144. package/src/effort-command.ts +87 -0
  145. package/src/enums.ts +77 -0
  146. package/src/errors.ts +199 -0
  147. package/src/extension-reload.ts +100 -0
  148. package/src/fs-persistence.ts +124 -0
  149. package/src/index.ts +176 -0
  150. package/src/logger.ts +88 -0
  151. package/src/model-routing.ts +73 -0
  152. package/src/model-spec.ts +309 -0
  153. package/src/model-tier-config.ts +296 -0
  154. package/src/run-persistence.ts +484 -0
  155. package/src/saved-commands.ts +115 -0
  156. package/src/shared-store.ts +228 -0
  157. package/src/structured-output.ts +47 -0
  158. package/src/task-panel.ts +490 -0
  159. package/src/usage-limit-scheduler.ts +432 -0
  160. package/src/web-tools.ts +124 -0
  161. package/src/workflow-authoring-coverage.ts +486 -0
  162. package/src/workflow-authoring-reference.ts +186 -0
  163. package/src/workflow-capability-contract.ts +806 -0
  164. package/src/workflow-commands.ts +287 -0
  165. package/src/workflow-comprehension.ts +1673 -0
  166. package/src/workflow-context-measurement.ts +262 -0
  167. package/src/workflow-control-tool.ts +238 -0
  168. package/src/workflow-delivery-choice.ts +69 -0
  169. package/src/workflow-editor.ts +444 -0
  170. package/src/workflow-manager.ts +1405 -0
  171. package/src/workflow-paths.ts +63 -0
  172. package/src/workflow-release-gate.ts +529 -0
  173. package/src/workflow-saved.ts +180 -0
  174. package/src/workflow-settings.ts +194 -0
  175. package/src/workflow-tool.ts +464 -0
  176. package/src/workflow-ui.ts +1789 -0
  177. package/src/workflow.ts +1615 -0
  178. package/src/workflows-models-command.ts +211 -0
  179. package/src/worktree.ts +76 -0
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Bundled workflow commands: `/deep-research`, `/adversarial-review`,
3
+ * `/multi-perspective`, `/code-review`, and `/codebase-audit`.
4
+ *
5
+ * Each command starts its generated workflow through the WorkflowManager's
6
+ * background path — the command returns immediately, progress is visible in
7
+ * the task panel and `/workflows` (pause/stop work like any managed run), and
8
+ * the report is delivered back into the conversation on completion by
9
+ * installResultDelivery. Running inline in the handler instead would block the
10
+ * whole session until the workflow finished (#104).
11
+ */
12
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
+ import type { WorkflowManager } from "./workflow-manager.js";
14
+ import { type WorkflowStorage } from "./workflow-saved.js";
15
+ export declare function registerBuiltinWorkflows(pi: ExtensionAPI, opts: {
16
+ cwd: string;
17
+ manager: WorkflowManager;
18
+ storage?: WorkflowStorage;
19
+ }): void;
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Bundled workflow commands: `/deep-research`, `/adversarial-review`,
3
+ * `/multi-perspective`, `/code-review`, and `/codebase-audit`.
4
+ *
5
+ * Each command starts its generated workflow through the WorkflowManager's
6
+ * background path — the command returns immediately, progress is visible in
7
+ * the task panel and `/workflows` (pause/stop work like any managed run), and
8
+ * the report is delivered back into the conversation on completion by
9
+ * installResultDelivery. Running inline in the handler instead would block the
10
+ * whole session until the workflow finished (#104).
11
+ */
12
+ import { execFile } from "node:child_process";
13
+ import { promisify } from "node:util";
14
+ import { findBuiltinWorkflow } from "./builtin-workflows.js";
15
+ import { MAX_DIFF_CHARS } from "./code-review.js";
16
+ import { parseCommandArgs } from "./saved-commands.js";
17
+ import { createWorkflowStorage } from "./workflow-saved.js";
18
+ const execFileAsync = promisify(execFile);
19
+ /**
20
+ * Cap on the diff-source exec's stdout+stderr buffer. Node's default (1 MB)
21
+ * throws on anything but a small diff — `gh pr diff` on a sizeable PR routinely
22
+ * exceeds it. 64 MB comfortably covers any realistic diff while still bounding
23
+ * worst-case memory; the prompt-side cap (code-review.ts's MAX_DIFF_CHARS) is
24
+ * what actually protects the review from a huge diff, not this buffer.
25
+ */
26
+ const DIFF_EXEC_MAX_BUFFER = 64 * 1024 * 1024;
27
+ function alreadyRegistered(pi, name) {
28
+ try {
29
+ return (pi.getCommands?.() ?? []).some((c) => c.name === name);
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ /** Split a command argument string into tokens, respecting single/double quotes. */
36
+ function tokenizeArgs(input) {
37
+ const tokens = [];
38
+ for (const m of input.matchAll(/"([^"]*)"|'([^']*)'|(\S+)/g)) {
39
+ tokens.push(m[1] ?? m[2] ?? m[3] ?? "");
40
+ }
41
+ return tokens;
42
+ }
43
+ /**
44
+ * Start a built-in workflow through the manager's background path and tell the
45
+ * user where to watch it. startInBackground can throw synchronously (script
46
+ * parse, run lease) — surface that as a notify instead of an unhandled error.
47
+ * Async failures are handled by the manager's generic delivery ("✗ Background
48
+ * workflow … failed"), so no handler-side await is needed — that await is
49
+ * exactly what used to hang the session (#104).
50
+ */
51
+ function startBackground(manager, ctx, name, script, args, exec) {
52
+ try {
53
+ const { runId } = manager.startInBackground(script, args, exec ?? {});
54
+ ctx.ui.notify(`/${name} running in the background (${runId}) — watch the task panel or /workflows; the report is posted here when it finishes.`, "info");
55
+ }
56
+ catch (error) {
57
+ ctx.ui.notify(`${name} failed to start: ${error instanceof Error ? error.message : error}`, "error");
58
+ }
59
+ }
60
+ /**
61
+ * Look up a built-in descriptor by its fixed, hardcoded name. Every call site
62
+ * below passes one of the 5 literal names in BUILTIN_WORKFLOWS, so this can
63
+ * only throw if that registry and this file's command names fall out of sync
64
+ * — a programming error, not a user-input problem (tests pin the names stay
65
+ * in sync, see builtin-commands.test.ts).
66
+ */
67
+ function requireBuiltin(name) {
68
+ const found = findBuiltinWorkflow(name);
69
+ if (!found)
70
+ throw new Error(`internal error: no built-in workflow registered for "${name}"`);
71
+ return found;
72
+ }
73
+ /**
74
+ * Resolve a built-in's script/exec context for the given args, surfacing an
75
+ * invalid-args error (e.g. a whitespace-only string that passes the handler's
76
+ * cheap `!value` check but fails the registry's real validation) as the same
77
+ * kind of warning notify the handlers already use for their own validation,
78
+ * rather than an uncaught rejection.
79
+ */
80
+ function resolveBuiltinOrNotify(name, cwd, args, ctx) {
81
+ try {
82
+ return requireBuiltin(name).resolve(cwd, args);
83
+ }
84
+ catch (error) {
85
+ ctx.ui.notify(`/${name}: ${error instanceof Error ? error.message : String(error)}`, "warning");
86
+ return undefined;
87
+ }
88
+ }
89
+ export function registerBuiltinWorkflows(pi, opts) {
90
+ const { cwd, manager } = opts;
91
+ const storage = opts.storage ?? createWorkflowStorage(cwd);
92
+ /**
93
+ * A project/user saved workflow always takes precedence over a built-in of
94
+ * the same name — on every path, not just the `workflow` tool's `name`
95
+ * input. Builtins are registered as commands before saved workflows
96
+ * (registerAllSavedWorkflows skips a name that's already registered), so
97
+ * without this dynamic check a same-named saved workflow would silently
98
+ * never run from its slash command. Checking here, at invocation time
99
+ * rather than registration time, makes "saved wins" hold regardless of
100
+ * registration order. Mirrors registerSavedWorkflow's own handler exactly
101
+ * (same parseCommandArgs call, same startBackground path, no builtin exec
102
+ * context) so a shadowed command behaves identically to how it would if the
103
+ * saved workflow itself had been registered under this name.
104
+ */
105
+ function runSavedShadowIfPresent(name, rawArgs, ctx) {
106
+ const saved = storage.load(name);
107
+ if (!saved)
108
+ return false;
109
+ startBackground(manager, ctx, name, saved.script, parseCommandArgs(rawArgs, saved.parameters));
110
+ return true;
111
+ }
112
+ if (!alreadyRegistered(pi, "deep-research")) {
113
+ pi.registerCommand("deep-research", {
114
+ description: "Research a question across the web with cross-checked sources",
115
+ async handler(args, ctx) {
116
+ if (runSavedShadowIfPresent("deep-research", args, ctx))
117
+ return;
118
+ const question = args.trim();
119
+ if (!question)
120
+ return ctx.ui.notify("Usage: /deep-research <question>", "warning");
121
+ // Resolve through the shared builtin registry (builtin-workflows.ts) so
122
+ // this command and the workflow tool's `name` input always run the exact
123
+ // same generated script and exec context (tools/toolset) for this pattern.
124
+ const resolved = resolveBuiltinOrNotify("deep-research", cwd, { question }, ctx);
125
+ if (!resolved)
126
+ return;
127
+ startBackground(manager, ctx, "deep-research", resolved.script, { question }, {
128
+ tools: resolved.tools,
129
+ toolset: resolved.toolset,
130
+ });
131
+ },
132
+ });
133
+ }
134
+ if (!alreadyRegistered(pi, "adversarial-review")) {
135
+ pi.registerCommand("adversarial-review", {
136
+ description: "Investigate a task, then cross-check each finding with skeptical reviewers",
137
+ async handler(args, ctx) {
138
+ if (runSavedShadowIfPresent("adversarial-review", args, ctx))
139
+ return;
140
+ const task = args.trim();
141
+ if (!task)
142
+ return ctx.ui.notify("Usage: /adversarial-review <task or question>", "warning");
143
+ const resolved = resolveBuiltinOrNotify("adversarial-review", cwd, { task }, ctx);
144
+ if (!resolved)
145
+ return;
146
+ startBackground(manager, ctx, "adversarial-review", resolved.script, { task });
147
+ },
148
+ });
149
+ }
150
+ if (!alreadyRegistered(pi, "code-review")) {
151
+ pi.registerCommand("code-review", {
152
+ description: "Multi-angle parallel code review: 7 specialized finders (correctness, reuse, simplification, efficiency, altitude) + verify pass → ranked findings",
153
+ async handler(args, ctx) {
154
+ if (runSavedShadowIfPresent("code-review", args, ctx))
155
+ return;
156
+ const input = args.trim();
157
+ let diffSource = "git diff HEAD";
158
+ let diff = "";
159
+ try {
160
+ let cmd;
161
+ let cmdArgs;
162
+ if (!input) {
163
+ diffSource = "git diff HEAD";
164
+ cmd = "git";
165
+ cmdArgs = ["diff", "HEAD"];
166
+ }
167
+ else if (/^\d+$/.test(input)) {
168
+ diffSource = `gh pr diff ${input}`;
169
+ cmd = "gh";
170
+ cmdArgs = ["pr", "diff", input];
171
+ }
172
+ else if (input.includes("..")) {
173
+ diffSource = `git diff ${input}`;
174
+ cmd = "git";
175
+ cmdArgs = ["diff", input];
176
+ }
177
+ else {
178
+ diffSource = `git diff HEAD -- ${input}`;
179
+ cmd = "git";
180
+ cmdArgs = ["diff", "HEAD", "--", input];
181
+ }
182
+ // execFile (not exec/shell) + array args: input can't break out into a
183
+ // shell command. maxBuffer raised well past Node's 1MB default so a
184
+ // large `gh pr diff` doesn't throw ERR_CHILD_PROCESS_STDOUT_MAXBUFFER.
185
+ const { stdout } = await execFileAsync(cmd, cmdArgs, { cwd, maxBuffer: DIFF_EXEC_MAX_BUFFER });
186
+ diff = stdout;
187
+ if (!diff.trim()) {
188
+ return ctx.ui.notify(`No diff output from: ${diffSource}`, "warning");
189
+ }
190
+ }
191
+ catch (err) {
192
+ const code = err?.code;
193
+ if (code === "ERR_CHILD_PROCESS_STDOUT_MAXBUFFER") {
194
+ return ctx.ui.notify(`Diff from ${diffSource} exceeds the ${Math.floor(DIFF_EXEC_MAX_BUFFER / (1024 * 1024))}MB capture limit — ` +
195
+ `narrow the target (e.g. a specific file or path) and try again.`, "error");
196
+ }
197
+ return ctx.ui.notify(`Failed to get diff (${diffSource}): ${err instanceof Error ? err.message : err}`, "error");
198
+ }
199
+ // The workflow itself also caps prompt size (MAX_DIFF_CHARS), but truncating
200
+ // here lets us tell the user clearly rather than have it happen silently deep
201
+ // inside the generated script.
202
+ const originalLength = diff.length;
203
+ if (originalLength > MAX_DIFF_CHARS) {
204
+ diff = diff.slice(0, MAX_DIFF_CHARS);
205
+ ctx.ui.notify(`Diff is ${originalLength.toLocaleString()} characters — truncated to the first ` +
206
+ `${MAX_DIFF_CHARS.toLocaleString()} for the review. Findings past the cut are not covered.`, "warning");
207
+ }
208
+ const resolved = resolveBuiltinOrNotify("code-review", cwd, { diff, diffSource }, ctx);
209
+ if (!resolved)
210
+ return;
211
+ startBackground(manager, ctx, "code-review", resolved.script, { diff, diffSource });
212
+ },
213
+ });
214
+ }
215
+ if (!alreadyRegistered(pi, "multi-perspective")) {
216
+ pi.registerCommand("multi-perspective", {
217
+ description: "Analyze a topic from several independent perspectives in parallel, then synthesize",
218
+ async handler(args, ctx) {
219
+ if (runSavedShadowIfPresent("multi-perspective", args, ctx))
220
+ return;
221
+ const [topic, ...rest] = tokenizeArgs(args);
222
+ if (!topic) {
223
+ return ctx.ui.notify('Usage: /multi-perspective "<topic>" [perspective1] [perspective2] …', "warning");
224
+ }
225
+ // resolve() falls back to a broadly-useful default set when fewer than
226
+ // two perspectives are given (see builtin-workflows.ts).
227
+ const resolved = resolveBuiltinOrNotify("multi-perspective", cwd, { topic, perspectives: rest }, ctx);
228
+ if (!resolved)
229
+ return;
230
+ startBackground(manager, ctx, "multi-perspective", resolved.script);
231
+ },
232
+ });
233
+ }
234
+ if (!alreadyRegistered(pi, "codebase-audit")) {
235
+ pi.registerCommand("codebase-audit", {
236
+ description: "Run parallel checks against a codebase scope, then cross-validate and report",
237
+ async handler(args, ctx) {
238
+ if (runSavedShadowIfPresent("codebase-audit", args, ctx))
239
+ return;
240
+ const [scope, ...checks] = tokenizeArgs(args);
241
+ if (!scope || checks.length === 0) {
242
+ return ctx.ui.notify('Usage: /codebase-audit <scope> "<check1>" ["<check2>" …]', "warning");
243
+ }
244
+ const resolved = resolveBuiltinOrNotify("codebase-audit", cwd, { scope, checks }, ctx);
245
+ if (!resolved)
246
+ return;
247
+ startBackground(manager, ctx, "codebase-audit", resolved.script);
248
+ },
249
+ });
250
+ }
251
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Shared registry of the 5 curated built-in workflow patterns
3
+ * (`deep-research`, `adversarial-review`, `code-review`, `multi-perspective`,
4
+ * `codebase-audit`).
5
+ *
6
+ * This is the single place that turns a pattern's name + caller-supplied args
7
+ * into a runnable script (and, where a pattern needs it, an exec context such
8
+ * as web tools). Both entry points a model or user can reach a built-in
9
+ * through — the `/deep-research`-style slash commands (builtin-commands.ts)
10
+ * and the `workflow` tool's `name` input (workflow-tool.ts) — resolve through
11
+ * this one registry, so the two paths can never drift apart and the
12
+ * per-pattern generator scripts are written exactly once.
13
+ */
14
+ import { type ToolDefinition } from "@earendil-works/pi-coding-agent";
15
+ import type { WorkflowStorage } from "./workflow-saved.js";
16
+ /** Default perspective set used when a caller gives fewer than two. */
17
+ export declare const DEFAULT_MULTI_PERSPECTIVES: readonly string[];
18
+ /** A resolved, ready-to-run script plus the exec context it needs (if any). */
19
+ export interface BuiltinWorkflowInvocation {
20
+ script: string;
21
+ tools?: ToolDefinition[];
22
+ toolset?: string;
23
+ }
24
+ export interface BuiltinWorkflowDescriptor {
25
+ /** Also the slash-command name (without the leading `/`). */
26
+ name: string;
27
+ description: string;
28
+ /** Build the script (and exec context) for one invocation; throws on invalid `args`. */
29
+ resolve(cwd: string, args: unknown): BuiltinWorkflowInvocation;
30
+ }
31
+ /** The 5 curated built-in workflow patterns, keyed by their stable name. */
32
+ export declare const BUILTIN_WORKFLOWS: readonly BuiltinWorkflowDescriptor[];
33
+ /** Stable list of built-in workflow pattern names, in registry order. */
34
+ export declare const BUILTIN_WORKFLOW_NAMES: readonly string[];
35
+ export declare function findBuiltinWorkflow(name: string): BuiltinWorkflowDescriptor | undefined;
36
+ /**
37
+ * Resolve a name to a runnable invocation, checking project/user saved
38
+ * workflows first and falling back to the built-in patterns — the same
39
+ * precedence `workflow-saved.ts` already uses internally (project > user), one
40
+ * level up: saved workflows (of either scope) beat a built-in of the same name.
41
+ */
42
+ export declare function resolveWorkflowInvocation(name: string, args: unknown, ctx: {
43
+ storage: WorkflowStorage;
44
+ cwd: string;
45
+ }): BuiltinWorkflowInvocation | undefined;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Shared registry of the 5 curated built-in workflow patterns
3
+ * (`deep-research`, `adversarial-review`, `code-review`, `multi-perspective`,
4
+ * `codebase-audit`).
5
+ *
6
+ * This is the single place that turns a pattern's name + caller-supplied args
7
+ * into a runnable script (and, where a pattern needs it, an exec context such
8
+ * as web tools). Both entry points a model or user can reach a built-in
9
+ * through — the `/deep-research`-style slash commands (builtin-commands.ts)
10
+ * and the `workflow` tool's `name` input (workflow-tool.ts) — resolve through
11
+ * this one registry, so the two paths can never drift apart and the
12
+ * per-pattern generator scripts are written exactly once.
13
+ */
14
+ import { createCodingTools } from "@earendil-works/pi-coding-agent";
15
+ import { generateAdversarialReviewWorkflow, generateMultiPerspectiveWorkflow } from "./adversarial-review.js";
16
+ import { generateCodeReviewWorkflow } from "./code-review.js";
17
+ import { generateCodebaseAuditWorkflow, generateDeepResearchWorkflow } from "./deep-research.js";
18
+ import { createWebTools } from "./web-tools.js";
19
+ /** Default perspective set used when a caller gives fewer than two. */
20
+ export const DEFAULT_MULTI_PERSPECTIVES = [
21
+ "technical",
22
+ "product",
23
+ "security",
24
+ "user experience",
25
+ "maintainability",
26
+ ];
27
+ function asRecord(args) {
28
+ return args && typeof args === "object" ? args : {};
29
+ }
30
+ function requireNonEmptyString(value, argName, patternName) {
31
+ if (typeof value !== "string" || !value.trim()) {
32
+ throw new Error(`Built-in workflow "${patternName}" requires args.${argName} to be a non-empty string.`);
33
+ }
34
+ return value;
35
+ }
36
+ function requireStringArray(value, argName, patternName) {
37
+ if (!Array.isArray(value) || value.length === 0 || !value.every((v) => typeof v === "string" && v.trim())) {
38
+ throw new Error(`Built-in workflow "${patternName}" requires args.${argName} to be a non-empty array of non-empty strings.`);
39
+ }
40
+ return value;
41
+ }
42
+ /** The 5 curated built-in workflow patterns, keyed by their stable name. */
43
+ export const BUILTIN_WORKFLOWS = [
44
+ {
45
+ name: "deep-research",
46
+ description: "Research a question across the web with cross-checked sources. args: { question: string }.",
47
+ resolve(cwd, args) {
48
+ requireNonEmptyString(asRecord(args).question, "question", "deep-research");
49
+ return {
50
+ script: generateDeepResearchWorkflow(),
51
+ // Research agents need real web access on top of the coding tools; the
52
+ // "web-research" tag is what a resumed run re-resolves (see
53
+ // WorkflowManagerOptions.toolsets).
54
+ tools: [...createCodingTools(cwd), ...createWebTools()],
55
+ toolset: "web-research",
56
+ };
57
+ },
58
+ },
59
+ {
60
+ name: "adversarial-review",
61
+ description: "Investigate a task, then cross-check each finding with skeptical reviewers. args: { task: string, reviewers?: number, threshold?: number }.",
62
+ resolve(_cwd, args) {
63
+ requireNonEmptyString(asRecord(args).task, "task", "adversarial-review");
64
+ return { script: generateAdversarialReviewWorkflow() };
65
+ },
66
+ },
67
+ {
68
+ name: "code-review",
69
+ description: "Multi-angle parallel code review: 7 specialized finders (correctness, reuse, simplification, efficiency, altitude) + verify pass → ranked findings. args: { diff: string, diffSource?: string }.",
70
+ resolve(_cwd, args) {
71
+ // Truncation past MAX_DIFF_CHARS already happens inside the generated
72
+ // script at runtime (see code-review.ts); a caller invoking by name is
73
+ // responsible for supplying `diff` (e.g. by running `git diff` itself),
74
+ // unlike the /code-review slash command, which fetches it automatically.
75
+ requireNonEmptyString(asRecord(args).diff, "diff", "code-review");
76
+ return { script: generateCodeReviewWorkflow() };
77
+ },
78
+ },
79
+ {
80
+ name: "multi-perspective",
81
+ description: "Analyze a topic from several independent perspectives in parallel, then synthesize. args: { topic: string, perspectives?: string[] }.",
82
+ resolve(_cwd, args) {
83
+ const record = asRecord(args);
84
+ const topic = requireNonEmptyString(record.topic, "topic", "multi-perspective");
85
+ const perspectives = Array.isArray(record.perspectives) && record.perspectives.length >= 2
86
+ ? requireStringArray(record.perspectives, "perspectives", "multi-perspective")
87
+ : [...DEFAULT_MULTI_PERSPECTIVES];
88
+ return { script: generateMultiPerspectiveWorkflow(topic, perspectives) };
89
+ },
90
+ },
91
+ {
92
+ name: "codebase-audit",
93
+ description: "Run parallel checks against a codebase scope, then cross-validate and report. args: { scope: string, checks: string[] }.",
94
+ resolve(_cwd, args) {
95
+ const record = asRecord(args);
96
+ const scope = requireNonEmptyString(record.scope, "scope", "codebase-audit");
97
+ const checks = requireStringArray(record.checks, "checks", "codebase-audit");
98
+ return { script: generateCodebaseAuditWorkflow(scope, checks) };
99
+ },
100
+ },
101
+ ];
102
+ /** Stable list of built-in workflow pattern names, in registry order. */
103
+ export const BUILTIN_WORKFLOW_NAMES = BUILTIN_WORKFLOWS.map((w) => w.name);
104
+ export function findBuiltinWorkflow(name) {
105
+ return BUILTIN_WORKFLOWS.find((w) => w.name === name);
106
+ }
107
+ /**
108
+ * Resolve a name to a runnable invocation, checking project/user saved
109
+ * workflows first and falling back to the built-in patterns — the same
110
+ * precedence `workflow-saved.ts` already uses internally (project > user), one
111
+ * level up: saved workflows (of either scope) beat a built-in of the same name.
112
+ */
113
+ export function resolveWorkflowInvocation(name, args, ctx) {
114
+ const saved = ctx.storage.load(name);
115
+ if (saved)
116
+ return { script: saved.script };
117
+ const builtin = findBuiltinWorkflow(name);
118
+ if (builtin)
119
+ return builtin.resolve(ctx.cwd, args);
120
+ return undefined;
121
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Multi-angle parallel code review workflow.
3
+ * 7 specialized finder agents → verify pass → ranked report.
4
+ */
5
+ /**
6
+ * Hard cap on diff characters fed into the review. This bounds worst-case
7
+ * prompt size across 7 parallel finders + a per-candidate verify pass, even
8
+ * when the diff-source exec step (see builtin-commands.ts) already raised its
9
+ * own maxBuffer and successfully read a very large diff. Oversized diffs are
10
+ * truncated rather than rejected — findings in the untruncated prefix still
11
+ * have value — and the truncation is surfaced to the user, not silent.
12
+ */
13
+ export declare const MAX_DIFF_CHARS = 200000;
14
+ /**
15
+ * Generate a code-review workflow script.
16
+ *
17
+ * The workflow expects `args` to be passed with shape:
18
+ * { diff: string, diffSource: string }
19
+ *
20
+ * Model tier routing follows the spec:
21
+ * Finders A/B/C → medium (correctness)
22
+ * Finders D/E/F → small (cleanup)
23
+ * Finder G → big (altitude / abstraction)
24
+ * Synthesis → big
25
+ */
26
+ export declare function generateCodeReviewWorkflow(): string;
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Multi-angle parallel code review workflow.
3
+ * 7 specialized finder agents → verify pass → ranked report.
4
+ */
5
+ /**
6
+ * Hard cap on diff characters fed into the review. This bounds worst-case
7
+ * prompt size across 7 parallel finders + a per-candidate verify pass, even
8
+ * when the diff-source exec step (see builtin-commands.ts) already raised its
9
+ * own maxBuffer and successfully read a very large diff. Oversized diffs are
10
+ * truncated rather than rejected — findings in the untruncated prefix still
11
+ * have value — and the truncation is surfaced to the user, not silent.
12
+ */
13
+ export const MAX_DIFF_CHARS = 200_000;
14
+ /**
15
+ * Generate a code-review workflow script.
16
+ *
17
+ * The workflow expects `args` to be passed with shape:
18
+ * { diff: string, diffSource: string }
19
+ *
20
+ * Model tier routing follows the spec:
21
+ * Finders A/B/C → medium (correctness)
22
+ * Finders D/E/F → small (cleanup)
23
+ * Finder G → big (altitude / abstraction)
24
+ * Synthesis → big
25
+ */
26
+ export function generateCodeReviewWorkflow() {
27
+ return `export const meta = {
28
+ name: 'code_review',
29
+ description: 'Multi-angle parallel code review: 7 finder angles + verify pass → ranked findings',
30
+ phases: [
31
+ { title: 'Find' },
32
+ { title: 'Verify' },
33
+ { title: 'Report' },
34
+ ],
35
+ }
36
+
37
+ const MAX_DIFF_CHARS = ${MAX_DIFF_CHARS}
38
+ const rawDiff = (args && args.diff) || ''
39
+ const diffSource = (args && args.diffSource) || 'git diff HEAD'
40
+ const diffTruncated = rawDiff.length > MAX_DIFF_CHARS
41
+ const diff = diffTruncated ? rawDiff.slice(0, MAX_DIFF_CHARS) : rawDiff
42
+ if (diffTruncated) {
43
+ log(
44
+ 'Diff truncated for review: showing the first ' + MAX_DIFF_CHARS + ' of ' + rawDiff.length +
45
+ ' characters (' + (rawDiff.length - MAX_DIFF_CHARS) + ' omitted). Findings past the cut are not covered.'
46
+ )
47
+ }
48
+ const candidateSchema = {
49
+ type: 'object',
50
+ properties: {
51
+ candidates: {
52
+ type: 'array',
53
+ items: {
54
+ type: 'object',
55
+ properties: {
56
+ file: { type: 'string' },
57
+ line: { type: 'number' },
58
+ summary: { type: 'string' },
59
+ failure_scenario: { type: 'string' },
60
+ },
61
+ required: ['file', 'line', 'summary', 'failure_scenario'],
62
+ },
63
+ },
64
+ },
65
+ required: ['candidates'],
66
+ }
67
+
68
+ const diffBlock = '\\n\\n<diff source=\\"' + diffSource + '\\"' + (diffTruncated ? ' truncated=\\"true\\"' : '') + '>\\n' +
69
+ diff + (diffTruncated ? '\\n\\n[... diff truncated: ' + (rawDiff.length - MAX_DIFF_CHARS) + ' more characters omitted ...]' : '') +
70
+ '\\n</diff>\\n'
71
+ const base = 'Use the read/grep tools to pull in any additional file context you need.' + diffBlock
72
+
73
+ phase('Find')
74
+ const finders = await parallel([
75
+ () => agent(
76
+ 'You are a line-by-line correctness scanner. Hunt ONLY for: inverted conditions, off-by-one errors, ' +
77
+ 'null/nil dereferences, wrong variable used, swallowed errors. For each candidate name the exact file, ' +
78
+ 'line number, a one-line summary, and the concrete failure scenario. Return ONLY issues you can justify ' +
79
+ 'with a line in the diff.' + base,
80
+ { label: 'A-line-scan', tier: 'medium', schema: candidateSchema }
81
+ ),
82
+ () => agent(
83
+ 'You are a removed-behavior auditor. For every deleted line or block in the diff: name the invariant ' +
84
+ 'or contract it enforced, then find where (or prove) that contract is re-established elsewhere. ' +
85
+ 'Report only gaps where the invariant is NOT re-established.' + base,
86
+ { label: 'B-removed-behavior', tier: 'medium', schema: candidateSchema }
87
+ ),
88
+ () => agent(
89
+ 'You are a cross-file call-site tracer. For each function/method whose signature or behavior changed ' +
90
+ 'in the diff: grep the codebase for callers, then check whether each call site is still correct after ' +
91
+ 'the change. Report only call sites that are now broken or need updating.' + base,
92
+ { label: 'C-cross-file-tracer', tier: 'medium', schema: candidateSchema }
93
+ ),
94
+ () => agent(
95
+ 'You are a reuse finder. Identify new code in the diff that duplicates existing helpers, utilities, ' +
96
+ 'or patterns already present in the codebase. Propose the existing symbol that should be used instead.' + base,
97
+ { label: 'D-reuse', tier: 'small', schema: candidateSchema }
98
+ ),
99
+ () => agent(
100
+ 'You are a simplification finder. Look for: redundant state that could be derived, copy-paste ' +
101
+ 'variation that could be a shared function, and dead code introduced by the diff.' + base,
102
+ { label: 'E-simplification', tier: 'small', schema: candidateSchema }
103
+ ),
104
+ () => agent(
105
+ 'You are an efficiency finder. Identify: redundant I/O or network calls, sequential work that could ' +
106
+ 'be parallel, and blocking operations on the startup or hot path introduced by the diff.' + base,
107
+ { label: 'F-efficiency', tier: 'small', schema: candidateSchema }
108
+ ),
109
+ () => agent(
110
+ 'You are an altitude reviewer. Assess whether the change is made at the RIGHT abstraction level. ' +
111
+ 'Look for: bandaids on shared infrastructure that should be fixed at the root, fixes in the wrong ' +
112
+ 'layer (e.g. compensating in the UI for a data model problem), or the change solving a symptom ' +
113
+ 'rather than the cause.' + base,
114
+ { label: 'G-altitude', tier: 'big', schema: candidateSchema }
115
+ ),
116
+ ])
117
+
118
+ // Collect and deduplicate candidates across all finders
119
+ const allRaw = finders.flatMap((r, fi) => {
120
+ const label = ['A','B','C','D','E','F','G'][fi]
121
+ return ((r && r.candidates) || []).map((c) => ({ ...c, angle: label }))
122
+ })
123
+
124
+ // Deduplicate: same file + line + first 40 chars of summary → keep first
125
+ const seen = new Set()
126
+ const allCandidates = allRaw.filter((c) => {
127
+ const key = (c.file || '') + ':' + (c.line || 0) + ':' + (c.summary || '').slice(0, 40)
128
+ if (seen.has(key)) return false
129
+ seen.add(key)
130
+ return true
131
+ })
132
+
133
+ phase('Verify')
134
+ // NOTE: deliberately NOT using the verify() stdlib helper here. verify() only
135
+ // returns a boolean real/not-real vote; this phase needs the 3-way
136
+ // CONFIRMED/PLAUSIBLE/REFUTED verdict so the synthesis report can hedge
137
+ // ("worth a second look" vs "will break"). Since only REFUTED is filtered out
138
+ // below, verify()'s boolean would collapse CONFIRMED and PLAUSIBLE into one
139
+ // bucket and lose that signal for no behavioral gain — verify({reviewers: 1})
140
+ // is already a single agent() call under the hood, same as this.
141
+ const verdicts = allCandidates.length > 0
142
+ ? await parallel(allCandidates.map((c, i) => () =>
143
+ agent(
144
+ 'You are a verifier. Determine whether this code review finding is CONFIRMED, PLAUSIBLE, or REFUTED. ' +
145
+ 'CONFIRMED = you can trace the exact failure in the diff. PLAUSIBLE = concern is valid but not certain. ' +
146
+ 'REFUTED = finding is wrong or already handled.\\n\\n' +
147
+ 'FINDING:\\nFile: ' + c.file + '\\nLine: ' + c.line + '\\nSummary: ' + c.summary + '\\n' +
148
+ 'Failure scenario: ' + c.failure_scenario + diffBlock,
149
+ {
150
+ label: 'verify-' + (i + 1),
151
+ schema: {
152
+ type: 'object',
153
+ properties: { verdict: { type: 'string', enum: ['CONFIRMED', 'PLAUSIBLE', 'REFUTED'] }, reason: { type: 'string' } },
154
+ required: ['verdict'],
155
+ },
156
+ }
157
+ )
158
+ ))
159
+ : []
160
+
161
+ const surviving = allCandidates
162
+ .map((c, i) => ({ ...c, verdict: (verdicts[i] && verdicts[i].verdict) || 'PLAUSIBLE', verifyReason: (verdicts[i] && verdicts[i].reason) || '' }))
163
+ .filter((c) => c.verdict !== 'REFUTED')
164
+
165
+ // Rank: correctness (A/B/C) before cleanup (D/E/F) before altitude (G), cap at 10
166
+ const rankAngle = (a) => ['A','B','C'].includes(a) ? 0 : ['D','E','F'].includes(a) ? 1 : 2
167
+ surviving.sort((a, b) => rankAngle(a.angle) - rankAngle(b.angle))
168
+ const top = surviving.slice(0, 10)
169
+
170
+ phase('Report')
171
+ const synthesis = await agent(
172
+ 'You are a senior code reviewer writing the final report. Below are the verified findings from a ' +
173
+ 'multi-angle code review (already ranked by severity). Write a concise markdown report: ' +
174
+ '1 sentence per finding with file, line, and the failure scenario. Note the total found vs shown. ' +
175
+ 'Correctness issues (A/B/C) come first, then cleanup (D/E/F), then altitude (G).\\n\\n' +
176
+ 'FINDINGS JSON:\\n' + JSON.stringify(top, null, 2),
177
+ { label: 'synthesis', tier: 'big' }
178
+ )
179
+
180
+ return { total: allCandidates.length, surviving: surviving.length, findings: top, report: synthesis, diffTruncated }`;
181
+ }