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
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { access, mkdir, readdir, writeFile } from "node:fs/promises";
|
|
1
|
+
import { access, mkdir, readdir, unlink, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { STAGE_ID_PATTERN } from "./createStage.js";
|
|
4
|
-
import {
|
|
5
|
-
import { loadStage } from "./loadStage.js";
|
|
4
|
+
import { inferIdFromUsesPath, normalizePipelineStageEntries, toWiringRefs, } from "./normalizePipelineStageEntry.js";
|
|
6
5
|
import { readYamlObject } from "./readYamlObject.js";
|
|
7
|
-
import {
|
|
6
|
+
import { resolvePipelineDagFromRefs } from "./resolvePipelineDag.js";
|
|
7
|
+
import { validatePipeline } from "./validateCatalog.js";
|
|
8
|
+
import { loadPipelineOutcome } from "./loadPipeline.js";
|
|
8
9
|
function isPlainObject(value) {
|
|
9
10
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
10
11
|
}
|
|
@@ -17,24 +18,136 @@ function validatePipelineId(id) {
|
|
|
17
18
|
}
|
|
18
19
|
return null;
|
|
19
20
|
}
|
|
21
|
+
function defaultUsesPath(stageId) {
|
|
22
|
+
return `./${stageId}.yaml`;
|
|
23
|
+
}
|
|
20
24
|
export function normalizeCreatePipelineStages(stages) {
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
return stages.map((stage) => ({
|
|
26
|
+
...stage,
|
|
27
|
+
uses: stage.uses ?? (stage.inline ? undefined : defaultUsesPath(stage.id)),
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
function stageRefToRaw(ref) {
|
|
31
|
+
const raw = { id: ref.id };
|
|
32
|
+
if (ref.needs !== undefined)
|
|
33
|
+
raw.needs = ref.needs;
|
|
34
|
+
if (ref.uses !== undefined)
|
|
35
|
+
raw.uses = ref.uses;
|
|
36
|
+
if (ref.inline) {
|
|
37
|
+
raw.system_prompt = ref.inline.system_prompt;
|
|
38
|
+
raw.model = ref.inline.model;
|
|
39
|
+
if (ref.inline.gate_kinds !== undefined) {
|
|
40
|
+
raw.gate_kinds = ref.inline.gate_kinds;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return raw;
|
|
44
|
+
}
|
|
45
|
+
function toAuthoringRawEntries(stages, declaringPath) {
|
|
46
|
+
return stages.map((stage) => ({
|
|
47
|
+
raw: stageRefToRaw(stage),
|
|
48
|
+
declaringPath,
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
function normalizedToCreateRefs(entries) {
|
|
52
|
+
return entries.map((entry) => {
|
|
53
|
+
const ref = { id: entry.id };
|
|
54
|
+
if (entry.needs !== undefined)
|
|
55
|
+
ref.needs = entry.needs;
|
|
56
|
+
if (entry.body.kind === "uses") {
|
|
57
|
+
ref.uses = entry.body.path;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const raw = entry.body.raw;
|
|
61
|
+
ref.inline = {
|
|
62
|
+
system_prompt: String(raw.system_prompt ?? ""),
|
|
63
|
+
model: String(raw.model ?? ""),
|
|
64
|
+
...(Array.isArray(raw.gate_kinds)
|
|
65
|
+
? { gate_kinds: raw.gate_kinds }
|
|
66
|
+
: {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return ref;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function normalizeAuthoringStages(stages, ctx) {
|
|
73
|
+
const prepared = normalizeCreatePipelineStages(stages);
|
|
74
|
+
const normalizeOutcome = normalizePipelineStageEntries(toAuthoringRawEntries(prepared, ctx.path), ctx);
|
|
75
|
+
if (!normalizeOutcome.ok) {
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
status: 422,
|
|
79
|
+
error: normalizeOutcome.issues[0]?.message ?? "Invalid pipeline stages",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
resolvePipelineDagFromRefs(toWiringRefs(normalizeOutcome.value), ctx);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
87
|
+
return { ok: false, status: 422, error: message };
|
|
88
|
+
}
|
|
89
|
+
return { ok: true, stages: normalizedToCreateRefs(normalizeOutcome.value) };
|
|
90
|
+
}
|
|
91
|
+
function parseInlineBody(entry, index) {
|
|
92
|
+
if (typeof entry.system_prompt !== "string" || entry.system_prompt.length === 0) {
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
status: 400,
|
|
96
|
+
error: `stages[${index}].system_prompt is required for inline stage`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (typeof entry.model !== "string" || entry.model.length === 0) {
|
|
100
|
+
return {
|
|
101
|
+
ok: false,
|
|
102
|
+
status: 400,
|
|
103
|
+
error: `stages[${index}].model is required for inline stage`,
|
|
104
|
+
};
|
|
23
105
|
}
|
|
24
|
-
|
|
106
|
+
const inline = {
|
|
107
|
+
system_prompt: entry.system_prompt,
|
|
108
|
+
model: entry.model,
|
|
109
|
+
};
|
|
110
|
+
if (entry.gate_kinds !== undefined) {
|
|
111
|
+
if (!Array.isArray(entry.gate_kinds)) {
|
|
112
|
+
return {
|
|
113
|
+
ok: false,
|
|
114
|
+
status: 400,
|
|
115
|
+
error: `stages[${index}].gate_kinds must be an array`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
inline.gate_kinds = entry.gate_kinds;
|
|
119
|
+
}
|
|
120
|
+
return inline;
|
|
25
121
|
}
|
|
26
122
|
function parseStageEntry(entry, index) {
|
|
123
|
+
if (typeof entry === "string") {
|
|
124
|
+
const hint = entry
|
|
125
|
+
? `bare string stage refs are not supported; use { id: "${entry}", uses: "./${entry}.yaml" } or inline body`
|
|
126
|
+
: `bare string stage refs are not supported; use { id: "…", uses: "./….yaml" } or inline body`;
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
status: 400,
|
|
130
|
+
error: `stages[${index}]: ${hint}`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
27
133
|
if (!isPlainObject(entry)) {
|
|
28
134
|
return {
|
|
29
135
|
ok: false,
|
|
30
136
|
status: 400,
|
|
31
|
-
error: `stages[${index}] must be
|
|
137
|
+
error: `stages[${index}] must be an object with id`,
|
|
32
138
|
};
|
|
33
139
|
}
|
|
34
|
-
|
|
140
|
+
let id = typeof entry.id === "string" ? entry.id : undefined;
|
|
141
|
+
const uses = typeof entry.uses === "string" && entry.uses.trim() ? entry.uses : undefined;
|
|
142
|
+
if (!id && uses) {
|
|
143
|
+
const inferred = inferIdFromUsesPath(uses);
|
|
144
|
+
if (inferred)
|
|
145
|
+
id = inferred;
|
|
146
|
+
}
|
|
147
|
+
if (!id) {
|
|
35
148
|
return { ok: false, status: 400, error: `stages[${index}].id is required` };
|
|
36
149
|
}
|
|
37
|
-
const ref = { id
|
|
150
|
+
const ref = { id };
|
|
38
151
|
if (entry.needs !== undefined) {
|
|
39
152
|
if (typeof entry.needs !== "string") {
|
|
40
153
|
return {
|
|
@@ -45,12 +158,45 @@ function parseStageEntry(entry, index) {
|
|
|
45
158
|
}
|
|
46
159
|
ref.needs = entry.needs;
|
|
47
160
|
}
|
|
161
|
+
if (entry.uses !== undefined) {
|
|
162
|
+
if (typeof entry.uses !== "string" || !entry.uses.trim()) {
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
status: 400,
|
|
166
|
+
error: `stages[${index}].uses must be a non-empty string`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
ref.uses = entry.uses;
|
|
170
|
+
}
|
|
171
|
+
else if (uses) {
|
|
172
|
+
ref.uses = uses;
|
|
173
|
+
}
|
|
174
|
+
const hasInlineFields = entry.system_prompt !== undefined ||
|
|
175
|
+
entry.model !== undefined ||
|
|
176
|
+
entry.gate_kinds !== undefined;
|
|
177
|
+
if (hasInlineFields) {
|
|
178
|
+
const inline = parseInlineBody(entry, index);
|
|
179
|
+
if ("ok" in inline)
|
|
180
|
+
return inline;
|
|
181
|
+
ref.inline = inline;
|
|
182
|
+
}
|
|
48
183
|
return ref;
|
|
49
184
|
}
|
|
50
185
|
export function parseCreatePipelineBody(body) {
|
|
51
186
|
if (!isPlainObject(body)) {
|
|
52
187
|
return { ok: false, status: 400, error: "Request body must be an object" };
|
|
53
188
|
}
|
|
189
|
+
if (typeof body.directory !== "string" || !body.directory.trim()) {
|
|
190
|
+
return { ok: false, status: 400, error: "directory is required" };
|
|
191
|
+
}
|
|
192
|
+
const directory = body.directory.trim().replace(/\\/g, "/");
|
|
193
|
+
if (directory.endsWith(".yaml") || directory.endsWith(".yml")) {
|
|
194
|
+
return {
|
|
195
|
+
ok: false,
|
|
196
|
+
status: 400,
|
|
197
|
+
error: "directory must be a catalog folder, not a pipeline file path",
|
|
198
|
+
};
|
|
199
|
+
}
|
|
54
200
|
if (typeof body.id !== "string") {
|
|
55
201
|
return { ok: false, status: 400, error: "id is required" };
|
|
56
202
|
}
|
|
@@ -64,32 +210,6 @@ export function parseCreatePipelineBody(body) {
|
|
|
64
210
|
if (body.stages.length === 0) {
|
|
65
211
|
return { ok: false, status: 400, error: "stages must be a non-empty array" };
|
|
66
212
|
}
|
|
67
|
-
let hasString = false;
|
|
68
|
-
let hasObject = false;
|
|
69
|
-
for (const entry of body.stages) {
|
|
70
|
-
if (typeof entry === "string") {
|
|
71
|
-
hasString = true;
|
|
72
|
-
}
|
|
73
|
-
else if (isPlainObject(entry)) {
|
|
74
|
-
hasObject = true;
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
return { ok: false, status: 400, error: "stages must be an array of strings" };
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (hasString && hasObject) {
|
|
81
|
-
return {
|
|
82
|
-
ok: false,
|
|
83
|
-
status: 400,
|
|
84
|
-
error: "stages must be either all strings or all objects",
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
if (hasString) {
|
|
88
|
-
return {
|
|
89
|
-
id: body.id,
|
|
90
|
-
stages: body.stages,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
213
|
const refs = [];
|
|
94
214
|
for (let index = 0; index < body.stages.length; index++) {
|
|
95
215
|
const parsed = parseStageEntry(body.stages[index], index);
|
|
@@ -99,23 +219,54 @@ export function parseCreatePipelineBody(body) {
|
|
|
99
219
|
refs.push(parsed);
|
|
100
220
|
}
|
|
101
221
|
return {
|
|
222
|
+
directory,
|
|
102
223
|
id: body.id,
|
|
103
224
|
stages: refs,
|
|
104
225
|
};
|
|
105
226
|
}
|
|
227
|
+
function formatInlineYamlScalar(value) {
|
|
228
|
+
if (value.includes("\n") ||
|
|
229
|
+
/[:#"'&*!|>@`[\]{}]/.test(value) ||
|
|
230
|
+
value.startsWith(" ") ||
|
|
231
|
+
value.endsWith(" ") ||
|
|
232
|
+
value.startsWith("-") ||
|
|
233
|
+
value.startsWith("?")) {
|
|
234
|
+
return JSON.stringify(value);
|
|
235
|
+
}
|
|
236
|
+
return value;
|
|
237
|
+
}
|
|
106
238
|
export function pipelineConfigToYaml(pipeline, options) {
|
|
107
239
|
const lines = [`id: ${pipeline.id}`, "stages:"];
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
lines.push(` - ${stage.id}`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
for (const stage of pipeline.stages) {
|
|
240
|
+
for (const stage of pipeline.stages) {
|
|
241
|
+
if (options.format === "linear" && !stage.needs && !stage.inline && stage.uses) {
|
|
115
242
|
lines.push(` - id: ${stage.id}`);
|
|
116
|
-
|
|
117
|
-
|
|
243
|
+
lines.push(` uses: ${stage.uses}`);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
lines.push(` - id: ${stage.id}`);
|
|
247
|
+
if (stage.needs) {
|
|
248
|
+
lines.push(` needs: ${stage.needs}`);
|
|
249
|
+
}
|
|
250
|
+
if (stage.inline) {
|
|
251
|
+
if (stage.inline.gate_kinds && stage.inline.gate_kinds.length > 0) {
|
|
252
|
+
lines.push(" gate_kinds:");
|
|
253
|
+
for (const kind of stage.inline.gate_kinds) {
|
|
254
|
+
lines.push(` - ${kind}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (stage.inline.system_prompt.includes("\n")) {
|
|
258
|
+
lines.push(" system_prompt: |");
|
|
259
|
+
for (const line of stage.inline.system_prompt.split("\n")) {
|
|
260
|
+
lines.push(` ${line}`);
|
|
261
|
+
}
|
|
118
262
|
}
|
|
263
|
+
else {
|
|
264
|
+
lines.push(` system_prompt: ${formatInlineYamlScalar(stage.inline.system_prompt)}`);
|
|
265
|
+
}
|
|
266
|
+
lines.push(` model: ${formatInlineYamlScalar(stage.inline.model)}`);
|
|
267
|
+
}
|
|
268
|
+
else if (stage.uses) {
|
|
269
|
+
lines.push(` uses: ${stage.uses}`);
|
|
119
270
|
}
|
|
120
271
|
}
|
|
121
272
|
lines.push("");
|
|
@@ -130,14 +281,21 @@ async function fileExists(filePath) {
|
|
|
130
281
|
return false;
|
|
131
282
|
}
|
|
132
283
|
}
|
|
133
|
-
|
|
284
|
+
function resolveDirectory(projectRoot, directory) {
|
|
285
|
+
const absDirectory = path.resolve(projectRoot, directory);
|
|
286
|
+
const rel = path.relative(projectRoot, absDirectory);
|
|
287
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
return absDirectory;
|
|
291
|
+
}
|
|
292
|
+
async function findPipelineIdCollision(pipelineDirectory, projectRoot, id, targetPath) {
|
|
134
293
|
if (await fileExists(targetPath)) {
|
|
135
|
-
return path.relative(
|
|
294
|
+
return path.relative(projectRoot, targetPath);
|
|
136
295
|
}
|
|
137
|
-
const dir = path.join(cwd, "pipelines");
|
|
138
296
|
let entries;
|
|
139
297
|
try {
|
|
140
|
-
entries = await readdir(
|
|
298
|
+
entries = await readdir(pipelineDirectory, { withFileTypes: true });
|
|
141
299
|
}
|
|
142
300
|
catch {
|
|
143
301
|
return null;
|
|
@@ -145,13 +303,13 @@ async function findPipelineIdCollision(cwd, id, targetPath) {
|
|
|
145
303
|
for (const entry of entries) {
|
|
146
304
|
if (!entry.isFile() || !entry.name.endsWith(".yaml"))
|
|
147
305
|
continue;
|
|
148
|
-
const filePath = path.join(
|
|
306
|
+
const filePath = path.join(pipelineDirectory, entry.name);
|
|
149
307
|
if (filePath === targetPath)
|
|
150
308
|
continue;
|
|
151
309
|
try {
|
|
152
310
|
const raw = await readYamlObject(filePath);
|
|
153
311
|
if (raw?.id === id) {
|
|
154
|
-
return path.relative(
|
|
312
|
+
return path.relative(projectRoot, filePath);
|
|
155
313
|
}
|
|
156
314
|
}
|
|
157
315
|
catch {
|
|
@@ -160,7 +318,7 @@ async function findPipelineIdCollision(cwd, id, targetPath) {
|
|
|
160
318
|
}
|
|
161
319
|
return null;
|
|
162
320
|
}
|
|
163
|
-
async function validateStageReferences(
|
|
321
|
+
async function validateStageReferences(pipelineDirectory, stages) {
|
|
164
322
|
const seen = new Set();
|
|
165
323
|
for (const stage of stages) {
|
|
166
324
|
if (seen.has(stage.id)) {
|
|
@@ -172,44 +330,77 @@ async function validateStageReferences(cwd, stages) {
|
|
|
172
330
|
}
|
|
173
331
|
seen.add(stage.id);
|
|
174
332
|
}
|
|
175
|
-
const
|
|
333
|
+
const inlineCount = stages.filter((stage) => stage.inline).length;
|
|
334
|
+
if (inlineCount > 0 && stages.length > 1) {
|
|
335
|
+
return {
|
|
336
|
+
ok: false,
|
|
337
|
+
status: 422,
|
|
338
|
+
error: "Inline stage bodies are supported for single-stage pipelines only",
|
|
339
|
+
};
|
|
340
|
+
}
|
|
176
341
|
for (const stage of stages) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
342
|
+
if (stage.inline)
|
|
343
|
+
continue;
|
|
344
|
+
const uses = stage.uses ?? defaultUsesPath(stage.id);
|
|
345
|
+
const stagePath = path.resolve(pipelineDirectory, uses);
|
|
346
|
+
if (!(await fileExists(stagePath))) {
|
|
182
347
|
return {
|
|
183
348
|
ok: false,
|
|
184
349
|
status: 422,
|
|
185
|
-
error:
|
|
350
|
+
error: `missing stage file for "${stage.id}" at ${uses}`,
|
|
186
351
|
};
|
|
187
352
|
}
|
|
188
353
|
}
|
|
189
354
|
return { ok: true };
|
|
190
355
|
}
|
|
191
|
-
function
|
|
192
|
-
if (input.stages.length > 0 && typeof input.stages[0] === "string") {
|
|
193
|
-
return input.stages;
|
|
194
|
-
}
|
|
195
|
-
return normalized.map((ref) => ref.needs ? { id: ref.id, needs: ref.needs } : { id: ref.id });
|
|
196
|
-
}
|
|
197
|
-
function usesDagYaml(input, normalized) {
|
|
198
|
-
if (input.stages.length > 0 && typeof input.stages[0] !== "string") {
|
|
199
|
-
return true;
|
|
200
|
-
}
|
|
356
|
+
function usesDagYaml(normalized) {
|
|
201
357
|
return normalized.some((ref) => ref.needs !== undefined);
|
|
202
358
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
359
|
+
function buildPipelineListing(projectRoot, relPath, loaded) {
|
|
360
|
+
return {
|
|
361
|
+
path: relPath,
|
|
362
|
+
id: loaded.pipeline.id,
|
|
363
|
+
stages: loaded.stages.map((stage) => {
|
|
364
|
+
const source = loaded.stageSources?.[stage.id];
|
|
365
|
+
const listing = {
|
|
366
|
+
id: stage.id,
|
|
367
|
+
...(stage.gate_kinds ? { gate_kinds: stage.gate_kinds } : {}),
|
|
368
|
+
};
|
|
369
|
+
if (source?.kind === "inline") {
|
|
370
|
+
return { ...listing, inline: true };
|
|
371
|
+
}
|
|
372
|
+
if (source?.kind === "file") {
|
|
373
|
+
return {
|
|
374
|
+
...listing,
|
|
375
|
+
uses_path: path.relative(projectRoot, source.path).replace(/\\/g, "/"),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
return listing;
|
|
379
|
+
}),
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
export async function createPipeline(projectRoot, input) {
|
|
383
|
+
const pipelineDirectory = resolveDirectory(projectRoot, input.directory);
|
|
384
|
+
if (!pipelineDirectory) {
|
|
385
|
+
return {
|
|
386
|
+
ok: false,
|
|
387
|
+
status: 400,
|
|
388
|
+
error: "directory must be inside the project root",
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
const filePath = path.join(pipelineDirectory, `${input.id}.pipeline.yaml`);
|
|
392
|
+
const relPath = path.relative(projectRoot, filePath).replace(/\\/g, "/");
|
|
393
|
+
const validationCtx = { pipelineId: input.id, path: relPath };
|
|
394
|
+
const normalizedOutcome = normalizeAuthoringStages(input.stages, validationCtx);
|
|
395
|
+
if (!normalizedOutcome.ok) {
|
|
396
|
+
return normalizedOutcome;
|
|
397
|
+
}
|
|
398
|
+
const normalized = normalizedOutcome.stages;
|
|
399
|
+
const stageValidation = await validateStageReferences(pipelineDirectory, normalized);
|
|
206
400
|
if (!stageValidation.ok) {
|
|
207
401
|
return stageValidation;
|
|
208
402
|
}
|
|
209
|
-
const
|
|
210
|
-
const filePath = path.join(pipelinesDir, `${input.id}.yaml`);
|
|
211
|
-
const relPath = path.relative(cwd, filePath);
|
|
212
|
-
const collision = await findPipelineIdCollision(cwd, input.id, filePath);
|
|
403
|
+
const collision = await findPipelineIdCollision(pipelineDirectory, projectRoot, input.id, filePath);
|
|
213
404
|
if (collision) {
|
|
214
405
|
return {
|
|
215
406
|
ok: false,
|
|
@@ -217,39 +408,40 @@ export async function createPipeline(cwd, input) {
|
|
|
217
408
|
error: `Pipeline id already exists (${collision})`,
|
|
218
409
|
};
|
|
219
410
|
}
|
|
220
|
-
const
|
|
221
|
-
try {
|
|
222
|
-
resolvePipelineDag(resolverInput, { pipelineId: input.id, path: relPath });
|
|
223
|
-
}
|
|
224
|
-
catch (err) {
|
|
225
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
226
|
-
return { ok: false, status: 422, error: message };
|
|
227
|
-
}
|
|
228
|
-
const yamlFormat = usesDagYaml(input, normalized) ? "dag" : "linear";
|
|
411
|
+
const yamlFormat = usesDagYaml(normalized) ? "dag" : "linear";
|
|
229
412
|
try {
|
|
230
|
-
await mkdir(
|
|
413
|
+
await mkdir(pipelineDirectory, { recursive: true });
|
|
231
414
|
await writeFile(filePath, pipelineConfigToYaml({ id: input.id, stages: normalized }, { format: yamlFormat }), "utf8");
|
|
232
415
|
}
|
|
233
416
|
catch (err) {
|
|
234
417
|
const message = err instanceof Error ? err.message : String(err);
|
|
235
418
|
return { ok: false, status: 500, error: message };
|
|
236
419
|
}
|
|
237
|
-
|
|
238
|
-
|
|
420
|
+
const validation = await validatePipeline(relPath, {
|
|
421
|
+
cwd: projectRoot,
|
|
422
|
+
validateStages: true,
|
|
423
|
+
strict: true,
|
|
424
|
+
});
|
|
425
|
+
if (!validation.ok) {
|
|
426
|
+
await unlink(filePath).catch(() => { });
|
|
427
|
+
const errorFinding = validation.findings.find((finding) => finding.severity === "error");
|
|
239
428
|
return {
|
|
240
|
-
ok:
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
id: loaded.pipeline.id,
|
|
244
|
-
stages: loaded.stages.map((stage) => ({
|
|
245
|
-
id: stage.id,
|
|
246
|
-
...(stage.gate_kinds ? { gate_kinds: stage.gate_kinds } : {}),
|
|
247
|
-
})),
|
|
248
|
-
},
|
|
429
|
+
ok: false,
|
|
430
|
+
status: 422,
|
|
431
|
+
error: errorFinding?.message ?? "Pipeline validation failed",
|
|
249
432
|
};
|
|
250
433
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
434
|
+
const loadOutcome = await loadPipelineOutcome(relPath, { cwd: projectRoot });
|
|
435
|
+
if (!loadOutcome.ok) {
|
|
436
|
+
await unlink(filePath).catch(() => { });
|
|
437
|
+
return {
|
|
438
|
+
ok: false,
|
|
439
|
+
status: 500,
|
|
440
|
+
error: loadOutcome.issues[0]?.message ?? "Failed to load created pipeline",
|
|
441
|
+
};
|
|
254
442
|
}
|
|
443
|
+
return {
|
|
444
|
+
ok: true,
|
|
445
|
+
pipeline: buildPipelineListing(projectRoot, relPath, loadOutcome.value),
|
|
446
|
+
};
|
|
255
447
|
}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { type StageGateKind } from "../types/stage.js";
|
|
2
|
-
import type { ValidStageListing } from "./listConfig.js";
|
|
3
2
|
export declare const STAGE_ID_PATTERN: RegExp;
|
|
4
3
|
export type CreateStageInput = {
|
|
4
|
+
pipeline_directory: string;
|
|
5
|
+
filename: string;
|
|
5
6
|
id: string;
|
|
6
7
|
system_prompt: string;
|
|
7
8
|
model: string;
|
|
8
9
|
gate_kinds?: StageGateKind[];
|
|
9
10
|
};
|
|
11
|
+
export type CreatedStageListing = {
|
|
12
|
+
path: string;
|
|
13
|
+
id: string;
|
|
14
|
+
gate_kinds?: StageGateKind[];
|
|
15
|
+
};
|
|
10
16
|
export type CreateStageParseError = {
|
|
11
17
|
ok: false;
|
|
12
18
|
status: 400;
|
|
@@ -14,12 +20,12 @@ export type CreateStageParseError = {
|
|
|
14
20
|
};
|
|
15
21
|
export type CreateStageResult = {
|
|
16
22
|
ok: true;
|
|
17
|
-
stage:
|
|
23
|
+
stage: CreatedStageListing;
|
|
18
24
|
} | {
|
|
19
25
|
ok: false;
|
|
20
26
|
status: 400 | 409 | 500;
|
|
21
27
|
error: string;
|
|
22
28
|
};
|
|
23
29
|
export declare function parseCreateStageBody(body: unknown): CreateStageInput | CreateStageParseError;
|
|
24
|
-
export declare function stageConfigToYaml(stage: CreateStageInput): string;
|
|
25
|
-
export declare function createStage(
|
|
30
|
+
export declare function stageConfigToYaml(stage: Omit<CreateStageInput, "pipeline_directory" | "filename">): string;
|
|
31
|
+
export declare function createStage(projectRoot: string, input: CreateStageInput): Promise<CreateStageResult>;
|
|
@@ -43,6 +43,16 @@ export function parseCreateStageBody(body) {
|
|
|
43
43
|
if ("skill" in body) {
|
|
44
44
|
return { ok: false, status: 400, error: "skill is not supported" };
|
|
45
45
|
}
|
|
46
|
+
if (typeof body.pipeline_directory !== "string" || !body.pipeline_directory.trim()) {
|
|
47
|
+
return { ok: false, status: 400, error: "pipeline_directory is required" };
|
|
48
|
+
}
|
|
49
|
+
if (typeof body.filename !== "string" || !body.filename.trim()) {
|
|
50
|
+
return { ok: false, status: 400, error: "filename is required" };
|
|
51
|
+
}
|
|
52
|
+
const filename = body.filename.trim().replace(/\\/g, "/");
|
|
53
|
+
if (!filename.endsWith(".yaml") && !filename.endsWith(".yml")) {
|
|
54
|
+
return { ok: false, status: 400, error: "filename must end with .yaml" };
|
|
55
|
+
}
|
|
46
56
|
if (typeof body.id !== "string") {
|
|
47
57
|
return { ok: false, status: 400, error: "id is required" };
|
|
48
58
|
}
|
|
@@ -65,6 +75,8 @@ export function parseCreateStageBody(body) {
|
|
|
65
75
|
};
|
|
66
76
|
}
|
|
67
77
|
return {
|
|
78
|
+
pipeline_directory: body.pipeline_directory.trim().replace(/\\/g, "/"),
|
|
79
|
+
filename,
|
|
68
80
|
id: body.id,
|
|
69
81
|
system_prompt: body.system_prompt,
|
|
70
82
|
model: body.model,
|
|
@@ -112,14 +124,21 @@ async function fileExists(filePath) {
|
|
|
112
124
|
return false;
|
|
113
125
|
}
|
|
114
126
|
}
|
|
115
|
-
|
|
127
|
+
function resolvePipelineDirectory(projectRoot, pipelineDirectory) {
|
|
128
|
+
const absDirectory = path.resolve(projectRoot, pipelineDirectory);
|
|
129
|
+
const rel = path.relative(projectRoot, absDirectory);
|
|
130
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
return absDirectory;
|
|
134
|
+
}
|
|
135
|
+
async function findStageIdCollision(pipelineDirectory, projectRoot, id, targetPath) {
|
|
116
136
|
if (await fileExists(targetPath)) {
|
|
117
|
-
return path.relative(
|
|
137
|
+
return path.relative(projectRoot, targetPath);
|
|
118
138
|
}
|
|
119
|
-
const dir = path.join(cwd, "stages");
|
|
120
139
|
let entries;
|
|
121
140
|
try {
|
|
122
|
-
entries = await readdir(
|
|
141
|
+
entries = await readdir(pipelineDirectory, { withFileTypes: true });
|
|
123
142
|
}
|
|
124
143
|
catch {
|
|
125
144
|
return null;
|
|
@@ -127,13 +146,13 @@ async function findStageIdCollision(cwd, id, targetPath) {
|
|
|
127
146
|
for (const entry of entries) {
|
|
128
147
|
if (!entry.isFile() || !entry.name.endsWith(".yaml"))
|
|
129
148
|
continue;
|
|
130
|
-
const filePath = path.join(
|
|
149
|
+
const filePath = path.join(pipelineDirectory, entry.name);
|
|
131
150
|
if (filePath === targetPath)
|
|
132
151
|
continue;
|
|
133
152
|
try {
|
|
134
153
|
const raw = await readYamlObject(filePath);
|
|
135
154
|
if (raw?.id === id) {
|
|
136
|
-
return path.relative(
|
|
155
|
+
return path.relative(projectRoot, filePath);
|
|
137
156
|
}
|
|
138
157
|
}
|
|
139
158
|
catch {
|
|
@@ -142,11 +161,18 @@ async function findStageIdCollision(cwd, id, targetPath) {
|
|
|
142
161
|
}
|
|
143
162
|
return null;
|
|
144
163
|
}
|
|
145
|
-
export async function createStage(
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
164
|
+
export async function createStage(projectRoot, input) {
|
|
165
|
+
const pipelineDirectory = resolvePipelineDirectory(projectRoot, input.pipeline_directory);
|
|
166
|
+
if (!pipelineDirectory) {
|
|
167
|
+
return {
|
|
168
|
+
ok: false,
|
|
169
|
+
status: 400,
|
|
170
|
+
error: "pipeline_directory must be inside the project root",
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const filePath = path.join(pipelineDirectory, path.basename(input.filename));
|
|
174
|
+
const relPath = path.relative(projectRoot, filePath).replace(/\\/g, "/");
|
|
175
|
+
const collision = await findStageIdCollision(pipelineDirectory, projectRoot, input.id, filePath);
|
|
150
176
|
if (collision) {
|
|
151
177
|
return {
|
|
152
178
|
ok: false,
|
|
@@ -155,8 +181,13 @@ export async function createStage(cwd, input) {
|
|
|
155
181
|
};
|
|
156
182
|
}
|
|
157
183
|
try {
|
|
158
|
-
await mkdir(
|
|
159
|
-
await writeFile(filePath, stageConfigToYaml(
|
|
184
|
+
await mkdir(pipelineDirectory, { recursive: true });
|
|
185
|
+
await writeFile(filePath, stageConfigToYaml({
|
|
186
|
+
id: input.id,
|
|
187
|
+
system_prompt: input.system_prompt,
|
|
188
|
+
model: input.model,
|
|
189
|
+
...(input.gate_kinds ? { gate_kinds: input.gate_kinds } : {}),
|
|
190
|
+
}), "utf8");
|
|
160
191
|
}
|
|
161
192
|
catch (err) {
|
|
162
193
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -169,7 +200,6 @@ export async function createStage(cwd, input) {
|
|
|
169
200
|
stage: {
|
|
170
201
|
path: relPath,
|
|
171
202
|
id: stage.id,
|
|
172
|
-
used_by_pipeline_ids: [],
|
|
173
203
|
...(stage.gate_kinds ? { gate_kinds: stage.gate_kinds } : {}),
|
|
174
204
|
},
|
|
175
205
|
};
|