stageflow 0.1.0 → 0.2.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 (38) hide show
  1. package/README.md +33 -0
  2. package/dist/cli/ciIdentity.d.ts +14 -0
  3. package/dist/cli/ciIdentity.js +52 -0
  4. package/dist/cli/runCommand.d.ts +24 -0
  5. package/dist/cli/runCommand.js +168 -0
  6. package/dist/cli/runOutput.d.ts +19 -0
  7. package/dist/cli/runOutput.js +125 -0
  8. package/dist/cli.d.ts +11 -1
  9. package/dist/cli.js +41 -58
  10. package/dist/mcp/server.js +1 -1
  11. package/dist/runstore/port.d.ts +6 -0
  12. package/dist/runstore/sqlite/SqliteRunStore.js +20 -4
  13. package/dist/runstore/sqlite/schema.d.ts +1 -1
  14. package/dist/runstore/sqlite/schema.js +4 -1
  15. package/dist/runtime/pipelineRunner.d.ts +8 -0
  16. package/dist/runtime/pipelineRunner.js +10 -0
  17. package/dist/runtime/pipelineScheduler.d.ts +1 -0
  18. package/dist/runtime/pipelineScheduler.js +19 -3
  19. package/dist/runtime/resumeReconstruct.js +4 -3
  20. package/dist/runtime/runManager.d.ts +4 -0
  21. package/dist/runtime/runManager.js +14 -3
  22. package/dist/runtime/stageProcessLauncher.d.ts +1 -0
  23. package/dist/runtime/stageProcessLauncher.js +3 -0
  24. package/dist/runtime/stageRunner.d.ts +2 -0
  25. package/dist/runtime/stageRunner.js +9 -2
  26. package/dist/runtime/stageWorker.js +2 -0
  27. package/dist/runtime/stageWorkerProtocol.d.ts +1 -0
  28. package/package.json +1 -1
  29. package/dist/agent/cursorExtension.d.ts +0 -12
  30. package/dist/agent/cursorExtension.js +0 -88
  31. package/dist/runstore/catalog.d.ts +0 -8
  32. package/dist/runstore/catalog.js +0 -16
  33. package/dist/runstore/disk/DiskRunStore.d.ts +0 -30
  34. package/dist/runstore/disk/DiskRunStore.js +0 -238
  35. package/dist/runstore/layout.d.ts +0 -22
  36. package/dist/runstore/layout.js +0 -58
  37. package/dist/runtime/hitlSeams.d.ts +0 -38
  38. package/dist/runtime/hitlSeams.js +0 -2
@@ -27,6 +27,15 @@ function ensureCheckoutRootColumn(db) {
27
27
  db.exec(`ALTER TABLE runs ADD COLUMN checkout_root TEXT`);
28
28
  }
29
29
  }
30
+ function ensureCiIdentityColumns(db) {
31
+ const cols = db.prepare(`PRAGMA table_info(runs)`).all();
32
+ const names = new Set(cols.map((c) => c.name));
33
+ for (const name of ["git_sha", "ci_pr_url", "ci_job_url"]) {
34
+ if (!names.has(name)) {
35
+ db.exec(`ALTER TABLE runs ADD COLUMN ${name} TEXT`);
36
+ }
37
+ }
38
+ }
30
39
  function ensurePipelineDagColumn(db) {
31
40
  const cols = db.prepare(`PRAGMA table_info(runs)`).all();
32
41
  if (!cols.some((c) => c.name === "pipeline_dag_json")) {
@@ -87,6 +96,7 @@ export class SqliteRunStore {
87
96
  this.db.pragma(`busy_timeout = ${readSqliteBusyTimeoutMs()}`);
88
97
  this.db.exec(SCHEMA_SQL);
89
98
  ensureCheckoutRootColumn(this.db);
99
+ ensureCiIdentityColumns(this.db);
90
100
  ensurePipelineDagColumn(this.db);
91
101
  ensureStageExecutionsTable(this.db);
92
102
  ensureStageEventsAttemptColumn(this.db);
@@ -107,9 +117,9 @@ export class SqliteRunStore {
107
117
  const now = new Date().toISOString();
108
118
  this.db
109
119
  .prepare(`INSERT INTO runs
110
- (run_id, pipeline_id, task_id, task_yaml, status, created_at, updated_at, checkout_root, pipeline_dag_json)
120
+ (run_id, pipeline_id, task_id, task_yaml, status, created_at, updated_at, checkout_root, pipeline_dag_json, git_sha, ci_pr_url, ci_job_url)
111
121
  VALUES
112
- (@run_id, @pipeline_id, @task_id, @task_yaml, @status, @created_at, @updated_at, @checkout_root, @pipeline_dag_json)`)
122
+ (@run_id, @pipeline_id, @task_id, @task_yaml, @status, @created_at, @updated_at, @checkout_root, @pipeline_dag_json, @git_sha, @ci_pr_url, @ci_job_url)`)
113
123
  .run({
114
124
  run_id: runId,
115
125
  pipeline_id: input.pipelineId,
@@ -122,6 +132,9 @@ export class SqliteRunStore {
122
132
  pipeline_dag_json: input.pipelineDag
123
133
  ? JSON.stringify(input.pipelineDag)
124
134
  : null,
135
+ git_sha: input.gitSha ?? null,
136
+ ci_pr_url: input.ciPrUrl ?? null,
137
+ ci_job_url: input.ciJobUrl ?? null,
125
138
  });
126
139
  return { runId, workspaceDir };
127
140
  }
@@ -378,7 +391,7 @@ export class SqliteRunStore {
378
391
  async listRuns() {
379
392
  await this.ready();
380
393
  const rows = this.db
381
- .prepare(`SELECT run_id, pipeline_id, task_id, task_yaml, status, created_at, updated_at, checkout_root, pipeline_dag_json
394
+ .prepare(`SELECT run_id, pipeline_id, task_id, task_yaml, status, created_at, updated_at, checkout_root, pipeline_dag_json, git_sha, ci_pr_url, ci_job_url
382
395
  FROM runs ORDER BY created_at DESC`)
383
396
  .all();
384
397
  const summaries = [];
@@ -410,11 +423,14 @@ export class SqliteRunStore {
410
423
  task_id: row.task_id ?? undefined,
411
424
  updated_at: row.updated_at,
412
425
  ...(row.checkout_root != null ? { checkout_root: row.checkout_root } : {}),
426
+ ...(row.git_sha != null ? { git_sha: row.git_sha } : {}),
427
+ ...(row.ci_pr_url != null ? { ci_pr_url: row.ci_pr_url } : {}),
428
+ ...(row.ci_job_url != null ? { ci_job_url: row.ci_job_url } : {}),
413
429
  };
414
430
  }
415
431
  getRunRow(runId) {
416
432
  const row = this.db
417
- .prepare(`SELECT run_id, pipeline_id, task_id, task_yaml, status, created_at, updated_at, checkout_root, pipeline_dag_json
433
+ .prepare(`SELECT run_id, pipeline_id, task_id, task_yaml, status, created_at, updated_at, checkout_root, pipeline_dag_json, git_sha, ci_pr_url, ci_job_url
418
434
  FROM runs WHERE run_id = ?`)
419
435
  .get(runId);
420
436
  if (!row)
@@ -1 +1 @@
1
- export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS runs (\n run_id TEXT PRIMARY KEY,\n pipeline_id TEXT NOT NULL,\n task_id TEXT,\n task_yaml TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n checkout_root TEXT,\n pipeline_dag_json TEXT\n);\n\nCREATE TABLE IF NOT EXISTS stages (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n status TEXT,\n summary TEXT,\n envelope_json TEXT,\n started_at TEXT,\n finished_at TEXT,\n PRIMARY KEY (run_id, stage_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE TABLE IF NOT EXISTS stage_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n at TEXT NOT NULL,\n event TEXT NOT NULL,\n payload_json TEXT,\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_runs_status_created\n ON runs (status, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_runs_pipeline_created\n ON runs (pipeline_id, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_stage_events_run_stage_at\n ON stage_events (run_id, stage_id, at);\n\nCREATE TABLE IF NOT EXISTS stage_executions (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n envelope_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_stage_executions_run_stage\n ON stage_executions (run_id, stage_id, attempt);\n";
1
+ export declare const SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS runs (\n run_id TEXT PRIMARY KEY,\n pipeline_id TEXT NOT NULL,\n task_id TEXT,\n task_yaml TEXT NOT NULL,\n status TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n checkout_root TEXT,\n pipeline_dag_json TEXT,\n git_sha TEXT,\n ci_pr_url TEXT,\n ci_job_url TEXT\n);\n\nCREATE TABLE IF NOT EXISTS stages (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n status TEXT,\n summary TEXT,\n envelope_json TEXT,\n started_at TEXT,\n finished_at TEXT,\n PRIMARY KEY (run_id, stage_id),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE TABLE IF NOT EXISTS stage_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n at TEXT NOT NULL,\n event TEXT NOT NULL,\n payload_json TEXT,\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_runs_status_created\n ON runs (status, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_runs_pipeline_created\n ON runs (pipeline_id, created_at DESC);\n\nCREATE INDEX IF NOT EXISTS idx_stage_events_run_stage_at\n ON stage_events (run_id, stage_id, at);\n\nCREATE TABLE IF NOT EXISTS stage_executions (\n run_id TEXT NOT NULL,\n stage_id TEXT NOT NULL,\n attempt INTEGER NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n finished_at TEXT,\n envelope_json TEXT,\n PRIMARY KEY (run_id, stage_id, attempt),\n FOREIGN KEY (run_id) REFERENCES runs(run_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_stage_executions_run_stage\n ON stage_executions (run_id, stage_id, attempt);\n";
@@ -8,7 +8,10 @@ CREATE TABLE IF NOT EXISTS runs (
8
8
  created_at TEXT NOT NULL,
9
9
  updated_at TEXT NOT NULL,
10
10
  checkout_root TEXT,
11
- pipeline_dag_json TEXT
11
+ pipeline_dag_json TEXT,
12
+ git_sha TEXT,
13
+ ci_pr_url TEXT,
14
+ ci_job_url TEXT
12
15
  );
13
16
 
14
17
  CREATE TABLE IF NOT EXISTS stages (
@@ -8,8 +8,10 @@ import { type StageExecutionMode } from "./stageConcurrency.js";
8
8
  import { StageProcessLauncher } from "./stageProcessLauncher.js";
9
9
  import type { OperatorCatalog } from "./stageAttemptBootstrap.js";
10
10
  export { PipelineValidationError } from "./pipelineValidationError.js";
11
+ export type PipelineRunOutcome = "succeeded" | "failed" | "waiting";
11
12
  export type PipelineRunResult = {
12
13
  ok: boolean;
14
+ outcome: PipelineRunOutcome;
13
15
  runDir: string;
14
16
  runId: string;
15
17
  reason?: string;
@@ -34,6 +36,7 @@ export type PreparedPipeline = {
34
36
  executionMode?: StageExecutionMode;
35
37
  stageProcessLauncher?: StageProcessLauncher;
36
38
  operatorCatalog?: OperatorCatalog;
39
+ skipGates?: boolean;
37
40
  };
38
41
  export type ExecuteStagesOptions = {
39
42
  maxActiveStagesPerRun?: number;
@@ -58,6 +61,7 @@ export declare function runPipeline(options: {
58
61
  executionMode?: StageExecutionMode;
59
62
  stageProcessLauncher?: StageProcessLauncher;
60
63
  operatorCatalog?: OperatorCatalog;
64
+ skipGates?: boolean;
61
65
  }): Promise<PipelineRunResult>;
62
66
  /** Create the run immediately, then execute stages in the returned promise. */
63
67
  export declare function startPipeline(options: {
@@ -68,10 +72,14 @@ export declare function startPipeline(options: {
68
72
  pipeline: string;
69
73
  cwd?: string;
70
74
  checkoutOverride?: string;
75
+ gitSha?: string;
76
+ ciPrUrl?: string;
77
+ ciJobUrl?: string;
71
78
  hitl?: StageHitlController;
72
79
  maxActiveStagesPerRun?: number;
73
80
  executionMode?: StageExecutionMode;
74
81
  stageProcessLauncher?: StageProcessLauncher;
75
82
  operatorCatalog?: OperatorCatalog;
83
+ skipGates?: boolean;
76
84
  }): Promise<StartedPipeline>;
77
85
  export declare function resolveTaskPath(taskArg: string, cwd?: string): string;
@@ -46,6 +46,9 @@ async function preparePipeline(options) {
46
46
  taskYaml,
47
47
  taskId: task.id,
48
48
  checkoutRoot,
49
+ gitSha: options.gitSha,
50
+ ciPrUrl: options.ciPrUrl,
51
+ ciJobUrl: options.ciJobUrl,
49
52
  pipelineDag: buildPipelineDagSnapshotFromLoaded(loaded),
50
53
  });
51
54
  const executionMode = readStageExecutionMode(process.env, options.executionMode);
@@ -62,6 +65,7 @@ async function preparePipeline(options) {
62
65
  executionMode,
63
66
  stageProcessLauncher,
64
67
  operatorCatalog: options.operatorCatalog,
68
+ skipGates: options.skipGates,
65
69
  };
66
70
  }
67
71
  export async function executeStages(prepared, options) {
@@ -94,6 +98,7 @@ export async function runPipeline(options) {
94
98
  executionMode: options.executionMode,
95
99
  stageProcessLauncher: options.stageProcessLauncher,
96
100
  operatorCatalog: options.operatorCatalog,
101
+ skipGates: options.skipGates,
97
102
  });
98
103
  return executeStages(prepared, {
99
104
  maxActiveStagesPerRun: options.maxActiveStagesPerRun,
@@ -112,10 +117,14 @@ export async function startPipeline(options) {
112
117
  pipeline: options.pipeline,
113
118
  cwd,
114
119
  checkoutOverride: options.checkoutOverride,
120
+ gitSha: options.gitSha,
121
+ ciPrUrl: options.ciPrUrl,
122
+ ciJobUrl: options.ciJobUrl,
115
123
  hitl: options.hitl,
116
124
  executionMode: options.executionMode,
117
125
  stageProcessLauncher: options.stageProcessLauncher,
118
126
  operatorCatalog: options.operatorCatalog,
127
+ skipGates: options.skipGates,
119
128
  });
120
129
  const done = executeStages(prepared, {
121
130
  maxActiveStagesPerRun: options.maxActiveStagesPerRun,
@@ -125,6 +134,7 @@ export async function startPipeline(options) {
125
134
  await prepared.store.updateRunStatus(prepared.run.runId, "failed").catch(() => undefined);
126
135
  return {
127
136
  ok: false,
137
+ outcome: "failed",
128
138
  runDir: prepared.run.workspaceDir,
129
139
  runId: prepared.run.runId,
130
140
  reason: err instanceof Error ? err.message : String(err),
@@ -21,6 +21,7 @@ type SchedulerPreparedPipeline = {
21
21
  checkoutRoot?: string;
22
22
  hitl?: StageHitlController;
23
23
  operatorCatalog?: OperatorCatalog;
24
+ skipGates?: boolean;
24
25
  };
25
26
  export type { SchedulerPreparedPipeline };
26
27
  type RetryContext = {
@@ -93,7 +93,8 @@ export async function resumeRun(options) {
93
93
  const hasActive = [...hydrated.states.values()].some((s) => s === "active");
94
94
  if (hasActive) {
95
95
  return {
96
- ok: true,
96
+ ok: false,
97
+ outcome: "waiting",
97
98
  runDir: prepared.run.workspaceDir,
98
99
  runId: prepared.run.runId,
99
100
  };
@@ -107,6 +108,7 @@ export async function resumeRun(options) {
107
108
  await prepared.store.updateRunStatus(prepared.run.runId, allSucceeded ? "succeeded" : "failed");
108
109
  return {
109
110
  ok: allSucceeded,
111
+ outcome: allSucceeded ? "succeeded" : "failed",
110
112
  runDir: prepared.run.workspaceDir,
111
113
  runId: prepared.run.runId,
112
114
  ...(allSucceeded
@@ -344,6 +346,7 @@ export async function runPipelineDag(options) {
344
346
  ...(prepared.operatorCatalog !== undefined
345
347
  ? { operatorCatalog: prepared.operatorCatalog }
346
348
  : {}),
349
+ ...(prepared.skipGates ? { skipGates: true } : {}),
347
350
  });
348
351
  if (launchResult.type === "succeeded") {
349
352
  try {
@@ -377,6 +380,7 @@ export async function runPipelineDag(options) {
377
380
  factoryCwd: cwd,
378
381
  operatorCatalog: prepared.operatorCatalog,
379
382
  completedEnvelopes,
383
+ skipGates: prepared.skipGates,
380
384
  });
381
385
  if (isRunStageWaiting(result)) {
382
386
  await onStageFailure(stageId, WAIT_WITHOUT_WORKER_DISPATCH);
@@ -461,7 +465,12 @@ export async function runPipelineDag(options) {
461
465
  }
462
466
  const hasWaiting = [...states.values()].some((s) => s === "waiting");
463
467
  if (hasWaiting && !schedulingHalted) {
464
- return { ok: true, runDir: run.workspaceDir, runId: run.runId };
468
+ return {
469
+ ok: false,
470
+ outcome: "waiting",
471
+ runDir: run.workspaceDir,
472
+ runId: run.runId,
473
+ };
465
474
  }
466
475
  if (schedulingHalted) {
467
476
  markSkippedPending();
@@ -470,6 +479,7 @@ export async function runPipelineDag(options) {
470
479
  }
471
480
  return {
472
481
  ok: false,
482
+ outcome: "failed",
473
483
  runDir: run.workspaceDir,
474
484
  runId: run.runId,
475
485
  reason: firstFailureReason,
@@ -482,6 +492,7 @@ export async function runPipelineDag(options) {
482
492
  }
483
493
  return {
484
494
  ok: false,
495
+ outcome: "failed",
485
496
  runDir: run.workspaceDir,
486
497
  runId: run.runId,
487
498
  reason: firstFailureReason ?? "pipeline incomplete",
@@ -490,5 +501,10 @@ export async function runPipelineDag(options) {
490
501
  if (retryContext === undefined) {
491
502
  await store.updateRunStatus(run.runId, "succeeded");
492
503
  }
493
- return { ok: true, runDir: run.workspaceDir, runId: run.runId };
504
+ return {
505
+ ok: true,
506
+ outcome: "succeeded",
507
+ runDir: run.workspaceDir,
508
+ runId: run.runId,
509
+ };
494
510
  }
@@ -118,9 +118,10 @@ export async function reconstructAndContinue(ctx) {
118
118
  executionMode: ctx.executionMode,
119
119
  stageProcessLauncher: ctx.stageProcessLauncher,
120
120
  });
121
- return rest.ok
122
- ? { ok: true }
123
- : { ok: false, reason: rest.reason };
121
+ if (rest.outcome === "failed") {
122
+ return { ok: false, reason: rest.reason };
123
+ }
124
+ return { ok: true };
124
125
  }
125
126
  catch (err) {
126
127
  const reason = err instanceof Error
@@ -100,6 +100,10 @@ export declare class RunManager {
100
100
  pipeline: string;
101
101
  task?: string | TaskFile;
102
102
  checkoutOverride?: string;
103
+ skipGates?: boolean;
104
+ gitSha?: string;
105
+ ciPrUrl?: string;
106
+ ciJobUrl?: string;
103
107
  }): Promise<StartRunResult>;
104
108
  rerun(runId: string): Promise<StartRunResult>;
105
109
  retryStage(runId: string, stageId: string): Promise<RetryStageResult>;
@@ -347,7 +347,11 @@ export class RunManager {
347
347
  status: 400,
348
348
  };
349
349
  }
350
- return this.reserveAndStartPipeline(taskYaml, input.pipeline, `task file ${label}`, cwd, input.checkoutOverride);
350
+ return this.reserveAndStartPipeline(taskYaml, input.pipeline, `task file ${label}`, cwd, input.checkoutOverride, input.skipGates, {
351
+ gitSha: input.gitSha,
352
+ ciPrUrl: input.ciPrUrl,
353
+ ciJobUrl: input.ciJobUrl,
354
+ });
351
355
  }
352
356
  async rerun(runId) {
353
357
  const cwd = this.options.cwd ?? process.cwd();
@@ -549,7 +553,10 @@ export class RunManager {
549
553
  executionMode: this.executionMode,
550
554
  stageProcessLauncher: launcher,
551
555
  });
552
- return rest.ok ? { ok: true } : { ok: false, reason: rest.reason };
556
+ if (rest.outcome === "failed") {
557
+ return { ok: false, reason: rest.reason };
558
+ }
559
+ return { ok: true };
553
560
  }
554
561
  catch (err) {
555
562
  const reason = err instanceof Error
@@ -591,7 +598,7 @@ export class RunManager {
591
598
  this.attachedWaiting.delete(`${runId}\0${stageId}`);
592
599
  }
593
600
  }
594
- async reserveAndStartPipeline(taskYaml, pipelineId, taskLabel, cwd, checkoutOverride) {
601
+ async reserveAndStartPipeline(taskYaml, pipelineId, taskLabel, cwd, checkoutOverride, skipGates, ciIdentity) {
595
602
  let checkoutKey;
596
603
  try {
597
604
  const task = loadTaskFromYaml(taskYaml, taskLabel);
@@ -619,11 +626,15 @@ export class RunManager {
619
626
  pipeline: pipelineId,
620
627
  cwd,
621
628
  checkoutOverride,
629
+ gitSha: ciIdentity?.gitSha,
630
+ ciPrUrl: ciIdentity?.ciPrUrl,
631
+ ciJobUrl: ciIdentity?.ciJobUrl,
622
632
  hitl: this.hitl,
623
633
  maxActiveStagesPerRun: this.maxActiveStagesPerRun,
624
634
  executionMode: this.executionMode,
625
635
  stageProcessLauncher: this.stageProcessLauncher,
626
636
  operatorCatalog: this.options.operatorCatalog,
637
+ skipGates,
627
638
  });
628
639
  this.track(reserved.provisionalId, started.runId, started.done);
629
640
  return { ok: true, runId: started.runId, done: started.done };
@@ -8,6 +8,7 @@ export type StageLaunchInput = {
8
8
  attempt?: number;
9
9
  sessionFilePath?: string;
10
10
  operatorCatalog?: OperatorCatalog;
11
+ skipGates?: boolean;
11
12
  };
12
13
  export type StageLaunchResult = {
13
14
  type: "succeeded";
@@ -135,6 +135,9 @@ export class StageProcessLauncher {
135
135
  if (input.operatorCatalog?.agentDir !== undefined) {
136
136
  args.push("--operator-agent-dir", input.operatorCatalog.agentDir);
137
137
  }
138
+ if (input.skipGates) {
139
+ args.push("--skip-gates");
140
+ }
138
141
  const child = fork(this.cliEntry, args, {
139
142
  cwd: input.rootDir,
140
143
  env: { ...process.env, ...this.env, [SF_STAGE_WORKER]: "1" },
@@ -43,6 +43,7 @@ export type RunStageOptions = {
43
43
  factoryCwd?: string;
44
44
  operatorCatalog?: OperatorCatalog;
45
45
  completedEnvelopes?: Map<string, StageEnvelope>;
46
+ skipGates?: boolean;
46
47
  };
47
48
  /**
48
49
  * Yield loop: next() until completed; on wait, coordinator enterWait (KTD6).
@@ -55,5 +56,6 @@ export declare function runStageYieldLoop(options: {
55
56
  workerMode?: boolean;
56
57
  store?: RunStore;
57
58
  attemptCtx?: StageAttemptContext;
59
+ skipGates?: boolean;
58
60
  }): Promise<RunStageYieldLoopResult>;
59
61
  export declare function runStage(options: RunStageOptions): Promise<RunStageOutcome>;
@@ -73,10 +73,16 @@ async function finalizeStageResult(options) {
73
73
  * Yield loop: next() until completed; on wait, coordinator enterWait (KTD6).
74
74
  */
75
75
  export async function runStageYieldLoop(options) {
76
- const { handle, runId, stageId, hitl, workerMode, store, attemptCtx } = options;
76
+ const { handle, runId, stageId, hitl, workerMode, store, attemptCtx, skipGates } = options;
77
77
  while (true) {
78
78
  const event = await handle.next();
79
79
  if (event.status === "waiting_for_input") {
80
+ if (skipGates) {
81
+ return {
82
+ ok: false,
83
+ reason: "skip-gates: stage requested wait",
84
+ };
85
+ }
80
86
  if (workerMode) {
81
87
  if (!store) {
82
88
  return {
@@ -107,7 +113,7 @@ export async function runStageYieldLoop(options) {
107
113
  }
108
114
  }
109
115
  export async function runStage(options) {
110
- const { agent, store, runId, stage, task, dag, checkoutRoot, workspaceDir, hitl, skipStarted, existingHandle, workerMode, roots: rootsOverride, attemptCtx, factoryCwd, operatorCatalog, completedEnvelopes, } = options;
116
+ const { agent, store, runId, stage, task, dag, checkoutRoot, workspaceDir, hitl, skipStarted, existingHandle, workerMode, roots: rootsOverride, attemptCtx, factoryCwd, operatorCatalog, completedEnvelopes, skipGates, } = options;
111
117
  const attemptOpt = attemptCtx?.eventOptions();
112
118
  const baseRoots = rootsOverride ??
113
119
  buildStageRoots(workspaceDir ?? store.getWorkspaceDir(runId), stage.id, checkoutRoot, attemptCtx);
@@ -183,6 +189,7 @@ export async function runStage(options) {
183
189
  workerMode,
184
190
  store: workerMode ? store : undefined,
185
191
  attemptCtx,
192
+ skipGates,
186
193
  });
187
194
  }
188
195
  catch (err) {
@@ -68,6 +68,7 @@ export async function runStageWorker(input) {
68
68
  attemptCtx,
69
69
  factoryCwd: input.rootDir,
70
70
  operatorCatalog: input.operatorCatalog,
71
+ skipGates: input.skipGates,
71
72
  });
72
73
  return outcome;
73
74
  }
@@ -85,6 +86,7 @@ export async function runStageWorker(input) {
85
86
  attemptCtx,
86
87
  factoryCwd: input.rootDir,
87
88
  operatorCatalog: input.operatorCatalog,
89
+ skipGates: input.skipGates,
88
90
  });
89
91
  }
90
92
  finally {
@@ -15,6 +15,7 @@ export type StageWorkerInput = {
15
15
  attempt?: number;
16
16
  sessionFilePath?: string;
17
17
  operatorCatalog?: OperatorCatalog;
18
+ skipGates?: boolean;
18
19
  };
19
20
  export type StageWorkerResult = {
20
21
  type: "succeeded";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stageflow",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Stageflow — personal CLI pipeline runtime for configurable SDLC stages on Pi",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,12 +0,0 @@
1
- /**
2
- * Locate the pi-cursor-sdk extension entry without opening full global package
3
- * discovery. Stages stay sealed; only this allowlisted path is loaded.
4
- *
5
- * Resolution order:
6
- * 1. SOFTWARE_FACTORY_CURSOR_EXTENSION (absolute path to the extension .ts/.js)
7
- * 2. Path package from ~/.pi/agent/settings.json (same source interactive pi uses)
8
- * 3. npm install under ~/.pi/agent/npm/node_modules/pi-cursor-sdk
9
- * 4. Sibling checkout at ../pi-cursor-sdk relative to this repo
10
- */
11
- export declare function resolveCursorExtensionPath(): string | undefined;
12
- export declare function isCursorModelRef(modelRef: string): boolean;
@@ -1,88 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- /**
6
- * Locate the pi-cursor-sdk extension entry without opening full global package
7
- * discovery. Stages stay sealed; only this allowlisted path is loaded.
8
- *
9
- * Resolution order:
10
- * 1. SOFTWARE_FACTORY_CURSOR_EXTENSION (absolute path to the extension .ts/.js)
11
- * 2. Path package from ~/.pi/agent/settings.json (same source interactive pi uses)
12
- * 3. npm install under ~/.pi/agent/npm/node_modules/pi-cursor-sdk
13
- * 4. Sibling checkout at ../pi-cursor-sdk relative to this repo
14
- */
15
- export function resolveCursorExtensionPath() {
16
- const fromEnv = process.env.SOFTWARE_FACTORY_CURSOR_EXTENSION?.trim();
17
- if (fromEnv && existsSync(fromEnv)) {
18
- return path.resolve(fromEnv);
19
- }
20
- const fromSettings = resolveFromPiSettings();
21
- if (fromSettings) {
22
- return fromSettings;
23
- }
24
- const npmEntry = path.join(os.homedir(), ".pi", "agent", "npm", "node_modules", "pi-cursor-sdk", "src", "index.ts");
25
- if (existsSync(npmEntry)) {
26
- return npmEntry;
27
- }
28
- const here = path.dirname(fileURLToPath(import.meta.url));
29
- const sibling = path.resolve(here, "../../../pi-cursor-sdk/src/index.ts");
30
- if (existsSync(sibling)) {
31
- return sibling;
32
- }
33
- return undefined;
34
- }
35
- export function isCursorModelRef(modelRef) {
36
- const slash = modelRef.indexOf("/");
37
- if (slash <= 0) {
38
- return false;
39
- }
40
- return modelRef.slice(0, slash).toLowerCase() === "cursor";
41
- }
42
- function resolveFromPiSettings() {
43
- const settingsPath = path.join(os.homedir(), ".pi", "agent", "settings.json");
44
- if (!existsSync(settingsPath)) {
45
- return undefined;
46
- }
47
- let settings;
48
- try {
49
- settings = JSON.parse(readFileSync(settingsPath, "utf8"));
50
- }
51
- catch {
52
- return undefined;
53
- }
54
- if (!Array.isArray(settings.packages)) {
55
- return undefined;
56
- }
57
- const agentDir = path.join(os.homedir(), ".pi", "agent");
58
- for (const entry of settings.packages) {
59
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
60
- continue;
61
- }
62
- const pkg = entry;
63
- if (typeof pkg.source !== "string") {
64
- continue;
65
- }
66
- if (!pkg.source.includes("pi-cursor-sdk")) {
67
- continue;
68
- }
69
- const packageRoot = path.resolve(agentDir, pkg.source);
70
- if (Array.isArray(pkg.extensions)) {
71
- for (const ext of pkg.extensions) {
72
- if (typeof ext !== "string") {
73
- continue;
74
- }
75
- const rel = ext.replace(/^\+/, "");
76
- const full = path.resolve(packageRoot, rel);
77
- if (existsSync(full)) {
78
- return full;
79
- }
80
- }
81
- }
82
- const declared = path.join(packageRoot, "src", "index.ts");
83
- if (existsSync(declared)) {
84
- return declared;
85
- }
86
- }
87
- return undefined;
88
- }
@@ -1,8 +0,0 @@
1
- /**
2
- * @deprecated Prefer RunStore.listRuns / readRun via createRunStore.
3
- */
4
- export { deriveStatusFromStages, type StageLogEvent, type StageSnapshot, type RunSummary, type RunDetail, } from "./port.js";
5
- import type { RunDetail, RunSummary, StageLogEvent } from "./port.js";
6
- export declare function listRuns(rootDir: string): Promise<RunSummary[]>;
7
- export declare function readRun(rootDir: string, runId: string): Promise<RunDetail>;
8
- export declare function readStageLog(runDir: string, stageId: string): Promise<StageLogEvent[]>;
@@ -1,16 +0,0 @@
1
- /**
2
- * @deprecated Prefer RunStore.listRuns / readRun via createRunStore.
3
- */
4
- export { deriveStatusFromStages, } from "./port.js";
5
- import { DiskRunStore } from "./disk/DiskRunStore.js";
6
- import { storeRootFor } from "./paths.js";
7
- import { readStageLogFromDisk } from "./disk/catalogHelpers.js";
8
- export async function listRuns(rootDir) {
9
- return new DiskRunStore(storeRootFor(rootDir)).listRuns();
10
- }
11
- export async function readRun(rootDir, runId) {
12
- return new DiskRunStore(storeRootFor(rootDir)).readRun(runId);
13
- }
14
- export async function readStageLog(runDir, stageId) {
15
- return readStageLogFromDisk(runDir, stageId);
16
- }
@@ -1,30 +0,0 @@
1
- import type { StageEnvelope } from "../../types/envelope.js";
2
- import type { StageLogLine } from "../../agent/activity.js";
3
- import type { CreateRunInput, CreatedRun, RunMeta, RunStatus, RunStore, StageExecution, StageExecutionPatch, StageLogEvent } from "../port.js";
4
- export declare class DiskRunStore implements RunStore {
5
- private readonly storeRoot;
6
- constructor(storeRoot: string);
7
- getWorkspaceDir(runId: string): string;
8
- createRun(input: CreateRunInput): Promise<CreatedRun>;
9
- updateRunStatus(runId: string, status: RunStatus): Promise<void>;
10
- readRunMeta(runId: string): Promise<RunMeta>;
11
- readTaskYaml(runId: string): Promise<string>;
12
- ensureStageWorkspace(runId: string, stageId: string): Promise<void>;
13
- ensureAttemptWorkspace(runId: string, stageId: string, attempt: number): Promise<void>;
14
- createStageExecution(runId: string, stageId: string): Promise<StageExecution>;
15
- listStageExecutions(runId: string, stageId: string): Promise<StageExecution[]>;
16
- getLatestStageExecution(runId: string, stageId: string): Promise<StageExecution | null>;
17
- countStageAttempts(runId: string, stageId: string): Promise<number>;
18
- getStageExecution(runId: string, stageId: string, attempt: number): Promise<StageExecution>;
19
- updateStageExecution(runId: string, stageId: string, attempt: number, patch: StageExecutionPatch): Promise<void>;
20
- writeEnvelope(runId: string, stageId: string, envelope: StageEnvelope, options?: {
21
- attempt?: number;
22
- }): Promise<void>;
23
- readEnvelope(runId: string, stageId: string): Promise<StageEnvelope>;
24
- appendStageEvent(runId: string, stageId: string, event: StageLogLine, options?: {
25
- attempt?: number;
26
- }): Promise<void>;
27
- listStageEvents(runId: string, stageId: string, attempt?: number): Promise<StageLogEvent[]>;
28
- listRuns(): Promise<import("../port.js").RunSummary[]>;
29
- readRun(runId: string): Promise<import("../port.js").RunDetail>;
30
- }