stageflow 0.13.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/dist/agent/piAdapter.js +5 -2
- package/dist/agent/port.d.ts +2 -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 +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 +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 +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/stageRunner.d.ts +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/ui/assets/{index-Yzh42vhf.js → index-GtyiWiZg.js} +1 -1
- package/dist/ui/index.html +1 -1
- 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/agent/piAdapter.js
CHANGED
|
@@ -390,10 +390,10 @@ function buildUserPrompt(input, emitToolName, emitHintOverride, artifactToolName
|
|
|
390
390
|
if (successor.cloneInputSchema === undefined) {
|
|
391
391
|
return header;
|
|
392
392
|
}
|
|
393
|
-
return `${header}\n Assignment schema (
|
|
393
|
+
return `${header}\n Assignment schema (io.input.schema):\n${JSON.stringify(successor.cloneInputSchema, null, 2)}`;
|
|
394
394
|
});
|
|
395
395
|
emitHint += `\nClonable successors — emit clone_forks covering each of these ids exactly once:\n${successorLines.join("\n")}`;
|
|
396
|
-
emitHint += `\nEach once or fanout envelope is a full StageEnvelope — it requires status ("success" or "failure"), summary (non-empty string), and artifacts (array of strings).
|
|
396
|
+
emitHint += `\nEach once or fanout envelope is a full StageEnvelope — it requires status ("success" or "failure"), summary (non-empty string), and artifacts (array of strings). io.input.schema fields belong in envelope.payload, not at the top level of the clone_forks item.`;
|
|
397
397
|
emitHint += `\nAllowed clone actions: ${allowedActions.join(", ")}`;
|
|
398
398
|
}
|
|
399
399
|
if (input.feedbackLoopEmitContext !== undefined) {
|
|
@@ -431,6 +431,9 @@ function buildUserPrompt(input, emitToolName, emitHintOverride, artifactToolName
|
|
|
431
431
|
`Goal: ${input.task.goal}`,
|
|
432
432
|
input.task.context ? `Context: ${input.task.context}` : "",
|
|
433
433
|
input.task.constraints ? `Constraints: ${input.task.constraints}` : "",
|
|
434
|
+
input.task.input !== undefined
|
|
435
|
+
? `Input: ${JSON.stringify(input.task.input, null, 2)}`
|
|
436
|
+
: "",
|
|
434
437
|
formatPriorEnvelope(input.priorEnvelope, input.priorEnvelopes, input.priorEnvelopesByStage),
|
|
435
438
|
input.feedbackLoopContext !== undefined
|
|
436
439
|
? formatFeedbackLoopContext(input.feedbackLoopContext, input.sessionMode)
|
package/dist/agent/port.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { StageEnvelope, TerminalEnvelope } from "../types/envelope.js";
|
|
2
2
|
import type { CompletionContract } from "../types/completion.js";
|
|
3
3
|
import type { CloneEmitContext, ForkEmitContext } from "../types/forkChoice.js";
|
|
4
|
-
import type {
|
|
4
|
+
import type { LoadedStageConfig } from "../types/stage.js";
|
|
5
5
|
import type { FeedbackLoopConfig } from "../types/pipeline.js";
|
|
6
6
|
import type { TaskFile } from "../types/task.js";
|
|
7
7
|
import type { StageActivityEvent } from "./activity.js";
|
|
@@ -45,7 +45,7 @@ export type FeedbackLoopContext = {
|
|
|
45
45
|
};
|
|
46
46
|
export type StageRunInput = {
|
|
47
47
|
roots: StageRoots;
|
|
48
|
-
stage:
|
|
48
|
+
stage: LoadedStageConfig;
|
|
49
49
|
/** Runtime instance id (`work~2`). Defaults to `stage.id` when omitted. */
|
|
50
50
|
stageId?: string;
|
|
51
51
|
task: TaskFile;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const STAGEFLOW_YAML = "version: 1\ncatalog:\n pipelines:\n - pipelines\n tasks:\n - tasks\n patterns:\n pipeline: \"*.pipeline.yaml\"\n task: \"*.task.yaml\"\n";
|
|
2
|
-
export declare const HELLO_PIPELINE_YAML = "id: hello\nstages:\n - id: hello\n system_prompt: Say hello and emit a success envelope.\n
|
|
2
|
+
export declare const HELLO_PIPELINE_YAML = "id: hello\nmodel: anthropic/claude-sonnet-4-5\nstages:\n - id: hello\n system_prompt: Say hello and emit a success envelope.\n";
|
|
3
3
|
export declare const HELLO_TASK_YAML = "id: hello\ngoal: Run the hello pipeline scaffold.\n";
|
|
@@ -9,10 +9,10 @@ catalog:
|
|
|
9
9
|
task: "*.task.yaml"
|
|
10
10
|
`;
|
|
11
11
|
export const HELLO_PIPELINE_YAML = `id: hello
|
|
12
|
+
model: anthropic/claude-sonnet-4-5
|
|
12
13
|
stages:
|
|
13
14
|
- id: hello
|
|
14
15
|
system_prompt: Say hello and emit a success envelope.
|
|
15
|
-
model: anthropic/claude-sonnet-4-5
|
|
16
16
|
`;
|
|
17
17
|
export const HELLO_TASK_YAML = `id: hello
|
|
18
18
|
goal: Run the hello pipeline scaffold.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const MIGRATE_YAML_USAGE = "Usage:\n sf migrate-yaml [path] [--root <path>] [--write] [--json] [--force]\n\n Convert legacy catalog YAML to target YAML (io / verify / on_verify_fail).\n Dry-run is the default. --write applies changes. Path may be a pipeline,\n stage, task, or catalog root. Does not rewrite .stageflow snapshots.";
|
|
2
|
+
export type MigrateYamlCommandIo = {
|
|
3
|
+
log: (line: string) => void;
|
|
4
|
+
error: (line: string) => void;
|
|
5
|
+
};
|
|
6
|
+
export type MigrateYamlJson = {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
write: boolean;
|
|
9
|
+
planned: string[];
|
|
10
|
+
written: string[];
|
|
11
|
+
skipped: string[];
|
|
12
|
+
errors: string[];
|
|
13
|
+
};
|
|
14
|
+
export declare function runMigrateYamlCommand(args: string[], options?: {
|
|
15
|
+
cwd?: string;
|
|
16
|
+
projectRoot?: string;
|
|
17
|
+
io?: Partial<MigrateYamlCommandIo>;
|
|
18
|
+
}): Promise<number>;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { applyMigrateYamlPlan, fileDirtyVsHead, planMigrateYaml, } from "../config/migrateYaml.js";
|
|
3
|
+
export const MIGRATE_YAML_USAGE = `Usage:
|
|
4
|
+
sf migrate-yaml [path] [--root <path>] [--write] [--json] [--force]
|
|
5
|
+
|
|
6
|
+
Convert legacy catalog YAML to target YAML (io / verify / on_verify_fail).
|
|
7
|
+
Dry-run is the default. --write applies changes. Path may be a pipeline,
|
|
8
|
+
stage, task, or catalog root. Does not rewrite .stageflow snapshots.`;
|
|
9
|
+
const defaultIo = {
|
|
10
|
+
log: (line) => console.log(line),
|
|
11
|
+
error: (line) => console.error(line),
|
|
12
|
+
};
|
|
13
|
+
function parseMigrateYamlArgs(args) {
|
|
14
|
+
if (args.length === 0) {
|
|
15
|
+
return { help: false, write: false, json: false, force: false };
|
|
16
|
+
}
|
|
17
|
+
if (args[0] === "--help" || args[0] === "-h") {
|
|
18
|
+
return { help: true, write: false, json: false, force: false };
|
|
19
|
+
}
|
|
20
|
+
let target;
|
|
21
|
+
let root;
|
|
22
|
+
let write = false;
|
|
23
|
+
let json = false;
|
|
24
|
+
let force = false;
|
|
25
|
+
let help = false;
|
|
26
|
+
for (let i = 0; i < args.length; i++) {
|
|
27
|
+
const arg = args[i];
|
|
28
|
+
if (arg === "--help" || arg === "-h") {
|
|
29
|
+
help = true;
|
|
30
|
+
}
|
|
31
|
+
else if (arg === "--write") {
|
|
32
|
+
write = true;
|
|
33
|
+
}
|
|
34
|
+
else if (arg === "--json") {
|
|
35
|
+
json = true;
|
|
36
|
+
}
|
|
37
|
+
else if (arg === "--force") {
|
|
38
|
+
force = true;
|
|
39
|
+
}
|
|
40
|
+
else if (arg === "--root") {
|
|
41
|
+
const value = args[++i];
|
|
42
|
+
if (value === undefined || value.length === 0) {
|
|
43
|
+
throw new Error("Missing value for --root");
|
|
44
|
+
}
|
|
45
|
+
root = value;
|
|
46
|
+
}
|
|
47
|
+
else if (arg.startsWith("-")) {
|
|
48
|
+
throw new Error(`Unknown flag: ${arg}`);
|
|
49
|
+
}
|
|
50
|
+
else if (target !== undefined) {
|
|
51
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
target = arg;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (target !== undefined && root !== undefined) {
|
|
58
|
+
throw new Error("Use at most one of a positional path or --root");
|
|
59
|
+
}
|
|
60
|
+
return { help, target: target ?? root, write, json, force };
|
|
61
|
+
}
|
|
62
|
+
function planToJson(plan, options) {
|
|
63
|
+
return {
|
|
64
|
+
ok: options.ok,
|
|
65
|
+
write: options.write,
|
|
66
|
+
planned: plan.writes.map((item) => item.file),
|
|
67
|
+
written: options.written,
|
|
68
|
+
skipped: plan.skipped,
|
|
69
|
+
errors: [...plan.errors, ...(options.extraErrors ?? [])],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function formatHuman(plan, write, extraErrors) {
|
|
73
|
+
const lines = [];
|
|
74
|
+
const errors = [...plan.errors, ...extraErrors];
|
|
75
|
+
if (errors.length > 0) {
|
|
76
|
+
lines.push("Migration failed:");
|
|
77
|
+
for (const error of errors)
|
|
78
|
+
lines.push(` ${error}`);
|
|
79
|
+
return lines.join("\n");
|
|
80
|
+
}
|
|
81
|
+
if (write) {
|
|
82
|
+
if (plan.writes.length === 0) {
|
|
83
|
+
lines.push("Nothing to write.");
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
lines.push("Wrote:");
|
|
87
|
+
for (const item of plan.writes)
|
|
88
|
+
lines.push(` ${item.file}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else if (plan.writes.length === 0) {
|
|
92
|
+
lines.push("Nothing to migrate.");
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
lines.push("Would write:");
|
|
96
|
+
for (const item of plan.writes)
|
|
97
|
+
lines.push(` ${item.file}`);
|
|
98
|
+
lines.push("");
|
|
99
|
+
lines.push("Dry-run; pass --write to apply.");
|
|
100
|
+
}
|
|
101
|
+
if (plan.skipped.length > 0) {
|
|
102
|
+
lines.push("");
|
|
103
|
+
lines.push("Skipped:");
|
|
104
|
+
for (const file of plan.skipped)
|
|
105
|
+
lines.push(` ${file}`);
|
|
106
|
+
}
|
|
107
|
+
return lines.join("\n");
|
|
108
|
+
}
|
|
109
|
+
export async function runMigrateYamlCommand(args, options = {}) {
|
|
110
|
+
const cwd = options.cwd ?? process.cwd();
|
|
111
|
+
const projectRoot = options.projectRoot ?? cwd;
|
|
112
|
+
const out = { ...defaultIo, ...options.io };
|
|
113
|
+
try {
|
|
114
|
+
const parsed = parseMigrateYamlArgs(args);
|
|
115
|
+
if (parsed.help) {
|
|
116
|
+
out.error(MIGRATE_YAML_USAGE);
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
const target = path.resolve(cwd, parsed.target ?? ".");
|
|
120
|
+
const plan = await planMigrateYaml(target, { cwd, projectRoot });
|
|
121
|
+
const extraErrors = [];
|
|
122
|
+
if (plan.errors.length > 0) {
|
|
123
|
+
if (parsed.json) {
|
|
124
|
+
out.log(JSON.stringify(planToJson(plan, { write: parsed.write, written: [], ok: false }), null, 2));
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
out.error(formatHuman(plan, parsed.write, extraErrors));
|
|
128
|
+
out.error(MIGRATE_YAML_USAGE);
|
|
129
|
+
}
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
if (parsed.write && !parsed.force) {
|
|
133
|
+
for (const item of plan.writes) {
|
|
134
|
+
if (fileDirtyVsHead(item.absPath)) {
|
|
135
|
+
extraErrors.push(`Refusing to overwrite uncommitted changes in ${item.file} (pass --force)`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (extraErrors.length > 0) {
|
|
139
|
+
if (parsed.json) {
|
|
140
|
+
out.log(JSON.stringify(planToJson(plan, {
|
|
141
|
+
write: true,
|
|
142
|
+
written: [],
|
|
143
|
+
ok: false,
|
|
144
|
+
extraErrors,
|
|
145
|
+
}), null, 2));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
out.error(formatHuman(plan, parsed.write, extraErrors));
|
|
149
|
+
}
|
|
150
|
+
return 1;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
let written = [];
|
|
154
|
+
if (parsed.write) {
|
|
155
|
+
await applyMigrateYamlPlan(plan);
|
|
156
|
+
written = plan.writes.map((item) => item.file);
|
|
157
|
+
}
|
|
158
|
+
if (parsed.json) {
|
|
159
|
+
out.log(JSON.stringify(planToJson(plan, {
|
|
160
|
+
write: parsed.write,
|
|
161
|
+
written,
|
|
162
|
+
ok: true,
|
|
163
|
+
}), null, 2));
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
out.log(formatHuman(plan, parsed.write, extraErrors));
|
|
167
|
+
}
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
const message = err instanceof Error
|
|
172
|
+
? err.message
|
|
173
|
+
: typeof err === "string"
|
|
174
|
+
? err
|
|
175
|
+
: "Migrate-yaml command failed";
|
|
176
|
+
out.error(message);
|
|
177
|
+
out.error(MIGRATE_YAML_USAGE);
|
|
178
|
+
return 1;
|
|
179
|
+
}
|
|
180
|
+
}
|
package/dist/cli/runOutput.js
CHANGED
|
@@ -46,30 +46,38 @@ function formatStartFailureJson(started) {
|
|
|
46
46
|
return formatRunStartFailedJson(started);
|
|
47
47
|
}
|
|
48
48
|
function baseRunCompletionPayload(result) {
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
const payload = result.outcome === "succeeded"
|
|
50
|
+
? {
|
|
51
51
|
ok: true,
|
|
52
52
|
outcome: "succeeded",
|
|
53
53
|
runId: result.runId,
|
|
54
54
|
runDir: result.runDir,
|
|
55
|
-
}
|
|
55
|
+
}
|
|
56
|
+
: result.outcome === "waiting"
|
|
57
|
+
? {
|
|
58
|
+
ok: false,
|
|
59
|
+
outcome: "waiting",
|
|
60
|
+
runId: result.runId,
|
|
61
|
+
runDir: result.runDir,
|
|
62
|
+
}
|
|
63
|
+
: {
|
|
64
|
+
ok: false,
|
|
65
|
+
outcome: "failed",
|
|
66
|
+
runId: result.runId,
|
|
67
|
+
runDir: result.runDir,
|
|
68
|
+
};
|
|
69
|
+
if (result.outcome === "failed" && result.reason !== undefined) {
|
|
70
|
+
payload.reason = result.reason;
|
|
56
71
|
}
|
|
57
|
-
if (result.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
72
|
+
if (Array.isArray(result.findings) && result.findings.length > 0) {
|
|
73
|
+
payload.findings = result.findings.map((finding) => ({
|
|
74
|
+
severity: finding.severity,
|
|
75
|
+
code: finding.code,
|
|
76
|
+
file: finding.path,
|
|
77
|
+
message: finding.message,
|
|
78
|
+
category: finding.category,
|
|
79
|
+
}));
|
|
64
80
|
}
|
|
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
81
|
return payload;
|
|
74
82
|
}
|
|
75
83
|
function formatRunCompletionJson(result) {
|
|
@@ -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;
|