stageflow 0.12.0 → 0.15.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/README.md +5 -1
- package/dist/agent/claudeAdapter.js +31 -1
- package/dist/agent/piAdapter.d.ts +6 -4
- package/dist/agent/piAdapter.js +71 -26
- package/dist/agent/piIsolatedMcp.d.ts +23 -0
- package/dist/agent/piIsolatedMcp.js +223 -0
- package/dist/agent/piIsolatedMcpProbe.d.ts +15 -0
- package/dist/agent/piIsolatedMcpProbe.js +292 -0
- package/dist/agent/port.d.ts +4 -2
- package/dist/agent/port.js +1 -1
- package/dist/cli/initTemplates.d.ts +1 -1
- package/dist/cli/initTemplates.js +1 -1
- package/dist/cli/migrateYamlCommand.d.ts +18 -0
- package/dist/cli/migrateYamlCommand.js +180 -0
- package/dist/cli/runOutput.js +26 -18
- package/dist/cli/validateCommand.d.ts +1 -0
- package/dist/cli/validateCommand.js +2 -0
- package/dist/cli.js +15 -1
- package/dist/config/browseCatalog.js +1 -1
- package/dist/config/createPipeline.d.ts +1 -1
- package/dist/config/createPipeline.js +23 -11
- package/dist/config/createStage.d.ts +1 -1
- package/dist/config/createStage.js +10 -5
- package/dist/config/legacyYaml.d.ts +28 -0
- package/dist/config/legacyYaml.js +98 -0
- package/dist/config/loadOutcome.d.ts +2 -1
- package/dist/config/loadOutcome.js +4 -1
- package/dist/config/loadPipeline.d.ts +2 -0
- package/dist/config/loadPipeline.js +151 -8
- package/dist/config/loadStage.d.ts +9 -1
- package/dist/config/loadStage.js +207 -29
- package/dist/config/loadStageflowManifest.js +10 -0
- package/dist/config/loadTask.js +14 -0
- package/dist/config/materializeStageModels.d.ts +7 -0
- package/dist/config/materializeStageModels.js +35 -0
- package/dist/config/mergePipelineIncludes.d.ts +5 -1
- package/dist/config/mergePipelineIncludes.js +82 -4
- package/dist/config/migrateYaml.d.ts +19 -0
- package/dist/config/migrateYaml.js +627 -0
- package/dist/config/modelField.d.ts +13 -0
- package/dist/config/modelField.js +16 -0
- package/dist/config/normalizePipelineStageEntry.js +48 -3
- package/dist/config/parseCompletionContract.d.ts +5 -2
- package/dist/config/parseCompletionContract.js +16 -3
- package/dist/config/parsePreEmitChecks.js +4 -0
- package/dist/config/pipelineNeeds.js +3 -3
- package/dist/config/pipelineStageKeys.d.ts +11 -0
- package/dist/config/pipelineStageKeys.js +15 -0
- package/dist/config/printTargetYaml.d.ts +28 -0
- package/dist/config/printTargetYaml.js +243 -0
- package/dist/config/resolveModel.d.ts +20 -0
- package/dist/config/resolveModel.js +35 -0
- package/dist/config/resolveStageMcpServers.d.ts +35 -0
- package/dist/config/resolveStageMcpServers.js +207 -0
- package/dist/config/validateCatalog.d.ts +5 -1
- package/dist/config/validateCatalog.js +87 -6
- package/dist/config/validateCompletionContract.js +5 -0
- package/dist/config/yamlDialect.d.ts +29 -0
- package/dist/config/yamlDialect.js +327 -0
- package/dist/envelope/payloadSchema.d.ts +37 -5
- package/dist/envelope/payloadSchema.js +272 -22
- package/dist/mcp/catalogTools.js +1 -0
- package/dist/mcp/readArtifact.d.ts +2 -0
- package/dist/mcp/readArtifact.js +25 -1
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/runstore/pipelineDagSnapshot.js +2 -0
- package/dist/runstore/port.d.ts +1 -0
- package/dist/runtime/completionCheckRunner.d.ts +3 -2
- package/dist/runtime/completionCheckRunner.js +3 -2
- package/dist/runtime/envelopeRouting.d.ts +2 -2
- package/dist/runtime/pipelineRunner.d.ts +3 -0
- package/dist/runtime/pipelineRunner.js +23 -3
- package/dist/runtime/runManager.js +3 -2
- package/dist/runtime/stageAttemptBootstrap.d.ts +2 -2
- package/dist/runtime/stageAttemptBootstrap.js +38 -2
- package/dist/runtime/stageRunner.d.ts +2 -2
- package/dist/runtime/taskInput.d.ts +7 -0
- package/dist/runtime/taskInput.js +60 -0
- package/dist/runtime/verifiedStageExecution.d.ts +2 -0
- package/dist/runtime/verifiedStageExecution.js +13 -2
- package/dist/server/http.js +23 -4
- package/dist/server/projectMcpRoutes.d.ts +6 -0
- package/dist/server/projectMcpRoutes.js +38 -0
- package/dist/types/completion.d.ts +6 -0
- package/dist/types/pipeline.d.ts +23 -2
- package/dist/types/preEmitCheck.d.ts +4 -6
- package/dist/types/stage.d.ts +24 -4
- package/dist/types/stageflowManifest.d.ts +2 -0
- package/dist/types/task.d.ts +1 -0
- package/dist/ui/assets/index-GtyiWiZg.js +118 -0
- package/dist/ui/index.html +1 -1
- package/package.json +4 -1
- package/skills/stageflow-author/SKILL.md +20 -2
- package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +9 -1
- package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +6 -2
- package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +9 -1
- package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +16 -1
- package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +12 -2
- package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +16 -1
- package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +11 -2
- package/skills/stageflow-author/assets/examples/linear-review/review.yaml +19 -1
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +9 -1
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +6 -2
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +16 -1
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +9 -1
- package/skills/stageflow-author/references/catalog-mapping.md +80 -17
- package/skills/stageflow-author/references/catalog-write-conventions.md +2 -2
- package/skills/stageflow-author/references/stage-prompt-template.md +63 -4
- package/skills/stageflow-session-capture/SKILL.md +1 -1
- package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +2 -1
- package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +13 -2
- package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +18 -2
- package/skills/stageflow-session-capture/references/catalog-authoring.md +6 -3
- package/dist/ui/assets/index-CXGz4nL7.js +0 -118
package/dist/config/loadStage.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { parseAgentField } from "../agent/agentBackend.js";
|
|
2
|
+
import { STAGEFLOW_MCP_SERVER_NAME } from "../agent/claudeTools.js";
|
|
2
3
|
import { CLONE_ACTIONS } from "../types/forkChoice.js";
|
|
3
4
|
import { STAGE_GATE_KINDS, } from "../types/stage.js";
|
|
4
|
-
import { compilePayloadSchema } from "../envelope/payloadSchema.js";
|
|
5
|
+
import { compilePayloadSchema, UnresolvedSchemaRefError } from "../envelope/payloadSchema.js";
|
|
5
6
|
import { loadFailure, loadSuccess } from "./loadOutcome.js";
|
|
7
|
+
import { parseModelField } from "./modelField.js";
|
|
8
|
+
import { allowLegacyYamlAuthoring, dialectWarningForDocument, legacyAuthoringRejected, presentLegacyKeys, } from "./legacyYaml.js";
|
|
6
9
|
import { parsePreEmitChecks } from "./parsePreEmitChecks.js";
|
|
7
10
|
import { readYamlObject } from "./readYamlObject.js";
|
|
11
|
+
import { applyCompiledBody, classifyYamlDocument, compileTargetContract, dialectFromKeys, mixedDialectIssue, STAGE_FILE_WIRING_KEYS, } from "./yamlDialect.js";
|
|
12
|
+
const afterCompletionByStage = new WeakMap();
|
|
13
|
+
/** After-phase IR from target `verify` (`when` includes after). Stamped onto DAG `completion`. */
|
|
14
|
+
export function afterCompletionForStage(stage) {
|
|
15
|
+
return afterCompletionByStage.get(stage);
|
|
16
|
+
}
|
|
8
17
|
function isGateKind(value) {
|
|
9
18
|
return STAGE_GATE_KINDS.includes(value);
|
|
10
19
|
}
|
|
@@ -91,13 +100,76 @@ function parseGateKinds(raw, label) {
|
|
|
91
100
|
}
|
|
92
101
|
return loadSuccess(kinds);
|
|
93
102
|
}
|
|
94
|
-
function
|
|
95
|
-
if (
|
|
96
|
-
|
|
103
|
+
export function parseStageMcp(raw, label, stageId) {
|
|
104
|
+
if (raw === undefined)
|
|
105
|
+
return loadSuccess(undefined);
|
|
106
|
+
if (!Array.isArray(raw)) {
|
|
107
|
+
return loadFailure([
|
|
108
|
+
{
|
|
109
|
+
code: "stage.invalid_mcp",
|
|
110
|
+
message: `Invalid stage ${label}: mcp must be an array of server names`,
|
|
111
|
+
category: "stage",
|
|
112
|
+
stageId,
|
|
113
|
+
},
|
|
114
|
+
]);
|
|
115
|
+
}
|
|
116
|
+
const names = [];
|
|
117
|
+
const seen = new Set();
|
|
118
|
+
for (const item of raw) {
|
|
119
|
+
if (typeof item !== "string") {
|
|
120
|
+
return loadFailure([
|
|
121
|
+
{
|
|
122
|
+
code: "stage.invalid_mcp",
|
|
123
|
+
message: `Invalid stage ${label}: mcp entries must be strings`,
|
|
124
|
+
category: "stage",
|
|
125
|
+
stageId,
|
|
126
|
+
},
|
|
127
|
+
]);
|
|
128
|
+
}
|
|
129
|
+
const name = item.trim();
|
|
130
|
+
if (name === "") {
|
|
131
|
+
return loadFailure([
|
|
132
|
+
{
|
|
133
|
+
code: "stage.invalid_mcp",
|
|
134
|
+
message: `Invalid stage ${label}: mcp entries must be non-empty strings`,
|
|
135
|
+
category: "stage",
|
|
136
|
+
stageId,
|
|
137
|
+
},
|
|
138
|
+
]);
|
|
139
|
+
}
|
|
140
|
+
if (name === STAGEFLOW_MCP_SERVER_NAME) {
|
|
141
|
+
return loadFailure([
|
|
142
|
+
{
|
|
143
|
+
code: "stage.invalid_mcp",
|
|
144
|
+
message: `Invalid stage ${label}: mcp must not include reserved name "${STAGEFLOW_MCP_SERVER_NAME}"`,
|
|
145
|
+
category: "stage",
|
|
146
|
+
stageId,
|
|
147
|
+
},
|
|
148
|
+
]);
|
|
149
|
+
}
|
|
150
|
+
if (seen.has(name)) {
|
|
151
|
+
return loadFailure([
|
|
152
|
+
{
|
|
153
|
+
code: "stage.invalid_mcp",
|
|
154
|
+
message: `Invalid stage ${label}: mcp contains duplicate name "${name}"`,
|
|
155
|
+
category: "stage",
|
|
156
|
+
stageId,
|
|
157
|
+
},
|
|
158
|
+
]);
|
|
159
|
+
}
|
|
160
|
+
seen.add(name);
|
|
161
|
+
names.push(name);
|
|
162
|
+
}
|
|
163
|
+
return loadSuccess(names);
|
|
164
|
+
}
|
|
165
|
+
function parseStageFields(raw, label, entryId, deferSchemaRefs) {
|
|
166
|
+
// Reads IR field names. Target YAML must already be compiled via
|
|
167
|
+
// applyCompiledBody; legacy YAML uses these keys as authoring.
|
|
168
|
+
if (typeof raw.system_prompt !== "string") {
|
|
97
169
|
return loadFailure([
|
|
98
170
|
{
|
|
99
171
|
code: "stage.invalid_shape",
|
|
100
|
-
message: `Invalid stage ${label}: system_prompt
|
|
172
|
+
message: `Invalid stage ${label}: system_prompt is a required string`,
|
|
101
173
|
category: "stage",
|
|
102
174
|
stageId: entryId,
|
|
103
175
|
},
|
|
@@ -106,8 +178,21 @@ function parseStageFields(raw, label, entryId) {
|
|
|
106
178
|
const stage = {
|
|
107
179
|
id: entryId,
|
|
108
180
|
system_prompt: raw.system_prompt,
|
|
109
|
-
model: raw.model,
|
|
110
181
|
};
|
|
182
|
+
const modelField = parseModelField(raw.model);
|
|
183
|
+
if (!modelField.ok) {
|
|
184
|
+
return loadFailure([
|
|
185
|
+
{
|
|
186
|
+
code: "stage.invalid_model",
|
|
187
|
+
message: `Invalid stage ${label}: ${modelField.message}`,
|
|
188
|
+
category: "stage",
|
|
189
|
+
stageId: entryId,
|
|
190
|
+
},
|
|
191
|
+
]);
|
|
192
|
+
}
|
|
193
|
+
if (modelField.value !== undefined) {
|
|
194
|
+
stage.model = modelField.value;
|
|
195
|
+
}
|
|
111
196
|
if (raw.payload_schema !== undefined) {
|
|
112
197
|
if (raw.payload_schema === null ||
|
|
113
198
|
typeof raw.payload_schema !== "object" ||
|
|
@@ -125,15 +210,29 @@ function parseStageFields(raw, label, entryId) {
|
|
|
125
210
|
compilePayloadSchema(raw.payload_schema);
|
|
126
211
|
}
|
|
127
212
|
catch (err) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
213
|
+
if (err instanceof UnresolvedSchemaRefError) {
|
|
214
|
+
if (!deferSchemaRefs) {
|
|
215
|
+
return loadFailure([
|
|
216
|
+
{
|
|
217
|
+
code: "stage.unresolved_schema_ref",
|
|
218
|
+
message: `Invalid stage ${label}: ${err.message}`,
|
|
219
|
+
category: "stage",
|
|
220
|
+
stageId: entryId,
|
|
221
|
+
},
|
|
222
|
+
]);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
227
|
+
return loadFailure([
|
|
228
|
+
{
|
|
229
|
+
code: "stage.invalid_payload_schema",
|
|
230
|
+
message: `Invalid stage ${label}: invalid payload_schema: ${message}`,
|
|
231
|
+
category: "stage",
|
|
232
|
+
stageId: entryId,
|
|
233
|
+
},
|
|
234
|
+
]);
|
|
235
|
+
}
|
|
137
236
|
}
|
|
138
237
|
stage.payload_schema = raw.payload_schema;
|
|
139
238
|
}
|
|
@@ -154,15 +253,29 @@ function parseStageFields(raw, label, entryId) {
|
|
|
154
253
|
compilePayloadSchema(raw.clone_input_schema);
|
|
155
254
|
}
|
|
156
255
|
catch (err) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
256
|
+
if (err instanceof UnresolvedSchemaRefError) {
|
|
257
|
+
if (!deferSchemaRefs) {
|
|
258
|
+
return loadFailure([
|
|
259
|
+
{
|
|
260
|
+
code: "stage.unresolved_schema_ref",
|
|
261
|
+
message: `Invalid stage ${label}: ${err.message}`,
|
|
262
|
+
category: "stage",
|
|
263
|
+
stageId: entryId,
|
|
264
|
+
},
|
|
265
|
+
]);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
270
|
+
return loadFailure([
|
|
271
|
+
{
|
|
272
|
+
code: "stage.invalid_clone_input_schema",
|
|
273
|
+
message: `Invalid stage ${label}: invalid clone_input_schema: ${message}`,
|
|
274
|
+
category: "stage",
|
|
275
|
+
stageId: entryId,
|
|
276
|
+
},
|
|
277
|
+
]);
|
|
278
|
+
}
|
|
166
279
|
}
|
|
167
280
|
stage.clone_input_schema = raw.clone_input_schema;
|
|
168
281
|
}
|
|
@@ -223,6 +336,12 @@ function parseStageFields(raw, label, entryId) {
|
|
|
223
336
|
}
|
|
224
337
|
stage.skill = raw.skill.trim();
|
|
225
338
|
}
|
|
339
|
+
const mcpOutcome = parseStageMcp(raw.mcp, label, entryId);
|
|
340
|
+
if (!mcpOutcome.ok)
|
|
341
|
+
return mcpOutcome;
|
|
342
|
+
if (mcpOutcome.value !== undefined) {
|
|
343
|
+
stage.mcp = mcpOutcome.value;
|
|
344
|
+
}
|
|
226
345
|
const agentField = parseAgentField(raw.agent);
|
|
227
346
|
if (!agentField.ok) {
|
|
228
347
|
return loadFailure([
|
|
@@ -241,9 +360,28 @@ function parseStageFields(raw, label, entryId) {
|
|
|
241
360
|
}
|
|
242
361
|
export function loadStageFromObjectOutcome(raw, ctx) {
|
|
243
362
|
const label = `${ctx.entryId} (${ctx.declaringPath})`;
|
|
244
|
-
|
|
363
|
+
const deferSchemaRefs = ctx.deferSchemaRefs === true;
|
|
364
|
+
const dialect = dialectFromKeys(Object.keys(raw));
|
|
365
|
+
if (dialect === "invalid") {
|
|
366
|
+
return loadFailure([mixedDialectIssue()]);
|
|
367
|
+
}
|
|
368
|
+
const rejected = legacyAuthoringRejected(dialect, label, presentLegacyKeys(Object.keys(raw)));
|
|
369
|
+
if (rejected)
|
|
370
|
+
return loadFailure([rejected]);
|
|
371
|
+
if (dialect === "target") {
|
|
372
|
+
const compiled = compileTargetContract(raw, {
|
|
373
|
+
stageId: ctx.entryId,
|
|
374
|
+
label,
|
|
375
|
+
category: "stage",
|
|
376
|
+
deferSchemaRefs,
|
|
377
|
+
});
|
|
378
|
+
if (!compiled.ok)
|
|
379
|
+
return compiled;
|
|
380
|
+
return parseStageFields(applyCompiledBody(raw, compiled.value), label, ctx.entryId, deferSchemaRefs);
|
|
381
|
+
}
|
|
382
|
+
return parseStageFields(raw, label, ctx.entryId, deferSchemaRefs);
|
|
245
383
|
}
|
|
246
|
-
export async function loadStageOutcome(filePath) {
|
|
384
|
+
export async function loadStageOutcome(filePath, options = {}) {
|
|
247
385
|
let raw;
|
|
248
386
|
try {
|
|
249
387
|
raw = await readYamlObject(filePath);
|
|
@@ -262,12 +400,47 @@ export async function loadStageOutcome(filePath) {
|
|
|
262
400
|
return loadFailure([
|
|
263
401
|
{
|
|
264
402
|
code: "stage.invalid_shape",
|
|
265
|
-
message: `Invalid stage file ${filePath}: id
|
|
403
|
+
message: `Invalid stage file ${filePath}: id and system_prompt are required strings`,
|
|
266
404
|
category: "stage",
|
|
267
405
|
},
|
|
268
406
|
]);
|
|
269
407
|
}
|
|
270
|
-
const
|
|
408
|
+
const dialect = classifyYamlDocument(raw);
|
|
409
|
+
if (dialect === "invalid") {
|
|
410
|
+
return loadFailure([mixedDialectIssue()]);
|
|
411
|
+
}
|
|
412
|
+
const rejected = legacyAuthoringRejected(dialect, filePath, presentLegacyKeys(Object.keys(raw)));
|
|
413
|
+
if (rejected)
|
|
414
|
+
return loadFailure([rejected]);
|
|
415
|
+
if (dialect === "target") {
|
|
416
|
+
const wiring = STAGE_FILE_WIRING_KEYS.find((key) => raw[key] !== undefined);
|
|
417
|
+
if (wiring) {
|
|
418
|
+
return loadFailure([
|
|
419
|
+
{
|
|
420
|
+
code: "stage.invalid_shape",
|
|
421
|
+
message: `Invalid stage file ${filePath}: new-dialect stage files must not declare wiring key "${wiring}"`,
|
|
422
|
+
category: "stage",
|
|
423
|
+
stageId: raw.id,
|
|
424
|
+
},
|
|
425
|
+
]);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
let parseRaw = raw;
|
|
429
|
+
let afterCompletion;
|
|
430
|
+
const deferSchemaRefs = options.deferSchemaRefs === true;
|
|
431
|
+
if (dialect === "target") {
|
|
432
|
+
const compiled = compileTargetContract(raw, {
|
|
433
|
+
stageId: raw.id,
|
|
434
|
+
label: `file ${filePath}`,
|
|
435
|
+
category: "stage",
|
|
436
|
+
deferSchemaRefs,
|
|
437
|
+
});
|
|
438
|
+
if (!compiled.ok)
|
|
439
|
+
return compiled;
|
|
440
|
+
parseRaw = applyCompiledBody(raw, compiled.value);
|
|
441
|
+
afterCompletion = compiled.value.completion;
|
|
442
|
+
}
|
|
443
|
+
const outcome = parseStageFields(parseRaw, `file ${filePath}`, raw.id, deferSchemaRefs);
|
|
271
444
|
if (!outcome.ok)
|
|
272
445
|
return outcome;
|
|
273
446
|
if (outcome.value.id !== raw.id) {
|
|
@@ -280,7 +453,12 @@ export async function loadStageOutcome(filePath) {
|
|
|
280
453
|
},
|
|
281
454
|
]);
|
|
282
455
|
}
|
|
283
|
-
|
|
456
|
+
if (afterCompletion)
|
|
457
|
+
afterCompletionByStage.set(outcome.value, afterCompletion);
|
|
458
|
+
const warning = allowLegacyYamlAuthoring()
|
|
459
|
+
? dialectWarningForDocument(raw, filePath)
|
|
460
|
+
: undefined;
|
|
461
|
+
return loadSuccess(outcome.value, warning ? [warning] : undefined);
|
|
284
462
|
}
|
|
285
463
|
export async function loadStage(filePath) {
|
|
286
464
|
const outcome = await loadStageOutcome(filePath);
|
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { parse as parseYaml } from "yaml";
|
|
4
4
|
import { parseAgentField } from "../agent/agentBackend.js";
|
|
5
5
|
import { loadFailure, loadSuccess } from "./loadOutcome.js";
|
|
6
|
+
import { parseModelField } from "./modelField.js";
|
|
6
7
|
const DEFAULT_PIPELINE_PATTERN = "*.pipeline.yaml";
|
|
7
8
|
const DEFAULT_TASK_PATTERN = "*.task.yaml";
|
|
8
9
|
export function manifestPathForProject(projectRoot) {
|
|
@@ -117,6 +118,14 @@ export function parseStageflowManifestOutcome(yamlText, manifestPath, projectRoo
|
|
|
117
118
|
else {
|
|
118
119
|
agent = agentField.value;
|
|
119
120
|
}
|
|
121
|
+
let model;
|
|
122
|
+
const modelField = parseModelField(record.model);
|
|
123
|
+
if (!modelField.ok) {
|
|
124
|
+
issues.push(catalogIssue("catalog.manifest_invalid", modelField.message));
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
model = modelField.value;
|
|
128
|
+
}
|
|
120
129
|
if (issues.length > 0) {
|
|
121
130
|
return loadFailure(issues);
|
|
122
131
|
}
|
|
@@ -140,6 +149,7 @@ export function parseStageflowManifestOutcome(yamlText, manifestPath, projectRoo
|
|
|
140
149
|
version: 1,
|
|
141
150
|
catalog,
|
|
142
151
|
...(agent !== undefined ? { agent } : {}),
|
|
152
|
+
...(model !== undefined ? { model } : {}),
|
|
143
153
|
};
|
|
144
154
|
return loadSuccess({
|
|
145
155
|
path: manifestPath,
|
package/dist/config/loadTask.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { parse as parseYaml } from "yaml";
|
|
3
3
|
import { loadFailure, loadSuccess } from "./loadOutcome.js";
|
|
4
|
+
function isPlainObject(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
4
7
|
export function parseTaskFile(raw, source = "task") {
|
|
5
8
|
const record = raw;
|
|
6
9
|
if (typeof record?.id !== "string" || typeof record?.goal !== "string") {
|
|
@@ -13,12 +16,23 @@ export function parseTaskFile(raw, source = "task") {
|
|
|
13
16
|
},
|
|
14
17
|
]);
|
|
15
18
|
}
|
|
19
|
+
if (record.input !== undefined && !isPlainObject(record.input)) {
|
|
20
|
+
return loadFailure([
|
|
21
|
+
{
|
|
22
|
+
code: "task.invalid_shape",
|
|
23
|
+
message: `Invalid ${source}: input must be an object`,
|
|
24
|
+
category: "task",
|
|
25
|
+
taskId: record.id,
|
|
26
|
+
},
|
|
27
|
+
]);
|
|
28
|
+
}
|
|
16
29
|
return loadSuccess({
|
|
17
30
|
id: record.id,
|
|
18
31
|
goal: record.goal,
|
|
19
32
|
context: typeof record.context === "string" ? record.context : undefined,
|
|
20
33
|
constraints: typeof record.constraints === "string" ? record.constraints : undefined,
|
|
21
34
|
checkout: typeof record.checkout === "string" ? record.checkout : undefined,
|
|
35
|
+
...(record.input !== undefined ? { input: record.input } : {}),
|
|
22
36
|
});
|
|
23
37
|
}
|
|
24
38
|
export function loadTaskFromYamlOutcome(yamlText, source = "task yaml") {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { LoadedStageConfig, StageConfig } from "../types/stage.js";
|
|
2
|
+
import { type LoadOutcome } from "./loadOutcome.js";
|
|
3
|
+
export declare function materializeStageModels(stages: StageConfig[], options: {
|
|
4
|
+
pipelineModel?: string;
|
|
5
|
+
pipelineId: string;
|
|
6
|
+
projectRoot: string;
|
|
7
|
+
}): Promise<LoadOutcome<LoadedStageConfig[]>>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import { loadFailure, loadSuccess } from "./loadOutcome.js";
|
|
3
|
+
import { loadStageflowManifestOutcome, manifestPathForProject, } from "./loadStageflowManifest.js";
|
|
4
|
+
import { globalModelFromManifest, resolveModelOutcome } from "./resolveModel.js";
|
|
5
|
+
export async function materializeStageModels(stages, options) {
|
|
6
|
+
let globalModel;
|
|
7
|
+
const manifestPath = manifestPathForProject(options.projectRoot);
|
|
8
|
+
let manifestExists = true;
|
|
9
|
+
try {
|
|
10
|
+
await access(manifestPath);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
manifestExists = false;
|
|
14
|
+
}
|
|
15
|
+
if (manifestExists) {
|
|
16
|
+
const manifestOutcome = await loadStageflowManifestOutcome(options.projectRoot);
|
|
17
|
+
if (!manifestOutcome.ok) {
|
|
18
|
+
return loadFailure(manifestOutcome.issues);
|
|
19
|
+
}
|
|
20
|
+
globalModel = globalModelFromManifest(manifestOutcome.value);
|
|
21
|
+
}
|
|
22
|
+
const materialized = [];
|
|
23
|
+
for (const stage of stages) {
|
|
24
|
+
const modelOutcome = resolveModelOutcome({
|
|
25
|
+
stage: stage.model,
|
|
26
|
+
pipeline: options.pipelineModel,
|
|
27
|
+
global: globalModel,
|
|
28
|
+
}, { stageId: stage.id, pipelineId: options.pipelineId });
|
|
29
|
+
if (!modelOutcome.ok) {
|
|
30
|
+
return loadFailure(modelOutcome.issues);
|
|
31
|
+
}
|
|
32
|
+
materialized.push({ ...stage, model: modelOutcome.value });
|
|
33
|
+
}
|
|
34
|
+
return loadSuccess(materialized);
|
|
35
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { PayloadSchemaMap } from "../envelope/payloadSchema.js";
|
|
2
|
+
import { type LoadIssue, type LoadOutcome } from "./loadOutcome.js";
|
|
2
3
|
export type RawMergedEntry = {
|
|
3
4
|
raw: unknown;
|
|
4
5
|
declaringPath: string;
|
|
@@ -7,4 +8,7 @@ export declare function mergePipelineStages(rootPath: string): Promise<LoadOutco
|
|
|
7
8
|
entries: RawMergedEntry[];
|
|
8
9
|
pipelineId: string;
|
|
9
10
|
agent?: string;
|
|
11
|
+
model?: string;
|
|
12
|
+
schemas?: PayloadSchemaMap;
|
|
13
|
+
warnings: LoadIssue[];
|
|
10
14
|
}>>;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { parseAgentField } from "../agent/agentBackend.js";
|
|
3
3
|
import { loadFailure, loadSuccess } from "./loadOutcome.js";
|
|
4
|
+
import { allowLegacyYamlAuthoring, dialectWarningForDocument, legacyAuthoringRejected, presentLegacyKeys, } from "./legacyYaml.js";
|
|
5
|
+
import { parseModelField } from "./modelField.js";
|
|
4
6
|
import { readYamlObject } from "./readYamlObject.js";
|
|
7
|
+
import { classifyYamlDocument, collectDocumentKeys, mixedDialectIssue, } from "./yamlDialect.js";
|
|
5
8
|
function isPlainObject(value) {
|
|
6
9
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7
10
|
}
|
|
@@ -12,7 +15,7 @@ function formatIncludeCycle(stack, nextPath) {
|
|
|
12
15
|
const chain = [...stack, nextPath].map((p) => path.basename(p));
|
|
13
16
|
return chain.join(" → ");
|
|
14
17
|
}
|
|
15
|
-
async function visitPipelineFile(filePath, stack, idLocations, entries) {
|
|
18
|
+
async function visitPipelineFile(filePath, stack, idLocations, entries, warnings) {
|
|
16
19
|
const absPath = normalizePath(filePath);
|
|
17
20
|
if (stack.some((p) => normalizePath(p) === absPath)) {
|
|
18
21
|
const cycleChain = formatIncludeCycle(stack, absPath);
|
|
@@ -38,6 +41,28 @@ async function visitPipelineFile(filePath, stack, idLocations, entries) {
|
|
|
38
41
|
},
|
|
39
42
|
]);
|
|
40
43
|
}
|
|
44
|
+
const dialect = classifyYamlDocument(raw);
|
|
45
|
+
if (dialect === "invalid") {
|
|
46
|
+
return loadFailure([mixedDialectIssue()]);
|
|
47
|
+
}
|
|
48
|
+
const rejected = legacyAuthoringRejected(dialect, absPath, presentLegacyKeys(collectDocumentKeys(raw)));
|
|
49
|
+
if (rejected)
|
|
50
|
+
return loadFailure([rejected]);
|
|
51
|
+
if (allowLegacyYamlAuthoring()) {
|
|
52
|
+
const warning = dialectWarningForDocument(raw, absPath);
|
|
53
|
+
if (warning)
|
|
54
|
+
warnings.push(warning);
|
|
55
|
+
}
|
|
56
|
+
const isFragment = stack.length > 0;
|
|
57
|
+
if (isFragment && raw.schemas !== undefined) {
|
|
58
|
+
return loadFailure([
|
|
59
|
+
{
|
|
60
|
+
code: "pipeline.include_invalid",
|
|
61
|
+
message: `Invalid include in ${absPath}: schemas is only allowed on the pipeline file, not on include fragments`,
|
|
62
|
+
category: "pipeline",
|
|
63
|
+
},
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
41
66
|
const nextStack = [...stack, absPath];
|
|
42
67
|
if (raw.include !== undefined) {
|
|
43
68
|
if (!Array.isArray(raw.include)) {
|
|
@@ -61,7 +86,7 @@ async function visitPipelineFile(filePath, stack, idLocations, entries) {
|
|
|
61
86
|
]);
|
|
62
87
|
}
|
|
63
88
|
const includePath = normalizePath(path.resolve(path.dirname(absPath), item.local));
|
|
64
|
-
const includeResult = await visitPipelineFile(includePath, nextStack, idLocations, entries);
|
|
89
|
+
const includeResult = await visitPipelineFile(includePath, nextStack, idLocations, entries, warnings);
|
|
65
90
|
if (!includeResult.ok)
|
|
66
91
|
return includeResult;
|
|
67
92
|
}
|
|
@@ -136,9 +161,26 @@ export async function mergePipelineStages(rootPath) {
|
|
|
136
161
|
]);
|
|
137
162
|
}
|
|
138
163
|
const agent = agentField.value;
|
|
164
|
+
const modelField = parseModelField(raw.model);
|
|
165
|
+
if (!modelField.ok) {
|
|
166
|
+
return loadFailure([
|
|
167
|
+
{
|
|
168
|
+
code: "pipeline.invalid_model",
|
|
169
|
+
message: `Invalid pipeline ${absRoot}: ${modelField.message}`,
|
|
170
|
+
category: "pipeline",
|
|
171
|
+
pipelineId,
|
|
172
|
+
},
|
|
173
|
+
]);
|
|
174
|
+
}
|
|
175
|
+
const model = modelField.value;
|
|
176
|
+
const schemasOutcome = parsePipelineSchemas(raw.schemas, absRoot, pipelineId);
|
|
177
|
+
if (!schemasOutcome.ok)
|
|
178
|
+
return schemasOutcome;
|
|
179
|
+
const schemas = schemasOutcome.value;
|
|
139
180
|
const idLocations = new Map();
|
|
140
181
|
const entries = [];
|
|
141
|
-
const
|
|
182
|
+
const warnings = [];
|
|
183
|
+
const mergeResult = await visitPipelineFile(absRoot, [], idLocations, entries, warnings);
|
|
142
184
|
if (!mergeResult.ok)
|
|
143
185
|
return mergeResult;
|
|
144
186
|
if (entries.length === 0) {
|
|
@@ -150,5 +192,41 @@ export async function mergePipelineStages(rootPath) {
|
|
|
150
192
|
},
|
|
151
193
|
]);
|
|
152
194
|
}
|
|
153
|
-
return loadSuccess({
|
|
195
|
+
return loadSuccess({
|
|
196
|
+
entries,
|
|
197
|
+
pipelineId,
|
|
198
|
+
...(agent !== undefined ? { agent } : {}),
|
|
199
|
+
...(model !== undefined ? { model } : {}),
|
|
200
|
+
...(schemas !== undefined ? { schemas } : {}),
|
|
201
|
+
warnings,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function parsePipelineSchemas(raw, absPath, pipelineId) {
|
|
205
|
+
if (raw === undefined)
|
|
206
|
+
return loadSuccess(undefined);
|
|
207
|
+
if (!isPlainObject(raw)) {
|
|
208
|
+
return loadFailure([
|
|
209
|
+
{
|
|
210
|
+
code: "pipeline.invalid_shape",
|
|
211
|
+
message: `Invalid pipeline ${absPath}: schemas must be an object`,
|
|
212
|
+
category: "pipeline",
|
|
213
|
+
pipelineId,
|
|
214
|
+
},
|
|
215
|
+
]);
|
|
216
|
+
}
|
|
217
|
+
const schemas = {};
|
|
218
|
+
for (const [name, value] of Object.entries(raw)) {
|
|
219
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
220
|
+
return loadFailure([
|
|
221
|
+
{
|
|
222
|
+
code: "pipeline.invalid_shape",
|
|
223
|
+
message: `Invalid pipeline ${absPath}: schemas.${name} must be an object`,
|
|
224
|
+
category: "pipeline",
|
|
225
|
+
pipelineId,
|
|
226
|
+
},
|
|
227
|
+
]);
|
|
228
|
+
}
|
|
229
|
+
schemas[name] = value;
|
|
230
|
+
}
|
|
231
|
+
return loadSuccess(schemas);
|
|
154
232
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type MigrateYamlWrite = {
|
|
2
|
+
file: string;
|
|
3
|
+
absPath: string;
|
|
4
|
+
content: string;
|
|
5
|
+
};
|
|
6
|
+
export type MigrateYamlPlan = {
|
|
7
|
+
writes: MigrateYamlWrite[];
|
|
8
|
+
skipped: string[];
|
|
9
|
+
errors: string[];
|
|
10
|
+
};
|
|
11
|
+
export declare function catalogKind(raw: Record<string, unknown>, filePath: string): "pipeline" | "stage" | "task" | "unknown";
|
|
12
|
+
export declare function fileDirtyVsHead(absPath: string): boolean;
|
|
13
|
+
export declare function planMigrateYaml(targetPath: string, options?: {
|
|
14
|
+
cwd: string;
|
|
15
|
+
projectRoot?: string;
|
|
16
|
+
}): Promise<MigrateYamlPlan>;
|
|
17
|
+
export declare function applyMigrateYamlPlan(plan: MigrateYamlPlan, testOnly?: {
|
|
18
|
+
beforeRename?: () => Promise<void>;
|
|
19
|
+
}): Promise<void>;
|