@davidorex/pi-workflows 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -4
- package/README.md +12 -2
- package/agents/architecture-designer.agent.yaml +58 -0
- package/agents/architecture-inferrer.agent.yaml +60 -0
- package/agents/gap-identifier.agent.yaml +66 -0
- package/agents/handoff-writer.agent.yaml +64 -0
- package/agents/plan-creator.agent.yaml +63 -0
- package/agents/project-definer.agent.yaml +53 -0
- package/agents/project-inferrer.agent.yaml +63 -0
- package/agents/requirements-gatherer.agent.yaml +58 -0
- package/dist/agent-spec.d.ts +27 -0
- package/dist/agent-spec.d.ts.map +1 -0
- package/dist/agent-spec.js +112 -0
- package/dist/agent-spec.js.map +1 -0
- package/dist/checkpoint.d.ts +50 -0
- package/dist/checkpoint.d.ts.map +1 -0
- package/dist/checkpoint.js +109 -0
- package/dist/checkpoint.js.map +1 -0
- package/dist/completion.d.ts +18 -0
- package/dist/completion.d.ts.map +1 -0
- package/dist/completion.js +58 -0
- package/dist/completion.js.map +1 -0
- package/dist/dag.d.ts +56 -0
- package/dist/dag.d.ts.map +1 -0
- package/dist/dag.js +178 -0
- package/dist/dag.js.map +1 -0
- package/dist/dispatch.d.ts +35 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +311 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/expression.d.ts +64 -0
- package/dist/expression.d.ts.map +1 -0
- package/dist/expression.js +294 -0
- package/dist/expression.js.map +1 -0
- package/dist/format.d.ts +19 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +42 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +724 -0
- package/dist/index.js.map +1 -0
- package/dist/output.d.ts +12 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +59 -0
- package/dist/output.js.map +1 -0
- package/dist/state.d.ts +66 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +227 -0
- package/dist/state.js.map +1 -0
- package/dist/step-agent.d.ts +35 -0
- package/dist/step-agent.d.ts.map +1 -0
- package/dist/step-agent.js +171 -0
- package/dist/step-agent.js.map +1 -0
- package/dist/step-command.d.ts +21 -0
- package/dist/step-command.d.ts.map +1 -0
- package/dist/step-command.js +150 -0
- package/dist/step-command.js.map +1 -0
- package/dist/step-foreach.d.ts +33 -0
- package/dist/step-foreach.d.ts.map +1 -0
- package/dist/step-foreach.js +112 -0
- package/dist/step-foreach.js.map +1 -0
- package/dist/step-gate.d.ts +18 -0
- package/dist/step-gate.d.ts.map +1 -0
- package/dist/step-gate.js +137 -0
- package/dist/step-gate.js.map +1 -0
- package/dist/step-loop.d.ts +32 -0
- package/dist/step-loop.d.ts.map +1 -0
- package/dist/step-loop.js +278 -0
- package/dist/step-loop.js.map +1 -0
- package/dist/step-monitor.d.ts +56 -0
- package/dist/step-monitor.d.ts.map +1 -0
- package/dist/step-monitor.js +317 -0
- package/dist/step-monitor.js.map +1 -0
- package/dist/step-parallel.d.ts +43 -0
- package/dist/step-parallel.d.ts.map +1 -0
- package/dist/step-parallel.js +122 -0
- package/dist/step-parallel.js.map +1 -0
- package/{src/step-pause.ts → dist/step-pause.d.ts} +3 -13
- package/dist/step-pause.d.ts.map +1 -0
- package/dist/step-pause.js +22 -0
- package/dist/step-pause.js.map +1 -0
- package/dist/step-shared.d.ts +63 -0
- package/dist/step-shared.d.ts.map +1 -0
- package/dist/step-shared.js +136 -0
- package/dist/step-shared.js.map +1 -0
- package/dist/step-transform.d.ts +11 -0
- package/dist/step-transform.d.ts.map +1 -0
- package/dist/step-transform.js +41 -0
- package/dist/step-transform.js.map +1 -0
- package/dist/template.d.ts +51 -0
- package/dist/template.d.ts.map +1 -0
- package/{src/template.ts → dist/template.js} +32 -38
- package/dist/template.js.map +1 -0
- package/dist/test-helpers.d.ts +17 -0
- package/dist/test-helpers.d.ts.map +1 -0
- package/dist/test-helpers.js +46 -0
- package/dist/test-helpers.js.map +1 -0
- package/dist/tui.d.ts +47 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +143 -0
- package/dist/tui.js.map +1 -0
- package/dist/types.d.ts +165 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/workflow-discovery.d.ts +23 -0
- package/dist/workflow-discovery.d.ts.map +1 -0
- package/dist/workflow-discovery.js +103 -0
- package/dist/workflow-discovery.js.map +1 -0
- package/dist/workflow-executor.d.ts +50 -0
- package/dist/workflow-executor.d.ts.map +1 -0
- package/dist/workflow-executor.js +738 -0
- package/dist/workflow-executor.js.map +1 -0
- package/dist/workflow-sdk.d.ts +44 -0
- package/dist/workflow-sdk.d.ts.map +1 -0
- package/dist/workflow-sdk.js +457 -0
- package/dist/workflow-sdk.js.map +1 -0
- package/dist/workflow-spec.d.ts +33 -0
- package/dist/workflow-spec.d.ts.map +1 -0
- package/dist/workflow-spec.js +476 -0
- package/dist/workflow-spec.js.map +1 -0
- package/dist/workflows-dir.d.ts +3 -0
- package/dist/workflows-dir.d.ts.map +1 -0
- package/{src/workflows-dir.ts → dist/workflows-dir.js} +1 -0
- package/dist/workflows-dir.js.map +1 -0
- package/package.json +53 -38
- package/schemas/decomposition-specs.schema.json +48 -48
- package/schemas/execution-results.schema.json +48 -48
- package/schemas/investigation-findings.schema.json +43 -43
- package/schemas/pattern-analysis.schema.json +41 -41
- package/schemas/phase.schema.json +48 -0
- package/schemas/plan-breakdown.schema.json +20 -20
- package/schemas/quality-analysis.schema.json +33 -33
- package/schemas/research-findings.schema.json +42 -42
- package/schemas/structure-analysis.schema.json +40 -40
- package/schemas/synthesis.schema.json +31 -31
- package/schemas/verifier-output.schema.json +94 -94
- package/skill-narrative.md +107 -0
- package/skills/pi-workflows/SKILL.md +185 -0
- package/skills/pi-workflows/references/bundled-resources.md +87 -0
- package/templates/architecture-designer/task.md +117 -0
- package/templates/architecture-inferrer/task.md +61 -0
- package/templates/gap-identifier/task.md +103 -0
- package/templates/handoff-writer/task.md +91 -0
- package/templates/plan-creator/task.md +143 -0
- package/templates/project-definer/task.md +67 -0
- package/templates/project-inferrer/task.md +56 -0
- package/templates/requirements-gatherer/task.md +90 -0
- package/workflows/analyze-existing-project.workflow.yaml +94 -0
- package/workflows/create-handoff.workflow.yaml +66 -0
- package/workflows/create-phase.workflow.yaml +1 -1
- package/workflows/do-gap.workflow.yaml +4 -4
- package/workflows/fix-audit.workflow.yaml +1 -1
- package/workflows/gap-to-phase.workflow.yaml +1 -1
- package/workflows/init-new-project.workflow.yaml +81 -0
- package/workflows/plan-from-requirements.workflow.yaml +59 -0
- package/workflows/self-implement.workflow.yaml +3 -3
- package/workflows/typed-analysis.workflow.yaml +4 -4
- package/src/agent-spec.test.ts +0 -289
- package/src/agent-spec.ts +0 -122
- package/src/block-validation.test.ts +0 -350
- package/src/checkpoint.test.ts +0 -237
- package/src/checkpoint.ts +0 -140
- package/src/completion.test.ts +0 -143
- package/src/completion.ts +0 -68
- package/src/dag.test.ts +0 -350
- package/src/dag.ts +0 -219
- package/src/dispatch.test.ts +0 -475
- package/src/dispatch.ts +0 -353
- package/src/expression.test.ts +0 -353
- package/src/expression.ts +0 -332
- package/src/format.test.ts +0 -80
- package/src/format.ts +0 -41
- package/src/graduated-failure.test.ts +0 -556
- package/src/index.test.ts +0 -114
- package/src/index.ts +0 -516
- package/src/loop.test.ts +0 -551
- package/src/output.test.ts +0 -213
- package/src/output.ts +0 -70
- package/src/parallel-integration.test.ts +0 -175
- package/src/resume.test.ts +0 -192
- package/src/state.test.ts +0 -192
- package/src/state.ts +0 -254
- package/src/step-agent.test.ts +0 -327
- package/src/step-agent.ts +0 -178
- package/src/step-command.test.ts +0 -330
- package/src/step-command.ts +0 -132
- package/src/step-foreach.test.ts +0 -647
- package/src/step-foreach.ts +0 -148
- package/src/step-gate.test.ts +0 -185
- package/src/step-gate.ts +0 -116
- package/src/step-loop.test.ts +0 -626
- package/src/step-loop.ts +0 -323
- package/src/step-parallel.test.ts +0 -475
- package/src/step-parallel.ts +0 -168
- package/src/step-pause.test.ts +0 -30
- package/src/step-shared.test.ts +0 -355
- package/src/step-shared.ts +0 -157
- package/src/step-transform.test.ts +0 -155
- package/src/step-transform.ts +0 -47
- package/src/template-integration.test.ts +0 -72
- package/src/template.test.ts +0 -162
- package/src/test-helpers.ts +0 -51
- package/src/tui.test.ts +0 -355
- package/src/tui.ts +0 -182
- package/src/types.ts +0 -195
- package/src/verifier-schema.test.ts +0 -226
- package/src/workflow-discovery.test.ts +0 -105
- package/src/workflow-discovery.ts +0 -114
- package/src/workflow-executor.test.ts +0 -1532
- package/src/workflow-executor.ts +0 -810
- package/src/workflow-sdk.test.ts +0 -238
- package/src/workflow-sdk.ts +0 -262
- package/src/workflow-spec.test.ts +0 -576
- package/src/workflow-spec.ts +0 -471
package/src/workflow-spec.ts
DELETED
|
@@ -1,471 +0,0 @@
|
|
|
1
|
-
import { parse as parseYaml } from "yaml";
|
|
2
|
-
import type { WorkflowSpec, StepSpec, StepOutputSpec, CompletionSpec, ArtifactSpec, LoopSpec, GateSpec, TransformSpec, RetryConfig } from "./types.ts";
|
|
3
|
-
|
|
4
|
-
/** Descriptor for a workflow step type — used by SDK and spec validation. */
|
|
5
|
-
export interface StepTypeDescriptor {
|
|
6
|
-
name: string;
|
|
7
|
-
field: string;
|
|
8
|
-
retryable: boolean;
|
|
9
|
-
supportsInput: boolean;
|
|
10
|
-
supportsOutput: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Step type registry — add a step type here, parsing and SDK both see it automatically. */
|
|
14
|
-
export const STEP_TYPES: StepTypeDescriptor[] = [
|
|
15
|
-
{ name: "agent", field: "agent", retryable: true, supportsInput: true, supportsOutput: true },
|
|
16
|
-
{ name: "gate", field: "gate", retryable: false, supportsInput: false, supportsOutput: false },
|
|
17
|
-
{ name: "transform", field: "transform", retryable: false, supportsInput: false, supportsOutput: false },
|
|
18
|
-
{ name: "loop", field: "loop", retryable: true, supportsInput: false, supportsOutput: false },
|
|
19
|
-
{ name: "parallel", field: "parallel", retryable: true, supportsInput: false, supportsOutput: false },
|
|
20
|
-
{ name: "pause", field: "pause", retryable: false, supportsInput: false, supportsOutput: false },
|
|
21
|
-
{ name: "command", field: "command", retryable: false, supportsInput: true, supportsOutput: true },
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
/** Set of valid step type field names — derived from STEP_TYPES. */
|
|
25
|
-
export const STEP_TYPE_FIELDS = new Set(STEP_TYPES.map(t => t.field));
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Error class for spec parsing failures.
|
|
29
|
-
*/
|
|
30
|
-
export class WorkflowSpecError extends Error {
|
|
31
|
-
readonly filePath: string;
|
|
32
|
-
readonly reason: string;
|
|
33
|
-
|
|
34
|
-
constructor(filePath: string, reason: string) {
|
|
35
|
-
super(`Invalid workflow spec (${filePath}): ${reason}`);
|
|
36
|
-
this.name = "WorkflowSpecError";
|
|
37
|
-
this.filePath = filePath;
|
|
38
|
-
this.reason = reason;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Parse a YAML string into a WorkflowSpec.
|
|
44
|
-
* Validates structure (required fields, types).
|
|
45
|
-
* Does NOT validate JSON Schemas or resolve agent references — that happens at execution time.
|
|
46
|
-
*
|
|
47
|
-
* @param content - raw YAML string
|
|
48
|
-
* @param filePath - absolute path to the file (stored on the spec, used in error messages)
|
|
49
|
-
* @param source - "user" or "project"
|
|
50
|
-
* @throws WorkflowSpecError on invalid structure
|
|
51
|
-
*/
|
|
52
|
-
export function parseWorkflowSpec(content: string, filePath: string, source: "user" | "project"): WorkflowSpec {
|
|
53
|
-
let doc: unknown;
|
|
54
|
-
try {
|
|
55
|
-
doc = parseYaml(content);
|
|
56
|
-
} catch (err) {
|
|
57
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
58
|
-
throw new WorkflowSpecError(filePath, `invalid YAML: ${msg}`);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (typeof doc !== "object" || doc === null || Array.isArray(doc)) {
|
|
62
|
-
throw new WorkflowSpecError(filePath, "'name' is required");
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const raw = doc as Record<string, unknown>;
|
|
66
|
-
|
|
67
|
-
// Validate name
|
|
68
|
-
if (!("name" in raw) || raw.name === undefined || raw.name === null) {
|
|
69
|
-
throw new WorkflowSpecError(filePath, "'name' is required");
|
|
70
|
-
}
|
|
71
|
-
if (typeof raw.name !== "string") {
|
|
72
|
-
throw new WorkflowSpecError(filePath, "'name' is required");
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Validate steps
|
|
76
|
-
if (!("steps" in raw) || raw.steps === undefined || raw.steps === null) {
|
|
77
|
-
throw new WorkflowSpecError(filePath, "'steps' must be a non-empty object");
|
|
78
|
-
}
|
|
79
|
-
if (typeof raw.steps !== "object" || Array.isArray(raw.steps)) {
|
|
80
|
-
throw new WorkflowSpecError(filePath, "'steps' must be a non-empty object");
|
|
81
|
-
}
|
|
82
|
-
const rawSteps = raw.steps as Record<string, unknown>;
|
|
83
|
-
if (Object.keys(rawSteps).length === 0) {
|
|
84
|
-
throw new WorkflowSpecError(filePath, "'steps' must be a non-empty object");
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Validate each step
|
|
88
|
-
const steps: Record<string, StepSpec> = {};
|
|
89
|
-
for (const [stepName, stepValue] of Object.entries(rawSteps)) {
|
|
90
|
-
steps[stepName] = validateStep(stepValue, stepName, filePath);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Build the spec with defaults
|
|
94
|
-
const spec: WorkflowSpec = {
|
|
95
|
-
name: raw.name as string,
|
|
96
|
-
description: typeof raw.description === "string" ? raw.description : "",
|
|
97
|
-
steps,
|
|
98
|
-
source,
|
|
99
|
-
filePath,
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
if (raw.version !== undefined) spec.version = raw.version as string;
|
|
103
|
-
if (raw.input !== undefined) spec.input = raw.input as Record<string, unknown>;
|
|
104
|
-
if (raw.output !== undefined) spec.output = raw.output as Record<string, unknown>;
|
|
105
|
-
|
|
106
|
-
// triggerTurn defaults to true
|
|
107
|
-
if (typeof raw.triggerTurn === "boolean") {
|
|
108
|
-
spec.triggerTurn = raw.triggerTurn;
|
|
109
|
-
} else {
|
|
110
|
-
spec.triggerTurn = true;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// completion (optional)
|
|
114
|
-
if ("completion" in raw && raw.completion !== undefined) {
|
|
115
|
-
if (typeof raw.completion !== "object" || raw.completion === null || Array.isArray(raw.completion)) {
|
|
116
|
-
throw new WorkflowSpecError(filePath, "'completion' must be an object");
|
|
117
|
-
}
|
|
118
|
-
const rawComp = raw.completion as Record<string, unknown>;
|
|
119
|
-
|
|
120
|
-
// Mutual exclusivity: template and message cannot coexist
|
|
121
|
-
if ("template" in rawComp && "message" in rawComp) {
|
|
122
|
-
throw new WorkflowSpecError(filePath, "'completion' cannot have both 'template' and 'message'");
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Must have at least one
|
|
126
|
-
if (!("template" in rawComp) && !("message" in rawComp)) {
|
|
127
|
-
throw new WorkflowSpecError(filePath, "'completion' must have either 'template' or 'message'");
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const completion: CompletionSpec = {};
|
|
131
|
-
|
|
132
|
-
if (typeof rawComp.template === "string") {
|
|
133
|
-
completion.template = rawComp.template;
|
|
134
|
-
} else if ("template" in rawComp) {
|
|
135
|
-
throw new WorkflowSpecError(filePath, "'completion.template' must be a string");
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (typeof rawComp.message === "string") {
|
|
139
|
-
completion.message = rawComp.message;
|
|
140
|
-
} else if ("message" in rawComp) {
|
|
141
|
-
throw new WorkflowSpecError(filePath, "'completion.message' must be a string");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if ("include" in rawComp) {
|
|
145
|
-
if (!Array.isArray(rawComp.include)) {
|
|
146
|
-
throw new WorkflowSpecError(filePath, "'completion.include' must be an array of strings");
|
|
147
|
-
}
|
|
148
|
-
for (const item of rawComp.include) {
|
|
149
|
-
if (typeof item !== "string") {
|
|
150
|
-
throw new WorkflowSpecError(filePath, "'completion.include' must be an array of strings");
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
completion.include = rawComp.include as string[];
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
spec.completion = completion;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// artifacts (optional)
|
|
160
|
-
if ("artifacts" in raw && raw.artifacts !== undefined) {
|
|
161
|
-
if (typeof raw.artifacts !== "object" || raw.artifacts === null || Array.isArray(raw.artifacts)) {
|
|
162
|
-
throw new WorkflowSpecError(filePath, "'artifacts' must be an object");
|
|
163
|
-
}
|
|
164
|
-
const rawArtifacts = raw.artifacts as Record<string, unknown>;
|
|
165
|
-
const artifacts: Record<string, ArtifactSpec> = {};
|
|
166
|
-
for (const [artName, artValue] of Object.entries(rawArtifacts)) {
|
|
167
|
-
if (typeof artValue !== "object" || artValue === null || Array.isArray(artValue)) {
|
|
168
|
-
throw new WorkflowSpecError(filePath, `artifact '${artName}' must be an object`);
|
|
169
|
-
}
|
|
170
|
-
const rawArt = artValue as Record<string, unknown>;
|
|
171
|
-
if (typeof rawArt.path !== "string") {
|
|
172
|
-
throw new WorkflowSpecError(filePath, `artifact '${artName}' must have a 'path' string`);
|
|
173
|
-
}
|
|
174
|
-
if (typeof rawArt.from !== "string") {
|
|
175
|
-
throw new WorkflowSpecError(filePath, `artifact '${artName}' must have a 'from' string`);
|
|
176
|
-
}
|
|
177
|
-
const artifact: ArtifactSpec = {
|
|
178
|
-
path: rawArt.path,
|
|
179
|
-
from: rawArt.from,
|
|
180
|
-
};
|
|
181
|
-
if (rawArt.schema !== undefined) {
|
|
182
|
-
if (typeof rawArt.schema !== "string") {
|
|
183
|
-
throw new WorkflowSpecError(filePath, `artifact '${artName}' schema must be a string`);
|
|
184
|
-
}
|
|
185
|
-
artifact.schema = rawArt.schema;
|
|
186
|
-
}
|
|
187
|
-
artifacts[artName] = artifact;
|
|
188
|
-
}
|
|
189
|
-
spec.artifacts = artifacts;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return spec;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Validate and parse a single step from raw YAML data.
|
|
197
|
-
* Enforces that exactly one of agent, gate, transform, or loop is set.
|
|
198
|
-
* Rejects steps with `workflow` (not yet supported).
|
|
199
|
-
* Recursively validates sub-steps within loops.
|
|
200
|
-
*/
|
|
201
|
-
function validateStep(stepValue: unknown, stepName: string, filePath: string): StepSpec {
|
|
202
|
-
if (typeof stepValue !== "object" || stepValue === null || Array.isArray(stepValue)) {
|
|
203
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' must be an object`);
|
|
204
|
-
}
|
|
205
|
-
const rawStep = stepValue as Record<string, unknown>;
|
|
206
|
-
|
|
207
|
-
// Reject workflow (not yet supported)
|
|
208
|
-
if ("workflow" in rawStep && rawStep.workflow !== undefined) {
|
|
209
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}': nested workflows ('workflow') are not yet supported`);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Count step types (derived from STEP_TYPES registry)
|
|
213
|
-
const presentTypes = STEP_TYPES.filter(t => t.field in rawStep && rawStep[t.field] !== undefined);
|
|
214
|
-
const stepTypeList = STEP_TYPES.map(t => t.field).join(", ");
|
|
215
|
-
|
|
216
|
-
if (presentTypes.length === 0) {
|
|
217
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' must have exactly one of: ${stepTypeList}`);
|
|
218
|
-
}
|
|
219
|
-
if (presentTypes.length > 1) {
|
|
220
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' must have exactly one of: ${stepTypeList}`);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Individual flags for downstream parsing (derived from presentTypes check above)
|
|
224
|
-
const hasAgent = presentTypes[0].field === "agent";
|
|
225
|
-
const hasGate = presentTypes[0].field === "gate";
|
|
226
|
-
const hasTransform = presentTypes[0].field === "transform";
|
|
227
|
-
const hasLoop = presentTypes[0].field === "loop";
|
|
228
|
-
const hasParallel = presentTypes[0].field === "parallel";
|
|
229
|
-
const hasPause = presentTypes[0].field === "pause";
|
|
230
|
-
const hasCommand = presentTypes[0].field === "command";
|
|
231
|
-
|
|
232
|
-
const step: StepSpec = {};
|
|
233
|
-
|
|
234
|
-
// Common optional fields
|
|
235
|
-
if (rawStep.when !== undefined) step.when = rawStep.when as string;
|
|
236
|
-
if (rawStep.timeout !== undefined) step.timeout = rawStep.timeout as { seconds: number };
|
|
237
|
-
|
|
238
|
-
// Retry config (optional)
|
|
239
|
-
if ("retry" in rawStep && rawStep.retry !== undefined) {
|
|
240
|
-
if (typeof rawStep.retry !== "object" || rawStep.retry === null || Array.isArray(rawStep.retry)) {
|
|
241
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' retry must be an object`);
|
|
242
|
-
}
|
|
243
|
-
const rawRetry = rawStep.retry as Record<string, unknown>;
|
|
244
|
-
const retry: RetryConfig = {};
|
|
245
|
-
if ("maxAttempts" in rawRetry) {
|
|
246
|
-
if (typeof rawRetry.maxAttempts !== "number" || !Number.isInteger(rawRetry.maxAttempts) || rawRetry.maxAttempts < 1) {
|
|
247
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' retry.maxAttempts must be a positive integer`);
|
|
248
|
-
}
|
|
249
|
-
retry.maxAttempts = rawRetry.maxAttempts;
|
|
250
|
-
}
|
|
251
|
-
if ("onExhausted" in rawRetry) {
|
|
252
|
-
if (rawRetry.onExhausted !== "fail" && rawRetry.onExhausted !== "skip") {
|
|
253
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' retry.onExhausted must be 'fail' or 'skip'`);
|
|
254
|
-
}
|
|
255
|
-
retry.onExhausted = rawRetry.onExhausted;
|
|
256
|
-
}
|
|
257
|
-
if ("steeringMessage" in rawRetry) {
|
|
258
|
-
if (typeof rawRetry.steeringMessage !== "string") {
|
|
259
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' retry.steeringMessage must be a string`);
|
|
260
|
-
}
|
|
261
|
-
retry.steeringMessage = rawRetry.steeringMessage;
|
|
262
|
-
}
|
|
263
|
-
step.retry = retry;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// forEach and as (can combine with any step type)
|
|
267
|
-
if ("forEach" in rawStep && rawStep.forEach !== undefined) {
|
|
268
|
-
if (typeof rawStep.forEach !== "string") {
|
|
269
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' forEach must be a string`);
|
|
270
|
-
}
|
|
271
|
-
step.forEach = rawStep.forEach;
|
|
272
|
-
}
|
|
273
|
-
if ("as" in rawStep && rawStep.as !== undefined) {
|
|
274
|
-
if (typeof rawStep.as !== "string") {
|
|
275
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' as must be a string`);
|
|
276
|
-
}
|
|
277
|
-
step.as = rawStep.as;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Pause step
|
|
281
|
-
if (hasPause) {
|
|
282
|
-
if (typeof rawStep.pause !== "string" && rawStep.pause !== true) {
|
|
283
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' pause must be a string or true`);
|
|
284
|
-
}
|
|
285
|
-
step.pause = rawStep.pause as string | boolean;
|
|
286
|
-
return step;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Command step
|
|
290
|
-
if (hasCommand) {
|
|
291
|
-
if (typeof rawStep.command !== "string") {
|
|
292
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' command must be a string`);
|
|
293
|
-
}
|
|
294
|
-
step.command = rawStep.command;
|
|
295
|
-
|
|
296
|
-
// output spec (optional, same as agent)
|
|
297
|
-
if ("output" in rawStep && rawStep.output !== undefined) {
|
|
298
|
-
if (typeof rawStep.output !== "object" || rawStep.output === null || Array.isArray(rawStep.output)) {
|
|
299
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' output must be an object`);
|
|
300
|
-
}
|
|
301
|
-
const rawOutput = rawStep.output as Record<string, unknown>;
|
|
302
|
-
const output: StepOutputSpec = {};
|
|
303
|
-
if ("format" in rawOutput) {
|
|
304
|
-
output.format = rawOutput.format as StepOutputSpec["format"];
|
|
305
|
-
}
|
|
306
|
-
if ("path" in rawOutput) {
|
|
307
|
-
if (typeof rawOutput.path !== "string") {
|
|
308
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' output.path must be a string`);
|
|
309
|
-
}
|
|
310
|
-
output.path = rawOutput.path;
|
|
311
|
-
}
|
|
312
|
-
step.output = output;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// input (optional, for expression resolution context)
|
|
316
|
-
if ("input" in rawStep && rawStep.input !== undefined) {
|
|
317
|
-
if (typeof rawStep.input !== "object" || rawStep.input === null || Array.isArray(rawStep.input)) {
|
|
318
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' input must be an object`);
|
|
319
|
-
}
|
|
320
|
-
step.input = rawStep.input as Record<string, unknown>;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
return step;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Agent step
|
|
327
|
-
if (hasAgent) {
|
|
328
|
-
if (typeof rawStep.agent !== "string") {
|
|
329
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' agent must be a string`);
|
|
330
|
-
}
|
|
331
|
-
step.agent = rawStep.agent;
|
|
332
|
-
|
|
333
|
-
// model must be a string if present
|
|
334
|
-
if ("model" in rawStep && rawStep.model !== undefined) {
|
|
335
|
-
if (typeof rawStep.model !== "string") {
|
|
336
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' model must be a string`);
|
|
337
|
-
}
|
|
338
|
-
step.model = rawStep.model;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// input must be an object if present
|
|
342
|
-
if ("input" in rawStep && rawStep.input !== undefined) {
|
|
343
|
-
if (typeof rawStep.input !== "object" || rawStep.input === null || Array.isArray(rawStep.input)) {
|
|
344
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' input must be an object`);
|
|
345
|
-
}
|
|
346
|
-
step.input = rawStep.input as Record<string, unknown>;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// output must be an object if present
|
|
350
|
-
if ("output" in rawStep && rawStep.output !== undefined) {
|
|
351
|
-
if (typeof rawStep.output !== "object" || rawStep.output === null || Array.isArray(rawStep.output)) {
|
|
352
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' output must be an object`);
|
|
353
|
-
}
|
|
354
|
-
const rawOutput = rawStep.output as Record<string, unknown>;
|
|
355
|
-
const output: StepOutputSpec = {};
|
|
356
|
-
if ("format" in rawOutput) {
|
|
357
|
-
output.format = rawOutput.format as StepOutputSpec["format"];
|
|
358
|
-
}
|
|
359
|
-
if ("schema" in rawOutput) {
|
|
360
|
-
if (typeof rawOutput.schema !== "string") {
|
|
361
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' output.schema must be a string`);
|
|
362
|
-
}
|
|
363
|
-
output.schema = rawOutput.schema;
|
|
364
|
-
}
|
|
365
|
-
if ("path" in rawOutput) {
|
|
366
|
-
if (typeof rawOutput.path !== "string") {
|
|
367
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' output.path must be a string`);
|
|
368
|
-
}
|
|
369
|
-
output.path = rawOutput.path;
|
|
370
|
-
}
|
|
371
|
-
step.output = output;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Gate step
|
|
376
|
-
if (hasGate) {
|
|
377
|
-
if (typeof rawStep.gate !== "object" || rawStep.gate === null || Array.isArray(rawStep.gate)) {
|
|
378
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' gate must be an object`);
|
|
379
|
-
}
|
|
380
|
-
const rawGate = rawStep.gate as Record<string, unknown>;
|
|
381
|
-
if (typeof rawGate.check !== "string") {
|
|
382
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' gate must have a 'check' string`);
|
|
383
|
-
}
|
|
384
|
-
const gate: GateSpec = { check: rawGate.check };
|
|
385
|
-
if (rawGate.onPass !== undefined) gate.onPass = rawGate.onPass as GateSpec["onPass"];
|
|
386
|
-
if (rawGate.onFail !== undefined) gate.onFail = rawGate.onFail as GateSpec["onFail"];
|
|
387
|
-
step.gate = gate;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Transform step
|
|
391
|
-
if (hasTransform) {
|
|
392
|
-
if (typeof rawStep.transform !== "object" || rawStep.transform === null || Array.isArray(rawStep.transform)) {
|
|
393
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' transform must be an object`);
|
|
394
|
-
}
|
|
395
|
-
const rawTransform = rawStep.transform as Record<string, unknown>;
|
|
396
|
-
if (typeof rawTransform.mapping !== "object" || rawTransform.mapping === null || Array.isArray(rawTransform.mapping)) {
|
|
397
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' transform must have a 'mapping' object`);
|
|
398
|
-
}
|
|
399
|
-
step.transform = { mapping: rawTransform.mapping as Record<string, unknown> };
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Loop step
|
|
403
|
-
if (hasLoop) {
|
|
404
|
-
if (typeof rawStep.loop !== "object" || rawStep.loop === null || Array.isArray(rawStep.loop)) {
|
|
405
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' loop must be an object`);
|
|
406
|
-
}
|
|
407
|
-
const rawLoop = rawStep.loop as Record<string, unknown>;
|
|
408
|
-
|
|
409
|
-
// Must have maxAttempts (number or expression string) or attempts (string expression)
|
|
410
|
-
const maxAttemptsRaw = rawLoop.maxAttempts;
|
|
411
|
-
const hasMaxAttemptsNum = typeof maxAttemptsRaw === "number";
|
|
412
|
-
const hasMaxAttemptsExpr = typeof maxAttemptsRaw === "string";
|
|
413
|
-
const hasAttempts = "attempts" in rawLoop && typeof rawLoop.attempts === "string";
|
|
414
|
-
if (!hasMaxAttemptsNum && !hasMaxAttemptsExpr && !hasAttempts) {
|
|
415
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' loop must have 'maxAttempts' (number or expression) or 'attempts' (expression)`);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Must have non-empty steps
|
|
419
|
-
if (!("steps" in rawLoop) || typeof rawLoop.steps !== "object" || rawLoop.steps === null || Array.isArray(rawLoop.steps)) {
|
|
420
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' loop must have a non-empty 'steps' object`);
|
|
421
|
-
}
|
|
422
|
-
const rawLoopSteps = rawLoop.steps as Record<string, unknown>;
|
|
423
|
-
if (Object.keys(rawLoopSteps).length === 0) {
|
|
424
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' loop must have a non-empty 'steps' object`);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// Recursively validate sub-steps
|
|
428
|
-
const loopSteps: Record<string, StepSpec> = {};
|
|
429
|
-
for (const [subName, subValue] of Object.entries(rawLoopSteps)) {
|
|
430
|
-
loopSteps[subName] = validateStep(subValue, `${stepName}.loop.${subName}`, filePath);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
const loop: LoopSpec = {
|
|
434
|
-
maxAttempts: hasMaxAttemptsNum ? maxAttemptsRaw as number : undefined,
|
|
435
|
-
steps: loopSteps,
|
|
436
|
-
};
|
|
437
|
-
// If maxAttempts is an expression string, treat it as the `attempts` field
|
|
438
|
-
if (hasMaxAttemptsExpr) {
|
|
439
|
-
loop.attempts = maxAttemptsRaw as string;
|
|
440
|
-
}
|
|
441
|
-
if (hasAttempts) loop.attempts = rawLoop.attempts as string;
|
|
442
|
-
if (hasMaxAttemptsNum) loop.maxAttempts = maxAttemptsRaw as number;
|
|
443
|
-
|
|
444
|
-
// onExhausted is an optional step
|
|
445
|
-
if ("onExhausted" in rawLoop && rawLoop.onExhausted !== undefined) {
|
|
446
|
-
loop.onExhausted = validateStep(rawLoop.onExhausted, `${stepName}.loop.onExhausted`, filePath);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
step.loop = loop;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Parallel step
|
|
453
|
-
if (hasParallel) {
|
|
454
|
-
if (typeof rawStep.parallel !== "object" || rawStep.parallel === null || Array.isArray(rawStep.parallel)) {
|
|
455
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' parallel must be a non-empty object`);
|
|
456
|
-
}
|
|
457
|
-
const rawParallel = rawStep.parallel as Record<string, unknown>;
|
|
458
|
-
if (Object.keys(rawParallel).length === 0) {
|
|
459
|
-
throw new WorkflowSpecError(filePath, `step '${stepName}' parallel must be a non-empty object`);
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Recursively validate sub-steps
|
|
463
|
-
const parallelSteps: Record<string, StepSpec> = {};
|
|
464
|
-
for (const [subName, subValue] of Object.entries(rawParallel)) {
|
|
465
|
-
parallelSteps[subName] = validateStep(subValue, `${stepName}.parallel.${subName}`, filePath);
|
|
466
|
-
}
|
|
467
|
-
step.parallel = parallelSteps;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
return step;
|
|
471
|
-
}
|