stageflow 0.13.0 → 0.16.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/dist/agent/activity.d.ts +3 -0
- package/dist/agent/claudeAdapter.js +84 -7
- package/dist/agent/claudeSession.d.ts +3 -0
- package/dist/agent/piAdapter.d.ts +2 -0
- package/dist/agent/piAdapter.js +34 -7
- package/dist/agent/port.d.ts +5 -2
- 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 +58 -26
- 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 +150 -8
- package/dist/config/loadStage.d.ts +8 -1
- package/dist/config/loadStage.js +138 -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 +41 -2
- 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 +14 -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/validateCatalog.d.ts +5 -1
- package/dist/config/validateCatalog.js +36 -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/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 +11 -0
- package/dist/runstore/runProjection.js +9 -0
- package/dist/runstore/sqlite/SqliteRunStore.js +27 -4
- package/dist/runstore/sqlite/schema.d.ts +1 -1
- package/dist/runstore/sqlite/schema.js +2 -0
- package/dist/runstore/stageExecution.d.ts +3 -0
- package/dist/runstore/stageExecution.js +6 -2
- package/dist/runstore/stageSnapshot.js +11 -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/stageRunner.d.ts +2 -2
- package/dist/runtime/stageRunner.js +2 -2
- package/dist/runtime/taskInput.d.ts +7 -0
- package/dist/runtime/taskInput.js +60 -0
- package/dist/runtime/verifiedStageExecution.js +3 -0
- package/dist/types/completion.d.ts +6 -0
- package/dist/types/pipeline.d.ts +21 -2
- package/dist/types/preEmitCheck.d.ts +4 -6
- package/dist/types/stage.d.ts +22 -3
- package/dist/types/stageflowManifest.d.ts +2 -0
- package/dist/types/task.d.ts +1 -0
- package/dist/types/usage.d.ts +16 -0
- package/dist/types/usage.js +24 -0
- package/dist/ui/assets/{index-uVHiae8s.css → index-D2Wt8hwH.css} +1 -1
- package/dist/ui/assets/index-XWJovnp7.js +118 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/skills/stageflow-author/SKILL.md +7 -7
- 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 +30 -28
- package/skills/stageflow-author/references/catalog-write-conventions.md +2 -2
- package/skills/stageflow-author/references/stage-prompt-template.md +5 -5
- 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 +2 -2
- package/dist/ui/assets/index-Yzh42vhf.js +0 -118
package/dist/cli/runOutput.js
CHANGED
|
@@ -2,6 +2,21 @@ import { projectRun } from "../projection/projectRun.js";
|
|
|
2
2
|
function stringify(payload) {
|
|
3
3
|
return JSON.stringify(payload, null, 2);
|
|
4
4
|
}
|
|
5
|
+
function formatUsd(amount) {
|
|
6
|
+
return `$${amount.toFixed(4)}`;
|
|
7
|
+
}
|
|
8
|
+
/** Best-effort total cost for a run; undefined (not thrown) on any read failure or when no stage reported usage. */
|
|
9
|
+
async function tryReadTotalCostUsd(store, runId) {
|
|
10
|
+
if (!store)
|
|
11
|
+
return undefined;
|
|
12
|
+
try {
|
|
13
|
+
const detail = await store.readRun(runId);
|
|
14
|
+
return detail.total_cost_usd;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
5
20
|
function isBusyCode(code) {
|
|
6
21
|
return code === "busy_capacity" || code === "busy_checkout";
|
|
7
22
|
}
|
|
@@ -46,40 +61,53 @@ function formatStartFailureJson(started) {
|
|
|
46
61
|
return formatRunStartFailedJson(started);
|
|
47
62
|
}
|
|
48
63
|
function baseRunCompletionPayload(result) {
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
const payload = result.outcome === "succeeded"
|
|
65
|
+
? {
|
|
51
66
|
ok: true,
|
|
52
67
|
outcome: "succeeded",
|
|
53
68
|
runId: result.runId,
|
|
54
69
|
runDir: result.runDir,
|
|
55
|
-
}
|
|
70
|
+
}
|
|
71
|
+
: result.outcome === "waiting"
|
|
72
|
+
? {
|
|
73
|
+
ok: false,
|
|
74
|
+
outcome: "waiting",
|
|
75
|
+
runId: result.runId,
|
|
76
|
+
runDir: result.runDir,
|
|
77
|
+
}
|
|
78
|
+
: {
|
|
79
|
+
ok: false,
|
|
80
|
+
outcome: "failed",
|
|
81
|
+
runId: result.runId,
|
|
82
|
+
runDir: result.runDir,
|
|
83
|
+
};
|
|
84
|
+
if (result.outcome === "failed" && result.reason !== undefined) {
|
|
85
|
+
payload.reason = result.reason;
|
|
56
86
|
}
|
|
57
|
-
if (result.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
87
|
+
if (Array.isArray(result.findings) && result.findings.length > 0) {
|
|
88
|
+
payload.findings = result.findings.map((finding) => ({
|
|
89
|
+
severity: finding.severity,
|
|
90
|
+
code: finding.code,
|
|
91
|
+
file: finding.path,
|
|
92
|
+
message: finding.message,
|
|
93
|
+
category: finding.category,
|
|
94
|
+
}));
|
|
64
95
|
}
|
|
65
|
-
const payload = {
|
|
66
|
-
ok: false,
|
|
67
|
-
outcome: "failed",
|
|
68
|
-
runId: result.runId,
|
|
69
|
-
runDir: result.runDir,
|
|
70
|
-
};
|
|
71
|
-
if (result.reason !== undefined)
|
|
72
|
-
payload.reason = result.reason;
|
|
73
96
|
return payload;
|
|
74
97
|
}
|
|
75
|
-
function
|
|
76
|
-
|
|
98
|
+
async function formatRunCompletionJsonWithCost(result, store) {
|
|
99
|
+
const totalCostUsd = await tryReadTotalCostUsd(store, result.runId);
|
|
100
|
+
return stringify({
|
|
101
|
+
...baseRunCompletionPayload(result),
|
|
102
|
+
...(totalCostUsd !== undefined ? { total_cost_usd: totalCostUsd } : {}),
|
|
103
|
+
});
|
|
77
104
|
}
|
|
78
105
|
async function formatRunCompletionJsonWithStages(result, store) {
|
|
79
106
|
const detail = await store.readRun(result.runId);
|
|
80
107
|
const projection = projectRun(detail);
|
|
81
108
|
return stringify({
|
|
82
109
|
...baseRunCompletionPayload(result),
|
|
110
|
+
...(detail.total_cost_usd !== undefined ? { total_cost_usd: detail.total_cost_usd } : {}),
|
|
83
111
|
stages: projection.stages,
|
|
84
112
|
});
|
|
85
113
|
}
|
|
@@ -93,18 +121,21 @@ function writeStartFailureHuman(started, io) {
|
|
|
93
121
|
io.error(`conflictingCheckout: ${started.conflictingCheckout}`);
|
|
94
122
|
}
|
|
95
123
|
}
|
|
96
|
-
function writeCompletionHuman(result, io) {
|
|
124
|
+
function writeCompletionHuman(result, io, totalCostUsd) {
|
|
97
125
|
switch (result.outcome) {
|
|
98
126
|
case "waiting":
|
|
99
127
|
io.error(`Pipeline waiting. Run folder: ${result.runDir} (${result.runId})`);
|
|
100
|
-
|
|
128
|
+
break;
|
|
101
129
|
case "failed":
|
|
102
130
|
io.error(`Pipeline failed: ${result.reason}`);
|
|
103
131
|
io.error(`Run folder: ${result.runDir}`);
|
|
104
|
-
|
|
132
|
+
break;
|
|
105
133
|
case "succeeded":
|
|
106
134
|
io.log(`Pipeline succeeded. Run folder: ${result.runDir}`);
|
|
107
|
-
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
if (totalCostUsd !== undefined) {
|
|
138
|
+
io.error(`Cost: ${formatUsd(totalCostUsd)}`);
|
|
108
139
|
}
|
|
109
140
|
}
|
|
110
141
|
function exitCodeForRunOutcome(outcome) {
|
|
@@ -143,11 +174,12 @@ export async function reportCliRun(event, options) {
|
|
|
143
174
|
}
|
|
144
175
|
}
|
|
145
176
|
else {
|
|
146
|
-
options.io.log(
|
|
177
|
+
options.io.log(await formatRunCompletionJsonWithCost(event.result, options.store));
|
|
147
178
|
}
|
|
148
179
|
}
|
|
149
180
|
else {
|
|
150
|
-
|
|
181
|
+
const totalCostUsd = await tryReadTotalCostUsd(options.store, event.result.runId);
|
|
182
|
+
writeCompletionHuman(event.result, options.io, totalCostUsd);
|
|
151
183
|
}
|
|
152
184
|
return exitCodeForRunOutcome(event.result.outcome);
|
|
153
185
|
}
|
|
@@ -6,6 +6,7 @@ export type ValidateCommandIo = {
|
|
|
6
6
|
};
|
|
7
7
|
export declare function runValidateCommand(args: string[], options?: {
|
|
8
8
|
cwd?: string;
|
|
9
|
+
projectRoot?: string;
|
|
9
10
|
io?: Partial<ValidateCommandIo>;
|
|
10
11
|
validateCatalog?: (options: Parameters<typeof defaultValidateCatalog>[0]) => Promise<ValidationResult>;
|
|
11
12
|
}): Promise<number>;
|
|
@@ -61,6 +61,7 @@ function parseValidateArgs(args) {
|
|
|
61
61
|
}
|
|
62
62
|
export async function runValidateCommand(args, options = {}) {
|
|
63
63
|
const cwd = options.cwd ?? process.cwd();
|
|
64
|
+
const projectRoot = options.projectRoot ?? cwd;
|
|
64
65
|
const out = { ...defaultIo, ...options.io };
|
|
65
66
|
const validateCatalogFn = options.validateCatalog ?? defaultValidateCatalog;
|
|
66
67
|
try {
|
|
@@ -73,6 +74,7 @@ export async function runValidateCommand(args, options = {}) {
|
|
|
73
74
|
const result = await validateCatalogFn({
|
|
74
75
|
scope,
|
|
75
76
|
cwd,
|
|
77
|
+
projectRoot,
|
|
76
78
|
pipeline: parsed.pipeline,
|
|
77
79
|
task: parsed.task,
|
|
78
80
|
strict: parsed.strict,
|
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ import { RUNS_USAGE, runRunsCommand } from "./cli/runsCommand.js";
|
|
|
14
14
|
import { resolveOperatorCatalog } from "./cli/operatorCatalog.js";
|
|
15
15
|
import { SKILLS_USAGE, runSkillsCommand } from "./cli/skillsCommand.js";
|
|
16
16
|
import { VALIDATE_USAGE, runValidateCommand } from "./cli/validateCommand.js";
|
|
17
|
+
import { MIGRATE_YAML_USAGE, runMigrateYamlCommand } from "./cli/migrateYamlCommand.js";
|
|
17
18
|
import { createRunStore } from "./runstore/createStore.js";
|
|
18
19
|
import { resolveStageflowContext } from "./project/resolveStageflowContext.js";
|
|
19
20
|
import { exitForOutcome, runStageWorker } from "./runtime/stageWorker.js";
|
|
@@ -26,6 +27,7 @@ const USAGE = `Usage:
|
|
|
26
27
|
sf init
|
|
27
28
|
sf run --task <path> --pipeline <path> [--checkout <path>] [--json] [--include stages] [--skip-gates] [--git-sha <sha>] [--ci-pr-url <url>] [--ci-job-url <url>] [--operator-cwd <path>] [--operator-agent-dir <path>]
|
|
28
29
|
sf validate [--pipeline <path>] [--task <path>] [--strict] [--json]
|
|
30
|
+
sf migrate-yaml [path] [--root <path>] [--write] [--json] [--force]
|
|
29
31
|
sf artifact read --run <runId> --path <relPath> [--out <file>]
|
|
30
32
|
sf envelope get --run <runId> --stage <stageId> [--json] [--from <sf-run.json>] [--detect-stage <id>] [--format envelope|handoff]
|
|
31
33
|
sf export-run --run <runId> [--from <sf-run.json>] [--out <file>]
|
|
@@ -64,6 +66,8 @@ ${RUN_USAGE}
|
|
|
64
66
|
|
|
65
67
|
${VALIDATE_USAGE}
|
|
66
68
|
|
|
69
|
+
${MIGRATE_YAML_USAGE}
|
|
70
|
+
|
|
67
71
|
${ARTIFACT_USAGE}
|
|
68
72
|
|
|
69
73
|
${ENVELOPE_USAGE}
|
|
@@ -93,6 +97,7 @@ function parseArgs(argv) {
|
|
|
93
97
|
const command = args[0];
|
|
94
98
|
if (command === "providers" ||
|
|
95
99
|
command === "validate" ||
|
|
100
|
+
command === "migrate-yaml" ||
|
|
96
101
|
command === "run" ||
|
|
97
102
|
command === "init" ||
|
|
98
103
|
command === "artifact" ||
|
|
@@ -275,7 +280,16 @@ async function main(argv) {
|
|
|
275
280
|
return runInitCommand(argv.slice(3), { cwd: ctx.invocationCwd });
|
|
276
281
|
}
|
|
277
282
|
if (parsed.command === "validate") {
|
|
278
|
-
return runValidateCommand(argv.slice(3), {
|
|
283
|
+
return runValidateCommand(argv.slice(3), {
|
|
284
|
+
cwd: ctx.invocationCwd,
|
|
285
|
+
projectRoot: ctx.projectRoot,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
if (parsed.command === "migrate-yaml") {
|
|
289
|
+
return runMigrateYamlCommand(argv.slice(3), {
|
|
290
|
+
cwd: ctx.invocationCwd,
|
|
291
|
+
projectRoot: ctx.projectRoot,
|
|
292
|
+
});
|
|
279
293
|
}
|
|
280
294
|
if (parsed.command === "run") {
|
|
281
295
|
return runRunCommand(argv.slice(3), {
|
|
@@ -106,7 +106,7 @@ async function listModelsFromManifest(projectRoot, manifest) {
|
|
|
106
106
|
try {
|
|
107
107
|
const loaded = await loadPipeline(filePath, { cwd: projectRoot });
|
|
108
108
|
for (const stage of loaded.stages) {
|
|
109
|
-
if (stage.model
|
|
109
|
+
if (stage.model?.trim()) {
|
|
110
110
|
models.add(stage.model);
|
|
111
111
|
}
|
|
112
112
|
}
|
|
@@ -3,7 +3,7 @@ import type { StageGateKind } from "../types/stage.js";
|
|
|
3
3
|
import type { PipelineListing } from "./listConfig.js";
|
|
4
4
|
export type CreatePipelineStageInline = {
|
|
5
5
|
system_prompt: string;
|
|
6
|
-
model
|
|
6
|
+
model?: string;
|
|
7
7
|
gate_kinds?: StageGateKind[];
|
|
8
8
|
};
|
|
9
9
|
export type CreatePipelineStageRef = {
|
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { parsePipelineNeeds } from "./pipelineNeeds.js";
|
|
4
4
|
import { STAGE_ID_PATTERN } from "./createStage.js";
|
|
5
5
|
import { inferIdFromUsesPath, normalizePipelineStageEntries, toWiringRefs, } from "./normalizePipelineStageEntry.js";
|
|
6
|
+
import { parseModelField } from "./modelField.js";
|
|
6
7
|
import { readYamlObject } from "./readYamlObject.js";
|
|
7
8
|
import { resolvePipelineDagFromRefs } from "./resolvePipelineDag.js";
|
|
8
9
|
import { validatePipeline } from "./validateCatalog.js";
|
|
@@ -36,7 +37,9 @@ function stageRefToRaw(ref) {
|
|
|
36
37
|
raw.uses = ref.uses;
|
|
37
38
|
if (ref.inline) {
|
|
38
39
|
raw.system_prompt = ref.inline.system_prompt;
|
|
39
|
-
|
|
40
|
+
if (ref.inline.model !== undefined && ref.inline.model.length > 0) {
|
|
41
|
+
raw.model = ref.inline.model;
|
|
42
|
+
}
|
|
40
43
|
if (ref.inline.gate_kinds !== undefined) {
|
|
41
44
|
raw.gate_kinds = ref.inline.gate_kinds;
|
|
42
45
|
}
|
|
@@ -61,7 +64,9 @@ function normalizedToCreateRefs(entries) {
|
|
|
61
64
|
const raw = entry.body.raw;
|
|
62
65
|
ref.inline = {
|
|
63
66
|
system_prompt: String(raw.system_prompt ?? ""),
|
|
64
|
-
|
|
67
|
+
...(typeof raw.model === "string" && raw.model.length > 0
|
|
68
|
+
? { model: raw.model }
|
|
69
|
+
: {}),
|
|
65
70
|
...(Array.isArray(raw.gate_kinds)
|
|
66
71
|
? { gate_kinds: raw.gate_kinds }
|
|
67
72
|
: {}),
|
|
@@ -97,17 +102,22 @@ function parseInlineBody(entry, index) {
|
|
|
97
102
|
error: `stages[${index}].system_prompt is required for inline stage`,
|
|
98
103
|
};
|
|
99
104
|
}
|
|
100
|
-
if (typeof entry.model !== "string" || entry.model.length === 0) {
|
|
101
|
-
return {
|
|
102
|
-
ok: false,
|
|
103
|
-
status: 400,
|
|
104
|
-
error: `stages[${index}].model is required for inline stage`,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
105
|
const inline = {
|
|
108
106
|
system_prompt: entry.system_prompt,
|
|
109
|
-
model: entry.model,
|
|
110
107
|
};
|
|
108
|
+
if (entry.model !== undefined) {
|
|
109
|
+
const modelField = parseModelField(entry.model);
|
|
110
|
+
if (!modelField.ok) {
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
status: 400,
|
|
114
|
+
error: `stages[${index}].${modelField.message}`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (modelField.value !== undefined) {
|
|
118
|
+
inline.model = modelField.value;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
111
121
|
if (entry.gate_kinds !== undefined) {
|
|
112
122
|
if (!Array.isArray(entry.gate_kinds)) {
|
|
113
123
|
return {
|
|
@@ -288,7 +298,9 @@ export function pipelineConfigToYaml(pipeline, options) {
|
|
|
288
298
|
else {
|
|
289
299
|
lines.push(` system_prompt: ${formatInlineYamlScalar(stage.inline.system_prompt)}`);
|
|
290
300
|
}
|
|
291
|
-
|
|
301
|
+
if (stage.inline.model !== undefined && stage.inline.model.length > 0) {
|
|
302
|
+
lines.push(` model: ${formatInlineYamlScalar(stage.inline.model)}`);
|
|
303
|
+
}
|
|
292
304
|
}
|
|
293
305
|
else if (stage.uses) {
|
|
294
306
|
lines.push(` uses: ${stage.uses}`);
|
|
@@ -2,6 +2,7 @@ import { access, mkdir, readdir, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { STAGE_GATE_KINDS, } from "../types/stage.js";
|
|
4
4
|
import { loadStage } from "./loadStage.js";
|
|
5
|
+
import { parseModelField } from "./modelField.js";
|
|
5
6
|
import { readYamlObject } from "./readYamlObject.js";
|
|
6
7
|
export const STAGE_ID_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
7
8
|
function isGateKind(value) {
|
|
@@ -63,9 +64,11 @@ export function parseCreateStageBody(body) {
|
|
|
63
64
|
if (typeof body.system_prompt !== "string" || body.system_prompt.length === 0) {
|
|
64
65
|
return { ok: false, status: 400, error: "system_prompt is required" };
|
|
65
66
|
}
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
const modelField = parseModelField(body.model);
|
|
68
|
+
if (!modelField.ok) {
|
|
69
|
+
return { ok: false, status: 400, error: modelField.message };
|
|
68
70
|
}
|
|
71
|
+
const model = modelField.value;
|
|
69
72
|
const gateKinds = parseGateKinds(body.gate_kinds);
|
|
70
73
|
if (gateKinds === "invalid") {
|
|
71
74
|
return {
|
|
@@ -79,7 +82,7 @@ export function parseCreateStageBody(body) {
|
|
|
79
82
|
filename,
|
|
80
83
|
id: body.id,
|
|
81
84
|
system_prompt: body.system_prompt,
|
|
82
|
-
model:
|
|
85
|
+
...(model !== undefined ? { model } : {}),
|
|
83
86
|
...(gateKinds !== undefined ? { gate_kinds: gateKinds } : {}),
|
|
84
87
|
};
|
|
85
88
|
}
|
|
@@ -116,7 +119,9 @@ export function stageConfigToYaml(stage) {
|
|
|
116
119
|
else {
|
|
117
120
|
lines.push(`system_prompt: ${formatInlineYamlScalar(stage.system_prompt)}`);
|
|
118
121
|
}
|
|
119
|
-
|
|
122
|
+
if (stage.model !== undefined && stage.model.length > 0) {
|
|
123
|
+
lines.push(`model: ${formatInlineYamlScalar(stage.model)}`);
|
|
124
|
+
}
|
|
120
125
|
lines.push("");
|
|
121
126
|
return lines.join("\n");
|
|
122
127
|
}
|
|
@@ -190,7 +195,7 @@ export async function createStage(projectRoot, input) {
|
|
|
190
195
|
await writeFile(filePath, stageConfigToYaml({
|
|
191
196
|
id: input.id,
|
|
192
197
|
system_prompt: input.system_prompt,
|
|
193
|
-
model: input.model,
|
|
198
|
+
...(input.model !== undefined && input.model.length > 0 ? { model: input.model } : {}),
|
|
194
199
|
...(input.gate_kinds !== undefined ? { gate_kinds: input.gate_kinds } : {}),
|
|
195
200
|
}), "utf8");
|
|
196
201
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dual-read adapter: accept old YAML authoring keys and warn (`catalog.legacy_yaml`).
|
|
3
|
+
*
|
|
4
|
+
* This is not the runtime IR. Stage workers, snapshots, emit, and VSE already
|
|
5
|
+
* use those same strings as TypeScript fields (`StageConfig.payload_schema`,
|
|
6
|
+
* DAG `completion` / `recovery`). Target YAML compiles onto that IR in
|
|
7
|
+
* `yamlDialect.ts`. New catalog fields go on `io` / `verify` / `on_verify_fail`,
|
|
8
|
+
* not here.
|
|
9
|
+
*
|
|
10
|
+
* Off: STAGEFLOW_LEGACY_YAML=0. migrate-yaml still reads legacy via
|
|
11
|
+
* withLegacyYamlAllowedAsync.
|
|
12
|
+
*
|
|
13
|
+
* Delete this file plus migrateYaml.ts, printTargetYaml.ts,
|
|
14
|
+
* migrateYamlCommand.ts, and tests/fixtures/dual-read to stop accepting
|
|
15
|
+
* payload_schema / pre_emit_checks / completion / recovery as authoring keys.
|
|
16
|
+
*/
|
|
17
|
+
import type { LoadIssue } from "./loadOutcome.js";
|
|
18
|
+
import type { YamlDialect } from "./yamlDialect.js";
|
|
19
|
+
export declare const LEGACY_CONTRACT_KEYS: Set<string>;
|
|
20
|
+
export declare const LEGACY_KEY_REPLACEMENTS: Record<string, string>;
|
|
21
|
+
export declare function formatLegacyReplacements(keys: string[]): string;
|
|
22
|
+
export declare function presentLegacyKeys(keys: Iterable<string>): string[];
|
|
23
|
+
export declare function legacyYamlIssue(fileLabel: string, keys: string[]): LoadIssue;
|
|
24
|
+
export declare function dialectWarningForDocument(raw: Record<string, unknown>, fileLabel: string): LoadIssue | undefined;
|
|
25
|
+
export declare function allowLegacyYamlAuthoring(): boolean;
|
|
26
|
+
export declare function withLegacyYamlAllowed<T>(fn: () => T): T;
|
|
27
|
+
export declare function withLegacyYamlAllowedAsync<T>(fn: () => Promise<T>): Promise<T>;
|
|
28
|
+
export declare function legacyAuthoringRejected(dialect: YamlDialect, fileLabel: string, keys: string[]): LoadIssue | undefined;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dual-read adapter: accept old YAML authoring keys and warn (`catalog.legacy_yaml`).
|
|
3
|
+
*
|
|
4
|
+
* This is not the runtime IR. Stage workers, snapshots, emit, and VSE already
|
|
5
|
+
* use those same strings as TypeScript fields (`StageConfig.payload_schema`,
|
|
6
|
+
* DAG `completion` / `recovery`). Target YAML compiles onto that IR in
|
|
7
|
+
* `yamlDialect.ts`. New catalog fields go on `io` / `verify` / `on_verify_fail`,
|
|
8
|
+
* not here.
|
|
9
|
+
*
|
|
10
|
+
* Off: STAGEFLOW_LEGACY_YAML=0. migrate-yaml still reads legacy via
|
|
11
|
+
* withLegacyYamlAllowedAsync.
|
|
12
|
+
*
|
|
13
|
+
* Delete this file plus migrateYaml.ts, printTargetYaml.ts,
|
|
14
|
+
* migrateYamlCommand.ts, and tests/fixtures/dual-read to stop accepting
|
|
15
|
+
* payload_schema / pre_emit_checks / completion / recovery as authoring keys.
|
|
16
|
+
*/
|
|
17
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
18
|
+
export const LEGACY_CONTRACT_KEYS = new Set([
|
|
19
|
+
"payload_schema",
|
|
20
|
+
"clone_input_schema",
|
|
21
|
+
"pre_emit_checks",
|
|
22
|
+
"completion",
|
|
23
|
+
"recovery",
|
|
24
|
+
]);
|
|
25
|
+
export const LEGACY_KEY_REPLACEMENTS = {
|
|
26
|
+
payload_schema: "io.output.schema",
|
|
27
|
+
clone_input_schema: "io.input.schema",
|
|
28
|
+
pre_emit_checks: "verify items with when including emit",
|
|
29
|
+
completion: "verify items with when including after",
|
|
30
|
+
recovery: "on_verify_fail",
|
|
31
|
+
};
|
|
32
|
+
const TARGET_AUTHORING_KEYS = new Set(["io", "verify", "on_verify_fail"]);
|
|
33
|
+
const legacyYamlOverride = new AsyncLocalStorage();
|
|
34
|
+
function collectDocumentKeys(raw) {
|
|
35
|
+
const keys = new Set(Object.keys(raw));
|
|
36
|
+
if (Array.isArray(raw.stages)) {
|
|
37
|
+
for (const entry of raw.stages) {
|
|
38
|
+
if (typeof entry === "object" && entry !== null && !Array.isArray(entry)) {
|
|
39
|
+
for (const key of Object.keys(entry))
|
|
40
|
+
keys.add(key);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return keys;
|
|
45
|
+
}
|
|
46
|
+
export function formatLegacyReplacements(keys) {
|
|
47
|
+
return keys
|
|
48
|
+
.map((key) => {
|
|
49
|
+
const replacement = LEGACY_KEY_REPLACEMENTS[key];
|
|
50
|
+
return replacement ? `${key} → ${replacement}` : key;
|
|
51
|
+
})
|
|
52
|
+
.join(", ");
|
|
53
|
+
}
|
|
54
|
+
export function presentLegacyKeys(keys) {
|
|
55
|
+
const set = keys instanceof Set ? keys : new Set(keys);
|
|
56
|
+
return [...LEGACY_CONTRACT_KEYS].filter((key) => set.has(key));
|
|
57
|
+
}
|
|
58
|
+
export function legacyYamlIssue(fileLabel, keys) {
|
|
59
|
+
return {
|
|
60
|
+
code: "catalog.legacy_yaml",
|
|
61
|
+
message: `${fileLabel} uses legacy YAML keys (${formatLegacyReplacements(keys)})`,
|
|
62
|
+
category: "catalog",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function dialectWarningForDocument(raw, fileLabel) {
|
|
66
|
+
const keys = collectDocumentKeys(raw);
|
|
67
|
+
const legacyKeys = presentLegacyKeys(keys);
|
|
68
|
+
if (legacyKeys.length === 0)
|
|
69
|
+
return undefined;
|
|
70
|
+
for (const key of keys) {
|
|
71
|
+
if (TARGET_AUTHORING_KEYS.has(key))
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
return legacyYamlIssue(fileLabel, legacyKeys);
|
|
75
|
+
}
|
|
76
|
+
export function allowLegacyYamlAuthoring() {
|
|
77
|
+
if (legacyYamlOverride.getStore() === true)
|
|
78
|
+
return true;
|
|
79
|
+
const raw = process.env.STAGEFLOW_LEGACY_YAML;
|
|
80
|
+
if (raw === "0" || raw === "false")
|
|
81
|
+
return false;
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
export function withLegacyYamlAllowed(fn) {
|
|
85
|
+
return legacyYamlOverride.run(true, fn);
|
|
86
|
+
}
|
|
87
|
+
export function withLegacyYamlAllowedAsync(fn) {
|
|
88
|
+
return legacyYamlOverride.run(true, fn);
|
|
89
|
+
}
|
|
90
|
+
export function legacyAuthoringRejected(dialect, fileLabel, keys) {
|
|
91
|
+
if (dialect !== "legacy" || allowLegacyYamlAuthoring())
|
|
92
|
+
return undefined;
|
|
93
|
+
return {
|
|
94
|
+
code: "catalog.legacy_yaml",
|
|
95
|
+
message: `${fileLabel}: dual-read is disabled (STAGEFLOW_LEGACY_YAML=0). Run sf migrate-yaml or author io / verify / on_verify_fail (${formatLegacyReplacements(keys)}).`,
|
|
96
|
+
category: "catalog",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -23,9 +23,10 @@ export type LoadIssue = {
|
|
|
23
23
|
export type LoadOutcome<T> = {
|
|
24
24
|
ok: true;
|
|
25
25
|
value: T;
|
|
26
|
+
issues?: LoadIssue[];
|
|
26
27
|
} | {
|
|
27
28
|
ok: false;
|
|
28
29
|
issues: LoadIssue[];
|
|
29
30
|
};
|
|
30
31
|
export declare function loadFailure<T>(issues: LoadIssue[]): LoadOutcome<T>;
|
|
31
|
-
export declare function loadSuccess<T>(value: T): LoadOutcome<T>;
|
|
32
|
+
export declare function loadSuccess<T>(value: T, issues?: LoadIssue[]): LoadOutcome<T>;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export function loadFailure(issues) {
|
|
2
2
|
return { ok: false, issues };
|
|
3
3
|
}
|
|
4
|
-
export function loadSuccess(value) {
|
|
4
|
+
export function loadSuccess(value, issues) {
|
|
5
|
+
if (issues && issues.length > 0) {
|
|
6
|
+
return { ok: true, value, issues };
|
|
7
|
+
}
|
|
5
8
|
return { ok: true, value };
|
|
6
9
|
}
|
|
@@ -5,7 +5,9 @@ export type { LoadIssue, LoadOutcome } from "./loadOutcome.js";
|
|
|
5
5
|
export declare function resolvePipelinePath(pipelinePath: string, cwd: string): Promise<string>;
|
|
6
6
|
export declare function loadPipelineOutcome(nameOrPath: string, options?: {
|
|
7
7
|
cwd?: string;
|
|
8
|
+
projectRoot?: string;
|
|
8
9
|
}): Promise<LoadOutcome<LoadedPipeline>>;
|
|
9
10
|
export declare function loadPipeline(nameOrPath: string, options?: {
|
|
10
11
|
cwd?: string;
|
|
12
|
+
projectRoot?: string;
|
|
11
13
|
}): Promise<LoadedPipeline>;
|