stageflow 0.2.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/README.md +179 -14
  2. package/dist/agent/activity.d.ts +12 -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/cursorProvider.d.ts +1 -10
  7. package/dist/agent/cursorProvider.js +18 -10
  8. package/dist/agent/fakeAgent.js +15 -7
  9. package/dist/agent/piAdapter.d.ts +18 -1
  10. package/dist/agent/piAdapter.js +115 -45
  11. package/dist/agent/port.d.ts +7 -0
  12. package/dist/agent/port.js +3 -0
  13. package/dist/agent/providerAuth.js +12 -7
  14. package/dist/catalog/displayCatalogPath.d.ts +12 -0
  15. package/dist/catalog/displayCatalogPath.js +28 -0
  16. package/dist/cli/artifactCommand.d.ts +10 -0
  17. package/dist/cli/artifactCommand.js +120 -0
  18. package/dist/cli/envelopeCommand.d.ts +32 -0
  19. package/dist/cli/envelopeCommand.js +285 -0
  20. package/dist/cli/exportRunCommand.d.ts +10 -0
  21. package/dist/cli/exportRunCommand.js +150 -0
  22. package/dist/cli/handoffFormat.d.ts +22 -0
  23. package/dist/cli/handoffFormat.js +61 -0
  24. package/dist/cli/initCommand.d.ts +9 -0
  25. package/dist/cli/initCommand.js +71 -0
  26. package/dist/cli/initTemplates.d.ts +3 -0
  27. package/dist/cli/initTemplates.js +19 -0
  28. package/dist/cli/operatorCatalog.d.ts +10 -0
  29. package/dist/cli/operatorCatalog.js +16 -0
  30. package/dist/cli/runCommand.d.ts +7 -1
  31. package/dist/cli/runCommand.js +83 -11
  32. package/dist/cli/runOutput.d.ts +4 -1
  33. package/dist/cli/runOutput.js +36 -8
  34. package/dist/cli/skillsCommand.d.ts +16 -0
  35. package/dist/cli/skillsCommand.js +459 -0
  36. package/dist/cli/validateCommand.d.ts +1 -1
  37. package/dist/cli/validateCommand.js +20 -3
  38. package/dist/cli.d.ts +1 -0
  39. package/dist/cli.js +128 -22
  40. package/dist/config/browseCatalog.d.ts +45 -0
  41. package/dist/config/browseCatalog.js +170 -0
  42. package/dist/config/createPipeline.d.ts +12 -3
  43. package/dist/config/createPipeline.js +292 -100
  44. package/dist/config/createStage.d.ts +10 -4
  45. package/dist/config/createStage.js +44 -14
  46. package/dist/config/listConfig.d.ts +14 -19
  47. package/dist/config/listConfig.js +26 -191
  48. package/dist/config/listModelsFromManifest.d.ts +2 -0
  49. package/dist/config/listModelsFromManifest.js +4 -0
  50. package/dist/config/loadOutcome.d.ts +4 -0
  51. package/dist/config/loadPipeline.d.ts +1 -15
  52. package/dist/config/loadPipeline.js +88 -142
  53. package/dist/config/loadStage.d.ts +4 -0
  54. package/dist/config/loadStage.js +60 -32
  55. package/dist/config/loadStageflowManifest.d.ts +5 -0
  56. package/dist/config/loadStageflowManifest.js +157 -0
  57. package/dist/config/mergePipelineIncludes.d.ts +9 -0
  58. package/dist/config/mergePipelineIncludes.js +141 -0
  59. package/dist/config/normalizePipelineStageEntry.d.ts +18 -0
  60. package/dist/config/normalizePipelineStageEntry.js +210 -0
  61. package/dist/config/pipelineStageKeys.d.ts +4 -0
  62. package/dist/config/pipelineStageKeys.js +14 -0
  63. package/dist/config/resolveCatalogContext.d.ts +13 -0
  64. package/dist/config/resolveCatalogContext.js +13 -0
  65. package/dist/config/resolveForkEmitContext.d.ts +4 -0
  66. package/dist/config/resolveForkEmitContext.js +34 -0
  67. package/dist/config/resolvePipelineDag.d.ts +5 -1
  68. package/dist/config/resolvePipelineDag.js +86 -36
  69. package/dist/config/scanCatalogPaths.d.ts +4 -0
  70. package/dist/config/scanCatalogPaths.js +83 -0
  71. package/dist/config/validateCatalog.d.ts +22 -4
  72. package/dist/config/validateCatalog.js +169 -132
  73. package/dist/envelope/check.d.ts +2 -0
  74. package/dist/envelope/check.js +58 -0
  75. package/dist/envelope/cloneForks.d.ts +2 -0
  76. package/dist/envelope/cloneForks.js +62 -0
  77. package/dist/envelope/forkChoice.d.ts +3 -0
  78. package/dist/envelope/forkChoice.js +47 -0
  79. package/dist/index.d.ts +6 -0
  80. package/dist/index.js +6 -0
  81. package/dist/mcp/catalogTools.d.ts +3 -0
  82. package/dist/mcp/catalogTools.js +187 -0
  83. package/dist/mcp/controlTools.d.ts +3 -0
  84. package/dist/mcp/controlTools.js +147 -0
  85. package/dist/mcp/deps.d.ts +11 -0
  86. package/dist/mcp/deps.js +1 -0
  87. package/dist/mcp/projectRun.d.ts +1 -37
  88. package/dist/mcp/projectRun.js +1 -57
  89. package/dist/mcp/resources.d.ts +5 -0
  90. package/dist/mcp/resources.js +45 -0
  91. package/dist/mcp/server.d.ts +16 -3
  92. package/dist/mcp/server.js +159 -3
  93. package/dist/mcp/toolResults.d.ts +7 -0
  94. package/dist/mcp/toolResults.js +6 -0
  95. package/dist/mcp/tools.d.ts +2 -7
  96. package/dist/mcp/tools.js +52 -86
  97. package/dist/mcp/waitRun.d.ts +46 -0
  98. package/dist/mcp/waitRun.js +146 -0
  99. package/dist/mcp/waitingGates.d.ts +12 -0
  100. package/dist/mcp/waitingGates.js +83 -0
  101. package/dist/project/findProjectRoot.d.ts +2 -0
  102. package/dist/project/findProjectRoot.js +51 -0
  103. package/dist/project/globalHome.d.ts +2 -0
  104. package/dist/project/globalHome.js +19 -0
  105. package/dist/project/resolveProjectContext.d.ts +9 -0
  106. package/dist/project/resolveProjectContext.js +38 -0
  107. package/dist/project/resolveStageflowContext.d.ts +11 -0
  108. package/dist/project/resolveStageflowContext.js +66 -0
  109. package/dist/projection/projectRun.d.ts +44 -0
  110. package/dist/projection/projectRun.js +71 -0
  111. package/dist/prompt/priorEnvelope.d.ts +1 -1
  112. package/dist/prompt/priorEnvelope.js +4 -1
  113. package/dist/runstore/normalizeCatalogPath.d.ts +1 -0
  114. package/dist/runstore/normalizeCatalogPath.js +4 -0
  115. package/dist/runstore/paths.js +2 -0
  116. package/dist/runstore/pipelineDagSnapshot.d.ts +9 -0
  117. package/dist/runstore/pipelineDagSnapshot.js +90 -1
  118. package/dist/runstore/port.d.ts +22 -2
  119. package/dist/runstore/port.js +4 -1
  120. package/dist/runstore/runProjection.d.ts +1 -1
  121. package/dist/runstore/runProjection.js +25 -4
  122. package/dist/runstore/sqlite/SqliteRunStore.d.ts +3 -2
  123. package/dist/runstore/sqlite/SqliteRunStore.js +65 -7
  124. package/dist/runstore/sqlite/schema.d.ts +1 -1
  125. package/dist/runstore/sqlite/schema.js +4 -1
  126. package/dist/runstore/stageInstanceId.d.ts +4 -0
  127. package/dist/runstore/stageInstanceId.js +23 -0
  128. package/dist/runstore/syntheticStageSnapshot.d.ts +1 -1
  129. package/dist/runstore/syntheticStageSnapshot.js +2 -1
  130. package/dist/runstore/trackProjection.js +28 -12
  131. package/dist/runstore/workspaceLayout.d.ts +1 -0
  132. package/dist/runstore/workspaceLayout.js +1 -1
  133. package/dist/runtime/cloneSchedule.d.ts +18 -0
  134. package/dist/runtime/cloneSchedule.js +233 -0
  135. package/dist/runtime/credentialBinding.d.ts +8 -6
  136. package/dist/runtime/credentialBinding.js +18 -24
  137. package/dist/runtime/envelopeRouting.d.ts +2 -1
  138. package/dist/runtime/envelopeRouting.js +80 -13
  139. package/dist/runtime/pipelineRunner.d.ts +3 -0
  140. package/dist/runtime/pipelineRunner.js +15 -1
  141. package/dist/runtime/pipelineScheduler.d.ts +8 -2
  142. package/dist/runtime/pipelineScheduler.js +215 -39
  143. package/dist/runtime/reloadRunCatalog.d.ts +5 -0
  144. package/dist/runtime/reloadRunCatalog.js +44 -0
  145. package/dist/runtime/resumeReconstruct.d.ts +1 -0
  146. package/dist/runtime/resumeReconstruct.js +31 -10
  147. package/dist/runtime/runChangeBus.d.ts +18 -0
  148. package/dist/runtime/runChangeBus.js +83 -0
  149. package/dist/runtime/runManager.d.ts +4 -0
  150. package/dist/runtime/runManager.js +36 -16
  151. package/dist/runtime/settingsFile.d.ts +12 -0
  152. package/dist/runtime/settingsFile.js +87 -6
  153. package/dist/runtime/stageAttemptBootstrap.d.ts +1 -0
  154. package/dist/runtime/stageAttemptBootstrap.js +18 -7
  155. package/dist/runtime/stageProcessLauncher.js +3 -0
  156. package/dist/runtime/stageRecovery.js +3 -1
  157. package/dist/runtime/stageRunner.d.ts +1 -0
  158. package/dist/runtime/stageRunner.js +17 -15
  159. package/dist/runtime/stageWorker.js +13 -10
  160. package/dist/server/bootstrap.d.ts +32 -0
  161. package/dist/server/bootstrap.js +56 -0
  162. package/dist/server/createHttpHost.d.ts +34 -0
  163. package/dist/server/createHttpHost.js +70 -0
  164. package/dist/server/http.d.ts +6 -13
  165. package/dist/server/http.js +297 -352
  166. package/dist/server/mcpHost.d.ts +22 -0
  167. package/dist/server/mcpHost.js +21 -0
  168. package/dist/server/operatorResults.d.ts +13 -0
  169. package/dist/server/operatorResults.js +22 -0
  170. package/dist/tools/emitStageEnvelope.d.ts +12 -1
  171. package/dist/tools/emitStageEnvelope.js +27 -1
  172. package/dist/types/envelope.d.ts +3 -0
  173. package/dist/types/forkChoice.d.ts +30 -0
  174. package/dist/types/forkChoice.js +1 -0
  175. package/dist/types/pipeline.d.ts +59 -2
  176. package/dist/types/stageflowManifest.d.ts +23 -0
  177. package/dist/types/stageflowManifest.js +1 -0
  178. package/dist/ui/assets/{index-DCsDopak.css → index-C3N7MXAC.css} +1 -1
  179. package/dist/ui/assets/index-CSoGDA3A.js +118 -0
  180. package/dist/ui/index.html +3 -2
  181. package/dist/ui/stageflow-icon.svg +12 -0
  182. package/package.json +8 -5
  183. package/skills/install-suite.sh +107 -0
  184. package/skills/stageflow/SKILL.md +26 -0
  185. package/skills/stageflow/references/control-surface.md +26 -0
  186. package/skills/stageflow/scripts/detect-host.mjs +54 -0
  187. package/skills/stageflow/scripts/detect-host.test.mjs +97 -0
  188. package/skills/stageflow-author/SKILL.md +58 -0
  189. package/skills/stageflow-author/assets/examples/branch-decision/hotfix.yaml +10 -0
  190. package/skills/stageflow-author/assets/examples/branch-decision/release-gate.pipeline.yaml +12 -0
  191. package/skills/stageflow-author/assets/examples/branch-decision/run-tests.yaml +13 -0
  192. package/skills/stageflow-author/assets/examples/branch-decision/ship.yaml +10 -0
  193. package/skills/stageflow-author/assets/examples/linear-review/draft.yaml +9 -0
  194. package/skills/stageflow-author/assets/examples/linear-review/publish.yaml +10 -0
  195. package/skills/stageflow-author/assets/examples/linear-review/review-loop.pipeline.yaml +10 -0
  196. package/skills/stageflow-author/assets/examples/linear-review/review.yaml +15 -0
  197. package/skills/stageflow-author/assets/examples/non-sdlc-digest/gather.yaml +9 -0
  198. package/skills/stageflow-author/assets/examples/non-sdlc-digest/research-digest.pipeline.yaml +10 -0
  199. package/skills/stageflow-author/assets/examples/non-sdlc-digest/send.yaml +10 -0
  200. package/skills/stageflow-author/assets/examples/non-sdlc-digest/summarize.yaml +9 -0
  201. package/skills/stageflow-author/references/catalog-mapping.md +131 -0
  202. package/skills/stageflow-author/references/catalog-write-conventions.md +29 -0
  203. package/skills/stageflow-author/references/stage-prompt-template.md +55 -0
  204. package/skills/stageflow-author/references/validate-and-report.md +38 -0
  205. package/skills/stageflow-delegate/SKILL.md +18 -0
  206. package/skills/stageflow-delegate/references/authoring-or-run.md +15 -0
  207. package/skills/stageflow-delegate/references/example-walkthrough.md +30 -0
  208. package/skills/stageflow-delegate/references/pattern-detection.md +44 -0
  209. package/skills/stageflow-run/SKILL.md +151 -0
  210. package/skills/stageflow-run/references/mcp-call.md +17 -0
  211. package/skills/stageflow-run/references/task-and-pipeline-selection.md +52 -0
  212. package/skills/stageflow-run/scripts/mcp-call.mjs +299 -0
  213. package/skills/stageflow-run/scripts/mcp-call.test.mjs +271 -0
  214. package/skills/stageflow-session-capture/SKILL.md +94 -0
  215. package/skills/stageflow-session-capture/assets/example-pipeline/example.pipeline.yaml +7 -0
  216. package/skills/stageflow-session-capture/assets/example-pipeline/implement.yaml +8 -0
  217. package/skills/stageflow-session-capture/assets/example-pipeline/research.yaml +8 -0
  218. package/skills/stageflow-session-capture/references/catalog-authoring.md +40 -0
  219. package/skills/stageflow-session-capture/references/transcript-sources.md +25 -0
  220. package/skills/stageflow-session-capture/scripts/check-provider-gate.mjs +43 -0
  221. package/skills/stageflow-session-capture/scripts/locate-session-transcript.mjs +141 -0
  222. package/skills/stageflow-session-capture/scripts/resolve-catalog-id.mjs +119 -0
  223. package/skills/stageflow-setup/SKILL.md +110 -0
  224. package/dist/ui/assets/index-Cry0Tpfx.js +0 -118
@@ -1,6 +1,6 @@
1
1
  import type { StageEnvelope } from "../../types/envelope.js";
2
2
  import type { StageLogLine } from "../../agent/activity.js";
3
- import { type CreateRunInput, type CreatedRun, type RunDetail, type RunMeta, type RunStatus, type RunStore, type RunSummary, type StageExecution, type StageExecutionPatch, type StageLogEvent } from "../port.js";
3
+ import { type CreateRunInput, type CreatedRun, type ListRunsFilter, type RunDetail, type RunMeta, type RunPipelineDagSnapshot, type RunStatus, type RunStore, type RunSummary, type StageExecution, type StageExecutionPatch, type StageLogEvent } from "../port.js";
4
4
  export declare class SqliteRunStore implements RunStore {
5
5
  private readonly storeRoot;
6
6
  private readonly db;
@@ -11,6 +11,7 @@ export declare class SqliteRunStore implements RunStore {
11
11
  getWorkspaceDir(runId: string): string;
12
12
  createRun(input: CreateRunInput): Promise<CreatedRun>;
13
13
  updateRunStatus(runId: string, status: RunStatus): Promise<void>;
14
+ updatePipelineDag(runId: string, dag: RunPipelineDagSnapshot): Promise<void>;
14
15
  readRunMeta(runId: string): Promise<RunMeta>;
15
16
  readTaskYaml(runId: string): Promise<string>;
16
17
  ensureStageWorkspace(runId: string, stageId: string): Promise<void>;
@@ -37,7 +38,7 @@ export declare class SqliteRunStore implements RunStore {
37
38
  */
38
39
  private updateStageStatusDenorm;
39
40
  listStageEvents(runId: string, stageId: string, attempt?: number): Promise<StageLogEvent[]>;
40
- listRuns(): Promise<RunSummary[]>;
41
+ listRuns(filter?: ListRunsFilter): Promise<RunSummary[]>;
41
42
  readRun(runId: string): Promise<RunDetail>;
42
43
  private readPipelineDagSnapshotFromRow;
43
44
  private runMetaFromRow;
@@ -2,6 +2,7 @@ import Database from "better-sqlite3";
2
2
  import { mkdir } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { projectRunDetail, projectRunSummary, orderStageSnapshots } from "../runProjection.js";
5
+ import { normalizeCatalogPath } from "../normalizeCatalogPath.js";
5
6
  import { buildStageSnapshotFromStore } from "../stageSnapshot.js";
6
7
  import { parsePipelineDagSnapshot } from "../pipelineDagSnapshot.js";
7
8
  import { newRunId, runWorkspaceDir } from "../paths.js";
@@ -42,6 +43,15 @@ function ensurePipelineDagColumn(db) {
42
43
  db.exec(`ALTER TABLE runs ADD COLUMN pipeline_dag_json TEXT`);
43
44
  }
44
45
  }
46
+ function ensureRunLocatorColumns(db) {
47
+ const cols = db.prepare(`PRAGMA table_info(runs)`).all();
48
+ const names = new Set(cols.map((c) => c.name));
49
+ for (const name of ["pipeline_path", "task_path", "project_root"]) {
50
+ if (!names.has(name)) {
51
+ db.exec(`ALTER TABLE runs ADD COLUMN ${name} TEXT`);
52
+ }
53
+ }
54
+ }
45
55
  function ensureStageExecutionsTable(db) {
46
56
  db.exec(`
47
57
  CREATE TABLE IF NOT EXISTS stage_executions (
@@ -98,6 +108,7 @@ export class SqliteRunStore {
98
108
  ensureCheckoutRootColumn(this.db);
99
109
  ensureCiIdentityColumns(this.db);
100
110
  ensurePipelineDagColumn(this.db);
111
+ ensureRunLocatorColumns(this.db);
101
112
  ensureStageExecutionsTable(this.db);
102
113
  ensureStageEventsAttemptColumn(this.db);
103
114
  this.migratePromise = importDiskRunsIfEmpty(this.db, storeRoot).then(() => undefined);
@@ -115,11 +126,20 @@ export class SqliteRunStore {
115
126
  const workspaceDir = this.getWorkspaceDir(runId);
116
127
  await mkdir(path.join(workspaceDir, "stages"), { recursive: true });
117
128
  const now = new Date().toISOString();
129
+ const pipelinePath = input.pipelinePath
130
+ ? normalizeCatalogPath(input.pipelinePath)
131
+ : null;
132
+ const taskPath = input.taskPath
133
+ ? normalizeCatalogPath(input.taskPath)
134
+ : null;
135
+ const projectRoot = input.projectRoot
136
+ ? normalizeCatalogPath(input.projectRoot)
137
+ : null;
118
138
  this.db
119
139
  .prepare(`INSERT INTO runs
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)
140
+ (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, pipeline_path, task_path, project_root)
121
141
  VALUES
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)`)
142
+ (@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, @pipeline_path, @task_path, @project_root)`)
123
143
  .run({
124
144
  run_id: runId,
125
145
  pipeline_id: input.pipelineId,
@@ -135,6 +155,9 @@ export class SqliteRunStore {
135
155
  git_sha: input.gitSha ?? null,
136
156
  ci_pr_url: input.ciPrUrl ?? null,
137
157
  ci_job_url: input.ciJobUrl ?? null,
158
+ pipeline_path: pipelinePath,
159
+ task_path: taskPath,
160
+ project_root: projectRoot,
138
161
  });
139
162
  return { runId, workspaceDir };
140
163
  }
@@ -151,6 +174,19 @@ export class SqliteRunStore {
151
174
  throw new Error(`Run not found: ${runId}`);
152
175
  }
153
176
  }
177
+ async updatePipelineDag(runId, dag) {
178
+ await this.ready();
179
+ const result = this.db
180
+ .prepare(`UPDATE runs SET pipeline_dag_json = @pipeline_dag_json, updated_at = @updated_at WHERE run_id = @run_id`)
181
+ .run({
182
+ run_id: runId,
183
+ pipeline_dag_json: JSON.stringify(dag),
184
+ updated_at: new Date().toISOString(),
185
+ });
186
+ if (result.changes === 0) {
187
+ throw new Error(`Run not found: ${runId}`);
188
+ }
189
+ }
154
190
  async readRunMeta(runId) {
155
191
  await this.ready();
156
192
  return this.runMetaFromRow(this.getRunRow(runId));
@@ -388,12 +424,29 @@ export class SqliteRunStore {
388
424
  await this.ready();
389
425
  return this.loadEvents(runId, stageId, attempt);
390
426
  }
391
- async listRuns() {
427
+ async listRuns(filter) {
392
428
  await this.ready();
429
+ const clauses = [];
430
+ const params = [];
431
+ if (filter?.status !== undefined) {
432
+ clauses.push("status = ?");
433
+ params.push(filter.status);
434
+ }
435
+ if (filter?.since !== undefined) {
436
+ clauses.push("created_at >= ?");
437
+ params.push(filter.since);
438
+ }
439
+ if (filter?.pipeline !== undefined && filter.pipeline.trim().length > 0) {
440
+ const pipeline = filter.pipeline.trim();
441
+ const normalized = normalizeCatalogPath(pipeline);
442
+ clauses.push("(pipeline_id = ? OR pipeline_path = ? OR pipeline_path = ?)");
443
+ params.push(pipeline, pipeline, normalized);
444
+ }
445
+ const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
393
446
  const rows = this.db
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
395
- FROM runs ORDER BY created_at DESC`)
396
- .all();
447
+ .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, pipeline_path, task_path, project_root
448
+ FROM runs ${where} ORDER BY created_at DESC`)
449
+ .all(...params);
397
450
  const summaries = [];
398
451
  for (const row of rows) {
399
452
  const dagSnapshot = this.readPipelineDagSnapshotFromRow(row);
@@ -415,6 +468,7 @@ export class SqliteRunStore {
415
468
  return parsePipelineDagSnapshot(JSON.parse(row.pipeline_dag_json));
416
469
  }
417
470
  runMetaFromRow(row) {
471
+ const pipeline_dag = this.readPipelineDagSnapshotFromRow(row);
418
472
  return {
419
473
  run_id: row.run_id,
420
474
  pipeline_id: row.pipeline_id,
@@ -426,11 +480,15 @@ export class SqliteRunStore {
426
480
  ...(row.git_sha != null ? { git_sha: row.git_sha } : {}),
427
481
  ...(row.ci_pr_url != null ? { ci_pr_url: row.ci_pr_url } : {}),
428
482
  ...(row.ci_job_url != null ? { ci_job_url: row.ci_job_url } : {}),
483
+ ...(row.pipeline_path != null ? { pipeline_path: row.pipeline_path } : {}),
484
+ ...(row.task_path != null ? { task_path: row.task_path } : {}),
485
+ ...(row.project_root != null ? { project_root: row.project_root } : {}),
486
+ ...(pipeline_dag ? { pipeline_dag } : {}),
429
487
  };
430
488
  }
431
489
  getRunRow(runId) {
432
490
  const row = this.db
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
491
+ .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, pipeline_path, task_path, project_root
434
492
  FROM runs WHERE run_id = ?`)
435
493
  .get(runId);
436
494
  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 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";
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 pipeline_path TEXT,\n task_path TEXT,\n project_root 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";
@@ -11,7 +11,10 @@ CREATE TABLE IF NOT EXISTS runs (
11
11
  pipeline_dag_json TEXT,
12
12
  git_sha TEXT,
13
13
  ci_pr_url TEXT,
14
- ci_job_url TEXT
14
+ ci_job_url TEXT,
15
+ pipeline_path TEXT,
16
+ task_path TEXT,
17
+ project_root TEXT
15
18
  );
16
19
 
17
20
  CREATE TABLE IF NOT EXISTS stages (
@@ -0,0 +1,4 @@
1
+ import type { RunPipelineDagSnapshot } from "./port.js";
2
+ export declare function mintCloneInstanceId(catalogId: string, n: number): string;
3
+ export declare function mintCloneInstanceIds(catalogId: string, count: number): string[];
4
+ export declare function definitionIdForInstance(snapshot: RunPipelineDagSnapshot | null | undefined, instanceId: string): string;
@@ -0,0 +1,23 @@
1
+ import { guardStageId } from "./workspaceLayout.js";
2
+ function guardPositiveInteger(n, label) {
3
+ if (!Number.isInteger(n) || n < 1) {
4
+ throw new Error(`${label} must be a positive integer`);
5
+ }
6
+ }
7
+ export function mintCloneInstanceId(catalogId, n) {
8
+ guardPositiveInteger(n, "n");
9
+ guardStageId(catalogId);
10
+ const instanceId = `${catalogId}~${n}`;
11
+ guardStageId(instanceId);
12
+ return instanceId;
13
+ }
14
+ export function mintCloneInstanceIds(catalogId, count) {
15
+ guardPositiveInteger(count, "count");
16
+ return Array.from({ length: count }, (_, i) => mintCloneInstanceId(catalogId, i + 1));
17
+ }
18
+ export function definitionIdForInstance(snapshot, instanceId) {
19
+ const node = snapshot?.nodes.find((n) => n.id === instanceId);
20
+ if (!node)
21
+ return instanceId;
22
+ return node.definition_id ?? node.id;
23
+ }
@@ -1,2 +1,2 @@
1
1
  import type { StageSnapshot } from "./port.js";
2
- export declare function syntheticPendingSnapshot(stageId: string): StageSnapshot;
2
+ export declare function syntheticPendingSnapshot(stageId: string, definitionId?: string): StageSnapshot;
@@ -1,4 +1,4 @@
1
- export function syntheticPendingSnapshot(stageId) {
1
+ export function syntheticPendingSnapshot(stageId, definitionId) {
2
2
  return {
3
3
  stage_id: stageId,
4
4
  status: "pending",
@@ -6,5 +6,6 @@ export function syntheticPendingSnapshot(stageId) {
6
6
  envelope: null,
7
7
  artifacts: [],
8
8
  attempt_count: 1,
9
+ ...(definitionId !== undefined ? { definition_id: definitionId } : {}),
9
10
  };
10
11
  }
@@ -1,10 +1,14 @@
1
- import { linearCompatDagSnapshot } from "./pipelineDagSnapshot.js";
1
+ import { instancesOfDefinition, linearCompatDagSnapshot, } from "./pipelineDagSnapshot.js";
2
2
  function stageStarted(stage) {
3
3
  return stage.events.some((e) => e.event === "started" || e.event === "resumed");
4
4
  }
5
5
  function runHalted(stages) {
6
6
  return stages.some((s) => s.status === "failed");
7
7
  }
8
+ function predecessorIds(dag, needs) {
9
+ const preds = instancesOfDefinition(dag, needs);
10
+ return preds.length > 0 ? preds : [needs];
11
+ }
8
12
  function computeLayers(dag) {
9
13
  const layers = new Map();
10
14
  const nodeById = new Map(dag.nodes.map((n) => [n.id, n]));
@@ -17,7 +21,8 @@ function computeLayers(dag) {
17
21
  layers.set(id, 0);
18
22
  return 0;
19
23
  }
20
- const l = layerFor(node.needs) + 1;
24
+ const fromIds = predecessorIds(dag, node.needs);
25
+ const l = Math.max(...fromIds.map(layerFor)) + 1;
21
26
  layers.set(id, l);
22
27
  return l;
23
28
  }
@@ -32,6 +37,8 @@ function deriveReadiness(stage, dag, snapshotsById, halted) {
32
37
  return { readiness: "succeeded" };
33
38
  if (status === "failed")
34
39
  return { readiness: "failed" };
40
+ if (status === "skipped")
41
+ return { readiness: "skipped" };
35
42
  if (status === "running")
36
43
  return { readiness: "running" };
37
44
  if (status === "waiting_for_input")
@@ -42,9 +49,13 @@ function deriveReadiness(stage, dag, snapshotsById, halted) {
42
49
  const node = dag.nodes.find((n) => n.id === stage.stage_id);
43
50
  const need = node?.needs;
44
51
  if (need) {
45
- const predecessor = snapshotsById.get(need);
46
- if (!predecessor || predecessor.status !== "succeeded") {
47
- return { readiness: "blocked", blocked_by: [need] };
52
+ const fromIds = predecessorIds(dag, need);
53
+ const unresolved = fromIds.filter((id) => {
54
+ const predecessor = snapshotsById.get(id);
55
+ return !predecessor || predecessor.status !== "succeeded";
56
+ });
57
+ if (unresolved.length > 0) {
58
+ return { readiness: "blocked", blocked_by: unresolved };
48
59
  }
49
60
  }
50
61
  return { readiness: "ready" };
@@ -63,6 +74,7 @@ export function buildPipelineTrack(input) {
63
74
  }
64
75
  const node = dag.nodes.find((n) => n.id === stageId);
65
76
  const { readiness, blocked_by } = deriveReadiness(stage, dag, snapshotsById, halted);
77
+ const definitionId = node?.definition_id ?? stage.definition_id ?? stageId;
66
78
  const trackNode = {
67
79
  stage_id: stageId,
68
80
  status: stage.status,
@@ -70,10 +82,11 @@ export function buildPipelineTrack(input) {
70
82
  layer: layers.get(stageId) ?? 0,
71
83
  layer_order: node?.stageIndex ?? dag.stage_ids.indexOf(stageId),
72
84
  attempt_count: stage.attempt_count,
85
+ definition_id: definitionId,
73
86
  };
74
87
  if (blocked_by?.length)
75
88
  trackNode.blocked_by = blocked_by;
76
- const gateKinds = dag.gate_kinds?.[stageId];
89
+ const gateKinds = dag.gate_kinds?.[definitionId] ?? dag.gate_kinds?.[stageId];
77
90
  if (gateKinds?.length)
78
91
  trackNode.gate_kinds = gateKinds;
79
92
  return trackNode;
@@ -82,13 +95,16 @@ export function buildPipelineTrack(input) {
82
95
  for (const node of dag.nodes) {
83
96
  if (!node.needs)
84
97
  continue;
85
- const fromStage = snapshotsById.get(node.needs);
86
- const edge = { from: node.needs, to: node.id };
87
- if (fromStage?.status === "succeeded" &&
88
- fromStage.envelope?.summary) {
89
- edge.envelope_summary = fromStage.envelope.summary;
98
+ const fromIds = predecessorIds(dag, node.needs);
99
+ for (const fromId of fromIds) {
100
+ const fromStage = snapshotsById.get(fromId);
101
+ const edge = { from: fromId, to: node.id };
102
+ if (fromStage?.status === "succeeded" &&
103
+ fromStage.envelope?.summary) {
104
+ edge.envelope_summary = fromStage.envelope.summary;
105
+ }
106
+ edges.push(edge);
90
107
  }
91
- edges.push(edge);
92
108
  }
93
109
  return { nodes, edges };
94
110
  }
@@ -1,4 +1,5 @@
1
1
  export { stageDir } from "./paths.js";
2
+ export declare function guardStageId(stageId: string): void;
2
3
  export declare function attemptWorkspaceDir(workspaceDir: string, stageId: string, attempt: number): string;
3
4
  export declare function attemptLogPath(workspaceDir: string, stageId: string, attempt: number): string;
4
5
  export declare function attemptEnvelopePath(workspaceDir: string, stageId: string, attempt: number): string;
@@ -1,7 +1,7 @@
1
1
  import { access, lstat, mkdir, readdir, realpath } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  export { stageDir } from "./paths.js";
4
- function guardStageId(stageId) {
4
+ export function guardStageId(stageId) {
5
5
  if (stageId.includes("..") ||
6
6
  stageId.includes("/") ||
7
7
  stageId.includes("\\")) {
@@ -0,0 +1,18 @@
1
+ import type { RunPipelineDagSnapshot } from "../runstore/port.js";
2
+ import type { StageEnvelope } from "../types/envelope.js";
3
+ import type { ResolvedPipelineDag } from "../types/pipeline.js";
4
+ import type { StageScheduleState } from "./pipelineScheduler.js";
5
+ export declare function isCloneInstance(dag: ResolvedPipelineDag, stageId: string): boolean;
6
+ export declare function sequentialLaterCloneIds(dag: ResolvedPipelineDag, stageId: string, completedEnvelopes: Map<string, StageEnvelope>): string[];
7
+ export declare function cloneFanoutConflict(dag: ResolvedPipelineDag, envelope: StageEnvelope): string | undefined;
8
+ export declare function protectedClonableChildIds(dag: ResolvedPipelineDag, predecessorId: string, envelope: StageEnvelope): Set<string>;
9
+ export declare function cloneRetryDownstream(dag: ResolvedPipelineDag, stageIds: string[]): Set<string>;
10
+ export declare function cloneFailureContinuesSchedule(dag: ResolvedPipelineDag, stageId: string, completedEnvelopes: Map<string, StageEnvelope>): boolean;
11
+ export declare function cloneFailFastSkipIds(dag: ResolvedPipelineDag, stageId: string, completedEnvelopes: Map<string, StageEnvelope>): string[];
12
+ export declare function cloneScheduleAllowsRun(dag: ResolvedPipelineDag, stageId: string, states: Map<string, StageScheduleState>, completedEnvelopes: Map<string, StageEnvelope>): boolean;
13
+ export type ApplyCloneForksResult = {
14
+ dag: RunPipelineDagSnapshot;
15
+ skippedIds: string[];
16
+ };
17
+ export declare function applyCloneForksToSchedule(dag: ResolvedPipelineDag, predecessorId: string, envelope: StageEnvelope, states: Map<string, StageScheduleState>): ApplyCloneForksResult;
18
+ export declare function applyCloneForksFromEnvelopes(dag: ResolvedPipelineDag, states: Map<string, StageScheduleState>, completedEnvelopes: Map<string, StageEnvelope>): ApplyCloneForksResult;
@@ -0,0 +1,233 @@
1
+ import { appendCloneInstances, instancesOfDefinition, } from "../runstore/pipelineDagSnapshot.js";
2
+ import { definitionIdForInstance } from "../runstore/stageInstanceId.js";
3
+ import { collectDownstreamClosure, collectDownstreamStageIds } from "./dagTraversal.js";
4
+ function asDagSnapshot(dag) {
5
+ const snapshot = dag;
6
+ if (Array.isArray(snapshot.stage_ids))
7
+ return snapshot;
8
+ return {
9
+ ...dag,
10
+ stage_ids: dag.nodes.map((n) => n.id),
11
+ };
12
+ }
13
+ function definitionInstances(dag, catalogId) {
14
+ const snapshot = dag;
15
+ if (Array.isArray(snapshot.stage_ids)) {
16
+ return instancesOfDefinition(snapshot, catalogId);
17
+ }
18
+ return dag.nodes.some((n) => n.id === catalogId) ? [catalogId] : [];
19
+ }
20
+ export function isCloneInstance(dag, stageId) {
21
+ return definitionIdForInstance(asDagSnapshot(dag), stageId) !== stageId;
22
+ }
23
+ function isParallelCloneInstance(dag, stageId, completedEnvelopes) {
24
+ if (!isCloneInstance(dag, stageId))
25
+ return false;
26
+ const defId = definitionIdForInstance(asDagSnapshot(dag), stageId);
27
+ for (const envelope of completedEnvelopes.values()) {
28
+ for (const item of envelope.clone_forks ?? []) {
29
+ if (item.action === "fanout" &&
30
+ item.mode === "parallel" &&
31
+ item.successor_id === defId &&
32
+ definitionInstances(dag, defId).includes(stageId)) {
33
+ return true;
34
+ }
35
+ }
36
+ }
37
+ return false;
38
+ }
39
+ function isSequentialFanoutSuccessor(completedEnvelopes, successorId) {
40
+ for (const envelope of completedEnvelopes.values()) {
41
+ for (const item of envelope.clone_forks ?? []) {
42
+ if (item.action === "fanout" &&
43
+ item.mode === "sequential" &&
44
+ item.successor_id === successorId) {
45
+ return true;
46
+ }
47
+ }
48
+ }
49
+ return false;
50
+ }
51
+ function isSequentialCloneInstance(dag, stageId, completedEnvelopes) {
52
+ if (!isCloneInstance(dag, stageId))
53
+ return false;
54
+ const defId = definitionIdForInstance(asDagSnapshot(dag), stageId);
55
+ if (!isSequentialFanoutSuccessor(completedEnvelopes, defId))
56
+ return false;
57
+ return definitionInstances(dag, defId).includes(stageId);
58
+ }
59
+ function sequentialInstanceList(dag, stageId, completedEnvelopes) {
60
+ for (const envelope of completedEnvelopes.values()) {
61
+ for (const item of envelope.clone_forks ?? []) {
62
+ if (item.action !== "fanout" || item.mode !== "sequential")
63
+ continue;
64
+ const instances = definitionInstances(dag, item.successor_id);
65
+ if (instances.includes(stageId))
66
+ return instances;
67
+ }
68
+ }
69
+ return undefined;
70
+ }
71
+ function sequentialPreviousUnsatisfied(dag, stageId, states, completedEnvelopes) {
72
+ const instances = sequentialInstanceList(dag, stageId, completedEnvelopes);
73
+ if (instances === undefined)
74
+ return false;
75
+ const i = instances.indexOf(stageId);
76
+ if (i <= 0)
77
+ return false;
78
+ return states.get(instances[i - 1]) !== "succeeded";
79
+ }
80
+ export function sequentialLaterCloneIds(dag, stageId, completedEnvelopes) {
81
+ const instances = sequentialInstanceList(dag, stageId, completedEnvelopes);
82
+ if (instances === undefined)
83
+ return [];
84
+ const i = instances.indexOf(stageId);
85
+ if (i === -1)
86
+ return [];
87
+ return instances.slice(i + 1);
88
+ }
89
+ function joinSuccessorIds(dag, successorId) {
90
+ return dag.nodes.filter((n) => n.needs === successorId).map((n) => n.id);
91
+ }
92
+ function joinAndDownstreamIds(dag, cloneStageId) {
93
+ const defId = definitionIdForInstance(asDagSnapshot(dag), cloneStageId);
94
+ const ids = [];
95
+ for (const joinId of joinSuccessorIds(dag, defId)) {
96
+ ids.push(joinId);
97
+ for (const desc of collectDownstreamStageIds(dag, joinId)) {
98
+ ids.push(desc);
99
+ }
100
+ }
101
+ return ids;
102
+ }
103
+ export function cloneFanoutConflict(dag, envelope) {
104
+ const snapshot = asDagSnapshot(dag);
105
+ for (const item of envelope.clone_forks ?? []) {
106
+ if (item.action !== "fanout")
107
+ continue;
108
+ if (definitionInstances(dag, item.successor_id).some((id) => id !== item.successor_id)) {
109
+ return `clone fan-out for "${item.successor_id}" is already instanced`;
110
+ }
111
+ if (!snapshot.stage_ids.includes(item.successor_id)) {
112
+ return `clone fan-out for "${item.successor_id}" is already instanced`;
113
+ }
114
+ }
115
+ return undefined;
116
+ }
117
+ export function protectedClonableChildIds(dag, predecessorId, envelope) {
118
+ const ids = new Set();
119
+ for (const childId of dag.childrenOf[predecessorId] ?? []) {
120
+ const child = dag.nodes.find((n) => n.id === childId);
121
+ if (child?.clonable === true)
122
+ ids.add(childId);
123
+ }
124
+ for (const item of envelope.clone_forks ?? []) {
125
+ ids.add(item.successor_id);
126
+ }
127
+ return ids;
128
+ }
129
+ export function cloneRetryDownstream(dag, stageIds) {
130
+ const downstream = collectDownstreamClosure(dag, stageIds);
131
+ for (const id of stageIds) {
132
+ const defId = definitionIdForInstance(asDagSnapshot(dag), id);
133
+ for (const child of dag.childrenOf[defId] ?? []) {
134
+ downstream.add(child);
135
+ for (const desc of collectDownstreamStageIds(dag, child)) {
136
+ downstream.add(desc);
137
+ }
138
+ }
139
+ }
140
+ return downstream;
141
+ }
142
+ export function cloneFailureContinuesSchedule(dag, stageId, completedEnvelopes) {
143
+ return (isParallelCloneInstance(dag, stageId, completedEnvelopes) ||
144
+ isSequentialCloneInstance(dag, stageId, completedEnvelopes));
145
+ }
146
+ export function cloneFailFastSkipIds(dag, stageId, completedEnvelopes) {
147
+ if (isParallelCloneInstance(dag, stageId, completedEnvelopes)) {
148
+ return joinAndDownstreamIds(dag, stageId);
149
+ }
150
+ if (!isSequentialCloneInstance(dag, stageId, completedEnvelopes))
151
+ return [];
152
+ const instances = sequentialInstanceList(dag, stageId, completedEnvelopes);
153
+ if (instances === undefined)
154
+ return [];
155
+ const idx = instances.indexOf(stageId);
156
+ const ids = [];
157
+ for (const id of instances.slice(idx + 1)) {
158
+ ids.push(id);
159
+ }
160
+ ids.push(...joinAndDownstreamIds(dag, stageId));
161
+ return ids;
162
+ }
163
+ export function cloneScheduleAllowsRun(dag, stageId, states, completedEnvelopes) {
164
+ const node = dag.nodes.find((n) => n.id === stageId);
165
+ if (!node)
166
+ return false;
167
+ if (definitionInstances(dag, stageId).some((id) => id !== stageId)) {
168
+ return false;
169
+ }
170
+ if (node.needs === null) {
171
+ return !sequentialPreviousUnsatisfied(dag, stageId, states, completedEnvelopes);
172
+ }
173
+ const instances = definitionInstances(dag, node.needs);
174
+ if (instances.length <= 1) {
175
+ const parentId = instances[0] ?? node.needs;
176
+ if (states.get(parentId) !== "succeeded")
177
+ return false;
178
+ }
179
+ else if (!instances.every((id) => states.get(id) === "succeeded")) {
180
+ return false;
181
+ }
182
+ return !sequentialPreviousUnsatisfied(dag, stageId, states, completedEnvelopes);
183
+ }
184
+ export function applyCloneForksToSchedule(dag, predecessorId, envelope, states) {
185
+ let current = asDagSnapshot(dag);
186
+ const skippedIds = [];
187
+ const skipPending = (stageId) => {
188
+ if (states.get(stageId) === "pending") {
189
+ states.set(stageId, "skipped");
190
+ skippedIds.push(stageId);
191
+ }
192
+ };
193
+ for (const item of envelope.clone_forks ?? []) {
194
+ if (item.action === "skip") {
195
+ skipPending(item.successor_id);
196
+ for (const desc of collectDownstreamStageIds(current, item.successor_id)) {
197
+ skipPending(desc);
198
+ }
199
+ continue;
200
+ }
201
+ if (item.action !== "fanout")
202
+ continue;
203
+ const existing = definitionInstances(current, item.successor_id);
204
+ if (existing.some((id) => id !== item.successor_id)) {
205
+ continue;
206
+ }
207
+ const { snapshot, instanceIds } = appendCloneInstances(current, {
208
+ catalogId: item.successor_id,
209
+ predecessorId,
210
+ count: item.clones.length,
211
+ });
212
+ current = snapshot;
213
+ states.delete(item.successor_id);
214
+ for (const id of instanceIds) {
215
+ states.set(id, "pending");
216
+ }
217
+ }
218
+ return { dag: current, skippedIds };
219
+ }
220
+ export function applyCloneForksFromEnvelopes(dag, states, completedEnvelopes) {
221
+ let current = asDagSnapshot(dag);
222
+ const skippedIds = [];
223
+ const predecessors = [...current.nodes];
224
+ for (const node of predecessors) {
225
+ const envelope = completedEnvelopes.get(node.id);
226
+ if (!envelope?.clone_forks?.length)
227
+ continue;
228
+ const applied = applyCloneForksToSchedule(current, node.id, envelope, states);
229
+ current = applied.dag;
230
+ skippedIds.push(...applied.skippedIds);
231
+ }
232
+ return { dag: current, skippedIds };
233
+ }
@@ -1,5 +1,6 @@
1
- import { parseCredentialSource, readCredentialSourceFromFile, writeCredentialSourceToFile, type CredentialSource } from "./settingsFile.js";
2
- export { parseCredentialSource, readCredentialSourceFromFile, writeCredentialSourceToFile, };
1
+ import { type ProjectContext } from "../project/resolveProjectContext.js";
2
+ import { parseCredentialSource, readCredentialSourceFromContext, writeCredentialSourceToContext, type CredentialSource } from "./settingsFile.js";
3
+ export { parseCredentialSource, readCredentialSourceFromContext, writeCredentialSourceToContext, };
3
4
  export type CredentialBinding = {
4
5
  source: CredentialSource;
5
6
  authPath: string;
@@ -8,9 +9,10 @@ export type CredentialBinding = {
8
9
  export type ResolveCredentialBindingOptions = {
9
10
  piHomeAuthPath?: string;
10
11
  };
11
- export declare function sfOwnedAgentDir(cwd: string): string;
12
- export declare function sfOwnedAuthPath(cwd: string): string;
12
+ export declare function sfOwnedAgentDir(): string;
13
+ export declare function sfOwnedAuthPath(): string;
13
14
  export declare function piHomeAuthPath(override?: string): string;
14
15
  export declare function isUsableAuthFile(authPath: string): boolean;
15
- export declare function ensureSfOwnedAuthStore(cwd: string): string;
16
- export declare function resolveCredentialBinding(cwd: string, options?: ResolveCredentialBindingOptions): CredentialBinding;
16
+ export declare function ensureSfOwnedAuthStore(): string;
17
+ export declare function resolveCredentialBinding(ctx: ProjectContext | string, options?: ResolveCredentialBindingOptions): CredentialBinding;
18
+ export { readCredentialSourceFromFile, writeCredentialSourceToFile, } from "./settingsFile.js";