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.
Files changed (132) hide show
  1. package/README.md +190 -13
  2. package/dist/agent/activity.d.ts +10 -1
  3. package/dist/agent/activity.js +37 -2
  4. package/dist/agent/activityObserver.d.ts +5 -1
  5. package/dist/agent/activityObserver.js +174 -11
  6. package/dist/agent/fakeAgent.js +4 -0
  7. package/dist/agent/piAdapter.d.ts +16 -0
  8. package/dist/agent/piAdapter.js +88 -21
  9. package/dist/agent/port.d.ts +2 -0
  10. package/dist/agent/providerAuth.js +12 -7
  11. package/dist/catalog/displayCatalogPath.d.ts +12 -0
  12. package/dist/catalog/displayCatalogPath.js +28 -0
  13. package/dist/cli/ciIdentity.d.ts +14 -0
  14. package/dist/cli/ciIdentity.js +52 -0
  15. package/dist/cli/initCommand.d.ts +9 -0
  16. package/dist/cli/initCommand.js +71 -0
  17. package/dist/cli/initTemplates.d.ts +3 -0
  18. package/dist/cli/initTemplates.js +19 -0
  19. package/dist/cli/operatorCatalog.d.ts +10 -0
  20. package/dist/cli/operatorCatalog.js +16 -0
  21. package/dist/cli/runCommand.d.ts +26 -0
  22. package/dist/cli/runCommand.js +209 -0
  23. package/dist/cli/runOutput.d.ts +19 -0
  24. package/dist/cli/runOutput.js +125 -0
  25. package/dist/cli/validateCommand.d.ts +1 -1
  26. package/dist/cli/validateCommand.js +20 -3
  27. package/dist/cli.d.ts +12 -1
  28. package/dist/cli.js +82 -66
  29. package/dist/config/browseCatalog.d.ts +45 -0
  30. package/dist/config/browseCatalog.js +170 -0
  31. package/dist/config/createPipeline.d.ts +12 -3
  32. package/dist/config/createPipeline.js +292 -100
  33. package/dist/config/createStage.d.ts +10 -4
  34. package/dist/config/createStage.js +44 -14
  35. package/dist/config/listConfig.d.ts +14 -19
  36. package/dist/config/listConfig.js +26 -191
  37. package/dist/config/listModelsFromManifest.d.ts +2 -0
  38. package/dist/config/listModelsFromManifest.js +4 -0
  39. package/dist/config/loadOutcome.d.ts +4 -0
  40. package/dist/config/loadPipeline.d.ts +1 -15
  41. package/dist/config/loadPipeline.js +84 -142
  42. package/dist/config/loadStage.d.ts +4 -0
  43. package/dist/config/loadStage.js +60 -32
  44. package/dist/config/loadStageflowManifest.d.ts +5 -0
  45. package/dist/config/loadStageflowManifest.js +157 -0
  46. package/dist/config/mergePipelineIncludes.d.ts +9 -0
  47. package/dist/config/mergePipelineIncludes.js +141 -0
  48. package/dist/config/normalizePipelineStageEntry.d.ts +16 -0
  49. package/dist/config/normalizePipelineStageEntry.js +186 -0
  50. package/dist/config/pipelineStageKeys.d.ts +4 -0
  51. package/dist/config/pipelineStageKeys.js +14 -0
  52. package/dist/config/resolveCatalogContext.d.ts +13 -0
  53. package/dist/config/resolveCatalogContext.js +13 -0
  54. package/dist/config/resolveForkEmitContext.d.ts +3 -0
  55. package/dist/config/resolveForkEmitContext.js +12 -0
  56. package/dist/config/resolvePipelineDag.d.ts +5 -1
  57. package/dist/config/resolvePipelineDag.js +54 -36
  58. package/dist/config/scanCatalogPaths.d.ts +4 -0
  59. package/dist/config/scanCatalogPaths.js +83 -0
  60. package/dist/config/validateCatalog.d.ts +22 -4
  61. package/dist/config/validateCatalog.js +169 -132
  62. package/dist/envelope/check.js +7 -0
  63. package/dist/envelope/forkChoice.d.ts +3 -0
  64. package/dist/envelope/forkChoice.js +47 -0
  65. package/dist/index.d.ts +4 -0
  66. package/dist/index.js +4 -0
  67. package/dist/mcp/projectRun.d.ts +3 -0
  68. package/dist/mcp/projectRun.js +7 -0
  69. package/dist/mcp/server.js +1 -1
  70. package/dist/mcp/tools.js +28 -11
  71. package/dist/project/findProjectRoot.d.ts +2 -0
  72. package/dist/project/findProjectRoot.js +51 -0
  73. package/dist/project/globalHome.d.ts +2 -0
  74. package/dist/project/globalHome.js +19 -0
  75. package/dist/project/resolveProjectContext.d.ts +9 -0
  76. package/dist/project/resolveProjectContext.js +38 -0
  77. package/dist/project/resolveStageflowContext.d.ts +11 -0
  78. package/dist/project/resolveStageflowContext.js +66 -0
  79. package/dist/runstore/normalizeCatalogPath.d.ts +1 -0
  80. package/dist/runstore/normalizeCatalogPath.js +4 -0
  81. package/dist/runstore/port.d.ts +15 -0
  82. package/dist/runstore/runProjection.js +11 -1
  83. package/dist/runstore/sqlite/SqliteRunStore.js +46 -4
  84. package/dist/runstore/sqlite/schema.d.ts +1 -1
  85. package/dist/runstore/sqlite/schema.js +7 -1
  86. package/dist/runtime/credentialBinding.d.ts +8 -6
  87. package/dist/runtime/credentialBinding.js +18 -24
  88. package/dist/runtime/pipelineRunner.d.ts +11 -0
  89. package/dist/runtime/pipelineRunner.js +25 -1
  90. package/dist/runtime/pipelineScheduler.d.ts +3 -0
  91. package/dist/runtime/pipelineScheduler.js +70 -14
  92. package/dist/runtime/reloadRunCatalog.d.ts +5 -0
  93. package/dist/runtime/reloadRunCatalog.js +44 -0
  94. package/dist/runtime/resumeReconstruct.d.ts +1 -0
  95. package/dist/runtime/resumeReconstruct.js +27 -10
  96. package/dist/runtime/runManager.d.ts +8 -0
  97. package/dist/runtime/runManager.js +49 -18
  98. package/dist/runtime/settingsFile.d.ts +12 -0
  99. package/dist/runtime/settingsFile.js +87 -6
  100. package/dist/runtime/stageAttemptBootstrap.js +3 -0
  101. package/dist/runtime/stageProcessLauncher.d.ts +1 -0
  102. package/dist/runtime/stageProcessLauncher.js +6 -0
  103. package/dist/runtime/stageRecovery.js +3 -1
  104. package/dist/runtime/stageRunner.d.ts +2 -0
  105. package/dist/runtime/stageRunner.js +9 -2
  106. package/dist/runtime/stageWorker.js +4 -6
  107. package/dist/runtime/stageWorkerProtocol.d.ts +1 -0
  108. package/dist/server/http.js +33 -9
  109. package/dist/tools/emitStageEnvelope.d.ts +3 -1
  110. package/dist/tools/emitStageEnvelope.js +11 -1
  111. package/dist/types/envelope.d.ts +1 -0
  112. package/dist/types/forkChoice.d.ts +8 -0
  113. package/dist/types/forkChoice.js +1 -0
  114. package/dist/types/pipeline.d.ts +51 -2
  115. package/dist/types/stageflowManifest.d.ts +23 -0
  116. package/dist/types/stageflowManifest.js +1 -0
  117. package/dist/ui/assets/index-CFSzDZje.js +118 -0
  118. package/dist/ui/assets/{index-DCsDopak.css → index-DefBlEvN.css} +1 -1
  119. package/dist/ui/index.html +3 -2
  120. package/dist/ui/stageflow-icon.svg +12 -0
  121. package/package.json +7 -5
  122. package/dist/agent/cursorExtension.d.ts +0 -12
  123. package/dist/agent/cursorExtension.js +0 -88
  124. package/dist/runstore/catalog.d.ts +0 -8
  125. package/dist/runstore/catalog.js +0 -16
  126. package/dist/runstore/disk/DiskRunStore.d.ts +0 -30
  127. package/dist/runstore/disk/DiskRunStore.js +0 -238
  128. package/dist/runstore/layout.d.ts +0 -22
  129. package/dist/runstore/layout.js +0 -58
  130. package/dist/runtime/hitlSeams.d.ts +0 -38
  131. package/dist/runtime/hitlSeams.js +0 -2
  132. package/dist/ui/assets/index-Cry0Tpfx.js +0 -118
@@ -41,8 +41,8 @@ import { createAskOperatorTool, } from "../tools/askOperator.js";
41
41
  import { createWriteStageArtifactTool } from "../tools/writeStageArtifact.js";
42
42
  import "./cursorProvider.js";
43
43
  import { findProviderSupport } from "./providerSupport.js";
44
- import { mapSessionEventToActivity } from "./activity.js";
45
- import { createStageActivityObserver } from "./activityObserver.js";
44
+ import { mapSessionEventToActivity, readActivityVerbose } from "./activity.js";
45
+ import { createStageActivityObserver, } from "./activityObserver.js";
46
46
  import { DEFAULT_STAGE_TIMEOUT_MS, runStageViaOpen } from "./port.js";
47
47
  /**
48
48
  * Stage tool allowlist for sealed Pi sessions.
@@ -260,31 +260,98 @@ export function createConnectedAskWaitStageHandle(options) {
260
260
  },
261
261
  };
262
262
  }
263
+ /**
264
+ * Extract display text from a Pi tool_execution_update partialResult.
265
+ * Prefers content[].text blocks; otherwise JSON-stringifies.
266
+ */
267
+ export function extractPartialResultText(partialResult) {
268
+ if (partialResult === undefined || partialResult === null) {
269
+ return "";
270
+ }
271
+ if (typeof partialResult === "string") {
272
+ return partialResult;
273
+ }
274
+ if (typeof partialResult === "object") {
275
+ const content = partialResult.content;
276
+ if (Array.isArray(content)) {
277
+ const parts = [];
278
+ for (const block of content) {
279
+ if (!block || typeof block !== "object")
280
+ continue;
281
+ const b = block;
282
+ if (b.type === "text" && typeof b.text === "string") {
283
+ parts.push(b.text);
284
+ }
285
+ }
286
+ if (parts.length > 0) {
287
+ return parts.join("");
288
+ }
289
+ }
290
+ try {
291
+ return JSON.stringify(partialResult);
292
+ }
293
+ catch {
294
+ return String(partialResult);
295
+ }
296
+ }
297
+ return String(partialResult);
298
+ }
299
+ /**
300
+ * Adapter-edge routing: stream deltas to the observer, map milestones to
301
+ * StageActivityEvent. Verbose thinking / live tool partials go to observer
302
+ * hooks (stderr streams + coalesced/throttled persist via onActivity).
303
+ */
304
+ export function routeSessionEventToProgress(event, options) {
305
+ const { observer, verbose } = options;
306
+ if (event.type === "message_update") {
307
+ const ame = event.assistantMessageEvent;
308
+ if (ame && typeof ame === "object") {
309
+ const update = ame;
310
+ if (update.type === "text_delta") {
311
+ observer.onAssistantTextDelta(update.delta ?? "");
312
+ return;
313
+ }
314
+ if (verbose && update.type === "thinking_delta") {
315
+ observer.onThinkingDelta(update.delta ?? "");
316
+ return;
317
+ }
318
+ }
319
+ return;
320
+ }
321
+ if (verbose &&
322
+ event.type === "tool_execution_update") {
323
+ const text = extractPartialResultText(event.partialResult);
324
+ if (text) {
325
+ observer.onToolPartialResult(text);
326
+ }
327
+ return;
328
+ }
329
+ if (event.type === "message_end" || event.type === "agent_end") {
330
+ observer.onStreamBoundary();
331
+ }
332
+ if (event.type === "agent_start" ||
333
+ event.type === "turn_start" ||
334
+ event.type === "tool_execution_start") {
335
+ observer.onStreamBoundary();
336
+ }
337
+ const activity = mapSessionEventToActivity(event);
338
+ if (!activity) {
339
+ return;
340
+ }
341
+ observer.onActivity(activity);
342
+ }
263
343
  /**
264
344
  * Map Pi session events to StageActivityEvent at the adapter edge, then
265
345
  * forward to the activity observer (stderr + onActivity).
266
346
  */
267
347
  function attachStageProgress(session, onActivity) {
268
348
  const observer = createStageActivityObserver({ onActivity, writeStderr: true });
349
+ const verbose = readActivityVerbose();
269
350
  const unsubscribe = session.subscribe((event) => {
270
- if (event.type === "message_update" &&
271
- event.assistantMessageEvent.type === "text_delta") {
272
- observer.onAssistantTextDelta(event.assistantMessageEvent.delta ?? "");
273
- return;
274
- }
275
- if (event.type === "message_end" || event.type === "agent_end") {
276
- observer.onStreamBoundary();
277
- }
278
- if (event.type === "agent_start" ||
279
- event.type === "turn_start" ||
280
- event.type === "tool_execution_start") {
281
- observer.onStreamBoundary();
282
- }
283
- const activity = mapSessionEventToActivity(event);
284
- if (!activity) {
285
- return;
286
- }
287
- observer.onActivity(activity);
351
+ routeSessionEventToProgress(event, {
352
+ observer,
353
+ verbose,
354
+ });
288
355
  });
289
356
  return () => {
290
357
  unsubscribe();
@@ -616,7 +683,7 @@ async function prepareStageSessionWiring(input, sessionManager, existingAskWaitC
616
683
  const provider = findProviderSupport(input.stage.model);
617
684
  const capture = {};
618
685
  const askWaitChannel = existingAskWaitChannel ?? new AskOperatorWaitChannel();
619
- const emitDef = createEmitStageEnvelopeTool(capture, input.stage.payload_schema);
686
+ const emitDef = createEmitStageEnvelopeTool(capture, input.stage.payload_schema, input.forkEmitContext);
620
687
  const emitTool = defineTool(emitDef);
621
688
  const askDef = createAskOperatorTool({
622
689
  requestWait: (prompt) => askWaitChannel.requestWait(prompt),
@@ -1,4 +1,5 @@
1
1
  import type { StageEnvelope } from "../types/envelope.js";
2
+ import type { ForkEmitContext } from "../types/forkChoice.js";
2
3
  import type { StageConfig } from "../types/stage.js";
3
4
  import type { TaskFile } from "../types/task.js";
4
5
  import type { StageActivityEvent } from "./activity.js";
@@ -15,6 +16,7 @@ export type StageRunInput = {
15
16
  /** Optional observe hook; HITL wait/answer uses openStage beside this. */
16
17
  onActivity?: (event: StageActivityEvent) => void;
17
18
  skillFilePath?: string;
19
+ forkEmitContext?: ForkEmitContext;
18
20
  };
19
21
  export type StageRunResult = {
20
22
  ok: true;
@@ -1,5 +1,7 @@
1
1
  import { CredentialSynchronizationError, ModelRuntime, } from "@earendil-works/pi-coding-agent";
2
- import { isUsableAuthFile, parseCredentialSource, piHomeAuthPath, readCredentialSourceFromFile, resolveCredentialBinding, writeCredentialSourceToFile, } from "../runtime/credentialBinding.js";
2
+ import { isUsableAuthFile, parseCredentialSource, piHomeAuthPath, resolveCredentialBinding, writeCredentialSourceToContext, } from "../runtime/credentialBinding.js";
3
+ import { readPersistedCredentialSourceFromContext, } from "../runtime/settingsFile.js";
4
+ import { resolveProjectContext } from "../project/resolveProjectContext.js";
3
5
  export function makeMutationLock() {
4
6
  let tail = Promise.resolve();
5
7
  return {
@@ -245,8 +247,9 @@ export async function logoutProvider(cwd, providerId, ctx = defaultContext) {
245
247
  });
246
248
  }
247
249
  export function detectPiHome(cwd) {
248
- const binding = resolveCredentialBinding(cwd);
249
- const persisted = readCredentialSourceFromFile(cwd);
250
+ const projectCtx = resolveProjectContext(cwd);
251
+ const binding = resolveCredentialBinding(projectCtx);
252
+ const persisted = readPersistedCredentialSourceFromContext(projectCtx);
250
253
  return {
251
254
  piHomeUsable: isUsableAuthFile(piHomeAuthPath()),
252
255
  ...(persisted !== undefined ? { credentialSource: persisted } : {}),
@@ -255,8 +258,9 @@ export function detectPiHome(cwd) {
255
258
  };
256
259
  }
257
260
  export function getCredentialSourceSettings(cwd) {
258
- const persisted = readCredentialSourceFromFile(cwd);
259
- const binding = resolveCredentialBinding(cwd);
261
+ const projectCtx = resolveProjectContext(cwd);
262
+ const persisted = readPersistedCredentialSourceFromContext(projectCtx);
263
+ const binding = resolveCredentialBinding(projectCtx);
260
264
  return {
261
265
  ...(persisted !== undefined ? { credentialSource: persisted } : {}),
262
266
  binding: {
@@ -270,8 +274,9 @@ export function setCredentialSource(cwd, credentialSource) {
270
274
  if (parsed === undefined) {
271
275
  throw new ProviderAuthError('credentialSource must be "pi_home" or "sf_owned"', 400);
272
276
  }
273
- writeCredentialSourceToFile(cwd, parsed);
274
- const binding = resolveCredentialBinding(cwd);
277
+ const projectCtx = resolveProjectContext(cwd);
278
+ writeCredentialSourceToContext(projectCtx, parsed);
279
+ const binding = resolveCredentialBinding(projectCtx);
275
280
  return {
276
281
  credentialSource: parsed,
277
282
  binding: {
@@ -0,0 +1,12 @@
1
+ export declare function displayCatalogPath(absPath: string, projectRoot?: string): string;
2
+ export declare function normalizeCatalogSlashes(pathValue: string): string;
3
+ export type PipelinePathMatchInput = {
4
+ pipeline_id: string;
5
+ pipeline_path?: string;
6
+ project_root?: string;
7
+ };
8
+ export type PipelineListingPath = {
9
+ id: string;
10
+ path: string;
11
+ };
12
+ export declare function matchPipelineRun(run: PipelinePathMatchInput, pipeline: PipelineListingPath): boolean;
@@ -0,0 +1,28 @@
1
+ export function displayCatalogPath(absPath, projectRoot) {
2
+ if (projectRoot) {
3
+ const root = projectRoot.replace(/\\/g, "/").replace(/\/$/, "");
4
+ const normalized = absPath.replace(/\\/g, "/");
5
+ if (normalized === root)
6
+ return ".";
7
+ const prefix = `${root}/`;
8
+ if (normalized.startsWith(prefix)) {
9
+ return normalized.slice(prefix.length);
10
+ }
11
+ }
12
+ const parts = absPath.replace(/\\/g, "/").split("/");
13
+ return parts[parts.length - 1] ?? absPath;
14
+ }
15
+ export function normalizeCatalogSlashes(pathValue) {
16
+ return pathValue.replace(/\\/g, "/");
17
+ }
18
+ export function matchPipelineRun(run, pipeline) {
19
+ if (run.pipeline_id === pipeline.id)
20
+ return true;
21
+ if (run.pipeline_path && run.project_root) {
22
+ const rel = displayCatalogPath(run.pipeline_path, run.project_root);
23
+ if (normalizeCatalogSlashes(rel) === normalizeCatalogSlashes(pipeline.path)) {
24
+ return true;
25
+ }
26
+ }
27
+ return false;
28
+ }
@@ -0,0 +1,14 @@
1
+ export type CiIdentityFlags = {
2
+ gitSha?: string;
3
+ ciPrUrl?: string;
4
+ ciJobUrl?: string;
5
+ };
6
+ export type CiIdentity = {
7
+ gitSha?: string;
8
+ ciPrUrl?: string;
9
+ ciJobUrl?: string;
10
+ };
11
+ export declare function resolveCiIdentity(input: {
12
+ flags: CiIdentityFlags;
13
+ env: Record<string, string | undefined>;
14
+ }): CiIdentity;
@@ -0,0 +1,52 @@
1
+ function nonempty(value) {
2
+ if (value === undefined)
3
+ return undefined;
4
+ const trimmed = value.trim();
5
+ return trimmed.length === 0 ? undefined : trimmed;
6
+ }
7
+ function prNumberFromEnv(env) {
8
+ const ref = nonempty(env.GITHUB_REF);
9
+ if (ref !== undefined) {
10
+ const match = /^refs\/pull\/(\d+)\//.exec(ref);
11
+ if (match?.[1] !== undefined)
12
+ return match[1];
13
+ }
14
+ const refName = nonempty(env.GITHUB_REF_NAME);
15
+ if (refName !== undefined) {
16
+ const match = /^(\d+)\/merge$/.exec(refName);
17
+ if (match?.[1] !== undefined)
18
+ return match[1];
19
+ }
20
+ return undefined;
21
+ }
22
+ export function resolveCiIdentity(input) {
23
+ const result = {};
24
+ const gitSha = nonempty(input.flags.gitSha) ?? nonempty(input.env.GITHUB_SHA);
25
+ if (gitSha !== undefined)
26
+ result.gitSha = gitSha;
27
+ const flagPrUrl = nonempty(input.flags.ciPrUrl);
28
+ if (flagPrUrl !== undefined) {
29
+ result.ciPrUrl = flagPrUrl;
30
+ }
31
+ else {
32
+ const prNumber = prNumberFromEnv(input.env);
33
+ const server = nonempty(input.env.GITHUB_SERVER_URL);
34
+ const repo = nonempty(input.env.GITHUB_REPOSITORY);
35
+ if (prNumber !== undefined && server !== undefined && repo !== undefined) {
36
+ result.ciPrUrl = `${server}/${repo}/pull/${prNumber}`;
37
+ }
38
+ }
39
+ const flagJobUrl = nonempty(input.flags.ciJobUrl);
40
+ if (flagJobUrl !== undefined) {
41
+ result.ciJobUrl = flagJobUrl;
42
+ }
43
+ else {
44
+ const server = nonempty(input.env.GITHUB_SERVER_URL);
45
+ const repo = nonempty(input.env.GITHUB_REPOSITORY);
46
+ const runId = nonempty(input.env.GITHUB_RUN_ID);
47
+ if (server !== undefined && repo !== undefined && runId !== undefined) {
48
+ result.ciJobUrl = `${server}/${repo}/actions/runs/${runId}`;
49
+ }
50
+ }
51
+ return result;
52
+ }
@@ -0,0 +1,9 @@
1
+ export declare const INIT_USAGE = "Usage:\n sf init";
2
+ export type InitCommandIo = {
3
+ log: (line: string) => void;
4
+ error: (line: string) => void;
5
+ };
6
+ export declare function runInitCommand(args: string[], options?: {
7
+ cwd?: string;
8
+ io?: Partial<InitCommandIo>;
9
+ }): Promise<number>;
@@ -0,0 +1,71 @@
1
+ import { access, mkdir, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { findProjectRoot } from "../project/findProjectRoot.js";
4
+ import { ensureGlobalHome } from "../project/globalHome.js";
5
+ import { HELLO_PIPELINE_YAML, HELLO_TASK_YAML, STAGEFLOW_YAML, } from "./initTemplates.js";
6
+ export const INIT_USAGE = `Usage:
7
+ sf init`;
8
+ const defaultIo = {
9
+ log: (line) => console.log(line),
10
+ error: (line) => console.error(line),
11
+ };
12
+ const INIT_TARGETS = [
13
+ { relPath: "stageflow.yaml", content: STAGEFLOW_YAML },
14
+ { relPath: "pipelines/hello.pipeline.yaml", content: HELLO_PIPELINE_YAML },
15
+ { relPath: "tasks/hello.task.yaml", content: HELLO_TASK_YAML },
16
+ ];
17
+ async function fileExists(absPath) {
18
+ try {
19
+ await access(absPath);
20
+ return true;
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
26
+ function parseInitArgs(args) {
27
+ if (args.length === 0) {
28
+ return { help: false };
29
+ }
30
+ if (args[0] === "--help" || args[0] === "-h") {
31
+ return { help: true };
32
+ }
33
+ if (args[0].startsWith("-")) {
34
+ throw new Error(`Unknown flag: ${args[0]}`);
35
+ }
36
+ throw new Error(`Unexpected argument: ${args[0]}`);
37
+ }
38
+ export async function runInitCommand(args, options = {}) {
39
+ const cwd = options.cwd ?? process.cwd();
40
+ const out = { ...defaultIo, ...options.io };
41
+ try {
42
+ const parsed = parseInitArgs(args);
43
+ if (parsed.help) {
44
+ out.error(INIT_USAGE);
45
+ return 0;
46
+ }
47
+ const initRoot = findProjectRoot(cwd) ?? path.resolve(cwd);
48
+ ensureGlobalHome();
49
+ for (const target of INIT_TARGETS) {
50
+ const absPath = path.join(initRoot, target.relPath);
51
+ if (await fileExists(absPath)) {
52
+ out.log(`Skipped ${target.relPath} (exists)`);
53
+ continue;
54
+ }
55
+ await mkdir(path.dirname(absPath), { recursive: true });
56
+ await writeFile(absPath, target.content, "utf8");
57
+ out.log(`Created ${target.relPath}`);
58
+ }
59
+ return 0;
60
+ }
61
+ catch (err) {
62
+ const message = err instanceof Error
63
+ ? err.message
64
+ : typeof err === "string"
65
+ ? err
66
+ : "Init command failed";
67
+ out.error(message);
68
+ out.error(INIT_USAGE);
69
+ return 1;
70
+ }
71
+ }
@@ -0,0 +1,3 @@
1
+ export declare const STAGEFLOW_YAML = "version: 1\ncatalog:\n pipelines:\n - pipelines\n tasks:\n - tasks\n patterns:\n pipeline: \"*.pipeline.yaml\"\n task: \"*.task.yaml\"\n";
2
+ export declare const HELLO_PIPELINE_YAML = "id: hello\nstages:\n - id: hello\n system_prompt: Say hello and emit a success envelope.\n model: anthropic/claude-sonnet-4-5\n";
3
+ export declare const HELLO_TASK_YAML = "id: hello\ngoal: Run the hello pipeline scaffold.\n";
@@ -0,0 +1,19 @@
1
+ export const STAGEFLOW_YAML = `version: 1
2
+ catalog:
3
+ pipelines:
4
+ - pipelines
5
+ tasks:
6
+ - tasks
7
+ patterns:
8
+ pipeline: "*.pipeline.yaml"
9
+ task: "*.task.yaml"
10
+ `;
11
+ export const HELLO_PIPELINE_YAML = `id: hello
12
+ stages:
13
+ - id: hello
14
+ system_prompt: Say hello and emit a success envelope.
15
+ model: anthropic/claude-sonnet-4-5
16
+ `;
17
+ export const HELLO_TASK_YAML = `id: hello
18
+ goal: Run the hello pipeline scaffold.
19
+ `;
@@ -0,0 +1,10 @@
1
+ import type { OperatorCatalog } from "../runtime/stageAttemptBootstrap.js";
2
+ export type OperatorCatalogFlags = {
3
+ operatorCwd?: string;
4
+ operatorAgentDir?: string;
5
+ };
6
+ export declare function resolveOperatorCatalog(input: {
7
+ flags: OperatorCatalogFlags;
8
+ env: Record<string, string | undefined>;
9
+ defaultCwd: string;
10
+ }): OperatorCatalog;
@@ -0,0 +1,16 @@
1
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
2
+ function nonempty(value) {
3
+ if (value === undefined)
4
+ return undefined;
5
+ const trimmed = value.trim();
6
+ return trimmed.length === 0 ? undefined : trimmed;
7
+ }
8
+ export function resolveOperatorCatalog(input) {
9
+ const cwd = nonempty(input.flags.operatorCwd) ??
10
+ nonempty(input.env.STAGEFLOW_OPERATOR_CWD) ??
11
+ input.defaultCwd;
12
+ const agentDir = nonempty(input.flags.operatorAgentDir) ??
13
+ nonempty(input.env.STAGEFLOW_OPERATOR_AGENT_DIR) ??
14
+ getAgentDir();
15
+ return { cwd, agentDir };
16
+ }
@@ -0,0 +1,26 @@
1
+ import { type StartRunResult } from "../runtime/runManager.js";
2
+ import { type CliRunReportIo } from "./runOutput.js";
3
+ export declare const RUN_USAGE = "Usage:\n 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>]";
4
+ export type RunCommandIo = CliRunReportIo;
5
+ export type StartRunFn = (input: {
6
+ task: string;
7
+ pipeline: string;
8
+ checkoutOverride?: string;
9
+ skipGates?: boolean;
10
+ gitSha?: string;
11
+ ciPrUrl?: string;
12
+ ciJobUrl?: string;
13
+ }) => Promise<StartRunResult>;
14
+ export declare function completeCliRun(started: Extract<StartRunResult, {
15
+ ok: true;
16
+ }>, io?: RunCommandIo, options?: {
17
+ json?: boolean;
18
+ }): Promise<number>;
19
+ export declare function runRunCommand(args: string[], options?: {
20
+ cwd?: string;
21
+ projectRoot?: string;
22
+ isGitProject?: boolean;
23
+ io?: Partial<RunCommandIo>;
24
+ startRun?: StartRunFn;
25
+ env?: Record<string, string | undefined>;
26
+ }): Promise<number>;
@@ -0,0 +1,209 @@
1
+ import { PiAgentAdapter } from "../agent/piAdapter.js";
2
+ import { createRunStore } from "../runstore/createStore.js";
3
+ import { PipelineValidationError } from "../runtime/pipelineRunner.js";
4
+ import { RunManager } from "../runtime/runManager.js";
5
+ import { readStageExecutionMode } from "../runtime/stageConcurrency.js";
6
+ import { reportCliRun, } from "./runOutput.js";
7
+ import { resolveCiIdentity } from "./ciIdentity.js";
8
+ import { resolveOperatorCatalog } from "./operatorCatalog.js";
9
+ import { exitCodeForValidation, formatValidationHuman, formatValidationJson, } from "./validateOutput.js";
10
+ export const RUN_USAGE = `Usage:
11
+ 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>]`;
12
+ const defaultIo = {
13
+ log: (line) => console.log(line),
14
+ error: (line) => console.error(line),
15
+ };
16
+ function parseRunArgs(args) {
17
+ if (args.length === 0) {
18
+ return { help: false, json: false, skipGates: false };
19
+ }
20
+ if (args[0] === "--help" || args[0] === "-h") {
21
+ return { help: true, json: false, skipGates: false };
22
+ }
23
+ let task;
24
+ let pipeline;
25
+ let checkout;
26
+ let gitSha;
27
+ let ciPrUrl;
28
+ let ciJobUrl;
29
+ let operatorCwd;
30
+ let operatorAgentDir;
31
+ let json = false;
32
+ let skipGates = false;
33
+ let help = false;
34
+ for (let i = 0; i < args.length; i++) {
35
+ const arg = args[i];
36
+ if (arg === "--help" || arg === "-h") {
37
+ help = true;
38
+ }
39
+ else if (arg === "--task") {
40
+ const value = args[++i];
41
+ if (value === undefined || value.length === 0) {
42
+ throw new Error("Missing value for --task");
43
+ }
44
+ task = value;
45
+ }
46
+ else if (arg === "--pipeline") {
47
+ const value = args[++i];
48
+ if (value === undefined || value.length === 0) {
49
+ throw new Error("Missing value for --pipeline");
50
+ }
51
+ pipeline = value;
52
+ }
53
+ else if (arg === "--checkout") {
54
+ const value = args[++i];
55
+ if (value === undefined || value.length === 0) {
56
+ throw new Error("Missing value for --checkout");
57
+ }
58
+ checkout = value;
59
+ }
60
+ else if (arg === "--git-sha") {
61
+ const value = args[++i];
62
+ if (value === undefined || value.length === 0) {
63
+ throw new Error("Missing value for --git-sha");
64
+ }
65
+ gitSha = value;
66
+ }
67
+ else if (arg === "--ci-pr-url") {
68
+ const value = args[++i];
69
+ if (value === undefined || value.length === 0) {
70
+ throw new Error("Missing value for --ci-pr-url");
71
+ }
72
+ ciPrUrl = value;
73
+ }
74
+ else if (arg === "--ci-job-url") {
75
+ const value = args[++i];
76
+ if (value === undefined || value.length === 0) {
77
+ throw new Error("Missing value for --ci-job-url");
78
+ }
79
+ ciJobUrl = value;
80
+ }
81
+ else if (arg === "--operator-cwd") {
82
+ const value = args[++i];
83
+ if (value === undefined || value.length === 0) {
84
+ throw new Error("Missing value for --operator-cwd");
85
+ }
86
+ operatorCwd = value;
87
+ }
88
+ else if (arg === "--operator-agent-dir") {
89
+ const value = args[++i];
90
+ if (value === undefined || value.length === 0) {
91
+ throw new Error("Missing value for --operator-agent-dir");
92
+ }
93
+ operatorAgentDir = value;
94
+ }
95
+ else if (arg === "--json") {
96
+ json = true;
97
+ }
98
+ else if (arg === "--skip-gates") {
99
+ skipGates = true;
100
+ }
101
+ else if (arg.startsWith("-")) {
102
+ throw new Error(`Unknown flag: ${arg}`);
103
+ }
104
+ else {
105
+ throw new Error(`Unexpected argument: ${arg}`);
106
+ }
107
+ }
108
+ return {
109
+ help,
110
+ json,
111
+ skipGates,
112
+ task,
113
+ pipeline,
114
+ checkout,
115
+ gitSha,
116
+ ciPrUrl,
117
+ ciJobUrl,
118
+ operatorCwd,
119
+ operatorAgentDir,
120
+ };
121
+ }
122
+ function defaultStartRun(cwd, projectRoot, isGitProject, operatorCatalog) {
123
+ return async (input) => {
124
+ const store = createRunStore({ rootDir: projectRoot });
125
+ const manager = new RunManager({
126
+ agent: new PiAgentAdapter(),
127
+ store,
128
+ cwd,
129
+ projectRoot,
130
+ isGitProject,
131
+ operatorCatalog,
132
+ executionMode: readStageExecutionMode(process.env, "process"),
133
+ });
134
+ return manager.startRun(input);
135
+ };
136
+ }
137
+ export async function completeCliRun(started, io = defaultIo, options = {}) {
138
+ const result = await started.done;
139
+ return reportCliRun({ kind: "completion", result }, { json: options.json, io });
140
+ }
141
+ export async function runRunCommand(args, options = {}) {
142
+ const cwd = options.cwd ?? process.cwd();
143
+ const projectRoot = options.projectRoot ?? cwd;
144
+ const isGitProject = options.isGitProject ?? false;
145
+ const out = { ...defaultIo, ...options.io };
146
+ let parsed;
147
+ try {
148
+ parsed = parseRunArgs(args);
149
+ }
150
+ catch (err) {
151
+ const message = err instanceof Error ? err.message : String(err);
152
+ out.error(message);
153
+ out.error(RUN_USAGE);
154
+ return 1;
155
+ }
156
+ if (parsed.help) {
157
+ out.error(RUN_USAGE);
158
+ return 0;
159
+ }
160
+ if (!parsed.task || !parsed.pipeline) {
161
+ out.error("Missing --task and/or --pipeline");
162
+ out.error(RUN_USAGE);
163
+ return 1;
164
+ }
165
+ const operatorCatalog = resolveOperatorCatalog({
166
+ flags: {
167
+ operatorCwd: parsed.operatorCwd,
168
+ operatorAgentDir: parsed.operatorAgentDir,
169
+ },
170
+ env: options.env ?? process.env,
171
+ defaultCwd: cwd,
172
+ });
173
+ const startRun = options.startRun ??
174
+ defaultStartRun(cwd, projectRoot, isGitProject, operatorCatalog);
175
+ const identity = resolveCiIdentity({
176
+ flags: {
177
+ gitSha: parsed.gitSha,
178
+ ciPrUrl: parsed.ciPrUrl,
179
+ ciJobUrl: parsed.ciJobUrl,
180
+ },
181
+ env: options.env ?? process.env,
182
+ });
183
+ try {
184
+ const started = await startRun({
185
+ task: parsed.task,
186
+ pipeline: parsed.pipeline,
187
+ checkoutOverride: parsed.checkout,
188
+ ...(parsed.skipGates ? { skipGates: true } : {}),
189
+ ...identity,
190
+ });
191
+ if (!started.ok) {
192
+ return reportCliRun({ kind: "start-failure", started }, { json: parsed.json, io: out });
193
+ }
194
+ return completeCliRun(started, out, { json: parsed.json });
195
+ }
196
+ catch (err) {
197
+ if (err instanceof PipelineValidationError) {
198
+ if (parsed.json) {
199
+ out.log(formatValidationJson(err.result));
200
+ }
201
+ else {
202
+ out.error(formatValidationHuman(err.result));
203
+ }
204
+ return exitCodeForValidation(err.result);
205
+ }
206
+ out.error(err instanceof Error ? err.message : String(err));
207
+ return 1;
208
+ }
209
+ }