stageflow 0.2.0 → 0.8.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 +179 -14
- package/dist/agent/activity.d.ts +12 -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/cursorProvider.d.ts +1 -10
- package/dist/agent/cursorProvider.js +18 -10
- package/dist/agent/fakeAgent.js +15 -7
- package/dist/agent/piAdapter.d.ts +18 -1
- package/dist/agent/piAdapter.js +115 -45
- package/dist/agent/port.d.ts +7 -0
- package/dist/agent/port.js +3 -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/artifactCommand.d.ts +10 -0
- package/dist/cli/artifactCommand.js +120 -0
- package/dist/cli/envelopeCommand.d.ts +32 -0
- package/dist/cli/envelopeCommand.js +285 -0
- package/dist/cli/exportRunCommand.d.ts +10 -0
- package/dist/cli/exportRunCommand.js +150 -0
- package/dist/cli/handoffFormat.d.ts +22 -0
- package/dist/cli/handoffFormat.js +61 -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 +7 -1
- package/dist/cli/runCommand.js +83 -11
- package/dist/cli/runOutput.d.ts +4 -1
- package/dist/cli/runOutput.js +36 -8
- package/dist/cli/skillsCommand.d.ts +16 -0
- package/dist/cli/skillsCommand.js +459 -0
- package/dist/cli/validateCommand.d.ts +1 -1
- package/dist/cli/validateCommand.js +20 -3
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +128 -22
- 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 +88 -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 +18 -0
- package/dist/config/normalizePipelineStageEntry.js +210 -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 +4 -0
- package/dist/config/resolveForkEmitContext.js +34 -0
- package/dist/config/resolvePipelineDag.d.ts +5 -1
- package/dist/config/resolvePipelineDag.js +86 -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.d.ts +2 -0
- package/dist/envelope/check.js +58 -0
- package/dist/envelope/cloneForks.d.ts +2 -0
- package/dist/envelope/cloneForks.js +62 -0
- package/dist/envelope/forkChoice.d.ts +3 -0
- package/dist/envelope/forkChoice.js +47 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/mcp/catalogTools.d.ts +3 -0
- package/dist/mcp/catalogTools.js +187 -0
- package/dist/mcp/controlTools.d.ts +3 -0
- package/dist/mcp/controlTools.js +147 -0
- package/dist/mcp/deps.d.ts +11 -0
- package/dist/mcp/deps.js +1 -0
- package/dist/mcp/projectRun.d.ts +1 -37
- package/dist/mcp/projectRun.js +1 -57
- package/dist/mcp/resources.d.ts +5 -0
- package/dist/mcp/resources.js +45 -0
- package/dist/mcp/server.d.ts +16 -3
- package/dist/mcp/server.js +159 -3
- package/dist/mcp/toolResults.d.ts +7 -0
- package/dist/mcp/toolResults.js +6 -0
- package/dist/mcp/tools.d.ts +2 -7
- package/dist/mcp/tools.js +52 -86
- package/dist/mcp/waitRun.d.ts +46 -0
- package/dist/mcp/waitRun.js +146 -0
- package/dist/mcp/waitingGates.d.ts +12 -0
- package/dist/mcp/waitingGates.js +83 -0
- 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/projection/projectRun.d.ts +44 -0
- package/dist/projection/projectRun.js +71 -0
- package/dist/prompt/priorEnvelope.d.ts +1 -1
- package/dist/prompt/priorEnvelope.js +4 -1
- package/dist/runstore/normalizeCatalogPath.d.ts +1 -0
- package/dist/runstore/normalizeCatalogPath.js +4 -0
- package/dist/runstore/paths.js +2 -0
- package/dist/runstore/pipelineDagSnapshot.d.ts +9 -0
- package/dist/runstore/pipelineDagSnapshot.js +90 -1
- package/dist/runstore/port.d.ts +22 -2
- package/dist/runstore/port.js +4 -1
- package/dist/runstore/runProjection.d.ts +1 -1
- package/dist/runstore/runProjection.js +25 -4
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +3 -2
- package/dist/runstore/sqlite/SqliteRunStore.js +65 -7
- package/dist/runstore/sqlite/schema.d.ts +1 -1
- package/dist/runstore/sqlite/schema.js +4 -1
- package/dist/runstore/stageInstanceId.d.ts +4 -0
- package/dist/runstore/stageInstanceId.js +23 -0
- package/dist/runstore/syntheticStageSnapshot.d.ts +1 -1
- package/dist/runstore/syntheticStageSnapshot.js +2 -1
- package/dist/runstore/trackProjection.js +28 -12
- package/dist/runstore/workspaceLayout.d.ts +1 -0
- package/dist/runstore/workspaceLayout.js +1 -1
- package/dist/runtime/cloneSchedule.d.ts +18 -0
- package/dist/runtime/cloneSchedule.js +233 -0
- package/dist/runtime/credentialBinding.d.ts +8 -6
- package/dist/runtime/credentialBinding.js +18 -24
- package/dist/runtime/envelopeRouting.d.ts +2 -1
- package/dist/runtime/envelopeRouting.js +80 -13
- package/dist/runtime/pipelineRunner.d.ts +3 -0
- package/dist/runtime/pipelineRunner.js +15 -1
- package/dist/runtime/pipelineScheduler.d.ts +8 -2
- package/dist/runtime/pipelineScheduler.js +215 -39
- 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 +31 -10
- package/dist/runtime/runChangeBus.d.ts +18 -0
- package/dist/runtime/runChangeBus.js +83 -0
- package/dist/runtime/runManager.d.ts +4 -0
- package/dist/runtime/runManager.js +36 -16
- package/dist/runtime/settingsFile.d.ts +12 -0
- package/dist/runtime/settingsFile.js +87 -6
- package/dist/runtime/stageAttemptBootstrap.d.ts +1 -0
- package/dist/runtime/stageAttemptBootstrap.js +18 -7
- package/dist/runtime/stageProcessLauncher.js +3 -0
- package/dist/runtime/stageRecovery.js +3 -1
- package/dist/runtime/stageRunner.d.ts +1 -0
- package/dist/runtime/stageRunner.js +17 -15
- package/dist/runtime/stageWorker.js +13 -10
- package/dist/server/bootstrap.d.ts +32 -0
- package/dist/server/bootstrap.js +56 -0
- package/dist/server/createHttpHost.d.ts +34 -0
- package/dist/server/createHttpHost.js +70 -0
- package/dist/server/http.d.ts +6 -13
- package/dist/server/http.js +297 -352
- package/dist/server/mcpHost.d.ts +22 -0
- package/dist/server/mcpHost.js +21 -0
- package/dist/server/operatorResults.d.ts +13 -0
- package/dist/server/operatorResults.js +22 -0
- package/dist/tools/emitStageEnvelope.d.ts +12 -1
- package/dist/tools/emitStageEnvelope.js +27 -1
- package/dist/types/envelope.d.ts +3 -0
- package/dist/types/forkChoice.d.ts +30 -0
- package/dist/types/forkChoice.js +1 -0
- package/dist/types/pipeline.d.ts +59 -2
- package/dist/types/stageflowManifest.d.ts +23 -0
- package/dist/types/stageflowManifest.js +1 -0
- package/dist/ui/assets/{index-DCsDopak.css → index-C3N7MXAC.css} +1 -1
- package/dist/ui/assets/index-CSoGDA3A.js +118 -0
- package/dist/ui/index.html +3 -2
- package/dist/ui/stageflow-icon.svg +12 -0
- package/package.json +8 -5
- package/skills/install-suite.sh +107 -0
- package/skills/stageflow/SKILL.md +26 -0
- package/skills/stageflow/references/control-surface.md +26 -0
- package/skills/stageflow/scripts/detect-host.mjs +54 -0
- package/skills/stageflow/scripts/detect-host.test.mjs +97 -0
- package/skills/stageflow-author/SKILL.md +58 -0
- package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -0
- package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -0
- package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +13 -0
- package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +10 -0
- package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +9 -0
- package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +10 -0
- package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +10 -0
- package/skills/stageflow-author/assets/examples/linear-review/review.yaml +15 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +9 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +10 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +10 -0
- package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +9 -0
- package/skills/stageflow-author/references/catalog-mapping.md +131 -0
- package/skills/stageflow-author/references/catalog-write-conventions.md +29 -0
- package/skills/stageflow-author/references/stage-prompt-template.md +55 -0
- package/skills/stageflow-author/references/validate-and-report.md +38 -0
- package/skills/stageflow-delegate/SKILL.md +18 -0
- package/skills/stageflow-delegate/references/authoring-or-run.md +15 -0
- package/skills/stageflow-delegate/references/example-walkthrough.md +30 -0
- package/skills/stageflow-delegate/references/pattern-detection.md +44 -0
- package/skills/stageflow-run/SKILL.md +151 -0
- package/skills/stageflow-run/references/mcp-call.md +17 -0
- package/skills/stageflow-run/references/task-and-pipeline-selection.md +52 -0
- package/skills/stageflow-run/scripts/mcp-call.mjs +299 -0
- package/skills/stageflow-run/scripts/mcp-call.test.mjs +271 -0
- package/skills/stageflow-session-capture/SKILL.md +94 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +7 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +8 -0
- package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +8 -0
- package/skills/stageflow-session-capture/references/catalog-authoring.md +40 -0
- package/skills/stageflow-session-capture/references/transcript-sources.md +25 -0
- package/skills/stageflow-session-capture/scripts/check-provider-gate.mjs +43 -0
- package/skills/stageflow-session-capture/scripts/locate-session-transcript.mjs +141 -0
- package/skills/stageflow-session-capture/scripts/resolve-catalog-id.mjs +119 -0
- package/skills/stageflow-setup/SKILL.md +110 -0
- package/dist/ui/assets/index-Cry0Tpfx.js +0 -118
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { loadFailure, loadSuccess } from "./loadOutcome.js";
|
|
3
|
+
import { BODY_KEYS, isAllowedPipelineStageEntryKey, } from "./pipelineStageKeys.js";
|
|
4
|
+
function isPlainObject(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
export function inferIdFromUsesPath(usesPath) {
|
|
8
|
+
const base = path.basename(usesPath);
|
|
9
|
+
if (!base)
|
|
10
|
+
return null;
|
|
11
|
+
if (base.endsWith(".stage.yaml")) {
|
|
12
|
+
const id = base.slice(0, -".stage.yaml".length);
|
|
13
|
+
return id || null;
|
|
14
|
+
}
|
|
15
|
+
if (base.endsWith(".yaml")) {
|
|
16
|
+
const id = base.slice(0, -".yaml".length);
|
|
17
|
+
return id || null;
|
|
18
|
+
}
|
|
19
|
+
return base;
|
|
20
|
+
}
|
|
21
|
+
function extractBodyRaw(raw) {
|
|
22
|
+
const body = {};
|
|
23
|
+
for (const key of Object.keys(raw)) {
|
|
24
|
+
if (BODY_KEYS.has(key)) {
|
|
25
|
+
body[key] = raw[key];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return body;
|
|
29
|
+
}
|
|
30
|
+
function hasBodyKey(raw) {
|
|
31
|
+
return Object.keys(raw).some((key) => BODY_KEYS.has(key) && key !== "skill");
|
|
32
|
+
}
|
|
33
|
+
function readSkill(raw) {
|
|
34
|
+
if (raw.skill === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
if (typeof raw.skill !== "string" || raw.skill.trim() === "") {
|
|
37
|
+
return loadFailure([
|
|
38
|
+
{
|
|
39
|
+
code: "stage.invalid_skill",
|
|
40
|
+
message: "skill must be a non-empty string",
|
|
41
|
+
category: "stage",
|
|
42
|
+
},
|
|
43
|
+
]);
|
|
44
|
+
}
|
|
45
|
+
return loadSuccess(raw.skill.trim());
|
|
46
|
+
}
|
|
47
|
+
function stringStageRefMessage(index, declaringPath, entry) {
|
|
48
|
+
const hint = entry
|
|
49
|
+
? `bare string stage refs are not supported; use { id: "${entry}", uses: "./${entry}.yaml" } or inline body`
|
|
50
|
+
: `bare string stage refs are not supported; use { id: "…", uses: "./….yaml" } or inline body`;
|
|
51
|
+
return `Invalid stage entry at index ${index} in ${declaringPath}: ${hint}`;
|
|
52
|
+
}
|
|
53
|
+
export function normalizePipelineStageEntries(rawEntries, ctx) {
|
|
54
|
+
const normalized = [];
|
|
55
|
+
for (let index = 0; index < rawEntries.length; index++) {
|
|
56
|
+
const { raw, declaringPath } = rawEntries[index];
|
|
57
|
+
if (typeof raw === "string") {
|
|
58
|
+
return loadFailure([
|
|
59
|
+
{
|
|
60
|
+
code: "pipeline.string_stage_ref",
|
|
61
|
+
message: stringStageRefMessage(index, declaringPath, raw),
|
|
62
|
+
category: "pipeline",
|
|
63
|
+
pipelineId: ctx.pipelineId,
|
|
64
|
+
},
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
67
|
+
if (!isPlainObject(raw)) {
|
|
68
|
+
return loadFailure([
|
|
69
|
+
{
|
|
70
|
+
code: "pipeline.invalid_shape",
|
|
71
|
+
message: `Invalid stage entry at index ${index} in ${declaringPath}: expected object`,
|
|
72
|
+
category: "pipeline",
|
|
73
|
+
pipelineId: ctx.pipelineId,
|
|
74
|
+
},
|
|
75
|
+
]);
|
|
76
|
+
}
|
|
77
|
+
for (const key of Object.keys(raw)) {
|
|
78
|
+
if (!isAllowedPipelineStageEntryKey(key)) {
|
|
79
|
+
return loadFailure([
|
|
80
|
+
{
|
|
81
|
+
code: "pipeline.invalid_shape",
|
|
82
|
+
message: `Pipeline ${ctx.pipelineId} (${ctx.path}): invalid stage entry at index ${index}: unknown key "${key}"`,
|
|
83
|
+
category: "pipeline",
|
|
84
|
+
pipelineId: ctx.pipelineId,
|
|
85
|
+
},
|
|
86
|
+
]);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const uses = typeof raw.uses === "string" ? raw.uses : undefined;
|
|
90
|
+
const hasBody = hasBodyKey(raw);
|
|
91
|
+
const skillOutcome = readSkill(raw);
|
|
92
|
+
if (skillOutcome !== undefined && !skillOutcome.ok) {
|
|
93
|
+
return skillOutcome;
|
|
94
|
+
}
|
|
95
|
+
const skill = skillOutcome?.ok ? skillOutcome.value : undefined;
|
|
96
|
+
if (uses && hasBody) {
|
|
97
|
+
return loadFailure([
|
|
98
|
+
{
|
|
99
|
+
code: "pipeline.stage_uses_inline_conflict",
|
|
100
|
+
message: `Pipeline ${ctx.pipelineId} (${ctx.path}): stage entry at index ${index} has both uses and inline body fields`,
|
|
101
|
+
category: "pipeline",
|
|
102
|
+
pipelineId: ctx.pipelineId,
|
|
103
|
+
},
|
|
104
|
+
]);
|
|
105
|
+
}
|
|
106
|
+
if (!uses && !hasBody) {
|
|
107
|
+
const entryLabel = typeof raw.id === "string" ? raw.id : String(index);
|
|
108
|
+
return loadFailure([
|
|
109
|
+
{
|
|
110
|
+
code: "pipeline.stage_missing_body",
|
|
111
|
+
message: `Pipeline ${ctx.pipelineId} (${ctx.path}): stage "${entryLabel}" has no uses: path or inline body`,
|
|
112
|
+
category: "pipeline",
|
|
113
|
+
pipelineId: ctx.pipelineId,
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
116
|
+
}
|
|
117
|
+
let id = typeof raw.id === "string" && raw.id ? raw.id : undefined;
|
|
118
|
+
if (!id && uses) {
|
|
119
|
+
const inferred = inferIdFromUsesPath(uses);
|
|
120
|
+
if (inferred)
|
|
121
|
+
id = inferred;
|
|
122
|
+
}
|
|
123
|
+
if (!id) {
|
|
124
|
+
return loadFailure([
|
|
125
|
+
{
|
|
126
|
+
code: "pipeline.invalid_shape",
|
|
127
|
+
message: `Pipeline ${ctx.pipelineId} (${ctx.path}): invalid stage entry at index ${index}: id must be a non-empty string or inferrable from uses path`,
|
|
128
|
+
category: "pipeline",
|
|
129
|
+
pipelineId: ctx.pipelineId,
|
|
130
|
+
},
|
|
131
|
+
]);
|
|
132
|
+
}
|
|
133
|
+
if (raw.needs !== undefined) {
|
|
134
|
+
if (Array.isArray(raw.needs)) {
|
|
135
|
+
return loadFailure([
|
|
136
|
+
{
|
|
137
|
+
code: "pipeline.dag_error",
|
|
138
|
+
message: `Pipeline ${ctx.pipelineId} (${ctx.path}): stage "${id}": needs must be a string, not an array`,
|
|
139
|
+
category: "pipeline",
|
|
140
|
+
pipelineId: ctx.pipelineId,
|
|
141
|
+
},
|
|
142
|
+
]);
|
|
143
|
+
}
|
|
144
|
+
if (typeof raw.needs !== "string" || !raw.needs) {
|
|
145
|
+
return loadFailure([
|
|
146
|
+
{
|
|
147
|
+
code: "pipeline.dag_error",
|
|
148
|
+
message: `Pipeline ${ctx.pipelineId} (${ctx.path}): stage "${id}": needs must be a non-empty string`,
|
|
149
|
+
category: "pipeline",
|
|
150
|
+
pipelineId: ctx.pipelineId,
|
|
151
|
+
},
|
|
152
|
+
]);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
let forkValue;
|
|
156
|
+
if (raw.fork !== undefined) {
|
|
157
|
+
if (!isPlainObject(raw.fork)) {
|
|
158
|
+
return loadFailure([
|
|
159
|
+
{
|
|
160
|
+
code: "pipeline.dag_error",
|
|
161
|
+
message: `Pipeline ${ctx.pipelineId} (${ctx.path}): stage "${id}": fork must be an object`,
|
|
162
|
+
category: "pipeline",
|
|
163
|
+
pipelineId: ctx.pipelineId,
|
|
164
|
+
},
|
|
165
|
+
]);
|
|
166
|
+
}
|
|
167
|
+
forkValue = raw.fork;
|
|
168
|
+
}
|
|
169
|
+
let body;
|
|
170
|
+
if (uses) {
|
|
171
|
+
const absolutePath = path.resolve(path.dirname(declaringPath), uses);
|
|
172
|
+
body = { kind: "uses", path: uses, absolutePath };
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
body = { kind: "inline", raw: extractBodyRaw(raw) };
|
|
176
|
+
}
|
|
177
|
+
const entry = {
|
|
178
|
+
id,
|
|
179
|
+
declaringPath,
|
|
180
|
+
body,
|
|
181
|
+
...(typeof raw.needs === "string" ? { needs: raw.needs } : {}),
|
|
182
|
+
...(forkValue !== undefined ? { fork: forkValue } : {}),
|
|
183
|
+
...(raw.clonable !== undefined ? { clonable: raw.clonable } : {}),
|
|
184
|
+
...(raw.clone_cap !== undefined ? { clone_cap: raw.clone_cap } : {}),
|
|
185
|
+
...(skill !== undefined ? { skill } : {}),
|
|
186
|
+
};
|
|
187
|
+
const priorPath = normalized.find((e) => e.id === id)?.declaringPath;
|
|
188
|
+
if (priorPath) {
|
|
189
|
+
return loadFailure([
|
|
190
|
+
{
|
|
191
|
+
code: "pipeline.include_duplicate_stage",
|
|
192
|
+
message: `Duplicate stage id "${id}" in ${declaringPath} (also declared in ${priorPath})`,
|
|
193
|
+
category: "pipeline",
|
|
194
|
+
pipelineId: ctx.pipelineId,
|
|
195
|
+
},
|
|
196
|
+
]);
|
|
197
|
+
}
|
|
198
|
+
normalized.push(entry);
|
|
199
|
+
}
|
|
200
|
+
return loadSuccess(normalized);
|
|
201
|
+
}
|
|
202
|
+
export function toWiringRefs(entries) {
|
|
203
|
+
return entries.map((entry) => ({
|
|
204
|
+
id: entry.id,
|
|
205
|
+
...(entry.needs !== undefined ? { needs: entry.needs } : {}),
|
|
206
|
+
...(entry.fork !== undefined ? { fork: entry.fork } : {}),
|
|
207
|
+
...(entry.clonable !== undefined ? { clonable: entry.clonable } : {}),
|
|
208
|
+
...(entry.clone_cap !== undefined ? { clone_cap: entry.clone_cap } : {}),
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const WIRING_KEYS = new Set(["id", "needs", "fork", "uses", "clonable", "clone_cap"]);
|
|
2
|
+
export const BODY_KEYS = new Set([
|
|
3
|
+
"system_prompt",
|
|
4
|
+
"model",
|
|
5
|
+
"payload_schema",
|
|
6
|
+
"gate_kinds",
|
|
7
|
+
"skill",
|
|
8
|
+
]);
|
|
9
|
+
export function isPipelineStageBodyKey(key) {
|
|
10
|
+
return BODY_KEYS.has(key);
|
|
11
|
+
}
|
|
12
|
+
export function isAllowedPipelineStageEntryKey(key) {
|
|
13
|
+
return WIRING_KEYS.has(key) || BODY_KEYS.has(key);
|
|
14
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LoadedManifest } from "../types/stageflowManifest.js";
|
|
2
|
+
import type { LoadIssue } from "./loadOutcome.js";
|
|
3
|
+
import { type CatalogManifestStatus, type StageflowContext } from "../project/resolveStageflowContext.js";
|
|
4
|
+
export type { CatalogManifestStatus };
|
|
5
|
+
export type CatalogContext = {
|
|
6
|
+
projectRoot: string | null;
|
|
7
|
+
manifest: LoadedManifest | null;
|
|
8
|
+
manifestStatus: CatalogManifestStatus;
|
|
9
|
+
issues: LoadIssue[];
|
|
10
|
+
};
|
|
11
|
+
export declare function catalogContextFromStageflow(ctx: StageflowContext): CatalogContext;
|
|
12
|
+
/** @deprecated Prefer resolveStageflowContext */
|
|
13
|
+
export declare function resolveCatalogContext(invocationCwd: string): Promise<CatalogContext>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { resolveStageflowContext, } from "../project/resolveStageflowContext.js";
|
|
2
|
+
export function catalogContextFromStageflow(ctx) {
|
|
3
|
+
return {
|
|
4
|
+
projectRoot: ctx.isGitProject ? ctx.projectRoot : null,
|
|
5
|
+
manifest: ctx.manifest,
|
|
6
|
+
manifestStatus: ctx.manifestStatus,
|
|
7
|
+
issues: ctx.manifestIssues,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/** @deprecated Prefer resolveStageflowContext */
|
|
11
|
+
export async function resolveCatalogContext(invocationCwd) {
|
|
12
|
+
return catalogContextFromStageflow(await resolveStageflowContext(invocationCwd));
|
|
13
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CloneEmitContext, ForkEmitContext } from "../types/forkChoice.js";
|
|
2
|
+
import type { ResolvedPipelineDag } from "../types/pipeline.js";
|
|
3
|
+
export declare function resolveCloneEmitContext(dag: ResolvedPipelineDag, stageId: string): CloneEmitContext | undefined;
|
|
4
|
+
export declare function resolveForkEmitContext(dag: ResolvedPipelineDag, stageId: string): ForkEmitContext | undefined;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function resolveCloneEmitContext(dag, stageId) {
|
|
2
|
+
const childIds = dag.childrenOf[stageId] ?? [];
|
|
3
|
+
const clonableSuccessors = [];
|
|
4
|
+
for (const childId of childIds) {
|
|
5
|
+
const node = dag.nodes.find((n) => n.id === childId);
|
|
6
|
+
if (node?.clonable === true) {
|
|
7
|
+
clonableSuccessors.push({
|
|
8
|
+
successorId: childId,
|
|
9
|
+
cloneCap: node.clone_cap,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (clonableSuccessors.length === 0)
|
|
14
|
+
return undefined;
|
|
15
|
+
return { clonableSuccessors };
|
|
16
|
+
}
|
|
17
|
+
export function resolveForkEmitContext(dag, stageId) {
|
|
18
|
+
const dagNode = dag.nodes.find((n) => n.id === stageId);
|
|
19
|
+
if (!dagNode?.fork)
|
|
20
|
+
return undefined;
|
|
21
|
+
const immediateSuccessorIds = (dag.childrenOf[stageId] ?? []).filter((childId) => {
|
|
22
|
+
const child = dag.nodes.find((n) => n.id === childId);
|
|
23
|
+
return child?.clonable !== true;
|
|
24
|
+
});
|
|
25
|
+
if (immediateSuccessorIds.length === 0)
|
|
26
|
+
return undefined;
|
|
27
|
+
return {
|
|
28
|
+
immediateSuccessorIds,
|
|
29
|
+
forkShape: {
|
|
30
|
+
cardinality: dagNode.fork.select,
|
|
31
|
+
allowNone: dagNode.fork.allow_none,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import type { PipelineStageYamlEntry, ResolvedPipelineDag } from "../types/pipeline.js";
|
|
1
|
+
import type { PipelineStageRef, PipelineStageYamlEntry, ResolvedPipelineDag } from "../types/pipeline.js";
|
|
2
2
|
export type ResolvePipelineDagContext = {
|
|
3
3
|
pipelineId: string;
|
|
4
4
|
path: string;
|
|
5
5
|
};
|
|
6
6
|
export declare function parsePipelineStageEntries(raw: unknown, ctx: ResolvePipelineDagContext): PipelineStageYamlEntry[];
|
|
7
|
+
export declare function resolvePipelineDagFromRefs(refs: PipelineStageRef[], ctx: ResolvePipelineDagContext): {
|
|
8
|
+
stages: string[];
|
|
9
|
+
dag: ResolvedPipelineDag;
|
|
10
|
+
};
|
|
7
11
|
export declare function resolvePipelineDag(rawStages: unknown, ctx: ResolvePipelineDagContext): {
|
|
8
12
|
stages: string[];
|
|
9
13
|
dag: ResolvedPipelineDag;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { isAllowedPipelineStageEntryKey } from "./pipelineStageKeys.js";
|
|
2
|
+
const ALLOWED_FORK_KEYS = new Set(["select", "allow_none"]);
|
|
2
3
|
function pipelineLabel(ctx) {
|
|
3
4
|
return `Pipeline ${ctx.pipelineId} (${ctx.path})`;
|
|
4
5
|
}
|
|
@@ -19,26 +20,45 @@ export function parsePipelineStageEntries(raw, ctx) {
|
|
|
19
20
|
for (let index = 0; index < raw.length; index++) {
|
|
20
21
|
const entry = raw[index];
|
|
21
22
|
if (typeof entry === "string") {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
continue;
|
|
23
|
+
const hint = entry
|
|
24
|
+
? `invalid stage entry at index ${index}: bare string stage refs are not supported; use { id: "${entry}", uses: "./${entry}.yaml" } or inline body`
|
|
25
|
+
: `invalid stage entry at index ${index}: bare string stage refs are not supported; use { id: "…", uses: "./….yaml" } or inline body`;
|
|
26
|
+
throw new Error(formatError(ctx, hint));
|
|
27
27
|
}
|
|
28
28
|
if (!isPlainObject(entry)) {
|
|
29
29
|
throw new Error(formatError(ctx, `invalid stage entry at index ${index}`));
|
|
30
30
|
}
|
|
31
31
|
const keys = Object.keys(entry);
|
|
32
32
|
for (const key of keys) {
|
|
33
|
-
if (!
|
|
33
|
+
if (!isAllowedPipelineStageEntryKey(key)) {
|
|
34
34
|
throw new Error(formatError(ctx, `invalid stage entry "${String(entry.id ?? index)}": unknown key "${key}"`));
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
if (typeof entry.id !== "string" || !entry.id) {
|
|
38
38
|
throw new Error(formatError(ctx, `invalid stage entry at index ${index}: id must be a non-empty string`));
|
|
39
39
|
}
|
|
40
|
+
let forkValue;
|
|
41
|
+
if (entry.fork !== undefined) {
|
|
42
|
+
if (!isPlainObject(entry.fork)) {
|
|
43
|
+
throw new Error(formatError(ctx, `stage "${entry.id}": fork must be an object`));
|
|
44
|
+
}
|
|
45
|
+
for (const fk of Object.keys(entry.fork)) {
|
|
46
|
+
if (!ALLOWED_FORK_KEYS.has(fk)) {
|
|
47
|
+
throw new Error(formatError(ctx, `stage "${entry.id}": fork: unknown key "${fk}"`));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
forkValue = entry.fork;
|
|
51
|
+
}
|
|
52
|
+
const clonableFields = {
|
|
53
|
+
...(entry.clonable !== undefined ? { clonable: entry.clonable } : {}),
|
|
54
|
+
...(entry.clone_cap !== undefined ? { clone_cap: entry.clone_cap } : {}),
|
|
55
|
+
};
|
|
40
56
|
if (entry.needs === undefined) {
|
|
41
|
-
entries.push({
|
|
57
|
+
entries.push({
|
|
58
|
+
id: entry.id,
|
|
59
|
+
...(forkValue !== undefined ? { fork: forkValue } : {}),
|
|
60
|
+
...clonableFields,
|
|
61
|
+
});
|
|
42
62
|
continue;
|
|
43
63
|
}
|
|
44
64
|
if (Array.isArray(entry.needs)) {
|
|
@@ -50,29 +70,24 @@ export function parsePipelineStageEntries(raw, ctx) {
|
|
|
50
70
|
if (typeof entry.needs !== "string" || !entry.needs) {
|
|
51
71
|
throw new Error(formatError(ctx, `stage "${entry.id}": needs must be a non-empty string`));
|
|
52
72
|
}
|
|
53
|
-
entries.push({
|
|
73
|
+
entries.push({
|
|
74
|
+
id: entry.id,
|
|
75
|
+
needs: entry.needs,
|
|
76
|
+
...(forkValue !== undefined ? { fork: forkValue } : {}),
|
|
77
|
+
...clonableFields,
|
|
78
|
+
});
|
|
54
79
|
}
|
|
55
80
|
return entries;
|
|
56
81
|
}
|
|
57
82
|
function normalizeToEdges(entries) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
});
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
edges.push({
|
|
70
|
-
id: entry.id,
|
|
71
|
-
needs: entry.needs ?? null,
|
|
72
|
-
stageIndex: index,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
return edges;
|
|
83
|
+
return entries.map((entry, index) => ({
|
|
84
|
+
id: entry.id,
|
|
85
|
+
needs: entry.needs ?? null,
|
|
86
|
+
stageIndex: index,
|
|
87
|
+
...(entry.fork !== undefined ? { fork: entry.fork } : {}),
|
|
88
|
+
...(entry.clonable !== undefined ? { clonable: entry.clonable } : {}),
|
|
89
|
+
...(entry.clone_cap !== undefined ? { clone_cap: entry.clone_cap } : {}),
|
|
90
|
+
}));
|
|
76
91
|
}
|
|
77
92
|
function detectDuplicateIds(edges, ctx) {
|
|
78
93
|
const seen = new Set();
|
|
@@ -160,7 +175,7 @@ function topologicalSort(edges) {
|
|
|
160
175
|
indegree.set(edge.id, (indegree.get(edge.id) ?? 0) + 1);
|
|
161
176
|
children.get(edge.needs)?.push(edge.id);
|
|
162
177
|
}
|
|
163
|
-
for (const [
|
|
178
|
+
for (const [, childIds] of children) {
|
|
164
179
|
childIds.sort((a, b) => (byId.get(a)?.stageIndex ?? 0) - (byId.get(b)?.stageIndex ?? 0));
|
|
165
180
|
}
|
|
166
181
|
const roots = edges
|
|
@@ -208,12 +223,44 @@ function buildResolvedPipelineDag(edges) {
|
|
|
208
223
|
needs: edge.needs,
|
|
209
224
|
ancestors: ancestorsById.get(edge.id) ?? [],
|
|
210
225
|
stageIndex: edge.stageIndex,
|
|
226
|
+
...(edge.fork !== undefined
|
|
227
|
+
? { fork: { select: edge.fork.select, allow_none: edge.fork.allow_none ?? false } }
|
|
228
|
+
: {}),
|
|
229
|
+
...(edge.clonable === true
|
|
230
|
+
? { clonable: true, clone_cap: edge.clone_cap ?? 5 }
|
|
231
|
+
: {}),
|
|
211
232
|
}));
|
|
212
233
|
return { nodes, roots, childrenOf };
|
|
213
234
|
}
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
235
|
+
function validateForkFields(edges, dag, ctx) {
|
|
236
|
+
for (const edge of edges) {
|
|
237
|
+
if (!edge.fork)
|
|
238
|
+
continue;
|
|
239
|
+
if (edge.fork.select !== "one" && edge.fork.select !== "subset") {
|
|
240
|
+
throw new Error(formatError(ctx, `stage "${edge.id}": fork.select must be "one" or "subset"${edge.fork.select === undefined ? " (missing)" : `, got "${String(edge.fork.select)}"`}`));
|
|
241
|
+
}
|
|
242
|
+
if ((dag.childrenOf[edge.id] ?? []).length === 0) {
|
|
243
|
+
throw new Error(formatError(ctx, `fork on stage "${edge.id}": no children in the DAG`));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function validateClonableFields(edges, dag, ctx) {
|
|
248
|
+
for (const edge of edges) {
|
|
249
|
+
if (edge.clone_cap !== undefined && edge.clonable !== true) {
|
|
250
|
+
throw new Error(formatError(ctx, `stage "${edge.id}": clone_cap requires clonable: true`));
|
|
251
|
+
}
|
|
252
|
+
if (edge.clonable === true && edge.clone_cap !== undefined) {
|
|
253
|
+
if (!Number.isInteger(edge.clone_cap) || edge.clone_cap < 2) {
|
|
254
|
+
throw new Error(formatError(ctx, `stage "${edge.id}": clone_cap must be an integer greater than or equal to 2`));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (edge.clonable === true && (dag.childrenOf[edge.id] ?? []).length === 0) {
|
|
258
|
+
throw new Error(formatError(ctx, `clonable on stage "${edge.id}": no children in the DAG`));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
export function resolvePipelineDagFromRefs(refs, ctx) {
|
|
263
|
+
const edges = normalizeToEdges(refs);
|
|
217
264
|
detectDuplicateIds(edges, ctx);
|
|
218
265
|
validateNeedsTargets(edges, ctx);
|
|
219
266
|
detectCycle(edges, ctx);
|
|
@@ -222,8 +269,14 @@ export function resolvePipelineDag(rawStages, ctx) {
|
|
|
222
269
|
.sort((a, b) => a.stageIndex - b.stageIndex)
|
|
223
270
|
.map((edge) => edge.id);
|
|
224
271
|
const dag = buildResolvedPipelineDag(edges);
|
|
272
|
+
validateForkFields(edges, dag, ctx);
|
|
273
|
+
validateClonableFields(edges, dag, ctx);
|
|
225
274
|
return { stages, dag };
|
|
226
275
|
}
|
|
276
|
+
export function resolvePipelineDag(rawStages, ctx) {
|
|
277
|
+
const entries = parsePipelineStageEntries(rawStages, ctx);
|
|
278
|
+
return resolvePipelineDagFromRefs(entries, ctx);
|
|
279
|
+
}
|
|
227
280
|
export function areResolvedDagsEquivalent(a, b) {
|
|
228
281
|
const nodeIdsA = new Set(a.nodes.map((node) => node.id));
|
|
229
282
|
const nodeIdsB = new Set(b.nodes.map((node) => node.id));
|
|
@@ -256,16 +309,13 @@ export function extractPipelineStageIds(rawStages) {
|
|
|
256
309
|
const stageIds = [];
|
|
257
310
|
for (const entry of rawStages) {
|
|
258
311
|
if (typeof entry === "string") {
|
|
259
|
-
|
|
260
|
-
return null;
|
|
261
|
-
stageIds.push(entry);
|
|
262
|
-
continue;
|
|
312
|
+
return null;
|
|
263
313
|
}
|
|
264
314
|
if (!isPlainObject(entry))
|
|
265
315
|
return null;
|
|
266
316
|
const keys = Object.keys(entry);
|
|
267
317
|
for (const key of keys) {
|
|
268
|
-
if (!
|
|
318
|
+
if (!isAllowedPipelineStageEntryKey(key))
|
|
269
319
|
return null;
|
|
270
320
|
}
|
|
271
321
|
if (typeof entry.id !== "string" || !entry.id)
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { LoadedManifest } from "../types/stageflowManifest.js";
|
|
2
|
+
export type CatalogPathKind = "pipeline" | "task";
|
|
3
|
+
export declare function isExcluded(repoRelPath: string, exclude: string[] | undefined): boolean;
|
|
4
|
+
export declare function scanCatalogPaths(manifest: LoadedManifest, kind: CatalogPathKind): Promise<string[]>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { readdir, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
function normalizeRepoRel(repoRelPath) {
|
|
4
|
+
return repoRelPath.replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
5
|
+
}
|
|
6
|
+
export function isExcluded(repoRelPath, exclude) {
|
|
7
|
+
if (!exclude || exclude.length === 0) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const normalized = normalizeRepoRel(repoRelPath);
|
|
11
|
+
for (const entry of exclude) {
|
|
12
|
+
const prefix = normalizeRepoRel(entry);
|
|
13
|
+
if (normalized === prefix || normalized.startsWith(`${prefix}/`)) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
function globPatternToRegExp(pattern) {
|
|
20
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
21
|
+
return new RegExp(`^${escaped}$`);
|
|
22
|
+
}
|
|
23
|
+
function matchesPattern(basename, pattern) {
|
|
24
|
+
return globPatternToRegExp(pattern).test(basename);
|
|
25
|
+
}
|
|
26
|
+
async function walkDirectory(absDir) {
|
|
27
|
+
const files = [];
|
|
28
|
+
let entries;
|
|
29
|
+
try {
|
|
30
|
+
entries = await readdir(absDir, { withFileTypes: true, recursive: true });
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return files;
|
|
34
|
+
}
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
if (!entry.isFile())
|
|
37
|
+
continue;
|
|
38
|
+
const parentDir = entry.parentPath ?? absDir;
|
|
39
|
+
files.push(path.join(parentDir, entry.name));
|
|
40
|
+
}
|
|
41
|
+
return files;
|
|
42
|
+
}
|
|
43
|
+
async function expandCatalogEntry(projectRoot, entry, kind, pattern) {
|
|
44
|
+
const absPath = path.join(projectRoot, entry);
|
|
45
|
+
let entryStat;
|
|
46
|
+
try {
|
|
47
|
+
entryStat = await stat(absPath);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
if (entryStat.isFile()) {
|
|
53
|
+
return [absPath];
|
|
54
|
+
}
|
|
55
|
+
if (!entryStat.isDirectory()) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
const files = await walkDirectory(absPath);
|
|
59
|
+
return files.filter((filePath) => matchesPattern(path.basename(filePath), pattern));
|
|
60
|
+
}
|
|
61
|
+
export async function scanCatalogPaths(manifest, kind) {
|
|
62
|
+
const { projectRoot, manifest: doc, patterns } = manifest;
|
|
63
|
+
const entries = kind === "pipeline" ? doc.catalog.pipelines : doc.catalog.tasks;
|
|
64
|
+
const pattern = kind === "pipeline" ? patterns.pipeline : patterns.task;
|
|
65
|
+
const exclude = doc.catalog.exclude;
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
const resolved = [];
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
const absPaths = await expandCatalogEntry(projectRoot, entry, kind, pattern);
|
|
70
|
+
for (const absPath of absPaths) {
|
|
71
|
+
const normalized = path.resolve(absPath);
|
|
72
|
+
if (seen.has(normalized))
|
|
73
|
+
continue;
|
|
74
|
+
const repoRel = normalizeRepoRel(path.relative(projectRoot, normalized));
|
|
75
|
+
if (isExcluded(repoRel, exclude))
|
|
76
|
+
continue;
|
|
77
|
+
seen.add(normalized);
|
|
78
|
+
resolved.push(normalized);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
resolved.sort((a, b) => a.localeCompare(b));
|
|
82
|
+
return resolved;
|
|
83
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import type { LoadedPipeline } from "../types/pipeline.js";
|
|
1
2
|
import type { LoadIssue } from "./loadOutcome.js";
|
|
2
3
|
export type ValidationSeverity = "error" | "warning";
|
|
3
|
-
export type ValidationCategory = "pipeline" | "stage" | "catalog";
|
|
4
|
-
export type ValidationFindingCode = "pipeline.invalid_shape" | "pipeline.dag_error" | "pipeline.missing_stage" | "pipeline.load_error" | "stage.invalid_shape" | "stage.invalid_payload_schema" | "stage.invalid_gate_kinds" | "stage.invalid_skill" | "stage.load_error" | "stage.id_filename_mismatch" | "catalog.duplicate_pipeline_id" | "catalog.
|
|
4
|
+
export type ValidationCategory = "pipeline" | "stage" | "catalog" | "task";
|
|
5
|
+
export type ValidationFindingCode = "pipeline.invalid_shape" | "pipeline.dag_error" | "pipeline.missing_stage" | "pipeline.load_error" | "pipeline.string_stage_ref" | "pipeline.stage_uses_inline_conflict" | "pipeline.stage_missing_body" | "pipeline.stage_id_mismatch" | "pipeline.include_cycle" | "pipeline.include_invalid" | "pipeline.include_duplicate_stage" | "stage.invalid_shape" | "stage.invalid_payload_schema" | "stage.invalid_gate_kinds" | "stage.invalid_skill" | "stage.load_error" | "stage.id_filename_mismatch" | "task.invalid_shape" | "task.load_error" | "catalog.duplicate_pipeline_id" | "catalog.manifest_missing" | "catalog.manifest_invalid" | "catalog.empty_catalog" | "catalog.manifest_load_error";
|
|
5
6
|
export type ValidationFinding = {
|
|
6
7
|
severity: ValidationSeverity;
|
|
7
8
|
code: ValidationFindingCode | string;
|
|
@@ -11,12 +12,12 @@ export type ValidationFinding = {
|
|
|
11
12
|
pipelineId?: string;
|
|
12
13
|
stageId?: string;
|
|
13
14
|
};
|
|
14
|
-
export type ValidationScope = "full" | "pipeline";
|
|
15
|
+
export type ValidationScope = "full" | "pipeline" | "task";
|
|
15
16
|
export type ValidateCatalogOptions = {
|
|
16
17
|
cwd?: string;
|
|
17
|
-
stagesDir?: string;
|
|
18
18
|
scope: ValidationScope;
|
|
19
19
|
pipeline?: string;
|
|
20
|
+
task?: string;
|
|
20
21
|
strict?: boolean;
|
|
21
22
|
};
|
|
22
23
|
export type ValidationResult = {
|
|
@@ -33,4 +34,21 @@ export declare function effectiveSeverity(finding: ValidationFinding, strict: bo
|
|
|
33
34
|
export declare function buildValidationResult(scope: ValidationScope, findings: ValidationFinding[], strict: boolean): ValidationResult;
|
|
34
35
|
export declare function findingsFromLoadIssues(cwd: string, absPath: string, issues: LoadIssue[]): ValidationFinding[];
|
|
35
36
|
export declare function findingStageIdFilenameMismatch(cwd: string, absPath: string, fileStem: string, declaredId: string): ValidationFinding;
|
|
37
|
+
export type ValidatePipelineOptions = {
|
|
38
|
+
cwd?: string;
|
|
39
|
+
validateStages?: boolean;
|
|
40
|
+
strict?: boolean;
|
|
41
|
+
};
|
|
42
|
+
export declare function validatePipeline(nameOrPath: string, options?: ValidatePipelineOptions): Promise<ValidationResult>;
|
|
43
|
+
export type LoadPipelineValidatedResult = {
|
|
44
|
+
ok: true;
|
|
45
|
+
loaded: LoadedPipeline;
|
|
46
|
+
} | {
|
|
47
|
+
ok: false;
|
|
48
|
+
findings: ValidationFinding[];
|
|
49
|
+
};
|
|
50
|
+
export declare function loadPipelineValidated(nameOrPath: string, options?: {
|
|
51
|
+
cwd?: string;
|
|
52
|
+
validateStages?: boolean;
|
|
53
|
+
}): Promise<LoadPipelineValidatedResult>;
|
|
36
54
|
export declare function validateCatalog(options: ValidateCatalogOptions): Promise<ValidationResult>;
|