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