stageflow 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +190 -13
- package/dist/agent/activity.d.ts +10 -1
- package/dist/agent/activity.js +37 -2
- package/dist/agent/activityObserver.d.ts +5 -1
- package/dist/agent/activityObserver.js +174 -11
- package/dist/agent/fakeAgent.js +4 -0
- package/dist/agent/piAdapter.d.ts +16 -0
- package/dist/agent/piAdapter.js +88 -21
- package/dist/agent/port.d.ts +2 -0
- package/dist/agent/providerAuth.js +12 -7
- package/dist/catalog/displayCatalogPath.d.ts +12 -0
- package/dist/catalog/displayCatalogPath.js +28 -0
- package/dist/cli/ciIdentity.d.ts +14 -0
- package/dist/cli/ciIdentity.js +52 -0
- package/dist/cli/initCommand.d.ts +9 -0
- package/dist/cli/initCommand.js +71 -0
- package/dist/cli/initTemplates.d.ts +3 -0
- package/dist/cli/initTemplates.js +19 -0
- package/dist/cli/operatorCatalog.d.ts +10 -0
- package/dist/cli/operatorCatalog.js +16 -0
- package/dist/cli/runCommand.d.ts +26 -0
- package/dist/cli/runCommand.js +209 -0
- package/dist/cli/runOutput.d.ts +19 -0
- package/dist/cli/runOutput.js +125 -0
- package/dist/cli/validateCommand.d.ts +1 -1
- package/dist/cli/validateCommand.js +20 -3
- package/dist/cli.d.ts +12 -1
- package/dist/cli.js +82 -66
- package/dist/config/browseCatalog.d.ts +45 -0
- package/dist/config/browseCatalog.js +170 -0
- package/dist/config/createPipeline.d.ts +12 -3
- package/dist/config/createPipeline.js +292 -100
- package/dist/config/createStage.d.ts +10 -4
- package/dist/config/createStage.js +44 -14
- package/dist/config/listConfig.d.ts +14 -19
- package/dist/config/listConfig.js +26 -191
- package/dist/config/listModelsFromManifest.d.ts +2 -0
- package/dist/config/listModelsFromManifest.js +4 -0
- package/dist/config/loadOutcome.d.ts +4 -0
- package/dist/config/loadPipeline.d.ts +1 -15
- package/dist/config/loadPipeline.js +84 -142
- package/dist/config/loadStage.d.ts +4 -0
- package/dist/config/loadStage.js +60 -32
- package/dist/config/loadStageflowManifest.d.ts +5 -0
- package/dist/config/loadStageflowManifest.js +157 -0
- package/dist/config/mergePipelineIncludes.d.ts +9 -0
- package/dist/config/mergePipelineIncludes.js +141 -0
- package/dist/config/normalizePipelineStageEntry.d.ts +16 -0
- package/dist/config/normalizePipelineStageEntry.js +186 -0
- package/dist/config/pipelineStageKeys.d.ts +4 -0
- package/dist/config/pipelineStageKeys.js +14 -0
- package/dist/config/resolveCatalogContext.d.ts +13 -0
- package/dist/config/resolveCatalogContext.js +13 -0
- package/dist/config/resolveForkEmitContext.d.ts +3 -0
- package/dist/config/resolveForkEmitContext.js +12 -0
- package/dist/config/resolvePipelineDag.d.ts +5 -1
- package/dist/config/resolvePipelineDag.js +54 -36
- package/dist/config/scanCatalogPaths.d.ts +4 -0
- package/dist/config/scanCatalogPaths.js +83 -0
- package/dist/config/validateCatalog.d.ts +22 -4
- package/dist/config/validateCatalog.js +169 -132
- package/dist/envelope/check.js +7 -0
- package/dist/envelope/forkChoice.d.ts +3 -0
- package/dist/envelope/forkChoice.js +47 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/mcp/projectRun.d.ts +3 -0
- package/dist/mcp/projectRun.js +7 -0
- package/dist/mcp/server.js +1 -1
- package/dist/mcp/tools.js +28 -11
- package/dist/project/findProjectRoot.d.ts +2 -0
- package/dist/project/findProjectRoot.js +51 -0
- package/dist/project/globalHome.d.ts +2 -0
- package/dist/project/globalHome.js +19 -0
- package/dist/project/resolveProjectContext.d.ts +9 -0
- package/dist/project/resolveProjectContext.js +38 -0
- package/dist/project/resolveStageflowContext.d.ts +11 -0
- package/dist/project/resolveStageflowContext.js +66 -0
- package/dist/runstore/normalizeCatalogPath.d.ts +1 -0
- package/dist/runstore/normalizeCatalogPath.js +4 -0
- package/dist/runstore/port.d.ts +15 -0
- package/dist/runstore/runProjection.js +11 -1
- package/dist/runstore/sqlite/SqliteRunStore.js +46 -4
- package/dist/runstore/sqlite/schema.d.ts +1 -1
- package/dist/runstore/sqlite/schema.js +7 -1
- package/dist/runtime/credentialBinding.d.ts +8 -6
- package/dist/runtime/credentialBinding.js +18 -24
- package/dist/runtime/pipelineRunner.d.ts +11 -0
- package/dist/runtime/pipelineRunner.js +25 -1
- package/dist/runtime/pipelineScheduler.d.ts +3 -0
- package/dist/runtime/pipelineScheduler.js +70 -14
- package/dist/runtime/reloadRunCatalog.d.ts +5 -0
- package/dist/runtime/reloadRunCatalog.js +44 -0
- package/dist/runtime/resumeReconstruct.d.ts +1 -0
- package/dist/runtime/resumeReconstruct.js +27 -10
- package/dist/runtime/runManager.d.ts +8 -0
- package/dist/runtime/runManager.js +49 -18
- package/dist/runtime/settingsFile.d.ts +12 -0
- package/dist/runtime/settingsFile.js +87 -6
- package/dist/runtime/stageAttemptBootstrap.js +3 -0
- package/dist/runtime/stageProcessLauncher.d.ts +1 -0
- package/dist/runtime/stageProcessLauncher.js +6 -0
- package/dist/runtime/stageRecovery.js +3 -1
- package/dist/runtime/stageRunner.d.ts +2 -0
- package/dist/runtime/stageRunner.js +9 -2
- package/dist/runtime/stageWorker.js +4 -6
- package/dist/runtime/stageWorkerProtocol.d.ts +1 -0
- package/dist/server/http.js +33 -9
- package/dist/tools/emitStageEnvelope.d.ts +3 -1
- package/dist/tools/emitStageEnvelope.js +11 -1
- package/dist/types/envelope.d.ts +1 -0
- package/dist/types/forkChoice.d.ts +8 -0
- package/dist/types/forkChoice.js +1 -0
- package/dist/types/pipeline.d.ts +51 -2
- package/dist/types/stageflowManifest.d.ts +23 -0
- package/dist/types/stageflowManifest.js +1 -0
- package/dist/ui/assets/index-CFSzDZje.js +118 -0
- package/dist/ui/assets/{index-DCsDopak.css → index-DefBlEvN.css} +1 -1
- package/dist/ui/index.html +3 -2
- package/dist/ui/stageflow-icon.svg +12 -0
- package/package.json +7 -5
- package/dist/agent/cursorExtension.d.ts +0 -12
- package/dist/agent/cursorExtension.js +0 -88
- package/dist/runstore/catalog.d.ts +0 -8
- package/dist/runstore/catalog.js +0 -16
- package/dist/runstore/disk/DiskRunStore.d.ts +0 -30
- package/dist/runstore/disk/DiskRunStore.js +0 -238
- package/dist/runstore/layout.d.ts +0 -22
- package/dist/runstore/layout.js +0 -58
- package/dist/runtime/hitlSeams.d.ts +0 -38
- package/dist/runtime/hitlSeams.js +0 -2
- package/dist/ui/assets/index-Cry0Tpfx.js +0 -118
|
@@ -1,68 +1,38 @@
|
|
|
1
1
|
import { access } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { loadFailure, loadSuccess } from "./loadOutcome.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
async function resolvePipelinePath(
|
|
9
|
-
const
|
|
4
|
+
import { mergePipelineStages } from "./mergePipelineIncludes.js";
|
|
5
|
+
import { normalizePipelineStageEntries, toWiringRefs, } from "./normalizePipelineStageEntry.js";
|
|
6
|
+
import { loadStageFromObjectOutcome, loadStageOutcome } from "./loadStage.js";
|
|
7
|
+
import { resolvePipelineDagFromRefs } from "./resolvePipelineDag.js";
|
|
8
|
+
export async function resolvePipelinePath(pipelinePath, cwd) {
|
|
9
|
+
const resolved = path.normalize(path.resolve(cwd, pipelinePath));
|
|
10
10
|
try {
|
|
11
|
-
await access(
|
|
12
|
-
return
|
|
11
|
+
await access(resolved);
|
|
12
|
+
return resolved;
|
|
13
13
|
}
|
|
14
14
|
catch {
|
|
15
|
-
|
|
16
|
-
await access(candidate);
|
|
17
|
-
return candidate;
|
|
15
|
+
throw new Error(`Pipeline file not found: ${resolved}. Pass a filesystem path to --pipeline (e.g. pipelines/hello.pipeline.yaml), not a bare pipeline id.`);
|
|
18
16
|
}
|
|
19
17
|
}
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
},
|
|
35
|
-
]);
|
|
36
|
-
}
|
|
37
|
-
let raw;
|
|
38
|
-
try {
|
|
39
|
-
raw = await readYamlObject(pipelinePath);
|
|
40
|
-
}
|
|
41
|
-
catch (err) {
|
|
42
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
43
|
-
return loadFailure([
|
|
44
|
-
{
|
|
45
|
-
code: "pipeline.load_error",
|
|
46
|
-
message,
|
|
47
|
-
category: "pipeline",
|
|
48
|
-
},
|
|
49
|
-
]);
|
|
50
|
-
}
|
|
51
|
-
if (typeof raw?.id !== "string" || !Array.isArray(raw.stages)) {
|
|
52
|
-
return loadFailure([
|
|
53
|
-
{
|
|
54
|
-
code: "pipeline.invalid_shape",
|
|
55
|
-
message: `Invalid pipeline ${pipelinePath}: id and stages[] are required`,
|
|
56
|
-
category: "pipeline",
|
|
57
|
-
},
|
|
58
|
-
]);
|
|
59
|
-
}
|
|
60
|
-
const pipelineId = raw.id;
|
|
61
|
-
const ctx = { pipelineId: raw.id, path: pipelinePath };
|
|
18
|
+
async function loadPipelineFromPath(pipelinePath) {
|
|
19
|
+
const normalizedPipelinePath = path.normalize(path.resolve(pipelinePath));
|
|
20
|
+
const mergeOutcome = await mergePipelineStages(normalizedPipelinePath);
|
|
21
|
+
if (!mergeOutcome.ok) {
|
|
22
|
+
return loadFailure(mergeOutcome.issues);
|
|
23
|
+
}
|
|
24
|
+
const { entries: rawEntries, pipelineId } = mergeOutcome.value;
|
|
25
|
+
const ctx = { pipelineId, path: normalizedPipelinePath };
|
|
26
|
+
const normalizeOutcome = normalizePipelineStageEntries(rawEntries, ctx);
|
|
27
|
+
if (!normalizeOutcome.ok) {
|
|
28
|
+
return loadFailure(normalizeOutcome.issues);
|
|
29
|
+
}
|
|
30
|
+
const normalizedEntries = normalizeOutcome.value;
|
|
31
|
+
const wiringRefs = toWiringRefs(normalizedEntries);
|
|
62
32
|
let stageIds;
|
|
63
33
|
let dag;
|
|
64
34
|
try {
|
|
65
|
-
const resolved =
|
|
35
|
+
const resolved = resolvePipelineDagFromRefs(wiringRefs, ctx);
|
|
66
36
|
stageIds = resolved.stages;
|
|
67
37
|
dag = resolved.dag;
|
|
68
38
|
}
|
|
@@ -77,115 +47,87 @@ export async function loadPipelineOutcome(nameOrPath, options = {}) {
|
|
|
77
47
|
},
|
|
78
48
|
]);
|
|
79
49
|
}
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
stages: stageIds,
|
|
83
|
-
};
|
|
50
|
+
const entryById = new Map(normalizedEntries.map((entry) => [entry.id, entry]));
|
|
51
|
+
const stageSources = {};
|
|
84
52
|
const stages = [];
|
|
85
|
-
for (const stageId of
|
|
86
|
-
const
|
|
87
|
-
|
|
53
|
+
for (const stageId of stageIds) {
|
|
54
|
+
const entry = entryById.get(stageId);
|
|
55
|
+
if (!entry) {
|
|
56
|
+
return loadFailure([
|
|
57
|
+
{
|
|
58
|
+
code: "pipeline.invalid_shape",
|
|
59
|
+
message: `Pipeline ${pipelineId}: missing normalized entry for stage "${stageId}"`,
|
|
60
|
+
category: "pipeline",
|
|
61
|
+
pipelineId,
|
|
62
|
+
},
|
|
63
|
+
]);
|
|
64
|
+
}
|
|
65
|
+
if (entry.body.kind === "inline") {
|
|
66
|
+
const inlineOutcome = loadStageFromObjectOutcome(entry.body.raw, {
|
|
67
|
+
entryId: entry.id,
|
|
68
|
+
declaringPath: entry.declaringPath,
|
|
69
|
+
});
|
|
70
|
+
if (!inlineOutcome.ok) {
|
|
71
|
+
return loadFailure(inlineOutcome.issues);
|
|
72
|
+
}
|
|
73
|
+
stages.push(inlineOutcome.value);
|
|
74
|
+
stageSources[stageId] = { kind: "inline" };
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const stageOutcome = await loadStageOutcome(entry.body.absolutePath);
|
|
88
78
|
if (!stageOutcome.ok) {
|
|
89
|
-
const message = `Pipeline ${pipeline.id} references missing stage "${stageId}" at ${stagePath}`;
|
|
90
79
|
return loadFailure([
|
|
91
80
|
{
|
|
92
81
|
code: "pipeline.missing_stage",
|
|
93
|
-
message
|
|
82
|
+
message: `Pipeline ${pipelineId} references missing stage "${stageId}" at ${entry.body.absolutePath}`,
|
|
94
83
|
category: "pipeline",
|
|
95
84
|
pipelineId,
|
|
96
85
|
},
|
|
86
|
+
...stageOutcome.issues,
|
|
97
87
|
]);
|
|
98
88
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return findingStageIdFilenameMismatch(cwd, stagePath, fileStem, raw.id);
|
|
89
|
+
if (stageOutcome.value.id !== entry.id) {
|
|
90
|
+
return loadFailure([
|
|
91
|
+
{
|
|
92
|
+
code: "pipeline.stage_id_mismatch",
|
|
93
|
+
message: `Pipeline ${pipelineId}: stage entry "${entry.id}" in ${entry.declaringPath} uses ${entry.body.path} which declares id "${stageOutcome.value.id}"`,
|
|
94
|
+
category: "pipeline",
|
|
95
|
+
pipelineId,
|
|
96
|
+
},
|
|
97
|
+
]);
|
|
109
98
|
}
|
|
99
|
+
stages.push(stageOutcome.value);
|
|
100
|
+
stageSources[stageId] = { kind: "file", path: entry.body.absolutePath };
|
|
110
101
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (!outcome.ok) {
|
|
123
|
-
findings.push(...findingsFromLoadIssues(cwd, stagePath, outcome.issues));
|
|
124
|
-
return findings;
|
|
125
|
-
}
|
|
126
|
-
if (!mismatch && path.basename(stagePath, ".yaml") !== outcome.value.id) {
|
|
127
|
-
findings.push(findingStageIdFilenameMismatch(cwd, stagePath, path.basename(stagePath, ".yaml"), outcome.value.id));
|
|
128
|
-
}
|
|
129
|
-
return findings;
|
|
102
|
+
const pipeline = {
|
|
103
|
+
id: pipelineId,
|
|
104
|
+
stages: stageIds,
|
|
105
|
+
};
|
|
106
|
+
return loadSuccess({
|
|
107
|
+
pipeline,
|
|
108
|
+
stages,
|
|
109
|
+
dag,
|
|
110
|
+
pipelinePath: normalizedPipelinePath,
|
|
111
|
+
stageSources,
|
|
112
|
+
});
|
|
130
113
|
}
|
|
131
|
-
export async function
|
|
114
|
+
export async function loadPipelineOutcome(nameOrPath, options = {}) {
|
|
132
115
|
const cwd = options.cwd ?? process.cwd();
|
|
133
|
-
const stagesDir = options.stagesDir ?? path.join(cwd, "stages");
|
|
134
|
-
const validateStages = options.validateStages ?? true;
|
|
135
116
|
let pipelinePath;
|
|
136
117
|
try {
|
|
137
118
|
pipelinePath = await resolvePipelinePath(nameOrPath, cwd);
|
|
138
119
|
}
|
|
139
120
|
catch (err) {
|
|
140
121
|
const message = err instanceof Error ? err.message : String(err);
|
|
141
|
-
return
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
},
|
|
149
|
-
]),
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
const referencedStageIds = new Set();
|
|
153
|
-
try {
|
|
154
|
-
const raw = await readYamlObject(pipelinePath);
|
|
155
|
-
if (typeof raw?.id === "string" && Array.isArray(raw.stages)) {
|
|
156
|
-
const stageIds = extractPipelineStageIds(raw.stages);
|
|
157
|
-
if (stageIds) {
|
|
158
|
-
for (const stageId of stageIds) {
|
|
159
|
-
referencedStageIds.add(stageId);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
catch {
|
|
165
|
-
// loadPipelineOutcome will report read errors
|
|
166
|
-
}
|
|
167
|
-
const outcome = await loadPipelineOutcome(nameOrPath, { cwd, stagesDir });
|
|
168
|
-
const findings = [];
|
|
169
|
-
if (!outcome.ok) {
|
|
170
|
-
findings.push(...findingsFromLoadIssues(cwd, pipelinePath, outcome.issues));
|
|
171
|
-
if (validateStages) {
|
|
172
|
-
for (const stageId of referencedStageIds) {
|
|
173
|
-
const stagePath = path.join(stagesDir, `${stageId}.yaml`);
|
|
174
|
-
findings.push(...(await validateStageFile(cwd, stagePath)));
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return { ok: false, findings };
|
|
178
|
-
}
|
|
179
|
-
if (validateStages) {
|
|
180
|
-
for (const stageId of outcome.value.pipeline.stages) {
|
|
181
|
-
const stagePath = path.join(stagesDir, `${stageId}.yaml`);
|
|
182
|
-
findings.push(...(await validateStageFile(cwd, stagePath)));
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
if (findings.some((finding) => finding.severity === "error")) {
|
|
186
|
-
return { ok: false, findings };
|
|
122
|
+
return loadFailure([
|
|
123
|
+
{
|
|
124
|
+
code: "pipeline.load_error",
|
|
125
|
+
message,
|
|
126
|
+
category: "pipeline",
|
|
127
|
+
},
|
|
128
|
+
]);
|
|
187
129
|
}
|
|
188
|
-
return
|
|
130
|
+
return loadPipelineFromPath(pipelinePath);
|
|
189
131
|
}
|
|
190
132
|
export async function loadPipeline(nameOrPath, options = {}) {
|
|
191
133
|
const outcome = await loadPipelineOutcome(nameOrPath, options);
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { type StageConfig } from "../types/stage.js";
|
|
2
2
|
import { type LoadOutcome } from "./loadOutcome.js";
|
|
3
|
+
export declare function loadStageFromObjectOutcome(raw: Record<string, unknown>, ctx: {
|
|
4
|
+
entryId: string;
|
|
5
|
+
declaringPath: string;
|
|
6
|
+
}): LoadOutcome<StageConfig>;
|
|
3
7
|
export declare function loadStageOutcome(filePath: string): Promise<LoadOutcome<StageConfig>>;
|
|
4
8
|
export declare function loadStage(filePath: string): Promise<StageConfig>;
|
package/dist/config/loadStage.js
CHANGED
|
@@ -5,14 +5,14 @@ import { readYamlObject } from "./readYamlObject.js";
|
|
|
5
5
|
function isGateKind(value) {
|
|
6
6
|
return STAGE_GATE_KINDS.includes(value);
|
|
7
7
|
}
|
|
8
|
-
function parseGateKinds(raw,
|
|
8
|
+
function parseGateKinds(raw, label) {
|
|
9
9
|
if (raw === undefined)
|
|
10
10
|
return loadSuccess(undefined);
|
|
11
11
|
if (!Array.isArray(raw) || !raw.every((item) => typeof item === "string")) {
|
|
12
12
|
return loadFailure([
|
|
13
13
|
{
|
|
14
14
|
code: "stage.invalid_gate_kinds",
|
|
15
|
-
message: `Invalid stage
|
|
15
|
+
message: `Invalid stage ${label}: gate_kinds must be an array of strings`,
|
|
16
16
|
category: "stage",
|
|
17
17
|
},
|
|
18
18
|
]);
|
|
@@ -23,7 +23,7 @@ function parseGateKinds(raw, filePath) {
|
|
|
23
23
|
return loadFailure([
|
|
24
24
|
{
|
|
25
25
|
code: "stage.invalid_gate_kinds",
|
|
26
|
-
message: `Invalid stage
|
|
26
|
+
message: `Invalid stage ${label}: unsupported gate kind "${item}" (allowed: ${STAGE_GATE_KINDS.join(", ")})`,
|
|
27
27
|
category: "stage",
|
|
28
28
|
},
|
|
29
29
|
]);
|
|
@@ -32,35 +32,20 @@ function parseGateKinds(raw, filePath) {
|
|
|
32
32
|
}
|
|
33
33
|
return loadSuccess(kinds.length > 0 ? kinds : undefined);
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
raw = await readYamlObject(filePath);
|
|
39
|
-
}
|
|
40
|
-
catch (err) {
|
|
41
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
42
|
-
return loadFailure([
|
|
43
|
-
{
|
|
44
|
-
code: "stage.load_error",
|
|
45
|
-
message,
|
|
46
|
-
category: "stage",
|
|
47
|
-
},
|
|
48
|
-
]);
|
|
49
|
-
}
|
|
50
|
-
if (typeof raw?.id !== "string" ||
|
|
51
|
-
typeof raw?.system_prompt !== "string" ||
|
|
52
|
-
typeof raw?.model !== "string") {
|
|
35
|
+
function parseStageFields(raw, label, entryId) {
|
|
36
|
+
if (typeof raw.system_prompt !== "string" ||
|
|
37
|
+
typeof raw.model !== "string") {
|
|
53
38
|
return loadFailure([
|
|
54
39
|
{
|
|
55
40
|
code: "stage.invalid_shape",
|
|
56
|
-
message: `Invalid stage
|
|
41
|
+
message: `Invalid stage ${label}: system_prompt and model are required strings`,
|
|
57
42
|
category: "stage",
|
|
58
|
-
stageId:
|
|
43
|
+
stageId: entryId,
|
|
59
44
|
},
|
|
60
45
|
]);
|
|
61
46
|
}
|
|
62
47
|
const stage = {
|
|
63
|
-
id:
|
|
48
|
+
id: entryId,
|
|
64
49
|
system_prompt: raw.system_prompt,
|
|
65
50
|
model: raw.model,
|
|
66
51
|
};
|
|
@@ -71,9 +56,9 @@ export async function loadStageOutcome(filePath) {
|
|
|
71
56
|
return loadFailure([
|
|
72
57
|
{
|
|
73
58
|
code: "stage.invalid_payload_schema",
|
|
74
|
-
message: `Invalid stage
|
|
59
|
+
message: `Invalid stage ${label}: payload_schema must be an object`,
|
|
75
60
|
category: "stage",
|
|
76
|
-
stageId:
|
|
61
|
+
stageId: entryId,
|
|
77
62
|
},
|
|
78
63
|
]);
|
|
79
64
|
}
|
|
@@ -85,19 +70,19 @@ export async function loadStageOutcome(filePath) {
|
|
|
85
70
|
return loadFailure([
|
|
86
71
|
{
|
|
87
72
|
code: "stage.invalid_payload_schema",
|
|
88
|
-
message: `Invalid stage
|
|
73
|
+
message: `Invalid stage ${label}: invalid payload_schema: ${message}`,
|
|
89
74
|
category: "stage",
|
|
90
|
-
stageId:
|
|
75
|
+
stageId: entryId,
|
|
91
76
|
},
|
|
92
77
|
]);
|
|
93
78
|
}
|
|
94
79
|
stage.payload_schema = raw.payload_schema;
|
|
95
80
|
}
|
|
96
|
-
const gateKindsOutcome = parseGateKinds(raw.gate_kinds,
|
|
81
|
+
const gateKindsOutcome = parseGateKinds(raw.gate_kinds, label);
|
|
97
82
|
if (!gateKindsOutcome.ok) {
|
|
98
83
|
const issues = gateKindsOutcome.issues.map((issue) => ({
|
|
99
84
|
...issue,
|
|
100
|
-
stageId:
|
|
85
|
+
stageId: entryId,
|
|
101
86
|
}));
|
|
102
87
|
return loadFailure(issues);
|
|
103
88
|
}
|
|
@@ -108,9 +93,9 @@ export async function loadStageOutcome(filePath) {
|
|
|
108
93
|
return loadFailure([
|
|
109
94
|
{
|
|
110
95
|
code: "stage.invalid_skill",
|
|
111
|
-
message: `Invalid stage
|
|
96
|
+
message: `Invalid stage ${label}: skill must be a non-empty string`,
|
|
112
97
|
category: "stage",
|
|
113
|
-
stageId:
|
|
98
|
+
stageId: entryId,
|
|
114
99
|
},
|
|
115
100
|
]);
|
|
116
101
|
}
|
|
@@ -118,6 +103,49 @@ export async function loadStageOutcome(filePath) {
|
|
|
118
103
|
}
|
|
119
104
|
return loadSuccess(stage);
|
|
120
105
|
}
|
|
106
|
+
export function loadStageFromObjectOutcome(raw, ctx) {
|
|
107
|
+
const label = `${ctx.entryId} (${ctx.declaringPath})`;
|
|
108
|
+
return parseStageFields(raw, label, ctx.entryId);
|
|
109
|
+
}
|
|
110
|
+
export async function loadStageOutcome(filePath) {
|
|
111
|
+
let raw;
|
|
112
|
+
try {
|
|
113
|
+
raw = await readYamlObject(filePath);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
117
|
+
return loadFailure([
|
|
118
|
+
{
|
|
119
|
+
code: "stage.load_error",
|
|
120
|
+
message,
|
|
121
|
+
category: "stage",
|
|
122
|
+
},
|
|
123
|
+
]);
|
|
124
|
+
}
|
|
125
|
+
if (typeof raw?.id !== "string") {
|
|
126
|
+
return loadFailure([
|
|
127
|
+
{
|
|
128
|
+
code: "stage.invalid_shape",
|
|
129
|
+
message: `Invalid stage file ${filePath}: id, system_prompt, and model are required strings`,
|
|
130
|
+
category: "stage",
|
|
131
|
+
},
|
|
132
|
+
]);
|
|
133
|
+
}
|
|
134
|
+
const outcome = parseStageFields(raw, `file ${filePath}`, raw.id);
|
|
135
|
+
if (!outcome.ok)
|
|
136
|
+
return outcome;
|
|
137
|
+
if (outcome.value.id !== raw.id) {
|
|
138
|
+
return loadFailure([
|
|
139
|
+
{
|
|
140
|
+
code: "stage.invalid_shape",
|
|
141
|
+
message: `Invalid stage file ${filePath}: id mismatch`,
|
|
142
|
+
category: "stage",
|
|
143
|
+
stageId: raw.id,
|
|
144
|
+
},
|
|
145
|
+
]);
|
|
146
|
+
}
|
|
147
|
+
return outcome;
|
|
148
|
+
}
|
|
121
149
|
export async function loadStage(filePath) {
|
|
122
150
|
const outcome = await loadStageOutcome(filePath);
|
|
123
151
|
if (!outcome.ok) {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { LoadedManifest } from "../types/stageflowManifest.js";
|
|
2
|
+
import { type LoadOutcome } from "./loadOutcome.js";
|
|
3
|
+
export declare function manifestPathForProject(projectRoot: string): string;
|
|
4
|
+
export declare function parseStageflowManifestOutcome(yamlText: string, manifestPath: string, projectRoot: string): LoadOutcome<LoadedManifest>;
|
|
5
|
+
export declare function loadStageflowManifestOutcome(projectRoot: string): Promise<LoadOutcome<LoadedManifest>>;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
4
|
+
import { loadFailure, loadSuccess } from "./loadOutcome.js";
|
|
5
|
+
const DEFAULT_PIPELINE_PATTERN = "*.pipeline.yaml";
|
|
6
|
+
const DEFAULT_TASK_PATTERN = "*.task.yaml";
|
|
7
|
+
export function manifestPathForProject(projectRoot) {
|
|
8
|
+
return path.join(projectRoot, "stageflow.yaml");
|
|
9
|
+
}
|
|
10
|
+
function catalogIssue(code, message) {
|
|
11
|
+
return { code, message, category: "catalog" };
|
|
12
|
+
}
|
|
13
|
+
function normalizeEntryPath(entry) {
|
|
14
|
+
return entry.replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
15
|
+
}
|
|
16
|
+
function parseStringArray(raw, field, issues) {
|
|
17
|
+
if (!Array.isArray(raw)) {
|
|
18
|
+
issues.push(catalogIssue("catalog.manifest_invalid", `catalog.${field} must be an array`));
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const entries = [];
|
|
22
|
+
for (const item of raw) {
|
|
23
|
+
if (typeof item !== "string" || item.trim().length === 0) {
|
|
24
|
+
issues.push(catalogIssue("catalog.manifest_invalid", `catalog.${field} entries must be non-empty strings`));
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const normalized = normalizeEntryPath(item.trim());
|
|
28
|
+
if (path.isAbsolute(normalized)) {
|
|
29
|
+
issues.push(catalogIssue("catalog.manifest_invalid", `catalog.${field} entries must be repo-relative paths`));
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
entries.push(normalized);
|
|
33
|
+
}
|
|
34
|
+
return entries;
|
|
35
|
+
}
|
|
36
|
+
function parsePatterns(raw, issues) {
|
|
37
|
+
const defaults = {
|
|
38
|
+
pipeline: DEFAULT_PIPELINE_PATTERN,
|
|
39
|
+
task: DEFAULT_TASK_PATTERN,
|
|
40
|
+
};
|
|
41
|
+
if (raw === undefined || raw === null) {
|
|
42
|
+
return defaults;
|
|
43
|
+
}
|
|
44
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
45
|
+
issues.push(catalogIssue("catalog.manifest_invalid", "catalog.patterns must be an object"));
|
|
46
|
+
return defaults;
|
|
47
|
+
}
|
|
48
|
+
const record = raw;
|
|
49
|
+
const pipeline = record.pipeline === undefined
|
|
50
|
+
? defaults.pipeline
|
|
51
|
+
: typeof record.pipeline === "string" && record.pipeline.trim().length > 0
|
|
52
|
+
? record.pipeline.trim()
|
|
53
|
+
: null;
|
|
54
|
+
const task = record.task === undefined
|
|
55
|
+
? defaults.task
|
|
56
|
+
: typeof record.task === "string" && record.task.trim().length > 0
|
|
57
|
+
? record.task.trim()
|
|
58
|
+
: null;
|
|
59
|
+
if (pipeline === null) {
|
|
60
|
+
issues.push(catalogIssue("catalog.manifest_invalid", "catalog.patterns.pipeline must be a string"));
|
|
61
|
+
}
|
|
62
|
+
if (task === null) {
|
|
63
|
+
issues.push(catalogIssue("catalog.manifest_invalid", "catalog.patterns.task must be a string"));
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
pipeline: pipeline ?? defaults.pipeline,
|
|
67
|
+
task: task ?? defaults.task,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export function parseStageflowManifestOutcome(yamlText, manifestPath, projectRoot) {
|
|
71
|
+
let raw;
|
|
72
|
+
try {
|
|
73
|
+
raw = parseYaml(yamlText);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
77
|
+
return loadFailure([catalogIssue("catalog.manifest_load_error", message)]);
|
|
78
|
+
}
|
|
79
|
+
const issues = [];
|
|
80
|
+
const record = raw;
|
|
81
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) {
|
|
82
|
+
return loadFailure([
|
|
83
|
+
catalogIssue("catalog.manifest_invalid", "stageflow.yaml must be a mapping"),
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
if (record.version !== 1) {
|
|
87
|
+
issues.push(catalogIssue("catalog.manifest_invalid", `Unsupported manifest version: ${record.version}`));
|
|
88
|
+
}
|
|
89
|
+
const catalogRaw = record.catalog;
|
|
90
|
+
if (catalogRaw === undefined ||
|
|
91
|
+
typeof catalogRaw !== "object" ||
|
|
92
|
+
catalogRaw === null ||
|
|
93
|
+
Array.isArray(catalogRaw)) {
|
|
94
|
+
issues.push(catalogIssue("catalog.manifest_invalid", "catalog object is required"));
|
|
95
|
+
if (issues.length > 0) {
|
|
96
|
+
return loadFailure(issues);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const catalogRecord = catalogRaw;
|
|
100
|
+
const pipelines = parseStringArray(catalogRecord.pipelines, "pipelines", issues);
|
|
101
|
+
const tasks = parseStringArray(catalogRecord.tasks, "tasks", issues);
|
|
102
|
+
const excludeRaw = catalogRecord.exclude;
|
|
103
|
+
let exclude;
|
|
104
|
+
if (excludeRaw !== undefined) {
|
|
105
|
+
const parsed = parseStringArray(excludeRaw, "exclude", issues);
|
|
106
|
+
if (parsed !== null) {
|
|
107
|
+
exclude = parsed;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const patterns = parsePatterns(catalogRecord.patterns, issues);
|
|
111
|
+
if (issues.length > 0) {
|
|
112
|
+
return loadFailure(issues);
|
|
113
|
+
}
|
|
114
|
+
if (pipelines === null || tasks === null) {
|
|
115
|
+
return loadFailure(issues);
|
|
116
|
+
}
|
|
117
|
+
const catalog = { pipelines, tasks };
|
|
118
|
+
if (exclude !== undefined) {
|
|
119
|
+
catalog.exclude = exclude;
|
|
120
|
+
}
|
|
121
|
+
if (catalogRecord.patterns !== undefined) {
|
|
122
|
+
catalog.patterns = {};
|
|
123
|
+
if (typeof catalogRecord.patterns.pipeline === "string") {
|
|
124
|
+
catalog.patterns.pipeline = patterns.pipeline;
|
|
125
|
+
}
|
|
126
|
+
if (typeof catalogRecord.patterns.task === "string") {
|
|
127
|
+
catalog.patterns.task = patterns.task;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const manifest = { version: 1, catalog };
|
|
131
|
+
return loadSuccess({
|
|
132
|
+
path: manifestPath,
|
|
133
|
+
projectRoot,
|
|
134
|
+
manifest,
|
|
135
|
+
patterns,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
export async function loadStageflowManifestOutcome(projectRoot) {
|
|
139
|
+
const manifestPath = manifestPathForProject(projectRoot);
|
|
140
|
+
try {
|
|
141
|
+
await access(manifestPath);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return loadFailure([
|
|
145
|
+
catalogIssue("catalog.manifest_load_error", `Manifest not found: ${manifestPath}`),
|
|
146
|
+
]);
|
|
147
|
+
}
|
|
148
|
+
let yamlText;
|
|
149
|
+
try {
|
|
150
|
+
yamlText = await readFile(manifestPath, "utf8");
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
154
|
+
return loadFailure([catalogIssue("catalog.manifest_load_error", message)]);
|
|
155
|
+
}
|
|
156
|
+
return parseStageflowManifestOutcome(yamlText, manifestPath, projectRoot);
|
|
157
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type LoadOutcome } from "./loadOutcome.js";
|
|
2
|
+
export type RawMergedEntry = {
|
|
3
|
+
raw: unknown;
|
|
4
|
+
declaringPath: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function mergePipelineStages(rootPath: string): Promise<LoadOutcome<{
|
|
7
|
+
entries: RawMergedEntry[];
|
|
8
|
+
pipelineId: string;
|
|
9
|
+
}>>;
|