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
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PipelineRunResult } from "../runtime/pipelineRunner.js";
|
|
2
|
+
import type { StartRunResult } from "../runtime/runManager.js";
|
|
3
|
+
export type CliRunReportIo = {
|
|
4
|
+
log: (line: string) => void;
|
|
5
|
+
error: (line: string) => void;
|
|
6
|
+
};
|
|
7
|
+
export type CliRunReportEvent = {
|
|
8
|
+
kind: "start-failure";
|
|
9
|
+
started: Extract<StartRunResult, {
|
|
10
|
+
ok: false;
|
|
11
|
+
}>;
|
|
12
|
+
} | {
|
|
13
|
+
kind: "completion";
|
|
14
|
+
result: PipelineRunResult;
|
|
15
|
+
};
|
|
16
|
+
export declare function reportCliRun(event: CliRunReportEvent, options: {
|
|
17
|
+
json?: boolean;
|
|
18
|
+
io: CliRunReportIo;
|
|
19
|
+
}): number;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
function stringify(payload) {
|
|
2
|
+
return JSON.stringify(payload, null, 2);
|
|
3
|
+
}
|
|
4
|
+
function isBusyCode(code) {
|
|
5
|
+
return code === "busy_capacity" || code === "busy_checkout";
|
|
6
|
+
}
|
|
7
|
+
function formatRunBusyJson(started) {
|
|
8
|
+
const payload = {
|
|
9
|
+
ok: false,
|
|
10
|
+
outcome: "busy",
|
|
11
|
+
};
|
|
12
|
+
if (started.code !== undefined)
|
|
13
|
+
payload.code = started.code;
|
|
14
|
+
payload.reason = started.reason;
|
|
15
|
+
if (started.activeCount !== undefined)
|
|
16
|
+
payload.activeCount = started.activeCount;
|
|
17
|
+
if (started.maxConcurrent !== undefined) {
|
|
18
|
+
payload.maxConcurrent = started.maxConcurrent;
|
|
19
|
+
}
|
|
20
|
+
if (started.activeRunIds !== undefined) {
|
|
21
|
+
payload.activeRunIds = started.activeRunIds;
|
|
22
|
+
}
|
|
23
|
+
if (started.conflictingRunId !== undefined) {
|
|
24
|
+
payload.conflictingRunId = started.conflictingRunId;
|
|
25
|
+
}
|
|
26
|
+
if (started.conflictingCheckout !== undefined) {
|
|
27
|
+
payload.conflictingCheckout = started.conflictingCheckout;
|
|
28
|
+
}
|
|
29
|
+
return stringify(payload);
|
|
30
|
+
}
|
|
31
|
+
function formatRunStartFailedJson(started) {
|
|
32
|
+
const payload = {
|
|
33
|
+
ok: false,
|
|
34
|
+
outcome: "failed",
|
|
35
|
+
reason: started.reason,
|
|
36
|
+
};
|
|
37
|
+
if (started.code !== undefined)
|
|
38
|
+
payload.code = started.code;
|
|
39
|
+
return stringify(payload);
|
|
40
|
+
}
|
|
41
|
+
function formatStartFailureJson(started) {
|
|
42
|
+
if (isBusyCode(started.code)) {
|
|
43
|
+
return formatRunBusyJson(started);
|
|
44
|
+
}
|
|
45
|
+
return formatRunStartFailedJson(started);
|
|
46
|
+
}
|
|
47
|
+
function formatRunCompletionJson(result) {
|
|
48
|
+
if (result.outcome === "succeeded") {
|
|
49
|
+
return stringify({
|
|
50
|
+
ok: true,
|
|
51
|
+
outcome: "succeeded",
|
|
52
|
+
runId: result.runId,
|
|
53
|
+
runDir: result.runDir,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (result.outcome === "waiting") {
|
|
57
|
+
return stringify({
|
|
58
|
+
ok: false,
|
|
59
|
+
outcome: "waiting",
|
|
60
|
+
runId: result.runId,
|
|
61
|
+
runDir: result.runDir,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const payload = {
|
|
65
|
+
ok: false,
|
|
66
|
+
outcome: "failed",
|
|
67
|
+
runId: result.runId,
|
|
68
|
+
runDir: result.runDir,
|
|
69
|
+
};
|
|
70
|
+
if (result.reason !== undefined)
|
|
71
|
+
payload.reason = result.reason;
|
|
72
|
+
return stringify(payload);
|
|
73
|
+
}
|
|
74
|
+
function writeStartFailureHuman(started, io) {
|
|
75
|
+
const code = started.code ?? "error";
|
|
76
|
+
io.error(`${code}: ${started.reason}`);
|
|
77
|
+
if (started.conflictingRunId !== undefined) {
|
|
78
|
+
io.error(`conflictingRunId: ${started.conflictingRunId}`);
|
|
79
|
+
}
|
|
80
|
+
if (started.conflictingCheckout !== undefined) {
|
|
81
|
+
io.error(`conflictingCheckout: ${started.conflictingCheckout}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function writeCompletionHuman(result, io) {
|
|
85
|
+
switch (result.outcome) {
|
|
86
|
+
case "waiting":
|
|
87
|
+
io.error(`Pipeline waiting. Run folder: ${result.runDir} (${result.runId})`);
|
|
88
|
+
return;
|
|
89
|
+
case "failed":
|
|
90
|
+
io.error(`Pipeline failed: ${result.reason}`);
|
|
91
|
+
io.error(`Run folder: ${result.runDir}`);
|
|
92
|
+
return;
|
|
93
|
+
case "succeeded":
|
|
94
|
+
io.log(`Pipeline succeeded. Run folder: ${result.runDir}`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function exitCodeForRunOutcome(outcome) {
|
|
99
|
+
switch (outcome) {
|
|
100
|
+
case "succeeded":
|
|
101
|
+
return 0;
|
|
102
|
+
case "waiting":
|
|
103
|
+
return 2;
|
|
104
|
+
case "failed":
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export function reportCliRun(event, options) {
|
|
109
|
+
if (event.kind === "start-failure") {
|
|
110
|
+
if (options.json) {
|
|
111
|
+
options.io.log(formatStartFailureJson(event.started));
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
writeStartFailureHuman(event.started, options.io);
|
|
115
|
+
}
|
|
116
|
+
return 1;
|
|
117
|
+
}
|
|
118
|
+
if (options.json) {
|
|
119
|
+
options.io.log(formatRunCompletionJson(event.result));
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
writeCompletionHuman(event.result, options.io);
|
|
123
|
+
}
|
|
124
|
+
return exitCodeForRunOutcome(event.result.outcome);
|
|
125
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { validateCatalog as defaultValidateCatalog, type ValidationResult } from "../config/validateCatalog.js";
|
|
2
|
-
export declare const VALIDATE_USAGE = "Usage:\n sf validate [--pipeline <
|
|
2
|
+
export declare const VALIDATE_USAGE = "Usage:\n sf validate [--pipeline <path>] [--task <path>] [--strict] [--json]\n\n With no flags, validates all pipelines and tasks declared in stageflow.yaml (manifest-all).\n --pipeline validates one pipeline file (includes uses:/include: transitively).\n --task validates one task file.";
|
|
3
3
|
export type ValidateCommandIo = {
|
|
4
4
|
log: (line: string) => void;
|
|
5
5
|
error: (line: string) => void;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { validateCatalog as defaultValidateCatalog, } from "../config/validateCatalog.js";
|
|
2
2
|
import { exitCodeForValidation, formatValidationHuman, formatValidationJson, } from "./validateOutput.js";
|
|
3
3
|
export const VALIDATE_USAGE = `Usage:
|
|
4
|
-
sf validate [--pipeline <
|
|
4
|
+
sf validate [--pipeline <path>] [--task <path>] [--strict] [--json]
|
|
5
|
+
|
|
6
|
+
With no flags, validates all pipelines and tasks declared in stageflow.yaml (manifest-all).
|
|
7
|
+
--pipeline validates one pipeline file (includes uses:/include: transitively).
|
|
8
|
+
--task validates one task file.`;
|
|
5
9
|
const defaultIo = {
|
|
6
10
|
log: (line) => console.log(line),
|
|
7
11
|
error: (line) => console.error(line),
|
|
@@ -14,6 +18,7 @@ function parseValidateArgs(args) {
|
|
|
14
18
|
return { help: true, strict: false, json: false };
|
|
15
19
|
}
|
|
16
20
|
let pipeline;
|
|
21
|
+
let task;
|
|
17
22
|
let strict = false;
|
|
18
23
|
let json = false;
|
|
19
24
|
let help = false;
|
|
@@ -29,6 +34,13 @@ function parseValidateArgs(args) {
|
|
|
29
34
|
}
|
|
30
35
|
pipeline = value;
|
|
31
36
|
}
|
|
37
|
+
else if (arg === "--task") {
|
|
38
|
+
const value = args[++i];
|
|
39
|
+
if (value === undefined || value.length === 0) {
|
|
40
|
+
throw new Error("Missing value for --task");
|
|
41
|
+
}
|
|
42
|
+
task = value;
|
|
43
|
+
}
|
|
32
44
|
else if (arg === "--strict") {
|
|
33
45
|
strict = true;
|
|
34
46
|
}
|
|
@@ -42,7 +54,10 @@ function parseValidateArgs(args) {
|
|
|
42
54
|
throw new Error(`Unexpected argument: ${arg}`);
|
|
43
55
|
}
|
|
44
56
|
}
|
|
45
|
-
|
|
57
|
+
if (pipeline !== undefined && task !== undefined) {
|
|
58
|
+
throw new Error("Use at most one of --pipeline or --task");
|
|
59
|
+
}
|
|
60
|
+
return { help, pipeline, task, strict, json };
|
|
46
61
|
}
|
|
47
62
|
export async function runValidateCommand(args, options = {}) {
|
|
48
63
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -54,10 +69,12 @@ export async function runValidateCommand(args, options = {}) {
|
|
|
54
69
|
out.error(VALIDATE_USAGE);
|
|
55
70
|
return 0;
|
|
56
71
|
}
|
|
72
|
+
const scope = parsed.pipeline ? "pipeline" : parsed.task ? "task" : "full";
|
|
57
73
|
const result = await validateCatalogFn({
|
|
58
|
-
scope
|
|
74
|
+
scope,
|
|
59
75
|
cwd,
|
|
60
76
|
pipeline: parsed.pipeline,
|
|
77
|
+
task: parsed.task,
|
|
61
78
|
strict: parsed.strict,
|
|
62
79
|
});
|
|
63
80
|
if (parsed.json) {
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,2 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
import type { OperatorCatalog } from "./runtime/stageAttemptBootstrap.js";
|
|
3
|
+
export declare function parseRunStageArgs(argv: string[]): {
|
|
4
|
+
runId: string;
|
|
5
|
+
stageId: string;
|
|
6
|
+
mode?: "run" | "resume";
|
|
7
|
+
resumeAnswer?: unknown;
|
|
8
|
+
attempt?: number;
|
|
9
|
+
sessionFilePath?: string;
|
|
10
|
+
operatorCatalog?: OperatorCatalog;
|
|
11
|
+
skipGates?: boolean;
|
|
12
|
+
};
|
|
13
|
+
export declare function sameCliEntry(a: string, b: string): boolean;
|
package/dist/cli.js
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import {
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
4
6
|
import { PiAgentAdapter } from "./agent/piAdapter.js";
|
|
7
|
+
import { INIT_USAGE, runInitCommand } from "./cli/initCommand.js";
|
|
5
8
|
import { PROVIDERS_USAGE, runProvidersCommand } from "./cli/providersCommand.js";
|
|
9
|
+
import { RUN_USAGE, runRunCommand } from "./cli/runCommand.js";
|
|
10
|
+
import { resolveOperatorCatalog } from "./cli/operatorCatalog.js";
|
|
6
11
|
import { VALIDATE_USAGE, runValidateCommand } from "./cli/validateCommand.js";
|
|
7
|
-
import { exitCodeForValidation, formatValidationHuman, } from "./cli/validateOutput.js";
|
|
8
12
|
import { createRunStore } from "./runstore/createStore.js";
|
|
9
|
-
import {
|
|
10
|
-
import { RunManager } from "./runtime/runManager.js";
|
|
11
|
-
import { readStageExecutionMode } from "./runtime/stageConcurrency.js";
|
|
13
|
+
import { resolveStageflowContext } from "./project/resolveStageflowContext.js";
|
|
12
14
|
import { exitForOutcome, runStageWorker } from "./runtime/stageWorker.js";
|
|
13
15
|
import { SF_STAGE_WORKER } from "./runtime/stageWorkerProtocol.js";
|
|
14
16
|
import { DEFAULT_PORT, startUiServer } from "./server/http.js";
|
|
15
17
|
const USAGE = `Usage:
|
|
16
|
-
sf
|
|
17
|
-
sf
|
|
18
|
+
sf init
|
|
19
|
+
sf run --task <path> --pipeline <path> [--checkout <path>] [--json] [--skip-gates] [--git-sha <sha>] [--ci-pr-url <url>] [--ci-job-url <url>] [--operator-cwd <path>] [--operator-agent-dir <path>]
|
|
20
|
+
sf validate [--pipeline <path>] [--task <path>] [--strict] [--json]
|
|
18
21
|
sf ui [--port ${DEFAULT_PORT}]
|
|
19
22
|
sf providers list
|
|
20
23
|
sf providers status [--provider <id>]
|
|
@@ -28,6 +31,10 @@ Stageflow (sf) runs YAML-defined automatic stage pipelines.
|
|
|
28
31
|
|
|
29
32
|
Store backend: SF_STORE=sqlite only. SF_STORE=disk is rejected; disk-era .stageflow/runs trees import when the SQLite store is empty. Data under .stageflow/.
|
|
30
33
|
|
|
34
|
+
${INIT_USAGE}
|
|
35
|
+
|
|
36
|
+
${RUN_USAGE}
|
|
37
|
+
|
|
31
38
|
${VALIDATE_USAGE}
|
|
32
39
|
|
|
33
40
|
${PROVIDERS_USAGE}`;
|
|
@@ -41,7 +48,10 @@ function parseArgs(argv) {
|
|
|
41
48
|
return { help: true };
|
|
42
49
|
}
|
|
43
50
|
const command = args[0];
|
|
44
|
-
if (command === "providers" ||
|
|
51
|
+
if (command === "providers" ||
|
|
52
|
+
command === "validate" ||
|
|
53
|
+
command === "run" ||
|
|
54
|
+
command === "init") {
|
|
45
55
|
return { help: false, command };
|
|
46
56
|
}
|
|
47
57
|
if (args.includes("--help") || args.includes("-h")) {
|
|
@@ -75,14 +85,16 @@ function parseArgs(argv) {
|
|
|
75
85
|
}
|
|
76
86
|
return { help: false, command, task, pipeline, port, checkout };
|
|
77
87
|
}
|
|
78
|
-
function parseRunStageArgs(argv) {
|
|
88
|
+
export function parseRunStageArgs(argv) {
|
|
79
89
|
let runId;
|
|
80
90
|
let stageId;
|
|
81
91
|
let mode;
|
|
82
92
|
let resumeAnswer;
|
|
83
93
|
let attempt;
|
|
84
94
|
let sessionFilePath;
|
|
95
|
+
let operatorCwd;
|
|
85
96
|
let operatorAgentDir;
|
|
97
|
+
let skipGates = false;
|
|
86
98
|
for (let i = 0; i < argv.length; i++) {
|
|
87
99
|
if (argv[i] === "--run-id") {
|
|
88
100
|
runId = argv[++i];
|
|
@@ -126,16 +138,30 @@ function parseRunStageArgs(argv) {
|
|
|
126
138
|
throw new Error("Missing value for --session-file");
|
|
127
139
|
}
|
|
128
140
|
}
|
|
141
|
+
else if (argv[i] === "--operator-cwd") {
|
|
142
|
+
operatorCwd = argv[++i];
|
|
143
|
+
if (operatorCwd === undefined) {
|
|
144
|
+
throw new Error("Missing value for --operator-cwd");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
129
147
|
else if (argv[i] === "--operator-agent-dir") {
|
|
130
148
|
operatorAgentDir = argv[++i];
|
|
131
149
|
if (operatorAgentDir === undefined) {
|
|
132
150
|
throw new Error("Missing value for --operator-agent-dir");
|
|
133
151
|
}
|
|
134
152
|
}
|
|
153
|
+
else if (argv[i] === "--skip-gates") {
|
|
154
|
+
skipGates = true;
|
|
155
|
+
}
|
|
135
156
|
}
|
|
136
157
|
if (!runId || !stageId) {
|
|
137
158
|
throw new Error("Missing --run-id and/or --stage-id");
|
|
138
159
|
}
|
|
160
|
+
const operatorCatalog = resolveOperatorCatalog({
|
|
161
|
+
flags: { operatorCwd, operatorAgentDir },
|
|
162
|
+
env: process.env,
|
|
163
|
+
defaultCwd: process.cwd(),
|
|
164
|
+
});
|
|
139
165
|
return {
|
|
140
166
|
runId,
|
|
141
167
|
stageId,
|
|
@@ -143,9 +169,8 @@ function parseRunStageArgs(argv) {
|
|
|
143
169
|
resumeAnswer,
|
|
144
170
|
attempt,
|
|
145
171
|
sessionFilePath,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
: {}),
|
|
172
|
+
skipGates,
|
|
173
|
+
operatorCatalog,
|
|
149
174
|
};
|
|
150
175
|
}
|
|
151
176
|
async function handleInternalRunStage(argv) {
|
|
@@ -163,6 +188,7 @@ async function handleInternalRunStage(argv) {
|
|
|
163
188
|
attempt: parsed.attempt,
|
|
164
189
|
sessionFilePath: parsed.sessionFilePath,
|
|
165
190
|
operatorCatalog: parsed.operatorCatalog,
|
|
191
|
+
skipGates: parsed.skipGates,
|
|
166
192
|
});
|
|
167
193
|
exitForOutcome(outcome);
|
|
168
194
|
}
|
|
@@ -185,18 +211,30 @@ async function main(argv) {
|
|
|
185
211
|
console.log(USAGE);
|
|
186
212
|
return argv.slice(2).length === 0 ? 1 : 0;
|
|
187
213
|
}
|
|
188
|
-
const
|
|
214
|
+
const ctx = await resolveStageflowContext(process.cwd());
|
|
215
|
+
if (parsed.command === "init") {
|
|
216
|
+
return runInitCommand(argv.slice(3), { cwd: ctx.invocationCwd });
|
|
217
|
+
}
|
|
189
218
|
if (parsed.command === "validate") {
|
|
190
|
-
return runValidateCommand(argv.slice(3), { cwd:
|
|
219
|
+
return runValidateCommand(argv.slice(3), { cwd: ctx.invocationCwd });
|
|
220
|
+
}
|
|
221
|
+
if (parsed.command === "run") {
|
|
222
|
+
return runRunCommand(argv.slice(3), {
|
|
223
|
+
cwd: ctx.invocationCwd,
|
|
224
|
+
projectRoot: ctx.projectRoot,
|
|
225
|
+
isGitProject: ctx.isGitProject,
|
|
226
|
+
});
|
|
191
227
|
}
|
|
192
228
|
if (parsed.command === "providers") {
|
|
193
|
-
return runProvidersCommand(argv.slice(3),
|
|
229
|
+
return runProvidersCommand(argv.slice(3), ctx.invocationCwd);
|
|
194
230
|
}
|
|
195
|
-
const store = createRunStore({ rootDir });
|
|
231
|
+
const store = createRunStore({ rootDir: ctx.projectRoot });
|
|
196
232
|
if (parsed.command === "ui") {
|
|
197
233
|
const { url, mcpUrl } = await startUiServer({
|
|
198
234
|
agent: new PiAgentAdapter(),
|
|
199
235
|
store,
|
|
236
|
+
cwd: ctx.invocationCwd,
|
|
237
|
+
rootDir: ctx.projectRoot,
|
|
200
238
|
port: parsed.port,
|
|
201
239
|
});
|
|
202
240
|
console.log(`Operator console: ${url}`);
|
|
@@ -214,60 +252,38 @@ async function main(argv) {
|
|
|
214
252
|
}
|
|
215
253
|
return handleInternalRunStage(subArgs.slice(1));
|
|
216
254
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
console.error(USAGE);
|
|
220
|
-
return 1;
|
|
221
|
-
}
|
|
222
|
-
if (!parsed.task || !parsed.pipeline) {
|
|
223
|
-
console.error("Missing --task and/or --pipeline");
|
|
224
|
-
console.error(USAGE);
|
|
225
|
-
return 1;
|
|
226
|
-
}
|
|
227
|
-
const manager = new RunManager({
|
|
228
|
-
agent: new PiAgentAdapter(),
|
|
229
|
-
store,
|
|
230
|
-
cwd: rootDir,
|
|
231
|
-
operatorCatalog: { cwd: rootDir, agentDir: getAgentDir() },
|
|
232
|
-
executionMode: readStageExecutionMode(process.env, "process"),
|
|
233
|
-
});
|
|
234
|
-
const started = await manager.startRun({
|
|
235
|
-
task: parsed.task,
|
|
236
|
-
pipeline: parsed.pipeline,
|
|
237
|
-
checkoutOverride: parsed.checkout,
|
|
238
|
-
});
|
|
239
|
-
if (!started.ok) {
|
|
240
|
-
const code = started.code ?? "error";
|
|
241
|
-
console.error(`${code}: ${started.reason}`);
|
|
242
|
-
if (started.conflictingRunId !== undefined) {
|
|
243
|
-
console.error(`conflictingRunId: ${started.conflictingRunId}`);
|
|
244
|
-
}
|
|
245
|
-
if (started.conflictingCheckout !== undefined) {
|
|
246
|
-
console.error(`conflictingCheckout: ${started.conflictingCheckout}`);
|
|
247
|
-
}
|
|
248
|
-
return started.status ?? 1;
|
|
249
|
-
}
|
|
250
|
-
const result = await started.done;
|
|
251
|
-
if (result.ok) {
|
|
252
|
-
console.log(`Pipeline succeeded. Run folder: ${result.runDir}`);
|
|
253
|
-
return 0;
|
|
254
|
-
}
|
|
255
|
-
console.error(`Pipeline failed: ${result.reason}`);
|
|
256
|
-
console.error(`Run folder: ${result.runDir}`);
|
|
255
|
+
console.error(`Unknown command: ${parsed.command}`);
|
|
256
|
+
console.error(USAGE);
|
|
257
257
|
return 1;
|
|
258
258
|
}
|
|
259
259
|
catch (err) {
|
|
260
|
-
if (err instanceof PipelineValidationError) {
|
|
261
|
-
console.error(formatValidationHuman(err.result));
|
|
262
|
-
return exitCodeForValidation(err.result);
|
|
263
|
-
}
|
|
264
260
|
console.error(err instanceof Error ? err.message : String(err));
|
|
265
261
|
return 1;
|
|
266
262
|
}
|
|
267
263
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
264
|
+
function normalizeCliEntry(filePath) {
|
|
265
|
+
const resolved = path.resolve(filePath);
|
|
266
|
+
let canonical;
|
|
267
|
+
try {
|
|
268
|
+
canonical = realpathSync(resolved);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
canonical = resolved;
|
|
272
|
+
}
|
|
273
|
+
return canonical.replace(/\.(?:[cm]?[jt]s)$/i, "");
|
|
274
|
+
}
|
|
275
|
+
export function sameCliEntry(a, b) {
|
|
276
|
+
return normalizeCliEntry(a) === normalizeCliEntry(b);
|
|
277
|
+
}
|
|
278
|
+
function isDirectCliInvocation() {
|
|
279
|
+
const self = fileURLToPath(import.meta.url);
|
|
280
|
+
return process.argv.slice(1).some((arg) => sameCliEntry(arg, self));
|
|
281
|
+
}
|
|
282
|
+
if (isDirectCliInvocation()) {
|
|
283
|
+
main(process.argv)
|
|
284
|
+
.then((code) => process.exit(code))
|
|
285
|
+
.catch((err) => {
|
|
286
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
287
|
+
process.exit(1);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { StageGateKind } from "../types/stage.js";
|
|
2
|
+
import type { LoadedManifest } from "../types/stageflowManifest.js";
|
|
3
|
+
import type { LoadIssue } from "./loadOutcome.js";
|
|
4
|
+
import type { CatalogContext } from "./resolveCatalogContext.js";
|
|
5
|
+
import { type CatalogManifestStatus, type StageflowContext } from "../project/resolveStageflowContext.js";
|
|
6
|
+
export type TaskListing = {
|
|
7
|
+
path: string;
|
|
8
|
+
id: string;
|
|
9
|
+
goal: string;
|
|
10
|
+
};
|
|
11
|
+
export type PipelineStageListing = {
|
|
12
|
+
id: string;
|
|
13
|
+
gate_kinds?: StageGateKind[];
|
|
14
|
+
uses_path?: string;
|
|
15
|
+
inline?: boolean;
|
|
16
|
+
};
|
|
17
|
+
export type PipelineListing = {
|
|
18
|
+
path: string;
|
|
19
|
+
id: string;
|
|
20
|
+
stages: PipelineStageListing[];
|
|
21
|
+
};
|
|
22
|
+
export type CatalogBrowseWarning = {
|
|
23
|
+
path: string;
|
|
24
|
+
kind: "pipeline" | "task";
|
|
25
|
+
error: string;
|
|
26
|
+
};
|
|
27
|
+
export type CatalogBrowseResult = {
|
|
28
|
+
projectRoot: string | null;
|
|
29
|
+
manifest: LoadedManifest | null;
|
|
30
|
+
manifestStatus: CatalogManifestStatus;
|
|
31
|
+
issues: LoadIssue[];
|
|
32
|
+
pipelines: PipelineListing[];
|
|
33
|
+
tasks: TaskListing[];
|
|
34
|
+
models: string[];
|
|
35
|
+
warnings: CatalogBrowseWarning[];
|
|
36
|
+
};
|
|
37
|
+
export type CatalogScanPaths = {
|
|
38
|
+
pipelinePaths: string[];
|
|
39
|
+
taskPaths: string[];
|
|
40
|
+
};
|
|
41
|
+
export declare function getCatalogScanPaths(ctx: CatalogContext): Promise<CatalogScanPaths | null>;
|
|
42
|
+
export declare function browseCatalog(invocationCwd: string | StageflowContext): Promise<CatalogBrowseResult>;
|
|
43
|
+
export declare function listPipelinesForContext(ctx: CatalogContext): Promise<PipelineListing[]>;
|
|
44
|
+
export declare function listTasksForContext(ctx: CatalogContext): Promise<TaskListing[]>;
|
|
45
|
+
export declare function listModelsForContext(ctx: CatalogContext): Promise<string[]>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { loadPipeline } from "./loadPipeline.js";
|
|
3
|
+
import { loadTask } from "./loadTask.js";
|
|
4
|
+
import { resolveStageflowContext, } from "../project/resolveStageflowContext.js";
|
|
5
|
+
import { scanCatalogPaths } from "./scanCatalogPaths.js";
|
|
6
|
+
const BAKED_MODEL_IDS = [
|
|
7
|
+
"anthropic/claude-sonnet-4-5",
|
|
8
|
+
"cursor/auto",
|
|
9
|
+
"cursor/composer-2-5",
|
|
10
|
+
];
|
|
11
|
+
function repoRelPath(projectRoot, absPath) {
|
|
12
|
+
return path.relative(projectRoot, absPath).replace(/\\/g, "/");
|
|
13
|
+
}
|
|
14
|
+
function mapStageListing(stage, source, projectRoot) {
|
|
15
|
+
const listing = {
|
|
16
|
+
id: stage.id,
|
|
17
|
+
...(stage.gate_kinds ? { gate_kinds: stage.gate_kinds } : {}),
|
|
18
|
+
};
|
|
19
|
+
if (source?.kind === "inline") {
|
|
20
|
+
listing.inline = true;
|
|
21
|
+
}
|
|
22
|
+
else if (source?.kind === "file") {
|
|
23
|
+
listing.uses_path = repoRelPath(projectRoot, source.path);
|
|
24
|
+
}
|
|
25
|
+
return listing;
|
|
26
|
+
}
|
|
27
|
+
function bakedModels() {
|
|
28
|
+
return [...BAKED_MODEL_IDS].sort((a, b) => a.localeCompare(b));
|
|
29
|
+
}
|
|
30
|
+
function catalogReadyFromStageflow(ctx) {
|
|
31
|
+
return ctx.isGitProject && ctx.manifestStatus === "ok" && ctx.manifest !== null;
|
|
32
|
+
}
|
|
33
|
+
function catalogReady(ctx) {
|
|
34
|
+
return ctx.manifestStatus === "ok" && ctx.projectRoot !== null && ctx.manifest !== null;
|
|
35
|
+
}
|
|
36
|
+
export async function getCatalogScanPaths(ctx) {
|
|
37
|
+
if (!catalogReady(ctx)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const [pipelinePaths, taskPaths] = await Promise.all([
|
|
41
|
+
scanCatalogPaths(ctx.manifest, "pipeline"),
|
|
42
|
+
scanCatalogPaths(ctx.manifest, "task"),
|
|
43
|
+
]);
|
|
44
|
+
return { pipelinePaths, taskPaths };
|
|
45
|
+
}
|
|
46
|
+
async function listTasksFromManifest(projectRoot, manifest, warnings) {
|
|
47
|
+
const filePaths = await scanCatalogPaths(manifest, "task");
|
|
48
|
+
const listings = [];
|
|
49
|
+
for (const filePath of filePaths) {
|
|
50
|
+
try {
|
|
51
|
+
const task = await loadTask(filePath);
|
|
52
|
+
listings.push({
|
|
53
|
+
path: repoRelPath(projectRoot, filePath),
|
|
54
|
+
id: task.id,
|
|
55
|
+
goal: task.goal,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
warnings.push({
|
|
60
|
+
path: repoRelPath(projectRoot, filePath),
|
|
61
|
+
kind: "task",
|
|
62
|
+
error: err instanceof Error ? err.message : String(err),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
listings.sort((a, b) => {
|
|
67
|
+
const pathCmp = a.path.localeCompare(b.path);
|
|
68
|
+
if (pathCmp !== 0)
|
|
69
|
+
return pathCmp;
|
|
70
|
+
return a.id.localeCompare(b.id);
|
|
71
|
+
});
|
|
72
|
+
return listings;
|
|
73
|
+
}
|
|
74
|
+
async function listPipelinesFromManifest(projectRoot, manifest, warnings) {
|
|
75
|
+
const filePaths = await scanCatalogPaths(manifest, "pipeline");
|
|
76
|
+
const listings = [];
|
|
77
|
+
for (const filePath of filePaths) {
|
|
78
|
+
try {
|
|
79
|
+
const loaded = await loadPipeline(filePath, { cwd: projectRoot });
|
|
80
|
+
listings.push({
|
|
81
|
+
path: repoRelPath(projectRoot, filePath),
|
|
82
|
+
id: loaded.pipeline.id,
|
|
83
|
+
stages: loaded.stages.map((stage) => mapStageListing(stage, loaded.stageSources?.[stage.id], projectRoot)),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
warnings.push({
|
|
88
|
+
path: repoRelPath(projectRoot, filePath),
|
|
89
|
+
kind: "pipeline",
|
|
90
|
+
error: err instanceof Error ? err.message : String(err),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
listings.sort((a, b) => {
|
|
95
|
+
const pathCmp = a.path.localeCompare(b.path);
|
|
96
|
+
if (pathCmp !== 0)
|
|
97
|
+
return pathCmp;
|
|
98
|
+
return a.id.localeCompare(b.id);
|
|
99
|
+
});
|
|
100
|
+
return listings;
|
|
101
|
+
}
|
|
102
|
+
async function listModelsFromManifest(projectRoot, manifest) {
|
|
103
|
+
const models = new Set(BAKED_MODEL_IDS);
|
|
104
|
+
const filePaths = await scanCatalogPaths(manifest, "pipeline");
|
|
105
|
+
for (const filePath of filePaths) {
|
|
106
|
+
try {
|
|
107
|
+
const loaded = await loadPipeline(filePath, { cwd: projectRoot });
|
|
108
|
+
for (const stage of loaded.stages) {
|
|
109
|
+
if (stage.model.trim()) {
|
|
110
|
+
models.add(stage.model);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// skip invalid pipelines
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return [...models].sort((a, b) => a.localeCompare(b));
|
|
119
|
+
}
|
|
120
|
+
export async function browseCatalog(invocationCwd) {
|
|
121
|
+
const ctx = typeof invocationCwd === "string"
|
|
122
|
+
? await resolveStageflowContext(invocationCwd)
|
|
123
|
+
: invocationCwd;
|
|
124
|
+
const base = {
|
|
125
|
+
projectRoot: ctx.isGitProject ? ctx.projectRoot : null,
|
|
126
|
+
manifest: ctx.manifest,
|
|
127
|
+
manifestStatus: ctx.manifestStatus,
|
|
128
|
+
issues: ctx.manifestIssues,
|
|
129
|
+
};
|
|
130
|
+
if (!catalogReadyFromStageflow(ctx)) {
|
|
131
|
+
return {
|
|
132
|
+
...base,
|
|
133
|
+
pipelines: [],
|
|
134
|
+
tasks: [],
|
|
135
|
+
models: bakedModels(),
|
|
136
|
+
warnings: [],
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const warnings = [];
|
|
140
|
+
const [pipelines, tasks, models] = await Promise.all([
|
|
141
|
+
listPipelinesFromManifest(ctx.projectRoot, ctx.manifest, warnings),
|
|
142
|
+
listTasksFromManifest(ctx.projectRoot, ctx.manifest, warnings),
|
|
143
|
+
listModelsFromManifest(ctx.projectRoot, ctx.manifest),
|
|
144
|
+
]);
|
|
145
|
+
return {
|
|
146
|
+
...base,
|
|
147
|
+
pipelines,
|
|
148
|
+
tasks,
|
|
149
|
+
models,
|
|
150
|
+
warnings,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
export async function listPipelinesForContext(ctx) {
|
|
154
|
+
if (!catalogReady(ctx)) {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
return listPipelinesFromManifest(ctx.projectRoot, ctx.manifest, []);
|
|
158
|
+
}
|
|
159
|
+
export async function listTasksForContext(ctx) {
|
|
160
|
+
if (!catalogReady(ctx)) {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
return listTasksFromManifest(ctx.projectRoot, ctx.manifest, []);
|
|
164
|
+
}
|
|
165
|
+
export async function listModelsForContext(ctx) {
|
|
166
|
+
if (!catalogReady(ctx)) {
|
|
167
|
+
return bakedModels();
|
|
168
|
+
}
|
|
169
|
+
return listModelsFromManifest(ctx.projectRoot, ctx.manifest);
|
|
170
|
+
}
|